def create_build_graph(compiler, linker): flags = BuildFlags().O(3).std(17).march("native").fpic() # Headers utils_h = SourceNode(PATHS["utils.hpp"]) factorial_h = SourceNode(PATHS["factorial.hpp"], [utils_h]) fibonacci_h = SourceNode(PATHS["fibonacci.hpp"], [utils_h]) # Source files factorial_cpp = SourceNode(PATHS["factorial.cpp"], [factorial_h], include_dirs=[ PATHS["include"], PATHS["src"], PATHS["test"], ROOT, TESTS_ROOT ]) fibonacci_cpp = SourceNode(PATHS["fibonacci.cpp"], [fibonacci_h], include_dirs=[ PATHS["include"], PATHS["src"], PATHS["test"], ROOT, TESTS_ROOT ]) test_cpp = SourceNode(PATHS["test.cpp"], [], include_dirs=[ PATHS["include"], PATHS["src"], PATHS["test"], ROOT, TESTS_ROOT ]) # Object files factorial_o = CompiledNode(os.path.join(PATHS["build"], "factorial.o"), input=factorial_cpp, compiler=compiler, flags=flags) fibonacci_o = CompiledNode(os.path.join(PATHS["build"], "fibonacci.o"), input=fibonacci_cpp, compiler=compiler, flags=flags) test_o = CompiledNode(os.path.join(PATHS["build"], "test.o"), input=test_cpp, compiler=compiler, flags=flags) # Library and executable libmath = LinkedNode(os.path.join(PATHS["build"], "libmath.so"), [factorial_o, fibonacci_o], linker=linker, hashed_path=os.path.join(PATHS["build"], "libmath.so"), flags=flags + BuildFlags()._enable_shared(), libs=["stdc++"]) test = LinkedNode(os.path.join(PATHS["build"], "test"), [test_o, libmath], linker=linker, hashed_path=os.path.join(PATHS["build"], "test"), flags=flags, libs=["stdc++", "math"], lib_dirs=[os.path.dirname(libmath.path)]) return Graph([ utils_h, factorial_h, fibonacci_h, factorial_cpp, fibonacci_cpp, test_cpp, factorial_o, fibonacci_o, test_o, libmath, test ])
def link_cmd(linker, input_paths, output_name, lib_dirs: List[str] = [], flags: BuildFlags = BuildFlags()): flags += BuildFlags().O(3).std(17).march("native").fpic() # Get output path output_path = os.path.join(PATHS["build"], output_name) # Generate the command needed return linker.link(input_paths, output_path, lib_dirs=lib_dirs, flags=flags), output_path
def compile_cmd(compiler, input_path: str, include_dirs: List[str] = [], flags: BuildFlags = BuildFlags()): flags += BuildFlags().O(3).std(17).march("native").fpic() include_dirs = include_dirs or [ PATHS["include"], PATHS["src"], PATHS["test"], ROOT, TESTS_ROOT ] # Get output path base = os.path.splitext(os.path.basename(input_path))[0] output_path = os.path.join(PATHS["build"], f"{base}.o") # Generate the command needed return compiler.compile(input_path, output_path, include_dirs, flags), output_path
def profile(self, name: str, flags: BuildFlags = BuildFlags(), build_dir: str = None, file_suffix: str = "") -> Profile: f""" Returns or creates a profile with the specified parameters. :param name: The name of this profile. :param flags: The flags to use for this profile. These will be applied to all targets for this profile. Per-target flags always take precedence. :param build_dir: The directory to use for build artifacts. Defaults to {os.path.join(self.build_dir, name)} :param file_suffix: A file suffix to attach to all artifacts generated for this profile. For example, the default debug profile attaches a ``_debug`` suffix to all library and executable names. :returns: :class:`sbuildr.Profile` """ if name not in self.profiles: build_dir = self.files.add_writable_dir( self.files.add_exclude_dir( os.path.abspath(build_dir or os.path.join(self.build_dir, name)))) G_LOGGER.verbose( f"Setting build directory for profile: {name} to: {build_dir}") self.profiles[name] = Profile(flags=flags, build_dir=build_dir, suffix=file_suffix) return self.profiles[name]
def compile(compiler, input_path: str, include_dirs: List[str] = [], flags: BuildFlags = BuildFlags()): cmd, output_path = compile_cmd(compiler, input_path, include_dirs, flags) print(f"Running command: {cmd}") subprocess.run(cmd) return output_path
def signature( self, input_paths: List[str], libs: List[str] = [], lib_dirs: List[str] = [], flags: BuildFlags = BuildFlags()) -> str: # Order of inputs does not matter, but order of libs does. sig = [self.ldef.executable()] + list(sorted( input_paths)) + self.ldef.parse_flags(flags) + libs + lib_dirs return utils.str_hash(sig)
def link(linker, input_paths, output_name, lib_dirs: List[str] = [], flags: BuildFlags = BuildFlags()): cmd, output_path = link_cmd(linker, input_paths, output_name, lib_dirs, flags) print(f"Running command: {cmd}") subprocess.run(cmd) return output_path
def __init__(self, path: str, input: SourceNode, compiler: compiler.Compiler, include_dirs: List[str] = [], flags: BuildFlags = BuildFlags()): super().__init__(path, [input]) self.compiler = compiler # All include directories required for this file. self.include_dirs = include_dirs self.flags = flags
def library( self, name: str, sources: List[str], flags: BuildFlags = BuildFlags(), libs: List[Union[DependencyLibrary, ProjectTarget, Library]] = [], compiler: compiler.Compiler = compiler.clang, include_dirs: List[str] = [], linker: linker.Linker = linker.clang, depends: List[Dependency] = [], internal=False, ) -> ProjectTarget: """ Adds a library target to all profiles within this project. :param name: The name of the target. This should NOT include platform-dependent extensions. :param sources: A list of names or paths of source files to include in this target. :param flags: Compiler and linker flags. See sbuildr.BuildFlags for details. :param libs: A list containing either :class:`ProjectTarget` s, :class:`DependencyLibrary` s or :class:`Library` s. :param compiler: The compiler to use for this target. Defaults to clang. :param include_dirs: A list of paths for preprocessor include directories. These directories take precedence over automatically deduced include directories. :param linker: The linker to use for this target. Defaults to clang. :param depends: Any additional dependencies not already captured in libs. This may include header only packages for example. :param internal: Whether this target is internal to the project, in which case it will not be installed. :returns: :class:`sbuildr.project.target.ProjectTarget` """ self.libraries[name] = self._target( name, paths.name_to_libname(name), sources, flags + BuildFlags()._enable_shared(), libs, compiler, include_dirs, linker, depends, internal, is_lib=True, ) return self.libraries[name]
def __init__(self, root: str = None, dirs: Set[str] = set(), build_dir: str = None): self.PROJECT_API_VERSION = Project.PROJECT_API_VERSION # The assumption is that the caller of the init function is the SBuildr file for the build. config_file = os.path.abspath(inspect.stack()[1][0].f_code.co_filename) # Keep track of all files present in project dirs. Since dirs is a set, files is guaranteed # to contain no duplicates as well. self.files = FileManager( root or os.path.abspath(os.path.dirname(config_file)), dirs) # The build directory will be writable, and excluded when the FileManager is searching for paths. self.build_dir = self.files.add_writable_dir( self.files.add_exclude_dir( build_dir or os.path.join(self.files.root_dir, "build"))) # TODO: Make this a parameter? self.common_build_dir = os.path.join(self.build_dir, "common") # Backend self.backend = None # Profiles consist of a graph of compiled/linked nodes. Each linked node is a # user-defined target for that profile. self.profiles: Dict[str, Profile] = {} # ProjectTargets combine linked nodes from one or more profiles for each user-defined target. # Each ProjectTarget maps profile names to their corresponding linked node for that target. self.executables: Dict[str, ProjectTarget] = {} self.tests: Dict[str, ProjectTarget] = {} self.libraries: Dict[str, ProjectTarget] = {} # Files installed by this project. self.public_headers: Set[str] = {} # Dependencies required for the public headers. self.public_header_dependencies: List[Dependency] = [] # Add default profiles self.profile(name="release", flags=BuildFlags().O(3).std(17).march("native").fpic()) self.profile( name="debug", flags=BuildFlags().O(0).std(17).debug().fpic().define("S_DEBUG"), file_suffix="_debug") # A graph describing the entire project. This is typically not constructed until just before the build self.graph: Graph = None
def __init__(self, path: str, inputs: List[Node], linker: linker.Linker, hashed_path: str, libs: List[str] = None, lib_dirs: List[str] = None, flags: BuildFlags = BuildFlags()): super().__init__(path=path, libs=libs, lib_dirs=lib_dirs) Node.__init__(self, path, inputs) self.hashed_path = hashed_path # The path including hash. self.path is a hard link of this path. self.linker = linker self.flags = flags
def test( self, name: str, sources: List[str], flags: BuildFlags = BuildFlags(), libs: List[Union[DependencyLibrary, ProjectTarget, Library]] = [], compiler: compiler.Compiler = compiler.clang, include_dirs: List[str] = [], linker: linker.Linker = linker.clang, depends: List[Dependency] = [], ) -> ProjectTarget: """ Adds an executable target to all profiles within this project. Test targets can be automatically built and run by using the ``test`` command on the CLI. :param name: The name of the target. This should NOT include platform-dependent extensions. :param sources: A list of names or paths of source files to include in this target. :param flags: Compiler and linker flags. See sbuildr.BuildFlags for details. :param libs: A list containing either :class:`ProjectTarget` s, :class:`DependencyLibrary` s or :class:`Library` s. :param compiler: The compiler to use for this target. Defaults to clang. :param include_dirs: A list of paths for preprocessor include directories. These directories take precedence over automatically deduced include directories. :param linker: The linker to use for this target. Defaults to clang. :param depends: Any additional dependencies not already captured in libs. This may include header only packages for example. :returns: :class:`sbuildr.project.target.ProjectTarget` """ self.tests[name] = self._target( name, paths.name_to_execname(name), sources, flags, libs, compiler, include_dirs, linker, depends, internal=True, is_lib=False, ) return self.tests[name]
def link( self, input_paths: List[str], output_path: str, libs: List[str] = [], lib_dirs: List[str] = [], flags: BuildFlags = BuildFlags()) -> List[str]: G_LOGGER.debug(f"self.ldef: {self.ldef}") linker_flags = self.ldef.parse_flags(flags) lib_dirs = [self.ldef.lib_dir(dir) for dir in lib_dirs] # In libs, absolute paths are not prepended with the lib prefix (e.g. -l) libs = [ lib if os.path.isabs(lib) else self.ldef.lib(lib) for lib in libs ] # The full command. cmd = [self.ldef.executable() ] + input_paths + libs + linker_flags + lib_dirs + [ self.ldef.output(output_path) ] G_LOGGER.debug( f"Linking: input_paths: {input_paths}, libs: {libs}, linker_flags: {linker_flags}, lib_dirs: {lib_dirs}" ) G_LOGGER.verbose(f"Link Command: {' '.join(cmd)}") return cmd
def build_libtest(compiler, linker): fibonacci = TestLinkers.compile(compiler, PATHS["fibonacci.cpp"]) factorial = TestLinkers.compile(compiler, PATHS["factorial.cpp"]) return TestLinkers.link(linker, [fibonacci, factorial, "-lstdc++"], "libtest.so", flags=BuildFlags()._enable_shared())
def compile(self, input_path: str, output_path: str, include_dirs: List[str]=[], flags: BuildFlags=BuildFlags()) -> List[str]: compiler_flags = self.cdef.parse_flags(flags) includes = [self.cdef.include(dir) for dir in include_dirs] # The full command, including the output file and the compile-only flag. cmd = [self.cdef.executable(), input_path] + compiler_flags + includes + [self.cdef.compile_only(), self.cdef.output(output_path)] G_LOGGER.verbose(f"Compile Command: {' '.join(cmd)}") return cmd
def signature(self, input_path: str, include_dirs: List[str]=[], flags: BuildFlags=BuildFlags()) -> str: sig = [self.cdef.executable()] + [input_path] + self.cdef.parse_flags(flags) + include_dirs return utils.str_hash(sig)