-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathcompat.cppm
More file actions
272 lines (235 loc) · 11.7 KB
/
Copy pathcompat.cppm
File metadata and controls
272 lines (235 loc) · 11.7 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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
// mcpp.pm.compat - compatibility facade for evolving package conventions.
// Legacy-only behavior lives under src/pm/compat/legacy.cppm so it has an
// explicit retirement boundary.
//
// DEPRECATION SCHEDULE:
// The shims in this module are slated for removal in mcpp 1.0.0.
// Projects should migrate to the canonical forms before that point:
//
// ┌─────────────────────────────────────┬──────────────────────────────┐
// │ Deprecated (works until 1.0) │ Canonical (use now) │
// ├─────────────────────────────────────┼──────────────────────────────┤
// │ name = "mcpplibs.cmdline" │ namespace = "mcpplibs" │
// │ (namespace embedded in name) │ name = "cmdline" │
// ├─────────────────────────────────────┼──────────────────────────────┤
// │ [dependencies] │ [dependencies.mcpplibs] │
// │ "mcpplibs.cmdline" = "0.0.2" │ cmdline = "0.0.2" │
// └─────────────────────────────────────┴──────────────────────────────┘
//
// See also: mcpp.pm.dep_spec (kDefaultNamespace definition).
export module mcpp.pm.compat;
export import mcpp.pm.compat.legacy;
import std;
import mcpp.pm.dep_spec;
export namespace mcpp::pm::compat {
// ─── Package name normalisation ──────────────────────────────────────
//
// Given a raw `package.name` and an optional `package.namespace` from
// an xpkg descriptor or mcpp.toml, produce the canonical (namespace,
// shortName) pair.
//
// Resolution order:
// 1. If `ns` is non-empty → use it directly; `name` is the short name.
// 2. If `name` contains a dot → split on the FIRST dot:
// - prefix = namespace, suffix = short name.
// (COMPAT: this is the legacy convention; warns on stderr.)
// 3. Otherwise → namespace = kDefaultNamespace ("mcpp"), name as-is.
struct ResolvedName {
std::string namespace_;
std::string shortName;
bool usedLegacySplit = false; // true when rule (2) fired
};
inline ResolvedName resolve_package_name(std::string_view name,
std::string_view ns)
{
ResolvedName r;
if (!ns.empty()) {
// Rule 1: explicit namespace field — canonical path.
r.namespace_ = std::string(ns);
r.shortName = std::string(name);
return r;
}
auto dot = name.find('.');
if (dot != std::string_view::npos) {
// Rule 2: legacy dotted name — split on first dot.
r.namespace_ = std::string(name.substr(0, dot));
r.shortName = std::string(name.substr(dot + 1));
r.usedLegacySplit = true;
return r;
}
// Rule 3: bare name → default namespace.
r.namespace_ = std::string(mcpp::pm::kDefaultNamespace);
r.shortName = std::string(name);
return r;
}
// ─── Descriptor-derived coordinates ──────────────────────────────────
//
// Derive the structured (namespace, shortName) for a package whose index
// descriptor was located by filename candidates from an unqualified spec
// (`mcpp new --template <pkg>`). The filename hit alone is not enough:
// pkgs/l/llmapi.lua is found by the bare name "llmapi" while the
// descriptor declares `namespace = "mcpplibs"` (and possibly a legacy
// dotted `name = "mcpplibs.llmapi"`), and xlings resolves install targets
// by the descriptor's qualified name.
//
// Unlike resolve_package_name(), a bare name with no namespace field
// stays in the index root (namespace "") — root packages such as "imgui"
// install by their bare name.
inline ResolvedName descriptor_coordinates(std::string_view specPkg,
std::string_view luaNs,
std::string_view luaName)
{
ResolvedName r;
std::string name(luaName.empty() ? specPkg : luaName);
r.namespace_ = std::string(luaNs);
if (!r.namespace_.empty()) {
// Legacy descriptors embed the namespace in the name too
// (namespace = "mcpplibs", name = "mcpplibs.llmapi").
auto prefix = r.namespace_ + ".";
if (name.starts_with(prefix)) {
name = name.substr(prefix.size());
r.usedLegacySplit = true;
}
} else if (auto dot = name.find('.'); dot != std::string::npos) {
// Legacy dotted name without a namespace field.
r.namespace_ = name.substr(0, dot);
name = name.substr(dot + 1);
r.usedLegacySplit = true;
}
r.shortName = std::move(name);
return r;
}
// Reconstruct the fully-qualified name from (namespace, shortName).
// Default-namespace packages use the bare short name; others use
// "ns.short".
inline std::string qualified_name(std::string_view ns,
std::string_view shortName)
{
if (ns.empty() || ns == mcpp::pm::kDefaultNamespace)
return std::string(shortName);
return std::format("{}.{}", ns, shortName);
}
// ─── Index directory naming ──────────────────────────────────────────
//
// Maps (indexName, namespace, shortName) → the xpkgs subdirectory name
// that xlings places the extracted tarball under.
//
// xlings layout: <ns>-x-<ns>.<short>/<version>/
// e.g. mcpplibs-x-mcpplibs.cmdline/0.0.2/
// compat-x-compat.mbedtls/3.6.1/
inline std::string xpkg_dir_name(std::string_view indexName,
std::string_view ns,
std::string_view shortName)
{
auto fqname = ns.empty() ? std::string(shortName)
: std::format("{}.{}", ns, shortName);
if (!ns.empty()) return std::format("{}-x-{}", ns, fqname);
return std::format("{}-x-{}", indexName, fqname);
}
// ─── xpkg .lua filename candidates (namespace-aware) ────────────────
//
// Given a structured (namespace, shortName), return the list of candidate
// filenames to search for in the index's pkgs/<letter>/ directories.
//
// The FIRST candidate is the canonical form; subsequent candidates are
// backward-compatibility fallbacks.
//
// DEPRECATION SCHEDULE (fallback candidates):
// Fallback candidates are slated for removal in mcpp 1.0.0.
// Package indices should use the canonical filename form by then.
inline std::vector<std::string> xpkg_lua_candidates(std::string_view ns,
std::string_view shortName)
{
std::vector<std::string> candidates;
auto qname = qualified_name(ns, shortName);
auto fqname = ns.empty() ? std::string(shortName)
: std::format("{}.{}", ns, shortName);
// Canonical: for default namespace → "<shortName>.lua" (e.g. "cmdline.lua")
// for non-default → "<ns>.<shortName>.lua" (e.g. "compat.mbedtls.lua")
if (ns.empty() || ns == mcpp::pm::kDefaultNamespace) {
candidates.push_back(std::string(shortName) + ".lua");
} else {
candidates.push_back(fqname + ".lua");
}
// ── Fallback candidates (COMPAT, remove in 1.0.0) ──────────────
// Fallback: full qualified name when it differs from qname
// (e.g. default-ns "mcpplibs": qname="cmdline", fqname="mcpplibs.cmdline")
auto qnameFile = qname + ".lua";
auto fqnameFile = fqname + ".lua";
if (fqnameFile != qnameFile &&
fqnameFile != candidates.front()) {
candidates.push_back(fqnameFile);
}
// Fallback: bare short name — covers mcpplibs packages whose
// index files are named "<shortName>.lua" without namespace prefix.
if (!ns.empty() && ns != mcpp::pm::kDefaultNamespace) {
candidates.push_back(std::string(shortName) + ".lua");
}
// Fallback: compat.<shortName>.lua — covers compat packages
// when the caller didn't specify the compat namespace.
auto compatPrefix = std::string(mcpp::pm::kCompatNamespace) + ".";
if (ns.empty() || ns == mcpp::pm::kDefaultNamespace) {
candidates.push_back(compatPrefix + std::string(shortName) + ".lua");
}
// Fallback: compat variants for non-default/non-compat namespaces.
if (!ns.empty() && ns != mcpp::pm::kDefaultNamespace
&& ns != mcpp::pm::kCompatNamespace) {
candidates.push_back(compatPrefix + fqname + ".lua");
candidates.push_back(compatPrefix + std::string(shortName) + ".lua");
}
return candidates;
}
// ─── install_path directory candidates (namespace-aware) ─────────────
//
// Given a structured (namespace, shortName) and index name, return the
// list of candidate directory names under <xpkgs>/ to search for.
//
// xlings directory layout: <ns>-x-<ns>.<shortName>/<version>/
// e.g. mcpplibs-x-mcpplibs.tinyhttps/0.2.2/
// compat-x-compat.mbedtls/3.6.1/
inline std::vector<std::string> install_dir_candidates(std::string_view ns,
std::string_view shortName,
std::string_view indexName)
{
std::vector<std::string> candidates;
auto fqname = ns.empty() ? std::string(shortName)
: std::format("{}.{}", ns, shortName);
// xlings names an xpkg directory `{package.namespace}-x-{package.name}` —
// whatever `name` literally says (SPEC-001 §6, verified against all
// installed packages). mcpp does not know the literal here, so it offers
// the two spellings a descriptor can legally use, BOTH canonical:
//
// <ns>-x-<shortName> SPEC-001 form (name = "zlib")
// <ns>-x-<ns>.<shortName> legacy FQN form (name = "compat.zlib")
//
// Short-name first: it is the form new descriptors use, and the one an
// ambiguous hit should resolve toward.
if (!ns.empty()) {
candidates.push_back(std::format("{}-x-{}", ns, shortName));
candidates.push_back(std::format("{}-x-{}", ns, fqname));
}
// ── Fallback candidates (COMPAT, remove in 1.0.0) ──────────────
// Old index-prefixed layout: <index>-x-<ns>.<shortName>
candidates.push_back(std::format("{}-x-{}", indexName, fqname));
// Fallback: compat.<shortName> packages serving default-namespace deps
// such as bare `gtest = "1.15.2"`.
if (ns.empty() || ns == mcpp::pm::kDefaultNamespace) {
candidates.push_back(std::format("compat-x-compat.{}", shortName));
candidates.push_back(std::format("compat-x-{}", shortName));
}
if (!ns.empty()) {
candidates.push_back(std::format("{}-x-{}", indexName, shortName));
}
// Legacy dotted dependency keys such as "mcpplibs.capi.lua" are parsed as
// ns="mcpplibs", shortName="capi.lua". Some xlings indices store these as
// nested namespaces: mcpplibs.capi-x-mcpplibs.capi.lua.
if (!ns.empty()) {
auto dot = shortName.rfind('.');
if (dot != std::string_view::npos) {
auto nestedNs = std::format("{}.{}", ns, shortName.substr(0, dot));
candidates.push_back(std::format("{}-x-{}", nestedNs, fqname));
}
}
return candidates;
}
} // namespace mcpp::pm::compat