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>
This commit is contained in:
parent
6e2d835aa6
commit
17021c6055
2 changed files with 48 additions and 13 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -71,16 +71,31 @@ export namespace Crafter {
|
|||
// clone can still differ in includeDirs, libDirs and libs.
|
||||
ExternalBuildResult ExternalOutputs(const ExternalDependency& dep, std::string_view target);
|
||||
|
||||
// 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
|
||||
|
|
@ -95,7 +110,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
|
||||
|
|
|
|||
Loading…
Reference in a new issue