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
if (const auto& arc = lib.archive_plan()) { | if (const auto& arc = lib.archive_plan()) { | ||||
lib_reqs.linkable_path = out_root / arc->calc_archive_file_path(toolchain); | 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; | return ureqs; | ||||
generate_compdb(plan, env); | generate_compdb(plan, env); | ||||
} | } | ||||
plan.render_all(env); | |||||
dds::stopwatch sw; | dds::stopwatch sw; | ||||
plan.compile_all(env, params.parallel_jobs); | plan.compile_all(env, params.parallel_jobs); | ||||
spdlog::info("Compilation completed in {:n}ms", sw.elapsed_ms().count()); | spdlog::info("Compilation completed in {:n}ms", sw.elapsed_ms().count()); |
} // namespace | } // 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 { | void build_plan::compile_all(const build_env& env, int njobs) const { | ||||
auto okay = dds::compile_all(iter_compilations(*this), env, njobs); | auto okay = dds::compile_all(iter_compilations(*this), env, njobs); | ||||
if (!okay) { | if (!okay) { |
* All of the packages in this plan | * All of the packages in this plan | ||||
*/ | */ | ||||
auto& packages() const noexcept { return _packages; } | 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. | * Compile all files in the plan. | ||||
*/ | */ |
std::vector<source_file> app_sources; | std::vector<source_file> app_sources; | ||||
std::vector<source_file> test_sources; | std::vector<source_file> test_sources; | ||||
std::vector<source_file> lib_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)); | auto qual_name = std::string(qual_name_.value_or(lib.manifest().name)); | ||||
app_sources.push_back(sfile); | app_sources.push_back(sfile); | ||||
} else if (sfile.kind == source_kind::source) { | } else if (sfile.kind == source_kind::source) { | ||||
lib_sources.push_back(sfile); | lib_sources.push_back(sfile); | ||||
} else if (sfile.kind == source_kind::header_template) { | |||||
template_sources.push_back(sfile); | |||||
} else { | } else { | ||||
assert(sfile.kind == source_kind::header); | assert(sfile.kind == source_kind::header); | ||||
} | } | ||||
compile_rules.enable_warnings() = params.enable_warnings; | compile_rules.enable_warnings() = params.enable_warnings; | ||||
compile_rules.uses() = lib.manifest().uses; | 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. | // Convert the library sources into their respective file compilation plans. | ||||
auto lib_compile_files = // | auto lib_compile_files = // | ||||
lib_sources // | lib_sources // | ||||
// If we have any compiled library files, generate a static library archive | // If we have any compiled library files, generate a static library archive | ||||
// for this library | // for this library | ||||
std::optional<create_archive_plan> create_archive; | |||||
std::optional<create_archive_plan> archive_plan; | |||||
if (!lib_compile_files.empty()) { | 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 | // Collect the paths to linker inputs that should be used when generating executables for this | ||||
link_executables.emplace_back(std::move(exe)); | 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! | // 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)}; | |||||
} | } |
#include <dds/build/plan/archive.hpp> | #include <dds/build/plan/archive.hpp> | ||||
#include <dds/build/plan/exe.hpp> | #include <dds/build/plan/exe.hpp> | ||||
#include <dds/build/plan/template.hpp> | |||||
#include <dds/library/root.hpp> | #include <dds/library/root.hpp> | ||||
#include <dds/usage_reqs.hpp> | #include <dds/usage_reqs.hpp> | ||||
#include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
library_root _lib; | library_root _lib; | ||||
/// The qualified name of the library | /// The qualified name of the library | ||||
std::string _qual_name; | std::string _qual_name; | ||||
/// The library's subdirectory within the output directory | |||||
fs::path _subdir; | |||||
/// The `create_archive_plan` for this library, if applicable | /// The `create_archive_plan` for this library, if applicable | ||||
std::optional<create_archive_plan> _create_archive; | std::optional<create_archive_plan> _create_archive; | ||||
/// The executables that should be linked as part of this library's build | /// The executables that should be linked as part of this library's build | ||||
std::vector<link_executable_plan> _link_exes; | std::vector<link_executable_plan> _link_exes; | ||||
/// The templates that must be rendered for this library | |||||
std::vector<render_template_plan> _templates; | |||||
public: | public: | ||||
/** | /** | ||||
*/ | */ | ||||
library_plan(library_root lib, | library_plan(library_root lib, | ||||
std::string_view qual_name, | std::string_view qual_name, | ||||
fs::path subdir, | |||||
std::optional<create_archive_plan> ar, | 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)) | : _lib(std::move(lib)) | ||||
, _qual_name(qual_name) | , _qual_name(qual_name) | ||||
, _subdir(std::move(subdir)) | |||||
, _create_archive(std::move(ar)) | , _create_archive(std::move(ar)) | ||||
, _link_exes(std::move(exes)) {} | |||||
, _link_exes(std::move(exes)) | |||||
, _templates(std::move(tmpls)) {} | |||||
/** | /** | ||||
* Get the underlying library object | * Get the underlying library object | ||||
* Get the qualified name of the library, as if for a libman usage requirement | * Get the qualified name of the library, as if for a libman usage requirement | ||||
*/ | */ | ||||
auto& qualified_name() const noexcept { return _qual_name; } | 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. | * The directory that defines the source root of the library. | ||||
*/ | */ | ||||
* components | * components | ||||
*/ | */ | ||||
auto& archive_plan() const noexcept { return _create_archive; } | 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 | * The executables that should be created by this library | ||||
*/ | */ | ||||
* The library identifiers that are linked by this library | * The library identifiers that are linked by this library | ||||
*/ | */ | ||||
auto& links() const noexcept { return _lib.manifest().links; } | 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. | * Named constructor: Create a new `library_plan` automatically from some build-time parameters. | ||||
std::optional<std::string_view> qual_name); | std::optional<std::string_view> qual_name); | ||||
}; | }; | ||||
} // namespace dds | |||||
} // namespace dds |
#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); | |||||
} |
#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 |
#pragma once | |||||
#include <string> | |||||
int config_file_value = 42; |
#include <info.hpp> | |||||
#include <cassert> | |||||
int main() { assert(config_file_value == 42); } |
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() |
full_cmd = itertools.chain([self.dds_exe], cmd) | full_cmd = itertools.chain([self.dds_exe], cmd) | ||||
return proc.run(full_cmd, cwd=cwd or self.source_root) | 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)) | cmdline = list(proc.flatten_cmd(cmd)) | ||||
res = self.run_unchecked(cmd, cwd=cwd) | res = self.run_unchecked(cmd, cwd=cwd) | ||||
if res.returncode != 0: | |||||
if res.returncode != 0 and check: | |||||
raise subprocess.CalledProcessError( | raise subprocess.CalledProcessError( | ||||
res.returncode, [self.dds_exe] + cmdline, res.stdout) | res.returncode, [self.dds_exe] + cmdline, res.stdout) | ||||
return res | return res | ||||
toolchain: str = None, | toolchain: str = None, | ||||
apps: bool = True, | apps: bool = True, | ||||
warnings: 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: | def sdist_create(self) -> subprocess.CompletedProcess: | ||||
return self.run([ | return self.run([ |