-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathpipeline.cppm
More file actions
196 lines (178 loc) · 8.33 KB
/
Copy pathpipeline.cppm
File metadata and controls
196 lines (178 loc) · 8.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// mcpp.pack.pipeline — pack orchestration: build (re-preparing for musl static
// when needed), pick the main binary, plan + run the bundler.
// Bodies moved verbatim from the CLI layer. Zero behavior change.
module;
#include <cstdio>
#include <cstdlib>
export module mcpp.pack.pipeline;
import std;
import mcpp.build.prepare;
import mcpp.build.backend;
import mcpp.build.distribution;
import mcpp.build.flags;
import mcpp.build.ninja;
import mcpp.build.plan;
import mcpp.config;
import mcpp.fetcher.progress;
import mcpp.pack;
import mcpp.ui;
namespace mcpp::pack {
// Everything after CLI option parsing for `mcpp pack`.
//
// `wantTarget` is the target NAME the user asked for, empty when they did not.
// It exists because `mcpp pack <name>` now routes on `[targets.<name>].kind`:
// a name that resolves to a program has to reach the binary selection below,
// or a project with two `bin` targets would accept `mcpp pack app2` and
// silently bundle app1 — the shape where the command succeeds and the answer
// is wrong.
export int build_and_pack(Options opts, bool modeFromUser,
const std::string& wantTarget = {}) {
// `--target *-linux-musl` without an explicit `--mode` implies
// `--mode static` — packaging a musl-static ELF as bundle-project
// would feed patchelf a static binary and crash. The docs treat
// this pair as equivalent; surface it in the code path too.
if (!modeFromUser && opts.targetTriple.find("-musl") != std::string::npos) {
opts.mode = mcpp::pack::Mode::Static;
modeFromUser = true; // user-equivalent intent — block manifest override
}
// ─── Build first (pack implies a fresh build) ────────────────────
mcpp::build::BuildOverrides ov;
if (opts.mode == mcpp::pack::Mode::Static && opts.targetTriple.empty())
ov.target_triple = "x86_64-linux-musl";
else
ov.target_triple = opts.targetTriple;
auto ctx = mcpp::build::prepare_build(/*print_fp=*/false, /*includeDevDeps=*/false,
/*extraTargets=*/{}, ov);
if (!ctx) {
mcpp::ui::error(ctx.error());
return 2;
}
// Manifest may override mode only when neither --mode nor an
// equivalent flag (--target *-musl → static) was given.
if (!modeFromUser && !ctx->manifest.packConfig.defaultMode.empty()) {
if (auto m = mcpp::pack::parse_mode(ctx->manifest.packConfig.defaultMode))
opts.mode = *m;
}
// Re-derive target triple: if mode is Static we force the musl
// triple even when the manifest's [pack].default_mode bumped us
// here after `prepare_build` ran with the host toolchain.
//
// ...but NOT over a target the user asked for. `--mode static` on its own
// has always meant "the musl-static ELF", and that stays; `--mode static
// --target x86_64-windows-gnu` used to silently become a Linux build,
// which was invisible while PE packaging did not exist and is a wrong
// answer now that it does. An explicit `--target` is an instruction.
if (opts.mode == mcpp::pack::Mode::Static
&& opts.targetTriple.empty()
&& ctx->tc.targetTriple.find("-musl") == std::string::npos) {
// Need to re-prepare the build with the musl target.
mcpp::build::BuildOverrides ov2;
ov2.target_triple = "x86_64-linux-musl";
auto ctx2 = mcpp::build::prepare_build(false, false, {}, ov2);
if (!ctx2) { mcpp::ui::error(ctx2.error()); return 2; }
ctx = std::move(ctx2);
}
auto be = mcpp::build::make_ninja_backend();
mcpp::build::BuildOptions bo;
auto br = be->build(ctx->plan, bo);
if (!br) {
// The compiler's own output, not just "build failed" — same reason as
// in the library pipeline.
if (!br.error().diagnosticOutput.empty()) {
std::fputs(br.error().diagnosticOutput.c_str(), stderr);
if (br.error().diagnosticOutput.back() != '\n') std::fputs("\n", stderr);
}
mcpp::ui::error(br.error().message);
return 1;
}
// ─── Pick the main binary target ─────────────────────────────────
//
// An explicitly named target wins over the package-name convention: the
// user said which one, and guessing past that is how `mcpp pack app2`
// would produce app1's bundle under app2's name.
std::filesystem::path mainBinary;
if (!wantTarget.empty()) {
for (auto& lu : ctx->plan.linkUnits) {
if (lu.kind == mcpp::build::LinkUnit::Binary && lu.targetName == wantTarget) {
mainBinary = ctx->outputDir / lu.output;
break;
}
}
if (mainBinary.empty()) {
mcpp::ui::error(std::format(
"target '{}' is not a program in this build", wantTarget));
return 2;
}
}
for (auto& lu : ctx->plan.linkUnits) {
if (!mainBinary.empty()) break;
if (lu.kind == mcpp::build::LinkUnit::Binary
&& lu.targetName == ctx->manifest.package.name)
{
mainBinary = ctx->outputDir / lu.output;
break;
}
}
if (mainBinary.empty()) {
// Fall back to the first binary target if package.name doesn't match.
for (auto& lu : ctx->plan.linkUnits) {
if (lu.kind == mcpp::build::LinkUnit::Binary) {
mainBinary = ctx->outputDir / lu.output;
break;
}
}
}
if (mainBinary.empty()) {
mcpp::ui::error("no binary target to pack");
return 1;
}
auto cfg = mcpp::config::load_or_init(/*quiet=*/false,
mcpp::fetcher::make_bootstrap_progress_callback());
if (!cfg) { mcpp::ui::error(cfg.error().message); return 4; }
// ─── What the build promised, and where its runtime lives ────────
//
// The C++ runtime contract has been resolved since the flags were
// computed; `pack` simply had no way to see it (design §4.3), so on PE
// nothing enforced it and on ELF the `ldd` closure agreed with it by
// luck. Reading the RESOLVED value rather than the manifest string is the
// point: a request that was downgraded (a per-role self-contained on
// /MD, say) must not make the package behave as though it had been
// honoured.
{
const auto flags = mcpp::build::compute_flags(ctx->plan);
opts.carryToolchainRuntime =
flags.contractByRole[static_cast<std::size_t>(
mcpp::build::dist::Role::Distributable)]
== mcpp::build::dist::Contract::ToolchainCoupled;
opts.toolchainRuntimeDirs = ctx->plan.toolchain.linkRuntimeDirs;
// Where a third-party dependency's shared library may be found. Both
// channels, because they answer for different things: the runtime
// library dirs are what `mcpp run` puts on the loader's path, and the
// link intent's search dirs are what a dependency package declared.
opts.depSearchDirs = ctx->plan.runtimeLibraryDirs;
for (auto const& d : ctx->plan.linkIntent.runtimeSearchDirs)
opts.depSearchDirs.push_back(d);
}
// ─── Build the plan + run ────────────────────────────────────────
auto plan = mcpp::pack::make_plan(ctx->manifest, *cfg, opts,
mainBinary, ctx->projectRoot, ctx->tc.targetTriple,
// From the RESOLVED graph. `mcpp why runtime` on a real imgui project
// lists `capability:opengl.glx.driver <- compat.glfw@3.4` — none of
// which appears in the project's own manifest.
ctx->plan.runtimeRequirements);
if (!plan) { mcpp::ui::error(plan.error().message); return 1; }
mcpp::ui::info("Packing", std::format("{} v{} ({})",
plan->packageName, plan->packageVersion,
mcpp::pack::mode_cli_name(plan->opts.mode)));
auto r = mcpp::pack::run(*plan, *cfg);
if (!r) {
mcpp::ui::error(r.error().message);
return 1;
}
auto pathCtx = mcpp::fetcher::make_path_ctx(&*cfg, ctx->projectRoot);
auto outPath = (opts.format == mcpp::pack::Format::Tar)
? plan->archivePath : plan->stagingRoot;
mcpp::ui::status("Packed", mcpp::ui::shorten_path(outPath, pathCtx));
return 0;
}
} // namespace mcpp::pack