ProjectArgs gets a sibling that makes its own sibling's spec at namespace scope. Before the fix that spec took the args the file was first loaded with; now it takes none on every load. The checks go through one CheckLoaded helper, which prints what each sibling was given, so a failure says which args a wrong frame handed over. ProjectDependency loads a fixture whose static initialiser asks for its pragma dependency, in a child, because a throw there terminates the process: before the fix it found none. The check that the dependency gets none of the consumer's args moves into the first load, which now passes the app an arg it ignores; the separate load could not fail its own check, and its early return skipped every later one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
106 lines
5.3 KiB
C++
106 lines
5.3 KiB
C++
// 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
|
|
// 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.
|
|
namespace {
|
|
std::int32_t Failures = 0;
|
|
|
|
void Check(bool cond, std::string_view msg) {
|
|
if (!cond) {
|
|
std::println(std::cerr, "FAIL: {}", msg);
|
|
++Failures;
|
|
}
|
|
}
|
|
|
|
// The single sibling lib, static and leaf declare, or nullptr.
|
|
const Configuration* OnlyDependency(const Configuration& cfg) {
|
|
return cfg.dependencies.size() == 1 ? cfg.dependencies[0] : nullptr;
|
|
}
|
|
|
|
// 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));
|
|
}
|
|
}
|
|
|
|
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");
|
|
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");
|
|
|
|
// One sibling further down, the same default passes on what the
|
|
// middle project was given, not what the top one was.
|
|
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");
|
|
}
|
|
} 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;
|
|
}
|