Skip to content

Commit 4cdf04d

Browse files
committed
fix: a pack of several members resolves a target name for the member an action is for, fails a member alone in the distribution step, and reports the members in [workspace] members order
- ${mcpp.target_file:<name>} is resolved by mcpp::build::resolve_target_file: the unit of the member the referring package acts for, and a refusal when several members define the name and the package acts for none of them. - With several members the distribution drive keeps going; each member's declared products are removed before it, so a file present after it is this pack's; a member without a staged tree whose provider reads it fails alone. - The members are reported, and the exit status chosen, in [workspace] members order across configurations. - e2e 870.
1 parent 87d7b0d commit 4cdf04d

10 files changed

Lines changed: 301 additions & 39 deletions

‎.agents/docs/2026-09-30-member-selection-and-build-program-cost-plan.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,23 @@ reuse of a stale compile. It found, and the integration corrected:
906906
- the announcement of a multi-member test plan, which named the virtual root;
907907
- comments and documentation that no longer described the code.
908908

909+
A second review, of the pack change alone, found no change to the pack of one
910+
member, and three defects in the pack of several, which are corrected (e2e 870):
911+
912+
- `${mcpp.target_file:<name>}` resolved to the last link unit of that name in
913+
the plan, so a member's distributable could be built from another member's
914+
program of the same target name. The name is now resolved for the member the
915+
action's package acts for, and refused when that is none of the members that
916+
define it.
917+
- One member's failing distribution step, or a member without a staged tree
918+
whose provider reads it, failed every member of its configuration. The drive
919+
keeps going when several members are packed, each member's declared products
920+
are removed before it, so that a file present after it is this pack's, and a
921+
member without a tree is failed alone and the pass prepared again for the
922+
others.
923+
- The members were reported in the order of their configurations. They are
924+
reported in `[workspace] members` order.
925+
909926
### 15.4 Verification before the pull request
910927

911928
- Unit tests: 142 passed.

‎CHANGELOG.md‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,12 @@ exits 101 (both under **Changed**).
7575
configuration. A positional target, several `--target` values, an `--output`
7676
that is a file, a member no provider of the requested format acts for, and
7777
two members that would write one destination are refused before anything is
78-
compiled. `mcpp pack -p <member>` keeps its meaning. The JSON envelope adds
78+
compiled. A member whose distribution step fails is reported by name and the
79+
others are packed; the members are reported in `[workspace] members` order,
80+
and `${mcpp.target_file:<name>}` names the target of the member an action is
81+
for. `mcpp pack -p <member>` keeps its meaning. The JSON envelope adds
7982
`data.stages` and a `member` field per artifact when several members are
80-
packed (e2e 867 to 869).
83+
packed (e2e 867 to 870).
8184

8285
### Fixed
8386

‎docs/10-pack-and-release.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,9 @@ What each member receives is what `mcpp pack -p <member>` gives it:
210210
outputs are verified to exist and are reported as `Packed` for that member.
211211

212212
`--workspace` packs a member only if it has a program target, and says which it
213-
skipped. A member named with `-p` that has none is refused. Members are packed in
214-
`[workspace] members` order, whatever order `-p` names them in. A member that
213+
skipped. A member named with `-p` that has none is refused. Members are packed
214+
one configuration at a time and reported in `[workspace] members` order, whatever
215+
order `-p` names them in. A member that
215216
fails, for instance one whose provider submitted nothing for the requested
216217
format, is reported by name and the others are packed; the exit status is
217218
non-zero if any member failed. One member, whether named with one `-p` or by the

‎docs/zh/10-pack-and-release.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ mcpp pack -p cli -p gui --format release
186186
所代表的成员。每个成员的输出都会核实存在,并以该成员的 `Packed` 报告。
187187

188188
`--workspace` 只打包有程序 target 的成员,并说明跳过了哪些;用 `-p` 点名的成员若没有
189-
程序 target 则被拒绝。成员按 `[workspace] members` 的顺序打包,与 `-p` 的书写顺序无关。
189+
程序 target 则被拒绝。成员按配置逐组打包,按 `[workspace] members` 的顺序报告,与 `-p` 的书写顺序无关。
190190
某个成员失败(例如它的提供者对所请求的格式什么也没有提交)时,按名字报告,其余成员
191191
照常打包;只要有一个成员失败,退出状态就非零。一个成员 —— 无论是用一个 `-p` 点名,还是
192192
由命令所在的目录决定 —— 与以前完全一样地打包:`mcpp pack -p X` 只规划 X 的闭包。在虚拟

‎src/build/plan.cppm‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,19 @@ expand_manifest_include_entry(const std::filesystem::path& root,
686686
// ends up spelled two ways.
687687
std::string qualified_package_name(const mcpp::manifest::Manifest& manifest);
688688

689+
// What `${mcpp.target_file:<name>}` names in `plan`: the build-dir-relative
690+
// output of the link unit of that target name. A plan of several members may
691+
// hold a target of one name in each of them; the name then means the unit of
692+
// `actingMember`, the member the referring package acts for. `output` is empty
693+
// when no unit has the name. `members` is non-empty when the name is ambiguous:
694+
// several members define it and none of them is `actingMember`.
695+
struct TargetFileAnswer {
696+
std::string output;
697+
std::vector<std::string> members;
698+
};
699+
TargetFileAnswer resolve_target_file(const BuildPlan& plan, std::string_view name,
700+
std::string_view actingMember);
701+
689702
// The objects a package contributes to an image that links it whole: its
690703
// module units, which link unconditionally, then its implementation units, in
691704
// plan order. A shared library that carries a private copy of a graph C++
@@ -3289,6 +3302,22 @@ make_plan(const mcpp::manifest::Manifest& manifest,
32893302
return plan;
32903303
}
32913304

3305+
TargetFileAnswer resolve_target_file(const BuildPlan& plan, std::string_view name,
3306+
std::string_view actingMember) {
3307+
TargetFileAnswer answer;
3308+
std::set<std::string> members;
3309+
for (auto const& lu : plan.linkUnits) {
3310+
if (lu.targetName != name) continue;
3311+
answer.output = lu.output.generic_string();
3312+
if (!actingMember.empty() && lu.memberOf == actingMember) return {answer.output, {}};
3313+
if (!lu.memberOf.empty()) members.insert(lu.memberOf);
3314+
}
3315+
// One member's plan, and a name one member defines, resolve as they always
3316+
// have: to the one unit, or to the last of the units that share the name.
3317+
if (members.size() > 1) answer.members.assign(members.begin(), members.end());
3318+
return answer;
3319+
}
3320+
32923321
std::vector<std::filesystem::path>
32933322
package_link_objects(const BuildPlan& plan, std::string_view packageName) {
32943323
std::vector<std::filesystem::path> objects;

‎src/build/prepare/plan.cpp‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,9 @@ static std::expected<void, std::string> step13_build_graph_actions(PrepareState&
917917
// become an edge with a blank path, and ninja reports that far away
918918
// from the typo that caused it.
919919
std::set<std::string> unresolvedTargets;
920+
// Names several members define, used by a package acting for none of
921+
// them (`resolve_target_file`): refused, never resolved by member order.
922+
std::map<std::string, std::vector<std::string>> ambiguousTargets;
920923
std::set<std::string> unresolvedArtifacts;
921924
// `${mcpp.stage_dir}` used where there is no staged tree, and used by an
922925
// action whose role runs before the link. Both are refusals rather than
@@ -953,6 +956,7 @@ static std::expected<void, std::string> step13_build_graph_actions(PrepareState&
953956
// The stage of the package being collected, as `collect` sets it: that of
954957
// the member it acts for (`PrepareState::packStageOf`), or why it has none.
955958
const BuildOverrides::PackStage* stage = nullptr;
959+
std::string actingMember; // the member the package acts for (actingMemberOf)
956960
std::string stageShared;
957961
bool stagePass = false;
958962
auto substitute = [&](std::string s, const char* actionId,
@@ -1005,12 +1009,10 @@ static std::expected<void, std::string> step13_build_graph_actions(PrepareState&
10051009
// "missing and no known rule to make it". Commands run with
10061010
// cwd = the build dir, so the relative form is also what the
10071011
// tool being invoked should receive.
1008-
std::string resolved;
1009-
for (auto const& lu : ctx.plan.linkUnits)
1010-
if (lu.targetName == name)
1011-
resolved = lu.output.generic_string();
1012-
if (resolved.empty()) unresolvedTargets.insert(name);
1013-
s.replace(p, close - p + 1, resolved);
1012+
auto answer = mcpp::build::resolve_target_file(ctx.plan, name, actingMember);
1013+
if (answer.output.empty()) unresolvedTargets.insert(name);
1014+
else if (!answer.members.empty()) ambiguousTargets[name] = answer.members;
1015+
s.replace(p, close - p + 1, answer.output);
10141016
}
10151017
// `${mcpp.artifact:<package>/<target>}` (mcpp#711): a dependency's
10161018
// program that an edge requested with `artifacts = [...]`, spelled
@@ -1048,6 +1050,7 @@ static std::expected<void, std::string> step13_build_graph_actions(PrepareState&
10481050
// mcpp#534's ordering edge is scoped to this name.
10491051
auto owner = mcpp::build::qualified_package_name(mm);
10501052
stage = state.packStageOf(packageIndex);
1053+
actingMember = state.actingMemberOf(packageIndex);
10511054
stagePass = stage && !stage->dir.empty();
10521055
stageShared = stage || state.overrides.pack_stages.empty()
10531056
? std::string{} : state.packSharedWhy(packageIndex);
@@ -1166,6 +1169,13 @@ static std::expected<void, std::string> step13_build_graph_actions(PrepareState&
11661169
bad, known.empty() ? std::string("none") : known));
11671170
}
11681171

1172+
for (auto const& [n, in] : ambiguousTargets)
1173+
return std::unexpected(std::format(
1174+
"build.mcpp action references ${{mcpp.target_file:{}}}, a target of "
1175+
"each of the members {}, and its package acts for none of them.\n"
1176+
" use: select one of the members, or give the targets distinct names",
1177+
n, std::format("{}", in)));
1178+
11691179
if (!unresolvedArtifacts.empty()) {
11701180
std::string bad, known;
11711181
for (auto const& n : unresolvedArtifacts) bad += (bad.empty() ? "" : ", ") + n;

‎src/build/prepare/state.cppm‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,18 @@ struct PrepareState {
391391
auto stage = stages.find(pack_owner(name, reach->second));
392392
return stage == stages.end() ? nullptr : &stage->second;
393393
}
394+
// The selected member package `i` acts for in a plan of several members:
395+
// itself when it is one, the one member whose closure reaches it when
396+
// exactly one does, and none (empty) otherwise, by `pack_owner`'s rule. It
397+
// is what a name the package's actions use is resolved against when two
398+
// members give that name to different things (`${mcpp.target_file:}`).
399+
std::string actingMemberOf(std::size_t i) {
400+
if (selectedMemberCount() < 2 || i >= packages.size()) return {};
401+
computePackReach();
402+
const auto name = mcpp::build::qualified_package_name(packages[i].manifest);
403+
auto reach = packReach.find(name);
404+
return reach == packReach.end() ? std::string{} : pack_owner(name, reach->second);
405+
}
394406
// The packaging pass's values of a build program's environment, for the
395407
// program of package `i`. A package that acts for no packed member is told
396408
// nothing, not even the format: its answer cannot depend on a request it

‎src/cli/cmd_publish.cppm‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ PackMembers pack_members(const mcpplibs::cmdline::ParsedArgs& parsed) {
398398
out.rc = 2;
399399
return out;
400400
}
401-
out.several = mcpp::pack::MemberPack{sel.root, std::move(*groups), {}};
401+
out.several = mcpp::pack::MemberPack{sel.root, std::move(*groups), {}, packable};
402402
return out;
403403
}
404404

0 commit comments

Comments
 (0)