Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d3c5fe6
Bake dependency runtime library_dirs into binary RUNPATH; default Lin…
d2learn-org Jun 3, 2026
af91e99
docs: runtime closure (rpath) + toolchain default design
d2learn-org Jun 3, 2026
6e79919
feat: mcpp new --template gui (imgui.app starter) + package-template …
d2learn-org Jun 3, 2026
074bcdd
feat: mcpp why / resolve --explain + capability-level doctor
d2learn-org Jun 3, 2026
0d537f6
refactor: capability/provider-driven doctor (no platform #ifdef)
d2learn-org Jun 3, 2026
2eb076c
feat: capability-driven ABI enforcement
d2learn-org Jun 3, 2026
388217d
feat: per-build resolution.json manifest artifact
d2learn-org Jun 3, 2026
b17adb9
refactor: resolution.json via mcpp.libs.json (nlohmann), not string c…
d2learn-org Jun 3, 2026
eade49e
feat: build profiles ([profile.<name>] + --profile)
d2learn-org Jun 3, 2026
1b4c117
fix: rpath only dependency runtime dirs; update first-run default test
d2learn-org Jun 3, 2026
acf2d5c
feat: Cargo-style features ([features] + --features + dep features=[.…
d2learn-org Jun 3, 2026
9c61fcc
feat: backend= dep knob, [runtime.<cap>] provider= override, [package…
d2learn-org Jun 3, 2026
4dbd074
fix: first-run default toolchain installs sysroot deps (glibc, linux-…
d2learn-org Jun 3, 2026
db3b47e
fix: first-run gcc default runs the same post-install fixup pipeline
d2learn-org Jun 3, 2026
81d84cf
fix: ownership guard — never fixup payloads inherited from another home
d2learn-org Jun 3, 2026
c7c8ff8
fix: payload probe falls back to the active home registry
d2learn-org Jun 3, 2026
4541b05
fix: validate probed sysroot carries C headers; diagnostic std-precom…
d2learn-org Jun 3, 2026
52c19ef
fix: payload C headers via -idirafter for GCC (include_next reachabil…
d2learn-org Jun 3, 2026
12ab866
fix: payload branch wires link-time C runtime (-B/-L glibc lib)
d2learn-org Jun 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .agents/docs/2026-06-03-package-templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# mcpp 模板系统(package-based templates)— 设计 + TODO

> 2026-06-03 · 状态:builtin 模板已实现;**package 模板为设计/TODO(本轮未实现)**
> 关联:agentdocs/2026-06-03-capability-architecture-rfc.md §9

## 目标

`mcpp new` 的模板不应只有内置几种,而应**复用"库模型":一个库同时携带实现 + 示例 +
可实例化模板**。让库作者把"上手骨架"和库一起分发,消费者一条命令拉起。

```
mcpp new myapp # builtin: bin(默认)
mcpp new myapp --template gui # builtin: imgui.app 窗口骨架(已实现)
mcpp new myapp --template imgui@0.0.2:window # package 模板(本设计)
mcpp new myapp --template imgui:window # 省略版本 = 最新
```

## 两层模型

### 1) builtin 模板(已实现)
- `--template bin|gui`,硬编码在 `src/cli.cppm cmd_new`。
- 用途:无网络/零依赖即可起步;`gui` 给出 imgui.app Tier-0 骨架。
- 这是 fallback,也是 package 模板的"标准库"等价物。

### 2) package 模板(设计 / TODO)
语法:`--template <pkg>[@<ver>]:<templatename>`。

**库侧目录约定**(库仓库里新增 `templates/`):
```
imgui-m/
├── src/ # 库实现
├── examples/ # 可运行示例
└── templates/
└── window/ # 模板名 = 目录名
├── template.toml # 模板元数据(见下)
├── mcpp.toml.in # 带占位符的清单
└── src/main.cpp.in # 带占位符的源码
```

**template.toml**:
```toml
[template]
name = "window"
description = "Minimal imgui.app window app"
# 占位符 → 取值来源
[template.vars]
PROJECT = "{{name}}" # mcpp new 的 name
IMGUI_VER = "{{self.version}}" # 该模板所属包的版本(自动)
# 生成后提示
post_message = "Edit src/main.cpp, then `mcpp run`."
```

**占位符渲染**:`{{name}}`、`{{self.version}}`、`{{self.name}}` 等;`.in` 后缀文件渲染后去掉 `.in`;非 `.in` 文件原样拷贝。

### 解析与执行流程(core)
1. 解析 `--template` 值:
- 不含 `:` → builtin(`bin`/`gui`)。
- 含 `:` → `pkg[@ver]:tmpl`。
2. 经现有 fetcher/index 解析并下载该 `pkg@ver`(复用 `mcpp.pm` / `fetcher.cppm`)。
3. 读取包内 `templates/<tmpl>/template.toml`;若缺失 → 报错并列出该包可用模板(`templates/*/`)。
4. 渲染:对模板目录递归拷贝,`.in` 文件做占位符替换,写入新项目目录。
5. 若模板 mcpp.toml 未声明对该库的依赖,自动注入 `[dependencies] <pkg> = "<ver>"`(让模板默认依赖它所属的库)。
6. 打印 `template.post_message`。

### 代码定位(实现时)
- `src/cli.cppm cmd_new`:解析 `--template`,分流 builtin vs package。
- 新增 `src/scaffold/template.cppm`:模板下载 + 渲染引擎(占位符、`.in` 处理)。
- 复用:`src/fetcher.cppm` / `mcpp.pm.*`(下载包)、`src/manifest.cppm`(注入依赖)。
- index:无需改 schema(模板随源码 tarball 分发,已在 `templates/`)。

### 发现/列举
- `mcpp new --list-templates <pkg>[@ver]`:下载并列出 `templates/*/` 及其 description。
- `mcpp new --template <pkg>:`(空模板名)→ 同上列举提示。

## 为什么这样设计(契合架构不变量)
- I5 复杂度下沉:模板由库作者写一次,消费者一条命令继承。
- I1/I4:`--template gui` builtin 保零配置;package 模板可被 `--list-templates` 解释。
- 与 capability 模型正交:模板只是"起点物料",不改变解析/能力体系。

## TODO(实现顺序)
- [ ] T1 模板字符串解析 `pkg@ver:tmpl`(+ builtin 分流)。
- [ ] T2 `template.cppm` 渲染引擎(`.in` + `{{var}}`)。
- [ ] T3 接 fetcher 下载模板包 + 读取 `templates/<tmpl>/`。
- [ ] T4 自动注入依赖 + post_message。
- [ ] T5 `--list-templates`。
- [ ] T6 imgui-m 仓增 `templates/window/`、`templates/headless/` 作为首批样例。
- [ ] T7 文档 + `mcpp new --help` 更新。

## 现状(本轮已落地)
- builtin `--template bin|gui` 已实现并验证(`mcpp new x --template gui` → imgui.app 窗口骨架 → 直接出窗口)。
- package 模板:本文件为设计与 TODO,留待后续实现。
83 changes: 83 additions & 0 deletions .agents/docs/2026-06-03-runtime-closure-and-toolchain-defaults.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# mcpp core: runtime closure (rpath) + toolchain defaults

> 2026-06-03 · part of the mcpp ecosystem打通 plan
> Master plan: /home/speak/workspace/github/agentdocs/2026-06-03-mcpp-ecosystem-architecture-plan.md

This change fixes two general, long-term issues that block "native + GUI"
packages (e.g. the imgui module package) from working out of the box. Neither
is a special-case for any one package.

## R2 — dependency `[runtime] library_dirs` were dropped from binary RUNPATH

### Symptom
A fresh consumer that depends (transitively) on `compat.glfw` builds fine but
`mcpp run` fails at window creation: `GLX: Failed to load GLX`.

### Root cause (confirmed in source)
- `compat.glx-runtime` (pulled in by `compat.glfw` on Linux) symlinks the host
GLVND/GL/GLX libraries into its install dir and declares
`[runtime] library_dirs = { mcpp_generated/glx_runtime/lib }`.
- `src/build/plan.cppm` (~L220) already collects every dependency package's
`runtime.library_dirs` into `plan.runtimeLibraryDirs` (resolved to absolute).
- BUT `src/build/flags.cppm` (~L258) built the produced binary's RUNPATH by
iterating only `plan.toolchain.linkRuntimeDirs` — i.e. the toolchain's own
runtime dirs. The dependency runtime dirs in `plan.runtimeLibraryDirs` were
never emitted as `-Wl,-rpath`. So the host-GL passthrough dir was not on the
binary's RUNPATH, and the dlopen()'d `libGL.so.1` / `libGLX.so.0` were
unreachable at run time.

The dependency dirs were correctly used for the *build/process* environment but
not baked into the *binary* — so anything reached via dlopen (GL/GLX, and any
plugin-style runtime lib) failed.

### Fix
`src/build/flags.cppm`: iterate `plan.runtimeLibraryDirs` (the union of
dependency runtime dirs + toolchain + payload) instead of
`plan.toolchain.linkRuntimeDirs` when emitting `-L`/`-Wl,-rpath`. This is a
superset, so toolchain dirs are still covered; it additionally bakes each
dependency's declared runtime dir into RUNPATH.

This is the correct general behavior: any package that declares
`[runtime] library_dirs` is promising "binaries that use me need these dirs at
run time"; the producer binary must carry them as RUNPATH.

## R1 — fresh-machine bootstrap default toolchain was musl-static on Linux

### Symptom
On a clean machine, "First run no toolchain configured" auto-installs
`gcc@15.1.0-musl` (musl, static). Building any package that links the glibc
world (X11/GL/system libs) then fails, e.g. `libXdmcp` `arc4random_buf`
implicit-declaration under musl.

### Root cause
`src/cli.cppm` (~L1390) hard-coded the Linux first-run default to
`gcc@15.1.0-musl`.

### Fix
Default Linux first-run toolchain to the platform-native glibc gcc
(`gcc@16.1.0`). musl-static remains fully available but **opt-in** via
`mcpp build --target x86_64-linux-musl` (which the project already supports via
`[target.x86_64-linux-musl]`). This mirrors Cargo/Rust: default triple is
`-gnu` (glibc), `-musl` is an explicit target for portable static binaries.
musl-static is a poor *default* because it cannot link the glibc/native world.

## Why these are long-term/industrial, not workarounds
- R2 makes the existing two-plane design actually work: the *host plane*
(drivers/GLVND, provided by `compat.glx-runtime`, never vendored) is bound to
the binary via RUNPATH, which is the standard ELF mechanism. No package code
changes; no env hacks.
- R1 aligns the default with the platform-native ABI, the same principle Cargo
uses. Static/musl stays a first-class explicit option.

## Test plan (acceptance, via imgui-m, no special-casing)
1. Self-build mcpp with these changes.
2. Fresh consumer: `mcpp new app && mcpp add imgui` then:
- `mcpp build` → uses glibc gcc by default (R1), no musl error.
- `readelf -d <bin>` → RUNPATH contains the `compat.glx-runtime` lib dir (R2).
- `mcpp run` → window opens, ImGui renders, no `GLX: Failed to load GLX`.
3. `mcpp test` headless still passes on all platforms.

## Follow-up (separate, tracked in master plan)
- Declarative `abi` capability on native packages so the resolver *derives* the
ABI-correct toolchain instead of relying on a good default (defense in depth).
- Capability→provider resolution for `opengl.glx.driver` (glvnd/cocoa/win32).
51 changes: 42 additions & 9 deletions src/build/flags.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,25 @@ CompileFlags compute_flags(const BuildPlan& plan) {
}
f.sysroot = link_toolchain_flags;
} else if (plan.toolchain.payloadPaths) {
// No sysroot but have payload paths: use -isystem.
// No usable sysroot: wire the C library headers from the payload.
// For GCC use -idirafter (appended after the built-in dirs) so that
// libstdc++'s #include_next wrappers can reach them; -isystem would
// place them BEFORE the built-ins, invisible to #include_next.
auto& pp = *plan.toolchain.payloadPaths;
compile_toolchain_flags += " -isystem" + escape_path(pp.glibcInclude);
const bool clangTc = mcpp::toolchain::is_clang(plan.toolchain);
auto inc_flag = [&](const std::filesystem::path& p) {
return (clangTc ? " -isystem" : " -idirafter") + escape_path(p);
};
compile_toolchain_flags += inc_flag(pp.glibcInclude);
if (!pp.linuxInclude.empty())
compile_toolchain_flags += " -isystem" + escape_path(pp.linuxInclude);
compile_toolchain_flags += inc_flag(pp.linuxInclude);
// Link-time C runtime: a usable --sysroot would have provided the
// startup objects and core libs implicitly. Without one, point the
// driver at the glibc payload lib dir: -B for crt1.o/crti.o discovery,
// -L for -lm/-lc resolution.
link_toolchain_flags += " -B" + escape_path(pp.glibcLib);
link_toolchain_flags += " -L" + escape_path(pp.glibcLib);
f.sysroot = link_toolchain_flags;
}

// Binutils -B flag
Expand All @@ -198,8 +212,14 @@ CompileFlags compute_flags(const BuildPlan& plan) {
// AR binary
f.arBinary = mcpp::toolchain::archive_tool(plan.toolchain);

// Opt level (musl ICE workaround)
std::string opt_flag = isMuslTc ? " -Og" : " -O2";
// Opt level + debug come from the resolved build profile
// ([profile.<name>] → buildConfig). musl keeps -Og as an ICE workaround
// unless the profile pins -O0.
auto& prof = plan.manifest.buildConfig;
std::string opt_flag = isMuslTc && prof.optLevel != "0"
? " -Og" : (" -O" + prof.optLevel);
if (prof.debug) opt_flag += " -g";
if (prof.lto) opt_flag += " -flto";

// User link flags
std::string user_ldflags;
Expand Down Expand Up @@ -256,10 +276,19 @@ CompileFlags compute_flags(const BuildPlan& plan) {
std::string static_stdlib = (f.staticStdlib && !isClang && !mcpp::platform::is_windows) ? " -static-libstdc++" : "";
std::string runtime_dirs;
if constexpr (mcpp::platform::supports_rpath) {
// Toolchain runtime dirs (glibc/gcc) as before...
for (auto& dir : plan.toolchain.linkRuntimeDirs) {
runtime_dirs += " -L" + escape_path(dir);
runtime_dirs += " -Wl,-rpath," + escape_path(dir);
}
// ...plus dependency packages' [runtime] library_dirs (e.g.
// compat.glx-runtime's host-GL passthrough), so dlopen()'d host libs
// (libGL/libGLX) are reachable at run time. Only the dep dirs — NOT the
// glibc payload dir — so static/musl links stay clean.
for (auto& dir : plan.depRuntimeLibraryDirs) {
runtime_dirs += " -L" + escape_path(dir);
runtime_dirs += " -Wl,-rpath," + escape_path(dir);
}
}

// For Clang with payload paths: add glibc lib + dynamic linker to link flags.
Expand All @@ -273,13 +302,17 @@ CompileFlags compute_flags(const BuildPlan& plan) {
payload_ld += " -Wl,--dynamic-linker=" + escape_path(loader);
}

std::string link_extra;
if (prof.lto) link_extra += " -flto";
if (prof.strip) link_extra += " -s";

if constexpr (mcpp::platform::is_windows) {
f.ld = user_ldflags;
f.ld = user_ldflags + link_extra;
} else if constexpr (mcpp::platform::needs_explicit_libcxx) {
f.ld = std::format("{}{}{} -lc++{}", full_static, static_stdlib, b_flag, user_ldflags);
f.ld = std::format("{}{}{} -lc++{}{}", full_static, static_stdlib, b_flag, user_ldflags, link_extra);
} else {
f.ld = std::format("{}{}{}{}{}{}{}", full_static, static_stdlib, link_toolchain_flags, b_flag,
runtime_dirs, payload_ld, user_ldflags);
f.ld = std::format("{}{}{}{}{}{}{}{}", full_static, static_stdlib, link_toolchain_flags, b_flag,
runtime_dirs, payload_ld, user_ldflags, link_extra);
}

return f;
Expand Down
25 changes: 23 additions & 2 deletions src/build/plan.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ struct BuildPlan {
std::vector<CompileUnit> compileUnits; // topologically sorted
std::vector<LinkUnit> linkUnits;
std::vector<std::filesystem::path> runtimeLibraryDirs;
// ONLY the dependency packages' [runtime] library_dirs (not toolchain/
// payload dirs). These are the dirs that must be baked into the produced
// binary's RUNPATH (e.g. compat.glx-runtime). Kept separate so static/musl
// links don't pull the glibc payload dir.
std::vector<std::filesystem::path> depRuntimeLibraryDirs;
// Aggregated host-runtime requirements from dependency packages'
// [runtime] metadata. Capability/provider-driven — no platform special-casing
// in mcpp: providers (e.g. compat.glx-runtime) declare these per platform.
std::vector<std::string> runtimeDlopenLibs; // union of deps' dlopen sonames
std::vector<std::string> runtimeCapabilities; // union of host capabilities
std::vector<std::pair<std::string, std::string>> runtimeProviders; // (capability, provider pkg)
};

// Build a BuildPlan from already-validated inputs.
Expand Down Expand Up @@ -219,8 +230,18 @@ BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,

for (auto const& package : packages) {
for (auto const& dir : package.manifest.runtimeConfig.libraryDirs) {
append_unique_path(plan.runtimeLibraryDirs,
dir.is_absolute() ? dir : package.root / dir);
auto abs = dir.is_absolute() ? dir : package.root / dir;
append_unique_path(plan.runtimeLibraryDirs, abs);
append_unique_path(plan.depRuntimeLibraryDirs, abs);
}
for (auto const& lib : package.manifest.runtimeConfig.dlopenLibs) {
if (std::ranges::find(plan.runtimeDlopenLibs, lib) == plan.runtimeDlopenLibs.end())
plan.runtimeDlopenLibs.push_back(lib);
}
for (auto const& cap : package.manifest.runtimeConfig.capabilities) {
if (std::ranges::find(plan.runtimeCapabilities, cap) == plan.runtimeCapabilities.end())
plan.runtimeCapabilities.push_back(cap);
plan.runtimeProviders.emplace_back(cap, package.manifest.package.name);
}
}
// The same private runtime directories embedded as executable RUNPATH are
Expand Down
Loading
Loading