-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathrefusal.cppm
More file actions
200 lines (188 loc) · 11.3 KB
/
Copy pathrefusal.cppm
File metadata and controls
200 lines (188 loc) · 11.3 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
197
198
199
200
// mcpp.build.refusal — the machine-readable identity of a target refusal.
//
// WHY A CODE AND NOT THE SENTENCE.
//
// mcpp refuses a target for several distinct reasons, and every one of them
// leaves the same trace: `prepare_build` returns `std::unexpected(<prose>)`.
// A caller that needs to know WHICH reason has, until now, had exactly one
// option — match the prose. `tests/matrix/scan.sh` did, and so did three e2e
// tests, and the cost was measured on 2026-08-26 within a single session:
// reworded `cannot emit it` to `cannot be emitted by`, and a test that had
// been asserting a refusal silently began asserting nothing.
//
// THE MESSAGE IS STILL A PROMISE. This does not replace it — a refusal that
// does not name the target, the reason and the way out is a defect whether or
// not it carries a code, and the e2e tests keep asserting exactly that. What
// the code replaces is *classification*: "did it refuse, and for which reason"
// is a question with a finite answer set, and a finite answer set should not be
// spelled as a substring search.
//
// ── Why a sink rather than a return type ───────────────────────────────────
//
// `prepare_build` returns `std::expected<BuildContext, std::string>` and it has
// dozens of error returns, of which these are six. Widening the error type
// would touch every consumer of `.error()` to express something only one of
// them reads. `mcpp::diag` settled the same trade-off the same way: a per-run
// sink, written where the fact is known and read where it is needed.
//
// SET IMMEDIATELY BEFORE THE `return`, NEVER EARLIER. A code set at the top
// of a branch that then does not refuse is worse than no code at all: it makes
// a successful build report a reason.
export module mcpp.build.refusal;
import std;
export namespace mcpp::build::refusal {
// ONE CODE PER DECISION, and the decisions are the ones a reader of the
// target matrix has to tell apart. Adding a refusal branch means adding a code
// here — an unnamed branch reports `other`, which is a visible admission
// rather than a silent merge into a neighbouring reason.
enum class Code {
None, // no refusal
UnknownTarget, // the spelling names no row, and no (arch, os) group
AmbiguousRequest, // several rows serve (arch, os) and none is the default
// The compiler a package requires cannot be used for this build: the
// project stated a different one, two packages disagree, the target row
// names a family that is the only one able to emit it, or no version of the
// required family exists to use. One code, because what a consumer does
// about all four is the same — read the message.
CompilerRequirementConflict,
TierPlanned, // the row exists in the vocabulary, nothing is wired
HostCannotServe, // no payload here, and no graph supplied the system
CapabilityPin, // the row's toolchain is a capability, not a preference
ConventionUnreplaced, // the convention was overridden and nothing replaced it
OsMismatch, // requested and resolved triples name different systems
LayerRequirement, // a package requires a layer the resolution did not give it
LayerOrdering, // the five layers do not stack (check_layering)
LldRequiredAbsent, // the row links through lld directly and none is here
HostToolToolchain, // build.mcpp under a cross target has no host toolchain
StdModulePrecompile, // the std module could not be precompiled
// Two packages provide one capability and at least one of them declared it
// exclusive. Distinct from CapabilityPin: that one is about SELECTING a
// provider for a requirement, this one is about two implementations of one
// interface being in the same link at all.
ExclusiveCapability,
// A package requires more of the machine than the machine was declared to
// have. Distinct from a capability that is missing entirely: here the thing
// exists and is too old.
VersionFloorUnmet,
// A source glob is constrained to a device set this build does not cover.
// Distinct from VersionFloorUnmet (the machine is too old) and from a
// prebuilt artifact's tag mismatch (that refusal is about consuming): here
// the project's own sources ask for a device the build was not told to
// target.
AccelMismatch,
// A source glob names an accelerator backend its own package does not list
// in `[package] accelerators`. Distinct from AccelMismatch, which is about
// THIS build's device set: this one is a property of the manifest alone and
// is refused whether or not the build names any accelerator at all.
AccelBackendUndeclared,
// A device-kind source reached no action. The engine has no compile rule
// for those extensions, so a file the package's build program did not claim
// is a file nothing compiles -- reported here rather than as the undefined
// reference it used to become at the link.
DeviceSourceUnconsumed,
// `build.mcpp` imports a module no dependency supplies as a host module.
// Distinct from an ordinary missing dependency: the package may well be in
// the graph and reaching the target, and still not be compiled for the
// build program, which is what `host-module = true` asks for.
HostModuleMissing,
// Two declarations name one xlings package at versions that cannot both
// hold. Distinct from VersionFloorUnmet, which is about the machine: this
// one is about two manifests disagreeing over a tool.
ToolVersionConflict,
// An Apple SDK this target needs is not on this machine. Distinct from
// HostCannotServe, which says no PAYLOAD here produces the target and
// waits for the graph in case a package supplies the system: an Apple SDK
// is not redistributable, so no package can supply it and nothing a later
// step learns changes the answer. Distinct from TierPlanned, which says
// the row is not wired at all -- these rows are.
AppleSdkAbsent,
// A dependency's C++ shared library in a graph whose C++ runtime is a
// package, with no statement that shared libraries carry a private copy.
// Distinct from LayerOrdering, which is about the layers not stacking: here
// they stack, and one image would link with no C++ runtime at all (#641).
SharedLibraryCxxRuntime,
// The plan needs something that has to be downloaded -- a toolchain, a
// package, a git revision, the package index -- and the run is offline
// (`--offline`, `MCPP_OFFLINE`, or the older `MCPP_NO_AUTO_INSTALL`).
// Distinct from every other code: nothing is wrong with the project, and
// one online run fixes it. An editor that plans offline by default tells
// the two apart by this code (#648).
OfflineDownloadRequired,
// The dependency graph contains a cycle of packages. Detected where the
// graph is resolved, so every cache mode refuses it alike (#649 E6); the
// build-cache key walk used to be the only place that noticed, and a
// `--cache=local` build of the same graph went ahead.
PackageCycle,
// A program or test whose manifest states a self-contained C++ runtime
// loads a C++ shared library of the same build that couples to a shared
// runtime, so the process would hold two C++ runtimes (#646 F3a). Distinct
// from SharedLibraryCxxRuntime, where the library has none: here both
// images have one and they are different copies.
ProgramCxxRuntimeSplit,
// A static package is reachable from two images of one build (two shared
// libraries, or a shared library and the program) on a format whose link
// or load cannot share one copy between them (#646 F1). Distinct from
// ExclusiveCapability, which is about two implementations of an interface:
// here it is one package that has no single image to live in.
StaticPackageInTwoImages,
Other, // a refusal that has not been given a code yet
};
constexpr std::string_view name(Code c) {
switch (c) {
case Code::None: return "none";
case Code::UnknownTarget: return "unknown-target";
case Code::AmbiguousRequest: return "ambiguous-request";
case Code::CompilerRequirementConflict:
return "compiler-requirement-conflict";
case Code::TierPlanned: return "tier-planned";
case Code::AppleSdkAbsent: return "apple-sdk-absent";
case Code::HostCannotServe: return "host-cannot-serve";
case Code::CapabilityPin: return "capability-pin";
case Code::ConventionUnreplaced: return "convention-unreplaced";
case Code::OsMismatch: return "os-mismatch";
case Code::LayerRequirement: return "layer-requirement";
case Code::LayerOrdering: return "layer-ordering";
case Code::LldRequiredAbsent: return "lld-required-absent";
case Code::HostToolToolchain: return "host-tool-toolchain";
case Code::StdModulePrecompile: return "std-module-precompile";
case Code::ExclusiveCapability: return "exclusive-capability";
case Code::VersionFloorUnmet: return "version-floor-unmet";
case Code::AccelMismatch: return "accel-mismatch";
case Code::AccelBackendUndeclared:
return "accel-backend-undeclared";
case Code::DeviceSourceUnconsumed:
return "device-source-unconsumed";
case Code::HostModuleMissing: return "host-module-missing";
case Code::ToolVersionConflict: return "tool-version-conflict";
case Code::SharedLibraryCxxRuntime:
return "shared-library-cxx-runtime";
case Code::OfflineDownloadRequired:
return "offline-download-required";
case Code::PackageCycle: return "package-cycle";
case Code::ProgramCxxRuntimeSplit:
return "program-cxx-runtime-split";
case Code::StaticPackageInTwoImages:
return "static-package-in-two-images";
case Code::Other: return "other";
}
return "other";
}
// Written by `prepare_build`'s refusal sites; read by the machine-readable
// output layer. `record` is what a refusal site calls; `take` reads and clears,
// so a later successful run cannot inherit an earlier run's reason.
void record(Code c);
Code take();
} // namespace mcpp::build::refusal
// ── implementation ──────────────────────────────────────────────────────────
namespace mcpp::build::refusal {
namespace {
// `thread_local`: `prepare_build` recurses for nested host sub-builds, and
// those run on the calling thread — but a build program's own sub-build must
// not leave its refusal behind for the outer one. Same thread, so the sink is
// shared deliberately; the outer refusal is recorded last and wins, which is
// the one the user was asking about.
thread_local Code g_last = Code::None;
} // namespace
void record(Code c) { g_last = c; }
Code take() { Code c = g_last; g_last = Code::None; return c; }
} // namespace mcpp::build::refusal