diff --git a/.clang-format b/.clang-format index 99466fb..4bde953 100644 --- a/.clang-format +++ b/.clang-format @@ -3,6 +3,7 @@ Language: Cpp BasedOnStyle: WebKit BinPackArguments: false ColumnLimit: 120 +NamespaceIndentation: None InsertNewlineAtEOF: true InsertTrailingCommas: Wrapped IncludeBlocks: Regroup diff --git a/conanfile.txt b/conanfile.txt index f9ea18f..7778a6c 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -9,7 +9,7 @@ argparse/2.9 spdlog/1.11.0 [test_requires] -gtest/cci.20210126 +gtest/1.16.0 [generators] CMakeDeps diff --git a/include/cppship/cmd/build.h b/include/cppship/cmd/build.h index d33a0d9..3d8a481 100644 --- a/include/cppship/cmd/build.h +++ b/include/cppship/cmd/build.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -12,6 +13,7 @@ #include "cppship/core/manifest.h" #include "cppship/core/profile.h" #include "cppship/core/workspace.h" +#include "cppship/util/cmd_runner.h" #include "cppship/util/fs.h" #include "cppship/util/repo.h" @@ -23,6 +25,7 @@ struct BuildOptions { int max_concurrency = gsl::narrow_cast(std::thread::hardware_concurrency()); Profile profile = Profile::debug; bool dry_run = false; + std::optional package; std::optional target; std::set groups; }; @@ -57,6 +60,8 @@ struct BuildContext { } [[nodiscard]] bool is_expired(const fs::path& path) const; + + [[nodiscard]] std::optional get_active_package() const; }; int run_build(const BuildOptions& options); @@ -72,12 +77,12 @@ void cppship_install( namespace cmd_internals { - std::string cmake_gen_config(const BuildContext& ctx, bool for_standalone_cmake = false); +std::string cmake_gen_config(const BuildContext& ctx, bool for_standalone_cmake = false); } void cmake_setup(const BuildContext& ctx); -int cmake_build(const BuildContext& ctx, const BuildOptions& options); +int cmake_build(const BuildContext& ctx, const BuildOptions& options, const util::CmdRunner& runner = {}); } diff --git a/include/cppship/core/manifest.h b/include/cppship/core/manifest.h index 8051406..98c8410 100644 --- a/include/cppship/core/manifest.h +++ b/include/cppship/core/manifest.h @@ -67,7 +67,7 @@ class Manifest { const PackageManifest* get_if_package() const { return std::get_if(&packages_); } - const PackageManifest* get(const fs::path& p) const + const PackageManifest* get_by_path(const fs::path& p) const { const auto* map = std::get_if<1>(&packages_); if (map == nullptr) { @@ -78,15 +78,14 @@ class Manifest { return it == map->end() ? nullptr : &it->second; } + const PackageManifest* get(std::string_view package) const; + const auto& list_packages() const { return std::get<1>(packages_); } const std::vector& dependencies() const { return mDependencies; } const std::vector& dev_dependencies() const { return mDevDependencies; } -private: - const PackageManifest& as_package_() const { return std::get(packages_); } - private: std::variant, PackageManifest> packages_; diff --git a/include/cppship/exception.h b/include/cppship/exception.h index 4f4932e..123b084 100644 --- a/include/cppship/exception.h +++ b/include/cppship/exception.h @@ -27,6 +27,18 @@ class CmdNotFound : public Error { std::string mCmd; }; +class InvalidCmdOption : public Error { +public: + explicit InvalidCmdOption(std::string_view option, const std::string& msg) + : Error(msg) + , mOption(option) + { + } + +private: + std::string mOption; +}; + class RunCmdFailed : public Error { public: RunCmdFailed(const int status, std::string_view cmd) @@ -54,4 +66,4 @@ class LayoutError : public Error { using Error::Error; }; -} \ No newline at end of file +} diff --git a/include/cppship/util/cmd_runner.h b/include/cppship/util/cmd_runner.h new file mode 100644 index 0000000..1275574 --- /dev/null +++ b/include/cppship/util/cmd_runner.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +namespace cppship::util { + +class CmdRunner { +public: + CmdRunner() = default; + + template + requires(!std::is_same_v) + explicit CmdRunner(Fn&& fn) + : mHook(std::forward(fn)) + { + } + + int run(std::string_view cmd) const; + +private: + std::function mHook; +}; + +} diff --git a/include/cppship/util/string.h b/include/cppship/util/string.h index 4f8f602..0b0bd34 100644 --- a/include/cppship/util/string.h +++ b/include/cppship/util/string.h @@ -22,4 +22,9 @@ template inline auto split(Input&& input, Pred pred) return split_to(std::forward(input), pred); } -} \ No newline at end of file +inline bool contains(std::string_view corpus, std::string_view patten) +{ + return corpus.find(patten) != std::string_view::npos; +} + +} diff --git a/lib/cmd/build.cpp b/lib/cmd/build.cpp index f563a4a..cb6e8ca 100644 --- a/lib/cmd/build.cpp +++ b/lib/cmd/build.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -32,6 +33,7 @@ #include "cppship/exception.h" #include "cppship/util/assert.h" #include "cppship/util/cmd.h" +#include "cppship/util/cmd_runner.h" #include "cppship/util/fs.h" #include "cppship/util/git.h" #include "cppship/util/io.h" @@ -60,6 +62,17 @@ bool cmd::BuildContext::is_expired(const fs::path& path) const return fs::last_write_time(path) < *ranges::max_element(manifest_last_update_times); } +std::optional cmd::BuildContext::get_active_package() const +{ + if (package_root == root) { + return std::nullopt; + } + + const auto path = fs::relative(package_root, root); + const auto* p = manifest.get_by_path(path); + return p != nullptr ? std::make_optional(p->name()) : std::nullopt; +} + int cmd::run_build(const BuildOptions& options) { BuildContext ctx(options.profile); @@ -173,7 +186,7 @@ void cmd::conan_setup(const BuildContext& ctx) oss << '\n' << "[test_requires]\n" - << "gtest/cci.20210126\n" + << "gtest/1.16.0\n" << "benchmark/1.7.1\n"; for (const auto& dep : result.conan_dev_dependencies) { const auto& desc = std::get(dep.desc); @@ -282,7 +295,7 @@ std::string cmd::cmd_internals::cmake_gen_config(const BuildContext& ctx, bool f }); for (const auto& [path, layout] : ctx.workspace) { - const auto* manifest = ctx.manifest.get(path); + const auto* manifest = ctx.manifest.get_by_path(path); enforce(manifest != nullptr, "manifest and workspace inconsistent"); gen.add(layout, *manifest, resolved_deps); @@ -381,10 +394,32 @@ std::string_view to_cmake_group(cmd::BuildGroup group) std::abort(); } +std::string to_cmake_group(cmd::BuildGroup group, const cmd::BuildContext& ctx, const cmd::BuildOptions& options) +{ + auto cmake_group = to_cmake_group(group); + const auto package = options.package.has_value() ? options.package : ctx.get_active_package(); + if (!package.has_value()) { + return std::string(cmake_group); + } + + return fmt::format("{}_{}", *package, cmake_group); +} + +void validate_options(const cmd::BuildContext& ctx, const cmd::BuildOptions& options) +{ + if (options.package.has_value()) { + if (ctx.manifest.get(*options.package) == nullptr) { + throw InvalidCmdOption("package", "invalid package specified by --package"); + } + } +} + } -int cmd::cmake_build(const BuildContext& ctx, const BuildOptions& options) +int cmd::cmake_build(const BuildContext& ctx, const BuildOptions& options, const util::CmdRunner& runner) { + validate_options(ctx, options); + auto cmd = fmt::format("cmake --build {} -j {} --config {}", ctx.profile_dir.string(), options.max_concurrency, @@ -392,13 +427,13 @@ int cmd::cmake_build(const BuildContext& ctx, const BuildOptions& options) if (options.target) { cmd += fmt::format(" --target {}", *options.target); } else if (options.groups.empty()) { - cmd += fmt::format(" --target {}", cmake::kCppshipGroupBinaries); + cmd += fmt::format(" --target {}", to_cmake_group(BuildGroup::binaries, ctx, options)); } else { for (const auto group : options.groups) { - cmd += fmt::format(" --target {}", to_cmake_group(group)); + cmd += fmt::format(" --target {}", to_cmake_group(group, ctx, options)); } } status("build", "{}", cmd); - return run_cmd(cmd); + return runner.run(cmd); } diff --git a/lib/core/manifest.cpp b/lib/core/manifest.cpp index 67f3a11..174caa2 100644 --- a/lib/core/manifest.cpp +++ b/lib/core/manifest.cpp @@ -9,7 +9,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -119,7 +121,7 @@ std::vector parse_dependencies(const toml::value& manifest, } else if (dep_config.is_table()) { if (dep_config.contains("git")) { GitDep desc; - desc.git = find(dep_config, "git"); + desc.git = toml::find(dep_config, "git"); desc.commit = find_or(dep_config, "commit", ""); if (desc.git.empty()) { throw Error { fmt::format("invalid git url {}", desc.git) }; @@ -276,7 +278,7 @@ Manifest::Manifest(const fs::path& file) std::set package_names; for (const auto& member : get_list(workspace, "members")) { auto package_path = fs::path(member); - const auto package_manifest_path = package_path / kRepoConfigFile; + const auto package_manifest_path = file.parent_path() / package_path / kRepoConfigFile; if (!fs::exists(package_manifest_path)) { throw Error { fmt::format("invalid workspace member {}", member) }; } @@ -298,6 +300,23 @@ Manifest::Manifest(const fs::path& file) } } +const PackageManifest* Manifest::get(std::string_view package) const +{ + switch (packages_.index()) { + case 1: { + const auto& ps = views::values(std::get<1>(packages_)); + const auto it = find(ps, package, &PackageManifest::name); + return it != ps.end() ? &*it : nullptr; + } + case 2: { + const auto& p = std::get<2>(packages_); + return p.name() == package ? &p : nullptr; + } + default: + return nullptr; + } +} + void cppship::generate_manifest(std::string_view name, CxxStd std, const fs::path& dir) { if (!fs::exists(dir)) { diff --git a/lib/util/cmd_runner.cpp b/lib/util/cmd_runner.cpp new file mode 100644 index 0000000..81f00ca --- /dev/null +++ b/lib/util/cmd_runner.cpp @@ -0,0 +1,16 @@ +#include "cppship/util/cmd_runner.h" + +#include "cppship/util/cmd.h" + +namespace cppship::util { + +int CmdRunner::run(std::string_view cmd) const +{ + if (mHook) { + return mHook(cmd); + } + + return run_cmd(cmd); +} + +} diff --git a/src/main.cpp b/src/main.cpp index c6d83cd..aafed34 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -40,6 +40,8 @@ struct SubCommand { int run() { return cmd_runner(parser); } }; +namespace { + Profile get_profile(const ArgumentParser& cmd) { if (cmd.is_used("profile")) { @@ -131,6 +133,7 @@ std::list build_commands(const ArgumentParser& common) .max_concurrency = get_concurrency(cmd), .profile = get_profile(cmd), .dry_run = cmd.get("-d"), + .package = cmd.present("--package"), .groups = groups, }); }); @@ -146,6 +149,7 @@ std::list build_commands(const ArgumentParser& common) .help("dry-run, generate compile_commands.json") .default_value(false) .implicit_value(true); + build.parser.add_argument("--package").help("package to build"); build.parser.add_argument("--profile").help("build with specific profile").default_value(kProfileDebug); build.parser.add_argument("--examples").help("build all examples").default_value(false).implicit_value(true); build.parser.add_argument("--tests").help("build all tests").default_value(false).implicit_value(true); @@ -274,6 +278,8 @@ std::list build_commands(const ArgumentParser& common) return commands; } +} + int main(int argc, const char* argv[]) try { spdlog::set_pattern("%v"); @@ -322,4 +328,4 @@ try { } catch (const std::exception& e) { error("unknown error {}", e.what()); return EXIT_FAILURE; -} \ No newline at end of file +} diff --git a/tests/cmd/build.cpp b/tests/cmd/build.cpp index fba8b3d..72167b3 100644 --- a/tests/cmd/build.cpp +++ b/tests/cmd/build.cpp @@ -2,8 +2,13 @@ #include +#include "cppship/cmake/group.h" #include "cppship/core/profile.h" +#include "cppship/exception.h" +#include "cppship/util/cmd_runner.h" +#include "cppship/util/fs.h" #include "cppship/util/io.h" +#include "cppship/util/string.h" namespace cppship { @@ -90,4 +95,112 @@ name = "p3")"); } } +TEST(build, get_active_package) +{ + const std::set package_manifests = { + "cppship.toml", + "package-1/cppship.toml", + "dir/package-2/cppship.toml", + "dir/package-3/cppship.toml", + }; + DirTree tree(package_manifests); + + write(tree.root() / "cppship.toml", R"( +[workspace] +members = ["package-1", "dir/package-2", "dir/package-3"])"); + write(tree.root() / "package-1/cppship.toml", R"( +[package] +version = "1.0.0" +name = "p1")"); + write(tree.root() / "dir/package-2/cppship.toml", R"( +[package] +version = "1.0.0" +name = "p2")"); + write(tree.root() / "dir/package-3/cppship.toml", R"( +[package] +version = "1.0.0" +name = "p3")"); + + { + cmd::BuildContext ctx(Profile::debug); + ASSERT_FALSE(ctx.get_active_package().has_value()); + } + + { + ScopedCurrentDir _("package-1"); + + cmd::BuildContext ctx(Profile::debug); + ASSERT_EQ(ctx.get_active_package().value_or("???"), "p1"); + } + + { + ScopedCurrentDir _("dir/package-2"); + + cmd::BuildContext ctx(Profile::debug); + ASSERT_EQ(ctx.get_active_package().value_or("???"), "p2"); + } + + { + ScopedCurrentDir _("dir/package-3"); + + cmd::BuildContext ctx(Profile::debug); + ASSERT_EQ(ctx.get_active_package().value_or("???"), "p3"); + } +} + +TEST(build, cmake_build) +{ + const std::set package_manifests = { + "cppship.toml", + "package-1/cppship.toml", + "dir/package-2/cppship.toml", + "dir/package-3/cppship.toml", + }; + DirTree tree(package_manifests); + + write(tree.root() / "cppship.toml", R"( +[workspace] +members = ["package-1", "dir/package-2", "dir/package-3"])"); + write(tree.root() / "package-1/cppship.toml", R"( +[package] +version = "1.0.0" +name = "p1")"); + write(tree.root() / "dir/package-2/cppship.toml", R"( +[package] +version = "1.0.0" +name = "p2")"); + write(tree.root() / "dir/package-3/cppship.toml", R"( +[package] +version = "1.0.0" +name = "p3")"); + + cmd::BuildContext ctx(Profile::debug); + + std::string cmd; + util::CmdRunner runner([&](std::string_view c) { + cmd.assign(c); + return 0; + }); + + cmd::cmake_build(ctx, {}, runner); + ASSERT_TRUE(cmd.ends_with(fmt::format("--target {}", cmake::kCppshipGroupBinaries))); + + ASSERT_THROW(cmd::cmake_build(ctx, { .package = "abc" }, runner), InvalidCmdOption); + + cmd::cmake_build(ctx, { .target = "p2" }, runner); + ASSERT_TRUE(cmd.ends_with("--target p2")); + + cmd::cmake_build(ctx, { .package = "p2" }, runner); + ASSERT_TRUE(cmd.ends_with(fmt::format("--target p2_{}", cmake::kCppshipGroupBinaries))); + + cmd::cmake_build(ctx, + { .package = "p2", + .groups = { + cmd::BuildGroup::benches, + cmd::BuildGroup::examples, + }, }, runner); + ASSERT_TRUE(util::contains(cmd, fmt::format("--target p2_{}", cmake::kCppshipGroupBenches))); + ASSERT_TRUE(util::contains(cmd, fmt::format("--target p2_{}", cmake::kCppshipGroupExamples))); +} + }