This is a very rudimentary version. All it does is copy and rename the template header file and place it in the build directory in a special subdirectory that is added as an #include-path. There is some code duplication and cleanup necessary. There are a few "magic strings" and "magic paths" that need to be removed as well.default_compile_flags
| @@ -153,6 +153,10 @@ prepare_ureqs(const build_plan& plan, const toolchain& toolchain, path_ref out_r | |||
| if (const auto& arc = lib.archive_plan()) { | |||
| lib_reqs.linkable_path = out_root / arc->calc_archive_file_path(toolchain); | |||
| } | |||
| if (lib.has_generated_headers()) { | |||
| lib_reqs.include_paths.push_back(out_root / "__dds/gen" | |||
| / lib.output_subdirectory()); | |||
| } | |||
| } | |||
| } | |||
| return ureqs; | |||
| @@ -225,6 +229,8 @@ void builder::build(const build_params& params) const { | |||
| generate_compdb(plan, env); | |||
| } | |||
| plan.render_all(env); | |||
| dds::stopwatch sw; | |||
| plan.compile_all(env, params.parallel_jobs); | |||
| spdlog::info("Compilation completed in {:n}ms", sw.elapsed_ms().count()); | |||
| @@ -74,6 +74,17 @@ bool parallel_run(Range&& rng, int n_jobs, Fn&& fn) { | |||
| } // namespace | |||
| void build_plan::render_all(build_env_ref env) const { | |||
| auto templates = _packages // | |||
| | ranges::view::transform(&package_plan::libraries) // | |||
| | ranges::view::join // | |||
| | ranges::view::transform(&library_plan::templates) // | |||
| | ranges::view::join; | |||
| for (const render_template_plan& tmpl : templates) { | |||
| tmpl.render(env); | |||
| } | |||
| } | |||
| void build_plan::compile_all(const build_env& env, int njobs) const { | |||
| auto okay = dds::compile_all(iter_compilations(*this), env, njobs); | |||
| if (!okay) { | |||
| @@ -28,6 +28,10 @@ public: | |||
| * All of the packages in this plan | |||
| */ | |||
| auto& packages() const noexcept { return _packages; } | |||
| /** | |||
| * Render all config templates in the plan. | |||
| */ | |||
| void render_all(const build_env& env) const; | |||
| /** | |||
| * Compile all files in the plan. | |||
| */ | |||
| @@ -17,6 +17,7 @@ library_plan library_plan::create(const library_root& lib, | |||
| std::vector<source_file> app_sources; | |||
| std::vector<source_file> test_sources; | |||
| std::vector<source_file> lib_sources; | |||
| std::vector<source_file> template_sources; | |||
| auto qual_name = std::string(qual_name_.value_or(lib.manifest().name)); | |||
| @@ -34,6 +35,8 @@ library_plan library_plan::create(const library_root& lib, | |||
| app_sources.push_back(sfile); | |||
| } else if (sfile.kind == source_kind::source) { | |||
| lib_sources.push_back(sfile); | |||
| } else if (sfile.kind == source_kind::header_template) { | |||
| template_sources.push_back(sfile); | |||
| } else { | |||
| assert(sfile.kind == source_kind::header); | |||
| } | |||
| @@ -45,6 +48,10 @@ library_plan library_plan::create(const library_root& lib, | |||
| compile_rules.enable_warnings() = params.enable_warnings; | |||
| compile_rules.uses() = lib.manifest().uses; | |||
| if (!template_sources.empty()) { | |||
| compile_rules.include_dirs().push_back("__dds/gen" / params.out_subdir); | |||
| } | |||
| // Convert the library sources into their respective file compilation plans. | |||
| auto lib_compile_files = // | |||
| lib_sources // | |||
| @@ -55,12 +62,12 @@ library_plan library_plan::create(const library_root& lib, | |||
| // If we have any compiled library files, generate a static library archive | |||
| // for this library | |||
| std::optional<create_archive_plan> create_archive; | |||
| std::optional<create_archive_plan> archive_plan; | |||
| if (!lib_compile_files.empty()) { | |||
| create_archive.emplace(lib.manifest().name, | |||
| qual_name, | |||
| params.out_subdir, | |||
| std::move(lib_compile_files)); | |||
| archive_plan.emplace(lib.manifest().name, | |||
| qual_name, | |||
| params.out_subdir, | |||
| std::move(lib_compile_files)); | |||
| } | |||
| // Collect the paths to linker inputs that should be used when generating executables for this | |||
| @@ -104,6 +111,16 @@ library_plan library_plan::create(const library_root& lib, | |||
| link_executables.emplace_back(std::move(exe)); | |||
| } | |||
| std::vector<render_template_plan> render_templates; | |||
| for (const auto& sf : template_sources) { | |||
| render_templates.emplace_back(sf, "__dds/gen" / params.out_subdir); | |||
| } | |||
| // Done! | |||
| return library_plan{lib, qual_name, std::move(create_archive), std::move(link_executables)}; | |||
| return library_plan{lib, | |||
| qual_name, | |||
| params.out_subdir, | |||
| std::move(archive_plan), | |||
| std::move(link_executables), | |||
| std::move(render_templates)}; | |||
| } | |||
| @@ -2,6 +2,7 @@ | |||
| #include <dds/build/plan/archive.hpp> | |||
| #include <dds/build/plan/exe.hpp> | |||
| #include <dds/build/plan/template.hpp> | |||
| #include <dds/library/root.hpp> | |||
| #include <dds/usage_reqs.hpp> | |||
| #include <dds/util/fs.hpp> | |||
| @@ -56,10 +57,14 @@ class library_plan { | |||
| library_root _lib; | |||
| /// The qualified name of the library | |||
| std::string _qual_name; | |||
| /// The library's subdirectory within the output directory | |||
| fs::path _subdir; | |||
| /// The `create_archive_plan` for this library, if applicable | |||
| std::optional<create_archive_plan> _create_archive; | |||
| /// The executables that should be linked as part of this library's build | |||
| std::vector<link_executable_plan> _link_exes; | |||
| /// The templates that must be rendered for this library | |||
| std::vector<render_template_plan> _templates; | |||
| public: | |||
| /** | |||
| @@ -70,12 +75,16 @@ public: | |||
| */ | |||
| library_plan(library_root lib, | |||
| std::string_view qual_name, | |||
| fs::path subdir, | |||
| std::optional<create_archive_plan> ar, | |||
| std::vector<link_executable_plan> exes) | |||
| std::vector<link_executable_plan> exes, | |||
| std::vector<render_template_plan> tmpls) | |||
| : _lib(std::move(lib)) | |||
| , _qual_name(qual_name) | |||
| , _subdir(std::move(subdir)) | |||
| , _create_archive(std::move(ar)) | |||
| , _link_exes(std::move(exes)) {} | |||
| , _link_exes(std::move(exes)) | |||
| , _templates(std::move(tmpls)) {} | |||
| /** | |||
| * Get the underlying library object | |||
| @@ -89,6 +98,10 @@ public: | |||
| * Get the qualified name of the library, as if for a libman usage requirement | |||
| */ | |||
| auto& qualified_name() const noexcept { return _qual_name; } | |||
| /** | |||
| * The output subdirectory of this library plan | |||
| */ | |||
| path_ref output_subdirectory() const noexcept { return _subdir; } | |||
| /** | |||
| * The directory that defines the source root of the library. | |||
| */ | |||
| @@ -98,6 +111,10 @@ public: | |||
| * components | |||
| */ | |||
| auto& archive_plan() const noexcept { return _create_archive; } | |||
| /** | |||
| * The template rendering plans for this library. | |||
| */ | |||
| auto& templates() const noexcept { return _templates; } | |||
| /** | |||
| * The executables that should be created by this library | |||
| */ | |||
| @@ -110,6 +127,10 @@ public: | |||
| * The library identifiers that are linked by this library | |||
| */ | |||
| auto& links() const noexcept { return _lib.manifest().links; } | |||
| /** | |||
| * Return `true` if this object has generated header files | |||
| */ | |||
| bool has_generated_headers() const noexcept { return !templates().empty(); } | |||
| /** | |||
| * Named constructor: Create a new `library_plan` automatically from some build-time parameters. | |||
| @@ -129,4 +150,4 @@ public: | |||
| std::optional<std::string_view> qual_name); | |||
| }; | |||
| } // namespace dds | |||
| } // namespace dds | |||
| @@ -0,0 +1,16 @@ | |||
| #include <dds/build/plan/template.hpp> | |||
| #include <dds/util/fs.hpp> | |||
| using namespace dds; | |||
| void render_template_plan::render(build_env_ref env) const { | |||
| auto content = slurp_file(_source.path); | |||
| // Calculate the destination of the template rendering | |||
| auto dest = env.output_root / _subdir / _source.relative_path(); | |||
| dest.replace_filename(dest.stem().stem().filename().string() + dest.extension().string()); | |||
| fs::create_directories(dest.parent_path()); | |||
| fs::copy_file(_source.path, dest, fs::copy_options::overwrite_existing); | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| #pragma once | |||
| #include <dds/build/plan/base.hpp> | |||
| #include <dds/source/file.hpp> | |||
| #include <utility> | |||
| namespace dds { | |||
| class render_template_plan { | |||
| /** | |||
| * The source file that defines the config template | |||
| */ | |||
| source_file _source; | |||
| /** | |||
| * The subdirectory in which the template should be rendered. | |||
| */ | |||
| fs::path _subdir; | |||
| public: | |||
| /** | |||
| * Create a new instance | |||
| * @param sf The source file of the template | |||
| * @param subdir The subdirectort into which the template should render | |||
| */ | |||
| render_template_plan(source_file sf, path_ref subdir) | |||
| : _source(std::move(sf)) | |||
| , _subdir(subdir) {} | |||
| /** | |||
| * Render the template into its output directory | |||
| */ | |||
| void render(build_env_ref) const; | |||
| }; | |||
| } // namespace dds | |||
| @@ -0,0 +1,5 @@ | |||
| #pragma once | |||
| #include <string> | |||
| int config_file_value = 42; | |||
| @@ -0,0 +1,5 @@ | |||
| #include <info.hpp> | |||
| #include <cassert> | |||
| int main() { assert(config_file_value == 42); } | |||
| @@ -0,0 +1,9 @@ | |||
| import pytest | |||
| from tests import DDS, dds_fixture_conf_1 | |||
| @dds_fixture_conf_1('copy_only') | |||
| def test_config_template(dds: DDS): | |||
| dds.build() | |||
| assert (dds.build_dir / '__dds/gen/info.hpp').is_file() | |||
| @@ -52,11 +52,11 @@ class DDS: | |||
| full_cmd = itertools.chain([self.dds_exe], cmd) | |||
| return proc.run(full_cmd, cwd=cwd or self.source_root) | |||
| def run(self, cmd: proc.CommandLine, *, | |||
| cwd: Path = None) -> subprocess.CompletedProcess: | |||
| def run(self, cmd: proc.CommandLine, *, cwd: Path = None, | |||
| check=True) -> subprocess.CompletedProcess: | |||
| cmdline = list(proc.flatten_cmd(cmd)) | |||
| res = self.run_unchecked(cmd, cwd=cwd) | |||
| if res.returncode != 0: | |||
| if res.returncode != 0 and check: | |||
| raise subprocess.CalledProcessError( | |||
| res.returncode, [self.dds_exe] + cmdline, res.stdout) | |||
| return res | |||
| @@ -86,18 +86,22 @@ class DDS: | |||
| toolchain: str = None, | |||
| apps: bool = True, | |||
| warnings: bool = True, | |||
| tests: bool = True) -> subprocess.CompletedProcess: | |||
| return self.run([ | |||
| 'build', | |||
| f'--out={self.build_dir}', | |||
| f'--toolchain={toolchain or self.default_builtin_toolchain}', | |||
| f'--catalog={self.catalog_path}', | |||
| f'--repo-dir={self.repo_dir}', | |||
| ['--no-tests'] if not tests else [], | |||
| ['--no-apps'] if not apps else [], | |||
| ['--no-warnings'] if not warnings else [], | |||
| self.project_dir_arg, | |||
| ]) | |||
| tests: bool = True, | |||
| check: bool = True) -> subprocess.CompletedProcess: | |||
| return self.run( | |||
| [ | |||
| 'build', | |||
| f'--out={self.build_dir}', | |||
| f'--toolchain={toolchain or self.default_builtin_toolchain}', | |||
| f'--catalog={self.catalog_path}', | |||
| f'--repo-dir={self.repo_dir}', | |||
| ['--no-tests'] if not tests else [], | |||
| ['--no-apps'] if not apps else [], | |||
| ['--no-warnings'] if not warnings else [], | |||
| self.project_dir_arg, | |||
| ], | |||
| check=check, | |||
| ) | |||
| def sdist_create(self) -> subprocess.CompletedProcess: | |||
| return self.run([ | |||