Skip to content

Commit 350089e

Browse files
authored
fix(build): support xlings mcpp build dependencies
Support xlings source builds via mcpp, tighten package dependency compatibility, and bump mcpp to 0.0.31.
1 parent e8d792b commit 350089e

20 files changed

Lines changed: 593 additions & 152 deletions

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [0.0.31] — 2026-05-30
7+
8+
### 修复
9+
10+
- 修复 xlings 项目使用 mcpp 构建时 custom index 首次同步、project data
11+
root 查找和 local index 相对路径解析的问题。
12+
- 支持 canonical nested dependency 写法:
13+
`[dependencies] capi.lua = "0.0.3"`
14+
`[dependencies.mcpplibs] capi.lua = "0.0.3"`
15+
- 将 legacy flat dotted dependency key 兼容解析集中到 `mcpp.pm.compat`,
16+
并标注该兼容路径将在 mcpp 1.0.0 移除。
17+
618
## [0.0.14] — 2026-05-13
719

820
LLVM / Clang 工具链支持与 xlings 镜像配置完善。

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "0.0.30"
3+
version = "0.0.31"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/build/compile_commands.cppm

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ std::vector<std::string> split_flags(std::string_view s) {
8181
return out;
8282
}
8383

84+
std::vector<std::string> local_include_args(const CompileUnit& cu) {
85+
std::vector<std::string> args;
86+
args.reserve(cu.localIncludeDirs.size());
87+
for (auto const& inc : cu.localIncludeDirs) {
88+
args.push_back("-I" + inc.string());
89+
}
90+
return args;
91+
}
92+
8493
} // namespace
8594

8695
std::string emit_compile_commands(const BuildPlan& plan, const CompileFlags& flags) {
@@ -96,6 +105,8 @@ std::string emit_compile_commands(const BuildPlan& plan, const CompileFlags& fla
96105
// Build arguments array.
97106
nlohmann::json args = nlohmann::json::array();
98107
args.push_back(compiler.string());
108+
for (auto& f : local_include_args(cu))
109+
args.push_back(std::move(f));
99110
for (auto& f : split_flags(flagStr))
100111
args.push_back(std::move(f));
101112
args.push_back("-c");

src/build/ninja_backend.cppm

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,27 @@ std::string escape_ninja_path(const std::filesystem::path& p) {
7272
return out;
7373
}
7474

75+
std::string escape_flag_path(const std::filesystem::path& p) {
76+
auto s = p.string();
77+
std::string out;
78+
out.reserve(s.size());
79+
for (char c : s) {
80+
if (c == ' ' || c == '$' || c == ':')
81+
out.push_back('$');
82+
out.push_back(c);
83+
}
84+
return out;
85+
}
86+
87+
std::string local_include_flags(const CompileUnit& cu) {
88+
std::string flags;
89+
for (auto const& inc : cu.localIncludeDirs) {
90+
flags += " -I";
91+
flags += escape_flag_path(inc);
92+
}
93+
return flags;
94+
}
95+
7596
void write_file(const std::filesystem::path& p, std::string_view content) {
7697
std::filesystem::create_directories(p.parent_path());
7798
std::ofstream os(p);
@@ -294,13 +315,13 @@ std::string emit_ninja_string(const BuildPlan& plan) {
294315
if constexpr (mcpp::platform::is_windows) {
295316
// Windows: skip BMI restat optimization (requires POSIX shell).
296317
append(std::format(" command = "
297-
"$cxx $cxxflags{} -c $in -o $out\n", module_output_flag));
318+
"$cxx $local_includes $cxxflags{} -c $in -o $out\n", module_output_flag));
298319
} else {
299320
append(std::format(" command = "
300321
"if [ -n \"$bmi_out\" ] && [ -f \"$bmi_out\" ]; then "
301322
"cp -p \"$bmi_out\" \"$bmi_out.bak\"; "
302323
"fi && "
303-
"$cxx $cxxflags{} -c $in -o $out && "
324+
"$cxx $local_includes $cxxflags{} -c $in -o $out && "
304325
"if [ -n \"$bmi_out\" ] && [ -f \"$bmi_out.bak\" ] && "
305326
"cmp -s \"$bmi_out\" \"$bmi_out.bak\"; then "
306327
"mv \"$bmi_out.bak\" \"$bmi_out\"; "
@@ -314,15 +335,15 @@ std::string emit_ninja_string(const BuildPlan& plan) {
314335
append("\n");
315336

316337
append("rule cxx_object\n");
317-
append(" command = $cxx $cxxflags -c $in -o $out\n");
338+
append(" command = $cxx $local_includes $cxxflags -c $in -o $out\n");
318339
append(" description = OBJ $out\n");
319340
if (dyndep)
320341
append(" restat = 1\n");
321342
append("\n");
322343

323344
if (need_c_rule) {
324345
append("rule c_object\n");
325-
append(" command = $cc $cflags -c $in -o $out\n");
346+
append(" command = $cc $local_includes $cflags -c $in -o $out\n");
326347
append(" description = CC $out\n");
327348
if (dyndep)
328349
append(" restat = 1\n");
@@ -348,7 +369,7 @@ std::string emit_ninja_string(const BuildPlan& plan) {
348369
append("rule cxx_scan\n");
349370
if (plan.scanDepsPath.empty()) {
350371
// GCC path: compiler-integrated P1689 scanning.
351-
append(" command = $cxx $cxxflags -fmodules "
372+
append(" command = $cxx $local_includes $cxxflags -fmodules "
352373
"-fdeps-format=p1689r5 "
353374
"-fdeps-file=$out -fdeps-target=$compile_target "
354375
"-M -MM -MF $out.dep -E $in -o $compile_target\n");
@@ -358,10 +379,10 @@ std::string emit_ninja_string(const BuildPlan& plan) {
358379
// Wrap in cmd /c for shell redirection (ninja on Windows uses
359380
// CreateProcess which doesn't interpret > as redirect).
360381
append(" command = cmd /c \"$scan_deps -format=p1689 -- "
361-
"$cxx $cxxflags -c $in -o $compile_target > $out\"\n");
382+
"$cxx $local_includes $cxxflags -c $in -o $compile_target > $out\"\n");
362383
} else {
363384
append(" command = $scan_deps -format=p1689 -- "
364-
"$cxx $cxxflags -c $in -o $compile_target > $out\n");
385+
"$cxx $local_includes $cxxflags -c $in -o $compile_target > $out\n");
365386
}
366387
}
367388
append(" description = SCAN $out\n\n");
@@ -438,6 +459,8 @@ std::string emit_ninja_string(const BuildPlan& plan) {
438459
append(std::format("build {} : cxx_scan {}\n", escape_ninja_path(ddi),
439460
escape_ninja_path(cu.source)));
440461
append(std::format(" compile_target = {}\n", escape_ninja_path(cu.object)));
462+
if (auto includes = local_include_flags(cu); !includes.empty())
463+
append(std::format(" local_includes ={}\n", includes));
441464
}
442465
append("\n");
443466

@@ -481,6 +504,8 @@ std::string emit_ninja_string(const BuildPlan& plan) {
481504
} else {
482505
out_line += "\n";
483506
}
507+
if (auto includes = local_include_flags(cu); !includes.empty())
508+
out_line += " local_includes =" + includes + "\n";
484509
append(std::move(out_line));
485510
}
486511
append("\n");
@@ -519,6 +544,8 @@ std::string emit_ninja_string(const BuildPlan& plan) {
519544
if (!implicit.empty())
520545
out_line += " |" + implicit;
521546
out_line += "\n";
547+
if (auto includes = local_include_flags(cu); !includes.empty())
548+
out_line += " local_includes =" + includes + "\n";
522549
// Clang needs $bmi_out to emit -fmodule-output=$bmi_out
523550
if (cu.providesModule) {
524551
out_line += " bmi_out = " + bmi_path(*cu.providesModule) + "\n";

src/build/plan.cppm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export namespace mcpp::build {
1717
struct CompileUnit {
1818
std::filesystem::path source;
1919
std::filesystem::path object; // relative to plan.outputDir
20+
std::vector<std::filesystem::path> localIncludeDirs;
2021
std::optional<std::string> providesModule; // logical name, if .cppm export
2122
std::vector<std::string> imports; // logical names imported
2223
};
@@ -119,6 +120,7 @@ BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,
119120
auto& u = graph.units[idx];
120121
CompileUnit cu;
121122
cu.source = u.path;
123+
cu.localIncludeDirs = u.localIncludeDirs;
122124
const auto fname = object_filename_for(u.path);
123125
if (basenameCount[fname] > 1) {
124126
// Use <sanitized-pkg>/<parent-dir-name> as prefix to handle

src/cli.cppm

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,42 +1368,27 @@ prepare_build(bool print_fingerprint,
13681368
}
13691369

13701370
// Set up project-level .mcpp/ directory for custom indices.
1371-
// This creates .mcpp/.xlings.json with non-builtin, non-local index
1371+
// This creates .mcpp/.xlings.json with custom non-builtin index
13721372
// entries so xlings can clone them into the project-scoped data dir.
13731373
if (!m->indices.empty()) {
13741374
auto cfg2 = get_cfg();
13751375
if (cfg2) {
13761376
mcpp::config::ensure_project_index_dir(**cfg2, *root, m->indices);
13771377

1378-
// Gap 1: On first build, .mcpp/data/ may be empty because
1378+
// On first build, the project xlings data root may be empty because
13791379
// ensure_project_index_dir only writes .xlings.json but doesn't
1380-
// trigger the actual clone. Check if there are any non-local,
1381-
// non-builtin indices and whether .mcpp/data/ exists with content.
1382-
// If not, run xlings update to clone them before dependency resolution.
1383-
bool hasCustomGitIndices = false;
1380+
// trigger clone/link creation. Check whether there are any custom
1381+
// non-builtin indices and whether the project data roots have index content.
1382+
// If not, run xlings update before dependency resolution.
1383+
bool hasCustomIndices = false;
13841384
for (auto& [idxName, spec] : m->indices) {
1385-
if (!spec.is_local() && !spec.is_builtin()) {
1386-
hasCustomGitIndices = true;
1385+
if (!spec.is_builtin()) {
1386+
hasCustomIndices = true;
13871387
break;
13881388
}
13891389
}
1390-
if (hasCustomGitIndices) {
1391-
auto dataDir = *root / ".mcpp" / "data";
1392-
bool needsClone = !std::filesystem::exists(dataDir);
1393-
if (!needsClone) {
1394-
// Check if data/ has any index directories (dirs with pkgs/ subdir)
1395-
std::error_code ec;
1396-
bool hasIndexRepo = false;
1397-
if (std::filesystem::is_directory(dataDir, ec)) {
1398-
for (auto& entry : std::filesystem::directory_iterator(dataDir, ec)) {
1399-
if (entry.is_directory() && std::filesystem::exists(entry.path() / "pkgs")) {
1400-
hasIndexRepo = true;
1401-
break;
1402-
}
1403-
}
1404-
}
1405-
needsClone = !hasIndexRepo;
1406-
}
1390+
if (hasCustomIndices) {
1391+
bool needsClone = !mcpp::config::project_index_data_initialized(*root);
14071392
if (needsClone) {
14081393
mcpp::ui::status("Fetching", "custom index repos (first use)");
14091394
auto projEnv = mcpp::config::make_project_xlings_env(**cfg2, *root);
@@ -1513,17 +1498,20 @@ prepare_build(bool print_fingerprint,
15131498
// validate the lua exists, then fall through to the normal install
15141499
// flow below.
15151500
if (idxSpec && idxSpec->is_local()) {
1501+
auto indexPath = mcpp::config::resolve_project_index_path(*root, *idxSpec);
15161502
auto luaCheck = mcpp::fetcher::Fetcher::read_xpkg_lua_from_path(
1517-
idxSpec->path, shortName);
1503+
indexPath, shortName);
15181504
if (!luaCheck) return std::unexpected(std::format(
15191505
"dependency '{}': not found in local index at '{}'",
1520-
depName, idxSpec->path.string()));
1506+
depName, indexPath.string()));
15211507
// lua found — fall through to normal install path resolution.
15221508
}
15231509

1524-
// For custom git indices, try project-level .mcpp/data/ first.
1510+
const bool useProjectEnv = idxSpec && !idxSpec->is_builtin();
1511+
1512+
// For custom indices, try project-level xlings data roots first.
15251513
std::optional<std::filesystem::path> installed;
1526-
if (idxSpec && !idxSpec->is_local() && !idxSpec->is_builtin()) {
1514+
if (useProjectEnv) {
15271515
installed = mcpp::fetcher::Fetcher::install_path_from_project_data(
15281516
*root, ns, shortName, version);
15291517
}
@@ -1539,11 +1527,6 @@ prepare_build(bool print_fingerprint,
15391527
: std::format("{}.{}", ns, shortName);
15401528
mcpp::ui::info("Downloading", std::format("{} v{}", fqname, version));
15411529

1542-
// Gap 2: For custom git indices, install using the project-level
1543-
// xlings env so packages land in .mcpp/data/xpkgs/ and the custom
1544-
// index clone is visible to xlings during resolution.
1545-
bool useProjectEnv = idxSpec && !idxSpec->is_local() && !idxSpec->is_builtin();
1546-
15471530
auto install_one = [&](std::string target) -> std::expected<mcpp::xlings::CallResult, mcpp::pm::CallError> {
15481531
if (useProjectEnv) {
15491532
auto projEnv = mcpp::config::make_project_xlings_env(**cfg, *root);
@@ -1559,7 +1542,7 @@ prepare_build(bool print_fingerprint,
15591542
return fetcher.install(targets, &progress);
15601543
};
15611544
auto target = std::format("{}@{}", fqname, version);
1562-
// For custom git indices, use indexName:shortName@version format
1545+
// For custom indices, use indexName:shortName@version format
15631546
// so xlings knows which index to resolve from.
15641547
if (useProjectEnv) {
15651548
target = std::format("{}:{}@{}", ns, shortName, version);
@@ -1800,6 +1783,9 @@ prepare_build(bool print_fingerprint,
18001783
const auto& name = item.name;
18011784
auto& spec = item.spec;
18021785

1786+
mcpp::pm::compat::normalize_nested_namespace(
1787+
spec.namespace_, spec.shortName, spec.legacyDottedKey);
1788+
18031789
// Pin SemVer constraint before dedup/fetch.
18041790
if (auto r = resolveSemver(spec, name); !r) {
18051791
return std::unexpected(r.error());
@@ -2965,7 +2951,7 @@ int cmd_index_update(const mcpplibs::cmdline::ParsedArgs& parsed) {
29652951
} else {
29662952
mcpp::ui::status("Updated", std::format("project index `{}`", idxName));
29672953
}
2968-
break; // ensure_project_index_dir handles all non-local indices at once
2954+
break; // ensure_project_index_dir handles all custom indices at once
29692955
}
29702956
}
29712957
}

src/config.cppm

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,51 @@ mcpp::xlings::Env make_project_xlings_env(const GlobalConfig& cfg,
104104
return { cfg.xlingsBinary, cfg.xlingsHome(), projectDir / ".mcpp" };
105105
}
106106

107+
std::vector<std::filesystem::path>
108+
project_xlings_data_roots(const std::filesystem::path& projectDir)
109+
{
110+
auto dotMcpp = projectDir / ".mcpp";
111+
return {
112+
dotMcpp / "data",
113+
dotMcpp / ".xlings" / "data",
114+
};
115+
}
116+
117+
std::filesystem::path
118+
resolve_project_index_path(const std::filesystem::path& projectDir,
119+
const mcpp::pm::IndexSpec& spec)
120+
{
121+
auto source = spec.path;
122+
if (source.is_relative()) source = projectDir / source;
123+
return source.lexically_normal();
124+
}
125+
126+
bool project_index_data_initialized(const std::filesystem::path& projectDir)
127+
{
128+
std::error_code ec;
129+
for (auto const& data : project_xlings_data_roots(projectDir)) {
130+
auto reposDir = data / "xim-index-repos";
131+
if (std::filesystem::exists(reposDir / "xim-indexrepos.json", ec)) {
132+
return true;
133+
}
134+
if (std::filesystem::is_directory(reposDir, ec)) {
135+
for (auto const& entry : std::filesystem::directory_iterator(reposDir, ec)) {
136+
if (entry.is_directory(ec) && std::filesystem::exists(entry.path() / "pkgs", ec)) {
137+
return true;
138+
}
139+
}
140+
}
141+
if (std::filesystem::is_directory(data, ec)) {
142+
for (auto const& entry : std::filesystem::directory_iterator(data, ec)) {
143+
if (entry.is_directory(ec) && std::filesystem::exists(entry.path() / "pkgs", ec)) {
144+
return true;
145+
}
146+
}
147+
}
148+
}
149+
return false;
150+
}
151+
107152
// Check that the sandbox bootstrap completed successfully.
108153
// Returns empty string if ok, or a description of what's missing.
109154
std::string check_base_init(const GlobalConfig& cfg) {
@@ -160,7 +205,7 @@ void reset_registry(const GlobalConfig& cfg) {
160205
}
161206

162207
// Ensure the project-level .mcpp/ directory exists and contains a
163-
// .xlings.json seeded with the custom (non-builtin, non-local) index
208+
// .xlings.json seeded with custom non-builtin index entries
164209
// entries. Returns true if a .mcpp/ directory was created/updated.
165210
bool ensure_project_index_dir(
166211
const GlobalConfig& cfg,
@@ -613,11 +658,17 @@ bool ensure_project_index_dir(
613658
const std::filesystem::path& projectDir,
614659
const std::map<std::string, mcpp::pm::IndexSpec>& indices)
615660
{
616-
// Collect custom (non-builtin, non-local) indices that need xlings cloning.
661+
// Collect custom non-builtin indices that need xlings project-scope data.
662+
// Local path indices are also seeded so xlings can create its own
663+
// project-local repo link and install packages from that index.
617664
std::vector<std::pair<std::string,std::string>> customRepos;
618665
for (auto& [name, spec] : indices) {
619666
if (spec.is_builtin()) continue;
620-
if (spec.is_local()) continue; // local path, mcpp reads directly
667+
if (spec.is_local()) {
668+
auto source = resolve_project_index_path(projectDir, spec);
669+
customRepos.emplace_back(name, source.string());
670+
continue;
671+
}
621672
customRepos.emplace_back(name, spec.url);
622673
}
623674

0 commit comments

Comments
 (0)