Skip to content

Commit 40c51b9

Browse files
authored
feat(build): support package-owned build metadata
Adds package-owned compile flags and generated files, fixes namespaced local path index lookup, quiets internal xlings update output, and bumps mcpp to 0.0.35.
1 parent b4e429d commit 40c51b9

16 files changed

Lines changed: 603 additions & 21 deletions
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# mcpp 0.0.35: Package-Owned Build Metadata Plan
2+
3+
> 状态: in progress
4+
> 分支: `codex/package-owned-build-flags`
5+
> 目标: 让依赖包在官方 mcpp-index 中携带自己的 C/C++ 编译 flags、include 优先级和配置头,从而让 xlings 不再把第三方 C 库的 configure 结果写在项目根 `mcpp.toml`
6+
7+
## 背景
8+
9+
xlings 现在可以用 `mcpp 0.0.34` 构建,但工程文件里有大量 C 库宏:
10+
11+
- zlib/lua 的平台配置头。
12+
- zstd 的 `ZSTD_DISABLE_ASM`
13+
- xz/liblzma 的 `HAVE_*`、encoder/decoder/check 配置。
14+
- libarchive 的压缩后端和平台 API 配置。
15+
16+
这些宏本质上是 zlib、xz、zstd、libarchive 等包自己的 configure 结果。它们放在 xlings 根 manifest 中会导致:
17+
18+
- 任何消费者都要重复维护同一批宏。
19+
- 包索引无法表达完整构建语义。
20+
- 后续把依赖迁移到官方 mcpp-index 后,仍然删不掉根级 `build.cflags`
21+
22+
## 设计目标
23+
24+
- [x] 依赖包描述中的 `mcpp.cflags` / `mcpp.cxxflags` 能作用到该包自己的 compile units。
25+
- [x] 包级 include dirs 在编译该包源码时优先于全局 include dirs。
26+
- [x] build fingerprint / cache key 包含包级 flags,避免 flags 变化后误用缓存。
27+
- [x] Form B 描述支持 `generated_files`,让官方索引包可以生成少量包私有配置头。
28+
- [x] 本地 `path` 索引读取使用命名空间候选,支持 `pkgs/c/compat.foo.lua` 这类官方索引布局。
29+
- [x] 保持现有项目级 flags 兼容,不破坏已有 manifest。
30+
- [x] 用 xlings 当前项目验证 `mcpp build``mcpp build --target x86_64-linux-musl`
31+
32+
## 预计修改点
33+
34+
- `src/manifest.cppm`
35+
- 保持现有解析入口。
36+
- 若缺少字段,不改变默认行为。
37+
- 解析 `mcpp.generated_files = { ["path"] = "content" }`
38+
- `src/build/plan*.cppm` 或当前 build plan 生成位置
39+
- 在 compile unit 上记录 origin package 或直接保存 package-owned flags。
40+
- `src/build/flags.cppm`
41+
- 生成编译命令时合并 per-unit package flags。
42+
- 合并顺序建议: package local include dirs -> project include dirs -> dependency propagated include dirs -> toolchain/sysroot。
43+
- `src/build/fingerprint*.cppm` 或现有 fingerprint 生成位置
44+
- 加入 package build metadata hash。
45+
- `tests/*`
46+
- 增加一个最小包级 cflags 测试: 依赖包源码必须依赖包内宏才能编译通过,主项目不声明该宏。
47+
- 增加一个最小 Form B `generated_files` 测试: 依赖包源码包含生成头,消费者不声明该头。
48+
49+
## 验证命令
50+
51+
```bash
52+
mcpp test
53+
```
54+
55+
```bash
56+
cd /home/speak/workspace/github/openxlings/xlings
57+
/home/speak/workspace/github/mcpp-community/mcpp/target/x86_64-linux-gnu/*/bin/mcpp build
58+
/home/speak/workspace/github/mcpp-community/mcpp/target/x86_64-linux-gnu/*/bin/mcpp build --target x86_64-linux-musl
59+
```
60+
61+
## Checkpoints
62+
63+
- [x] 文档 checkpoint commit: `9047604`.
64+
- [x] failing test added: `tests/e2e/50_package_owned_build_flags.sh`.
65+
- [x] package-owned flags implementation.
66+
- [x] package-owned generated support files implementation:
67+
- `tests/unit/test_manifest.cpp` 覆盖 Form B `generated_files` 解析。
68+
- `tests/e2e/51_package_generated_files.sh` 覆盖生成文件参与依赖包编译。
69+
- [x] local path index namespace lookup:
70+
- `tests/e2e/52_local_path_namespaced_index.sh` 覆盖 `compat` 命名空间文件名候选。
71+
- [x] xlings local validation:
72+
- `mcpp build` -> `target/x86_64-linux-gnu/ff952c89919589bb/bin/xlings --version` = `xlings 0.4.45`.
73+
- `mcpp build --target x86_64-linux-musl` -> `target/x86_64-linux-musl/7e48a312cd4dbb49/bin/xlings` static ELF.
74+
- [x] PR draft 创建: https://github.com/mcpp-community/mcpp/pull/88
75+
- [x] 版本 bump 到 `0.0.35`,并补充 `CHANGELOG.md` 发布说明。
76+
- [ ] CI 每 120s 检查一次直到完成。
77+
- [ ] CI 通过后发布 `0.0.35`

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,22 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [0.0.35] — 2026-05-30
7+
8+
### 新增
9+
10+
- 支持包描述拥有自己的 `cflags` / `cxxflags`,依赖包源码编译时会继承所属包
11+
的构建宏,消费方项目不再需要集中声明第三方 C 库的私有宏。
12+
- 支持 Form B `mcpp.generated_files`,官方索引包可以在包目录下生成少量配置头,
13+
用于承载平台兼容宏或库私有配置。
14+
15+
### 修复
16+
17+
- 修复本地 `path` 索引读取命名空间包时没有匹配
18+
`pkgs/<prefix>/<namespace>.<name>.lua` 的问题。
19+
- 自定义索引首次同步时保留 mcpp 的 `Fetching custom index repos`
20+
状态提示,但静默 xlings update 的内部逐项输出。
21+
622
## [0.0.33] — 2026-05-30
723

824
### 改进

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.34"
3+
version = "0.0.35"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/build/compile_commands.cppm

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,26 @@ std::vector<std::string> local_include_args(const CompileUnit& cu) {
9090
return args;
9191
}
9292

93+
std::vector<std::string> package_flag_args(const CompileUnit& cu, bool isCSource) {
94+
std::string joined;
95+
auto const& flags = isCSource ? cu.packageCflags : cu.packageCxxflags;
96+
for (auto const& flag : flags) {
97+
joined += ' ';
98+
joined += flag;
99+
}
100+
return split_flags(joined);
101+
}
102+
93103
} // namespace
94104

95105
std::string emit_compile_commands(const BuildPlan& plan, const CompileFlags& flags) {
96106
nlohmann::json entries = nlohmann::json::array();
97107

98108
for (auto& cu : plan.compileUnits) {
99109
// Pick compiler + flags based on source type.
100-
const auto& compiler = is_c_source(cu.source) ? flags.ccBinary : flags.cxxBinary;
101-
const auto& flagStr = is_c_source(cu.source) ? flags.cc : flags.cxx;
110+
const bool isCSource = is_c_source(cu.source);
111+
const auto& compiler = isCSource ? flags.ccBinary : flags.cxxBinary;
112+
const auto& flagStr = isCSource ? flags.cc : flags.cxx;
102113

103114
auto output_path = (plan.outputDir / cu.object).string();
104115

@@ -109,6 +120,8 @@ std::string emit_compile_commands(const BuildPlan& plan, const CompileFlags& fla
109120
args.push_back(std::move(f));
110121
for (auto& f : split_flags(flagStr))
111122
args.push_back(std::move(f));
123+
for (auto& f : package_flag_args(cu, isCSource))
124+
args.push_back(std::move(f));
112125
args.push_back("-c");
113126
args.push_back(cu.source.string());
114127
args.push_back("-o");

src/build/ninja_backend.cppm

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ std::string local_include_flags(const CompileUnit& cu) {
9393
return flags;
9494
}
9595

96+
std::string join_flags(const std::vector<std::string>& flags) {
97+
std::string out;
98+
for (auto const& flag : flags) {
99+
out += ' ';
100+
out += flag;
101+
}
102+
return out;
103+
}
104+
96105
void write_file(const std::filesystem::path& p, std::string_view content) {
97106
std::filesystem::create_directories(p.parent_path());
98107
std::ofstream os(p);
@@ -315,13 +324,13 @@ std::string emit_ninja_string(const BuildPlan& plan) {
315324
if constexpr (mcpp::platform::is_windows) {
316325
// Windows: skip BMI restat optimization (requires POSIX shell).
317326
append(std::format(" command = "
318-
"$cxx $local_includes $cxxflags{} -c $in -o $out\n", module_output_flag));
327+
"$cxx $local_includes $cxxflags $unit_cxxflags{} -c $in -o $out\n", module_output_flag));
319328
} else {
320329
append(std::format(" command = "
321330
"if [ -n \"$bmi_out\" ] && [ -f \"$bmi_out\" ]; then "
322331
"cp -p \"$bmi_out\" \"$bmi_out.bak\"; "
323332
"fi && "
324-
"$cxx $local_includes $cxxflags{} -c $in -o $out && "
333+
"$cxx $local_includes $cxxflags $unit_cxxflags{} -c $in -o $out && "
325334
"if [ -n \"$bmi_out\" ] && [ -f \"$bmi_out.bak\" ] && "
326335
"cmp -s \"$bmi_out\" \"$bmi_out.bak\"; then "
327336
"mv \"$bmi_out.bak\" \"$bmi_out\"; "
@@ -335,15 +344,15 @@ std::string emit_ninja_string(const BuildPlan& plan) {
335344
append("\n");
336345

337346
append("rule cxx_object\n");
338-
append(" command = $cxx $local_includes $cxxflags -c $in -o $out\n");
347+
append(" command = $cxx $local_includes $cxxflags $unit_cxxflags -c $in -o $out\n");
339348
append(" description = OBJ $out\n");
340349
if (dyndep)
341350
append(" restat = 1\n");
342351
append("\n");
343352

344353
if (need_c_rule) {
345354
append("rule c_object\n");
346-
append(" command = $cc $local_includes $cflags -c $in -o $out\n");
355+
append(" command = $cc $local_includes $cflags $unit_cflags -c $in -o $out\n");
347356
append(" description = CC $out\n");
348357
if (dyndep)
349358
append(" restat = 1\n");
@@ -370,6 +379,7 @@ std::string emit_ninja_string(const BuildPlan& plan) {
370379
if (plan.scanDepsPath.empty()) {
371380
// GCC path: compiler-integrated P1689 scanning.
372381
append(" command = $cxx $local_includes $cxxflags -fmodules "
382+
"$unit_cxxflags "
373383
"-fdeps-format=p1689r5 "
374384
"-fdeps-file=$out -fdeps-target=$compile_target "
375385
"-M -MM -MF $out.dep -E $in -o $compile_target\n");
@@ -379,10 +389,10 @@ std::string emit_ninja_string(const BuildPlan& plan) {
379389
// Wrap in cmd /c for shell redirection (ninja on Windows uses
380390
// CreateProcess which doesn't interpret > as redirect).
381391
append(" command = cmd /c \"$scan_deps -format=p1689 -- "
382-
"$cxx $local_includes $cxxflags -c $in -o $compile_target > $out\"\n");
392+
"$cxx $local_includes $cxxflags $unit_cxxflags -c $in -o $compile_target > $out\"\n");
383393
} else {
384394
append(" command = $scan_deps -format=p1689 -- "
385-
"$cxx $local_includes $cxxflags -c $in -o $compile_target > $out\n");
395+
"$cxx $local_includes $cxxflags $unit_cxxflags -c $in -o $compile_target > $out\n");
386396
}
387397
}
388398
append(" description = SCAN $out\n\n");
@@ -461,6 +471,8 @@ std::string emit_ninja_string(const BuildPlan& plan) {
461471
append(std::format(" compile_target = {}\n", escape_ninja_path(cu.object)));
462472
if (auto includes = local_include_flags(cu); !includes.empty())
463473
append(std::format(" local_includes ={}\n", includes));
474+
if (auto flags = join_flags(cu.packageCxxflags); !flags.empty())
475+
append(std::format(" unit_cxxflags ={}\n", flags));
464476
}
465477
append("\n");
466478

@@ -506,6 +518,13 @@ std::string emit_ninja_string(const BuildPlan& plan) {
506518
}
507519
if (auto includes = local_include_flags(cu); !includes.empty())
508520
out_line += " local_includes =" + includes + "\n";
521+
if (is_c_source(cu.source)) {
522+
if (auto flags = join_flags(cu.packageCflags); !flags.empty())
523+
out_line += " unit_cflags =" + flags + "\n";
524+
} else {
525+
if (auto flags = join_flags(cu.packageCxxflags); !flags.empty())
526+
out_line += " unit_cxxflags =" + flags + "\n";
527+
}
509528
append(std::move(out_line));
510529
}
511530
append("\n");
@@ -546,6 +565,13 @@ std::string emit_ninja_string(const BuildPlan& plan) {
546565
out_line += "\n";
547566
if (auto includes = local_include_flags(cu); !includes.empty())
548567
out_line += " local_includes =" + includes + "\n";
568+
if (is_c_source(cu.source)) {
569+
if (auto flags = join_flags(cu.packageCflags); !flags.empty())
570+
out_line += " unit_cflags =" + flags + "\n";
571+
} else {
572+
if (auto flags = join_flags(cu.packageCxxflags); !flags.empty())
573+
out_line += " unit_cxxflags =" + flags + "\n";
574+
}
549575
// Clang needs $bmi_out to emit -fmodule-output=$bmi_out
550576
if (cu.providesModule) {
551577
out_line += " bmi_out = " + bmi_path(*cu.providesModule) + "\n";

src/build/plan.cppm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ struct CompileUnit {
1818
std::filesystem::path source;
1919
std::filesystem::path object; // relative to plan.outputDir
2020
std::vector<std::filesystem::path> localIncludeDirs;
21+
std::vector<std::string> packageCflags;
22+
std::vector<std::string> packageCxxflags;
2123
std::optional<std::string> providesModule; // logical name, if .cppm export
2224
std::vector<std::string> imports; // logical names imported
2325
};
@@ -121,6 +123,8 @@ BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,
121123
CompileUnit cu;
122124
cu.source = u.path;
123125
cu.localIncludeDirs = u.localIncludeDirs;
126+
cu.packageCflags = u.packageCflags;
127+
cu.packageCxxflags = u.packageCxxflags;
124128
const auto fname = object_filename_for(u.path);
125129
if (basenameCount[fname] > 1) {
126130
// Use <sanitized-pkg>/<parent-dir-name> as prefix to handle

0 commit comments

Comments
 (0)