-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathcompat.cppm
More file actions
213 lines (188 loc) · 9.44 KB
/
Copy pathcompat.cppm
File metadata and controls
213 lines (188 loc) · 9.44 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
201
202
203
204
205
206
207
208
209
210
211
212
213
// mcpp.toolchain.compat — legacy-spelling compatibility layer.
//
// THE ONLY FILE that knows pre-0.0.93 toolchain/triple spellings. Core code
// (registry, prepare, lifecycle) sees canonical forms exclusively; the two
// public parse entry points call normalize_* first. Deleting this module
// would break exactly one thing: old inputs — never a canonical path.
//
// Owns four responsibilities (design §4.7):
// 1. spec aliases musl-gcc@V / gcc@V-musl / <triple>-gcc@V / mingw@V /
// mingw-cross@V / clang@V → (family, version, target)
// 2. triple aliases handled by triple::parse itself (GNU/LLVM/Apple
// spellings are grammar, not legacy) — compat only
// decides WHEN a compiler token is really a triple
// 3. persisted-state migration: old config/manifest spec strings normalize
// on the read path via the same normalize_spec
// 4. the one-line canonical hint text (printed at most once per process)
//
// NOT compat: xim package names (mingw-cross-gcc, musl-gcc, …). Those are
// the distribution layer's CURRENT identity — "cross" is legitimate there
// (musl.cc's -cross tarballs, Debian's g++-mingw-w64 precedent) — and they
// are produced by registry.cppm's payload mapping, not parsed from users.
//
// See .agents/docs/2026-07-15-toolchain-target-naming-unification-design.md.
module;
#include <cstdio>
export module mcpp.toolchain.compat;
import std;
import mcpp.platform;
import mcpp.toolchain.triple;
export namespace mcpp::toolchain::compat {
// A user/config spec token pair, normalized to the two-axis identity model.
struct NormalizedSpec {
std::string family; // "gcc" | "llvm" | "msvc" | "openkal-llvm"
std::string version; // numeric (possibly partial), or "system"; never "-musl"-suffixed
triple::Triple target; // empty = host
// WHICH PAYLOAD, when the family alone does not say. `emsdk` and
// `android-ndk` both normalise to the llvm family -- their compilers ARE
// clang -- so without this the two are indistinguishable from `xim:llvm`
// in every line mcpp prints. Empty for every other spelling.
std::string payload;
// Set when a legacy spelling was rewritten; `hint` is the one-line note.
bool changed = false;
std::string hint;
};
// Normalize a (compiler, version) token pair. The caller has already split
// a combined "name@ver" form. Returns nullopt for a compiler token outside
// the family set and its aliases — the caller owns the error message.
std::optional<NormalizedSpec> normalize_spec(std::string_view compiler,
std::string_view version);
// Print a normalization hint at most once per process (quiet by design:
// note-level, aliases are permanently supported — this is a pointer to the
// canonical spelling, not a deprecation warning).
void print_hint_once(std::string_view hint);
} // namespace mcpp::toolchain::compat
namespace mcpp::toolchain::compat {
namespace {
bool ends_with(std::string_view s, std::string_view suf) {
return s.size() >= suf.size()
&& s.compare(s.size() - suf.size(), suf.size(), suf) == 0;
}
std::string_view strip_namespace(std::string_view compiler) {
if (auto colon = compiler.find(':'); colon != std::string_view::npos)
return compiler.substr(colon + 1);
return compiler;
}
triple::Triple host_musl_triple() {
triple::Triple t;
t.arch = std::string(mcpp::platform::host_arch);
t.os = "linux";
t.env = "musl";
return t;
}
triple::Triple windows_gnu_triple() {
return { "x86_64", "windows", "gnu" };
}
NormalizedSpec with_hint(NormalizedSpec s, std::string_view oldSpelling) {
s.changed = true;
std::string canonical = std::format("{}@{}", s.family,
s.version.empty() ? std::string("<version>") : s.version);
if (!s.target.empty())
canonical += std::format(" targeting '{}'", s.target.str());
s.hint = std::format("'{}' is now {}", oldSpelling, canonical);
return s;
}
} // namespace
std::optional<NormalizedSpec> normalize_spec(std::string_view compilerIn,
std::string_view versionIn) {
std::string_view compiler = strip_namespace(compilerIn);
std::string version(versionIn);
// Legacy: musl flavor as a version suffix ("gcc@15.1.0-musl", "15-musl").
bool muslVersionSuffix = ends_with(version, "-musl");
if (muslVersionSuffix) version.resize(version.size() - 5);
NormalizedSpec out;
out.version = version;
// ── canonical families pass through ─────────────────────────────────────
// `openkal-llvm` NORMALISES TO `llvm`, AND USED TO BE A FAMILY OF ITS
// OWN. It named the same payload and carried a fact about where the TARGET
// SIDE comes from — which `mcpp.targetside` now resolves from what packages
// declare, after the graph exists, where the fact actually lives. The
// spelling is kept so a manifest or config written against it still
// resolves; it is an alias and nothing behaves differently under it.
if (compiler == "gcc" || compiler == "llvm" || compiler == "msvc"
|| compiler == "openkal-llvm") {
out.family = compiler == "openkal-llvm" ? "llvm" : std::string(compiler);
if (muslVersionSuffix && compiler == "gcc") {
out.target = host_musl_triple();
return with_hint(std::move(out),
std::format("{}@{}-musl", compiler, version));
}
if (muslVersionSuffix) return std::nullopt; // llvm/msvc have no musl flavor
return out;
}
// ── canonical families whose payload carries its own target ─────────────
//
// NOT ALIASES AND NOT LEGACY. `em++` and the NDK's `clang++` are clang, so
// the FAMILY is llvm and there is no fourth value to invent; what these
// spellings add is which payload answers, and for emsdk also which target
// -- the payload compiles for exactly one, so a spec that names it has
// already named the target. `with_hint` is deliberately not used: a hint
// says "this spelling is old, here is the current one", and these are the
// current ones.
if (compiler == "emsdk" || compiler == "emscripten") {
out.family = "llvm";
out.payload = "emsdk";
if (auto t = triple::parse("wasm32-emscripten")) out.target = *t;
if (muslVersionSuffix) return std::nullopt;
return out;
}
// The NDK serves BOTH Android arches from one payload, so it must NOT set
// a target: the arch arrives from `--target` or `[target.<triple>]`, and
// pinning one here would make `android-ndk@<v>` mean aarch64 to a reader
// who typed it for x86_64.
//
// ONE SPELLING. `ndk` was accepted here as an alias and the capability gate
// refuses it, because that gate compares the declared spelling against the
// ROW'S PIN -- `android-ndk@30.0.16248370` -- and `ndk@30.0.16248370` does
// not contain it. So the alias parsed and was then rejected at the point of
// use, which reads as a defect rather than as a naming choice.
//
// Withdrawn rather than completed. Teaching the gate to compare normalised
// payload names would make two spellings work and put the comparison in a
// second mechanism; one name makes the gate correct by construction. The
// name kept is the one the index uses, so there is a single string for this
// payload across the ecosystem.
if (compiler == "android-ndk") {
out.family = "llvm";
out.payload = "android-ndk";
if (muslVersionSuffix) return std::nullopt;
return out;
}
// ── legacy spellings ─────────────────────────────────────────────────────
if (compiler == "clang") { // alias family → llvm
out.family = "llvm";
return with_hint(std::move(out), std::format("clang@{}", version));
}
if (compiler == "musl-gcc") { // musl as compiler-name prefix
out.family = "gcc";
out.target = host_musl_triple();
return with_hint(std::move(out), std::format("musl-gcc@{}", version));
}
if (compiler == "mingw" || compiler == "mingw-cross"
|| compiler == "mingw-gcc" || compiler == "mingw-cross-gcc") {
// One concept, two host-split legacy names: GCC targeting Windows PE
// (GNU CRT). Which payload serves it (native winlibs vs Linux-hosted
// cross) is decided by registry's payload mapping from the HOST —
// never by the name the user typed.
out.family = "gcc";
out.target = windows_gnu_triple();
return with_hint(std::move(out), std::format("{}@{}", compiler, version));
}
if (ends_with(compiler, "-gcc")) { // triple-named: aarch64-linux-musl-gcc
auto tripleStr = compiler.substr(0, compiler.size() - 4);
if (auto t = triple::parse(tripleStr)) {
out.family = "gcc";
out.target = *t;
return with_hint(std::move(out), std::format("{}@{}", compiler, version));
}
}
return std::nullopt;
}
void print_hint_once(std::string_view hint) {
if (hint.empty()) return;
static bool printed = false;
if (printed) return;
printed = true;
std::println(stderr, "note: {}", hint);
}
} // namespace mcpp::toolchain::compat