Skip to content

Commit

Permalink
Merge pull request #63 from Trick-17/feature-sources-patterns
Browse files Browse the repository at this point in the history
- Added source exclude patterns
- Turned sources into patterns
- By default, only needed targets are built
- Targets to be built can be chosen with the -t flag
- Rebuild can be forced with the -f flag
- A dotfile of the dependency graph is written (if pydot is installed)
- Added -Wextra and -Wpedantic to default flags
- Fixed some minor bugs, added some tests
- Improved log messages
  • Loading branch information
GPMueller authored Sep 24, 2018
2 parents 1bf1cb7 + 422ebbb commit 013871d
Show file tree
Hide file tree
Showing 21 changed files with 629 additions and 296 deletions.
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -167,17 +167,17 @@ Note:

- external projects will be copied/downloaded into "build/targetname/external_sources"
- you can specify a subdirectory, if the thirdparty code has an unusual structure
- further granularity is given by ``include_directories`` and ``source_directories``
- further granularity is given by ``include_directories`` and ``sources``
- `sources`, `headers_exclude` and `sources_exclude` expect a list of globbing patterns or files (not folders!)

.. code:: toml
[mylib]
url = "/~https://github.com/trick-17/mylib"
version = 1.1 # will try to `git checkout 1.1`
directory = "sources" # will point to "build/mylib/external_sources/sources"
[mylib.sources]
include_directories = ["mylib/include"] # will point to "build/mylib/external_sources/sources/mylib/include"
source_directories = ["mylib/src"] # will point to "build/mylib/external_sources/sources/mylib/src"
sources = ["mylib/src/*"] # will list everything inside "build/mylib/external_sources/sources/mylib/src"
# Maybe we need to deactivate annoying warnings coming from the library
[mylib.flags]
compile = ["-Wno-deprecated-declarations", "-Wno-self-assign"]
Expand Down
135 changes: 92 additions & 43 deletions clang_build/clang_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
SharedLibrary as _SharedLibrary,\
StaticLibrary as _StaticLibrary,\
HeaderOnly as _HeaderOnly
from .dependency_tools import find_circular_dependencies as _find_circular_dependencies,\
find_non_existent_dependencies as _find_non_existent_dependencies,\
from .dependency_tools import find_non_existent_dependencies as _find_non_existent_dependencies,\
get_dependency_walk as _get_dependency_walk
from .io_tools import get_sources_and_headers as _get_sources_and_headers
from .progress_bar import CategoryProgress as _CategoryProgress,\
Expand Down Expand Up @@ -70,18 +69,42 @@ def parse_args(args):
parser.add_argument('-V', '--verbose',
help='activate more detailed output',
action='store_true')
parser.add_argument('-p', '--progress', help='activates a progress bar output. is overruled by -V and --debug', action='store_true')
parser.add_argument('-d', '--directory', type=_Path,
parser.add_argument('-p', '--progress',
help='activates a progress bar output',
action='store_true')
parser.add_argument('-d', '--directory',
type=_Path,
help='set the root source directory')
parser.add_argument('-b', '--build-type', choices=list(_BuildType), type=_BuildType, default=_BuildType.Default,
parser.add_argument('-b', '--build-type',
choices=list(_BuildType),
type=_BuildType,
default=_BuildType.Default,
help='set the build type for this project')
parser.add_argument('-j', '--jobs', type=int, default=1,
parser.add_argument('-a', '--all',
help='build every target, irrespective of whether any root target depends on it',
action='store_true')
parser.add_argument('-t', '--targets',
type=str,
default="",
help='only these targets and their dependencies should be built (comma-separated list)')
parser.add_argument('-f', '--force-rebuild',
help='whether the targets should be rebuilt',
action='store_true')
parser.add_argument('-j', '--jobs',
type=int,
default=1,
help='set the number of concurrent build jobs')
parser.add_argument('--debug', help='activates additional debug output, overrides verbosity option.', action='store_true')
parser.add_argument('--debug',
help='activates additional debug output, overrides verbosity option.',
action='store_true')
parser.add_argument('--no-graph',
help='deactivates output of a dependency graph dotfile',
action='store_true')
return parser.parse_args(args=args)


def _find_clang(logger):
clang = _which('clang')
clangpp = _which('clang++')
clang_ar = _which('llvm-ar')
if clangpp:
Expand All @@ -94,71 +117,82 @@ def _find_clang(logger):
error_message = 'Couldn\'t find llvm-ar executable'
logger.error(error_message)
raise RuntimeError(error_message)
if not clang:
error_message = 'Couldn\'t find clang executable'
logger.error(error_message)
raise RuntimeError(error_message)

logger.info(f'llvm root directory: {llvm_root}')
logger.info(f'clang++ executable: {clangpp}')
logger.info(f'llvm-ar executable: {clang_ar}')
logger.info(f'clang executable: {clang}')
logger.info(f'clang++ executable: {clangpp}')
logger.info(f'llvm-ar executable: {clang_ar}')
logger.info(f'Newest supported C++ dialect: {_get_max_supported_compiler_dialect(clangpp)}')

return clangpp, clang_ar
return clang, clangpp, clang_ar



class _Environment:
def __init__(self, args):
# Some defaults
self.logger = None
self.progress_disabled = True
self.buildType = None
self.clangpp = "clang++"
self.clang_ar = "llvm-ar"
self.logger = None
self.buildType = None
self.clang = "clang"
self.clangpp = "clang++"
self.clang_ar = "llvm-ar"
# Directory this was called from
self.calling_directory = _Path().resolve()
# Working directory is where the project root should be - this is searched for 'clang-build.toml'
self.working_directory = self.calling_directory

# Verbosity
if not args.debug:
if args.verbose:
_setup_logger(_logging.INFO)
else:
# Only file log
_setup_logger(None)
else:
_setup_logger(_logging.DEBUG)

# Progress bar
if args.progress:
self.progress_disabled = False

self.logger = _logging.getLogger(__name__)
self.logger.info(f'clang-build {__version__}')

# Check for clang++ executable
self.clangpp, self.clang_ar = _find_clang(self.logger)
self.clang, self.clangpp, self.clang_ar = _find_clang(self.logger)

# Working directory
if args.directory:
self.working_directory = args.directory.resolve()

if not self.working_directory.exists():
error_message = f'ERROR: specified non-existent directory [{self.working_directory}]'
error_message = f'ERROR: specified non-existent directory \'{self.working_directory}\''
self.logger.error(error_message)
raise RuntimeError(error_message)

self.logger.info(f'Working directory: {self.working_directory}')
self.logger.info(f'Working directory: \'{self.working_directory}\'')

# Build type (Default, Release, Debug)
self.buildType = args.build_type
self.logger.info(f'Build type: {self.buildType.name}')

# Whether to build all targets
self.build_all = True if args.all else False

# List of targets which should be built
self.target_list = []
if args.targets:
if args.all:
error_message = f'ERROR: specified target list \'{args.targets}\', but also flag \'--all\''
self.logger.error(error_message)
raise RuntimeError(error_message)
self.target_list = [str(target) for target in args.targets.split(',')]

# Whether to force a rebuild
self.force_rebuild = True if args.force_rebuild else False

# Multiprocessing pool
self.processpool = _Pool(processes = args.jobs)
self.logger.info(f'Running up to {args.jobs} concurrent build jobs')

# Build directory
self.build_directory = _Path('build')

# Progress bar
self.progress_disabled = False if args.progress else True

# Whether to create a dotfile for graphing dependencies
self.create_dependency_dotfile = False if args.no_graph else True


def build(args):
Expand All @@ -173,7 +207,7 @@ def build(args):
# Check for build configuration toml file
toml_file = _Path(environment.working_directory, 'clang-build.toml')
if toml_file.exists():
logger.info('Found config file')
logger.info(f'Found config file: \'{toml_file}\'')

# Parse config file
config = toml.load(str(toml_file))
Expand All @@ -187,10 +221,10 @@ def build(args):
multiple_projects = True

# Create root project
project = _Project(config, environment, multiple_projects)
root_project = _Project(config, environment, multiple_projects, True)

# Get list of all targets
target_list += project.get_targets()
target_list += root_project.get_targets(root_project.target_dont_build_list)

# # Generate list of all targets
# for project in working_projects:
Expand All @@ -201,19 +235,21 @@ def build(args):
files = _get_sources_and_headers({}, environment.working_directory, environment.build_directory)

if not files['sourcefiles']:
error_message = f'Error, no sources and no [clang-build.toml] found in folder: {environment.working_directory}'
error_message = f'Error, no sources and no \'clang-build.toml\' found in folder \'{environment.working_directory}\''
logger.error(error_message)
raise RuntimeError(error_message)
# Create target
target_list.append(
_Executable(
'',
'main',
environment.working_directory,
environment.build_directory.joinpath(environment.buildType.name.lower()),
files['headers'],
files['include_directories'],
files['sourcefiles'],
environment.buildType,
environment.clang,
environment.clangpp))

# Build the targets
Expand All @@ -236,7 +272,7 @@ def build(args):
for target in target_list:
if target.__class__ is not _HeaderOnly:
if target.unsuccessful_builds:
errors[target.name] = [source.compile_report for source in target.unsuccessful_builds]
errors[target.full_name] = [source.compile_report for source in target.unsuccessful_builds]
if errors:
raise _CompileError('Compilation was unsuccessful', errors)

Expand All @@ -251,7 +287,7 @@ def build(args):
for target in target_list:
if target.__class__ is not _HeaderOnly:
if target.unsuccessful_link:
errors[target.name] = target.link_report
errors[target.full_name] = target.link_report
if errors:
raise _LinkError('Linking was unsuccessful', errors)

Expand All @@ -260,26 +296,39 @@ def build(args):



def main():
def _main():
# Build
try:
build(parse_args(sys.argv[1:]))
args = parse_args(sys.argv[1:])

# Logger verbosity
if not args.debug:
if args.verbose:
_setup_logger(_logging.INFO)
else:
# Only file log
_setup_logger(None)
else:
_setup_logger(_logging.DEBUG)

build(args)

except _CompileError as compile_error:
logger = _logging.getLogger(__name__)
logger.error('Compilation was unsuccessful:')
for target, errors in compile_error.error_dict.items():
printout = f'Target [{target}] did not compile. Errors:\n'
printout = f'[{target}]: target did not compile. Errors:\n'
printout += ' '.join(errors)
logger.error(printout)
except _LinkError as link_error:
logger = _logging.getLogger(__name__)
logger.error('Linking was unsuccessful:')
for target, errors in link_error.error_dict.items():
printout = f'Target [{target}] did not link. Errors:\n{errors}'
printout = f'[{target}]: target did not link. Errors:\n{errors}'
logger.error(printout)



if __name__ == '__main__':
_freeze_support()
main()
_main()
17 changes: 16 additions & 1 deletion clang_build/dependency_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,19 @@ def get_dependency_walk(project):
subnames = str(dependency).split(".")
graph.add_edge(str(nodename), str(subnames[-1]))

return list(reversed(list(_nx.topological_sort(graph))))
return list(reversed(list(_nx.topological_sort(graph))))

def get_dependency_graph(project):
graph = _nx.DiGraph()
for nodename, node in project.items():
dependencies = node.get('dependencies', [])
if not dependencies:
graph.add_node(str(nodename))
continue

for dependency in dependencies:
# Split string at dots
subnames = str(dependency).split(".")
graph.add_edge(str(nodename), str(subnames[-1]))

return graph
Loading

0 comments on commit 013871d

Please sign in to comment.