Bläddra i källkod

Complete refactoring of builds

- Build execution is now entirely oriented around source distributions.
- Make a lot of build plan objects and methods more agnostic to the
  build environment. This allows more flexibility and delays generation
  of some build metadata, such that we are able to more easily generate
  usage requirements data between libraries in the project.
default_compile_flags
vector-of-bool 5 år sedan
förälder
incheckning
b99d957bc3
15 ändrade filer med 397 tillägg och 171 borttagningar
  1. +15
    -1
      src/dds.main.cpp
  2. +12
    -80
      src/dds/build.cpp
  3. +200
    -0
      src/dds/build/builder.cpp
  4. +57
    -0
      src/dds/build/builder.hpp
  5. +3
    -4
      src/dds/build/plan/archive.cpp
  6. +4
    -4
      src/dds/build/plan/archive.hpp
  7. +3
    -0
      src/dds/build/plan/base.hpp
  8. +3
    -0
      src/dds/build/plan/compile_file.cpp
  9. +9
    -0
      src/dds/build/plan/compile_file.hpp
  10. +6
    -1
      src/dds/build/plan/exe.cpp
  11. +11
    -4
      src/dds/build/plan/exe.hpp
  12. +23
    -34
      src/dds/build/plan/library.cpp
  13. +21
    -30
      src/dds/build/plan/library.hpp
  14. +23
    -7
      src/dds/usage_reqs.cpp
  15. +7
    -6
      src/dds/usage_reqs.hpp

+ 15
- 1
src/dds.main.cpp Visa fil

@@ -1,4 +1,5 @@
#include <dds/build.hpp>
#include <dds/build/builder.hpp>
#include <dds/catalog/catalog.hpp>
#include <dds/catalog/get.hpp>
#include <dds/repo/repo.hpp>
@@ -523,6 +524,14 @@ struct cli_build {
if (exists(man_filepath)) {
man = dds::package_manifest::load_from_file(man_filepath);
}

dds::builder bd;
dds::sdist_build_params main_params;
main_params.build_apps = !no_apps.Get();
main_params.enable_warnings = !no_warnings.Get();
main_params.run_tests = main_params.build_tests = !no_tests.Get();

bd.add(dds::sdist{man, project.root.Get()}, main_params);
if (lm_index) {
params.existing_lm_index = lm_index.Get();
} else {
@@ -553,8 +562,13 @@ struct cli_build {
})
| ranges::to_vector;
});
for (auto sd : params.dep_sdists) {
dds::sdist_build_params deps_params;
deps_params.subdir = dds::fs::path("_deps") / sd.manifest.pkg_id.to_string();
bd.add(std::move(sd), deps_params);
}
}
dds::build(params, man);
bd.build(params);
return 0;
}
};

+ 12
- 80
src/dds/build.cpp Visa fil

@@ -1,5 +1,6 @@
#include "./build.hpp"

#include <dds/build/builder.hpp>
#include <dds/build/plan/compile_exec.hpp>
#include <dds/catch2_embedded.hpp>
#include <dds/compdb.hpp>
@@ -61,7 +62,8 @@ fs::path export_project_library(const library_plan& lib, build_env_ref env, path
}

if (lib.create_archive()) {
auto ar_path = lib.create_archive()->calc_archive_file_path(env);
auto ar_path
= env.output_root / lib.create_archive()->calc_archive_file_path(env.toolchain);
auto ar_dest = lib_out_root / ar_path.filename();
fs::create_directories(ar_dest.parent_path());
fs::copy_file(ar_path, ar_dest);
@@ -120,76 +122,6 @@ load_usage_requirements(path_ref project_root, path_ref build_root, path_ref use
return usage_requirement_map::from_lm_index(idx);
}

void prepare_catch2_driver(library_build_params& lib_params,
test_lib test_driver,
const build_params& params,
build_env_ref env_) {
fs::path test_include_root = params.out_root / "_catch-2.10.2";
lib_params.test_include_dirs.emplace_back(test_include_root);

auto catch_hpp = test_include_root / "catch2/catch.hpp";
if (!fs::exists(catch_hpp)) {
fs::create_directories(catch_hpp.parent_path());
auto hpp_strm = open(catch_hpp, std::ios::out | std::ios::binary);
hpp_strm.write(detail::catch2_embedded_single_header_str,
std::strlen(detail::catch2_embedded_single_header_str));
hpp_strm.close();
}

if (test_driver == test_lib::catch_) {
// Don't generate a test library helper
return;
}

std::string fname;
std::string definition;

if (test_driver == test_lib::catch_main) {
fname = "catch-main.cpp";
definition = "CATCH_CONFIG_MAIN";
} else {
assert(false && "Impossible: Invalid `test_driver` for catch library");
std::terminate();
}

shared_compile_file_rules comp_rules;
comp_rules.defs().push_back(definition);

auto catch_cpp = test_include_root / "catch2" / fname;
auto cpp_strm = open(catch_cpp, std::ios::out | std::ios::binary);
cpp_strm << "#include \"./catch.hpp\"\n";
cpp_strm.close();

auto sf = source_file::from_path(catch_cpp, test_include_root);
assert(sf.has_value());

compile_file_plan plan{comp_rules, std::move(*sf), "Catch2", "v1"};

build_env env2 = env_;
env2.output_root /= "_test-driver";
auto obj_file = plan.calc_object_file_path(env2);

if (!fs::exists(obj_file)) {
spdlog::info("Compiling Catch2 test driver (This will only happen once)...");
compile_all(std::array{plan}, env2, 1);
}

lib_params.test_link_files.push_back(obj_file);
}

void prepare_test_driver(library_build_params& lib_params,
const build_params& params,
const package_manifest& man,
build_env_ref env) {
auto& test_driver = *man.test_driver;
if (test_driver == test_lib::catch_ || test_driver == test_lib::catch_main) {
prepare_catch2_driver(lib_params, test_driver, params, env);
} else {
assert(false && "Unreachable");
std::terminate();
}
}

void add_ureqs(usage_requirement_map& ureqs,
const sdist& sd,
const library& lib,
@@ -197,11 +129,11 @@ void add_ureqs(usage_requirement_map& ureqs,
build_env_ref env) {
lm::library& reqs = ureqs.add(sd.manifest.namespace_, lib.manifest().name);
reqs.include_paths.push_back(lib.public_include_dir());
reqs.name = lib.manifest().name;
reqs.uses = lib.manifest().uses;
reqs.links = lib.manifest().links;
if (lib_plan.create_archive()) {
reqs.linkable_path = lib_plan.create_archive()->calc_archive_file_path(env);
reqs.linkable_path
= env.output_root / lib_plan.create_archive()->calc_archive_file_path(env.toolchain);
}
// TODO: preprocessor definitions
}
@@ -236,7 +168,7 @@ void add_sdist_to_build(build_plan& plan,
shared_compile_file_rules comp_rules = lib.base_compile_rules();
library_build_params lib_params;
lib_params.out_subdir = fs::path("deps") / sd.manifest.pkg_id.name;
auto lib_plan = library_plan::create(lib, lib_params, ureqs);
auto lib_plan = library_plan::create(lib, lib_params);
// Create usage requirements for this libary.
add_ureqs(ureqs, sd, lib, lib_plan, env);
// Add it to the plan:
@@ -265,11 +197,13 @@ void add_deps_to_build(build_plan& plan,
void dds::build(const build_params& params, const package_manifest& man) {
fs::create_directories(params.out_root);
auto db = database::open(params.out_root / ".dds.db");
dds::build_env env{params.toolchain, params.out_root, db};

// The build plan we will fill out:
build_plan plan;

// Create a source distribution for the project, even if it doesn't have a manifest of its own
sdist root_sdist{man, params.root};

// Collect libraries for the current project
auto libs = collect_libraries(params.root);
if (!libs.size()) {
@@ -279,6 +213,8 @@ void dds::build(const build_params& params, const package_manifest& man) {

usage_requirement_map ureqs;

dds::build_env env{params.toolchain, params.out_root, db, ureqs};

if (params.existing_lm_index) {
ureqs = load_usage_requirements(params.root, params.out_root, *params.existing_lm_index);
} else {
@@ -295,13 +231,9 @@ void dds::build(const build_params& params, const package_manifest& man) {
lib_params.build_apps = params.build_apps;
lib_params.enable_warnings = params.enable_warnings;

if (man.test_driver) {
prepare_test_driver(lib_params, params, man, env);
}

for (const library& lib : libs) {
lib_params.out_subdir = fs::relative(lib.path(), params.root);
pkg.add_library(library_plan::create(lib, lib_params, ureqs));
pkg.add_library(library_plan::create(lib, lib_params));
}

if (params.generate_compdb) {

+ 200
- 0
src/dds/build/builder.cpp Visa fil

@@ -0,0 +1,200 @@
#include "./builder.hpp"

#include <dds/build/plan/compile_exec.hpp>
#include <dds/build/plan/full.hpp>
#include <dds/catch2_embedded.hpp>
#include <dds/compdb.hpp>
#include <dds/usage_reqs.hpp>
#include <dds/util/time.hpp>

#include <spdlog/spdlog.h>

#include <array>
#include <set>

using namespace dds;

namespace {

struct state {
bool generate_catch2_header = false;
bool generate_catch2_main = false;
};

lm::library
prepare_catch2_driver(test_lib test_driver, const build_params& params, build_env_ref env_) {
fs::path test_include_root = params.out_root / "_catch-2.10.2";

lm::library ret_lib;
auto catch_hpp = test_include_root / "catch2/catch.hpp";
if (!fs::exists(catch_hpp)) {
fs::create_directories(catch_hpp.parent_path());
auto hpp_strm = open(catch_hpp, std::ios::out | std::ios::binary);
hpp_strm.write(detail::catch2_embedded_single_header_str,
std::strlen(detail::catch2_embedded_single_header_str));
hpp_strm.close();
}
ret_lib.include_paths.push_back(test_include_root);

if (test_driver == test_lib::catch_) {
// Don't compile a library helper
return ret_lib;
}

std::string fname;
std::string definition;

if (test_driver == test_lib::catch_main) {
fname = "catch-main.cpp";
definition = "CATCH_CONFIG_MAIN";
} else {
assert(false && "Impossible: Invalid `test_driver` for catch library");
std::terminate();
}

shared_compile_file_rules comp_rules;
comp_rules.defs().push_back(definition);

auto catch_cpp = test_include_root / "catch2" / fname;
auto cpp_strm = open(catch_cpp, std::ios::out | std::ios::binary);
cpp_strm << "#include \"./catch.hpp\"\n";
cpp_strm.close();

auto sf = source_file::from_path(catch_cpp, test_include_root);
assert(sf.has_value());

compile_file_plan plan{comp_rules, std::move(*sf), "Catch2", "v1"};

build_env env2 = env_;
env2.output_root /= "_test-driver";
auto obj_file = plan.calc_object_file_path(env2);

if (!fs::exists(obj_file)) {
spdlog::info("Compiling Catch2 test driver (This will only happen once)...");
compile_all(std::array{plan}, env2, 1);
}

ret_lib.linkable_path = obj_file;
return ret_lib;
}

lm::library
prepare_test_driver(const build_params& params, test_lib test_driver, build_env_ref env) {
if (test_driver == test_lib::catch_ || test_driver == test_lib::catch_main) {
return prepare_catch2_driver(test_driver, params, env);
} else {
assert(false && "Unreachable");
std::terminate();
}
}

library_plan prepare_library(state& st,
const sdist_target& sdt,
const library& lib,
const package_manifest& pkg_man) {
library_build_params lp;
lp.out_subdir = sdt.params.subdir;
lp.build_apps = sdt.params.build_apps;
lp.build_tests = sdt.params.build_tests;
lp.enable_warnings = sdt.params.enable_warnings;
if (lp.build_tests) {
if (pkg_man.test_driver == test_lib::catch_
|| pkg_man.test_driver == test_lib::catch_main) {
lp.test_uses.push_back({".dds", "Catch"});
st.generate_catch2_header = true;
if (pkg_man.test_driver == test_lib::catch_main) {
lp.test_uses.push_back({".dds", "Catch-Main"});
st.generate_catch2_main = true;
}
}
}
return library_plan::create(lib, std::move(lp));
}

package_plan prepare_one(state& st, const sdist_target& sd) {
package_plan pkg{sd.sd.manifest.pkg_id.name, sd.sd.manifest.namespace_};
auto libs = collect_libraries(sd.sd.path);
for (const auto& lib : libs) {
pkg.add_library(prepare_library(st, sd, lib, sd.sd.manifest));
}
return pkg;
}

build_plan prepare_build_plan(state& st, const std::vector<sdist_target>& sdists) {
build_plan plan;
for (const auto& sd_target : sdists) {
plan.add_package(prepare_one(st, sd_target));
}
return plan;
}

usage_requirement_map
prepare_ureqs(const build_plan& plan, const toolchain& toolchain, path_ref out_root) {
usage_requirement_map ureqs;
for (const auto& pkg : plan.packages()) {
for (const auto& lib : pkg.libraries()) {
auto& lib_reqs = ureqs.add(pkg.namespace_(), lib.name());
lib_reqs.include_paths.push_back(lib.library_().public_include_dir());
lib_reqs.uses = lib.library_().manifest().uses;
lib_reqs.links = lib.library_().manifest().links;
if (const auto& arc = lib.create_archive()) {
lib_reqs.linkable_path = out_root / arc->calc_archive_file_path(toolchain);
}
}
}
return ureqs;
}

} // namespace

void builder::build(const build_params& params) const {
fs::create_directories(params.out_root);
auto db = database::open(params.out_root / ".dds.db");

state st;
auto plan = prepare_build_plan(st, _sdists);
auto ureqs = prepare_ureqs(plan, params.toolchain, params.out_root);
build_env env{params.toolchain, params.out_root, db, ureqs};

if (st.generate_catch2_main) {
auto catch_lib = prepare_test_driver(params, test_lib::catch_main, env);
ureqs.add(".dds", "Catch-Main") = catch_lib;
}
if (st.generate_catch2_header) {
auto catch_lib = prepare_test_driver(params, test_lib::catch_, env);
ureqs.add(".dds", "Catch") = catch_lib;
}

if (params.generate_compdb) {
generate_compdb(plan, env);
}

dds::stopwatch sw;
plan.compile_all(env, params.parallel_jobs);
spdlog::info("Compilation completed in {:n}ms", sw.elapsed_ms().count());

sw.reset();
plan.archive_all(env, params.parallel_jobs);
spdlog::info("Archiving completed in {:n}ms", sw.elapsed_ms().count());

if (params.build_tests || params.build_apps) {
sw.reset();
plan.link_all(env, params.parallel_jobs);
spdlog::info("Runtime binary linking completed in {:n}ms", sw.elapsed_ms().count());
}

if (params.build_tests) {
sw.reset();
auto test_failures = plan.run_all_tests(env, params.parallel_jobs);
spdlog::info("Test execution finished in {:n}ms", sw.elapsed_ms().count());

for (auto& failures : test_failures) {
spdlog::error("Test {} failed! Output:\n{}[dds - test output end]",
failures.executable_path.string(),
failures.output);
}
if (!test_failures.empty()) {
throw compile_failure("Test failures during the build!");
}
}
}

+ 57
- 0
src/dds/build/builder.hpp Visa fil

@@ -0,0 +1,57 @@
#pragma once

#include <dds/build/params.hpp>
#include <dds/source/dist.hpp>

#include <cassert>
#include <map>

namespace dds {

/**
* Parameters for building an individual source distribution as part of a larger build plan.
*/
struct sdist_build_params {
/// The subdirectory in which the source directory should be built
fs::path subdir;
/// Whether to build tests
bool build_tests = false;
/// Whether to run tests
bool run_tests = false;
/// Whether to build applications
bool build_apps = false;
/// Whether to enable build warnings
bool enable_warnings = false;
};

/**
* Just a pairing of an sdist to the parameters that are used to build it.
*/
struct sdist_target {
/// The source distribution
sdist sd;
/// The build parameters thereof
sdist_build_params params;
};

/**
* A builder object. Source distributions are added to the builder, and then they are all built in parallel via `build()`
*/
class builder {
/// Source distributions that have been added
std::vector<sdist_target> _sdists;

public:
/// Add more source distributions
void add(sdist sd) { add(std::move(sd), sdist_build_params()); }
void add(sdist sd, sdist_build_params params) {
_sdists.push_back({std::move(sd), std::move(params)});
}

/**
* Execute the build
*/
void build(const build_params& params) const;
};

} // namespace dds

+ 3
- 4
src/dds/build/plan/archive.cpp Visa fil

@@ -8,9 +8,8 @@

using namespace dds;

fs::path create_archive_plan::calc_archive_file_path(const build_env& env) const noexcept {
return env.output_root / _subdir
/ fmt::format("{}{}{}", "lib", _name, env.toolchain.archive_suffix());
fs::path create_archive_plan::calc_archive_file_path(const toolchain& tc) const noexcept {
return _subdir / fmt::format("{}{}{}", "lib", _name, tc.archive_suffix());
}

void create_archive_plan::archive(const build_env& env) const {
@@ -23,7 +22,7 @@ void create_archive_plan::archive(const build_env& env) const {
// Build up the archive command
archive_spec ar;
ar.input_files = std::move(objects);
ar.out_path = calc_archive_file_path(env);
ar.out_path = env.output_root / calc_archive_file_path(env.toolchain);
auto ar_cmd = env.toolchain.create_archive_command(ar);

// `out_relpath` is purely for the benefit of the user to have a short name

+ 4
- 4
src/dds/build/plan/archive.hpp Visa fil

@@ -43,11 +43,11 @@ public:
const std::string& name() const noexcept { return _name; }

/**
* Calculate the absolute path where the generated archive libary file will
* be generated after execution.
* @param env The build environment for the archival.
* Calculate the path relative to the build output root where the static library archive will be
* placed upon creation.
* @param tc The toolchain that will be used
*/
fs::path calc_archive_file_path(build_env_ref env) const noexcept;
fs::path calc_archive_file_path(const toolchain& tc) const noexcept;

/**
* Get the compilation plans for this library.

+ 3
- 0
src/dds/build/plan/base.hpp Visa fil

@@ -2,6 +2,7 @@

#include <dds/db/database.hpp>
#include <dds/toolchain/toolchain.hpp>
#include <dds/usage_reqs.hpp>
#include <dds/util/fs.hpp>

namespace dds {
@@ -10,6 +11,8 @@ struct build_env {
dds::toolchain toolchain;
fs::path output_root;
database& db;

const usage_requirement_map& ureqs;
};

using build_env_ref = const build_env&;

+ 3
- 0
src/dds/build/plan/compile_file.cpp Visa fil

@@ -16,6 +16,9 @@ compile_command_info compile_file_plan::generate_compile_command(build_env_ref e
compile_file_spec spec{_source.path, calc_object_file_path(env)};
spec.enable_warnings = _rules.enable_warnings();
extend(spec.include_dirs, _rules.include_dirs());
for (const auto& use : _rules.uses()) {
extend(spec.include_dirs, env.ureqs.include_paths(use));
}
extend(spec.definitions, _rules.defs());
return env.toolchain.create_compile_command(spec);
}

+ 9
- 0
src/dds/build/plan/compile_file.hpp Visa fil

@@ -3,6 +3,8 @@
#include <dds/build/plan/base.hpp>
#include <dds/source/file.hpp>

#include <libman/library.hpp>

#include <memory>

namespace dds {
@@ -25,6 +27,7 @@ class shared_compile_file_rules {
struct rules_impl {
std::vector<fs::path> inc_dirs;
std::vector<std::string> defs;
std::vector<lm::usage> uses;
bool enable_warnings = false;
};

@@ -53,6 +56,12 @@ public:
auto& defs() noexcept { return _impl->defs; }
auto& defs() const noexcept { return _impl->defs; }

/**
* Access the named usage requirements for this set of rules
*/
auto& uses() noexcept { return _impl->uses; }
auto& uses() const noexcept { return _impl->uses; }

/**
* A boolean to toggle compile warnings for the associated compiles
*/

+ 6
- 1
src/dds/build/plan/exe.cpp Visa fil

@@ -2,6 +2,7 @@

#include <dds/build/plan/library.hpp>
#include <dds/proc.hpp>
#include <dds/util/algo.hpp>
#include <dds/util/time.hpp>

#include <spdlog/spdlog.h>
@@ -20,10 +21,14 @@ void link_executable_plan::link(build_env_ref env, const library_plan& lib) cons
link_exe_spec spec;
spec.output = calc_executable_path(env);
spec.inputs = _input_libs;
for (const lm::usage& links : _links) {
extend(spec.inputs, env.ureqs.link_paths(links));
}
if (lib.create_archive()) {
// The associated library has compiled components. Add the static library a as a linker
// input
spec.inputs.push_back(lib.create_archive()->calc_archive_file_path(env));
spec.inputs.push_back(env.output_root
/ lib.create_archive()->calc_archive_file_path(env.toolchain));
}

// The main object should be a linker input, of course.

+ 11
- 4
src/dds/build/plan/exe.hpp Visa fil

@@ -3,6 +3,8 @@
#include <dds/build/plan/compile_file.hpp>
#include <dds/util/fs.hpp>

#include <libman/library.hpp>

#include <string>
#include <vector>

@@ -26,6 +28,8 @@ struct test_failure {
class link_executable_plan {
/// The linker inputs that should be linked into the executable
std::vector<fs::path> _input_libs;
/// Usage requirements for this executable
std::vector<lm::usage> _links;
/// The compilation plan for the entry-point source file
compile_file_plan _main_compile;
/// The subdirectory in which the executable should be generated
@@ -37,15 +41,18 @@ public:
/**
* Create a new instance
* @param in_libs Linker inputs for the executable
* @param links The library identifiers that the executable should link with
* @param cfp The file compilation that defines the entrypoint of the application
* @param out_subdir The subdirectory of the build root in which the executable should be placed
* @param name_ The name of the executable
*/
link_executable_plan(std::vector<fs::path> in_libs,
compile_file_plan cfp,
path_ref out_subdir,
std::string name_)
link_executable_plan(std::vector<fs::path> in_libs,
std::vector<lm::usage> links,
compile_file_plan cfp,
path_ref out_subdir,
std::string name_)
: _input_libs(std::move(in_libs))
, _links(std::move(links))
, _main_compile(std::move(cfp))
, _out_subdir(out_subdir)
, _name(std::move(name_)) {}

+ 23
- 34
src/dds/build/plan/library.cpp Visa fil

@@ -10,9 +10,7 @@

using namespace dds;

library_plan library_plan::create(const library& lib,
const library_build_params& params,
const usage_requirement_map& ureqs) {
library_plan library_plan::create(const library& lib, const library_build_params& params) {
// Source files are kept in three groups:
std::vector<source_file> app_sources;
std::vector<source_file> test_sources;
@@ -41,12 +39,7 @@ library_plan library_plan::create(const library& lib,
// 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);
}
compile_rules.uses() = lib.manifest().uses;

// Convert the library sources into their respective file compilation plans.
auto lib_compile_files = //
@@ -70,21 +63,19 @@ library_plan library_plan::create(const library& lib,

// 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(link_libs, ureqs.link_paths(use.namespace_, use.name));
}
for (auto& link : lib.manifest().links) {
extend(link_libs, ureqs.link_paths(link.namespace_, link.name));
}
std::vector<lm::usage> links;
extend(links, lib.manifest().uses);
extend(links, lib.manifest().links);

// 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);
std::vector<fs::path> link_libs;
std::vector<fs::path> test_link_libs = params.test_link_files;

// There may also be additional #include paths for test source files
// There may also be additional usage requirements for tests
auto test_rules = compile_rules.clone();
extend(test_rules.include_dirs(), params.test_include_dirs);
auto test_links = links;
extend(test_rules.uses(), params.test_uses);
extend(test_links, params.test_uses);

// Generate the plans to link any executables for this library
std::vector<link_executable_plan> link_executables;
@@ -99,21 +90,19 @@ library_plan library_plan::create(const library& lib,
auto rules = is_test ? test_rules : compile_rules;
// Pick input libs based on app/test
auto& exe_link_libs = is_test ? test_link_libs : link_libs;
auto& exe_links = is_test ? test_links : links;
// TODO: Apps/tests should only see the _public_ include dir, not both
link_executables.emplace_back(exe_link_libs,
compile_file_plan(rules,
source,
lib.manifest().name,
params.out_subdir / "obj"),
subdir,
source.path.stem().stem().string());
auto exe = link_executable_plan{exe_link_libs,
exe_links,
compile_file_plan(rules,
source,
lib.manifest().name,
params.out_subdir / "obj"),
subdir,
source.path.stem().stem().string()};
link_executables.emplace_back(std::move(exe));
}

// Done!
return library_plan{lib.manifest().name,
lib.path(),
std::move(create_archive),
std::move(link_executables),
lib.manifest().uses,
lib.manifest().links};
}
return library_plan{lib, std::move(create_archive), std::move(link_executables)};
}

+ 21
- 30
src/dds/build/plan/library.hpp Visa fil

@@ -6,6 +6,8 @@
#include <dds/usage_reqs.hpp>
#include <dds/util/fs.hpp>

#include <libman/library.hpp>

#include <optional>
#include <string>
#include <vector>
@@ -29,6 +31,9 @@ struct library_build_params {
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;

/// Libraries that are used by tests
std::vector<lm::usage> test_uses;
};

/**
@@ -47,50 +52,39 @@ struct library_build_params {
* initialize all of the constructor parameters correctly.
*/
class library_plan {
/// The name of the library
std::string _name;
/// The directory at the root of this library
fs::path _source_root;
/// The underlying library object
library _lib;
/// 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 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 lib The `library` object underlying this plan.
* @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,
library_plan(library lib,
std::optional<create_archive_plan> ar,
std::vector<link_executable_plan> exes,
std::vector<lm::usage> uses,
std::vector<lm::usage> links)
: _name(name)
, _source_root(source_root)
std::vector<link_executable_plan> exes)
: _lib(std::move(lib))
, _create_archive(std::move(ar))
, _link_exes(std::move(exes))
, _uses(std::move(uses))
, _links(std::move(links)) {}
, _link_exes(std::move(exes)) {}

/**
* Get the underlying library object
*/
auto& library_() const noexcept { return _lib; }
/**
* Get the name of the library
*/
auto& name() const noexcept { return _name; }
auto& name() const noexcept { return _lib.manifest().name; }
/**
* The directory that defines the source root of the library.
*/
path_ref source_root() const noexcept { return _source_root; }
path_ref source_root() const noexcept { return _lib.path(); }
/**
* A `create_archive_plan` object, or `nullopt`, depending on if this library has compiled
* components
@@ -103,11 +97,11 @@ public:
/**
* The library identifiers that are used by this library
*/
auto& uses() const noexcept { return _uses; }
auto& uses() const noexcept { return _lib.manifest().uses; }
/**
* The library identifiers that are linked by this library
*/
auto& links() const noexcept { return _links; }
auto& links() const noexcept { return _lib.manifest().links; }

/**
* Named constructor: Create a new `library_plan` automatically from some build-time parameters.
@@ -115,14 +109,11 @@ public:
* @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);
static library_plan create(const library& lib, const library_build_params& params);
};

} // namespace dds

+ 23
- 7
src/dds/usage_reqs.cpp Visa fil

@@ -9,8 +9,8 @@

using namespace dds;

const lm::library* usage_requirement_map::get(std::string ns, std::string name) const noexcept {
auto found = _reqs.find(library_key{ns, name});
const lm::library* usage_requirement_map::get(const lm::usage& key) const noexcept {
auto found = _reqs.find(key);
if (found == _reqs.end()) {
return nullptr;
}
@@ -54,21 +54,37 @@ usage_requirement_map usage_requirement_map::from_lm_index(const lm::index& idx)
return ret;
}

std::vector<fs::path> usage_requirement_map::link_paths(std::string ns, std::string name) const {
auto req = get(ns, name);
std::vector<fs::path> usage_requirement_map::link_paths(const lm::usage& key) const {
auto req = get(key);
if (!req) {
throw std::runtime_error(
fmt::format("Unable to find linking requirement '{}/{}'", ns, name));
fmt::format("Unable to find linking requirement '{}/{}'", key.namespace_, key.name));
}
std::vector<fs::path> ret;
if (req->linkable_path) {
ret.push_back(*req->linkable_path);
}
for (const auto& dep : req->uses) {
extend(ret, link_paths(dep.namespace_, dep.name));
extend(ret, link_paths(dep));
}
for (const auto& link : req->links) {
extend(ret, link_paths(link.namespace_, link.name));
extend(ret, link_paths(link));
}
return ret;
}

std::vector<fs::path> usage_requirement_map::include_paths(const lm::usage& usage) const {
std::vector<fs::path> ret;
auto lib = get(usage.namespace_, usage.name);
if (!lib) {
throw std::runtime_error(
fmt::format("Cannot find non-existent usage requirements for '{}/{}'",
usage.namespace_,
usage.name));
}
extend(ret, lib->include_paths);
for (const auto& transitive : lib->uses) {
extend(ret, include_paths(transitive));
}
return ret;
}

+ 7
- 6
src/dds/usage_reqs.hpp Visa fil

@@ -13,10 +13,7 @@ class shared_compile_file_rules;

class usage_requirement_map {

struct library_key {
std::string namespace_;
std::string name;
};
using library_key = lm::usage;

struct library_key_compare {
bool operator()(const library_key& lhs, const library_key& rhs) const noexcept {
@@ -36,12 +33,16 @@ class usage_requirement_map {
std::map<library_key, lm::library, library_key_compare> _reqs;

public:
const lm::library* get(std::string ns, std::string name) const noexcept;
const lm::library* get(const lm::usage& key) const noexcept;
const lm::library* get(std::string ns, std::string name) const noexcept {
return get({ns, name});
}
lm::library& add(std::string ns, std::string name);
void add(std::string ns, std::string name, lm::library lib) { add(ns, name) = lib; }

void apply(shared_compile_file_rules, std::string ns, std::string name) const;
std::vector<fs::path> link_paths(std::string ns, std::string name) const;
std::vector<fs::path> link_paths(const lm::usage&) const;
std::vector<fs::path> include_paths(const lm::usage& req) const;

static usage_requirement_map from_lm_index(const lm::index&) noexcept;
};

Laddar…
Avbryt
Spara