fix: let a project file ask whether it is the top-level project
Crafter.LintRules declares a test that links crafter-build, fetched with GitProject. crafter-build's own project.cpp loads Crafter.LintRules as a `#pragma crafter dependency`, so the chain was: crafter-build loads LintRules, LintRules' test wiring loads crafter-build's project.cpp from the external cache, which loads LintRules again, and the cycle guard threw. Every command that loads project.cpp in this repository has failed since that test was added, including the last CI run on master. A project file had no way to tell whether it was the one the user pointed crafter-build at or a dependency being loaded for somebody else, so it could not skip wiring that only its own build needs. IsTopLevelProject() answers that from the loading-frame stack ProjectDependency() already reads: one frame is the top-level project file, a second is only ever pushed for a dependency. Outside any project file it is true as well, which is what a test harness constructing Configurations expects. The ProjectDependency test records the answer from both the app and the greeter project files and checks each. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
c3d97be59e
commit
be88a194f4
5 changed files with 24 additions and 0 deletions
|
|
@ -554,6 +554,12 @@ Configuration* Crafter::ProjectDependency(std::string_view name) {
|
||||||
throw std::runtime_error(std::format("ProjectDependency('{}'): not declared by {} (declared: {})", name, frame.project.string(), declared.empty() ? "none" : declared));
|
throw std::runtime_error(std::format("ProjectDependency('{}'): not declared by {} (declared: {})", name, frame.project.string(), declared.empty() ? "none" : declared));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Crafter::IsTopLevelProject() {
|
||||||
|
// One frame is the project file crafter-build was pointed at; a second
|
||||||
|
// is only ever pushed for a dependency's project file.
|
||||||
|
return LoadingFrames.size() <= 1;
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32)
|
#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32)
|
||||||
CommandResult Crafter::RunCommandChecked(std::string_view cmd) {
|
CommandResult Crafter::RunCommandChecked(std::string_view cmd) {
|
||||||
std::array<char, 128> buffer;
|
std::array<char, 128> buffer;
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,15 @@ namespace Crafter {
|
||||||
// Throws when no such dependency was declared, or when called from
|
// Throws when no such dependency was declared, or when called from
|
||||||
// anywhere other than inside LoadProject.
|
// anywhere other than inside LoadProject.
|
||||||
export CRAFTER_API Configuration* ProjectDependency(std::string_view name);
|
export CRAFTER_API Configuration* ProjectDependency(std::string_view name);
|
||||||
|
// True while the project file crafter-build was pointed at runs its
|
||||||
|
// CrafterBuildProject, and when no project file is running at all; false
|
||||||
|
// inside a dependency's project file (a `#pragma crafter dependency`,
|
||||||
|
// GitProject or LocalProject target). Wire anything only the project's
|
||||||
|
// own build or test run needs behind it. The case that forced it: a
|
||||||
|
// library's test links crafter-build itself, so when crafter-build loads
|
||||||
|
// that library as a dependency, the test's GitProject loads crafter-build's
|
||||||
|
// project file in turn and the two recurse until the cycle guard throws.
|
||||||
|
export CRAFTER_API bool IsTopLevelProject();
|
||||||
// Resolves the directory holding distributed runtime assets (Crafter.Build
|
// Resolves the directory holding distributed runtime assets (Crafter.Build
|
||||||
// module sources, wasi-runtime/, etc). Honors CRAFTER_BUILD_HOME; otherwise
|
// module sources, wasi-runtime/, etc). Honors CRAFTER_BUILD_HOME; otherwise
|
||||||
// derives <prefix>/share/crafter-build from the running executable's path.
|
// derives <prefix>/share/crafter-build from the running executable's path.
|
||||||
|
|
|
||||||
|
|
@ -16,5 +16,8 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>)
|
||||||
// Once by the module it exports, once by its configuration name; the
|
// Once by the module it exports, once by its configuration name; the
|
||||||
// test expects the same object back for both.
|
// test expects the same object back for both.
|
||||||
cfg.dependencies = { ProjectDependency("Greeter"), ProjectDependency("greeter") };
|
cfg.dependencies = { ProjectDependency("Greeter"), ProjectDependency("greeter") };
|
||||||
|
// Reported through a flag the test can read back: this file is the one
|
||||||
|
// LoadProject was pointed at, the greeter's is not.
|
||||||
|
cfg.compileFlags.push_back(IsTopLevelProject() ? "-DTOP_LEVEL=1" : "-DTOP_LEVEL=0");
|
||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,7 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
|
||||||
std::array<fs::path, 1> ifaces = { "interfaces/Greeter" };
|
std::array<fs::path, 1> ifaces = { "interfaces/Greeter" };
|
||||||
std::array<fs::path, 0> impls = {};
|
std::array<fs::path, 0> impls = {};
|
||||||
cfg.GetInterfacesAndImplementations(ifaces, impls);
|
cfg.GetInterfacesAndImplementations(ifaces, impls);
|
||||||
|
// Loaded as a dependency, so this must come out false (see app/).
|
||||||
|
cfg.compileFlags.push_back(IsTopLevelProject() ? "-DTOP_LEVEL=1" : "-DTOP_LEVEL=0");
|
||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,11 +92,14 @@ int main(int argc, char** argv) {
|
||||||
const fs::path appSo = scratch / "app" / "build" / "project.so";
|
const fs::path appSo = scratch / "app" / "build" / "project.so";
|
||||||
std::array<std::string_view, 0> noArgs = {};
|
std::array<std::string_view, 0> noArgs = {};
|
||||||
|
|
||||||
|
Check(IsTopLevelProject(), "outside any project file counts as top level");
|
||||||
|
|
||||||
// The dependency is fetched, built and linked before project.cpp runs,
|
// The dependency is fetched, built and linked before project.cpp runs,
|
||||||
// and ProjectDependency() hands it back by either name.
|
// and ProjectDependency() hands it back by either name.
|
||||||
try {
|
try {
|
||||||
Configuration cfg = LoadProject(app, noArgs);
|
Configuration cfg = LoadProject(app, noArgs);
|
||||||
Check(cfg.name == "hello from greeter", std::format("project.cpp imported and called the dependency's module (got '{}')", cfg.name));
|
Check(cfg.name == "hello from greeter", std::format("project.cpp imported and called the dependency's module (got '{}')", cfg.name));
|
||||||
|
Check(std::ranges::contains(cfg.compileFlags, "-DTOP_LEVEL=1"), "the project LoadProject was pointed at sees itself as top level");
|
||||||
Check(cfg.dependencies.size() == 2, "ProjectDependency resolved by module name and by configuration name");
|
Check(cfg.dependencies.size() == 2, "ProjectDependency resolved by module name and by configuration name");
|
||||||
if (cfg.dependencies.size() == 2) {
|
if (cfg.dependencies.size() == 2) {
|
||||||
Check(cfg.dependencies[0] == cfg.dependencies[1], "both names resolve to the same dependency");
|
Check(cfg.dependencies[0] == cfg.dependencies[1], "both names resolve to the same dependency");
|
||||||
|
|
@ -105,6 +108,7 @@ int main(int argc, char** argv) {
|
||||||
Check(dep.target == HostTarget(), "the dependency was built for the host triple LoadProject uses");
|
Check(dep.target == HostTarget(), "the dependency was built for the host triple LoadProject uses");
|
||||||
Check(dep.march == "native", "the environment's march was overridden for host tooling");
|
Check(dep.march == "native", "the environment's march was overridden for host tooling");
|
||||||
Check(std::ranges::contains(dep.compileFlags, "-fPIC"), "the dependency archive was built position-independent");
|
Check(std::ranges::contains(dep.compileFlags, "-fPIC"), "the dependency archive was built position-independent");
|
||||||
|
Check(std::ranges::contains(dep.compileFlags, "-DTOP_LEVEL=0"), "a dependency's project file does not see itself as top level");
|
||||||
Check(fs::exists(ExpectedOutput(dep)), "the dependency archive exists");
|
Check(fs::exists(ExpectedOutput(dep)), "the dependency archive exists");
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue