diff --git a/AGENTS.md b/AGENTS.md index f0eff75..d3bd27b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -149,7 +149,7 @@ These were decided deliberately. Do not undo them without the maintainer's say-s ## Gotchas - A `#pragma crafter dependency` target is rebuilt on every load on purpose: clang crashes rather than erroring when it imports a BMI whose own imported PCM was rebuilt underneath it, and the depfile check is what catches a regenerated host cache. Do not optimise the rebuild away. -- A host-tooling library such as Crafter.LintRules may `import Crafter.Build;` without declaring it as a dependency, because `LoadProject` fills the cache it resolves from. Listing Crafter.Build in its `cfg.dependencies` would clone and build all of crafter-build for every consumer. Its test does link crafter-build, so that test is wired behind `IsTopLevelProject()`: without the guard, crafter-build loading the library as a dependency would load its own `project.cpp` again through the test's `GitProject` and hit the cycle guard. +- A host-tooling library such as Crafter.LintRules may `import Crafter.Build;` without declaring it as a dependency, because `LoadProject` fills the cache it resolves from. Listing Crafter.Build in its `cfg.dependencies` would clone and build all of crafter-build for every consumer. - `crafter-build clean` leaves a pragma dependency's build alone; it lives with the clone in the cache. - The bootstrap binary is compiled without `CRAFTER_BUILD_HAS_ASSET`, so `cfg.assets` only works in the self-hosted binary. Changing asset extensions means updating two maps in `Crafter.Build-Clang.cpp`: `assetOutput` and the transitive copy loop. - A warning is printed only when its unit actually recompiles. A build with nothing to do is silent, which is not evidence that a warning is gone. diff --git a/README.md b/README.md index 301f355..98f9d29 100644 --- a/README.md +++ b/README.md @@ -83,8 +83,6 @@ cfg.dependencies = { math }; 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. - ### Dependencies of project.cpp itself `project.cpp` can import modules from another crafter-build project. The file has to compile before it can say anything, so the dependency is a directive next to the `import` it enables: diff --git a/implementations/Crafter.Build-Platform.cpp b/implementations/Crafter.Build-Platform.cpp index 5ffaa17..5d6a493 100644 --- a/implementations/Crafter.Build-Platform.cpp +++ b/implementations/Crafter.Build-Platform.cpp @@ -554,12 +554,6 @@ 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)); } -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) CommandResult Crafter::RunCommandChecked(std::string_view cmd) { std::array buffer; diff --git a/interfaces/Crafter.Build-Platform.cppm b/interfaces/Crafter.Build-Platform.cppm index 0b37177..c120cc0 100644 --- a/interfaces/Crafter.Build-Platform.cppm +++ b/interfaces/Crafter.Build-Platform.cppm @@ -41,15 +41,6 @@ namespace Crafter { // Throws when no such dependency was declared, or when called from // anywhere other than inside LoadProject. 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 // module sources, wasi-runtime/, etc). Honors CRAFTER_BUILD_HOME; otherwise // derives /share/crafter-build from the running executable's path. diff --git a/tests/ProjectDependency/fixture/app/project.cpp b/tests/ProjectDependency/fixture/app/project.cpp index b6744d4..329f662 100644 --- a/tests/ProjectDependency/fixture/app/project.cpp +++ b/tests/ProjectDependency/fixture/app/project.cpp @@ -16,8 +16,5 @@ extern "C" Configuration CrafterBuildProject(std::span) // Once by the module it exports, once by its configuration name; the // test expects the same object back for both. 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; } diff --git a/tests/ProjectDependency/fixture/greeter/project.cpp b/tests/ProjectDependency/fixture/greeter/project.cpp index f9dcbbf..82eb6c1 100644 --- a/tests/ProjectDependency/fixture/greeter/project.cpp +++ b/tests/ProjectDependency/fixture/greeter/project.cpp @@ -19,7 +19,5 @@ extern "C" Configuration CrafterBuildProject(std::span a std::array ifaces = { "interfaces/Greeter" }; std::array 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; } diff --git a/tests/ProjectDependency/main.cpp b/tests/ProjectDependency/main.cpp index fcf1c57..3d80370 100644 --- a/tests/ProjectDependency/main.cpp +++ b/tests/ProjectDependency/main.cpp @@ -92,14 +92,11 @@ int main(int argc, char** argv) { const fs::path appSo = scratch / "app" / "build" / "project.so"; std::array noArgs = {}; - Check(IsTopLevelProject(), "outside any project file counts as top level"); - // The dependency is fetched, built and linked before project.cpp runs, // and ProjectDependency() hands it back by either name. try { 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(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"); if (cfg.dependencies.size() == 2) { Check(cfg.dependencies[0] == cfg.dependencies[1], "both names resolve to the same dependency"); @@ -108,7 +105,6 @@ int main(int argc, char** argv) { 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(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"); } } catch (const std::exception& e) {