[M8] reusable libraries: install layout + cargoxx-path deps
This commit is contained in:
@@ -69,7 +69,7 @@ TEST_CASE("cmd_build generates files for a no-deps binary project",
|
||||
REQUIRE(std::filesystem::exists(root / "Cargoxx.lock"));
|
||||
|
||||
auto cmake_text = read_file(root / "build" / "CMakeLists.txt");
|
||||
REQUIRE(cmake_text.find("project(hello LANGUAGES CXX)") != std::string::npos);
|
||||
REQUIRE(cmake_text.find("project(hello VERSION 0.1.0 LANGUAGES CXX)") != std::string::npos);
|
||||
REQUIRE(cmake_text.find("add_executable(hello_bin ../src/main.cpp)") !=
|
||||
std::string::npos);
|
||||
|
||||
|
||||
@@ -72,10 +72,15 @@ TEST_CASE("cmake_lists for a binary-only project", "[codegen][cmake]") {
|
||||
GenerateInputs in{m, layout, lock, {}, {}, ROOT};
|
||||
|
||||
auto out = cmake_lists(in);
|
||||
REQUIRE(out.find("project(hello LANGUAGES CXX)") != std::string::npos);
|
||||
REQUIRE(out.find("project(hello VERSION 0.1.0 LANGUAGES CXX)") != std::string::npos);
|
||||
REQUIRE(out.find("include(GNUInstallDirs)") != 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)") !=
|
||||
REQUIRE(out.find("set_target_properties(hello_bin PROPERTIES\n"
|
||||
" OUTPUT_NAME hello\n"
|
||||
" RUNTIME_OUTPUT_DIRECTORY \"${CMAKE_BINARY_DIR}/bin\")") !=
|
||||
std::string::npos);
|
||||
REQUIRE(out.find("install(TARGETS hello_bin RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})") !=
|
||||
std::string::npos);
|
||||
REQUIRE(out.find("add_library") == std::string::npos);
|
||||
REQUIRE(out.find("enable_testing") == std::string::npos);
|
||||
@@ -98,6 +103,15 @@ TEST_CASE("cmake_lists for a library-only project", "[codegen][cmake]") {
|
||||
REQUIRE(out.find("FILE_SET CXX_MODULES") != std::string::npos);
|
||||
REQUIRE(out.find("../src/lib.cppm") != std::string::npos);
|
||||
REQUIRE(out.find("add_executable") == std::string::npos);
|
||||
// Library projects emit install rules + Config.cmake + .pc.
|
||||
REQUIRE(out.find("install(TARGETS widget\n EXPORT widgetTargets") !=
|
||||
std::string::npos);
|
||||
REQUIRE(out.find("install(EXPORT widgetTargets") != std::string::npos);
|
||||
REQUIRE(out.find("configure_package_config_file(") != std::string::npos);
|
||||
REQUIRE(out.find("write_basic_package_version_file(") != std::string::npos);
|
||||
REQUIRE(out.find("widget.pc.in") != std::string::npos);
|
||||
REQUIRE(out.find("DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig") !=
|
||||
std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("cmake_lists wires up library + primary binary", "[codegen][cmake]") {
|
||||
@@ -138,6 +152,13 @@ TEST_CASE("cmake_lists emits extra binaries from src/bin/", "[codegen][cmake]")
|
||||
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);
|
||||
REQUIRE(out.find("set_target_properties(app_bin PROPERTIES\n"
|
||||
" OUTPUT_NAME app\n"
|
||||
" RUNTIME_OUTPUT_DIRECTORY \"${CMAKE_BINARY_DIR}/bin\")") !=
|
||||
std::string::npos);
|
||||
REQUIRE(out.find("set_target_properties(tool PROPERTIES\n"
|
||||
" RUNTIME_OUTPUT_DIRECTORY \"${CMAKE_BINARY_DIR}/bin\")") !=
|
||||
std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("cmake_lists emits tests with add_test", "[codegen][cmake]") {
|
||||
|
||||
8
tests/e2e/buildCppPackage/src/bin/extra.cpp
Normal file
8
tests/e2e/buildCppPackage/src/bin/extra.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
import std;
|
||||
int main() {
|
||||
nlohmann::json j;
|
||||
j["from"] = "extra";
|
||||
std::println("{}", j["from"].get<std::string>());
|
||||
return 0;
|
||||
}
|
||||
7
tests/e2e/pathDep/Cargoxx.toml
Normal file
7
tests/e2e/pathDep/Cargoxx.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "consumer"
|
||||
version = "0.1.0"
|
||||
edition = "cpp23"
|
||||
|
||||
[dependencies]
|
||||
greeter = { path = "./greeter" }
|
||||
10
tests/e2e/pathDep/flake.nix
Normal file
10
tests/e2e/pathDep/flake.nix
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
description = "e2e cargoxx path-dep smoke";
|
||||
|
||||
inputs.cargoxx.url = "path:../../..";
|
||||
|
||||
outputs = { self, cargoxx }: {
|
||||
packages.x86_64-linux.default =
|
||||
cargoxx.lib.x86_64-linux.buildCppPackage { src = ./.; };
|
||||
};
|
||||
}
|
||||
4
tests/e2e/pathDep/greeter/Cargoxx.toml
Normal file
4
tests/e2e/pathDep/greeter/Cargoxx.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "greeter"
|
||||
version = "0.1.0"
|
||||
edition = "cpp23"
|
||||
8
tests/e2e/pathDep/greeter/src/lib.cppm
Normal file
8
tests/e2e/pathDep/greeter/src/lib.cppm
Normal file
@@ -0,0 +1,8 @@
|
||||
export module greeter;
|
||||
import std;
|
||||
|
||||
export namespace greeter {
|
||||
auto hello(std::string_view who) -> std::string {
|
||||
return std::format("Hello from greeter, {}!", who);
|
||||
}
|
||||
} // namespace greeter
|
||||
48
tests/e2e/pathDep/run.sh
Executable file
48
tests/e2e/pathDep/run.sh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
repo="$(cd "${here}/../../.." && pwd)"
|
||||
cargoxx_bin="${CARGOXX_BIN:-${repo}/build/debug/cargoxx}"
|
||||
|
||||
if [[ ! -x "${cargoxx_bin}" ]]; then
|
||||
echo "error: cargoxx binary not found at ${cargoxx_bin}" >&2
|
||||
echo "build it first: nix develop --command cmake --build build/debug" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
work="$(mktemp -d -t cargoxx-e2e-pathdep-XXXXXX)"
|
||||
trap 'rm -rf "${work}"' EXIT
|
||||
|
||||
cp -r "${here}/." "${work}/"
|
||||
sed -i "s|path:\\.\\./\\.\\./\\.\\.|path:${repo}|" "${work}/flake.nix"
|
||||
|
||||
cd "${work}"
|
||||
|
||||
echo "=== cargoxx build --no-build in greeter (path dep generates its own lock)"
|
||||
(cd greeter && "${cargoxx_bin}" build --no-build)
|
||||
|
||||
echo "=== cargoxx build --no-build in consumer"
|
||||
"${cargoxx_bin}" build --no-build
|
||||
|
||||
[[ -f Cargoxx.lock ]] || { echo "Cargoxx.lock missing"; exit 1; }
|
||||
grep -q "source_kind = 'cargoxx-path'" Cargoxx.lock || \
|
||||
{ echo "Cargoxx.lock missing source_kind = cargoxx-path"; exit 1; }
|
||||
|
||||
# nix build needs the source tree to be a git tree so 'path:' input copies
|
||||
# Cargoxx.lock into the store. Init a throwaway git here.
|
||||
git init -q
|
||||
git add -A
|
||||
git -c user.email=e2e@cargoxx -c user.name=e2e commit -q -m fixture
|
||||
|
||||
echo "=== nix build .#default"
|
||||
out="$(nix build .#default --no-link --print-out-paths \
|
||||
--extra-experimental-features 'nix-command flakes')"
|
||||
|
||||
[[ -n "${out}" ]] || { echo "nix build produced no output path"; exit 1; }
|
||||
[[ -x "${out}/bin/consumer" ]] || { echo "missing ${out}/bin/consumer"; exit 1; }
|
||||
|
||||
echo "=== execute"
|
||||
"${out}/bin/consumer"
|
||||
|
||||
echo "ok"
|
||||
7
tests/e2e/pathDep/src/main.cpp
Normal file
7
tests/e2e/pathDep/src/main.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
import std;
|
||||
import greeter;
|
||||
|
||||
int main() {
|
||||
std::println("{}", greeter::hello("world"));
|
||||
return 0;
|
||||
}
|
||||
@@ -131,16 +131,31 @@ TEST_CASE("discover lists src/bin/*.cpp as additional binaries", "[layout]") {
|
||||
REQUIRE(r->binaries[2].name == "pkg");
|
||||
}
|
||||
|
||||
TEST_CASE("discover does not recurse into src/bin/", "[layout]") {
|
||||
TEST_CASE("discover ignores src/bin/<sub>/ subdirs without main.cpp", "[layout]") {
|
||||
TempProject p;
|
||||
p.touch("src/main.cpp");
|
||||
p.touch("src/bin/foo.cpp");
|
||||
p.touch("src/bin/sub/nested.cpp"); // should be ignored
|
||||
p.touch("src/bin/sub/nested.cpp"); // no main.cpp at sub/ root, ignored
|
||||
auto r = discover(p.root(), "pkg");
|
||||
REQUIRE(r.has_value());
|
||||
REQUIRE(r->binaries.size() == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("discover lists src/bin/<sub>/main.cpp as a binary named <sub>",
|
||||
"[layout]") {
|
||||
TempProject p;
|
||||
p.touch("src/main.cpp");
|
||||
p.touch("src/bin/extra/main.cpp");
|
||||
p.touch("src/bin/extra/helpers.cpp"); // not collected in v1
|
||||
auto r = discover(p.root(), "pkg");
|
||||
REQUIRE(r.has_value());
|
||||
REQUIRE(r->binaries.size() == 2);
|
||||
REQUIRE(r->binaries[0].name == "extra");
|
||||
REQUIRE(r->binaries[0].entry.filename() == "main.cpp");
|
||||
REQUIRE(r->binaries[0].entry.parent_path().filename() == "extra");
|
||||
REQUIRE(r->binaries[1].name == "pkg");
|
||||
}
|
||||
|
||||
TEST_CASE("discover lists tests/*.cpp", "[layout]") {
|
||||
TempProject p;
|
||||
p.touch("src/main.cpp");
|
||||
|
||||
@@ -118,6 +118,30 @@ TEST_CASE("write round-trips lockfile recipe fields", "[lockfile]") {
|
||||
REQUIRE(round_trip(l) == l);
|
||||
}
|
||||
|
||||
TEST_CASE("write round-trips cargoxx-path source fields", "[lockfile]") {
|
||||
Lockfile l{
|
||||
.version = 1,
|
||||
.packages = {
|
||||
LockfilePackage{
|
||||
.name = "mylib",
|
||||
.version = "*",
|
||||
.dependencies = {},
|
||||
.nixpkgs_attr = std::nullopt,
|
||||
.nixpkgs_rev = std::nullopt,
|
||||
.linkdb_source = "cargoxx-path",
|
||||
.find_package = "mylib CONFIG REQUIRED",
|
||||
.targets = {"mylib::mylib"},
|
||||
.pkg_config_module = std::nullopt,
|
||||
.brute_force_libs = {},
|
||||
.brute_force_includes = {},
|
||||
.source_kind = "cargoxx-path",
|
||||
.source_path = "../mylib",
|
||||
},
|
||||
},
|
||||
};
|
||||
REQUIRE(round_trip(l) == l);
|
||||
}
|
||||
|
||||
TEST_CASE("Lockfile::nixpkgs_rev returns the shared rev", "[lockfile]") {
|
||||
Lockfile l{
|
||||
.version = 1,
|
||||
|
||||
@@ -297,3 +297,42 @@ version = "0.1.0"
|
||||
REQUIRE_FALSE(r.has_value());
|
||||
REQUIRE(r.error().code == ErrorCode::ManifestInvalidField);
|
||||
}
|
||||
|
||||
TEST_CASE("parse recognizes { path = \"...\" } as a cargoxx path dep",
|
||||
"[manifest][parse]") {
|
||||
auto p = write_manifest(R"(
|
||||
[package]
|
||||
name = "consumer"
|
||||
version = "0.1.0"
|
||||
edition = "cpp23"
|
||||
|
||||
[dependencies]
|
||||
mylib = { path = "../mylib" }
|
||||
)");
|
||||
auto r = parse(p);
|
||||
REQUIRE(r.has_value());
|
||||
REQUIRE(r->dependencies.size() == 1);
|
||||
const auto& dep = r->dependencies[0];
|
||||
REQUIRE(dep.name == "mylib");
|
||||
REQUIRE(dep.source == cargoxx::manifest::DepSource::CargoxxPath);
|
||||
REQUIRE(dep.path.has_value());
|
||||
REQUIRE(*dep.path == "../mylib");
|
||||
// Version defaults to "*" when only `path` is given.
|
||||
REQUIRE(dep.version_spec == "*");
|
||||
}
|
||||
|
||||
TEST_CASE("parse rejects dep table without version or path",
|
||||
"[manifest][parse]") {
|
||||
auto p = write_manifest(R"(
|
||||
[package]
|
||||
name = "consumer"
|
||||
version = "0.1.0"
|
||||
edition = "cpp23"
|
||||
|
||||
[dependencies]
|
||||
mylib = { components = ["a"] }
|
||||
)");
|
||||
auto r = parse(p);
|
||||
REQUIRE_FALSE(r.has_value());
|
||||
REQUIRE(r.error().code == ErrorCode::ManifestInvalidField);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user