} | } | ||||
} | } | ||||
/** | |||||
* @brief Calculate a hash of the directory layout of the given directory. | |||||
* | |||||
* Because a tweaks-dir is specifically designed to have files added/removed within it, and | |||||
* its contents are inspected by `__has_include`, we need to have a way to invalidate any caches | |||||
* when the content of that directory changes. We don't care to hash the contents of the files, | |||||
* since those will already break any caches. | |||||
*/ | |||||
std::string hash_tweaks_dir(const fs::path& tweaks_dir) { | |||||
if (!fs::is_directory(tweaks_dir)) { | |||||
return "0"; // No tweaks directory, no cache to bust | |||||
} | |||||
std::vector<fs::path> children{fs::recursive_directory_iterator{tweaks_dir}, | |||||
fs::recursive_directory_iterator{}}; | |||||
std::sort(children.begin(), children.end()); | |||||
// A really simple inline djb2 hash | |||||
std::uint32_t hash = 5381; | |||||
for (auto& p : children) { | |||||
for (std::uint32_t c : fs::weakly_canonical(p).string()) { | |||||
hash = ((hash << 5) + hash) + c; | |||||
} | |||||
} | |||||
return std::to_string(hash); | |||||
} | |||||
template <typename Func> | template <typename Func> | ||||
void with_build_plan(const build_params& params, | void with_build_plan(const build_params& params, | ||||
const std::vector<sdist_target>& sdists, | const std::vector<sdist_target>& sdists, | ||||
params.out_root, | params.out_root, | ||||
db, | db, | ||||
toolchain_knobs{ | toolchain_knobs{ | ||||
.is_tty = stdout_is_a_tty(), | |||||
.is_tty = stdout_is_a_tty(), | |||||
.tweaks_dir = params.tweaks_dir, | |||||
}, | }, | ||||
ureqs, | ureqs, | ||||
}; | }; | ||||
if (env.knobs.tweaks_dir) { | |||||
env.knobs.cache_buster = hash_tweaks_dir(*env.knobs.tweaks_dir); | |||||
dds_log(trace, | |||||
"Build cache-buster value for tweaks-dir [{}] content is '{}'", | |||||
*env.knobs.tweaks_dir, | |||||
*env.knobs.cache_buster); | |||||
} | |||||
if (st.generate_catch2_main) { | if (st.generate_catch2_main) { | ||||
auto catch_lib = prepare_test_driver(params, test_lib::catch_main, env); | auto catch_lib = prepare_test_driver(params, test_lib::catch_main, env); | ||||
ureqs.add(".dds", "Catch-Main") = catch_lib; | ureqs.add(".dds", "Catch-Main") = catch_lib; |
std::optional<fs::path> existing_lm_index; | std::optional<fs::path> existing_lm_index; | ||||
std::optional<fs::path> emit_lmi; | std::optional<fs::path> emit_lmi; | ||||
std::optional<fs::path> emit_cmake{}; | std::optional<fs::path> emit_cmake{}; | ||||
std::optional<fs::path> tweaks_dir{}; | |||||
dds::toolchain toolchain; | dds::toolchain toolchain; | ||||
bool generate_compdb = true; | bool generate_compdb = true; | ||||
int parallel_jobs = 0; | int parallel_jobs = 0; | ||||
}; | }; | ||||
} // namespace dds | |||||
} // namespace dds |
.out_root = opts.out_path.value_or(fs::current_path() / "_build"), | .out_root = opts.out_path.value_or(fs::current_path() / "_build"), | ||||
.existing_lm_index = opts.build.lm_index, | .existing_lm_index = opts.build.lm_index, | ||||
.emit_lmi = {}, | .emit_lmi = {}, | ||||
.tweaks_dir = opts.build.tweaks_dir, | |||||
.toolchain = opts.load_toolchain(), | .toolchain = opts.load_toolchain(), | ||||
.parallel_jobs = opts.jobs, | .parallel_jobs = opts.jobs, | ||||
}); | }); |
.existing_lm_index = {}, | .existing_lm_index = {}, | ||||
.emit_lmi = opts.build.lm_index.value_or("INDEX.lmi"), | .emit_lmi = opts.build.lm_index.value_or("INDEX.lmi"), | ||||
.emit_cmake = opts.build_deps.cmake_file, | .emit_cmake = opts.build_deps.cmake_file, | ||||
.tweaks_dir = opts.build.tweaks_dir, | |||||
.toolchain = opts.load_toolchain(), | .toolchain = opts.load_toolchain(), | ||||
.parallel_jobs = opts.jobs, | .parallel_jobs = opts.jobs, | ||||
}; | }; |
.out_root = opts.out_path.value_or(fs::current_path() / "_build"), | .out_root = opts.out_path.value_or(fs::current_path() / "_build"), | ||||
.existing_lm_index = opts.build.lm_index, | .existing_lm_index = opts.build.lm_index, | ||||
.emit_lmi = {}, | .emit_lmi = {}, | ||||
.tweaks_dir = opts.build.tweaks_dir, | |||||
.toolchain = opts.load_toolchain(), | .toolchain = opts.load_toolchain(), | ||||
.parallel_jobs = opts.jobs, | .parallel_jobs = opts.jobs, | ||||
}); | }); |
.action = put_into(opts.repoman.repo_dir), | .action = put_into(opts.repoman.repo_dir), | ||||
}; | }; | ||||
argument tweaks_dir_arg{ | |||||
.long_spellings = {"tweaks-dir"}, | |||||
.short_spellings = {"TD"}, | |||||
.help | |||||
= "Base directory of " | |||||
"\x1b]8;;https://vector-of-bool.github.io/2020/10/04/lib-configuration.html\x1b\\tweak " | |||||
"headers\x1b]8;;\x1b\\ that should be available to the build.", | |||||
.valname = "<dir>", | |||||
.action = put_into(opts.build.tweaks_dir), | |||||
}; | |||||
void do_setup(argument_parser& parser) noexcept { | void do_setup(argument_parser& parser) noexcept { | ||||
parser.add_argument({ | parser.add_argument({ | ||||
.long_spellings = {"log-level"}, | .long_spellings = {"log-level"}, | ||||
build_cmd.add_argument(lm_index_arg.dup()).help | build_cmd.add_argument(lm_index_arg.dup()).help | ||||
= "Path to a libman index file to use for loading project dependencies"; | = "Path to a libman index file to use for loading project dependencies"; | ||||
build_cmd.add_argument(jobs_arg.dup()); | build_cmd.add_argument(jobs_arg.dup()); | ||||
build_cmd.add_argument(tweaks_dir_arg.dup()); | |||||
} | } | ||||
void setup_compile_file_cmd(argument_parser& compile_file_cmd) noexcept { | void setup_compile_file_cmd(argument_parser& compile_file_cmd) noexcept { | ||||
= "Set the maximum number of files to compile in parallel"; | = "Set the maximum number of files to compile in parallel"; | ||||
compile_file_cmd.add_argument(lm_index_arg.dup()); | compile_file_cmd.add_argument(lm_index_arg.dup()); | ||||
compile_file_cmd.add_argument(out_arg.dup()); | compile_file_cmd.add_argument(out_arg.dup()); | ||||
compile_file_cmd.add_argument(tweaks_dir_arg.dup()); | |||||
compile_file_cmd.add_argument({ | compile_file_cmd.add_argument({ | ||||
.help = "One or more source files to compile", | .help = "One or more source files to compile", | ||||
.valname = "<source-files>", | .valname = "<source-files>", | ||||
.valname = "<file-path>", | .valname = "<file-path>", | ||||
.action = debate::put_into(opts.build_deps.cmake_file), | .action = debate::put_into(opts.build_deps.cmake_file), | ||||
}); | }); | ||||
build_deps_cmd.add_argument(tweaks_dir_arg.dup()); | |||||
build_deps_cmd.add_argument({ | build_deps_cmd.add_argument({ | ||||
.help = "Dependency statement strings", | .help = "Dependency statement strings", | ||||
.valname = "<dependency>", | .valname = "<dependency>", |
opt_path lm_index; | opt_path lm_index; | ||||
std::vector<string> add_repos; | std::vector<string> add_repos; | ||||
bool update_repos = false; | bool update_repos = false; | ||||
opt_path tweaks_dir; | |||||
} build; | } build; | ||||
/** | /** |
extend(flags, _tty_flags); | extend(flags, _tty_flags); | ||||
} | } | ||||
if (knobs.cache_buster) { | |||||
// This is simply a CPP definition that is used to "bust" any caches that rely on inspecting | |||||
// the command-line of the compiler (including our own). | |||||
auto def = replace(_def_template, "[def]", "__dds_cachebust=" + *knobs.cache_buster); | |||||
extend(flags, def); | |||||
} | |||||
dds_log(trace, "#include-search dirs:"); | dds_log(trace, "#include-search dirs:"); | ||||
for (auto&& inc_dir : spec.include_dirs) { | for (auto&& inc_dir : spec.include_dirs) { | ||||
dds_log(trace, " - search: {}", inc_dir.string()); | dds_log(trace, " - search: {}", inc_dir.string()); | ||||
extend(flags, inc_args); | extend(flags, inc_args); | ||||
} | } | ||||
if (knobs.tweaks_dir) { | |||||
dds_log(trace, " - search (tweaks): {}", knobs.tweaks_dir->string()); | |||||
auto shortest = shortest_path_from(*knobs.tweaks_dir, cwd); | |||||
auto tweak_inc_args = include_args(shortest); | |||||
extend(flags, tweak_inc_args); | |||||
} | |||||
for (auto&& def : spec.definitions) { | for (auto&& def : spec.definitions) { | ||||
auto def_args = definition_args(def); | auto def_args = definition_args(def); | ||||
extend(flags, def_args); | extend(flags, def_args); |
struct toolchain_knobs { | struct toolchain_knobs { | ||||
bool is_tty = false; | bool is_tty = false; | ||||
// Directory storing tweaks for the compilation | |||||
std::optional<fs::path> tweaks_dir{}; | |||||
std::optional<std::string> cache_buster{}; | |||||
}; | }; | ||||
struct compile_file_spec { | struct compile_file_spec { |
#pragma once | |||||
#if __has_include(<tweakable.tweaks.hpp>) | |||||
#include <tweakable.tweaks.hpp> | |||||
#endif | |||||
namespace tweakable { | |||||
namespace config { | |||||
namespace defaults { | |||||
const int value = 99; | |||||
} // namespace defaults | |||||
using namespace defaults; | |||||
} // namespace config | |||||
} // namespace tweakable |
#pragma once | |||||
namespace tweakable { | |||||
extern int get_value(); | |||||
} // namespace tweakable |
{ | |||||
"name": "foo" | |||||
} |
{ | |||||
name: 'tweakable', | |||||
version: '1.2.3', | |||||
"namespace": "test", | |||||
} |
#include <tweakable.config.hpp> | |||||
#include <tweakable.hpp> | |||||
#include <iostream> | |||||
int tweakable::get_value() { return tweakable::config::value; } |
#include <tweakable.hpp> | |||||
int main() { return tweakable::get_value(); } |
from dds_ci.testing.fixtures import ProjectOpener | |||||
from dds_ci import paths, proc | |||||
def test_lib_with_tweaks(project_opener: ProjectOpener) -> None: | |||||
pr = project_opener.open('projects/tweaks') | |||||
pr.build() | |||||
app = pr.build_root / ('tweakable' + paths.EXE_SUFFIX) | |||||
res = proc.run([app]) | |||||
# The default value is 99: | |||||
assert res.returncode == 99 | |||||
# Build again, but with an empty/non-existent tweaks directory | |||||
pr.build(tweaks_dir=pr.root / 'conf') | |||||
res = proc.run([app]) | |||||
assert res.returncode == 99 | |||||
# Now write a tweaks header and rebuild: | |||||
pr.write( | |||||
'conf/tweakable.tweaks.hpp', r''' | |||||
#pragma once | |||||
namespace tweakable { | |||||
namespace config { | |||||
const int value = 41; | |||||
} | |||||
} | |||||
''') | |||||
pr.build(tweaks_dir=pr.root / 'conf') | |||||
res = proc.run([app]) | |||||
assert res.returncode == 41 |
toolchain: Optional[Path] = None, | toolchain: Optional[Path] = None, | ||||
build_root: Optional[Path] = None, | build_root: Optional[Path] = None, | ||||
jobs: Optional[int] = None, | jobs: Optional[int] = None, | ||||
tweaks_dir: Optional[Path] = None, | |||||
more_args: Optional[proc.CommandLine] = None, | more_args: Optional[proc.CommandLine] = None, | ||||
timeout: Optional[int] = None) -> None: | timeout: Optional[int] = None) -> None: | ||||
""" | """ | ||||
f'--jobs={jobs}', | f'--jobs={jobs}', | ||||
f'{self.project_dir_flag}={root}', | f'{self.project_dir_flag}={root}', | ||||
f'--out={build_root}', | f'--out={build_root}', | ||||
f'--tweaks-dir={tweaks_dir}' if tweaks_dir else (), | |||||
more_args or (), | more_args or (), | ||||
], | ], | ||||
timeout=timeout, | timeout=timeout, |
"""Argument for --project""" | """Argument for --project""" | ||||
return f'--project={self.root}' | return f'--project={self.root}' | ||||
def build(self, *, toolchain: Optional[Pathish] = None, timeout: Optional[int] = None) -> None: | |||||
def build(self, | |||||
*, | |||||
toolchain: Optional[Pathish] = None, | |||||
timeout: Optional[int] = None, | |||||
tweaks_dir: Optional[Path] = None) -> None: | |||||
""" | """ | ||||
Execute 'dds build' on the project | Execute 'dds build' on the project | ||||
""" | """ | ||||
build_root=self.build_root, | build_root=self.build_root, | ||||
toolchain=tc, | toolchain=tc, | ||||
timeout=timeout, | timeout=timeout, | ||||
more_args=['-ldebug']) | |||||
tweaks_dir=tweaks_dir, | |||||
more_args=['-ltrace']) | |||||
def compile_file(self, *paths: Pathish, toolchain: Optional[Pathish] = None) -> None: | def compile_file(self, *paths: Pathish, toolchain: Optional[Pathish] = None) -> None: | ||||
with tc_mod.fixup_toolchain(toolchain or tc_mod.get_default_test_toolchain()) as tc: | with tc_mod.fixup_toolchain(toolchain or tc_mod.get_default_test_toolchain()) as tc: |