@@ -4,78 +4,101 @@ | |||
#include <range/v3/view/concat.hpp> | |||
#include <range/v3/view/filter.hpp> | |||
#include <range/v3/view/transform.hpp> | |||
#include <cassert> | |||
using namespace dds; | |||
library_plan library_plan::create(const library& lib, | |||
const library_build_params& params, | |||
const usage_requirement_map& ureqs) { | |||
std::vector<compile_file_plan> compile_files; | |||
std::vector<link_executable_plan> link_executables; | |||
std::optional<create_archive_plan> create_archive; | |||
// Source files are kept in three groups: | |||
std::vector<source_file> app_sources; | |||
std::vector<source_file> test_sources; | |||
std::vector<source_file> lib_sources; | |||
// Collect the source for this library. This will look for any compilable sources in the `src/` | |||
// subdirectory of the library. | |||
auto src_dir = lib.src_dir(); | |||
if (src_dir.exists()) { | |||
// Sort each source file between the three source arrays, depending on | |||
// the kind of source that we are looking at. | |||
auto all_sources = src_dir.sources(); | |||
auto to_compile = all_sources | ranges::views::filter([&](const source_file& sf) { | |||
return (sf.kind == source_kind::source | |||
|| (sf.kind == source_kind::app && params.build_apps) | |||
|| (sf.kind == source_kind::test && params.build_tests)); | |||
}); | |||
for (const auto& sfile : to_compile) { | |||
for (const auto& sfile : all_sources) { | |||
if (sfile.kind == source_kind::test) { | |||
test_sources.push_back(sfile); | |||
} else if (sfile.kind == source_kind::app) { | |||
app_sources.push_back(sfile); | |||
} else { | |||
} else if (sfile.kind == source_kind::source) { | |||
lib_sources.push_back(sfile); | |||
} else { | |||
assert(sfile.kind == source_kind::header); | |||
} | |||
} | |||
} | |||
// Load up the compile rules | |||
auto compile_rules = lib.base_compile_rules(); | |||
compile_rules.enable_warnings() = params.enable_warnings; | |||
// Apply our transitive usage requirements. This gives us the search directories for our | |||
// dependencies. | |||
for (const auto& use : lib.manifest().uses) { | |||
ureqs.apply(compile_rules, use.namespace_, use.name); | |||
} | |||
for (const auto& sf : lib_sources) { | |||
compile_files.emplace_back(compile_rules, | |||
sf, | |||
lib.manifest().name, | |||
params.out_subdir / "obj"); | |||
} | |||
// Convert the library sources into their respective file compilation plans. | |||
auto lib_compile_files = // | |||
lib_sources // | |||
| ranges::views::transform([&](const source_file& sf) { | |||
return compile_file_plan(compile_rules, | |||
sf, | |||
lib.manifest().name, | |||
params.out_subdir / "obj"); | |||
}) | |||
| ranges::to_vector; | |||
if (!lib_sources.empty()) { | |||
create_archive.emplace(lib.manifest().name, params.out_subdir, std::move(compile_files)); | |||
// If we have any compiled library files, generate a static library archive | |||
// for this library | |||
std::optional<create_archive_plan> create_archive; | |||
if (!lib_compile_files.empty()) { | |||
create_archive.emplace(lib.manifest().name, | |||
params.out_subdir, | |||
std::move(lib_compile_files)); | |||
} | |||
std::vector<fs::path> in_libs; | |||
// Collect the paths to linker inputs that should be used when generating executables for this | |||
// library. | |||
std::vector<fs::path> link_libs; | |||
for (auto& use : lib.manifest().uses) { | |||
extend(in_libs, ureqs.link_paths(use.namespace_, use.name)); | |||
extend(link_libs, ureqs.link_paths(use.namespace_, use.name)); | |||
} | |||
for (auto& link : lib.manifest().links) { | |||
extend(in_libs, ureqs.link_paths(link.namespace_, link.name)); | |||
extend(link_libs, ureqs.link_paths(link.namespace_, link.name)); | |||
} | |||
auto test_in_libs = in_libs; | |||
extend(test_in_libs, params.test_link_files); | |||
// Linker inputs for tests may contain additional code for test execution | |||
auto test_link_libs = link_libs; | |||
extend(test_link_libs, params.test_link_files); | |||
// There may also be additional #include paths for test source files | |||
auto test_rules = compile_rules.clone(); | |||
extend(test_rules.include_dirs(), params.test_include_dirs); | |||
// Generate the plans to link any executables for this library | |||
std::vector<link_executable_plan> link_executables; | |||
for (const source_file& source : ranges::views::concat(app_sources, test_sources)) { | |||
const bool is_test = source.kind == source_kind::test; | |||
// Pick a subdir based on app/test | |||
auto subdir | |||
= source.kind == source_kind::test ? params.out_subdir / "test" : params.out_subdir; | |||
const auto subdir_base = is_test ? params.out_subdir / "test" : params.out_subdir; | |||
// Put test/app executables in a further subdirectory based on the source file path | |||
const auto subdir | |||
= subdir_base / fs::relative(source.path.parent_path(), lib.src_dir().path); | |||
// Pick compile rules based on app/test | |||
auto rules = source.kind == source_kind::test ? test_rules : compile_rules; | |||
auto rules = is_test ? test_rules : compile_rules; | |||
// Pick input libs based on app/test | |||
auto& exe_link_libs = source.kind == source_kind::test ? test_in_libs : in_libs; | |||
auto& exe_link_libs = is_test ? test_link_libs : link_libs; | |||
// TODO: Apps/tests should only see the _public_ include dir, not both | |||
link_executables.emplace_back(exe_link_libs, | |||
compile_file_plan(rules, | |||
@@ -86,6 +109,7 @@ library_plan library_plan::create(const library& lib, | |||
source.path.stem().stem().string()); | |||
} | |||
// Done! | |||
return library_plan{lib.manifest().name, | |||
lib.path(), | |||
std::move(create_archive), |
@@ -12,26 +12,64 @@ | |||
namespace dds { | |||
/** | |||
* The parameters that tweak the behavior of building a library | |||
*/ | |||
struct library_build_params { | |||
/// The subdirectory of the build root in which this library should place its files. | |||
fs::path out_subdir; | |||
bool build_tests = false; | |||
bool build_apps = false; | |||
bool enable_warnings = false; | |||
/// Whether tests should be compiled and linked for this library | |||
bool build_tests = false; | |||
/// Whether applications should be compiled and linked for this library | |||
bool build_apps = false; | |||
/// Whether compiler warnings should be enabled for building the source files in this library. | |||
bool enable_warnings = false; | |||
// Extras for compiling tests: | |||
/// Directories that should be on the #include search path when compiling tests | |||
std::vector<fs::path> test_include_dirs; | |||
/// Files that should be added as inputs when linking test executables | |||
std::vector<fs::path> test_link_files; | |||
}; | |||
/** | |||
* A `library_plan` is a composite object that keeps track of the parameters for building a library, | |||
* including: | |||
* | |||
* - If the library has compilable library source files, a `create_archive_plan` that details the | |||
* compilation of those source files and their collection into a static library archive. | |||
* - The executables that need to be linked when this library is built. This includes any tests and | |||
* apps that are part of this library. These can be enabled/disabled by setting the appropriate | |||
* values in `library_build_params`. | |||
* - The libraries that this library *uses*. | |||
* - The libraries that this library *links*. | |||
* | |||
* While there is a public constructor, it is best to use the `create` named constructor, which will | |||
* initialize all of the constructor parameters correctly. | |||
*/ | |||
class library_plan { | |||
std::string _name; | |||
fs::path _source_root; | |||
/// The name of the library | |||
std::string _name; | |||
/// The directory at the root of this library | |||
fs::path _source_root; | |||
/// The `create_archive_plan` for this library, if applicable | |||
std::optional<create_archive_plan> _create_archive; | |||
std::vector<link_executable_plan> _link_exes; | |||
std::vector<lm::usage> _uses; | |||
std::vector<lm::usage> _links; | |||
/// The executables that should be linked as part of this library's build | |||
std::vector<link_executable_plan> _link_exes; | |||
/// The libraries that we use | |||
std::vector<lm::usage> _uses; | |||
/// The libraries that we link | |||
std::vector<lm::usage> _links; | |||
public: | |||
/** | |||
* Construct a new `library_plan` | |||
* @param name The name of the library | |||
* @param source_root The directory that contains this library | |||
* @param ar The `create_archive_plan`, or `nullopt` for this library. | |||
* @param exes The `link_executable_plan` objects for this library. | |||
* @param uses The identities of the libraries that are used by this library | |||
* @param links The identities of the libraries that are linked by this library | |||
*/ | |||
library_plan(std::string_view name, | |||
path_ref source_root, | |||
std::optional<create_archive_plan> ar, | |||
@@ -45,15 +83,46 @@ public: | |||
, _uses(std::move(uses)) | |||
, _links(std::move(links)) {} | |||
/** | |||
* Get the name of the library | |||
*/ | |||
auto& name() const noexcept { return _name; } | |||
/** | |||
* The directory that defines the source root of the library. | |||
*/ | |||
path_ref source_root() const noexcept { return _source_root; } | |||
auto& name() const noexcept { return _name; } | |||
auto& create_archive() const noexcept { return _create_archive; } | |||
auto& executables() const noexcept { return _link_exes; } | |||
auto& uses() const noexcept { return _uses; } | |||
auto& links() const noexcept { return _links; } | |||
static library_plan | |||
create(const library&, const library_build_params&, const usage_requirement_map&); | |||
/** | |||
* A `create_archive_plan` object, or `nullopt`, depending on if this library has compiled | |||
* components | |||
*/ | |||
auto& create_archive() const noexcept { return _create_archive; } | |||
/** | |||
* The executables that should be created by this library | |||
*/ | |||
auto& executables() const noexcept { return _link_exes; } | |||
/** | |||
* The library identifiers that are used by this library | |||
*/ | |||
auto& uses() const noexcept { return _uses; } | |||
/** | |||
* The library identifiers that are linked by this library | |||
*/ | |||
auto& links() const noexcept { return _links; } | |||
/** | |||
* Named constructor: Create a new `library_plan` automatically from some build-time parameters. | |||
* | |||
* @param lib The `library` object from which we will inherit several properties. | |||
* @param params Parameters controlling the build of the library. i.e. if we create tests, | |||
* enable warnings, etc. | |||
* @param ureqs The usage requirements map. This should be populated as appropriate. | |||
* | |||
* The `lib` parameter defines the usage requirements of this library, and they are looked up in | |||
* the `ureqs` map. If there are any missing requirements, an exception will be thrown. | |||
*/ | |||
static library_plan create(const library& lib, | |||
const library_build_params& params, | |||
const usage_requirement_map& ureqs); | |||
}; | |||
} // namespace dds |
@@ -8,6 +8,6 @@ from dds_ci import proc | |||
) | |||
def test_catch_testdriver(dds: DDS): | |||
dds.build(tests=True) | |||
test_exe = dds.build_dir / f'test/calc{dds.exe_suffix}' | |||
test_exe = dds.build_dir / f'test/testlib/calc{dds.exe_suffix}' | |||
assert test_exe.exists() | |||
assert proc.run([test_exe]).returncode == 0 |