| #include "./build.hpp" | #include "./build.hpp" | ||||
| #include <dds/build/compile.hpp> | |||||
| #include <dds/build/plan/compile_file.hpp> | |||||
| #include <dds/logging.hpp> | #include <dds/logging.hpp> | ||||
| #include <dds/proc.hpp> | #include <dds/proc.hpp> | ||||
| #include <dds/project.hpp> | #include <dds/project.hpp> | ||||
| #include <dds/source.hpp> | #include <dds/source.hpp> | ||||
| #include <dds/toolchain.hpp> | #include <dds/toolchain.hpp> | ||||
| #include <dds/usage_reqs.hpp> | |||||
| #include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
| #include <dds/util/string.hpp> | #include <dds/util/string.hpp> | ||||
| #include <libman/index.hpp> | #include <libman/index.hpp> | ||||
| #include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
| #include <range/v3/action/join.hpp> | |||||
| #include <range/v3/algorithm/for_each.hpp> | |||||
| #include <range/v3/range/conversion.hpp> | #include <range/v3/range/conversion.hpp> | ||||
| #include <range/v3/view/drop.hpp> | |||||
| #include <range/v3/view/filter.hpp> | #include <range/v3/view/filter.hpp> | ||||
| #include <range/v3/view/join.hpp> | |||||
| #include <range/v3/view/transform.hpp> | #include <range/v3/view/transform.hpp> | ||||
| #include <algorithm> | #include <algorithm> | ||||
| } | } | ||||
| for (const auto& use : lib.manifest().uses) { | for (const auto& use : lib.manifest().uses) { | ||||
| pairs.emplace_back("Uses", use); | |||||
| pairs.emplace_back("Uses", fmt::format("{}/{}", use.namespace_, use.name)); | |||||
| } | } | ||||
| for (const auto& links : lib.manifest().links) { | for (const auto& links : lib.manifest().links) { | ||||
| pairs.emplace_back("Links", links); | |||||
| pairs.emplace_back("Links", fmt::format("{}/{}", links.namespace_, links.name)); | |||||
| } | } | ||||
| lm::write_pairs(lml_path, pairs); | lm::write_pairs(lml_path, pairs); | ||||
| lm::write_pairs(export_root / "package.lmp", pairs); | lm::write_pairs(export_root / "package.lmp", pairs); | ||||
| } | } | ||||
| void include_deps(const lm::index::library_index& lib_index, | |||||
| std::vector<fs::path>& includes, | |||||
| std::vector<std::string>& defines, | |||||
| std::string_view usage_key, | |||||
| bool is_public_usage) { | |||||
| auto pair = split(usage_key, "/"); | |||||
| if (pair.size() != 2) { | |||||
| throw compile_failure(fmt::format("Invalid `Uses`: {}", usage_key)); | |||||
| } | |||||
| auto& pkg_ns = pair[0]; | |||||
| auto& lib = pair[1]; | |||||
| auto found = lib_index.find(std::pair(pkg_ns, lib)); | |||||
| if (found == lib_index.end()) { | |||||
| throw compile_failure( | |||||
| fmt::format("No library '{}/{}': Check that it is installed and available", | |||||
| pkg_ns, | |||||
| lib)); | |||||
| } | |||||
| const lm::library& lm_lib = found->second; | |||||
| extend(includes, lm_lib.include_paths); | |||||
| extend(defines, lm_lib.preproc_defs); | |||||
| for (const auto& uses : lm_lib.uses) { | |||||
| include_deps(lib_index, includes, defines, uses, is_public_usage && true); | |||||
| } | |||||
| for (const auto& links : lm_lib.links) { | |||||
| include_deps(lib_index, includes, defines, links, false); | |||||
| } | |||||
| } | |||||
| std::vector<compile_file_plan> file_compilations_of_lib(const build_params& params, | |||||
| const library& lib) { | |||||
| const auto& sources = lib.all_sources(); | |||||
| std::vector<fs::path> dep_includes; | |||||
| std::vector<std::string> dep_defines; | |||||
| if (!lib.manifest().uses.empty() || !lib.manifest().links.empty()) { | |||||
| fs::path lm_index_path = params.lm_index; | |||||
| for (auto cand : {fs::path("INDEX.lmi"), params.out_root / "INDEX.lmi"}) { | |||||
| if (fs::exists(lm_index_path)) { | |||||
| break; | |||||
| } | |||||
| lm_index_path = params.root / cand; | |||||
| usage_requirement_map | |||||
| load_usage_requirements(path_ref project_root, path_ref build_root, path_ref user_lm_index) { | |||||
| fs::path lm_index_path = user_lm_index; | |||||
| for (auto cand : {project_root / "INDEX.lmi", build_root / "INDEX.lmi"}) { | |||||
| if (fs::exists(lm_index_path) || !user_lm_index.empty()) { | |||||
| break; | |||||
| } | } | ||||
| if (!fs::exists(lm_index_path)) { | |||||
| throw compile_failure( | |||||
| "No `INDEX.lmi` found, but we need to pull in dependencies." | |||||
| "Use a package manager to generate an INDEX.lmi"); | |||||
| } | |||||
| auto lm_index = lm::index::from_file(lm_index_path); | |||||
| auto lib_index = lm_index.build_library_index(); | |||||
| for (const auto& uses : lib.manifest().uses) { | |||||
| include_deps(lib_index, dep_includes, dep_defines, uses, true); | |||||
| } | |||||
| for (const auto& links : lib.manifest().links) { | |||||
| include_deps(lib_index, dep_includes, dep_defines, links, false); | |||||
| } | |||||
| spdlog::critical("Dependency resolution isn't fully implemented yet!!"); | |||||
| lm_index_path = cand; | |||||
| } | } | ||||
| if (sources.empty()) { | |||||
| spdlog::info("No source files found to compile"); | |||||
| if (!fs::exists(lm_index_path)) { | |||||
| spdlog::warn("No INDEX.lmi found, so we won't be able to load/use any dependencies"); | |||||
| return {}; | |||||
| } | } | ||||
| auto should_compile_source = [&](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)); | |||||
| }; | |||||
| shared_compile_file_rules rules; | |||||
| extend(rules.defs(), lib.manifest().private_defines); | |||||
| extend(rules.defs(), dep_defines); | |||||
| extend(rules.include_dirs(), lib.manifest().private_includes); | |||||
| extend(rules.include_dirs(), dep_includes); | |||||
| rules.include_dirs().push_back(fs::absolute(lib.path() / "src")); | |||||
| rules.include_dirs().push_back(fs::absolute(lib.path() / "include")); | |||||
| rules.enable_warnings() = params.enable_warnings; | |||||
| return // | |||||
| sources // | |||||
| | filter(should_compile_source) // | |||||
| | transform([&](auto&& src) { | |||||
| return compile_file_plan{rules, | |||||
| "obj/" + lib.manifest().name, | |||||
| src, | |||||
| lib.manifest().name}; | |||||
| }) // | |||||
| | to_vector; | |||||
| lm::index idx = lm::index::from_file(lm_index_path); | |||||
| return usage_requirement_map::from_lm_index(idx); | |||||
| } | } | ||||
| std::vector<dds::compile_file_plan> collect_compiles(const build_params& params, | |||||
| const project& project) { | |||||
| auto libs = iter_libraries(project); | |||||
| return // | |||||
| libs // | |||||
| | transform([&](auto&& lib) { return file_compilations_of_lib(params, lib); }) // | |||||
| | ranges::actions::join // | |||||
| | to_vector // | |||||
| ; | |||||
| } | |||||
| using object_file_index = std::map<fs::path, fs::path>; | |||||
| /** | |||||
| * Obtain the path to the object file that corresponds to the named source file | |||||
| */ | |||||
| fs::path obj_for_source(const object_file_index& idx, path_ref source_path) { | |||||
| auto iter = idx.find(source_path); | |||||
| if (iter == idx.end()) { | |||||
| assert(false && "Lookup on invalid source file"); | |||||
| std::terminate(); | |||||
| } | |||||
| return iter->second; | |||||
| } | |||||
| /** | |||||
| * Create the static library archive for the given library object. | |||||
| */ | |||||
| std::optional<fs::path> create_lib_archive(const build_params& params, | |||||
| const library& lib, | |||||
| const object_file_index& obj_idx) { | |||||
| archive_spec arc; | |||||
| arc.out_path = lib_archive_path(params, lib); | |||||
| // Collect object files that make up that library | |||||
| arc.input_files = // | |||||
| lib.all_sources() // | |||||
| | filter([](auto&& s) { return s.kind == source_kind::source; }) // | |||||
| | transform([&](auto&& s) { return obj_for_source(obj_idx, s.path); }) // | |||||
| | to_vector // | |||||
| ; | |||||
| if (arc.input_files.empty()) { | |||||
| return std::nullopt; | |||||
| } | |||||
| auto ar_cmd = params.toolchain.create_archive_command(arc); | |||||
| if (fs::exists(arc.out_path)) { | |||||
| fs::remove(arc.out_path); | |||||
| } | |||||
| spdlog::info("Create archive for {}: {}", lib.manifest().name, arc.out_path.string()); | |||||
| fs::create_directories(arc.out_path.parent_path()); | |||||
| auto ar_res = run_proc(ar_cmd); | |||||
| if (!ar_res.okay()) { | |||||
| spdlog::error("Failure creating archive library {}", arc.out_path); | |||||
| spdlog::error("Subcommand failed: {}", quote_command(ar_cmd)); | |||||
| spdlog::error("Subcommand produced output:\n{}", ar_res.output); | |||||
| throw archive_failure("Failed to create the library archive"); | |||||
| } | |||||
| return arc.out_path; | |||||
| } | |||||
| /** | |||||
| * Link a single test executable identified by a single source file | |||||
| */ | |||||
| fs::path link_one_exe(path_ref dest, | |||||
| path_ref source_file, | |||||
| const build_params& params, | |||||
| const library& lib, | |||||
| const object_file_index& obj_idx) { | |||||
| auto main_obj = obj_for_source(obj_idx, source_file); | |||||
| assert(fs::exists(main_obj)); | |||||
| link_exe_spec spec; | |||||
| spec.inputs.push_back(main_obj); | |||||
| auto lib_arc_path = lib_archive_path(params, lib); | |||||
| if (fs::is_regular_file(lib_arc_path)) { | |||||
| spec.inputs.push_back(lib_arc_path); | |||||
| } | |||||
| spec.output = dest; | |||||
| const auto link_command = params.toolchain.create_link_executable_command(spec); | |||||
| } // namespace | |||||
| spdlog::info("Create executable: {}", (fs::relative(spec.output, params.out_root)).string()); | |||||
| fs::create_directories(spec.output.parent_path()); | |||||
| auto proc_res = run_proc(link_command); | |||||
| if (!proc_res.okay()) { | |||||
| throw compile_failure( | |||||
| fmt::format("Failed to link test executable '{}'. Link command [{}] returned {}:\n{}", | |||||
| spec.output.string(), | |||||
| quote_command(link_command), | |||||
| proc_res.retc, | |||||
| proc_res.output)); | |||||
| void dds::build(const build_params& params, const package_manifest& man) { | |||||
| auto libs = collect_libraries(params.root); | |||||
| if (!libs.size()) { | |||||
| spdlog::warn("Nothing found to build!"); | |||||
| return; | |||||
| } | } | ||||
| return spec.output; | |||||
| } | |||||
| build_plan plan; | |||||
| auto& pkg = plan.add_package(package_plan(man.name, man.namespace_)); | |||||
| template <typename GetExeNameFn> | |||||
| std::vector<fs::path> link_executables(source_kind sk, | |||||
| GetExeNameFn&& get_exe_path, | |||||
| const build_params& params, | |||||
| const library& lib, | |||||
| const object_file_index& obj_idx) { | |||||
| return // | |||||
| lib.all_sources() // | |||||
| | filter([&](auto&& s) { return s.kind == sk; }) // | |||||
| | transform([&](auto&& s) { | |||||
| return link_one_exe(get_exe_path(s), s.path, params, lib, obj_idx); | |||||
| }) // | |||||
| | to_vector // | |||||
| ; | |||||
| } | |||||
| usage_requirement_map ureqs | |||||
| = load_usage_requirements(params.root, params.out_root, params.lm_index); | |||||
| struct link_results { | |||||
| std::optional<fs::path> archive_path; | |||||
| std::vector<fs::path> test_exes; | |||||
| std::vector<fs::path> app_exes; | |||||
| }; | |||||
| link_results | |||||
| link_project_lib(const build_params& params, const library& lib, const object_file_index& obj_idx) { | |||||
| link_results res; | |||||
| auto op_arc_path = create_lib_archive(params, lib, obj_idx); | |||||
| if (op_arc_path) { | |||||
| res.archive_path = *op_arc_path; | |||||
| } | |||||
| auto get_test_exe_path = [&](const source_file sf) { | |||||
| return params.out_root | |||||
| / fs::relative(sf.path, params.root) | |||||
| .replace_filename(sf.path.stem().stem().string() | |||||
| + params.toolchain.executable_suffix()); | |||||
| }; | |||||
| auto get_app_exe_path = [&](const source_file& sf) { | |||||
| return params.out_root | |||||
| / (sf.path.stem().stem().string() + params.toolchain.executable_suffix()); | |||||
| }; | |||||
| // Link test executables | |||||
| if (params.build_tests) { | |||||
| extend(res.test_exes, | |||||
| link_executables(source_kind::test, get_test_exe_path, params, lib, obj_idx)); | |||||
| library_build_params lib_params; | |||||
| lib_params.build_tests = params.build_tests; | |||||
| lib_params.build_apps = params.build_apps; | |||||
| 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)); | |||||
| } | } | ||||
| if (params.build_apps) { | |||||
| extend(res.app_exes, | |||||
| link_executables(source_kind::app, get_app_exe_path, params, lib, obj_idx)); | |||||
| } | |||||
| return res; | |||||
| } | |||||
| std::vector<link_results> link_project(const build_params& params, | |||||
| const project& pr, | |||||
| const std::vector<compile_file_plan>& compilations) { | |||||
| auto obj_index = // | |||||
| ranges::views::all(compilations) // | |||||
| | transform([&](const compile_file_plan& comp) -> std::pair<fs::path, fs::path> { | |||||
| return std::pair(comp.source.path, | |||||
| comp.get_object_file_path( | |||||
| build_env{params.toolchain, params.out_root})); | |||||
| }) // | |||||
| | ranges::to<object_file_index>() // | |||||
| ; | |||||
| auto libs = iter_libraries(pr); | |||||
| return libs // | |||||
| | transform([&](auto&& lib) { return link_project_lib(params, lib, obj_index); }) // | |||||
| | to_vector; | |||||
| } | |||||
| } // namespace | |||||
| void dds::build(const build_params& params, const package_manifest&) { | |||||
| auto libs = collect_libraries(params.root); | |||||
| // auto sroot = dds::sroot{params.root}; | |||||
| // auto comp_rules = sroot.base_compile_rules(); | |||||
| // sroot_build_params sr_params; | |||||
| // sr_params.main_name = man.name; | |||||
| // sr_params.build_tests = params.build_tests; | |||||
| // sr_params.build_apps = params.build_apps; | |||||
| // sr_params.compile_rules = comp_rules; | |||||
| // build_plan plan; | |||||
| // plan.add_sroot(sroot, sr_params); | |||||
| // plan.compile_all(params.toolchain, params.parallel_jobs, params.out_root); | |||||
| dds::build_env env{params.toolchain, params.out_root}; | |||||
| plan.compile_all(env, params.parallel_jobs); | |||||
| plan.archive_all(env, params.parallel_jobs); | |||||
| plan.link_all(env, params.parallel_jobs); | |||||
| auto project = project::from_directory(params.root); | auto project = project::from_directory(params.root); | ||||
| auto compiles = collect_compiles(params, project); | |||||
| dds::build_env env{params.toolchain, params.out_root}; | |||||
| dds::execute_all(compiles, params.parallel_jobs, env); | |||||
| // dds::execute_all(compiles, params.parallel_jobs, env); | |||||
| using namespace ranges::views; | using namespace ranges::views; | ||||
| auto link_res = link_project(params, project, compiles); | |||||
| auto all_tests = link_res // | |||||
| | transform([](auto&& link) { return link.test_exes; }) // | |||||
| | ranges::actions::join; | |||||
| int n_test_fails = 0; | |||||
| for (path_ref test_exe : all_tests) { | |||||
| spdlog::info("Running test: {}", fs::relative(test_exe, params.out_root).string()); | |||||
| const auto test_res = run_proc({test_exe.string()}); | |||||
| if (!test_res.okay()) { | |||||
| spdlog::error("TEST FAILED\n{}", test_res.output); | |||||
| n_test_fails++; | |||||
| } | |||||
| } | |||||
| if (n_test_fails) { | |||||
| throw compile_failure("Test failures during build"); | |||||
| } | |||||
| if (params.do_export) { | |||||
| export_project(params, project); | |||||
| } | |||||
| // auto link_res = link_project(params, project, compiles); | |||||
| // auto all_tests = link_res // | |||||
| // | transform([](auto&& link) { return link.test_exes; }) // | |||||
| // | ranges::actions::join; | |||||
| // int n_test_fails = 0; | |||||
| // for (path_ref test_exe : all_tests) { | |||||
| // spdlog::info("Running test: {}", fs::relative(test_exe, params.out_root).string()); | |||||
| // const auto test_res = run_proc({test_exe.string()}); | |||||
| // if (!test_res.okay()) { | |||||
| // spdlog::error("TEST FAILED\n{}", test_res.output); | |||||
| // n_test_fails++; | |||||
| // } | |||||
| // } | |||||
| // if (n_test_fails) { | |||||
| // throw compile_failure("Test failures during build"); | |||||
| // } | |||||
| // if (params.do_export) { | |||||
| // export_project(params, project); | |||||
| // } | |||||
| } | } |
| #include "./compile.hpp" | |||||
| #include <dds/proc.hpp> | |||||
| #include <dds/util/algo.hpp> | |||||
| #include <dds/util/signal.hpp> | |||||
| #include <spdlog/spdlog.h> | |||||
| #include <atomic> | |||||
| #include <mutex> | |||||
| #include <thread> | |||||
| #include <vector> | |||||
| using namespace dds; | |||||
| void compile_file_plan::compile(const build_env& env) const { | |||||
| const auto obj_path = get_object_file_path(env); | |||||
| fs::create_directories(obj_path.parent_path()); | |||||
| spdlog::info("[{}] Compile: {}", | |||||
| qualifier, | |||||
| fs::relative(source.path, source.basis_path).string()); | |||||
| auto start_time = std::chrono::steady_clock::now(); | |||||
| compile_file_spec spec{source.path, obj_path}; | |||||
| spec.enable_warnings = rules.enable_warnings(); | |||||
| extend(spec.include_dirs, rules.include_dirs()); | |||||
| extend(spec.definitions, rules.defs()); | |||||
| auto cmd = env.toolchain.create_compile_command(spec); | |||||
| auto compile_res = run_proc(cmd); | |||||
| auto end_time = std::chrono::steady_clock::now(); | |||||
| auto dur_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time); | |||||
| spdlog::info("[{}] Compile: {} - {:n}ms", | |||||
| qualifier, | |||||
| fs::relative(source.path, source.basis_path).string(), | |||||
| dur_ms.count()); | |||||
| if (!compile_res.okay()) { | |||||
| spdlog::error("Compilation failed: {}", source.path.string()); | |||||
| spdlog::error("Subcommand FAILED: {}\n{}", quote_command(cmd), compile_res.output); | |||||
| throw compile_failure(fmt::format("Compilation failed for {}", source.path.string())); | |||||
| } | |||||
| // MSVC prints the filename of the source file. Dunno why, but they do. | |||||
| if (compile_res.output.find(spec.source_path.filename().string() + "\r\n") == 0) { | |||||
| compile_res.output.erase(0, spec.source_path.filename().string().length() + 2); | |||||
| } | |||||
| if (!compile_res.output.empty()) { | |||||
| spdlog::warn("While compiling file {} [{}]:\n{}", | |||||
| spec.source_path.string(), | |||||
| quote_command(cmd), | |||||
| compile_res.output); | |||||
| } | |||||
| } | |||||
| fs::path compile_file_plan::get_object_file_path(const build_env& env) const noexcept { | |||||
| auto relpath = fs::relative(source.path, source.basis_path); | |||||
| auto ret = env.output_root / subdir / relpath; | |||||
| ret.replace_filename(relpath.filename().string() + env.toolchain.object_suffix()); | |||||
| return ret; | |||||
| } | |||||
| void dds::execute_all(const std::vector<compile_file_plan>& compilations, | |||||
| int n_jobs, | |||||
| const build_env& env) { | |||||
| // We don't bother with a nice thread pool, as the overhead of compiling | |||||
| // source files dwarfs the cost of interlocking. | |||||
| std::mutex mut; | |||||
| auto comp_iter = compilations.begin(); | |||||
| const auto end_iter = compilations.end(); | |||||
| std::vector<std::exception_ptr> exceptions; | |||||
| auto compile_one = [&]() mutable { | |||||
| while (true) { | |||||
| std::unique_lock lk{mut}; | |||||
| if (!exceptions.empty()) { | |||||
| break; | |||||
| } | |||||
| if (comp_iter == end_iter) { | |||||
| break; | |||||
| } | |||||
| auto& compilation = *comp_iter++; | |||||
| lk.unlock(); | |||||
| try { | |||||
| compilation.compile(env); | |||||
| cancellation_point(); | |||||
| } catch (...) { | |||||
| lk.lock(); | |||||
| exceptions.push_back(std::current_exception()); | |||||
| break; | |||||
| } | |||||
| } | |||||
| }; | |||||
| std::unique_lock lk{mut}; | |||||
| std::vector<std::thread> threads; | |||||
| if (n_jobs < 1) { | |||||
| n_jobs = std::thread::hardware_concurrency() + 2; | |||||
| } | |||||
| std::generate_n(std::back_inserter(threads), n_jobs, [&] { return std::thread(compile_one); }); | |||||
| spdlog::info("Parallel compile with {} threads", threads.size()); | |||||
| lk.unlock(); | |||||
| for (auto& t : threads) { | |||||
| t.join(); | |||||
| } | |||||
| for (auto eptr : exceptions) { | |||||
| try { | |||||
| std::rethrow_exception(eptr); | |||||
| } catch (const std::exception& e) { | |||||
| spdlog::error(e.what()); | |||||
| } | |||||
| } | |||||
| if (!exceptions.empty()) { | |||||
| throw compile_failure("Failed to compile library sources"); | |||||
| } | |||||
| } |
| #include <dds/proc.hpp> | #include <dds/proc.hpp> | ||||
| #include <range/v3/action/join.hpp> | #include <range/v3/action/join.hpp> | ||||
| #include <range/v3/view/concat.hpp> | |||||
| #include <range/v3/view/filter.hpp> | #include <range/v3/view/filter.hpp> | ||||
| #include <range/v3/view/join.hpp> | #include <range/v3/view/join.hpp> | ||||
| #include <range/v3/view/repeat_n.hpp> | |||||
| #include <range/v3/view/transform.hpp> | #include <range/v3/view/transform.hpp> | ||||
| #include <range/v3/view/zip.hpp> | |||||
| #include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||
| using namespace dds; | using namespace dds; | ||||
| library_plan library_plan::create(const library& lib, const library_build_params& params) { | |||||
| std::vector<compile_file_plan> compile_files; | |||||
| std::vector<create_exe_plan> link_executables; | |||||
| std::optional<create_archive_plan> create_archive; | |||||
| bool should_create_archive = false; | |||||
| std::vector<source_file> app_sources; | |||||
| std::vector<source_file> test_sources; | |||||
| std::vector<source_file> lib_sources; | |||||
| auto src_dir = lib.src_dir(); | |||||
| if (src_dir.exists()) { | |||||
| 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) { | |||||
| compile_file_plan cf_plan; | |||||
| cf_plan.source = sfile; | |||||
| cf_plan.qualifier = lib.manifest().name; | |||||
| cf_plan.rules = params.compile_rules; | |||||
| cf_plan.subdir = fs::path("obj") / lib.manifest().name; | |||||
| compile_files.push_back(std::move(cf_plan)); | |||||
| if (sfile.kind == source_kind::test) { | |||||
| test_sources.push_back(sfile); | |||||
| } else if (sfile.kind == source_kind::app) { | |||||
| app_sources.push_back(sfile); | |||||
| } else { | |||||
| should_create_archive = true; | |||||
| lib_sources.push_back(sfile); | |||||
| } | |||||
| } | |||||
| } | |||||
| if (!app_sources.empty() || !test_sources.empty()) { | |||||
| spdlog::critical("Apps/tests not implemented on this code path"); | |||||
| } | |||||
| if (should_create_archive) { | |||||
| create_archive_plan ar_plan; | |||||
| ar_plan.name = lib.manifest().name; | |||||
| ar_plan.out_dir = params.out_subdir; | |||||
| create_archive.emplace(std::move(ar_plan)); | |||||
| } | |||||
| return library_plan{lib.manifest().name, | |||||
| lib.path(), | |||||
| params.out_subdir, | |||||
| compile_files, | |||||
| create_archive, | |||||
| link_executables}; | |||||
| } | |||||
| namespace { | namespace { | ||||
| template <typename Range, typename Fn> | template <typename Range, typename Fn> | ||||
| } // namespace | } // namespace | ||||
| fs::path create_archive_plan::archive_file_path(const build_env& env) const noexcept { | |||||
| return env.output_root / fmt::format("{}{}{}", "lib", name, env.toolchain.archive_suffix()); | |||||
| } | |||||
| void create_archive_plan::archive(const build_env& env, | |||||
| const std::vector<fs::path>& objects) const { | |||||
| archive_spec ar; | |||||
| ar.input_files = objects; | |||||
| ar.out_path = archive_file_path(env); | |||||
| auto ar_cmd = env.toolchain.create_archive_command(ar); | |||||
| auto out_relpath = fs::relative(ar.out_path, env.output_root).string(); | |||||
| spdlog::info("[{}] Archive: {}", name, out_relpath); | |||||
| auto start_time = std::chrono::steady_clock::now(); | |||||
| auto ar_res = run_proc(ar_cmd); | |||||
| auto end_time = std::chrono::steady_clock::now(); | |||||
| auto dur_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time); | |||||
| spdlog::info("[{}] Archive: {} - {:n}ms", name, out_relpath, dur_ms.count()); | |||||
| if (!ar_res.okay()) { | |||||
| spdlog::error("Creating static library archive failed: {}", out_relpath); | |||||
| spdlog::error("Subcommand FAILED: {}\n{}", quote_command(ar_cmd), ar_res.output); | |||||
| throw std::runtime_error( | |||||
| fmt::format("Creating archive [{}] failed for '{}'", out_relpath, name)); | |||||
| } | |||||
| } | |||||
| namespace { | namespace { | ||||
| auto all_libraries(const build_plan& plan) { | auto all_libraries(const build_plan& plan) { | ||||
| return // | |||||
| plan.build_packages // | |||||
| | ranges::views::transform(&package_plan::create_libraries) // | |||||
| | ranges::views::join // | |||||
| return // | |||||
| plan.packages() // | |||||
| | ranges::views::transform(&package_plan::libraries) // | |||||
| | ranges::views::join // | |||||
| ; | ; | ||||
| } | } | ||||
| } // namespace | } // namespace | ||||
| 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 all_compiles = // | |||||
| all_libraries(*this) // | |||||
| | ranges::views::transform(&library_plan::compile_files) // | |||||
| | ranges::views::join // | |||||
| auto lib_compiles = // | |||||
| all_libraries(*this) // | |||||
| | ranges::views::transform(&library_plan::create_archive) // | |||||
| | ranges::views::filter([&](auto&& opt) { return bool(opt); }) // | |||||
| | ranges::views::transform([&](auto&& opt) -> auto& { return opt->compile_files(); }) // | |||||
| | ranges::views::join // | |||||
| ; | |||||
| auto exe_compiles = // | |||||
| all_libraries(*this) // | |||||
| | ranges::views::transform(&library_plan::executables) // | |||||
| | ranges::views::join // | |||||
| | ranges::views::transform(&link_executable_plan::main_compile_file) // | |||||
| ; | ; | ||||
| auto okay = parallel_run(all_compiles, njobs, [&](const auto& cf) { cf.compile(env); }); | |||||
| auto all_compiles = ranges::views::concat(lib_compiles, exe_compiles); | |||||
| auto okay | |||||
| = parallel_run(all_compiles, njobs, [&](const compile_file_plan& cf) { cf.compile(env); }); | |||||
| if (!okay) { | if (!okay) { | ||||
| throw std::runtime_error("Compilation failed."); | throw std::runtime_error("Compilation failed."); | ||||
| } | } | ||||
| void build_plan::archive_all(const build_env& env, int njobs) const { | void build_plan::archive_all(const build_env& env, int njobs) const { | ||||
| parallel_run(all_libraries(*this), njobs, [&](const library_plan& lib) { | parallel_run(all_libraries(*this), njobs, [&](const library_plan& lib) { | ||||
| if (!lib.create_archive) { | |||||
| return; | |||||
| if (lib.create_archive()) { | |||||
| lib.create_archive()->archive(env); | |||||
| } | } | ||||
| const auto& objects = ranges::views::all(lib.compile_files) // | |||||
| | ranges::views::filter([](auto&& comp) { | |||||
| return comp.source.kind == source_kind::source; | |||||
| }) // | |||||
| | ranges::views::transform( | |||||
| [&](auto&& comp) { return comp.get_object_file_path(env); }) // | |||||
| | ranges::to_vector // | |||||
| ; | |||||
| lib.create_archive->archive(env, objects); | |||||
| }); | }); | ||||
| } | } | ||||
| void build_plan::link_all(const build_env& env, int) const { | |||||
| for (auto&& lib : all_libraries(*this)) { | |||||
| for (auto&& exe : lib.executables()) { | |||||
| exe.link(env, lib); | |||||
| } | |||||
| } | |||||
| } |
| #pragma once | #pragma once | ||||
| #include <dds/build/compile.hpp> | |||||
| #include <dds/build/params.hpp> | #include <dds/build/params.hpp> | ||||
| #include <dds/build/plan/archive.hpp> | |||||
| #include <dds/build/plan/compile_file.hpp> | |||||
| #include <dds/build/plan/exe.hpp> | |||||
| #include <dds/build/plan/library.hpp> | |||||
| #include <dds/build/plan/package.hpp> | |||||
| #include <dds/library.hpp> | #include <dds/library.hpp> | ||||
| #include <dds/toolchain.hpp> | |||||
| #include <dds/util/fs.hpp> | |||||
| namespace dds { | namespace dds { | ||||
| struct create_archive_plan { | |||||
| std::string name; | |||||
| fs::path out_dir; | |||||
| fs::path archive_file_path(const build_env& env) const noexcept; | |||||
| void archive(const build_env& env, const std::vector<fs::path>& objects) const; | |||||
| }; | |||||
| struct create_exe_plan { | |||||
| std::vector<fs::path> in_sources; | |||||
| std::string name; | |||||
| fs::path out_dir; | |||||
| }; | |||||
| struct library_plan { | |||||
| std::string name; | |||||
| fs::path source_root; | |||||
| fs::path out_subdir; | |||||
| std::vector<compile_file_plan> compile_files; | |||||
| std::optional<create_archive_plan> create_archive; | |||||
| std::vector<create_exe_plan> link_executables; | |||||
| class build_plan { | |||||
| std::vector<package_plan> _packages; | |||||
| static library_plan create(const library& lib, const library_build_params& params); | |||||
| }; | |||||
| struct package_plan { | |||||
| std::string name; | |||||
| std::string namespace_; | |||||
| std::vector<std::string> pkg_requires; | |||||
| std::vector<library_plan> create_libraries; | |||||
| void add_library(const library& lib, const library_build_params& params) { | |||||
| create_libraries.push_back(library_plan::create(lib, params)); | |||||
| public: | |||||
| package_plan& add_package(package_plan p) noexcept { | |||||
| return _packages.emplace_back(std::move(p)); | |||||
| } | } | ||||
| }; | |||||
| struct build_plan { | |||||
| std::vector<package_plan> build_packages; | |||||
| void compile_all(const build_env& env, int njobs) const; | |||||
| void archive_all(const build_env& env, int njobs) const; | |||||
| auto& packages() const noexcept { return _packages; } | |||||
| void compile_all(const build_env& env, int njobs) const; | |||||
| void archive_all(const build_env& env, int njobs) const; | |||||
| void link_all(const build_env& env, int njobs) const; | |||||
| }; | }; | ||||
| } // namespace dds | } // namespace dds |
| #include "./archive.hpp" | |||||
| #include <dds/proc.hpp> | |||||
| #include <range/v3/view/transform.hpp> | |||||
| #include <spdlog/spdlog.h> | |||||
| using namespace dds; | |||||
| fs::path create_archive_plan::calc_archive_file_path(const build_env& env) const noexcept { | |||||
| return env.output_root / fmt::format("{}{}{}", "lib", _name, env.toolchain.archive_suffix()); | |||||
| } | |||||
| void create_archive_plan::archive(const build_env& env) const { | |||||
| const auto objects = // | |||||
| _compile_files // | |||||
| | ranges::views::transform([&](auto&& cf) { return cf.calc_object_file_path(env); }) | |||||
| | ranges::to_vector // | |||||
| ; | |||||
| archive_spec ar; | |||||
| ar.input_files = std::move(objects); | |||||
| ar.out_path = calc_archive_file_path(env); | |||||
| auto ar_cmd = env.toolchain.create_archive_command(ar); | |||||
| auto out_relpath = fs::relative(ar.out_path, env.output_root).string(); | |||||
| if (fs::exists(ar.out_path)) { | |||||
| fs::remove(ar.out_path); | |||||
| } | |||||
| spdlog::info("[{}] Archive: {}", _name, out_relpath); | |||||
| auto start_time = std::chrono::steady_clock::now(); | |||||
| auto ar_res = run_proc(ar_cmd); | |||||
| auto end_time = std::chrono::steady_clock::now(); | |||||
| auto dur_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time); | |||||
| spdlog::info("[{}] Archive: {} - {:n}ms", _name, out_relpath, dur_ms.count()); | |||||
| if (!ar_res.okay()) { | |||||
| spdlog::error("Creating static library archive failed: {}", out_relpath); | |||||
| spdlog::error("Subcommand FAILED: {}\n{}", quote_command(ar_cmd), ar_res.output); | |||||
| throw std::runtime_error( | |||||
| fmt::format("Creating archive [{}] failed for '{}'", out_relpath, _name)); | |||||
| } | |||||
| } |
| #pragma once | |||||
| #include <dds/build/plan/compile_file.hpp> | |||||
| #include <dds/util/fs.hpp> | |||||
| #include <string> | |||||
| namespace dds { | |||||
| class create_archive_plan { | |||||
| std::string _name; | |||||
| fs::path _subdir; | |||||
| std::vector<compile_file_plan> _compile_files; | |||||
| public: | |||||
| create_archive_plan(std::string_view name, path_ref subdir, std::vector<compile_file_plan> cfs) | |||||
| : _name(name) | |||||
| , _subdir(subdir) | |||||
| , _compile_files(std::move(cfs)) {} | |||||
| fs::path calc_archive_file_path(build_env_ref env) const noexcept; | |||||
| auto& compile_files() const noexcept { return _compile_files; } | |||||
| void archive(build_env_ref env) const; | |||||
| }; | |||||
| } // namespace dds |
| #pragma once | |||||
| #include <dds/toolchain.hpp> | |||||
| #include <dds/util/fs.hpp> | |||||
| namespace dds { | |||||
| struct build_env { | |||||
| dds::toolchain toolchain; | |||||
| fs::path output_root; | |||||
| }; | |||||
| using build_env_ref = const build_env&; | |||||
| } // namespace dds |
| #include "./compile_file.hpp" | |||||
| #include <dds/proc.hpp> | |||||
| #include <dds/util/algo.hpp> | |||||
| #include <dds/util/signal.hpp> | |||||
| #include <spdlog/spdlog.h> | |||||
| #include <atomic> | |||||
| #include <mutex> | |||||
| #include <thread> | |||||
| #include <vector> | |||||
| using namespace dds; | |||||
| void compile_file_plan::compile(const build_env& env) const { | |||||
| const auto obj_path = calc_object_file_path(env); | |||||
| fs::create_directories(obj_path.parent_path()); | |||||
| spdlog::info("[{}] Compile: {}", | |||||
| _qualifier, | |||||
| fs::relative(_source.path, _source.basis_path).string()); | |||||
| auto start_time = std::chrono::steady_clock::now(); | |||||
| compile_file_spec spec{_source.path, obj_path}; | |||||
| spec.enable_warnings = _rules.enable_warnings(); | |||||
| extend(spec.include_dirs, _rules.include_dirs()); | |||||
| extend(spec.definitions, _rules.defs()); | |||||
| auto cmd = env.toolchain.create_compile_command(spec); | |||||
| auto compile_res = run_proc(cmd); | |||||
| auto end_time = std::chrono::steady_clock::now(); | |||||
| auto dur_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time); | |||||
| spdlog::info("[{}] Compile: {} - {:n}ms", | |||||
| _qualifier, | |||||
| fs::relative(_source.path, _source.basis_path).string(), | |||||
| dur_ms.count()); | |||||
| if (!compile_res.okay()) { | |||||
| spdlog::error("Compilation failed: {}", _source.path.string()); | |||||
| spdlog::error("Subcommand FAILED: {}\n{}", quote_command(cmd), compile_res.output); | |||||
| throw compile_failure(fmt::format("Compilation failed for {}", _source.path.string())); | |||||
| } | |||||
| // MSVC prints the filename of the source file. Dunno why, but they do. | |||||
| if (compile_res.output.find(spec.source_path.filename().string() + "\r\n") == 0) { | |||||
| compile_res.output.erase(0, spec.source_path.filename().string().length() + 2); | |||||
| } | |||||
| if (!compile_res.output.empty()) { | |||||
| spdlog::warn("While compiling file {} [{}]:\n{}", | |||||
| spec.source_path.string(), | |||||
| quote_command(cmd), | |||||
| compile_res.output); | |||||
| } | |||||
| } | |||||
| fs::path compile_file_plan::calc_object_file_path(const build_env& env) const noexcept { | |||||
| auto relpath = fs::relative(_source.path, _source.basis_path); | |||||
| auto ret = env.output_root / _subdir / relpath; | |||||
| ret.replace_filename(relpath.filename().string() + env.toolchain.object_suffix()); | |||||
| return ret; | |||||
| } |
| #pragma once | #pragma once | ||||
| #include <dds/build/plan/base.hpp> | |||||
| #include <dds/source.hpp> | #include <dds/source.hpp> | ||||
| #include <dds/toolchain.hpp> | |||||
| #include <dds/util/fs.hpp> | |||||
| #include <memory> | #include <memory> | ||||
| #include <optional> | |||||
| #include <stdexcept> | |||||
| namespace dds { | namespace dds { | ||||
| struct build_env { | |||||
| dds::toolchain toolchain; | |||||
| fs::path output_root; | |||||
| }; | |||||
| struct compile_failure : std::runtime_error { | struct compile_failure : std::runtime_error { | ||||
| using runtime_error::runtime_error; | using runtime_error::runtime_error; | ||||
| }; | }; | ||||
| auto& enable_warnings() const noexcept { return _impl->enable_warnings; } | auto& enable_warnings() const noexcept { return _impl->enable_warnings; } | ||||
| }; | }; | ||||
| struct compile_file_plan { | |||||
| shared_compile_file_rules rules; | |||||
| fs::path subdir; | |||||
| dds::source_file source; | |||||
| std::string qualifier; | |||||
| class compile_file_plan { | |||||
| shared_compile_file_rules _rules; | |||||
| source_file _source; | |||||
| std::string _qualifier; | |||||
| fs::path _subdir; | |||||
| fs::path get_object_file_path(const build_env& env) const noexcept; | |||||
| void compile(const build_env&) const; | |||||
| public: | |||||
| compile_file_plan(shared_compile_file_rules rules, | |||||
| source_file sf, | |||||
| std::string_view qual, | |||||
| path_ref subdir) | |||||
| : _rules(rules) | |||||
| , _source(std::move(sf)) | |||||
| , _qualifier(qual) | |||||
| , _subdir(subdir) {} | |||||
| const source_file& source() const noexcept { return _source; } | |||||
| path_ref source_path() const noexcept { return _source.path; } | |||||
| fs::path calc_object_file_path(build_env_ref env) const noexcept; | |||||
| void compile(build_env_ref) const; | |||||
| }; | }; | ||||
| void execute_all(const std::vector<compile_file_plan>&, int n_jobs, const build_env& env); | |||||
| } // namespace dds | |||||
| } // namespace dds |
| #include "./exe.hpp" | |||||
| #include <dds/build/plan/library.hpp> | |||||
| #include <dds/proc.hpp> | |||||
| #include <range/v3/algorithm/find_if.hpp> | |||||
| #include <spdlog/spdlog.h> | |||||
| #include <cassert> | |||||
| using namespace dds; | |||||
| fs::path link_executable_plan::calc_executable_path(build_env_ref env) const noexcept { | |||||
| return env.output_root / _out_subdir / (_name + env.toolchain.executable_suffix()); | |||||
| } | |||||
| void link_executable_plan::link(build_env_ref env, const library_plan& lib) const { | |||||
| const auto out_path = calc_executable_path(env); | |||||
| link_exe_spec spec; | |||||
| spec.output = out_path; | |||||
| spec.inputs = _input_libs; | |||||
| if (lib.create_archive()) { | |||||
| spec.inputs.push_back(lib.create_archive()->calc_archive_file_path(env)); | |||||
| auto main_obj = _main_compile.calc_object_file_path(env); | |||||
| spec.inputs.push_back(std::move(main_obj)); | |||||
| } | |||||
| const auto link_command = env.toolchain.create_link_executable_command(spec); | |||||
| spdlog::info("Linking executable: {}", fs::relative(spec.output, env.output_root).string()); | |||||
| fs::create_directories(out_path.parent_path()); | |||||
| auto proc_res = run_proc(link_command); | |||||
| if (!proc_res.okay()) { | |||||
| throw compile_failure( | |||||
| fmt::format("Failed to link test executable '{}'. Link command [{}] returned {}:\n{}", | |||||
| spec.output.string(), | |||||
| quote_command(link_command), | |||||
| proc_res.retc, | |||||
| proc_res.output)); | |||||
| } | |||||
| } |
| #pragma once | |||||
| #include <dds/build/plan/compile_file.hpp> | |||||
| #include <dds/util/fs.hpp> | |||||
| #include <set> | |||||
| #include <tuple> | |||||
| #include <vector> | |||||
| namespace dds { | |||||
| class library_plan; | |||||
| class link_executable_plan { | |||||
| std::vector<fs::path> _input_libs; | |||||
| compile_file_plan _main_compile; | |||||
| fs::path _out_subdir; | |||||
| std::string _name; | |||||
| public: | |||||
| link_executable_plan(std::vector<fs::path> in_libs, | |||||
| compile_file_plan cfp, | |||||
| path_ref out_subdir, | |||||
| std::string name_) | |||||
| : _input_libs(std::move(in_libs)) | |||||
| , _main_compile(std::move(cfp)) | |||||
| , _out_subdir(out_subdir) | |||||
| , _name(std::move(name_)) {} | |||||
| auto& main_compile_file() const noexcept { return _main_compile; } | |||||
| fs::path calc_executable_path(const build_env& env) const noexcept; | |||||
| void link(const build_env&, const library_plan&) const; | |||||
| }; | |||||
| } // namespace dds |
| #pragma once | |||||
| #include <build/plan/package.hpp> | |||||
| #include <map> | |||||
| namespace dds { | |||||
| class build_plan2 {}; | |||||
| } // namespace dds |
| #include "./library.hpp" | |||||
| #include <dds/util/algo.hpp> | |||||
| #include <range/v3/view/filter.hpp> | |||||
| #include <range/v3/view/concat.hpp> | |||||
| #include <spdlog/spdlog.h> | |||||
| 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; | |||||
| std::vector<source_file> app_sources; | |||||
| std::vector<source_file> test_sources; | |||||
| std::vector<source_file> lib_sources; | |||||
| auto src_dir = lib.src_dir(); | |||||
| if (src_dir.exists()) { | |||||
| 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) { | |||||
| if (sfile.kind == source_kind::test) { | |||||
| test_sources.push_back(sfile); | |||||
| } else if (sfile.kind == source_kind::app) { | |||||
| app_sources.push_back(sfile); | |||||
| } else { | |||||
| lib_sources.push_back(sfile); | |||||
| } | |||||
| } | |||||
| } | |||||
| auto compile_rules = lib.base_compile_rules(); | |||||
| 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"); | |||||
| } | |||||
| if (!lib_sources.empty()) { | |||||
| create_archive.emplace(lib.manifest().name, params.out_subdir, std::move(compile_files)); | |||||
| } | |||||
| std::vector<fs::path> in_libs; | |||||
| for (auto& use : lib.manifest().uses) { | |||||
| extend(in_libs, ureqs.link_paths(use.namespace_, use.name)); | |||||
| } | |||||
| for (auto& link : lib.manifest().links) { | |||||
| extend(in_libs, ureqs.link_paths(link.namespace_, link.name)); | |||||
| } | |||||
| for (const source_file& source : ranges::views::concat(app_sources, test_sources)) { | |||||
| auto subdir = source.kind == source_kind::test ? params.out_subdir / "test" : params.out_subdir; | |||||
| link_executables.emplace_back(in_libs, | |||||
| compile_file_plan(compile_rules, | |||||
| source, | |||||
| lib.manifest().name, | |||||
| params.out_subdir / "obj"), | |||||
| subdir, | |||||
| source.path.stem().stem().string()); | |||||
| } | |||||
| if (!app_sources.empty()) { | |||||
| spdlog::critical("Apps not implemented on this code path!"); | |||||
| } | |||||
| return library_plan{lib.manifest().name, | |||||
| lib.path(), | |||||
| std::move(create_archive), | |||||
| std::move(link_executables)}; | |||||
| } |
| #pragma once | |||||
| #include <dds/build/plan/archive.hpp> | |||||
| #include <dds/build/plan/exe.hpp> | |||||
| #include <dds/library.hpp> | |||||
| #include <dds/usage_reqs.hpp> | |||||
| #include <dds/util/fs.hpp> | |||||
| #include <map> | |||||
| #include <optional> | |||||
| #include <string> | |||||
| #include <vector> | |||||
| namespace dds { | |||||
| class library_plan { | |||||
| std::string _name; | |||||
| fs::path _source_root; | |||||
| std::optional<create_archive_plan> _create_archive; | |||||
| std::vector<link_executable_plan> _link_exes; | |||||
| public: | |||||
| library_plan(std::string_view name, | |||||
| path_ref source_root, | |||||
| std::optional<create_archive_plan> ar, | |||||
| std::vector<link_executable_plan> exes) | |||||
| : _name(name) | |||||
| , _source_root(source_root) | |||||
| , _create_archive(std::move(ar)) | |||||
| , _link_exes(std::move(exes)) {} | |||||
| 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; } | |||||
| static library_plan | |||||
| create(const library&, const library_build_params&, const usage_requirement_map&); | |||||
| }; | |||||
| } // namespace dds |
| #pragma once | |||||
| #include <dds/build/plan/library.hpp> | |||||
| #include <map> | |||||
| #include <string> | |||||
| namespace dds { | |||||
| class package_plan { | |||||
| std::string _name; | |||||
| std::string _namespace; | |||||
| std::vector<library_plan> _libraries; | |||||
| public: | |||||
| package_plan(std::string_view name, std::string_view namespace_) | |||||
| : _name(name) | |||||
| , _namespace(namespace_) {} | |||||
| void add_library(library_plan lp) { _libraries.emplace_back(std::move(lp)); } | |||||
| auto& name() const noexcept { return _name; } | |||||
| auto& namespace_() const noexcept { return _namespace; } | |||||
| auto& libraries() const noexcept { return _libraries; } | |||||
| }; | |||||
| } // namespace dds |
| man.dependencies.end()); | man.dependencies.end()); | ||||
| }); | }); | ||||
| auto plan = dds::create_deps_build_plan(deps); | |||||
| auto tc = tc_filepath.get_toolchain(); | auto tc = tc_filepath.get_toolchain(); | ||||
| auto bdir = build_dir.Get(); | auto bdir = build_dir.Get(); | ||||
| dds::build_env env{std::move(tc), bdir}; | dds::build_env env{std::move(tc), bdir}; | ||||
| auto plan = dds::create_deps_build_plan(deps, env); | |||||
| plan.compile_all(env, 6); | plan.compile_all(env, 6); | ||||
| plan.archive_all(env, 6); | plan.archive_all(env, 6); | ||||
| if (!no_lmi.Get()) { | if (!no_lmi.Get()) { |
| #include <dds/repo/repo.hpp> | #include <dds/repo/repo.hpp> | ||||
| #include <dds/sdist.hpp> | #include <dds/sdist.hpp> | ||||
| #include <dds/usage_reqs.hpp> | |||||
| #include <dds/util/algo.hpp> | |||||
| #include <dds/util/string.hpp> | #include <dds/util/string.hpp> | ||||
| #include <libman/index.hpp> | #include <libman/index.hpp> | ||||
| #include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
| } | } | ||||
| using sdist_index_type = std::map<std::string, std::reference_wrapper<const sdist>>; | using sdist_index_type = std::map<std::string, std::reference_wrapper<const sdist>>; | ||||
| using sdist_names = std::set<std::string>; | |||||
| namespace { | namespace { | ||||
| void linkup_dependencies(shared_compile_file_rules& rules, | |||||
| const package_manifest& man, | |||||
| const sdist_index_type& sd_idx) { | |||||
| void resolve_ureqs_(shared_compile_file_rules& rules, | |||||
| const package_manifest& man, | |||||
| const sdist_index_type& sd_idx) { | |||||
| for (const dependency& dep : man.dependencies) { | for (const dependency& dep : man.dependencies) { | ||||
| auto found = sd_idx.find(dep.name); | auto found = sd_idx.find(dep.name); | ||||
| if (found == sd_idx.end()) { | if (found == sd_idx.end()) { | ||||
| dep.name, | dep.name, | ||||
| man.name)); | man.name)); | ||||
| } | } | ||||
| linkup_dependencies(rules, found->second.get().manifest, sd_idx); | |||||
| resolve_ureqs_(rules, found->second.get().manifest, sd_idx); | |||||
| auto lib_src = found->second.get().path / "src"; | auto lib_src = found->second.get().path / "src"; | ||||
| auto lib_include = found->second.get().path / "include"; | auto lib_include = found->second.get().path / "include"; | ||||
| if (fs::exists(lib_include)) { | if (fs::exists(lib_include)) { | ||||
| } | } | ||||
| } | } | ||||
| void add_sdist_to_dep_plan(build_plan& plan, const sdist& sd, const sdist_index_type& sd_idx) { | |||||
| auto& pkg = plan.build_packages.emplace_back(); | |||||
| pkg.name = sd.manifest.name; | |||||
| pkg.namespace_ = sd.manifest.namespace_; | |||||
| auto libs = collect_libraries(sd.path); | |||||
| void resolve_ureqs(shared_compile_file_rules rules, | |||||
| const sdist& sd, | |||||
| const library& lib, | |||||
| const library_plan& lib_plan, | |||||
| build_env_ref env, | |||||
| usage_requirement_map& ureqs) { | |||||
| // Add the transitive requirements for this library to our compile rules. | |||||
| for (auto&& use : lib.manifest().uses) { | |||||
| ureqs.apply(rules, use.namespace_, use.name); | |||||
| } | |||||
| // Create usage requirements for this libary. | |||||
| 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); | |||||
| } | |||||
| // TODO: preprocessor definitions | |||||
| } | |||||
| void add_sdist_to_dep_plan(build_plan& plan, | |||||
| const sdist& sd, | |||||
| build_env_ref env, | |||||
| const sdist_index_type& sd_idx, | |||||
| usage_requirement_map& ureqs, | |||||
| sdist_names& already_added) { | |||||
| if (already_added.find(sd.manifest.name) != already_added.end()) { | |||||
| // We've already loaded this package into the plan. | |||||
| return; | |||||
| } | |||||
| spdlog::debug("Add to plan: {}", sd.manifest.name); | |||||
| // First, load every dependency | |||||
| for (const auto& dep : sd.manifest.dependencies) { | |||||
| auto other = sd_idx.find(dep.name); | |||||
| assert(other != sd_idx.end() | |||||
| && "Failed to load a transitive dependency shortly after initializing them. What?"); | |||||
| add_sdist_to_dep_plan(plan, other->second, env, sd_idx, ureqs, already_added); | |||||
| } | |||||
| // Record that we have been processed: | |||||
| already_added.insert(sd.manifest.name); | |||||
| // Add the package: | |||||
| auto& pkg = plan.add_package(package_plan(sd.manifest.name, sd.manifest.namespace_)); | |||||
| auto libs = collect_libraries(sd.path); | |||||
| for (const auto& lib : libs) { | for (const auto& lib : libs) { | ||||
| shared_compile_file_rules comp_rules = lib.base_compile_rules(); | shared_compile_file_rules comp_rules = lib.base_compile_rules(); | ||||
| linkup_dependencies(comp_rules, sd.manifest, sd_idx); | |||||
| library_build_params params; | |||||
| params.compile_rules = comp_rules; | |||||
| pkg.add_library(lib, params); | |||||
| library_build_params params; | |||||
| auto lib_plan = library_plan::create(lib, params, ureqs); | |||||
| resolve_ureqs(comp_rules, sd, lib, lib_plan, env, ureqs); | |||||
| pkg.add_library(std::move(lib_plan)); | |||||
| } | } | ||||
| } | } | ||||
| } // namespace | } // namespace | ||||
| build_plan dds::create_deps_build_plan(const std::vector<sdist>& deps) { | |||||
| auto sd_idx = deps | ranges::views::transform([](const auto& sd) { | |||||
| return std::pair(sd.manifest.name, std::cref(sd)); | |||||
| }) | |||||
| build_plan dds::create_deps_build_plan(const std::vector<sdist>& deps, build_env_ref env) { | |||||
| auto sd_idx = deps // | |||||
| | ranges::views::transform( | |||||
| [](const auto& sd) { return std::pair(sd.manifest.name, std::cref(sd)); }) // | |||||
| | ranges::to<sdist_index_type>(); | | ranges::to<sdist_index_type>(); | ||||
| build_plan plan; | |||||
| build_plan plan; | |||||
| usage_requirement_map ureqs; | |||||
| sdist_names already_added; | |||||
| for (const sdist& sd : deps) { | for (const sdist& sd : deps) { | ||||
| spdlog::info("Recording dependency: {}", sd.manifest.name); | spdlog::info("Recording dependency: {}", sd.manifest.name); | ||||
| add_sdist_to_dep_plan(plan, sd, sd_idx); | |||||
| add_sdist_to_dep_plan(plan, sd, env, sd_idx, ureqs, already_added); | |||||
| } | } | ||||
| return plan; | return plan; | ||||
| } | } | ||||
| namespace { | namespace { | ||||
| fs::path generate_lml(const library_plan& lib, path_ref libdir, const build_env& env) { | fs::path generate_lml(const library_plan& lib, path_ref libdir, const build_env& env) { | ||||
| auto fname = lib.name + ".lml"; | |||||
| auto fname = lib.name() + ".lml"; | |||||
| auto lml_path = libdir / fname; | auto lml_path = libdir / fname; | ||||
| std::vector<lm::pair> kvs; | std::vector<lm::pair> kvs; | ||||
| kvs.emplace_back("Type", "Library"); | kvs.emplace_back("Type", "Library"); | ||||
| kvs.emplace_back("Name", lib.name); | |||||
| if (lib.create_archive) { | |||||
| kvs.emplace_back("Name", lib.name()); | |||||
| if (lib.create_archive()) { | |||||
| kvs.emplace_back("Path", | kvs.emplace_back("Path", | ||||
| fs::relative(lib.create_archive->archive_file_path(env), | |||||
| fs::relative(lib.create_archive()->calc_archive_file_path(env), | |||||
| lml_path.parent_path()) | lml_path.parent_path()) | ||||
| .string()); | .string()); | ||||
| } | } | ||||
| auto pub_inc_dir = lib.source_root / "include"; | |||||
| auto src_dir = lib.source_root / "src"; | |||||
| auto pub_inc_dir = lib.source_root() / "include"; | |||||
| auto src_dir = lib.source_root() / "src"; | |||||
| if (fs::exists(src_dir)) { | if (fs::exists(src_dir)) { | ||||
| pub_inc_dir = src_dir; | pub_inc_dir = src_dir; | ||||
| } | } | ||||
| } | } | ||||
| fs::path generate_lmp(const package_plan& pkg, path_ref basedir, const build_env& env) { | fs::path generate_lmp(const package_plan& pkg, path_ref basedir, const build_env& env) { | ||||
| auto fname = pkg.name + ".lmp"; | |||||
| auto fname = pkg.name() + ".lmp"; | |||||
| auto lmp_path = basedir / fname; | auto lmp_path = basedir / fname; | ||||
| std::vector<lm::pair> kvs; | std::vector<lm::pair> kvs; | ||||
| kvs.emplace_back("Type", "Package"); | kvs.emplace_back("Type", "Package"); | ||||
| kvs.emplace_back("Name", pkg.name); | |||||
| kvs.emplace_back("Namespace", pkg.namespace_); | |||||
| kvs.emplace_back("Name", pkg.name()); | |||||
| kvs.emplace_back("Namespace", pkg.namespace_()); | |||||
| for (auto&& lib : pkg.create_libraries) { | |||||
| auto lml = generate_lml(lib, basedir / pkg.name, env); | |||||
| for (auto&& lib : pkg.libraries()) { | |||||
| auto lml = generate_lml(lib, basedir / pkg.name(), env); | |||||
| kvs.emplace_back("Library", fs::relative(lml, lmp_path.parent_path()).string()); | kvs.emplace_back("Library", fs::relative(lml, lmp_path.parent_path()).string()); | ||||
| } | } | ||||
| auto lm_items_dir = out_filepath.parent_path() / "_libman"; | auto lm_items_dir = out_filepath.parent_path() / "_libman"; | ||||
| std::vector<lm::pair> kvs; | std::vector<lm::pair> kvs; | ||||
| kvs.emplace_back("Type", "Index"); | kvs.emplace_back("Type", "Index"); | ||||
| for (const package_plan& pkg : plan.build_packages) { | |||||
| for (const package_plan& pkg : plan.packages()) { | |||||
| auto pkg_lmp = generate_lmp(pkg, lm_items_dir, env); | auto pkg_lmp = generate_lmp(pkg, lm_items_dir, env); | ||||
| kvs.emplace_back("Package", | kvs.emplace_back("Package", | ||||
| fmt::format("{}; {}", | fmt::format("{}; {}", | ||||
| pkg.name, | |||||
| pkg.name(), | |||||
| fs::relative(pkg_lmp, out_filepath.parent_path()).string())); | fs::relative(pkg_lmp, out_filepath.parent_path()).string())); | ||||
| } | } | ||||
| lm::write_pairs(out_filepath, kvs); | lm::write_pairs(out_filepath, kvs); |
| return acc; | return acc; | ||||
| } | } | ||||
| build_plan create_deps_build_plan(const std::vector<sdist>& deps); | |||||
| build_plan create_deps_build_plan(const std::vector<sdist>& deps, build_env_ref env); | |||||
| void write_libman_index(path_ref where, const build_plan& plan, const build_env& env); | void write_libman_index(path_ref where, const build_plan& plan, const build_env& env); | ||||
| #include <dds/library.hpp> | #include <dds/library.hpp> | ||||
| #include <dds/build/compile.hpp> | |||||
| #include <dds/build/plan/compile_file.hpp> | |||||
| #include <dds/build/source_dir.hpp> | #include <dds/build/source_dir.hpp> | ||||
| #include <dds/util/algo.hpp> | #include <dds/util/algo.hpp> | ||||
| #pragma once | #pragma once | ||||
| #include <dds/build/compile.hpp> | |||||
| #include <dds/build/plan/compile_file.hpp> | |||||
| #include <dds/build/source_dir.hpp> | #include <dds/build/source_dir.hpp> | ||||
| #include <dds/library_manifest.hpp> | #include <dds/library_manifest.hpp> | ||||
| #include <dds/source.hpp> | #include <dds/source.hpp> | ||||
| }; | }; | ||||
| struct library_build_params { | struct library_build_params { | ||||
| fs::path out_subdir; | |||||
| bool build_tests = false; | |||||
| bool build_apps = false; | |||||
| std::vector<fs::path> rt_link_libraries; | |||||
| shared_compile_file_rules compile_rules; | |||||
| fs::path out_subdir; | |||||
| bool build_tests = false; | |||||
| bool build_apps = false; | |||||
| }; | }; | ||||
| std::vector<library> collect_libraries(path_ref where); | std::vector<library> collect_libraries(path_ref where); |
| #include "./library_manifest.hpp" | #include "./library_manifest.hpp" | ||||
| #include <dds/util/algo.hpp> | |||||
| #include <range/v3/view/transform.hpp> | |||||
| #include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
| #include <spdlog/fmt/fmt.h> | #include <spdlog/fmt/fmt.h> | ||||
| auto kvs = lm::parse_file(fpath); | auto kvs = lm::parse_file(fpath); | ||||
| library_manifest ret; | library_manifest ret; | ||||
| ret.name = fpath.parent_path().filename().string(); | ret.name = fpath.parent_path().filename().string(); | ||||
| std::vector<std::string> uses_strings; | |||||
| std::vector<std::string> links_strings; | |||||
| lm::read(fmt::format("Reading library manifest {}", fpath.string()), | lm::read(fmt::format("Reading library manifest {}", fpath.string()), | ||||
| kvs, | kvs, | ||||
| lm::read_accumulate("Private-Include", ret.private_includes), | |||||
| lm::read_accumulate("Private-Define", ret.private_defines), | |||||
| lm::read_accumulate("Uses", ret.uses), | |||||
| lm::read_accumulate("Links", ret.links), | |||||
| lm::read_opt("Name", ret.name), | |||||
| lm::read_accumulate("Uses", uses_strings), | |||||
| lm::read_accumulate("Links", links_strings), | |||||
| lm::read_required("Name", ret.name), | |||||
| lm::reject_unknown()); | lm::reject_unknown()); | ||||
| extend(ret.uses, ranges::views::transform(uses_strings, lm::split_usage_string)); | |||||
| extend(ret.links, ranges::views::transform(links_strings, lm::split_usage_string)); | |||||
| return ret; | return ret; | ||||
| } | } |
| #include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
| #include <libman/library.hpp> | |||||
| #include <vector> | #include <vector> | ||||
| namespace dds { | namespace dds { | ||||
| struct library_manifest { | struct library_manifest { | ||||
| std::string name; | |||||
| std::vector<fs::path> private_includes; | |||||
| std::vector<std::string> private_defines; | |||||
| std::vector<std::string> uses; | |||||
| std::vector<std::string> links; | |||||
| std::string name; | |||||
| std::vector<lm::usage> uses; | |||||
| std::vector<lm::usage> links; | |||||
| static library_manifest load_from_file(const fs::path&); | static library_manifest load_from_file(const fs::path&); | ||||
| }; | }; |
| #include "./usage_reqs.hpp" | |||||
| #include <dds/build/plan/compile_file.hpp> | |||||
| #include <dds/util/algo.hpp> | |||||
| #include <spdlog/fmt/fmt.h> | |||||
| #include <stdexcept> | |||||
| 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}); | |||||
| if (found == _reqs.end()) { | |||||
| return nullptr; | |||||
| } | |||||
| return &found->second; | |||||
| } | |||||
| lm::library& usage_requirement_map::add(std::string ns, std::string name) { | |||||
| auto pair = std::pair(library_key{ns, name}, lm::library{}); | |||||
| auto [inserted, did_insert] = _reqs.try_emplace(library_key{ns, name}, lm::library()); | |||||
| if (!did_insert) { | |||||
| throw std::runtime_error( | |||||
| fmt::format("More than one library is registered as {}/{}", ns, name)); | |||||
| } | |||||
| return inserted->second; | |||||
| } | |||||
| void usage_requirement_map::apply(shared_compile_file_rules rules, | |||||
| std::string ns, | |||||
| std::string name) const { | |||||
| auto reqs = get(ns, name); | |||||
| if (!reqs) { | |||||
| throw std::runtime_error( | |||||
| fmt::format("Unable to resolve usage requirements for '{}/{}'", ns, name)); | |||||
| } | |||||
| for (auto&& use : reqs->uses) { | |||||
| apply(rules, use.namespace_, use.name); | |||||
| } | |||||
| extend(rules.include_dirs(), reqs->include_paths); | |||||
| extend(rules.defs(), reqs->preproc_defs); | |||||
| } | |||||
| usage_requirement_map usage_requirement_map::from_lm_index(const lm::index& idx) noexcept { | |||||
| usage_requirement_map ret; | |||||
| for (const auto& pkg : idx.packages) { | |||||
| for (const auto& lib : pkg.libraries) { | |||||
| ret.add(pkg.namespace_, lib.name, lib); | |||||
| } | |||||
| } | |||||
| return ret; | |||||
| } | |||||
| std::vector<fs::path> usage_requirement_map::link_paths(std::string ns, std::string name) const { | |||||
| auto req = get(ns, name); | |||||
| if (!req) { | |||||
| throw std::runtime_error( | |||||
| fmt::format("Unable to find linking requirement '{}/{}'", ns, 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)); | |||||
| } | |||||
| for (const auto& link : req->links) { | |||||
| extend(ret, link_paths(link.namespace_, link.name)); | |||||
| } | |||||
| return ret; | |||||
| } |
| #pragma once | |||||
| #include <dds/util/fs.hpp> | |||||
| #include <libman/index.hpp> | |||||
| #include <libman/library.hpp> | |||||
| #include <map> | |||||
| #include <string> | |||||
| namespace dds { | |||||
| class shared_compile_file_rules; | |||||
| class usage_requirement_map { | |||||
| struct library_key { | |||||
| std::string namespace_; | |||||
| std::string name; | |||||
| }; | |||||
| struct library_key_compare { | |||||
| bool operator()(const library_key& lhs, const library_key& rhs) const noexcept { | |||||
| if (lhs.namespace_ < rhs.namespace_) { | |||||
| return true; | |||||
| } | |||||
| if (lhs.namespace_ > rhs.namespace_) { | |||||
| return false; | |||||
| } | |||||
| if (lhs.name < rhs.name) { | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| std::map<library_key, lm::library, library_key_compare> _reqs; | |||||
| public: | |||||
| const lm::library* get(std::string ns, std::string name) const noexcept; | |||||
| 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; | |||||
| static usage_requirement_map from_lm_index(const lm::index&) noexcept; | |||||
| }; | |||||
| } // namespace dds |
| library ret; | library ret; | ||||
| std::vector<std::string> uses_strs; | |||||
| std::vector<std::string> links_strs; | |||||
| std::string _type_; | std::string _type_; | ||||
| read(fmt::format("Reading library manifest file '{}'", fpath.string()), | read(fmt::format("Reading library manifest file '{}'", fpath.string()), | ||||
| pairs, | pairs, | ||||
| read_opt("Path", ret.linkable_path), | read_opt("Path", ret.linkable_path), | ||||
| read_accumulate("Include-Path", ret.include_paths), | read_accumulate("Include-Path", ret.include_paths), | ||||
| read_accumulate("Preprocessor-Define", ret.preproc_defs), | read_accumulate("Preprocessor-Define", ret.preproc_defs), | ||||
| read_accumulate("Uses", ret.uses), | |||||
| read_accumulate("Uses", uses_strs), | |||||
| read_accumulate("Links", links_strs), | |||||
| read_accumulate("Special-Uses", ret.special_uses)); | read_accumulate("Special-Uses", ret.special_uses)); | ||||
| auto split_req = [](auto&& str) {}; | |||||
| for (auto&& uses_str : uses_strs) { | |||||
| ret.uses.push_back(split_usage_string(uses_str)); | |||||
| } | |||||
| for (auto&& links_str : links_strs) { | |||||
| ret.links.push_back(split_usage_string(links_str)); | |||||
| } | |||||
| auto make_absolute = [&](path_ref p) { return fpath.parent_path() / p; }; | auto make_absolute = [&](path_ref p) { return fpath.parent_path() / p; }; | ||||
| std::transform(ret.include_paths.begin(), | std::transform(ret.include_paths.begin(), | ||||
| ret.include_paths.end(), | ret.include_paths.end(), | ||||
| } | } | ||||
| return ret; | return ret; | ||||
| } | |||||
| usage lm::split_usage_string(std::string_view str) { | |||||
| auto sl_pos = str.find('/'); | |||||
| if (sl_pos == str.npos) { | |||||
| throw std::runtime_error("Invalid Uses/Links specifier: " + std::string(str)); | |||||
| } | |||||
| auto ns = str.substr(0, sl_pos); | |||||
| auto name = str.substr(sl_pos + 1); | |||||
| return usage{std::string(ns), std::string(name)}; | |||||
| } | } |
| #include <optional> | #include <optional> | ||||
| #include <string> | #include <string> | ||||
| #include <string_view> | |||||
| namespace lm { | namespace lm { | ||||
| struct usage { | |||||
| std::string namespace_; | |||||
| std::string name; | |||||
| }; | |||||
| usage split_usage_string(std::string_view); | |||||
| class library { | class library { | ||||
| public: | public: | ||||
| std::string name; | std::string name; | ||||
| std::optional<fs::path> linkable_path; | std::optional<fs::path> linkable_path; | ||||
| std::vector<fs::path> include_paths; | std::vector<fs::path> include_paths; | ||||
| std::vector<std::string> preproc_defs; | std::vector<std::string> preproc_defs; | ||||
| std::vector<std::string> uses; | |||||
| std::vector<std::string> links; | |||||
| std::vector<usage> uses; | |||||
| std::vector<usage> links; | |||||
| std::vector<std::string> special_uses; | std::vector<std::string> special_uses; | ||||
| static library from_file(path_ref); | static library from_file(path_ref); |