Skip to content

Commit aac072c

Browse files
committed
Add feature tools (#709) and target artifact dependencies (#711)
#709 `[features.<f>] tools = ["<bin>"]` names the package's own programs a feature needs on the build machine. On every edge into the package, the active features' tools are requested as if the edge had written `tools = [...]`: built once for the host, reachable through `mcpp::dep_bin`. A name that is not a `kind = "bin"` target of the package is refused at load, naming the bin targets. #711 `x = { ..., artifacts = ["<bin>"] }` asks for the dependency's program built for the consumer's target and profile, as a link unit of the consumer's own plan, written to `bin/` beside its programs. The unit links the dependency's objects and those of every package it reaches; a package reached only through artifact edges is kept out of the consumer's images, since the edge takes the program and not the code. A package asked for artifacts is scanned and configured here even when all its targets are programs. `${mcpp.artifact:<package>/<target>}` names the output in actions, `mcpp pack` stages it beside the program, and `mcpp run` and the program-selection sites ignore it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MajuX4J8ewFeWvZJjzRJt5
1 parent 85c9037 commit aac072c

10 files changed

Lines changed: 645 additions & 6 deletions

‎modules/manifest/src/dep_spec.cppm‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ struct DependencySpec {
6767
// Empty by default: the cost (e.g. protobuf's libprotoc is ~157 extra TUs)
6868
// is paid by the consumer, so nothing is built unless someone asks.
6969
std::vector<std::string> tools;
70+
// mcpp#711: programs of the dependency that this consumer SHIPS -- the
71+
// names of its `kind = "bin"` targets, built for the consumer's TARGET and
72+
// profile as link units of the consumer's own plan, beside its programs in
73+
// `bin/`. The counterpart of `tools` (built for the build machine, in a
74+
// nested sub-build): a GUI that carries its updater executable wants the
75+
// updater for the machine the GUI runs on. An edge that names artifacts
76+
// takes the dependency's programs and not its code: nothing of the
77+
// package is linked into the consumer through this edge.
78+
std::vector<std::string> artifacts;
7079
// #355 step 5: compile this dependency's lib-root module interface FOR THE
7180
// HOST and make it importable from the consumer's build.mcpp — the
7281
// mechanism behind reusable build rules distributed as ordinary packages

‎modules/manifest/src/toml.cppm‎

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,14 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
10141014
if (!devExts.empty())
10151015
m.featureDeviceExtensions[fname] = std::move(devExts);
10161016
}
1017+
// The host tools this feature makes available (mcpp#709). Which
1018+
// targets exist is known only after target inference, so the
1019+
// names are checked in `load`, where the list is complete.
1020+
{
1021+
std::vector<std::string> tools;
1022+
read_str_array(ft, "tools", tools);
1023+
if (!tools.empty()) m.featureTools[fname] = std::move(tools);
1024+
}
10171025
// The module a consumer's build program imports for this rule.
10181026
if (auto it = ft.find("rule_module");
10191027
it != ft.end() && it->second.is_string())
@@ -1101,6 +1109,8 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
11011109
// and `tools = [...]` made ordinary builds of rule packages
11021110
// routine.
11031111
"device_extensions", "rule_module",
1112+
// mcpp#709: the host tools a feature makes available.
1113+
"tools",
11041114
};
11051115
for (auto& [fkey, fignored] : fval.as_table()) {
11061116
(void)fignored;
@@ -1842,7 +1852,7 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
18421852
|| k == "rev" || k == "tag" || k == "branch"
18431853
|| k == "features" || k == "default-features"
18441854
|| k == "workspace" || k == "visibility"
1845-
|| k == "backend" || k == "tools"
1855+
|| k == "backend" || k == "tools" || k == "artifacts"
18461856
|| k == "host-module" || k == "reexport"
18471857
|| k == "linkage";
18481858
};
@@ -2018,6 +2028,12 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
20182028
for (auto& tv : it->second.as_array())
20192029
if (tv.is_string()) spec.tools.push_back(tv.as_string());
20202030
}
2031+
// mcpp#711: `artifacts = ["updater"]` -- the dependency's programs,
2032+
// built for this package's target and shipped beside its own.
2033+
if (auto it = sub.find("artifacts"); it != sub.end() && it->second.is_array()) {
2034+
for (auto& av : it->second.as_array())
2035+
if (av.is_string()) spec.artifacts.push_back(av.as_string());
2036+
}
20212037
// #355 step 5: `host-module = true` — make this dependency's lib-root
20222038
// module importable from build.mcpp (reusable rules as packages).
20232039
if (auto it = sub.find("host-module"); it != sub.end() && it->second.is_bool()) {
@@ -4329,6 +4345,27 @@ std::expected<Manifest, ManifestError> load(const std::filesystem::path& path,
43294345
// M5.0: defaults + target inference (uses filesystem context relative to mcpp.toml).
43304346
apply_defaults_and_infer(*m, path.parent_path());
43314347

4348+
// `[features].<f>.tools` names this package's own programs (mcpp#709), and
4349+
// an inferred target exists only after the inference above.
4350+
for (auto it = m->featureTools.begin(); it != m->featureTools.end(); ++it) {
4351+
for (auto const& name : it->second) {
4352+
const Target* target = nullptr;
4353+
for (std::size_t i = 0; i < m->targets.size(); ++i)
4354+
if (m->targets[i].name == name) { target = &m->targets[i]; break; }
4355+
if (target != nullptr && target->kind == Target::Binary) continue;
4356+
std::string bins;
4357+
for (std::size_t i = 0; i < m->targets.size(); ++i)
4358+
if (m->targets[i].kind == Target::Binary)
4359+
bins += (bins.empty() ? "" : ", ") + m->targets[i].name;
4360+
return std::unexpected(ManifestError{std::format(
4361+
"[features.{}] tools names '{}', which is not a `kind = \"bin\"` "
4362+
"target of this package (its bin targets: {}). A feature makes "
4363+
"the package's own programs available as host tools.",
4364+
it->first, name, bins.empty() ? std::string("none") : bins),
4365+
path, 0, 0});
4366+
}
4367+
}
4368+
43324369
// A `[target.<sel>.targets.<name>]` row names a target, and an undeclared
43334370
// library target exists only after the inference above, so the name is
43344371
// checked here rather than where the row is parsed.

‎modules/manifest/src/types.cppm‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2070,6 +2070,14 @@ struct Manifest {
20702070
// feature is a build rule, here is what it compiles and here is how to
20712071
// reach it". A feature with one and not the other is refused at parse time.
20722072
std::map<std::string, std::string> featureRuleModule;
2073+
// `[features].<f>.tools` -- this package's own `kind = "bin"` targets that
2074+
// the feature makes available as host tools (mcpp#709). A consumer on whose
2075+
// behalf the feature is active receives each one as if its dependency edge
2076+
// had written `tools = [...]`: built once for the build machine, reachable
2077+
// from its build program through `mcpp::dep_bin`. The key moves a request a
2078+
// package already knows it needs into the package, so a consumer that
2079+
// enables a rule does not also have to name the rule's tools.
2080+
std::map<std::string, std::vector<std::string>> featureTools;
20732081
// Feature System v2 Stage 2a — dependencies activated by a feature. A dep
20742082
// declared ONLY here is optional: pulled into the resolution worklist only
20752083
// when its feature is active (root --features or a dep spec's features=[...]).

‎src/build/execute.cppm‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,9 @@ compute_run_targets(const mcpp::build::BuildPlan& plan) {
562562
std::vector<std::pair<std::string, std::string>> out;
563563
for (auto& lu : plan.linkUnits) {
564564
if (lu.kind != mcpp::build::LinkUnit::Binary) continue;
565+
// A dependency's program shipped with this one (mcpp#711) is not a
566+
// program of this package, and `mcpp run` does not choose it.
567+
if (!lu.artifactOf.empty()) continue;
565568
out.emplace_back(lu.targetName, lu.output.generic_string());
566569
}
567570
return out;
@@ -2219,6 +2222,7 @@ export int build_run_target(const std::optional<std::string>& targetName,
22192222
const mcpp::build::LinkUnit* chosen = nullptr;
22202223
for (auto& lu : ctx->plan.linkUnits) {
22212224
if (lu.kind != mcpp::build::LinkUnit::Binary) continue;
2225+
if (!lu.artifactOf.empty()) continue; // mcpp#711; see compute_run_targets
22222226
if (targetName && lu.targetName != *targetName) continue;
22232227
chosen = &lu;
22242228
if (targetName) break;

0 commit comments

Comments
 (0)