// 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 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. 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 and leaf declare, or nullptr. const Configuration* OnlyDependency(const Configuration& cfg) { return cfg.dependencies.size() == 1 ? cfg.dependencies[0] : nullptr; } } 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 args = { "--debug", "--flavor=x" }; const std::vector flavor = { "--flavor=x" }; const std::vector 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() == 3, std::format("the project declared its three siblings (got {})", cfg.dependencies.size())); if (cfg.dependencies.size() == 3) { const Configuration& inherited = *cfg.dependencies[0]; const Configuration& none = *cfg.dependencies[1]; const Configuration& replaced = *cfg.dependencies[2]; Check(inherited.debug && inherited.projectArgs == flavor, std::format("a LocalProject without .args gets the project's args (debug {}, projectArgs {})", inherited.debug, inherited.projectArgs)); Check(!none.debug && none.projectArgs.empty(), std::format("`.args = {{}}` forwards nothing (debug {}, projectArgs {})", none.debug, none.projectArgs)); Check(!replaced.debug && replaced.projectArgs == only, std::format("an explicit list replaces the args rather than adding to them (debug {}, projectArgs {})", replaced.debug, replaced.projectArgs)); 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. const Configuration* inheritedLeaf = OnlyDependency(inherited); Check(inheritedLeaf && inheritedLeaf->debug && inheritedLeaf->projectArgs == flavor, "the project's args reach its sibling's sibling"); const Configuration* noneLeaf = OnlyDependency(none); Check(noneLeaf && !noneLeaf->debug && noneLeaf->projectArgs.empty(), "a sibling loaded with nothing passes nothing on"); const Configuration* replacedLeaf = OnlyDependency(replaced); Check(replacedLeaf && !replacedLeaf->debug && replacedLeaf->projectArgs == only, "a sibling passes on the list it was given"); } } 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; }