[M3] add codegen::cmake_lists
This commit is contained in:
@@ -70,6 +70,14 @@ All notable changes to cargoxx will be documented in this file.
|
||||
not show `buildInputs`; we add one between `nativeBuildInputs` and the
|
||||
`env.NIX_CFLAGS_COMPILE` block as the natural slot for the deps that
|
||||
TECH_SPEC §10 says we splice in.
|
||||
- `codegen::cmake_lists(in) -> std::string` per `SPEC.md` §8 and
|
||||
`TECH_SPEC.md` §9: `find_package` per dep, optional library target
|
||||
with module units + private impl sources, primary binary
|
||||
`<pkg>_bin` linked against the library, additional binaries from
|
||||
`src/bin/`, tests with `enable_testing()` + `add_test`, examples,
|
||||
and `[build]` honoring `warnings_as_errors` and `sanitizers`. Source
|
||||
paths emitted relative to `build/` (i.e. prefixed with `../`).
|
||||
Output is deterministic. `tests/codegen_cmake.cpp` covers 11 cases.
|
||||
- SQLite overlay: `Database::open(overlay_path)` now opens (and creates,
|
||||
if missing) a per-user `linkdb.sqlite` cache, applying the schema from
|
||||
`SPEC.md` §9 idempotently. Default path is
|
||||
|
||||
@@ -49,6 +49,7 @@ target_sources(cargoxx
|
||||
src/linkdb/curated.cpp
|
||||
src/linkdb/overlay.cpp
|
||||
src/codegen/flake.cpp
|
||||
src/codegen/cmake.cpp
|
||||
src/cli/cmd_new.cpp
|
||||
src/cli/run.cpp
|
||||
PUBLIC
|
||||
|
||||
289
src/codegen/cmake.cpp
Normal file
289
src/codegen/cmake.cpp
Normal file
@@ -0,0 +1,289 @@
|
||||
module cargoxx.codegen;
|
||||
|
||||
import std;
|
||||
import cargoxx.manifest;
|
||||
import cargoxx.layout;
|
||||
import cargoxx.linkdb;
|
||||
import cargoxx.lockfile;
|
||||
|
||||
namespace cargoxx::codegen {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
auto edition_to_int(manifest::Edition e) -> int {
|
||||
switch (e) {
|
||||
case manifest::Edition::Cpp20:
|
||||
return 20;
|
||||
case manifest::Edition::Cpp23:
|
||||
return 23;
|
||||
case manifest::Edition::Cpp26:
|
||||
return 26;
|
||||
}
|
||||
return 23;
|
||||
}
|
||||
|
||||
auto rel_to_build(const fs::path& p, const fs::path& project_root) -> std::string {
|
||||
return (fs::path{".."} / p.lexically_relative(project_root)).generic_string();
|
||||
}
|
||||
|
||||
auto collect_dep_targets(const std::vector<linkdb::Recipe>& recipes)
|
||||
-> std::vector<std::string> {
|
||||
std::vector<std::string> out;
|
||||
for (const auto& r : recipes) {
|
||||
for (const auto& t : r.targets) {
|
||||
out.push_back(t);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
auto link_block(std::string_view target_name, std::string_view visibility,
|
||||
bool include_lib, std::string_view package_name,
|
||||
const std::vector<std::string>& dep_targets) -> std::string {
|
||||
if (!include_lib && dep_targets.empty()) {
|
||||
return "";
|
||||
}
|
||||
std::string out = std::format("target_link_libraries({} {}\n", target_name, visibility);
|
||||
if (include_lib) {
|
||||
out += std::format(" {}\n", package_name);
|
||||
}
|
||||
for (const auto& t : dep_targets) {
|
||||
out += std::format(" {}\n", t);
|
||||
}
|
||||
out += ")\n";
|
||||
return out;
|
||||
}
|
||||
|
||||
auto emit_header(const manifest::Manifest& m) -> std::string {
|
||||
return std::format(
|
||||
"cmake_minimum_required(VERSION 3.30)\n"
|
||||
"project({} LANGUAGES CXX)\n"
|
||||
"\n"
|
||||
"# Generated by cargoxx — do not edit.\n"
|
||||
"# Source of truth: ../Cargoxx.toml\n"
|
||||
"\n"
|
||||
"# ----- toolchain configuration -----\n"
|
||||
"set(CMAKE_CXX_STANDARD {})\n"
|
||||
"set(CMAKE_CXX_STANDARD_REQUIRED ON)\n"
|
||||
"set(CMAKE_CXX_EXTENSIONS OFF)\n"
|
||||
"set(CMAKE_CXX_SCAN_FOR_MODULES ON)\n"
|
||||
"set(CMAKE_EXPORT_COMPILE_COMMANDS ON)\n",
|
||||
m.package.name, edition_to_int(m.package.edition));
|
||||
}
|
||||
|
||||
auto emit_find_packages(const std::vector<linkdb::Recipe>& recipes) -> std::string {
|
||||
if (recipes.empty()) {
|
||||
return {};
|
||||
}
|
||||
std::string out = "\n# ----- dependencies -----\n";
|
||||
for (const auto& r : recipes) {
|
||||
out += std::format("find_package({})\n", r.find_package);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
auto emit_library(const layout::Target& lib, const std::string& package_name,
|
||||
const std::vector<linkdb::Recipe>& recipes,
|
||||
const fs::path& project_root) -> std::string {
|
||||
std::string out = "\n# ----- library target -----\n";
|
||||
out += std::format("add_library({} STATIC)\n", package_name);
|
||||
out += std::format("target_sources({}\n", package_name);
|
||||
out += " PUBLIC\n";
|
||||
out += " FILE_SET CXX_MODULES FILES\n";
|
||||
for (const auto& m : lib.module_units) {
|
||||
out += std::format(" {}\n", rel_to_build(m, project_root));
|
||||
}
|
||||
if (!lib.additional_sources.empty()) {
|
||||
out += " PRIVATE\n";
|
||||
for (const auto& s : lib.additional_sources) {
|
||||
out += std::format(" {}\n", rel_to_build(s, project_root));
|
||||
}
|
||||
}
|
||||
out += ")\n";
|
||||
out += link_block(package_name, "PUBLIC", false, package_name,
|
||||
collect_dep_targets(recipes));
|
||||
return out;
|
||||
}
|
||||
|
||||
auto emit_primary_binary(const layout::Target& bin, const std::string& package_name,
|
||||
const std::vector<linkdb::Recipe>& recipes, bool has_lib,
|
||||
const fs::path& project_root) -> std::string {
|
||||
std::string out = "\n# ----- binary target -----\n";
|
||||
out += std::format("add_executable({}_bin {})\n", package_name,
|
||||
rel_to_build(bin.entry, project_root));
|
||||
out += std::format("set_target_properties({}_bin PROPERTIES OUTPUT_NAME {})\n",
|
||||
package_name, package_name);
|
||||
out += link_block(std::format("{}_bin", package_name), "PRIVATE", has_lib, package_name,
|
||||
collect_dep_targets(recipes));
|
||||
return out;
|
||||
}
|
||||
|
||||
auto emit_extra_binary(const layout::Target& bin, const std::string& package_name,
|
||||
const std::vector<linkdb::Recipe>& recipes, bool has_lib,
|
||||
const fs::path& project_root) -> std::string {
|
||||
std::string out = std::format("\n# ----- binary target: {} -----\n", bin.name);
|
||||
out += std::format("add_executable({} {})\n", bin.name,
|
||||
rel_to_build(bin.entry, project_root));
|
||||
out += link_block(bin.name, "PRIVATE", has_lib, package_name,
|
||||
collect_dep_targets(recipes));
|
||||
return out;
|
||||
}
|
||||
|
||||
auto emit_test(const layout::Target& t, const std::string& package_name,
|
||||
const std::vector<linkdb::Recipe>& recipes, bool has_lib,
|
||||
const fs::path& project_root) -> std::string {
|
||||
auto target = std::format("test_{}", t.name);
|
||||
std::string out = std::format("add_executable({} {})\n", target,
|
||||
rel_to_build(t.entry, project_root));
|
||||
out += link_block(target, "PRIVATE", has_lib, package_name,
|
||||
collect_dep_targets(recipes));
|
||||
out += std::format("add_test(NAME {} COMMAND {})\n", t.name, target);
|
||||
return out;
|
||||
}
|
||||
|
||||
auto emit_example(const layout::Target& e, const std::string& package_name,
|
||||
const std::vector<linkdb::Recipe>& recipes, bool has_lib,
|
||||
const fs::path& project_root) -> std::string {
|
||||
auto target = std::format("example_{}", e.name);
|
||||
std::string out = std::format("add_executable({} {})\n", target,
|
||||
rel_to_build(e.entry, project_root));
|
||||
out += link_block(target, "PRIVATE", has_lib, package_name,
|
||||
collect_dep_targets(recipes));
|
||||
return out;
|
||||
}
|
||||
|
||||
auto find_primary_bin(const layout::DiscoveredLayout& layout) -> const layout::Target* {
|
||||
for (const auto& b : layout.binaries) {
|
||||
if (b.entry.filename() == "main.cpp" &&
|
||||
b.entry.parent_path().filename() == "src") {
|
||||
return &b;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto collect_all_target_names(const layout::DiscoveredLayout& layout,
|
||||
const std::string& package_name) -> std::vector<std::string> {
|
||||
std::vector<std::string> out;
|
||||
if (layout.library) {
|
||||
out.push_back(package_name);
|
||||
}
|
||||
if (find_primary_bin(layout) != nullptr) {
|
||||
out.push_back(std::format("{}_bin", package_name));
|
||||
}
|
||||
for (const auto& b : layout.binaries) {
|
||||
if (b.entry.filename() == "main.cpp" &&
|
||||
b.entry.parent_path().filename() == "src") {
|
||||
continue;
|
||||
}
|
||||
out.push_back(b.name);
|
||||
}
|
||||
for (const auto& t : layout.tests) {
|
||||
out.push_back(std::format("test_{}", t.name));
|
||||
}
|
||||
for (const auto& e : layout.examples) {
|
||||
out.push_back(std::format("example_{}", e.name));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
auto emit_build_flags(const manifest::BuildSettings& build,
|
||||
const layout::DiscoveredLayout& layout,
|
||||
const std::string& package_name) -> std::string {
|
||||
if (!build.warnings_as_errors && build.sanitizers.empty()) {
|
||||
return {};
|
||||
}
|
||||
auto targets = collect_all_target_names(layout, package_name);
|
||||
if (targets.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string targets_list;
|
||||
for (std::size_t i = 0; i < targets.size(); ++i) {
|
||||
if (i > 0) {
|
||||
targets_list += ' ';
|
||||
}
|
||||
targets_list += targets[i];
|
||||
}
|
||||
|
||||
std::string out = "\n# ----- build flags from [build] section -----\n";
|
||||
|
||||
if (build.warnings_as_errors) {
|
||||
out += std::format("foreach(target_name IN ITEMS {})\n", targets_list);
|
||||
out += " target_compile_options(${target_name} PRIVATE -Wall -Wextra -Wpedantic "
|
||||
"-Werror)\n";
|
||||
out += "endforeach()\n";
|
||||
}
|
||||
|
||||
if (!build.sanitizers.empty()) {
|
||||
std::string san_list;
|
||||
for (std::size_t i = 0; i < build.sanitizers.size(); ++i) {
|
||||
if (i > 0) {
|
||||
san_list += ',';
|
||||
}
|
||||
san_list += build.sanitizers[i];
|
||||
}
|
||||
out += std::format("foreach(target_name IN ITEMS {})\n", targets_list);
|
||||
out += std::format(" target_compile_options(${{target_name}} PRIVATE "
|
||||
"-fsanitize={})\n",
|
||||
san_list);
|
||||
out += std::format(" target_link_options(${{target_name}} PRIVATE -fsanitize={})\n",
|
||||
san_list);
|
||||
out += "endforeach()\n";
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto cmake_lists(const GenerateInputs& in) -> std::string {
|
||||
const auto& pkg_name = in.manifest.package.name;
|
||||
const bool has_lib = in.layout.library.has_value();
|
||||
|
||||
std::string out;
|
||||
out += emit_header(in.manifest);
|
||||
out += emit_find_packages(in.recipes);
|
||||
|
||||
if (in.layout.library) {
|
||||
out += emit_library(*in.layout.library, pkg_name, in.recipes, in.project_root);
|
||||
}
|
||||
|
||||
const auto* primary = find_primary_bin(in.layout);
|
||||
if (primary) {
|
||||
out += emit_primary_binary(*primary, pkg_name, in.recipes, has_lib, in.project_root);
|
||||
}
|
||||
|
||||
bool has_extra_bin = false;
|
||||
for (const auto& b : in.layout.binaries) {
|
||||
if (&b == primary) {
|
||||
continue;
|
||||
}
|
||||
if (!has_extra_bin) {
|
||||
has_extra_bin = true;
|
||||
}
|
||||
out += emit_extra_binary(b, pkg_name, in.recipes, has_lib, in.project_root);
|
||||
}
|
||||
|
||||
if (!in.layout.tests.empty()) {
|
||||
out += "\n# ----- tests -----\nenable_testing()\n";
|
||||
for (const auto& t : in.layout.tests) {
|
||||
out += emit_test(t, pkg_name, in.recipes, has_lib, in.project_root);
|
||||
}
|
||||
}
|
||||
|
||||
if (!in.layout.examples.empty()) {
|
||||
out += "\n# ----- examples -----\n";
|
||||
for (const auto& e : in.layout.examples) {
|
||||
out += emit_example(e, pkg_name, in.recipes, has_lib, in.project_root);
|
||||
}
|
||||
}
|
||||
|
||||
out += emit_build_flags(in.manifest.build, in.layout, pkg_name);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace cargoxx::codegen
|
||||
@@ -20,5 +20,6 @@ struct GenerateInputs {
|
||||
};
|
||||
|
||||
auto flake_nix(const GenerateInputs& in) -> std::string;
|
||||
auto cmake_lists(const GenerateInputs& in) -> std::string;
|
||||
|
||||
} // namespace cargoxx::codegen
|
||||
|
||||
@@ -16,4 +16,5 @@ cargoxx_add_test(lockfile_round_trip)
|
||||
cargoxx_add_test(linkdb_lookup)
|
||||
cargoxx_add_test(linkdb_overlay)
|
||||
cargoxx_add_test(codegen_flake)
|
||||
cargoxx_add_test(codegen_cmake)
|
||||
cargoxx_add_test(cmd_new)
|
||||
|
||||
281
tests/codegen_cmake.cpp
Normal file
281
tests/codegen_cmake.cpp
Normal file
@@ -0,0 +1,281 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
import cargoxx.codegen;
|
||||
import cargoxx.manifest;
|
||||
import cargoxx.layout;
|
||||
import cargoxx.lockfile;
|
||||
import cargoxx.linkdb;
|
||||
import std;
|
||||
|
||||
using cargoxx::codegen::cmake_lists;
|
||||
using cargoxx::codegen::GenerateInputs;
|
||||
using cargoxx::layout::DiscoveredLayout;
|
||||
using cargoxx::layout::Target;
|
||||
using cargoxx::layout::TargetKind;
|
||||
using cargoxx::linkdb::Recipe;
|
||||
using cargoxx::lockfile::Lockfile;
|
||||
using cargoxx::lockfile::LockfilePackage;
|
||||
using cargoxx::manifest::BuildSettings;
|
||||
using cargoxx::manifest::Edition;
|
||||
using cargoxx::manifest::Manifest;
|
||||
using cargoxx::manifest::Package;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view ROOT = "/proj";
|
||||
|
||||
auto pkg(std::string name, Edition ed = Edition::Cpp23) -> Package {
|
||||
return Package{.name = std::move(name),
|
||||
.version = "0.1.0",
|
||||
.edition = ed,
|
||||
.authors = {},
|
||||
.license = std::nullopt};
|
||||
}
|
||||
|
||||
auto lock_minimal() -> Lockfile { return Lockfile{1, {}}; }
|
||||
|
||||
auto src_target(TargetKind kind, std::string name, std::string rel_entry,
|
||||
std::vector<std::string> module_units = {}) -> Target {
|
||||
Target t{
|
||||
.kind = kind,
|
||||
.name = std::move(name),
|
||||
.entry = std::filesystem::path{ROOT} / rel_entry,
|
||||
.additional_sources = {},
|
||||
.module_units = {},
|
||||
};
|
||||
for (auto& u : module_units) {
|
||||
t.module_units.push_back(std::filesystem::path{ROOT} / u);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
auto recipe(std::string find_pkg, std::vector<std::string> targets) -> Recipe {
|
||||
return Recipe{
|
||||
.nixpkgs_attr = "ignored",
|
||||
.find_package = std::move(find_pkg),
|
||||
.targets = std::move(targets),
|
||||
.source = "curated",
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("cmake_lists for a binary-only project", "[codegen][cmake]") {
|
||||
Manifest m{pkg("hello"), {}, {}};
|
||||
DiscoveredLayout layout{
|
||||
.library = std::nullopt,
|
||||
.binaries = {src_target(TargetKind::Binary, "hello", "src/main.cpp")},
|
||||
.tests = {},
|
||||
.examples = {},
|
||||
};
|
||||
Lockfile lock = lock_minimal();
|
||||
GenerateInputs in{m, layout, lock, {}, ROOT};
|
||||
|
||||
auto out = cmake_lists(in);
|
||||
REQUIRE(out.find("project(hello LANGUAGES CXX)") != std::string::npos);
|
||||
REQUIRE(out.find("set(CMAKE_CXX_STANDARD 23)") != std::string::npos);
|
||||
REQUIRE(out.find("add_executable(hello_bin ../src/main.cpp)") != std::string::npos);
|
||||
REQUIRE(out.find("set_target_properties(hello_bin PROPERTIES OUTPUT_NAME hello)") !=
|
||||
std::string::npos);
|
||||
REQUIRE(out.find("add_library") == std::string::npos);
|
||||
REQUIRE(out.find("enable_testing") == std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("cmake_lists for a library-only project", "[codegen][cmake]") {
|
||||
Manifest m{pkg("widget"), {}, {}};
|
||||
DiscoveredLayout layout{
|
||||
.library = src_target(TargetKind::Library, "widget", "src/lib.cppm",
|
||||
{"src/lib.cppm"}),
|
||||
.binaries = {},
|
||||
.tests = {},
|
||||
.examples = {},
|
||||
};
|
||||
Lockfile lock = lock_minimal();
|
||||
GenerateInputs in{m, layout, lock, {}, ROOT};
|
||||
|
||||
auto out = cmake_lists(in);
|
||||
REQUIRE(out.find("add_library(widget STATIC)") != std::string::npos);
|
||||
REQUIRE(out.find("FILE_SET CXX_MODULES FILES") != std::string::npos);
|
||||
REQUIRE(out.find("../src/lib.cppm") != std::string::npos);
|
||||
REQUIRE(out.find("add_executable") == std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("cmake_lists wires up library + primary binary", "[codegen][cmake]") {
|
||||
Manifest m{pkg("app"), {}, {}};
|
||||
DiscoveredLayout layout{
|
||||
.library = src_target(TargetKind::Library, "app", "src/lib.cppm",
|
||||
{"src/lib.cppm"}),
|
||||
.binaries = {src_target(TargetKind::Binary, "app", "src/main.cpp")},
|
||||
.tests = {},
|
||||
.examples = {},
|
||||
};
|
||||
Lockfile lock = lock_minimal();
|
||||
GenerateInputs in{m, layout, lock, {}, ROOT};
|
||||
|
||||
auto out = cmake_lists(in);
|
||||
REQUIRE(out.find("add_library(app STATIC)") != std::string::npos);
|
||||
REQUIRE(out.find("add_executable(app_bin ../src/main.cpp)") != std::string::npos);
|
||||
// primary binary must link the library
|
||||
auto bin_link = out.find("target_link_libraries(app_bin PRIVATE");
|
||||
REQUIRE(bin_link != std::string::npos);
|
||||
auto end = out.find(')', bin_link);
|
||||
auto block = out.substr(bin_link, end - bin_link);
|
||||
REQUIRE(block.find("\n app\n") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("cmake_lists emits extra binaries from src/bin/", "[codegen][cmake]") {
|
||||
Manifest m{pkg("app"), {}, {}};
|
||||
DiscoveredLayout layout{
|
||||
.library = std::nullopt,
|
||||
.binaries = {src_target(TargetKind::Binary, "tool", "src/bin/tool.cpp"),
|
||||
src_target(TargetKind::Binary, "app", "src/main.cpp")},
|
||||
.tests = {},
|
||||
.examples = {},
|
||||
};
|
||||
Lockfile lock = lock_minimal();
|
||||
GenerateInputs in{m, layout, lock, {}, ROOT};
|
||||
|
||||
auto out = cmake_lists(in);
|
||||
REQUIRE(out.find("add_executable(app_bin ../src/main.cpp)") != std::string::npos);
|
||||
REQUIRE(out.find("add_executable(tool ../src/bin/tool.cpp)") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("cmake_lists emits tests with add_test", "[codegen][cmake]") {
|
||||
Manifest m{pkg("app"), {}, {}};
|
||||
DiscoveredLayout layout{
|
||||
.library = src_target(TargetKind::Library, "app", "src/lib.cppm",
|
||||
{"src/lib.cppm"}),
|
||||
.binaries = {},
|
||||
.tests = {src_target(TargetKind::Test, "basic", "tests/basic.cpp")},
|
||||
.examples = {},
|
||||
};
|
||||
Lockfile lock = lock_minimal();
|
||||
GenerateInputs in{m, layout, lock, {}, ROOT};
|
||||
|
||||
auto out = cmake_lists(in);
|
||||
REQUIRE(out.find("enable_testing()") != std::string::npos);
|
||||
REQUIRE(out.find("add_executable(test_basic ../tests/basic.cpp)") != std::string::npos);
|
||||
REQUIRE(out.find("add_test(NAME basic COMMAND test_basic)") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("cmake_lists emits examples", "[codegen][cmake]") {
|
||||
Manifest m{pkg("app"), {}, {}};
|
||||
DiscoveredLayout layout{
|
||||
.library = std::nullopt,
|
||||
.binaries = {src_target(TargetKind::Binary, "app", "src/main.cpp")},
|
||||
.tests = {},
|
||||
.examples = {src_target(TargetKind::Example, "demo", "examples/demo.cpp")},
|
||||
};
|
||||
Lockfile lock = lock_minimal();
|
||||
GenerateInputs in{m, layout, lock, {}, ROOT};
|
||||
|
||||
auto out = cmake_lists(in);
|
||||
REQUIRE(out.find("add_executable(example_demo ../examples/demo.cpp)") !=
|
||||
std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("cmake_lists emits find_package per dep", "[codegen][cmake]") {
|
||||
Manifest m{pkg("app"), {}, {}};
|
||||
DiscoveredLayout layout{
|
||||
.library = std::nullopt,
|
||||
.binaries = {src_target(TargetKind::Binary, "app", "src/main.cpp")},
|
||||
.tests = {},
|
||||
.examples = {},
|
||||
};
|
||||
Lockfile lock = lock_minimal();
|
||||
std::vector<Recipe> recipes = {
|
||||
recipe("fmt CONFIG REQUIRED", {"fmt::fmt"}),
|
||||
recipe("Boost REQUIRED COMPONENTS filesystem system",
|
||||
{"Boost::filesystem", "Boost::system"}),
|
||||
};
|
||||
GenerateInputs in{m, layout, lock, recipes, ROOT};
|
||||
|
||||
auto out = cmake_lists(in);
|
||||
REQUIRE(out.find("find_package(fmt CONFIG REQUIRED)") != std::string::npos);
|
||||
REQUIRE(out.find("find_package(Boost REQUIRED COMPONENTS filesystem system)") !=
|
||||
std::string::npos);
|
||||
|
||||
auto link_block = out.find("target_link_libraries(app_bin PRIVATE");
|
||||
REQUIRE(link_block != std::string::npos);
|
||||
auto end = out.find(')', link_block);
|
||||
auto block = out.substr(link_block, end - link_block);
|
||||
REQUIRE(block.find("fmt::fmt") != std::string::npos);
|
||||
REQUIRE(block.find("Boost::filesystem") != std::string::npos);
|
||||
REQUIRE(block.find("Boost::system") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("cmake_lists honors warnings_as_errors", "[codegen][cmake]") {
|
||||
Manifest m{
|
||||
pkg("app"),
|
||||
{},
|
||||
BuildSettings{.warnings_as_errors = true, .sanitizers = {}},
|
||||
};
|
||||
DiscoveredLayout layout{
|
||||
.library = std::nullopt,
|
||||
.binaries = {src_target(TargetKind::Binary, "app", "src/main.cpp")},
|
||||
.tests = {},
|
||||
.examples = {},
|
||||
};
|
||||
Lockfile lock = lock_minimal();
|
||||
GenerateInputs in{m, layout, lock, {}, ROOT};
|
||||
|
||||
auto out = cmake_lists(in);
|
||||
REQUIRE(out.find("foreach(target_name IN ITEMS app_bin)") != std::string::npos);
|
||||
REQUIRE(out.find("-Wall -Wextra -Wpedantic -Werror") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("cmake_lists honors sanitizers", "[codegen][cmake]") {
|
||||
Manifest m{
|
||||
pkg("app"),
|
||||
{},
|
||||
BuildSettings{.warnings_as_errors = false,
|
||||
.sanitizers = {"address", "undefined"}},
|
||||
};
|
||||
DiscoveredLayout layout{
|
||||
.library = std::nullopt,
|
||||
.binaries = {src_target(TargetKind::Binary, "app", "src/main.cpp")},
|
||||
.tests = {},
|
||||
.examples = {},
|
||||
};
|
||||
Lockfile lock = lock_minimal();
|
||||
GenerateInputs in{m, layout, lock, {}, ROOT};
|
||||
|
||||
auto out = cmake_lists(in);
|
||||
REQUIRE(out.find("-fsanitize=address,undefined") != std::string::npos);
|
||||
REQUIRE(out.find("target_link_options(${target_name} PRIVATE -fsanitize=") !=
|
||||
std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("cmake_lists maps editions to standard numbers", "[codegen][cmake]") {
|
||||
DiscoveredLayout layout{
|
||||
.library = std::nullopt,
|
||||
.binaries = {src_target(TargetKind::Binary, "app", "src/main.cpp")},
|
||||
.tests = {},
|
||||
.examples = {},
|
||||
};
|
||||
Lockfile lock = lock_minimal();
|
||||
|
||||
Manifest m20{pkg("app", Edition::Cpp20), {}, {}};
|
||||
REQUIRE(cmake_lists(GenerateInputs{m20, layout, lock, {}, ROOT})
|
||||
.find("set(CMAKE_CXX_STANDARD 20)") != std::string::npos);
|
||||
|
||||
Manifest m26{pkg("app", Edition::Cpp26), {}, {}};
|
||||
REQUIRE(cmake_lists(GenerateInputs{m26, layout, lock, {}, ROOT})
|
||||
.find("set(CMAKE_CXX_STANDARD 26)") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("cmake_lists is deterministic", "[codegen][cmake]") {
|
||||
Manifest m{pkg("app"), {}, {}};
|
||||
DiscoveredLayout layout{
|
||||
.library = src_target(TargetKind::Library, "app", "src/lib.cppm",
|
||||
{"src/lib.cppm"}),
|
||||
.binaries = {src_target(TargetKind::Binary, "app", "src/main.cpp")},
|
||||
.tests = {src_target(TargetKind::Test, "basic", "tests/basic.cpp")},
|
||||
.examples = {},
|
||||
};
|
||||
Lockfile lock = lock_minimal();
|
||||
std::vector<Recipe> recipes = {recipe("fmt CONFIG REQUIRED", {"fmt::fmt"})};
|
||||
GenerateInputs in{m, layout, lock, recipes, ROOT};
|
||||
|
||||
REQUIRE(cmake_lists(in) == cmake_lists(in));
|
||||
}
|
||||
Reference in New Issue
Block a user