| #include <dds/util/paths.hpp> | #include <dds/util/paths.hpp> | ||||
| #include <dds/util/signal.hpp> | #include <dds/util/signal.hpp> | ||||
| #include <range/v3/view/group_by.hpp> | |||||
| #include <range/v3/view/transform.hpp> | |||||
| #include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
| #include <args.hxx> | #include <args.hxx> | ||||
| using string_flag = args::ValueFlag<std::string>; | using string_flag = args::ValueFlag<std::string>; | ||||
| using path_flag = args::ValueFlag<dds::fs::path>; | using path_flag = args::ValueFlag<dds::fs::path>; | ||||
| struct toolchain_flag : string_flag { | |||||
| toolchain_flag(args::Group& grp) | |||||
| : string_flag{grp, | |||||
| "toolchain_file", | |||||
| "Path/ident of the toolchain file to use", | |||||
| {"toolchain", 'T'}, | |||||
| (dds::fs::current_path() / "toolchain.dds").string()} {} | |||||
| dds::toolchain get_toolchain() { | |||||
| const auto tc_path = this->Get(); | |||||
| if (tc_path.find(":") == 0) { | |||||
| auto default_tc = tc_path.substr(1); | |||||
| auto tc = dds::toolchain::get_builtin(default_tc); | |||||
| if (!tc.has_value()) { | |||||
| throw std::runtime_error( | |||||
| fmt::format("Invalid default toolchain name '{}'", default_tc)); | |||||
| } | |||||
| return std::move(*tc); | |||||
| } else { | |||||
| return dds::toolchain::load_from_file(tc_path); | |||||
| } | |||||
| } | |||||
| }; | |||||
| struct repo_where_flag : path_flag { | |||||
| repo_where_flag(args::Group& grp) | |||||
| : path_flag{grp, | |||||
| "dir", | |||||
| "Directory in which to initialize the repository", | |||||
| {"repo-dir"}, | |||||
| dds::repository::default_local_path()} {} | |||||
| }; | |||||
| /** | |||||
| * Base class holds the actual argument parser | |||||
| */ | |||||
| struct cli_base { | struct cli_base { | ||||
| args::ArgumentParser& parser; | args::ArgumentParser& parser; | ||||
| args::HelpFlag _help{parser, "help", "Display this help message and exit", {'h', "help"}}; | args::HelpFlag _help{parser, "help", "Display this help message and exit", {'h', "help"}}; | ||||
| // Test argument: | |||||
| args::Flag _verify_ident{parser, | |||||
| "test", | |||||
| "Print `yes` and exit 0. Useful for scripting.", | |||||
| {"are-you-the-real-dds?"}}; | |||||
| args::Group cmd_group{parser, "Available Commands"}; | args::Group cmd_group{parser, "Available Commands"}; | ||||
| }; | }; | ||||
| /** | |||||
| * Flags common to all subcommands | |||||
| */ | |||||
| struct common_flags { | struct common_flags { | ||||
| args::Command& cmd; | args::Command& cmd; | ||||
| args::HelpFlag _help{cmd, "help", "Print this help message and exit", {'h', "help"}}; | args::HelpFlag _help{cmd, "help", "Print this help message and exit", {'h', "help"}}; | ||||
| }; | }; | ||||
| /** | |||||
| * Flags common to project-related commands | |||||
| */ | |||||
| struct common_project_flags { | struct common_project_flags { | ||||
| args::Command& cmd; | args::Command& cmd; | ||||
| path_flag root{cmd, | path_flag root{cmd, | ||||
| "project_dir", | "project_dir", | ||||
| "Path to the directory containing the project", | "Path to the directory containing the project", | ||||
| {"project-dir"}, | |||||
| {'p', "project-dir"}, | |||||
| dds::fs::current_path()}; | dds::fs::current_path()}; | ||||
| }; | }; | ||||
| /* | |||||
| ######## ######## ######## ####### | |||||
| ## ## ## ## ## ## ## | |||||
| ## ## ## ## ## ## ## | |||||
| ######## ###### ######## ## ## | |||||
| ## ## ## ## ## ## | |||||
| ## ## ## ## ## ## | |||||
| ## ## ######## ## ####### | |||||
| */ | |||||
| struct cli_repo { | struct cli_repo { | ||||
| cli_base& base; | cli_base& base; | ||||
| args::Command cmd{base.cmd_group, "repo", "Manage the package repository"}; | args::Command cmd{base.cmd_group, "repo", "Manage the package repository"}; | ||||
| common_flags _common{cmd}; | common_flags _common{cmd}; | ||||
| path_flag where{cmd, "dir", "Directory in which to initialize the repository", {'d', "dir"}}; | |||||
| repo_where_flag where{cmd}; | |||||
| args::Group repo_group{cmd, "Repo subcommands"}; | args::Group repo_group{cmd, "Repo subcommands"}; | ||||
| common_flags _common{cmd}; | common_flags _common{cmd}; | ||||
| int run() { | int run() { | ||||
| return dds::repository::with_repository(dds::repository::default_local_path(), | |||||
| dds::repo_flags::none, | |||||
| [&](auto) { return 0; }); | |||||
| auto list_contents = [&](dds::repository repo) { | |||||
| auto same_name | |||||
| = [](auto&& a, auto&& b) { return a.manifest.name == b.manifest.name; }; | |||||
| auto all_sdists = repo.load_sdists(); | |||||
| auto grp_by_name = all_sdists // | |||||
| | ranges::views::group_by(same_name) // | |||||
| | ranges::views::transform(ranges::to_vector) // | |||||
| | ranges::views::transform([](auto&& grp) { | |||||
| assert(grp.size() > 0); | |||||
| return std::pair(grp[0].manifest.name, grp); | |||||
| }); | |||||
| for (const auto& [name, grp] : grp_by_name) { | |||||
| spdlog::info("{}:", name); | |||||
| for (const dds::sdist& sd : grp) { | |||||
| spdlog::info(" - {} [{}]", | |||||
| sd.manifest.version.to_string(), | |||||
| sd.md5_string()); | |||||
| } | |||||
| } | |||||
| return 0; | |||||
| }; | |||||
| return dds::repository::with_repository(parent.where.Get(), | |||||
| dds::repo_flags::read, | |||||
| list_contents); | |||||
| } | } | ||||
| } ls{*this}; | } ls{*this}; | ||||
| } | } | ||||
| }; | }; | ||||
| /* | |||||
| ###### ######## #### ###### ######## | |||||
| ## ## ## ## ## ## ## ## | |||||
| ## ## ## ## ## ## | |||||
| ###### ## ## ## ###### ## | |||||
| ## ## ## ## ## ## | |||||
| ## ## ## ## ## ## ## ## | |||||
| ###### ######## #### ###### ## | |||||
| */ | |||||
| struct cli_sdist { | struct cli_sdist { | ||||
| cli_base& base; | cli_base& base; | ||||
| args::Command cmd{base.cmd_group, "sdist", "Create a source distribution of a project"}; | args::Command cmd{base.cmd_group, "sdist", "Create a source distribution of a project"}; | ||||
| {"out"}, | {"out"}, | ||||
| dds::fs::current_path() / "project.dsd"}; | dds::fs::current_path() / "project.dsd"}; | ||||
| args::Flag force{cmd, "force", "Forcibly replace an existing result", {"force"}}; | |||||
| args::Flag export_{cmd, | |||||
| args::Flag force{cmd, "force", "Forcibly replace an existing result", {"force"}}; | |||||
| args::Flag export_{cmd, | |||||
| "export", | "export", | ||||
| "Export the result into the local repository", | "Export the result into the local repository", | ||||
| {'E', "export"}}; | {'E', "export"}}; | ||||
| repo_where_flag repo_where{cmd}; | |||||
| int run() { | int run() { | ||||
| dds::sdist_params params; | dds::sdist_params params; | ||||
| auto sdist = dds::create_sdist(params); | auto sdist = dds::create_sdist(params); | ||||
| if (export_.Get()) { | if (export_.Get()) { | ||||
| dds::repository::with_repository( // | dds::repository::with_repository( // | ||||
| dds::repository::default_local_path(), | |||||
| repo_where.Get(), | |||||
| dds::repo_flags::create_if_absent | dds::repo_flags::write_lock, | dds::repo_flags::create_if_absent | dds::repo_flags::write_lock, | ||||
| [&](dds::repository repo) { // | [&](dds::repository repo) { // | ||||
| repo.add_sdist(sdist); | repo.add_sdist(sdist); | ||||
| } | } | ||||
| }; | }; | ||||
| /* | |||||
| ######## ## ## #### ## ######## | |||||
| ## ## ## ## ## ## ## ## | |||||
| ## ## ## ## ## ## ## ## | |||||
| ######## ## ## ## ## ## ## | |||||
| ## ## ## ## ## ## ## ## | |||||
| ## ## ## ## ## ## ## ## | |||||
| ######## ####### #### ######## ######## | |||||
| */ | |||||
| struct cli_build { | struct cli_build { | ||||
| cli_base& base; | cli_base& base; | ||||
| args::Command cmd{base.cmd_group, "build", "Build a project"}; | args::Command cmd{base.cmd_group, "build", "Build a project"}; | ||||
| common_project_flags project{cmd}; | common_project_flags project{cmd}; | ||||
| string_flag tc_filepath{cmd, | |||||
| "toolchain_file", | |||||
| "Path to the toolchain file to use", | |||||
| {"toolchain", 'T'}, | |||||
| (dds::fs::current_path() / "toolchain.dds").string()}; | |||||
| args::Flag build_tests{cmd, "build_tests", "Build the tests", {"tests", 't'}}; | |||||
| args::Flag build_apps{cmd, "build_apps", "Build applications", {"apps", 'A'}}; | |||||
| args::Flag export_{cmd, "export", "Generate a library export", {"export", 'E'}}; | |||||
| args::Flag build_tests{cmd, "build_tests", "Build the tests", {"tests", 't'}}; | |||||
| args::Flag build_apps{cmd, "build_apps", "Build applications", {"apps", 'A'}}; | |||||
| args::Flag export_{cmd, "export", "Generate a library export", {"export", 'E'}}; | |||||
| toolchain_flag tc_filepath{cmd}; | |||||
| path_flag lm_index{cmd, | path_flag lm_index{cmd, | ||||
| "lm_index", | "lm_index", | ||||
| {"out"}, | {"out"}, | ||||
| dds::fs::current_path() / "_build"}; | dds::fs::current_path() / "_build"}; | ||||
| dds::toolchain _get_toolchain() { | |||||
| const auto tc_path = tc_filepath.Get(); | |||||
| if (tc_path.find(":") == 0) { | |||||
| auto default_tc = tc_path.substr(1); | |||||
| auto tc = dds::toolchain::get_builtin(default_tc); | |||||
| if (!tc.has_value()) { | |||||
| throw std::runtime_error( | |||||
| fmt::format("Invalid default toolchain name '{}'", default_tc)); | |||||
| } | |||||
| return std::move(*tc); | |||||
| } else { | |||||
| return dds::toolchain::load_from_file(tc_path); | |||||
| } | |||||
| } | |||||
| int run() { | int run() { | ||||
| dds::build_params params; | dds::build_params params; | ||||
| params.root = project.root.Get(); | params.root = project.root.Get(); | ||||
| params.out_root = out.Get(); | params.out_root = out.Get(); | ||||
| params.toolchain = _get_toolchain(); | |||||
| params.toolchain = tc_filepath.get_toolchain(); | |||||
| params.do_export = export_.Get(); | params.do_export = export_.Get(); | ||||
| params.build_tests = build_tests.Get(); | params.build_tests = build_tests.Get(); | ||||
| params.build_apps = build_apps.Get(); | params.build_apps = build_apps.Get(); | ||||
| } | } | ||||
| }; | }; | ||||
| /* | |||||
| ######## ######## ######## ###### | |||||
| ## ## ## ## ## ## ## | |||||
| ## ## ## ## ## ## | |||||
| ## ## ###### ######## ###### | |||||
| ## ## ## ## ## | |||||
| ## ## ## ## ## ## | |||||
| ######## ######## ## ###### | |||||
| */ | |||||
| struct cli_deps { | |||||
| cli_base& base; | |||||
| args::Command cmd{base.cmd_group, "deps", "Obtain/inspect/build deps for the project"}; | |||||
| common_flags _flags{cmd}; | |||||
| common_project_flags project{cmd}; | |||||
| args::Group deps_group{cmd, "Subcommands"}; | |||||
| dds::package_manifest load_package_manifest() { | |||||
| return dds::package_manifest::load_from_file(project.root.Get() / "package.dds"); | |||||
| } | |||||
| struct { | |||||
| cli_deps& parent; | |||||
| args::Command cmd{parent.deps_group, "ls", "List project dependencies"}; | |||||
| common_flags _common{cmd}; | |||||
| int run() { | |||||
| const auto man = parent.load_package_manifest(); | |||||
| for (const auto& dep : man.dependencies) { | |||||
| std::cout << dep.name << " " << dep.version.to_string() << '\n'; | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| } ls{*this}; | |||||
| struct { | |||||
| cli_deps& parent; | |||||
| args::Command cmd{parent.deps_group, "build", "Build project dependencies"}; | |||||
| common_flags _common{cmd}; | |||||
| path_flag build_dir{cmd, | |||||
| "build_dir", | |||||
| "Directory where build results will be stored", | |||||
| {"deps-build-dir"}, | |||||
| dds::fs::current_path() / "_build/deps"}; | |||||
| path_flag lmi_path{cmd, "lmi_path", "Destination for the INDEX.lmi file", {"lmi-path"}}; | |||||
| args::Flag no_lmi{cmd, | |||||
| "no_lmi", | |||||
| "If specified, will not generate an INDEX.lmi", | |||||
| {"skip-lmi"}}; | |||||
| repo_where_flag repo_where{cmd}; | |||||
| toolchain_flag tc_filepath{cmd}; | |||||
| void _build_one_dep(const dds::sdist& dep) { | |||||
| spdlog::info("Build dependency {} {}", | |||||
| dep.manifest.name, | |||||
| dep.manifest.version.to_string()); | |||||
| dds::build_params params; | |||||
| params.root = dep.path; | |||||
| params.toolchain = tc_filepath.get_toolchain(); | |||||
| params.out_root = build_dir.Get() | |||||
| / fmt::format("{}-{}", dep.manifest.name, dep.manifest.version.to_string()); | |||||
| dds::build(params, dep.manifest); | |||||
| } | |||||
| int run() { | |||||
| auto man = parent.load_package_manifest(); | |||||
| auto deps = dds::repository::with_repository( // | |||||
| repo_where.Get(), | |||||
| dds::repo_flags::read, | |||||
| [&](dds::repository repo) { | |||||
| return find_dependencies(repo, | |||||
| man.dependencies.begin(), | |||||
| man.dependencies.end()); | |||||
| }); | |||||
| for (auto&& dep : deps) { | |||||
| _build_one_dep(dep); | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| } build{*this}; | |||||
| int run() { | |||||
| if (ls.cmd) { | |||||
| return ls.run(); | |||||
| } else if (build.cmd) { | |||||
| return build.run(); | |||||
| } | |||||
| std::terminate(); | |||||
| return 0; | |||||
| } | |||||
| }; | |||||
| } // namespace | } // namespace | ||||
| /* | |||||
| ## ## ### #### ## ## | |||||
| ### ### ## ## ## ### ## | |||||
| #### #### ## ## ## #### ## | |||||
| ## ### ## ## ## ## ## ## ## | |||||
| ## ## ######### ## ## #### | |||||
| ## ## ## ## ## ## ### | |||||
| ## ## ## ## #### ## ## | |||||
| */ | |||||
| int main(int argc, char** argv) { | int main(int argc, char** argv) { | ||||
| spdlog::set_pattern("[%H:%M:%S] [%^%l%$] %v"); | spdlog::set_pattern("[%H:%M:%S] [%^%l%$] %v"); | ||||
| args::ArgumentParser parser("DDSLiM - The drop-dead-simple library manager"); | args::ArgumentParser parser("DDSLiM - The drop-dead-simple library manager"); | ||||
| cli_build build{cli}; | cli_build build{cli}; | ||||
| cli_sdist sdist{cli}; | cli_sdist sdist{cli}; | ||||
| cli_repo repo{cli}; | cli_repo repo{cli}; | ||||
| cli_deps deps{cli}; | |||||
| try { | try { | ||||
| parser.ParseCLI(argc, argv); | parser.ParseCLI(argc, argv); | ||||
| } catch (const args::Help&) { | } catch (const args::Help&) { | ||||
| dds::install_signal_handlers(); | dds::install_signal_handlers(); | ||||
| try { | try { | ||||
| if (build.cmd) { | |||||
| if (cli._verify_ident) { | |||||
| std::cout << "yes\n"; | |||||
| return 0; | |||||
| } else if (build.cmd) { | |||||
| return build.run(); | return build.run(); | ||||
| } else if (sdist.cmd) { | } else if (sdist.cmd) { | ||||
| return sdist.run(); | return sdist.run(); | ||||
| } else if (repo.cmd) { | } else if (repo.cmd) { | ||||
| return repo.run(); | return repo.run(); | ||||
| } else if (deps.cmd) { | |||||
| return deps.run(); | |||||
| } else { | } else { | ||||
| assert(false); | assert(false); | ||||
| std::terminate(); | std::terminate(); |
| #include "./deps.hpp" | |||||
| #include <dds/sdist.hpp> | |||||
| #include <dds/repo/repo.hpp> | |||||
| #include <dds/util/string.hpp> | |||||
| #include <range/v3/algorithm/partition_point.hpp> | |||||
| #include <spdlog/fmt/fmt.h> | |||||
| #include <cctype> | |||||
| using namespace dds; | |||||
| dependency dependency::parse_depends_string(std::string_view str) { | |||||
| const auto str_begin = str.data(); | |||||
| auto str_iter = str_begin; | |||||
| const auto str_end = str_iter + str.size(); | |||||
| while (str_iter != str_end && !std::isspace(*str_iter)) { | |||||
| ++str_iter; | |||||
| } | |||||
| auto name = trim(std::string_view(str_begin, str_iter - str_begin)); | |||||
| auto version_str = trim(std::string_view(str_iter, str_end - str_iter)); | |||||
| semver::version version; | |||||
| try { | |||||
| version = semver::version::parse(version_str); | |||||
| } catch (const semver::invalid_version&) { | |||||
| throw std::runtime_error( | |||||
| fmt::format("Invalid version string '{}' in dependency declaration '{}' (Should be a " | |||||
| "semver string. See https://semver.org/ for info)", | |||||
| version_str, | |||||
| str)); | |||||
| } | |||||
| return dependency{std::string(name), version}; | |||||
| } | |||||
| std::vector<sdist> dds::find_dependencies(const repository& repo, const dependency& dep) { | |||||
| std::vector<sdist> acc; | |||||
| detail::do_find_deps(repo, dep, acc); | |||||
| return acc; | |||||
| } | |||||
| void detail::do_find_deps(const repository& repo, const dependency& dep, std::vector<sdist>& sd) { | |||||
| auto sdist_opt = repo.get_sdist(dep.name, dep.version.to_string()); | |||||
| if (!sdist_opt) { | |||||
| throw std::runtime_error( | |||||
| fmt::format("Unable to find dependency to satisfy requirement: {} {}", | |||||
| dep.name, | |||||
| dep.version.to_string())); | |||||
| } | |||||
| sdist& new_sd = *sdist_opt; | |||||
| auto insert_point = ranges::partition_point(sd, [&](const sdist& cand) { | |||||
| return cand.path < new_sd.path; | |||||
| }); | |||||
| if (insert_point != sd.end() && insert_point->manifest.name == new_sd.manifest.name) { | |||||
| if (insert_point->manifest.version != new_sd.manifest.version) { | |||||
| assert(false && "Version conflict resolution not implemented yet"); | |||||
| std::terminate(); | |||||
| } | |||||
| return; | |||||
| } | |||||
| sd.insert(insert_point, std::move(new_sd)); | |||||
| } |
| #pragma once | |||||
| #include <semver/version.hpp> | |||||
| #include <string_view> | |||||
| namespace dds { | |||||
| class repository; | |||||
| struct sdist; | |||||
| enum class version_strength { | |||||
| exact, | |||||
| patch, | |||||
| minor, | |||||
| major, | |||||
| }; | |||||
| struct dependency { | |||||
| std::string name; | |||||
| semver::version version; | |||||
| static dependency parse_depends_string(std::string_view str); | |||||
| }; | |||||
| namespace detail { | |||||
| void do_find_deps(const repository&, const dependency& dep, std::vector<sdist>& acc); | |||||
| } // namespace detail | |||||
| std::vector<sdist> find_dependencies(const repository& repo, const dependency& dep); | |||||
| template <typename Iter, typename Snt> | |||||
| inline std::vector<sdist> find_dependencies(const repository& repo, Iter it, Snt stop) { | |||||
| std::vector<sdist> acc; | |||||
| while (it != stop) { | |||||
| detail::do_find_deps(repo, *it++, acc); | |||||
| } | |||||
| return acc; | |||||
| } | |||||
| } // namespace dds |
| #include "./package_manifest.hpp" | #include "./package_manifest.hpp" | ||||
| #include <dds/util/string.hpp> | |||||
| #include <libman/parse.hpp> | #include <libman/parse.hpp> | ||||
| #include <range/v3/view/split.hpp> | |||||
| #include <range/v3/view/split_when.hpp> | |||||
| #include <range/v3/view/transform.hpp> | |||||
| #include <spdlog/fmt/fmt.h> | #include <spdlog/fmt/fmt.h> | ||||
| using namespace dds; | using namespace dds; | ||||
| package_manifest package_manifest::load_from_file(const fs::path& fpath) { | package_manifest package_manifest::load_from_file(const fs::path& fpath) { | ||||
| auto kvs = lm::parse_file(fpath); | |||||
| package_manifest ret; | |||||
| auto kvs = lm::parse_file(fpath); | |||||
| package_manifest ret; | |||||
| std::string version_str; | |||||
| std::vector<std::string> depends_strs; | |||||
| lm::read(fmt::format("Reading package manifest '{}'", fpath.string()), | lm::read(fmt::format("Reading package manifest '{}'", fpath.string()), | ||||
| kvs, | kvs, | ||||
| lm::read_opt("Name", ret.name), | |||||
| lm::read_opt("Version", ret.version), | |||||
| lm::read_required("Name", ret.name), | |||||
| lm::read_required("Version", version_str), | |||||
| lm::read_accumulate("Depends", depends_strs), | |||||
| lm::reject_unknown()); | lm::reject_unknown()); | ||||
| if (ret.name.empty()) { | |||||
| throw std::runtime_error(fmt::format("'Name' field in [{}] may not be an empty string", fpath.string())); | |||||
| } | |||||
| if (version_str.empty()) { | |||||
| throw std::runtime_error(fmt::format("'Version' field in [{}] may not be an empty string", fpath.string())); | |||||
| } | |||||
| ret.version = semver::version::parse(version_str); | |||||
| ret.dependencies = depends_strs // | |||||
| | ranges::views::transform(dependency::parse_depends_string) // | |||||
| | ranges::to_vector; | |||||
| return ret; | return ret; | ||||
| } | } |
| #pragma once | #pragma once | ||||
| #include <dds/deps.hpp> | |||||
| #include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
| #include <semver/version.hpp> | |||||
| #include <optional> | |||||
| #include <string> | #include <string> | ||||
| #include <vector> | |||||
| namespace dds { | namespace dds { | ||||
| struct package_manifest { | struct package_manifest { | ||||
| std::string name; | |||||
| std::string version = "no-version"; | |||||
| std::string name; | |||||
| semver::version version; | |||||
| std::vector<dependency> dependencies; | |||||
| static package_manifest load_from_file(path_ref); | static package_manifest load_from_file(path_ref); | ||||
| }; | }; | ||||
| } // namespace dds | |||||
| } // namespace dds |
| #include <dds/sdist.hpp> | #include <dds/sdist.hpp> | ||||
| #include <dds/util/paths.hpp> | #include <dds/util/paths.hpp> | ||||
| #include <dds/util/string.hpp> | |||||
| #include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||
| #include <range/v3/action/join.hpp> | |||||
| #include <range/v3/range/conversion.hpp> | #include <range/v3/range/conversion.hpp> | ||||
| #include <range/v3/view/filter.hpp> | |||||
| #include <range/v3/view/join.hpp> | |||||
| #include <range/v3/view/transform.hpp> | |||||
| using namespace dds; | using namespace dds; | ||||
| } | } | ||||
| void repository::add_sdist(const sdist& sd) { | void repository::add_sdist(const sdist& sd) { | ||||
| auto sd_dest = _root / "dist" / sd.name() / sd.version() / sd.hash(); | |||||
| auto sd_dest | |||||
| = _root / "dist" / sd.manifest.name / sd.manifest.version.to_string() / sd.md5_string(); | |||||
| if (fs::exists(sd_dest)) { | if (fs::exists(sd_dest)) { | ||||
| spdlog::info("Source distribution '{}' is already available in the local repo", | spdlog::info("Source distribution '{}' is already available in the local repo", | ||||
| sd.path().string()); | |||||
| sd.path.string()); | |||||
| return; | return; | ||||
| } | } | ||||
| auto tmp_copy = sd_dest; | auto tmp_copy = sd_dest; | ||||
| fs::remove_all(tmp_copy); | fs::remove_all(tmp_copy); | ||||
| } | } | ||||
| fs::create_directories(tmp_copy.parent_path()); | fs::create_directories(tmp_copy.parent_path()); | ||||
| fs::copy(sd.path(), tmp_copy, fs::copy_options::recursive); | |||||
| fs::copy(sd.path, tmp_copy, fs::copy_options::recursive); | |||||
| fs::rename(tmp_copy, sd_dest); | fs::rename(tmp_copy, sd_dest); | ||||
| spdlog::info("Source distribution '{}' successfully exported", sd.ident()); | spdlog::info("Source distribution '{}' successfully exported", sd.ident()); | ||||
| } | } | ||||
| std::vector<sdist> repository::load_sdists() const noexcept { | |||||
| using namespace ranges; | |||||
| using namespace ranges::views; | |||||
| auto drop_dot_dirs | |||||
| = filter([](path_ref p) { return !starts_with(p.filename().string(), "."); }); | |||||
| auto iter_children = [&](path_ref p) { return fs::directory_iterator(p) | drop_dot_dirs; }; | |||||
| auto try_read_sdist = [](path_ref p) -> std::optional<sdist> { | |||||
| try { | |||||
| return sdist::from_directory(p); | |||||
| } catch (const std::runtime_error& e) { | |||||
| spdlog::error("Failed to load source distribution from directory '{}': {}", | |||||
| p.string(), | |||||
| e.what()); | |||||
| return std::nullopt; | |||||
| } | |||||
| }; | |||||
| return | |||||
| // Get the top-level `name` dirs | |||||
| fs::directory_iterator(_dist_dir()) // | |||||
| // Get the next level `version` dirs | |||||
| | transform(iter_children) // | |||||
| | views::join // | |||||
| // Get the next level `ident` dirs | |||||
| | transform(iter_children) // | |||||
| | views::join // | |||||
| // // Convert each dir into an `sdist` object | |||||
| | transform(try_read_sdist) // | |||||
| // // Drop items that failed to load | |||||
| | filter([](auto&& opt) { return opt.has_value(); }) // | |||||
| | transform([](auto&& opt) { return *opt; }) // | |||||
| | to_vector // | |||||
| ; | |||||
| } | |||||
| std::optional<sdist> repository::get_sdist(std::string_view name, std::string_view version) const { | |||||
| auto expect_path = _dist_dir() / name / version; | |||||
| if (!fs::is_directory(expect_path)) { | |||||
| return std::nullopt; | |||||
| } | |||||
| auto dir_iter = fs::directory_iterator(expect_path); | |||||
| if (dir_iter == fs::directory_iterator()) { | |||||
| return std::nullopt; | |||||
| } | |||||
| return sdist::from_directory(*dir_iter); | |||||
| } |
| #include <cassert> | #include <cassert> | ||||
| #include <functional> | #include <functional> | ||||
| #include <shared_mutex> | #include <shared_mutex> | ||||
| #include <vector> | |||||
| namespace dds { | namespace dds { | ||||
| enum repo_flags { | enum repo_flags { | ||||
| none = 0b00, | none = 0b00, | ||||
| read = none, | |||||
| create_if_absent = 0b01, | create_if_absent = 0b01, | ||||
| write_lock = 0b10, | write_lock = 0b10, | ||||
| }; | }; | ||||
| class repository { | class repository { | ||||
| fs::path _root; | fs::path _root; | ||||
| repository(path_ref p) | repository(path_ref p) | ||||
| : _root(p) {} | : _root(p) {} | ||||
| static void _log_blocking(path_ref dir) noexcept; | static void _log_blocking(path_ref dir) noexcept; | ||||
| static void _init_repo_dir(path_ref dir) noexcept; | static void _init_repo_dir(path_ref dir) noexcept; | ||||
| fs::path _dist_dir() const noexcept { return _root / "dist"; } | |||||
| public: | public: | ||||
| template <typename Func> | template <typename Func> | ||||
| static decltype(auto) with_repository(path_ref dirpath, repo_flags flags, Func&& fn) { | static decltype(auto) with_repository(path_ref dirpath, repo_flags flags, Func&& fn) { | ||||
| static fs::path default_local_path() noexcept; | static fs::path default_local_path() noexcept; | ||||
| void add_sdist(const sdist&); | |||||
| void add_sdist(const sdist&); | |||||
| std::optional<sdist> get_sdist(std::string_view name, std::string_view version) const; | |||||
| std::vector<sdist> load_sdists() const noexcept; | |||||
| }; | }; | ||||
| } // namespace dds | } // namespace dds |
| } | } | ||||
| sdist dds::create_sdist_in_dir(path_ref out, const sdist_params& params) { | sdist dds::create_sdist_in_dir(path_ref out, const sdist_params& params) { | ||||
| auto project = project::from_directory(params.project_dir); | |||||
| auto project = project::from_directory(params.project_dir); | |||||
| browns::md5 md5; | browns::md5 md5; | ||||
| if (project.main_library()) { | if (project.main_library()) { | ||||
| } | } | ||||
| auto man_path = project.root() / "package.dds"; | auto man_path = project.root() / "package.dds"; | ||||
| if (fs::is_regular_file(man_path)) { | |||||
| sdist_export_file(out, params.project_dir, man_path, md5); | |||||
| if (!fs::is_regular_file(man_path)) { | |||||
| throw std::runtime_error(fmt::format( | |||||
| "Creating a source distribution requires a package.dds file for the project")); | |||||
| } | } | ||||
| sdist_export_file(out, params.project_dir, man_path, md5); | |||||
| md5.pad(); | md5.pad(); | ||||
| auto hash_str = browns::format_digest(md5.digest()); | auto hash_str = browns::format_digest(md5.digest()); | ||||
| spdlog::info("Generated export as {}-{}", project.manifest().name, hash_str); | spdlog::info("Generated export as {}-{}", project.manifest().name, hash_str); | ||||
| std::vector<lm::pair> pairs; | std::vector<lm::pair> pairs; | ||||
| pairs.emplace_back("Name", project.manifest().name); | |||||
| pairs.emplace_back("Version", project.manifest().version); | |||||
| pairs.emplace_back("MD5-Hash", hash_str); | pairs.emplace_back("MD5-Hash", hash_str); | ||||
| lm::write_pairs(out / "_sdist.dds", pairs); | lm::write_pairs(out / "_sdist.dds", pairs); | ||||
| } | } | ||||
| sdist sdist::from_directory(path_ref where) { | sdist sdist::from_directory(path_ref where) { | ||||
| auto pkg_man = package_manifest::load_from_file(where / "package.dds"); | |||||
| auto meta_pairs = lm::parse_file(where / "_sdist.dds"); | auto meta_pairs = lm::parse_file(where / "_sdist.dds"); | ||||
| std::string name; | |||||
| std::string version; | |||||
| std::string hash_str; | std::string hash_str; | ||||
| lm::read("Loading source distribution", | |||||
| lm::read(fmt::format("Loading source distribution manifest from {}/_sdist.dds", where.string()), | |||||
| meta_pairs, | meta_pairs, | ||||
| lm::read_required("Name", name), | |||||
| lm::read_required("Version", version), | |||||
| lm::read_required("MD5-Hash", hash_str), | lm::read_required("MD5-Hash", hash_str), | ||||
| lm::reject_unknown()); | lm::reject_unknown()); | ||||
| return sdist{name, version, hash_str, where}; | |||||
| return sdist{std::move(pkg_man), | |||||
| browns::parse_digest<browns::md5::digest_type>(hash_str), | |||||
| where}; | |||||
| } | } |
| #pragma once | #pragma once | ||||
| #include <dds/package_manifest.hpp> | |||||
| #include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
| #include <browns/md5.hpp> | |||||
| #include <browns/output.hpp> | |||||
| namespace dds { | namespace dds { | ||||
| struct sdist_params { | struct sdist_params { | ||||
| bool include_tests = false; | bool include_tests = false; | ||||
| }; | }; | ||||
| class sdist { | |||||
| std::string _name; | |||||
| std::string _version; | |||||
| std::string _hash; | |||||
| fs::path _sdist_dir; | |||||
| struct sdist { | |||||
| package_manifest manifest; | |||||
| browns::md5::digest_type md5; | |||||
| fs::path path; | |||||
| public: | |||||
| sdist(std::string_view name, std::string_view version, std::string_view hash, path_ref path) | |||||
| : _name(name) | |||||
| , _version(version) | |||||
| , _hash(hash) | |||||
| , _sdist_dir(path) {} | |||||
| sdist(package_manifest man, browns::md5::digest_type hash, path_ref path) | |||||
| : manifest(std::move(man)) | |||||
| , md5(hash) | |||||
| , path(path) {} | |||||
| static sdist from_directory(path_ref p); | static sdist from_directory(path_ref p); | ||||
| std::string_view name() const noexcept { return _name; } | |||||
| std::string_view version() const noexcept { return _version; } | |||||
| std::string_view hash() const noexcept { return _hash; } | |||||
| path_ref path() const noexcept { return _sdist_dir; } | |||||
| std::string md5_string() const noexcept { return browns::format_digest(md5); } | |||||
| std::string ident() const noexcept { return _name + "." + _version + "." + _hash; } | |||||
| std::string ident() const noexcept { | |||||
| return manifest.name + "." + manifest.version.to_string() + "." + md5_string(); | |||||
| } | |||||
| }; | }; | ||||
| sdist create_sdist(const sdist_params&); | sdist create_sdist(const sdist_params&); |