Compare commits

..
7 changed files with 1 additions and 27 deletions

View file

@ -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.

View file

@ -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:

View file

@ -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<char, 128> buffer;

View file

@ -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 <prefix>/share/crafter-build from the running executable's path.

View file

@ -16,8 +16,5 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>)
// 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;
}

View file

@ -19,7 +19,5 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
std::array<fs::path, 1> ifaces = { "interfaces/Greeter" };
std::array<fs::path, 0> 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;
}

View file

@ -92,14 +92,11 @@ int main(int argc, char** argv) {
const fs::path appSo = scratch / "app" / "build" / "project.so";
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,
// 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) {