Skip to content

Commit 8300927

Browse files
committed
Add LLVM import std toolchain support
1 parent 9ecd104 commit 8300927

9 files changed

Lines changed: 323 additions & 45 deletions

File tree

src/build/flags.cppm

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ std::filesystem::path derive_c_compiler(const std::filesystem::path& cxxPath) {
5757
return parent / (cc_stem + ext.string());
5858
}
5959

60+
std::filesystem::path staged_std_bmi_path(const BuildPlan& plan) {
61+
if (plan.toolchain.compiler == mcpp::toolchain::CompilerId::Clang)
62+
return plan.outputDir / "pcm.cache" / "std.pcm";
63+
return plan.outputDir / "gcm.cache" / "std.gcm";
64+
}
65+
6066
// Escape a path for embedding in ninja rule strings.
6167
std::string escape_path(const std::filesystem::path& p) {
6268
auto s = p.string();
@@ -152,8 +158,12 @@ CompileFlags compute_flags(const BuildPlan& plan) {
152158

153159
// Assemble
154160
std::string module_flag = isClang ? "" : " -fmodules";
155-
f.cxx = std::format("-std=c++23{}{}{}{}{}{}{}", module_flag, opt_flag, pic_flag,
156-
sysroot_flag, b_flag, include_flags, user_cxxflags);
161+
std::string std_module_flag;
162+
if (isClang && !plan.stdBmiPath.empty()) {
163+
std_module_flag = " -fmodule-file=std=" + escape_path(staged_std_bmi_path(plan));
164+
}
165+
f.cxx = std::format("-std=c++23{}{}{}{}{}{}{}{}", module_flag, std_module_flag,
166+
opt_flag, pic_flag, sysroot_flag, b_flag, include_flags, user_cxxflags);
157167
f.cc = std::format("-std={}{}{}{}{}{}{}", c_std, opt_flag, pic_flag, sysroot_flag, b_flag,
158168
include_flags, user_cflags);
159169

src/build/ninja_backend.cppm

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,10 @@ std::string emit_ninja_string(const BuildPlan& plan) {
277277
append(" restat = 1\n\n");
278278
}
279279

280-
// Stage prebuilt std artifacts into our gcm.cache/
281-
auto std_bmi_dst = std::filesystem::path("gcm.cache") / "std.gcm";
280+
// Stage prebuilt std artifacts into the compiler-specific BMI cache.
281+
auto std_bmi_dst = plan.toolchain.compiler == mcpp::toolchain::CompilerId::Clang
282+
? std::filesystem::path("pcm.cache") / "std.pcm"
283+
: std::filesystem::path("gcm.cache") / "std.gcm";
282284
auto std_o_dst = std::filesystem::path("obj") / "std.o";
283285

284286
bool has_std_artifacts = !plan.stdBmiPath.empty() && !plan.stdObjectPath.empty();
@@ -383,7 +385,7 @@ std::string emit_ninja_string(const BuildPlan& plan) {
383385
for (auto& imp : cu.imports) {
384386
if (imp == "std" || imp == "std.compat") {
385387
if (has_std_artifacts)
386-
implicit += " gcm.cache/std.gcm";
388+
implicit += " " + escape_ninja_path(std_bmi_dst);
387389
continue;
388390
}
389391
implicit += " " + bmi_path(imp);

src/cli.cppm

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import mcpp.pm.resolver; // PR-R4: extracted from cli.cppm
3535
import mcpp.pm.commands; // PR-R5: cmd_add / cmd_remove / cmd_update live here now
3636
import mcpp.pm.mangle; // Level 1 multi-version fallback (cross-major coexistence)
3737
import mcpp.pm.compat; // 0.0.6: namespace field + dotted-name compat shims
38+
import mcpp.pm.dep_spec;
3839
import mcpp.ui;
3940
import mcpp.bmi_cache;
4041
import mcpp.dyndep;
@@ -1348,9 +1349,22 @@ prepare_build(bool print_fingerprint,
13481349
auto fqname = ns.empty() ? shortName
13491350
: std::format("{}.{}", ns, shortName);
13501351
mcpp::ui::info("Downloading", std::format("{} v{}", fqname, version));
1351-
std::vector<std::string> targets{ std::format("{}@{}", fqname, version) };
1352-
CliInstallProgress progress;
1353-
auto r = fetcher.install(targets, &progress);
1352+
auto install_one = [&](std::string target) {
1353+
std::vector<std::string> targets{ std::move(target) };
1354+
CliInstallProgress progress;
1355+
return fetcher.install(targets, &progress);
1356+
};
1357+
auto target = std::format("{}@{}", fqname, version);
1358+
auto r = install_one(target);
1359+
if (r && r->exitCode != 0 &&
1360+
(ns.empty() || ns == mcpp::pm::kDefaultNamespace)) {
1361+
auto compatTarget = std::format("compat.{}@{}", shortName, version);
1362+
if (compatTarget != target) {
1363+
mcpp::ui::info("Downloading", std::format("{} v{}",
1364+
std::format("compat.{}", shortName), version));
1365+
r = install_one(compatTarget);
1366+
}
1367+
}
13541368
if (!r) return std::unexpected(std::format(
13551369
"fetch '{}@{}': {}", depName, version, r.error().message));
13561370
if (r->exitCode != 0) {

src/config.cppm

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ struct GlobalConfig {
4646
// From config.toml [xlings]
4747
std::string xlingsBinaryMode; // "bundled" | "system" | absolute path
4848
std::filesystem::path xlingsHomeOverride; // empty = use registryDir
49+
std::string xlingsMirror = "CN"; // "CN" | "GLOBAL" | empty = xlings default
4950

5051
// From config.toml [index]
5152
std::string defaultIndex; // "mcpplibs"
@@ -174,6 +175,30 @@ void write_file(const std::filesystem::path& p, std::string_view content) {
174175
os << content;
175176
}
176177

178+
std::string normalize_xlings_mirror(std::string mirror) {
179+
for (char& ch : mirror) {
180+
if (ch >= 'a' && ch <= 'z') ch = static_cast<char>(ch - 'a' + 'A');
181+
}
182+
return mirror;
183+
}
184+
185+
std::optional<std::string> read_xlings_json_mirror(const std::filesystem::path& path) {
186+
std::ifstream is(path);
187+
if (!is) return std::nullopt;
188+
std::stringstream ss;
189+
ss << is.rdbuf();
190+
auto content = ss.str();
191+
auto key = content.find("\"mirror\"");
192+
if (key == std::string::npos) return std::nullopt;
193+
auto colon = content.find(':', key);
194+
if (colon == std::string::npos) return std::nullopt;
195+
auto first = content.find('"', colon + 1);
196+
if (first == std::string::npos) return std::nullopt;
197+
auto second = content.find('"', first + 1);
198+
if (second == std::string::npos) return std::nullopt;
199+
return content.substr(first + 1, second - first - 1);
200+
}
201+
177202
bool write_default_config_toml(const std::filesystem::path& path) {
178203
constexpr auto tmpl = R"(# mcpp global config — auto-generated; safe to edit.
179204
@@ -182,6 +207,8 @@ bool write_default_config_toml(const std::filesystem::path& path) {
182207
binary = "bundled"
183208
# home: empty = use $MCPP_HOME/registry; can override
184209
home = ""
210+
# mirror: "CN" | "GLOBAL" | "" (defer to xlings)
211+
mirror = "CN"
185212
186213
[index]
187214
default = "mcpplibs"
@@ -202,7 +229,8 @@ default_backend = "ninja"
202229
}
203230

204231
bool write_default_xlings_json(const std::filesystem::path& path,
205-
const std::vector<IndexRepo>& repos)
232+
const std::vector<IndexRepo>& repos,
233+
std::string_view mirror)
206234
{
207235
// Delegate to xlings module. Convert IndexRepo vec to pair span.
208236
std::vector<std::pair<std::string,std::string>> pairs;
@@ -212,7 +240,7 @@ bool write_default_xlings_json(const std::filesystem::path& path,
212240
// construct a temporary Env with home = path.parent_path().
213241
mcpp::xlings::Env env;
214242
env.home = path.parent_path();
215-
mcpp::xlings::seed_xlings_json(env, pairs);
243+
mcpp::xlings::seed_xlings_json(env, pairs, mirror);
216244
return std::filesystem::exists(path);
217245
}
218246

@@ -356,6 +384,15 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
356384
cfg.xlingsBinaryMode = doc->get_string("xlings.binary").value_or("bundled");
357385
if (auto h = doc->get_string("xlings.home"); h && !h->empty())
358386
cfg.xlingsHomeOverride = *h;
387+
cfg.xlingsMirror = normalize_xlings_mirror(
388+
doc->get_string("xlings.mirror").value_or("CN"));
389+
if (!cfg.xlingsMirror.empty() &&
390+
cfg.xlingsMirror != "CN" &&
391+
cfg.xlingsMirror != "GLOBAL") {
392+
return std::unexpected(ConfigError{
393+
std::format("invalid xlings.mirror '{}': expected CN, GLOBAL, or empty",
394+
cfg.xlingsMirror)});
395+
}
359396
cfg.defaultIndex = doc->get_string("index.default").value_or("mcpplibs");
360397
cfg.searchTtlSeconds = doc->get_int("cache.search_ttl_seconds").value_or(3600);
361398
cfg.defaultJobs = doc->get_int("build.default_jobs").value_or(0);
@@ -387,7 +424,7 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
387424
// 5. Seed registry/.xlings.json if missing
388425
auto xjson = cfg.xlingsHome() / ".xlings.json";
389426
if (!std::filesystem::exists(xjson)) {
390-
write_default_xlings_json(xjson, cfg.indexRepos);
427+
write_default_xlings_json(xjson, cfg.indexRepos, cfg.xlingsMirror);
391428
}
392429

393430
// 6. Acquire xlings binary if needed
@@ -408,6 +445,15 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
408445
"configured xlings binary not found: {}", cfg.xlingsBinary.string())});
409446
}
410447

448+
if (!cfg.xlingsMirror.empty() &&
449+
read_xlings_json_mirror(xjson).value_or("") != cfg.xlingsMirror) {
450+
auto rc = mcpp::xlings::set_mirror(make_xlings_env(cfg), cfg.xlingsMirror, true);
451+
if (rc != 0 && !quiet) {
452+
std::println(stderr,
453+
"warning: failed to set xlings mirror to '{}'", cfg.xlingsMirror);
454+
}
455+
}
456+
411457
// 7. Sandbox bootstrap (mcpp self-contained xlings environment).
412458
// Order matters:
413459
// a. Mirror xlings binary into sandbox so shim creation works.
@@ -429,6 +475,8 @@ void print_env(const GlobalConfig& cfg) {
429475
std::println("MCPP_HOME = {}", cfg.mcppHome.string());
430476
std::println("xlings binary = {}", cfg.xlingsBinary.string());
431477
std::println("xlings home = {}", cfg.xlingsHome().string());
478+
std::println("xlings mirror = {}",
479+
cfg.xlingsMirror.empty() ? "(xlings default)" : cfg.xlingsMirror);
432480
std::println("config = {}", cfg.configFile.string());
433481
std::println("BMI cache = {}", cfg.bmiCacheDir.string());
434482
std::println("meta cache = {}", cfg.metaCacheDir.string());

src/pm/compat.cppm

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,12 @@ inline std::vector<std::string> install_dir_candidates(std::string_view ns,
189189
// Old index-prefixed layout: <index>-x-<ns>.<shortName>
190190
candidates.push_back(std::format("{}-x-{}", indexName, fqname));
191191

192+
// Fallback: compat.<shortName> packages serving default-namespace deps
193+
// such as bare `gtest = "1.15.2"`.
194+
if (ns.empty() || ns == mcpp::pm::kDefaultNamespace) {
195+
candidates.push_back(std::format("compat-x-compat.{}", shortName));
196+
}
197+
192198
// Bare short name variants
193199
if (!ns.empty()) {
194200
candidates.push_back(std::format("{}-x-{}", ns, shortName));

src/toolchain/detect.cppm

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,39 @@ std::string lower_copy(std::string_view s) {
179179
return out;
180180
}
181181

182+
std::string trim_line(std::string s) {
183+
while (!s.empty() && (s.back() == '\n' || s.back() == '\r' || s.back() == ' '))
184+
s.pop_back();
185+
while (!s.empty() && (s.front() == '\n' || s.front() == '\r' || s.front() == ' '))
186+
s.erase(s.begin());
187+
return s;
188+
}
189+
190+
std::optional<std::string>
191+
json_string_value_after(std::string_view body, std::size_t start, std::string_view key) {
192+
auto keyToken = std::string{"\""} + std::string(key) + "\"";
193+
auto keyPos = body.find(keyToken, start);
194+
if (keyPos == std::string_view::npos) return std::nullopt;
195+
196+
auto colon = body.find(':', keyPos + keyToken.size());
197+
if (colon == std::string_view::npos) return std::nullopt;
198+
199+
auto quote = body.find('"', colon + 1);
200+
if (quote == std::string_view::npos) return std::nullopt;
201+
202+
std::string out;
203+
for (std::size_t i = quote + 1; i < body.size(); ++i) {
204+
char c = body[i];
205+
if (c == '"') return out;
206+
if (c == '\\' && i + 1 < body.size()) {
207+
out.push_back(body[++i]);
208+
} else {
209+
out.push_back(c);
210+
}
211+
}
212+
return std::nullopt;
213+
}
214+
182215
} // namespace
183216

184217
std::string compiler_env_prefix(const Toolchain& tc) {
@@ -212,6 +245,47 @@ std::optional<std::filesystem::path> find_std_module_source(
212245
return std::nullopt;
213246
}
214247

248+
std::optional<std::filesystem::path> find_libcxx_std_module_source(
249+
const std::filesystem::path& cxx_binary, const std::string& envPrefix) {
250+
auto manifest_r = run_capture(std::format("{}{} -print-library-module-manifest-path 2>/dev/null",
251+
envPrefix,
252+
mcpp::xlings::shq(cxx_binary.string())));
253+
if (manifest_r) {
254+
auto manifestPath = std::filesystem::path(trim_line(*manifest_r));
255+
if (!manifestPath.empty() && std::filesystem::exists(manifestPath)) {
256+
std::ifstream is(manifestPath);
257+
std::stringstream ss;
258+
ss << is.rdbuf();
259+
auto body = ss.str();
260+
261+
std::size_t cursor = 0;
262+
while (true) {
263+
auto logical = body.find("\"logical-name\"", cursor);
264+
if (logical == std::string::npos) break;
265+
auto name = json_string_value_after(body, logical, "logical-name");
266+
if (name && *name == "std") {
267+
auto src = json_string_value_after(body, logical, "source-path");
268+
if (src) {
269+
std::filesystem::path p = *src;
270+
if (p.is_relative())
271+
p = manifestPath.parent_path() / p;
272+
std::error_code ec;
273+
auto canon = std::filesystem::weakly_canonical(p, ec);
274+
if (!ec) p = canon;
275+
if (std::filesystem::exists(p)) return p;
276+
}
277+
}
278+
cursor = logical + 1;
279+
}
280+
}
281+
}
282+
283+
auto root = cxx_binary.parent_path().parent_path();
284+
auto fallback = root / "share" / "libc++" / "v1" / "std.cppm";
285+
if (std::filesystem::exists(fallback)) return fallback;
286+
return std::nullopt;
287+
}
288+
215289
std::expected<Toolchain, DetectError>
216290
detect(const std::filesystem::path& explicit_compiler) {
217291
Toolchain tc;
@@ -304,6 +378,10 @@ detect(const std::filesystem::path& explicit_compiler) {
304378
tc.stdlibId = "libc++";
305379
tc.stdlibVersion = tc.version.empty() ? "unknown" : tc.version;
306380
tc.linkRuntimeDirs = discover_link_runtime_dirs(tc.binaryPath, tc.targetTriple);
381+
if (auto p = find_libcxx_std_module_source(tc.binaryPath, envPrefix)) {
382+
tc.stdModuleSource = *p;
383+
tc.hasImportStd = true;
384+
}
307385
}
308386

309387
// std module source

0 commit comments

Comments
 (0)