2026-10-31 02:56:15 +01:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
|
|
|
|
|
|
import std;
|
|
|
|
|
import Crafter.Build;
|
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
|
using namespace Crafter;
|
|
|
|
|
|
|
|
|
|
// A GitProject or LocalProject without `.args` forwards the args of the
|
|
|
|
|
// project file that declares it: both specs default their args to
|
|
|
|
|
// ProjectArgs(). Before that default, every project.cpp with a sibling
|
|
|
|
|
// dependency copied its span into a std::vector<std::string> for each spec,
|
|
|
|
|
// and a spec that missed the copy built its sibling with none of the
|
|
|
|
|
// project's flags: for the host while the project cross-compiled, and in
|
|
|
|
|
// release while the project built --debug. The fixture is a chain
|
|
|
|
|
// app -> lib -> leaf of LocalProjects; app also loads lib with an empty and
|
|
|
|
|
// with a replaced list, and checks the GitProjectSpec default itself, which
|
2026-10-31 05:10:02 +01:00
|
|
|
// a clone would need a repository for. A spec made at namespace scope gets
|
|
|
|
|
// no args: made while dlopen runs the file's static initialisers, which
|
|
|
|
|
// happens on its first load only, it would otherwise keep that load's args
|
|
|
|
|
// for every later one.
|
2026-10-31 02:56:15 +01:00
|
|
|
namespace {
|
|
|
|
|
std::int32_t Failures = 0;
|
|
|
|
|
|
|
|
|
|
void Check(bool cond, std::string_view msg) {
|
|
|
|
|
if (!cond) {
|
|
|
|
|
std::println(std::cerr, "FAIL: {}", msg);
|
|
|
|
|
++Failures;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-31 05:10:02 +01:00
|
|
|
// The single sibling lib, static and leaf declare, or nullptr.
|
2026-10-31 02:56:15 +01:00
|
|
|
const Configuration* OnlyDependency(const Configuration& cfg) {
|
|
|
|
|
return cfg.dependencies.size() == 1 ? cfg.dependencies[0] : nullptr;
|
|
|
|
|
}
|
2026-10-31 05:10:02 +01:00
|
|
|
|
|
|
|
|
// What a sibling was loaded with, as ApplyStandardArgs split it up:
|
|
|
|
|
// --debug into cfg.debug, the uninterpreted rest into cfg.projectArgs.
|
|
|
|
|
// Printed either way, so a failure says which args the sibling read.
|
|
|
|
|
void CheckLoaded(const Configuration* cfg, bool debug, const std::vector<std::string>& rest, std::string_view what) {
|
|
|
|
|
if (!cfg) {
|
|
|
|
|
Check(false, std::format("{} (no such configuration)", what));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Check(cfg->debug == debug && cfg->projectArgs == rest, std::format("{} (debug {}, projectArgs {})", what, cfg->debug, cfg->projectArgs));
|
|
|
|
|
}
|
2026-10-31 02:56:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
const fs::path installedHome = GetCrafterBuildHome();
|
|
|
|
|
if (!fs::exists(installedHome / "Crafter.Build.cppm")) {
|
|
|
|
|
std::println(std::cerr, "SKIP: no module sources at {}", installedHome.string());
|
|
|
|
|
return 77;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Staged because LoadProject drops a build/ tree next to every project
|
|
|
|
|
// file it compiles.
|
|
|
|
|
const fs::path fixture = fs::current_path() / "tests" / "ProjectArgs" / "fixture";
|
|
|
|
|
const fs::path scratch = fs::temp_directory_path() / "crafter-build-project-args";
|
|
|
|
|
std::error_code ec;
|
|
|
|
|
fs::remove_all(scratch, ec);
|
|
|
|
|
fs::create_directories(scratch);
|
|
|
|
|
fs::copy(fixture, scratch, fs::copy_options::recursive);
|
|
|
|
|
|
|
|
|
|
Check(ProjectArgs().empty(), "no project args outside a project file");
|
|
|
|
|
|
|
|
|
|
// Loaded from the project's own directory, as crafter-build runs it: the
|
|
|
|
|
// fixture's LocalProject paths are relative to the working directory.
|
|
|
|
|
const fs::path cwd = fs::current_path();
|
|
|
|
|
fs::current_path(scratch / "app");
|
|
|
|
|
std::array<std::string_view, 2> args = { "--debug", "--flavor=x" };
|
|
|
|
|
const std::vector<std::string> flavor = { "--flavor=x" };
|
|
|
|
|
const std::vector<std::string> only = { "--only" };
|
|
|
|
|
try {
|
|
|
|
|
Configuration cfg = LoadProject("project.cpp", args);
|
|
|
|
|
Check(std::ranges::contains(cfg.compileFlags, "-DGIT_DEFAULT=1"), "a GitProjectSpec's args default to the project's own");
|
2026-10-31 05:10:02 +01:00
|
|
|
Check(cfg.dependencies.size() == 4, std::format("the project declared its four siblings (got {})", cfg.dependencies.size()));
|
|
|
|
|
if (cfg.dependencies.size() == 4) {
|
|
|
|
|
const Configuration* inherited = cfg.dependencies[0];
|
|
|
|
|
const Configuration* none = cfg.dependencies[1];
|
|
|
|
|
const Configuration* replaced = cfg.dependencies[2];
|
|
|
|
|
const Configuration* fromStatic = cfg.dependencies[3];
|
|
|
|
|
CheckLoaded(inherited, true, flavor, "a LocalProject without .args gets the project's args");
|
|
|
|
|
CheckLoaded(none, false, {}, "`.args = {}` forwards nothing");
|
|
|
|
|
CheckLoaded(replaced, false, only, "an explicit list replaces the args rather than adding to them");
|
|
|
|
|
Check(inherited != none && inherited != replaced && none != replaced, "each list loads its own configuration");
|
2026-10-31 02:56:15 +01:00
|
|
|
|
|
|
|
|
// One sibling further down, the same default passes on what the
|
|
|
|
|
// middle project was given, not what the top one was.
|
2026-10-31 05:10:02 +01:00
|
|
|
CheckLoaded(OnlyDependency(*inherited), true, flavor, "the project's args reach its sibling's sibling");
|
|
|
|
|
CheckLoaded(OnlyDependency(*none), false, {}, "a sibling loaded with nothing passes nothing on");
|
|
|
|
|
CheckLoaded(OnlyDependency(*replaced), false, only, "a sibling passes on the list it was given");
|
|
|
|
|
|
|
|
|
|
CheckLoaded(fromStatic, true, flavor, "the sibling with a namespace-scope spec is itself loaded with the args");
|
|
|
|
|
CheckLoaded(OnlyDependency(*fromStatic), false, {}, "a spec made at namespace scope forwards nothing");
|
2026-10-31 02:56:15 +01:00
|
|
|
}
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
fs::current_path(cwd);
|
|
|
|
|
std::println(std::cerr, "FAIL: LoadProject threw: {}", e.what());
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
fs::current_path(cwd);
|
|
|
|
|
|
|
|
|
|
Check(ProjectArgs().empty(), "no project args once the project file has returned");
|
|
|
|
|
return Failures > 0 ? 1 : 0;
|
|
|
|
|
}
|