Compare commits

..
Author SHA1 Message Date
f60268b17a
docs: sibling projects forward the parent's args by default
All checks were successful
Flatpak / flatpak (pull_request) Successful in 7m15s
The sibling-project example dropped its `.args = depArgs`, which no
longer has to be written, and says what a sibling receives instead: the
args the project file was loaded with, or whatever `.args` is set to,
with `{}` for nothing. It also says that releases up to 2.2.0 forward
nothing without `.args`, which matters for a project.cpp that has to
load on both.

The pragma section says that a project file's own dependency is built
for the host and gets none of the project's args, the one place the
default does not apply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:59:13 +01:00
cc2c5e62e9
refactor(lint): check this tree with the lite house rules
Crafter.LintRules now has AddLiteRules: the house rules without the ones
that only decide layout, whitespace and naming. This tree switches to it.
The full set's naming rule now wants camelCase statics and constants,
which this tree spells PascalCase throughout, so `lint` with it reported
49 findings on code that follows the documented convention; with the lite
set `lint` and `format --check` are clean.

AGENTS.md described the 17 rules as the enforced style and told
contributors to let `format` restyle their code. It now says which 8 rules
`lint` enforces, that `format` only rewrites string concatenation and
builtin integer types, and lists layout and naming as conventions kept by
hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:58:41 +01:00
352af4c9db
refactor: drop the depArgs copy from the self-host project.cpp
Crafter.Math and Crafter.Asset now get this project's args through the
GitProjectSpec default. Loading this file therefore needs a crafter-build
that exports ProjectArgs: after pulling, run ./build.sh before the
bootstrap binary loads it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:57:03 +01:00
e62d33c7f0
test: sibling projects inherit the loading project's args
ProjectArgs loads a chain app -> lib -> leaf of LocalProjects with --debug
and a project flag. lib, loaded without .args, has to see both, and so
does its own sibling, which takes the default one level further down.
Copies loaded with `.args = {}` and with an explicit list get nothing and
exactly that list. The GitProjectSpec default is checked on the spec
itself, since a GitProject would need a repository to clone. Without the
defaults, five of its checks fail.

ProjectDependency now also loads its consumer with a project flag: the
pragma dependency must still come back with no args. Without the explicit
`.args = {}` in the pragma path it gets the consumer's flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:56:15 +01:00
aec93617ee
feat(external): forward a project's args to its sibling projects by default
Every project.cpp with a sibling dependency opened with the same line,

    std::vector<std::string> depArgs(args.begin(), args.end());

and handed depArgs to each GitProject and LocalProject. CrafterBuildProject
receives a span of string_views and the specs wanted owned strings, and
without the copy the sibling got none of the project's args: it was built
for the host while the project cross-compiled, and in release while it
built --debug. GitProject then turned the copy straight back into
string_views for LoadProject.

Both specs now default their args to ProjectArgs(), the args LoadProject
was given for the project file whose CrafterBuildProject is running. It
reads the per-thread stack LoadProject already keeps for IsTopLevelProject
and ProjectDependency, whose frames now record the args. A default member
initializer rather than a new type or an optional: the member stays a
std::vector<std::string>, so every existing `.args = depArgs` keeps its
exact meaning (each of the ~50 calls across the Catcrafts projects passes
one), and `.args = {}` still forwards nothing. It is the shape of
Configuration::target = HostTarget(). A project.cpp that drops its .args
needs a crafter-build with this change: an older one compiles it and
forwards nothing.

ProjectArgs() is declared in :External, next to the defaults that call
it. :Platform, where it is defined, cannot be imported there: its
unexported `struct Configuration;` would make :External's exported one an
ill-formed redeclaration, which clang rejects.

The #pragma crafter dependency path now passes `.args = {}` explicitly.
Host tooling is built for the host whatever the consumer targets and was
loaded with no args; the default would otherwise hand it the consumer's
--target= and project flags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:56:14 +01:00
10 changed files with 245 additions and 32 deletions

View file

@ -95,14 +95,17 @@ Cross-target runs use `test --target=<triple>` with `--runner=local` or `--runne
## Code style
Style is enforced by the 17 rules in [Crafter.LintRules](https://forgejo.catcrafts.net/Catcrafts/Crafter.LintRules), which `project.cpp` pulls in through `#pragma crafter dependency` and applies with `crafter-build format` and `lint`. Most rules are transforms, so run `format` and let it restyle rather than hand-formatting. In short:
`project.cpp` pulls in [Crafter.LintRules](https://forgejo.catcrafts.net/Catcrafts/Crafter.LintRules) through `#pragma crafter dependency` and applies its lite set, `LintRules::AddLiteRules`: the 8 rules that ask for different code, without the ones that only decide layout, whitespace and naming. `lint` reports all 8; `format` applies the two transforms among them, `format-concat` and `fixed-width-types`, and leaves everything else as written. Enforced:
- The first three lines of every source: `// SPDX-License-Identifier: LGPL-3.0-only`, `// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®`, blank. Examples use MIT.
- `enum class` only. `std::println` instead of iostream. `std::format` instead of string concatenation. `std::int32_t` and friends instead of `int` or `long` (exempt: `main`, `argc`/`argv`, `extern "C"` and foreign API signatures). No `char*` outside C interop.
- `const` on scalar locals that are never written; `constexpr` on constants with constant initialisers.
Not checked, so match the surrounding code:
- Four-space indentation, no tabs, no trailing whitespace, final newline.
- `PascalCase` for types, functions, statics and constants; `camelCase` for variables; a trailing `_` is allowed.
- `enum class` only. `std::println` instead of iostream. `std::format` instead of string concatenation. `std::int32_t` and friends instead of `int` or `long` (exempt: `main`, `argc`/`argv`, `extern "C"` and foreign API signatures). No `char*` outside C interop.
- One declaration per statement. K&R braces. `if (x) return;` on one line when it fits. The formatter joins wrapped statements that fit on one line, so do not hand-wrap short ones. `if (` and `for (` with a space; no space before a call's `(`.
- `const` on scalar locals that are never written; `constexpr` on constants with constant initialisers.
- One declaration per statement. K&R braces. `if (x) return;` on one line when it fits, and no hand-wrapping of a statement that fits on one line. `if (` and `for (` with a space; no space before a call's `(`.
Suppress one finding with `// lint-disable-next-line <rule>` or a whole file with `// lint-disable-file <rule>`. Both are rare in this tree and need a reason.

View file

@ -69,12 +69,13 @@ Clones live at `~/.cache/crafter.build/external/<name>-<hash>/`, keyed by `(url,
```cpp
Configuration* math = GitProject({
.source = { .url = "https://forgejo.catcrafts.net/Catcrafts/Crafter.Math.git" }, // .branch or .commit to pin
.args = depArgs, // forwarded to its CrafterBuildProject
});
Configuration* sibling = LocalProject({ .projectFile = "../Crafter.Math/project.cpp", .args = depArgs });
Configuration* sibling = LocalProject({ .projectFile = "../Crafter.Math/project.cpp" });
cfg.dependencies = { math };
```
The sibling's `CrafterBuildProject` gets the args this `project.cpp` was loaded with (`ProjectArgs()`), so `--target=`, `--march=`, `--debug` and the project's own flags reach every sibling down the chain. Set `.args` to send something else: `{}` for nothing, or a list built from the project's args, say one without a flag only this project reads (it would otherwise give the sibling a build directory of its own) or with an extra `--target=`. Releases up to 2.2.0 forward nothing when `.args` is left out.
Clones share the external cache and identical specs share one `Configuration`. An unpinned source is `git pull`ed on every invocation, set `.commit` for reproducible, offline-after-first-fetch builds.
A project loaded this way runs its `CrafterBuildProject` as a dependency, and `IsTopLevelProject()` is `false` while it does (`true` in the project crafter-build was pointed at, and outside any project file). Wire test-only dependencies behind it: a library whose test links crafter-build itself would otherwise load crafter-build's `project.cpp` from inside crafter-build and recurse.
@ -94,6 +95,8 @@ import Crafter.LintRules;
#pragma crafter dependency "<source>" [branch "<name>" | commit "<sha>"] [project "<file>"]
```
Such a dependency is built for the host, whatever the project targets, and its `project.cpp` gets none of the project's args.
## Tests
Tests live under `tests/<Name>/`. The simplest case is a single C++ file:

View file

@ -413,6 +413,10 @@ namespace {
struct ProjectFileFrame {
fs::path project;
// What LoadProject was given, for ProjectArgs(). A view is enough:
// the caller's storage outlives LoadProject, and the frame is popped
// before LoadProject returns.
std::span<const std::string_view> args;
std::vector<Configuration*> dependencies;
};
@ -426,7 +430,10 @@ namespace {
// already on the stack is a cycle, which used to recurse forever.
class ProjectFileGuard {
public:
explicit ProjectFileGuard(const fs::path& absProject) {
// The args are recorded with the frame rather than just before
// CrafterBuildProject runs, so a spec built while dlopen runs the
// project file's static initialisers sees them too.
ProjectFileGuard(const fs::path& absProject, std::span<const std::string_view> args) {
for (const ProjectFileFrame& frame : LoadingFrames) {
if (frame.project != absProject) continue;
std::string chain;
@ -434,7 +441,7 @@ namespace {
throw std::runtime_error(std::format("circular project dependency: {}{}", chain, absProject.string()));
}
index_ = LoadingFrames.size();
LoadingFrames.push_back({ absProject, {} });
LoadingFrames.push_back({ absProject, args, {} });
}
~ProjectFileGuard() { LoadingFrames.pop_back(); }
ProjectFileGuard(const ProjectFileGuard&) = delete;
@ -451,8 +458,9 @@ namespace {
// same PCM cache LoadProject populates — the hostConfig LoadProject itself
// compiles project.so with, so the dependency's std/Crafter.Build PCMs
// land in the cache dir project.so is built against. Recursive, because
// the dependency's project.cpp forwards no arguments but still reads
// CRAFTER_BUILD_MARCH from the environment for its own deps.
// the dependency's project.cpp is loaded with no arguments and so passes
// none on, while its own deps still read CRAFTER_BUILD_MARCH from the
// environment.
void ForceHostTooling(Configuration& cfg, const Configuration& host, std::unordered_set<Configuration*>& seen) {
if (!seen.insert(&cfg).second) return;
cfg.target = host.target;
@ -495,10 +503,16 @@ namespace {
std::set<std::string> libs;
for (const PragmaDependency& pragma : pragmas) {
// Host tooling is loaded with none of the consumer's args, so both
// specs pass `.args = {}` instead of defaulting to them. The tool
// is built for the host whatever the consumer targets: given the
// consumer's --target=, its project.cpp would pick sources and
// flags for the wrong triple before ForceHostTooling resets the
// fields, and the consumer's own flags would hash it apart from
// the build every other consumer of the tool shares.
Configuration* cfg = nullptr;
if (IsGitSource(pragma.source)) {
GitProjectSpec spec;
spec.source = { pragma.source, pragma.branch, pragma.commit };
GitProjectSpec spec{ .source = { pragma.source, pragma.branch, pragma.commit }, .args = {} };
if (!pragma.project.empty()) spec.projectFile = pragma.project;
cfg = GitProject(spec);
} else {
@ -512,7 +526,7 @@ namespace {
PragmaError(absProject, pragma.line, "'project' only applies to a directory source");
}
if (!fs::exists(local)) PragmaError(absProject, pragma.line, std::format("no such project file: {}", local.string()));
cfg = LocalProject({ .projectFile = local });
cfg = LocalProject({ .projectFile = local, .args = {} });
}
if (std::ranges::contains(out.configs, cfg)) continue;
@ -580,6 +594,12 @@ bool Crafter::IsTopLevelProject() {
return LoadingFrames.size() <= 1;
}
std::vector<std::string> Crafter::ProjectArgs() {
if (LoadingFrames.empty()) return {};
const std::span<const std::string_view> args = LoadingFrames.back().args;
return std::vector<std::string>(args.begin(), args.end());
}
namespace {
// The one reading of a wait status (pclose, system) for every helper
// that runs a command: a signal death is reported the way a shell
@ -882,7 +902,7 @@ namespace {
Configuration Crafter::LoadProject(const fs::path& projectFile, std::span<const std::string_view> args) {
RequireToolchain();
fs::path absProject = fs::canonical(projectFile);
ProjectFileGuard guard(absProject);
ProjectFileGuard guard(absProject, args);
fs::path buildDir = absProject.parent_path() / "build";
if (!fs::exists(buildDir)) {
fs::create_directories(buildDir);

View file

@ -54,16 +54,31 @@ export namespace Crafter {
std::vector<std::string> ExternalIncludeFlags(const ExternalDependency& dep, std::string_view target);
ExternalBuildResult BuildExternal(const ExternalDependency& dep, std::string_view target, std::atomic<bool>& cancelled);
// The args LoadProject was given for the project file currently running
// its CrafterBuildProject: what that function receives, standard flags
// included (cfg.projectArgs is only the part ApplyStandardArgs left
// over). Empty when no project file is running, and on any thread but
// the one loading it, because it reads the stack LoadProject keeps per
// thread, as IsTopLevelProject does. The specs below default their args
// to it. Defined beside LoadProject, but declared here so those defaults
// can see it: importing :Platform into this partition would make the
// exported `struct Configuration;` above a redeclaration of :Platform's
// unexported one, which clang rejects.
std::vector<std::string> ProjectArgs();
// Specification for a sibling crafter-build project to fetch and depend on.
// GitSource picks the revision: leave branch + commit empty for the
// remote's default branch HEAD (sloppy but convenient), set branch to
// track a branch tip, or set commit to pin to an immutable SHA. args are
// forwarded verbatim to the dep's CrafterBuildProject — typically you
// pass through the parent's args so target/march/debug propagate.
// what the dep's CrafterBuildProject receives. They default to this
// project's own, so --target=, --march=, --debug and flags of its own
// such as --local reach a whole chain of sibling projects without being
// passed. Set them to forward something else: a copy without one of this
// project's flags, one with an extra --target=, or {} for nothing.
struct GitProjectSpec {
GitSource source;
fs::path projectFile = "project.cpp";
std::vector<std::string> args;
std::vector<std::string> args = ProjectArgs();
};
// Clones the spec's git URL into the per-project external cache (keyed by
@ -78,7 +93,7 @@ export namespace Crafter {
// multiple projects live side-by-side without going through git).
struct LocalProjectSpec {
fs::path projectFile; // relative to cwd or absolute
std::vector<std::string> args; // forwarded to dep's CrafterBuildProject
std::vector<std::string> args = ProjectArgs(); // as GitProjectSpec::args
};
// Same as GitProject but for a local on-disk project — no fetch, no

View file

@ -137,15 +137,8 @@ Remove it (do this before `crafter-build clean`, which deletes the repo the remo
}
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
std::vector<std::string> depArgs(args.begin(), args.end());
Configuration* math = GitProject({
.source = { "https://forgejo.catcrafts.net/Catcrafts/Crafter.Math.git" },
.args = depArgs,
});
Configuration* asset = GitProject({
.source = { "https://forgejo.catcrafts.net/Catcrafts/Crafter.Asset.git" },
.args = depArgs,
});
Configuration* math = GitProject({ .source = { "https://forgejo.catcrafts.net/Catcrafts/Crafter.Math.git" } });
Configuration* asset = GitProject({ .source = { "https://forgejo.catcrafts.net/Catcrafts/Crafter.Asset.git" } });
static auto CrafterBuildLib = std::make_unique<Configuration>();
CrafterBuildLib->path = "./";
@ -286,16 +279,22 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
.LinkFlag("-Wl,--export-dynamic").LinkFlag("-ldl");
cfg.AddTest("ProjectDependency").Dependencies({ CrafterBuildLib.get() })
.LinkFlag("-Wl,--export-dynamic").LinkFlag("-ldl");
cfg.AddTest("ProjectArgs").Dependencies({ CrafterBuildLib.get() })
.LinkFlag("-Wl,--export-dynamic").LinkFlag("-ldl");
cfg.AddTest("Script").Dependencies({ CrafterBuildLib.get() })
.LinkFlag("-Wl,--export-dynamic").LinkFlag("-ldl");
cfg.AddTest("ConcurrentDependencyReset").Dependencies({ CrafterBuildLib.get() });
cfg.AddTest("Lint").Dependencies({ CrafterBuildLib.get() });
}
// Dogfood: the house-style rules, imported from their own repo through
// the pragma above. Report rules gate `crafter-build lint`; transform
// rules also auto-fix under `crafter-build format`.
LintRules::AddRules(cfg);
// Dogfood: the lite house rules, imported from their own repo through
// the pragma above. Lite is the house rules without the ones that only
// decide layout, whitespace and naming, which this tree keeps by hand:
// the full set's naming rule, for one, wants camelCase where this tree
// spells its statics and constants PascalCase. Report rules gate
// `crafter-build lint`; the two transforms also auto-fix under
// `crafter-build format`.
LintRules::AddLiteRules(cfg);
// See the top of this file.
cfg.scripts.push_back({"flatpak", BuildFlatpak});

View file

@ -0,0 +1,29 @@
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
import std;
import Crafter.Build;
using namespace Crafter;
// Loads the same sibling three ways: with its args left at their default,
// with nothing forwarded, and with a list of its own. The test reads what
// each copy received back out of cfg.dependencies.
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
Configuration* inherited = LocalProject({ .projectFile = "../lib/project.cpp" });
Configuration* none = LocalProject({ .projectFile = "../lib/project.cpp", .args = {} });
Configuration* replaced = LocalProject({ .projectFile = "../lib/project.cpp", .args = { "--only" } });
Configuration cfg;
cfg.path = "./";
cfg.name = "app";
cfg.outputName = "app";
cfg.type = ConfigurationType::Executable;
ApplyStandardArgs(cfg, args);
cfg.dependencies = { inherited, none, replaced };
// GitProjectSpec takes the same default. Checked on the spec itself,
// since a GitProject would need a repository to clone.
const GitProjectSpec git{ .source = { .url = "unused" } };
const bool gitDefault = git.args == std::vector<std::string>(args.begin(), args.end());
cfg.compileFlags.push_back(gitDefault ? "-DGIT_DEFAULT=1" : "-DGIT_DEFAULT=0");
return cfg;
}

View file

@ -0,0 +1,17 @@
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
import std;
import Crafter.Build;
using namespace Crafter;
// The end of the chain: records what it was loaded with and nothing else.
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
Configuration cfg;
cfg.path = "./";
cfg.name = "leaf";
cfg.outputName = "leaf";
cfg.type = ConfigurationType::LibraryStatic;
ApplyStandardArgs(cfg, args);
return cfg;
}

View file

@ -0,0 +1,21 @@
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
import std;
import Crafter.Build;
using namespace Crafter;
// The middle of the chain. Its own sibling takes the default too, so
// whatever this file was loaded with has to reach the leaf unchanged.
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
Configuration* leaf = LocalProject({ .projectFile = "../leaf/project.cpp" });
Configuration cfg;
cfg.path = "./";
cfg.name = "lib";
cfg.outputName = "lib";
cfg.type = ConfigurationType::LibraryStatic;
ApplyStandardArgs(cfg, args);
cfg.dependencies = { leaf };
return cfg;
}

View file

@ -0,0 +1,91 @@
// 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.
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<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() == 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;
}

View file

@ -115,6 +115,21 @@ int main(int argc, char** argv) {
std::println(std::cerr, "FAIL: LoadProject threw: {}", e.what());
return 1;
}
// Host tooling gets none of the consumer's args, although a LocalProject
// spec defaults to them: the consumer's flags would split the tool's
// variant from every other consumer's, and its --target= would have the
// tool's project.cpp choose for the wrong triple.
try {
std::array<std::string_view, 1> flavored = { "--flavor=x" };
Configuration cfg = LoadProject(app, flavored);
const bool found = !cfg.dependencies.empty();
Check(found, "a consumer loaded with args still finds its pragma dependency");
if (found) Check(cfg.dependencies[0]->projectArgs.empty(), std::format("a pragma dependency is loaded without the consumer's args (got {})", cfg.dependencies[0]->projectArgs));
} catch (const std::exception& e) {
std::println(std::cerr, "FAIL: LoadProject with args threw: {}", e.what());
return 1;
}
Check(fs::exists(appSo), "project.so was produced");
if (!fs::exists(appSo)) return 1;
const fs::file_time_type firstSo = fs::last_write_time(appSo);