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 {
|
struct ProjectFileFrame {
|
||||||
fs::path project;
|
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;
|
std::vector<Configuration*> dependencies;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -426,7 +430,10 @@ namespace {
|
||||||
// already on the stack is a cycle, which used to recurse forever.
|
// already on the stack is a cycle, which used to recurse forever.
|
||||||
class ProjectFileGuard {
|
class ProjectFileGuard {
|
||||||
public:
|
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) {
|
for (const ProjectFileFrame& frame : LoadingFrames) {
|
||||||
if (frame.project != absProject) continue;
|
if (frame.project != absProject) continue;
|
||||||
std::string chain;
|
std::string chain;
|
||||||
|
|
@ -434,7 +441,7 @@ namespace {
|
||||||
throw std::runtime_error(std::format("circular project dependency: {}{}", chain, absProject.string()));
|
throw std::runtime_error(std::format("circular project dependency: {}{}", chain, absProject.string()));
|
||||||
}
|
}
|
||||||
index_ = LoadingFrames.size();
|
index_ = LoadingFrames.size();
|
||||||
LoadingFrames.push_back({ absProject, {} });
|
LoadingFrames.push_back({ absProject, args, {} });
|
||||||
}
|
}
|
||||||
~ProjectFileGuard() { LoadingFrames.pop_back(); }
|
~ProjectFileGuard() { LoadingFrames.pop_back(); }
|
||||||
ProjectFileGuard(const ProjectFileGuard&) = delete;
|
ProjectFileGuard(const ProjectFileGuard&) = delete;
|
||||||
|
|
@ -451,8 +458,9 @@ namespace {
|
||||||
// same PCM cache LoadProject populates — the hostConfig LoadProject itself
|
// same PCM cache LoadProject populates — the hostConfig LoadProject itself
|
||||||
// compiles project.so with, so the dependency's std/Crafter.Build PCMs
|
// compiles project.so with, so the dependency's std/Crafter.Build PCMs
|
||||||
// land in the cache dir project.so is built against. Recursive, because
|
// land in the cache dir project.so is built against. Recursive, because
|
||||||
// the dependency's project.cpp forwards no arguments but still reads
|
// the dependency's project.cpp is loaded with no arguments and so passes
|
||||||
// CRAFTER_BUILD_MARCH from the environment for its own deps.
|
// 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) {
|
void ForceHostTooling(Configuration& cfg, const Configuration& host, std::unordered_set<Configuration*>& seen) {
|
||||||
if (!seen.insert(&cfg).second) return;
|
if (!seen.insert(&cfg).second) return;
|
||||||
cfg.target = host.target;
|
cfg.target = host.target;
|
||||||
|
|
@ -495,10 +503,16 @@ namespace {
|
||||||
std::set<std::string> libs;
|
std::set<std::string> libs;
|
||||||
|
|
||||||
for (const PragmaDependency& pragma : pragmas) {
|
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;
|
Configuration* cfg = nullptr;
|
||||||
if (IsGitSource(pragma.source)) {
|
if (IsGitSource(pragma.source)) {
|
||||||
GitProjectSpec spec;
|
GitProjectSpec spec{ .source = { pragma.source, pragma.branch, pragma.commit }, .args = {} };
|
||||||
spec.source = { pragma.source, pragma.branch, pragma.commit };
|
|
||||||
if (!pragma.project.empty()) spec.projectFile = pragma.project;
|
if (!pragma.project.empty()) spec.projectFile = pragma.project;
|
||||||
cfg = GitProject(spec);
|
cfg = GitProject(spec);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -512,7 +526,7 @@ namespace {
|
||||||
PragmaError(absProject, pragma.line, "'project' only applies to a directory source");
|
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()));
|
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;
|
if (std::ranges::contains(out.configs, cfg)) continue;
|
||||||
|
|
||||||
|
|
@ -580,6 +594,12 @@ bool Crafter::IsTopLevelProject() {
|
||||||
return LoadingFrames.size() <= 1;
|
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 {
|
namespace {
|
||||||
// The one reading of a wait status (pclose, system) for every helper
|
// 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
|
// 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) {
|
Configuration Crafter::LoadProject(const fs::path& projectFile, std::span<const std::string_view> args) {
|
||||||
RequireToolchain();
|
RequireToolchain();
|
||||||
fs::path absProject = fs::canonical(projectFile);
|
fs::path absProject = fs::canonical(projectFile);
|
||||||
ProjectFileGuard guard(absProject);
|
ProjectFileGuard guard(absProject, args);
|
||||||
fs::path buildDir = absProject.parent_path() / "build";
|
fs::path buildDir = absProject.parent_path() / "build";
|
||||||
if (!fs::exists(buildDir)) {
|
if (!fs::exists(buildDir)) {
|
||||||
fs::create_directories(buildDir);
|
fs::create_directories(buildDir);
|
||||||
|
|
|
||||||
|
|
@ -71,16 +71,31 @@ export namespace Crafter {
|
||||||
// clone can still differ in includeDirs, libDirs and libs.
|
// clone can still differ in includeDirs, libDirs and libs.
|
||||||
ExternalBuildResult ExternalOutputs(const ExternalDependency& dep, std::string_view target);
|
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.
|
// Specification for a sibling crafter-build project to fetch and depend on.
|
||||||
// GitSource picks the revision: leave branch + commit empty for the
|
// GitSource picks the revision: leave branch + commit empty for the
|
||||||
// remote's default branch HEAD (sloppy but convenient), set branch to
|
// 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
|
// track a branch tip, or set commit to pin to an immutable SHA. args are
|
||||||
// forwarded verbatim to the dep's CrafterBuildProject — typically you
|
// what the dep's CrafterBuildProject receives. They default to this
|
||||||
// pass through the parent's args so target/march/debug propagate.
|
// 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 {
|
struct GitProjectSpec {
|
||||||
GitSource source;
|
GitSource source;
|
||||||
fs::path projectFile = "project.cpp";
|
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
|
// 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).
|
// multiple projects live side-by-side without going through git).
|
||||||
struct LocalProjectSpec {
|
struct LocalProjectSpec {
|
||||||
fs::path projectFile; // relative to cwd or absolute
|
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
|
// Same as GitProject but for a local on-disk project — no fetch, no
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue