From 0b9b094bb9a3d381ee1a63950c13b86671aed0fb Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 6 Aug 2026 23:00:06 +0800 Subject: [PATCH 01/13] =?UTF-8?q?test(grpc):=20=E6=96=B0=E5=A2=9E=20grpc-c?= =?UTF-8?q?odegen=20=E6=88=90=E5=91=98=20=E2=80=94=E2=80=94=20=E4=B8=80?= =?UTF-8?q?=E6=9D=A1=E4=BE=9D=E8=B5=96=E7=BB=8F**=E5=B7=B2=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E7=B4=A2=E5=BC=95**=E6=8B=BF=E5=88=B0=E6=95=B4?= =?UTF-8?q?=E6=9D=A1=E5=B7=A5=E5=85=B7=E9=93=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这是唯一能证明 `reexport = true` 真的经由已发布索引到达消费者的地方:mcpp 自己的 e2e 用的都是 path 依赖,grpc-m 的 examples/greeter 也是(它要测工作树)。 成员只声明: [target.'cfg(linux)'.dependencies.grpc] grpc = { version = "1.83.0", features = ["codegen"] } build.mcpp 一行 `grpcgen::generate_all()`,proto/ 里放一个 echo.proto,断言生成的 **service** stub 能用(`echotest.Echo`)—— 只跑 protoc 而没跑 grpc 插件的话, `Echo::service_full_name()` 根本不存在,所以半配置的 codegen 过不了这条。 它同时覆盖 `rerun_if_changed_glob`:文件清单没有写在任何地方。 顺带改掉两处已被本次推翻的陈述: * grpc-module 里「gRPC's codegen needs host tools mcpp cannot hand a consumer」 —— 自 mcpp 2026.8.6.2 起不再成立,reexport 正是把它们交出去的机制; * grpc.lua 头部还写着「归档换成 v1.83.0-2」,现已是 -3。 本机已跑通:service = echotest.Echo / grpc-codegen: OK。 --- ...ndex-framework-and-site-redesign-design.md | 580 ++++++++++++++++++ mcpp.toml | 1 + pkgs/g/grpc.lua | 7 +- tests/examples/grpc-codegen/build.mcpp | 10 + tests/examples/grpc-codegen/mcpp.toml | 41 ++ tests/examples/grpc-codegen/proto/echo.proto | 9 + tests/examples/grpc-codegen/tests/codegen.cpp | 37 ++ tests/examples/grpc-module/mcpp.toml | 10 +- 8 files changed, 689 insertions(+), 6 deletions(-) create mode 100644 docs/superpowers/specs/2026-08-06-xpkgindex-framework-and-site-redesign-design.md create mode 100644 tests/examples/grpc-codegen/build.mcpp create mode 100644 tests/examples/grpc-codegen/mcpp.toml create mode 100644 tests/examples/grpc-codegen/proto/echo.proto create mode 100644 tests/examples/grpc-codegen/tests/codegen.cpp diff --git a/docs/superpowers/specs/2026-08-06-xpkgindex-framework-and-site-redesign-design.md b/docs/superpowers/specs/2026-08-06-xpkgindex-framework-and-site-redesign-design.md new file mode 100644 index 00000000..b8289ca5 --- /dev/null +++ b/docs/superpowers/specs/2026-08-06-xpkgindex-framework-and-site-redesign-design.md @@ -0,0 +1,580 @@ +# xpkgindex 通用化 + 包索引站整站重做 设计文档 + +- 日期:2026-08-06 +- 涉及仓库:`openxlings/xpkgindex`(框架)、`mcpplibs/mcpp-index`(消费方 + mcpp 插件) +- 关联 issue:[mcpplibs/mcpp-index#170](https://github.com/mcpplibs/mcpp-index/issues/170) 包名与官网不符 +- 线上站点:https://mcpplibs.github.io/mcpp-index/ + +--- + +## 1. 背景与已证实的问题 + +`xpkgindex` 是一个 Python 静态站生成器(jinja2 渲染 + lupa 沙箱执行 `.lua` 描述符), +`mcpp-index` 的 `deploy-site.yml` 通过 `pip install git+…` 拉取它并生成 GitHub Pages 站点。 + +以下问题均已在**线上 `packages.json` 与本地全量解析**上核实,不是推测。 + +### 1.1 namespace 全链路缺失(#170 的根因) + +解析器已读到 `namespace`(`lua_parser.py` 把它写进 `Package.namespace`),但生成端全程只用 `pkg.name`: + +| 位置 | 现状 | 后果 | +|---|---|---| +| `generator.py` `_make_install_command` / `_package_to_json_dict` | `template.format(name=pkg.name)` | `mcpp add json@3.12.0`(应为 `nlohmann.json`)—— #170 | +| `generator.py` 详情页写盘 | `_safe_filename(pkg.name)` | 页面路径按短名 | +| `templates/index.html` | 卡片标题与链接用短名 | 列表出现同名卡片 | + +**页面覆盖是比 #170 更严重的后果。** 线上 `packages.json` 81 条中存在 5 组短名冲突: + +``` +ffmpeg × 3 (compat.ffmpeg / ffmpeg.ffmpeg / mcpplibs.ffmpeg) +imgui × 3 (compat.imgui / ocornut.imgui / mcpplibs.imgui) +llamacpp × 2 (mcpplibs.llamacpp / ggml-org.llamacpp) +lua × 2 (compat.lua / mcpplibs.capi.lua) +opencv × 2 (mcpplibs.opencv / opencv.opencv) +``` + +10 个包塌缩成 5 个 HTML 文件,后写入者覆盖先写入者,**另外 5 个包的详情页在线上不存在**,而首页仍然渲染 +出指向同一 URL 的重复卡片。 + +### 1.2 详情页展示的是 xim 的字段模型,mcpp 的信息几乎没读 + +对 81 个描述符做全量解析后的字段覆盖率: + +| 站点当前渲染的字段 | mcpp-index 覆盖率 | +|---|---| +| `categories` / `keywords` / `authors` / `homepage` / `docs` / `programs` / `archs` / `xvm_enable` | **0%** | +| `status` | 0%(页面上的 `dev` 全部是 model 默认值) | +| `licenses` | 99% | + +| 描述符中真实存在、站点 **完全不展示** 的 | 覆盖率 | +|---|---| +| `mcpp` 扩展块 | 65/81(80%) | +| `mcpp.targets` / `sources` / `language` / `import_std` / `include_dirs` | 78% / 77% / 75% / 75% / 73% | +| `mcpp.deps` | 26%(21 个包) | +| `mcpp.features` | 14% | +| `mcpp.modules` | 9%(7 个包) | +| 每版本 `url` 的 `GLOBAL`/`CN` 双镜像 | 271 / 323 条 | +| 每版本 `sha256` | 323 / 323(100%) | + +> 计数口径:323 是 **version × platform** 条目数;去重后为 **119 个(包, 版本)对**。 +> 首页统计必须用 119,用 323 会把版本数虚报近 3 倍。 + +直接后果: + +- 详情页的 Metadata 区块对 mcpp 的包基本为空,且硬渲染一行 **"XVM Managed: No"**(xim 概念,对 mcpp 无意义)。 +- 首页分类过滤条永远不渲染,hero 显示 **"0 categories"**。 +- **依赖区块对 26% 的包漏报**:解析器只读 `xpm..deps`(全仓仅 5 处),而 21 个包的依赖声明在 `mcpp.deps`。 +- C++ 用户最需要的信息(**这个包怎么接进我的代码**)一条都看不到。 + +### 1.3 安装命令推荐了错误的路径 + +`.xpkgindex.json` 现有: + +```json +"install_commands": { + "unix": "curl -fsSL https://github.com/mcpp-community/mcpp/releases/latest/download/install.sh | bash", + "windows": "iwr https://github.com/mcpp-community/mcpp/releases/latest/download/install.sh -useb | bash" +} +``` + +mcpp README 的权威写法是 **`xlings install mcpp -y` 为主**,裸脚本收进 `
`。且 Windows 那条是把 +shell 脚本灌进 PowerShell,不可用(正确为 `irm https://d2learn.org/xlings-install.ps1.txt | iex`)。 +配置里的 `mcpp-community/mcpp-index` 链接则已陈旧(GitHub 会重定向到 `mcpplibs/mcpp-index`,不是坏链但应更新)。 + +### 1.4 没有任何增长/活动/贡献者信息 + +站点不呈现索引的演进。而这些数据**已经存在于 git 历史中**,只是没被用:`pkgs/` 有 134 个提交、 +从 2026-05-01 的 2 个包长到 2026-08-06 的 81 个包。 + +### 1.5 框架不通用 + +核心 model 里的 `xvm_enable` / `programs` / `archs` 是 xim 生态字段,而 mcpp 的语义无处安放。 +框架与生态耦合,谁都服务不好。 + +--- + +## 2. 目标 / 非目标 + +### 目标 + +1. 修复 namespace 全链路(命令、URL、显示、搜索、去重),并让同类错误**在构建期报错而不是静默覆盖**。 +2. 把 `xpkgindex` 改造成**通用静态包索引框架 + 生态插件**,核心不认识任何具体生态。 +3. 整站重做:视觉基调 B3(代码优先)、首页三段式、详情页两栏。 +4. 呈现索引演进:增长曲线、history line、贡献者(三类)。 +5. 按**未来自建服务器**的形态定型数据契约与 URL,避免二次搬迁。 +6. 贡献指南进站,且不与仓库现有文档分叉。 + +### 非目标 + +- 不引入 Node 构建工具链(Astro/Vite 等)。 +- 本轮不做服务端;只保证契约与路由形态可被服务端原样接管。 +- 不做用户账号、下载统计、评论等需要后端状态的功能。 +- 不重写 lua 描述符格式,不改 `pkgs/**` 的 schema。 + +--- + +## 3. 架构 + +### 3.1 分层 + +``` +描述符源 (.lua) ──► Reader ──► Model ──► 序列化 (index.json) ──► Render (HTML) + │ │ ▲ ▲ + └── Plugin ──┴──────────────┘ │ + (identity / extensions / facets / blocks) ──────┘ +``` + +**四层单向依赖。** `index.json` 是层与层之间的正式契约,也是未来服务端 API 的响应体形态: +静态站是它的第一个消费者,不是唯一消费者。 + +### 3.2 核心 / 插件边界 + +| 归属 | 内容 | +|---|---| +| **核心**(与生态无关) | 描述符发现与读取(内置 xpkg-lua reader)· 身份/slug/URL 生成 · 版本-平台-镜像-校验和模型 · 搜索索引 · git 派生数据(增长曲线 / history line / 贡献者)· GitHub 补全与缓存 · guides(markdown 渲染)· 设计系统与深浅主题 · SEO/sitemap/feed · `index.json` 契约 · 插件加载与失败降级 | +| **插件**(生态特有) | 扩展块解析(`mcpp = {}`)· 仓库级配置(`mcpp.toml` / `index.toml`)· 规范名与安装命令拼法 · 分面轴 · 详情页 Block · 外部补全 · 关联用例代码 | + +**对称性约束:** 如果 mcpp 的语义靠插件,xim 的语义也必须靠插件。核心现有的 +`xvm_enable` / `programs` / `archs` 迁入 `xim` 插件。核心不得再出现任何生态专有字段。 + +### 3.3 URL 形态(现在就按服务端定型) + +| 页面 | URL | +|---|---| +| 首页 | `/` | +| 包详情 | `/packages/./` | +| 统计 | `/stats/` | +| 贡献者 | `/contributors/` | +| 指南 | `/guides//`(多语言:`/guides//zh/`) | +| 关于 | `/about/` | +| 数据 | `/index.json`、`/packages/./index.json` | + +不带 `.html` 扩展名(Pages 上落成目录 + `index.html`)。详情页本轮是**单页两栏**,但目录形态保证 +将来加 `/versions/`、`/build/` 子页不破坏既有链接。 + +--- + +## 4. 核心框架规格 + +### 4.1 身份模型(修复 #170) + +```python +@dataclass(frozen=True) +class Identity: + namespace: str # "nlohmann"、"" 表示无 + name: str # "json" + id: str # "nlohmann.json" —— 规范 ID,namespace 为空时等于 name + slug: str # URL 片段,默认等于 id + display: tuple[str,str] # ("nlohmann.", "json") 供模板弱化前缀渲染 +``` + +- 默认策略:`id = f"{namespace}.{name}" if namespace else name`。 +- 插件可通过 `identity()` 钩子覆盖(mcpp 插件确认此拼法;xim 插件返回 `None` 沿用默认,因为 + `xim-pkgindex` 24 个包**零个**带 namespace,行为不变)。 +- 安装命令模板可用占位符:`{id}`、`{namespace}`、`{name}`、`{version}`。 + mcpp-index 配置为 `mcpp add {id}@{version}`。 + +**唯一性断言(硬失败):** 序列化前校验所有 `slug` 唯一。冲突时构建**报错退出**并列出冲突包与源文件路径。 +1.1 那类静默覆盖不允许再次发生。 + +### 4.2 `index.json` 契约 + +```jsonc +{ + "schema": 1, + "site": { "title": "…", "description": "…", "generated_at": "2026-08-06T…Z", "commit": "312e8b0" }, + "index": { "spec": "1", "min_client": "2026.8.3.3" }, // 由插件的 on_index 填充 + "facets": [ { "key": "surface", "label": "接入方式", + "values": [ {"key":"module","label":"import","count":7}, … ] } ], + "packages": [ + { + "id": "nlohmann.json", "namespace": "nlohmann", "name": "json", "slug": "nlohmann.json", + "description": "…", "licenses": ["MIT"], "repo": "https://github.com/nlohmann/json", + "type": "package", + "install": "mcpp add nlohmann.json@3.12.0", + "latest": "3.12.0", + "platforms": { + "linux": { "versions": ["3.12.0"], "latest": "3.12.0" }, … + }, + "versions": [ + { "version": "3.12.0", "platforms": ["linux","windows","macosx"], + "urls": { "GLOBAL": "https://github.com/…", "CN": "https://gitcode.com/…" }, + "sha256": "4b92eb0c…" } + ], + "deps": ["compat.zlib"], "required_by": ["mcpplibs.llmapi"], + "people": { "upstream": {…}, "descriptor": [ {…} ] }, + "history": [ { "date": "2026-06-27", "kind": "added", "by": "sunrisepeak" } ], + "facets": { "surface": "module" }, + "extensions": { "mcpp": { … } }, // 插件产出,原样透出 + "blocks": [ { "plugin":"mcpp", "kind":"code", "title":"用法", … } ] + } + ] +} +``` + +单包 JSON 与 `packages[]` 的元素同构,便于服务端逐包返回。 +**向后兼容:** 保留 `/packages.json` 作为 schema 0 的别名产物一个发布周期,并在 About 页标注弃用。 + +### 4.3 插件系统 + +**加载。** `.xpkgindex.json`: + +```json +"plugins": ["./.xpkgindex/plugins/mcpp.py"] +``` + +- 仓内 Python 文件为主(零发布流程,跟描述符一起演进)。 +- 同时支持 pip `entry_points` 组 `xpkgindex.plugins`,供通用插件分发。 +- 信任模型不变:构建本来就在索引仓自己的 workflow 里执行仓内的 `.lua` 描述符。 + +**钩子(API v1,六个)。** + +```python +class Plugin: + api_version = 1 + name = "mcpp" + + def on_index(self, ctx: IndexContext) -> None: ... + # 仓库级:读 index.toml / mcpp.toml,写 ctx.index_meta + + def identity(self, raw: dict, path: str) -> Identity | None: ... + # 规范 ID / slug / 安装命令片段;返回 None 表示沿用核心默认 + + def on_package(self, draft: PackageDraft, raw: dict) -> None: ... + # 写 draft.extensions["mcpp"]、draft.facets、draft.deps + + def facets(self) -> list[Facet]: ... + # 声明分面轴及其取值标签(供列表页与搜索) + + def detail_blocks(self, pkg: Package) -> list[Block]: ... + # 详情页结构化区块 + + def enrich_remote(self, pkgs: list[Package], http: HttpCache) -> None: ... + # 构建期外部补全;必须可跳过、可缓存 +``` + +**Block 模型(插件不写 HTML)。** + +```python +Block = { + "kind": "kv" | "code" | "table" | "list" | "graph" | "callout", + "title": str, "collapsed": bool, "data": {...}, + "template": str | None, # 逃生舱:插件自带 Jinja 片段的相对路径 + "styles": str | None, # 逃生舱:scoped CSS +} +``` + +默认走核心的设计系统统一渲染,保证跨索引站视觉一致,且 Block 原样进 JSON; +**逃生舱必须显式声明** `template` / `styles`,核心以 `data-plugin=""` 作用域包裹注入的 CSS, +避免污染全局。 + +**失败降级。** 插件任一钩子抛异常:记录 warning、跳过该插件此次产出、构建继续。 +唯一例外是 `identity()` 返回重复 slug —— 走 4.1 的硬失败。 +`api_version` 不匹配时拒绝加载并 warning。 + +### 4.4 git 派生数据 + +一次 `git log --reverse --name-status --find-renames --date=iso -- ` 遍历,回放 `A/D/R` 维护活跃集合,同时产出三样东西: + +1. **增长曲线** —— 每日活跃包数时间序列。 +2. **history line** —— 全局活动流与每包历史(added / bumped / removed)。 +3. **贡献者** —— 每个描述符的作者集合与首次提交人。 + +**必须回放 A/D/R,不能只数 A。** 已验证:朴素累加 `--diff-filter=A` 得 86,实际 81 —— 差的 5 个来自删除与重命名。 + +**自校验断言:** 时间序列终值必须等于解析出的包数;不等则构建失败并打印差异清单。 + +**CI 要求:** `actions/checkout` 需 `fetch-depth: 0`。浅克隆时降级(跳过曲线与历史,warning),不使构建失败。 + +**身份归并。** git 里 `SPeak ` / `sunrisepeak ` / +`sunrisepeak ` 是同一人(10 个 git 身份 → 约 8 人)。归并顺序: + +1. GitHub commits API 的 `author.login`(权威,同时给头像); +2. `+@users.noreply.github.com` 邮箱解析(134 个提交中 13 个可解析); +3. 配置文件 `.xpkgindex/identities.json` 手工映射(兜底); +4. 都失败则按 `name ` 原样保留。 + +### 4.5 GitHub 补全 + +- 输入:每个包的 `repo` 字段(**100% 覆盖**)+ 配置的 ecosystem 仓库清单。 +- **并非全部在 GitHub**:81 个包的 repo 分布为 GitHub 61、`gitlab.freedesktop.org` 15(X11 那批)、 + `gitlab.com` 1、`sourceware.org` 1。补全按主机分派,非 GitHub 主机当前不拉取(仅显示链接与 owner 名), + 不得静默把它们当作"无上游信息"。GitLab 主机日后可加同形接口。 +- 拉取:上游 owner/org、头像、描述、star、主语言、topics、license、contributors。 +- 用途:补 0% 覆盖的元数据(topics → 分类候选、description → 卡片补充),以及"上游致谢"段。 +- **描述符里手写的字段永远优先**,拉取结果只填空。 +- 结果写入**可提交的缓存文件** `.xpkgindex/cache/github.json`(带 `fetched_at` 与 ETag)。 +- 无 token / 限流 / 离线:使用旧缓存,warning,不失败。缓存缺失则该区块不渲染。 + +### 4.6 guides(贡献指南页) + +配置声明 markdown 源,核心渲染成站内页: + +```json +"guides": { + "nav_label": "贡献", + "entries": [ + { "slug": "contributing", "title": "如何新增一个包", "path": "docs/README.md", + "translations": { "zh": "docs/zh/README.md" } }, + { "slug": "package-types", "title": "四种库形态", "path": "docs/package-types.md", + "translations": { "zh": "docs/zh/package-types.md" } }, + { "slug": "cn-mirror", "title": "CN 镜像闭环", "path": "docs/cn-mirror.md", + "translations": { "zh": "docs/zh/cn-mirror.md" } }, + { "slug": "repository-and-schema", "title": "仓库结构与 schema", "path": "docs/repository-and-schema.md", + "translations": { "zh": "docs/zh/repository-and-schema.md" } } + ] +} +``` + +- **单一真源:** 渲染仓库既有文档,不在站点另写一份,避免分叉。mcpp-index 已有完整双语文档集。 +- 渲染:标题锚点 + 右侧目录 + 代码块高亮 + 语言切换 + 相对链接改写(指向站内 guide 或 GitHub)。 +- 入口:主导航、贡献者页顶部、包详情页"描述符源码"旁的"照着加一个包"。 +- Markdown 依赖:`markdown-it-py`(纯 Python,无 Node)。 + +### 4.7 搜索 + +- 构建期产出精简索引(id / name / namespace / description / facets / 关键词),挂在 `/search-index.json`。 +- 客户端:前缀 + 子串 + namespace 感知匹配(搜 `json` 必须命中 `nlohmann.json`;搜 `nlohmann` 列出其全部包)。 +- 无 JS 时:列表页仍是完整静态 HTML,分面退化为普通链接页(`/?surface=module` 形态由构建期生成静态页)。 +- 服务端化时,同一份查询接口换成 API,前端不改。 + +### 4.8 设计系统与主题 + +- CSS 自定义属性 token 化:颜色 / 间距 / 字号 / 圆角 / 边框 / 阴影,一处定义。 +- 深浅双主题:`prefers-color-scheme` + 手动切换(`data-theme`),两个方向都要显式覆盖。 +- 语义色(接入方式轴):`module`=紫、`header`=石墨、`tool`=琥珀。色不是唯一信息载体,同时带文字标签。 +- 主题可由配置覆盖强调色。 + +### 4.9 SEO / 分发 + +- 每页 `` / `description` / OG / Twitter card。 +- `/sitemap.xml`、`/feed.xml`(新增包与版本更新的 Atom 流)。 +- 旧 URL 兼容见 §7。 + +--- + +## 5. mcpp 插件规格 + +位置:`mcpp-index/.xpkgindex/plugins/mcpp.py`。 + +| 钩子 | 行为 | +|---|---| +| `on_index` | 读 `index.toml` → `spec` / `min_mcpp` / `latest_mcpp`;读根 `mcpp.toml` → workspace 成员清单,建立"包 → 用例工程"索引 | +| `identity` | `id = namespace + "." + name`;安装命令 `mcpp add {id}@{version}` | +| `on_package` | 解析 `mcpp` 字段。**注意它有两种形态**:table(Form B 内联,63 个)或 string(Form A,值是归档内 `mcpp.toml` 的 glob,如 `*/plugin/mcpp.toml`,2 个)。提取 modules / targets / sources / include_dirs / language / import_std / c_standard / features / generated_files / deps。合并 `mcpp.deps` 与 `xpm.<platform>.deps` 两处依赖来源(修复 1.2 的漏报) | +| `facets` | `surface` 轴:`module`(有 `modules`,7)/ `tool`(target kind 含 bin,或无 mcpp 块的工具环境包,18)/ `header`(有 include_dirs 无 modules,53)/ `other`(3)。`namespace` 轴(16 个,compat 56 / mcpplibs 10) | +| `detail_blocks` | ① 接入方式与接口代码(`import X;` / `#include <X>` / `$ tool`)② 用法示例(见下)③ 构建语义 kv ④ features 表 ⑤ sources / include_dirs / generated_files(默认折叠)⑥ 镜像与 sha256 ⑦ min mcpp 兼容性 | +| `enrich_remote` | 不使用(上游补全由核心统一做) | + +**用法示例来源(本设计的关键增量):** 仓库自带 64 个 CI 跑绿的用例工程 +(`tests/examples/*/`,各有 `mcpp.toml` + `tests/*.cpp`)。通过用例工程 `mcpp.toml` 的 +`[dependencies.<ns>]` 反查回包,**36/81 个包能直接关联到至少一个用例**(其余多为 X11/GL 类传递依赖, +本就无独立用例)。详情页展示的是仓库里真在跑的代码,不是编造的片段: + +```cpp +// tests/examples/nlohmann.json/tests/roundtrip.cpp +import std; +import nlohmann.json; +``` + +有用例的包在列表行与详情页标 `✓ 用例`。 + +--- + +## 6. xim 插件规格 + +位置:`xpkgindex` 仓内置示例插件(或 `xim-pkgindex` 自带)。 + +- 迁移核心现有的 `xvm_enable` / `programs` / `archs`,渲染为 kv Block。 +- `identity` 返回 `None`(xim 包无 namespace,行为与今天一致)。 +- 安装命令模板保持 `xlings install {name}@{version}`。 +- 验收:`xim-pkgindex` 的 24 个包生成结果与重构前**逐字节等价**(golden 对比)。 + +--- + +## 7. 页面规格 + +### 7.1 首页(三段式) + +``` +[ nav: packages · stats · contributors · 贡献 · about · 搜索 ] +───────────────────────────────────────────────────────────── +段一 hero 统计(81 packages / 16 namespaces / 119 versions / 36 有用例) + 安装块(见 7.5) +───────────────────────────────────────────────────────────── +段二 脉搏带 [ 增长曲线 ] [ history line ] + 贡献者头像带 → /contributors/ +───────────────────────────────────────────────────────────── +段三 列表 分面:接入方式(import 7 / #include 53 / tool 18)× namespace(compat 56 …) + B3 代码优先行(见 7.2) +``` + +窄屏:脉搏带上下堆叠。 + +### 7.2 列表行(B3 代码优先) + +``` +┌──────────────────────────────────────────────────────────┐ +│ // nlohmann.json 3.12.0 — JSON for Modern C++ │ ← 注释行:包名/版本/描述 +│ import nlohmann.json; │ ← 主体:真正能写的那行代码 +│ ───────────────────────────────────────────────────── │ +│ mcpp add nlohmann.json@3.12.0 MIT · 3 平台 · ✓ 用例 │ ← 脚:安装命令 + 事实 +└──────────────────────────────────────────────────────────┘ +``` + +三档接入方式对应三种主体行:`import X;` / `#include <X>` / `$ tool`,左侧语义色。 +**B3 的已知代价是对新人不友好**(描述退为注释),缓解手段:注释行完整给出包名 + 版本 + 描述, +分面条常驻,搜索框在导航常驻。 + +### 7.3 详情页(两栏) + +| 主栏(叙事) | 侧栏(速查) | +|---|---| +| 标题 `nlohmann.` + `json` + 接入方式徽章 | 最新版本 | +| 接口代码 + `mcpp add …` | License | +| 用法示例(✓ CI 绿) | 平台 | +| 构建语义 Block(mcpp 插件) | 依赖 / 被依赖 | +| 版本 × 平台 × 镜像 × sha256 | min mcpp | +| | 人:上游 / 描述符维护者 | +| | 链接:GitHub · `.lua` 源码 · 用例工程 | +| | 该包 history | + +多版本包(grpc / opencv):版本表默认显示最近 5 个 + "展开全部"; +`sources` / `include_dirs` / `generated_files` 默认折叠。窄屏侧栏折到底部。 + +### 7.4 stats / contributors + +- `/stats/`:大图增长曲线(包数 / 版本条目数 / 命名空间数)+ 完整 history line + 按 namespace 的构成。 +- `/contributors/` 三段(均为**核心能力**,不属于任何插件): + 1. **索引贡献者** —— 谁写了 `pkgs/` 里的描述符。归并后约 8 人,含提交数、贡献包数、参与的包。 + 2. **上游致谢** —— 文案定稿: + > **上游致谢** + > 这些库由上游的作者与团队写就,索引只是把它们接入 mcpp。 + > 81 个包 ← 41 个上游项目 + > + > 点开看这个上游项目在索引里被打成了哪几个包。 + + 计数口径:按 `repo` 的 owner 去重得 45 个,减去本生态自有的 4 个 + (`mcpplibs` 14 包 / `mcpp-community` / `openxlings` / `Sunrisepeak` 2 包)得 41。 + 自有 owner 归入"生态贡献者"段,不进致谢段,避免自我致谢。该分界由配置的 `ecosystem.owners` 决定。 + 3. **生态贡献者(并集)** —— 跨仓去重合并,仓库清单来自配置 `ecosystem` + (`openxlings/xlings` 599★ / `mcpp-community/mcpp` 91★ / `mcpplibs/mcpp-index` / `mcpplibs/*`), + 每人标出参与了哪几个仓。 + +### 7.5 安装块(渐进披露) + +主命令 + 折叠区,与 mcpp README 严格一致: + +``` +安装 mcpp +┌────────────────────────────────────────┐ +│ xlings install mcpp -y [copy] │ ← 主路径 +└────────────────────────────────────────┘ +▸ 还没有 xlings? + Linux / macOS curl -fsSL https://d2learn.org/xlings-install.sh | bash + Windows · PS irm https://d2learn.org/xlings-install.ps1.txt | iex +``` + +配置形态(通用): + +```json +"install": { + "primary": { "label": "安装 mcpp", "command": "xlings install mcpp -y" }, + "fallback": { + "summary": "还没有 xlings?", + "commands": [ + { "os": "Linux / macOS", "command": "curl -fsSL https://d2learn.org/xlings-install.sh | bash" }, + { "os": "Windows · PowerShell", "command": "irm https://d2learn.org/xlings-install.ps1.txt | iex" } + ] + } +} +``` + +### 7.6 旧 URL 兼容 + +现有 `packages/<short>.html` 已被外部引用。为每个旧短名生成 `meta refresh` + `<link rel=canonical>` 的 +alias 页,指向新 URL;短名冲突时(imgui / ffmpeg / lua / opencv / llamacpp)alias 页改为 +**消歧页**,列出该短名下的全部包。alias 不进 sitemap。 + +--- + +## 8. mcpp-index 侧改动 + +1. `.xpkgindex.json` 重写:`plugins`、`install`(7.5)、`guides`(4.6)、`ecosystem`、修正陈旧链接。 +2. 新增 `.xpkgindex/plugins/mcpp.py`(§5)。 +3. `deploy-site.yml`:`fetch-depth: 0`;传入 `GITHUB_TOKEN` 供补全;缓存文件回写策略(拉取失败不中断)。 +4. `.gitignore`:忽略 `.superpowers/`。 +5. 描述符**不改**。0% 覆盖的元数据由构建期补全(§4.5),手写字段永远优先。 + +--- + +## 9. 错误处理与降级矩阵 + +| 情况 | 行为 | +|---|---| +| slug 冲突 | **构建失败**,列出冲突包与路径 | +| 曲线终值 ≠ 包数 | **构建失败**,打印差异清单 | +| 描述符解析失败 | warning + 跳过该包(现状保持),并在构建摘要中汇总条数 | +| 插件加载失败 / 钩子抛异常 | warning + 跳过该插件产出,构建继续 | +| 插件 `api_version` 不匹配 | 拒绝加载 + warning | +| GitHub 无 token / 限流 / 离线 | 用旧缓存;无缓存则不渲染相关区块;不失败 | +| 浅克隆(无完整 git 历史) | 跳过曲线 / history / 贡献者,warning;不失败 | +| guide markdown 缺失 | 跳过该条目 + warning | + +原则:**数据正确性问题硬失败,外部依赖问题软降级。** + +--- + +## 10. 测试策略 + +`xpkgindex` 当前**零测试**,这正是 1.1 的 bug 能活到线上的原因。 + +1. **身份单测**:`(namespace, name)` → `id` / `slug` / 安装命令,覆盖有无 namespace、多段 namespace(`mcpplibs.capi`)。 +2. **唯一性回归**:构造 `compat.imgui` / `ocornut.imgui` / `mcpplibs.imgui` 三包,断言生成三个不同页面。 +3. **曲线自校验**:合成 git 历史(含删除与重命名),断言终值等于包数。 +4. **插件契约**:假插件覆盖全部六个钩子;抛异常的插件不使构建失败。 +5. **golden 站点**:对 `xim-pkgindex` 24 个包生成结果做黄金文件对比(§6 等价性)。 +6. **降级路径**:无 token、浅克隆、缺 guide 各跑一次,断言产物仍完整。 +7. **契约快照**:`index.json` schema 快照测试,字段增删必须显式改基线。 + +--- + +## 11. 分期 + +| 阶段 | 内容 | 产出 | +|---|---|---| +| **P1 修正性** | 身份模型 + 唯一性断言 + `mcpp.deps` 合并 + 安装块修正 + 旧 URL alias + 上述 1/2 号测试 | **#170 关闭**,5 个丢失的详情页回来 | +| **P2 框架化** | 四层拆分 + `index.json` 契约 + 插件系统 + mcpp/xim 插件 + golden 测试 | 通用框架成型,生态字段全部出核心 | +| **P3 站点重做** | 设计系统 + 首页三段式 + B3 列表 + 两栏详情 + 搜索 + SEO | 视觉与信息架构落地 | +| **P4 演进数据** | git 派生数据 + GitHub 补全 + stats / contributors / guides 页 | 曲线、history line、三类贡献者、指南页 | + +P1 可独立发布,不必等待后续阶段。 + +--- + +## 12. 风险与未决 + +| 项 | 说明 | 处置 | +|---|---|---| +| B3 对新人不友好 | 描述退为注释行 | 7.2 的缓解手段;上线后观察,必要时给列表加"详细/紧凑"切换 | +| GitHub API 限流 | 未认证 60 次/时 | CI 用 `GITHUB_TOKEN`;缓存可提交;软降级 | +| `fetch-depth: 0` 成本 | 全量历史克隆 | 当前仓体量可忽略;历史增大后可改用 `--filter=blob:none` | +| 上游 owner ≠ 真实作者 | 镜像仓 / 组织仓 | 文案已按"作者与团队"表述;支持描述符手工覆盖 | +| 页面数增长 | alias + guides 多语言 | 远低于 Pages 1 GB 限制 | +| 插件逃生舱滥用 | 各站视觉分叉 | 逃生舱须显式声明;CSS 作用域隔离;文档中标注为例外路径 | + +--- + +## 13. 设计决策记录 + +| 决策 | 选择 | 理由 | +|---|---|---| +| 构建架构 | Python 单仓静态多页 | Pages 无服务端/无重写规则;SPA 深链对爬虫与分享不可见;避免双工具链 | +| 演进路径 | 数据即 API + 服务端形态 URL | 未来自建服务器时零改链接、前端不重写 | +| 缺失元数据 | 构建期 GitHub 补全 + 描述符可覆盖 | `repo` 100% 覆盖;不必人工回填 81 个描述符 | +| 插件能力 | 结构化 Block + 显式模板逃生舱 | 视觉一致性默认成立,极端定制仍有出口 | +| 视觉基调 | B3 代码优先 | 与"模块/接入方式"主题一致;仓库自带真实用例可支撑 | +| 首页布局 | C 三段式 | 曲线与 history line 都有足够宽度可读 | +| 详情页 | B 两栏 | 事实速查恒定位置;单页保持可加子页 | +| 上游段文案 | 致谢型 | 归属清晰:库属于上游,索引只做接入 | diff --git a/mcpp.toml b/mcpp.toml index 570c9175..3c380a68 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -36,6 +36,7 @@ members = [ "tests/examples/godot-cpp-module", "tests/examples/godot-cpp-module-v10", "tests/examples/godot-cpp-v10", + "tests/examples/grpc-codegen", "tests/examples/grpc-module", "tests/examples/gui-stack", "tests/examples/imgui", diff --git a/pkgs/g/grpc.lua b/pkgs/g/grpc.lua index 3c2cd09c..1d0a4034 100644 --- a/pkgs/g/grpc.lua +++ b/pkgs/g/grpc.lua @@ -6,9 +6,10 @@ -- 索引(#151),满打满算一天,唯一的消费者是本仓的 tests/examples/grpc-module。 -- 为一天的历史留一份永久重复条目不划算。 -- --- 归档换成 v1.83.0-2:`src/` / `include/` / `third_party/` 与 v1.83.0 逐字节相同, --- 多出的只有 plugin/(grpc_cpp_plugin)与 rules/(grpcgen)。v1.83.0 的 sha256 已 --- 经发布过,移动 tag 会让它校验失败(mcpp#349:发布数据不得让程序失效),故另起。 +-- 归档现为 v1.83.0-3。历次重打包中 `src/` / `include/` / `third_party/` 与 +-- v1.83.0 逐字节相同,变的只有 plugin/(grpc_cpp_plugin)、rules/(grpcgen) +-- 与本包 manifest 的 codegen feature。已发布过的 sha256 不能靠移动 tag 复用 +-- (mcpp#349:发布数据不得让程序失效),所以每次重打包都另起一个 tag。 -- Form A descriptor: the public gRPC package ships its own mcpp.toml, so this -- file carries metadata and a download address and nothing else. mcpp's default -- lookup finds <verdir>/*/mcpp.toml inside the GitHub source tarball wrap. diff --git a/tests/examples/grpc-codegen/build.mcpp b/tests/examples/grpc-codegen/build.mcpp new file mode 100644 index 00000000..562eb9fa --- /dev/null +++ b/tests/examples/grpc-codegen/build.mcpp @@ -0,0 +1,10 @@ +// One line, and it finds every .proto under proto/ by itself. +// +// The rule comes from the `grpcgen` package, which this project does NOT +// declare: `grpc = { features = ["codegen"] }` re-exports it, along with +// protoc and grpc_cpp_plugin. Which tools gRPC codegen needs is the gRPC +// package's knowledge. +import mcpp; +import grpcgen; + +int main() { return grpcgen::generate_all() ? 0 : 1; } diff --git a/tests/examples/grpc-codegen/mcpp.toml b/tests/examples/grpc-codegen/mcpp.toml new file mode 100644 index 00000000..41de3ec8 --- /dev/null +++ b/tests/examples/grpc-codegen/mcpp.toml @@ -0,0 +1,41 @@ +# grpc-codegen test project: the ONE-DEPENDENCY form of gRPC codegen. +# +# Its sibling tests/examples/grpc-module covers the module surface with no +# codegen at all. This member covers what that one's header says is impossible: +# +# "NO protoc output anywhere: gRPC's codegen needs host tools mcpp cannot +# hand a consumer" +# +# That stopped being true in mcpp 2026.8.6.2. `features = ["codegen"]` makes +# the grpc package re-export protoc, grpc_cpp_plugin and the grpcgen rule to +# whoever depends on it, so this manifest names ONE package and build.mcpp is +# one line — and the .proto below is compiled from a stub that did not exist +# when the build started. +# +# What this member protects, and what no other member can: +# * `reexport = true` really reaches a consumer through the published index +# (every other test of it is a path dependency in mcpp's own e2e); +# * `rerun_if_changed_glob` really drives `generate_all()` — the file list is +# never written down anywhere; +# * the host tools are built for the BUILD machine and produce stubs that +# link against the runtime this project links, which is the property the +# whole design exists to make inexpressible-to-get-wrong. +# +# linux + macOS only, for the same reason grpc-module is: compat.openssl has no +# windows xpm entry, so on windows this member carries no dependency and the +# test compiles to a no-op main(). +[package] +name = "grpc-codegen-tests" +version = "0.1.0" + +[target.'cfg(linux)'.dependencies.grpc] +grpc = { version = "1.83.0", features = ["codegen"] } + +[target.'cfg(linux)'.build] +cxxflags = ["-DHAVE_GRPC=1"] + +[target.'cfg(macos)'.dependencies.grpc] +grpc = { version = "1.83.0", features = ["codegen"] } + +[target.'cfg(macos)'.build] +cxxflags = ["-DHAVE_GRPC=1"] diff --git a/tests/examples/grpc-codegen/proto/echo.proto b/tests/examples/grpc-codegen/proto/echo.proto new file mode 100644 index 00000000..1d8c3904 --- /dev/null +++ b/tests/examples/grpc-codegen/proto/echo.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; +package echotest; + +message EchoRequest { string text = 1; } +message EchoReply { string text = 1; } + +service Echo { + rpc Say (EchoRequest) returns (EchoReply); +} diff --git a/tests/examples/grpc-codegen/tests/codegen.cpp b/tests/examples/grpc-codegen/tests/codegen.cpp new file mode 100644 index 00000000..747c8751 --- /dev/null +++ b/tests/examples/grpc-codegen/tests/codegen.cpp @@ -0,0 +1,37 @@ +// Asserts the generated stubs exist, compile and carry the SERVICE — a +// protobuf-only run would produce echo.pb.* and no Echo::Service, so a +// half-configured codegen cannot pass this. +// +// HAVE_GRPC comes from this project's own cfg-gated cxxflags: the package is +// linux/macOS-only, so elsewhere this file is an empty main(). +#ifdef HAVE_GRPC + +#include <cstdio> +#include <string> + +#include "echo.pb.h" +#include "echo.grpc.pb.h" + +int main() { + // The message half: a real generated type, round-tripped. + echotest::EchoRequest req; + req.set_text("mcpp"); + std::string wire; + if (!req.SerializeToString(&wire)) return 1; + echotest::EchoRequest back; + if (!back.ParseFromString(wire)) return 1; + if (back.text() != "mcpp") return 1; + + // The SERVICE half — this is what grpc_cpp_plugin produced, and what a + // protoc-only run would not have. + const std::string method = echotest::Echo::service_full_name(); + std::printf("service = %s\n", method.c_str()); + if (method != "echotest.Echo") return 1; + + std::printf("grpc-codegen: OK\n"); + return 0; +} + +#else +int main() { return 0; } +#endif diff --git a/tests/examples/grpc-module/mcpp.toml b/tests/examples/grpc-module/mcpp.toml index 714b9a8d..5ed62037 100644 --- a/tests/examples/grpc-module/mcpp.toml +++ b/tests/examples/grpc-module/mcpp.toml @@ -7,9 +7,13 @@ # resolve from the published index, which is the point: this member proves the # descriptor works against the real dependency set. # -# NO protoc output anywhere: gRPC's codegen needs host tools mcpp cannot hand a -# consumer, so this exercises the module surface instead. grpc-m's own -# examples/helloworld covers the generated-stub path end to end. +# NO protoc output anywhere: this member exercises the MODULE surface. Codegen +# is a separate member, tests/examples/grpc-codegen, which declares one +# dependency and lets the grpc package hand it the whole toolchain. +# +# (This used to read "gRPC's codegen needs host tools mcpp cannot hand a +# consumer". That stopped being true in mcpp 2026.8.6.2 — `reexport = true` +# is exactly the mechanism for handing them over.) [package] name = "grpc-module-tests" version = "0.1.0" From c83b08f498bfb40ed2268704f95dc66d924935d0 Mon Sep 17 00:00:00 2001 From: sunrisepeak <speakshen@163.com> Date: Thu, 6 Aug 2026 23:25:01 +0800 Subject: [PATCH 02/13] =?UTF-8?q?fix(grpc-codegen):=20windows=20=E4=B8=8A?= =?UTF-8?q?=E5=B8=A6=E4=B8=8A=E8=A7=84=E5=88=99=E5=8C=85,=E8=AE=A9?= =?UTF-8?q?=E6=88=90=E5=91=98=E9=80=80=E5=8C=96=E6=88=90=E5=8F=AF=E8=A7=81?= =?UTF-8?q?=E7=9A=84=E8=B7=B3=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI:`build.mcpp:8:8: fatal error: module 'grpcgen' not found`。 成员照抄 grpc-module 把 grpc 依赖放在 cfg(linux)/cfg(macos) 下(compat.openssl 没有 windows 条目),但 grpc-module **没有 build.mcpp**;本成员有,而 `import grpcgen;` 是**编译期**依赖 —— docs/01 §4.2 明令不得用 #if 包裹 import, 所以运行期的守卫救不了它。 grpcgen 是纯 C++23 规则包,自身没有平台受限的依赖,索引里**有 windows 条目**。 因此 windows 上单独声明它:import 成立,守在**调用**上早退。与 tests/examples/protobuf-protoc 的处理完全同形(`if target_os == windows return 0`), 成员照常构建,测试编译成一次可见的跳过。 Linux 本机复验:service = echotest.Echo / grpc-codegen: OK。 --- ...ndex-framework-and-site-redesign-design.md | 580 ------------------ tests/examples/grpc-codegen/build.mcpp | 21 +- tests/examples/grpc-codegen/mcpp.toml | 7 + 3 files changed, 24 insertions(+), 584 deletions(-) delete mode 100644 docs/superpowers/specs/2026-08-06-xpkgindex-framework-and-site-redesign-design.md diff --git a/docs/superpowers/specs/2026-08-06-xpkgindex-framework-and-site-redesign-design.md b/docs/superpowers/specs/2026-08-06-xpkgindex-framework-and-site-redesign-design.md deleted file mode 100644 index b8289ca5..00000000 --- a/docs/superpowers/specs/2026-08-06-xpkgindex-framework-and-site-redesign-design.md +++ /dev/null @@ -1,580 +0,0 @@ -# xpkgindex 通用化 + 包索引站整站重做 设计文档 - -- 日期:2026-08-06 -- 涉及仓库:`openxlings/xpkgindex`(框架)、`mcpplibs/mcpp-index`(消费方 + mcpp 插件) -- 关联 issue:[mcpplibs/mcpp-index#170](https://github.com/mcpplibs/mcpp-index/issues/170) 包名与官网不符 -- 线上站点:https://mcpplibs.github.io/mcpp-index/ - ---- - -## 1. 背景与已证实的问题 - -`xpkgindex` 是一个 Python 静态站生成器(jinja2 渲染 + lupa 沙箱执行 `.lua` 描述符), -`mcpp-index` 的 `deploy-site.yml` 通过 `pip install git+…` 拉取它并生成 GitHub Pages 站点。 - -以下问题均已在**线上 `packages.json` 与本地全量解析**上核实,不是推测。 - -### 1.1 namespace 全链路缺失(#170 的根因) - -解析器已读到 `namespace`(`lua_parser.py` 把它写进 `Package.namespace`),但生成端全程只用 `pkg.name`: - -| 位置 | 现状 | 后果 | -|---|---|---| -| `generator.py` `_make_install_command` / `_package_to_json_dict` | `template.format(name=pkg.name)` | `mcpp add json@3.12.0`(应为 `nlohmann.json`)—— #170 | -| `generator.py` 详情页写盘 | `_safe_filename(pkg.name)` | 页面路径按短名 | -| `templates/index.html` | 卡片标题与链接用短名 | 列表出现同名卡片 | - -**页面覆盖是比 #170 更严重的后果。** 线上 `packages.json` 81 条中存在 5 组短名冲突: - -``` -ffmpeg × 3 (compat.ffmpeg / ffmpeg.ffmpeg / mcpplibs.ffmpeg) -imgui × 3 (compat.imgui / ocornut.imgui / mcpplibs.imgui) -llamacpp × 2 (mcpplibs.llamacpp / ggml-org.llamacpp) -lua × 2 (compat.lua / mcpplibs.capi.lua) -opencv × 2 (mcpplibs.opencv / opencv.opencv) -``` - -10 个包塌缩成 5 个 HTML 文件,后写入者覆盖先写入者,**另外 5 个包的详情页在线上不存在**,而首页仍然渲染 -出指向同一 URL 的重复卡片。 - -### 1.2 详情页展示的是 xim 的字段模型,mcpp 的信息几乎没读 - -对 81 个描述符做全量解析后的字段覆盖率: - -| 站点当前渲染的字段 | mcpp-index 覆盖率 | -|---|---| -| `categories` / `keywords` / `authors` / `homepage` / `docs` / `programs` / `archs` / `xvm_enable` | **0%** | -| `status` | 0%(页面上的 `dev` 全部是 model 默认值) | -| `licenses` | 99% | - -| 描述符中真实存在、站点 **完全不展示** 的 | 覆盖率 | -|---|---| -| `mcpp` 扩展块 | 65/81(80%) | -| `mcpp.targets` / `sources` / `language` / `import_std` / `include_dirs` | 78% / 77% / 75% / 75% / 73% | -| `mcpp.deps` | 26%(21 个包) | -| `mcpp.features` | 14% | -| `mcpp.modules` | 9%(7 个包) | -| 每版本 `url` 的 `GLOBAL`/`CN` 双镜像 | 271 / 323 条 | -| 每版本 `sha256` | 323 / 323(100%) | - -> 计数口径:323 是 **version × platform** 条目数;去重后为 **119 个(包, 版本)对**。 -> 首页统计必须用 119,用 323 会把版本数虚报近 3 倍。 - -直接后果: - -- 详情页的 Metadata 区块对 mcpp 的包基本为空,且硬渲染一行 **"XVM Managed: No"**(xim 概念,对 mcpp 无意义)。 -- 首页分类过滤条永远不渲染,hero 显示 **"0 categories"**。 -- **依赖区块对 26% 的包漏报**:解析器只读 `xpm.<platform>.deps`(全仓仅 5 处),而 21 个包的依赖声明在 `mcpp.deps`。 -- C++ 用户最需要的信息(**这个包怎么接进我的代码**)一条都看不到。 - -### 1.3 安装命令推荐了错误的路径 - -`.xpkgindex.json` 现有: - -```json -"install_commands": { - "unix": "curl -fsSL https://github.com/mcpp-community/mcpp/releases/latest/download/install.sh | bash", - "windows": "iwr https://github.com/mcpp-community/mcpp/releases/latest/download/install.sh -useb | bash" -} -``` - -mcpp README 的权威写法是 **`xlings install mcpp -y` 为主**,裸脚本收进 `<details>`。且 Windows 那条是把 -shell 脚本灌进 PowerShell,不可用(正确为 `irm https://d2learn.org/xlings-install.ps1.txt | iex`)。 -配置里的 `mcpp-community/mcpp-index` 链接则已陈旧(GitHub 会重定向到 `mcpplibs/mcpp-index`,不是坏链但应更新)。 - -### 1.4 没有任何增长/活动/贡献者信息 - -站点不呈现索引的演进。而这些数据**已经存在于 git 历史中**,只是没被用:`pkgs/` 有 134 个提交、 -从 2026-05-01 的 2 个包长到 2026-08-06 的 81 个包。 - -### 1.5 框架不通用 - -核心 model 里的 `xvm_enable` / `programs` / `archs` 是 xim 生态字段,而 mcpp 的语义无处安放。 -框架与生态耦合,谁都服务不好。 - ---- - -## 2. 目标 / 非目标 - -### 目标 - -1. 修复 namespace 全链路(命令、URL、显示、搜索、去重),并让同类错误**在构建期报错而不是静默覆盖**。 -2. 把 `xpkgindex` 改造成**通用静态包索引框架 + 生态插件**,核心不认识任何具体生态。 -3. 整站重做:视觉基调 B3(代码优先)、首页三段式、详情页两栏。 -4. 呈现索引演进:增长曲线、history line、贡献者(三类)。 -5. 按**未来自建服务器**的形态定型数据契约与 URL,避免二次搬迁。 -6. 贡献指南进站,且不与仓库现有文档分叉。 - -### 非目标 - -- 不引入 Node 构建工具链(Astro/Vite 等)。 -- 本轮不做服务端;只保证契约与路由形态可被服务端原样接管。 -- 不做用户账号、下载统计、评论等需要后端状态的功能。 -- 不重写 lua 描述符格式,不改 `pkgs/**` 的 schema。 - ---- - -## 3. 架构 - -### 3.1 分层 - -``` -描述符源 (.lua) ──► Reader ──► Model ──► 序列化 (index.json) ──► Render (HTML) - │ │ ▲ ▲ - └── Plugin ──┴──────────────┘ │ - (identity / extensions / facets / blocks) ──────┘ -``` - -**四层单向依赖。** `index.json` 是层与层之间的正式契约,也是未来服务端 API 的响应体形态: -静态站是它的第一个消费者,不是唯一消费者。 - -### 3.2 核心 / 插件边界 - -| 归属 | 内容 | -|---|---| -| **核心**(与生态无关) | 描述符发现与读取(内置 xpkg-lua reader)· 身份/slug/URL 生成 · 版本-平台-镜像-校验和模型 · 搜索索引 · git 派生数据(增长曲线 / history line / 贡献者)· GitHub 补全与缓存 · guides(markdown 渲染)· 设计系统与深浅主题 · SEO/sitemap/feed · `index.json` 契约 · 插件加载与失败降级 | -| **插件**(生态特有) | 扩展块解析(`mcpp = {}`)· 仓库级配置(`mcpp.toml` / `index.toml`)· 规范名与安装命令拼法 · 分面轴 · 详情页 Block · 外部补全 · 关联用例代码 | - -**对称性约束:** 如果 mcpp 的语义靠插件,xim 的语义也必须靠插件。核心现有的 -`xvm_enable` / `programs` / `archs` 迁入 `xim` 插件。核心不得再出现任何生态专有字段。 - -### 3.3 URL 形态(现在就按服务端定型) - -| 页面 | URL | -|---|---| -| 首页 | `/` | -| 包详情 | `/packages/<namespace>.<name>/` | -| 统计 | `/stats/` | -| 贡献者 | `/contributors/` | -| 指南 | `/guides/<slug>/`(多语言:`/guides/<slug>/zh/`) | -| 关于 | `/about/` | -| 数据 | `/index.json`、`/packages/<namespace>.<name>/index.json` | - -不带 `.html` 扩展名(Pages 上落成目录 + `index.html`)。详情页本轮是**单页两栏**,但目录形态保证 -将来加 `/versions/`、`/build/` 子页不破坏既有链接。 - ---- - -## 4. 核心框架规格 - -### 4.1 身份模型(修复 #170) - -```python -@dataclass(frozen=True) -class Identity: - namespace: str # "nlohmann"、"" 表示无 - name: str # "json" - id: str # "nlohmann.json" —— 规范 ID,namespace 为空时等于 name - slug: str # URL 片段,默认等于 id - display: tuple[str,str] # ("nlohmann.", "json") 供模板弱化前缀渲染 -``` - -- 默认策略:`id = f"{namespace}.{name}" if namespace else name`。 -- 插件可通过 `identity()` 钩子覆盖(mcpp 插件确认此拼法;xim 插件返回 `None` 沿用默认,因为 - `xim-pkgindex` 24 个包**零个**带 namespace,行为不变)。 -- 安装命令模板可用占位符:`{id}`、`{namespace}`、`{name}`、`{version}`。 - mcpp-index 配置为 `mcpp add {id}@{version}`。 - -**唯一性断言(硬失败):** 序列化前校验所有 `slug` 唯一。冲突时构建**报错退出**并列出冲突包与源文件路径。 -1.1 那类静默覆盖不允许再次发生。 - -### 4.2 `index.json` 契约 - -```jsonc -{ - "schema": 1, - "site": { "title": "…", "description": "…", "generated_at": "2026-08-06T…Z", "commit": "312e8b0" }, - "index": { "spec": "1", "min_client": "2026.8.3.3" }, // 由插件的 on_index 填充 - "facets": [ { "key": "surface", "label": "接入方式", - "values": [ {"key":"module","label":"import","count":7}, … ] } ], - "packages": [ - { - "id": "nlohmann.json", "namespace": "nlohmann", "name": "json", "slug": "nlohmann.json", - "description": "…", "licenses": ["MIT"], "repo": "https://github.com/nlohmann/json", - "type": "package", - "install": "mcpp add nlohmann.json@3.12.0", - "latest": "3.12.0", - "platforms": { - "linux": { "versions": ["3.12.0"], "latest": "3.12.0" }, … - }, - "versions": [ - { "version": "3.12.0", "platforms": ["linux","windows","macosx"], - "urls": { "GLOBAL": "https://github.com/…", "CN": "https://gitcode.com/…" }, - "sha256": "4b92eb0c…" } - ], - "deps": ["compat.zlib"], "required_by": ["mcpplibs.llmapi"], - "people": { "upstream": {…}, "descriptor": [ {…} ] }, - "history": [ { "date": "2026-06-27", "kind": "added", "by": "sunrisepeak" } ], - "facets": { "surface": "module" }, - "extensions": { "mcpp": { … } }, // 插件产出,原样透出 - "blocks": [ { "plugin":"mcpp", "kind":"code", "title":"用法", … } ] - } - ] -} -``` - -单包 JSON 与 `packages[]` 的元素同构,便于服务端逐包返回。 -**向后兼容:** 保留 `/packages.json` 作为 schema 0 的别名产物一个发布周期,并在 About 页标注弃用。 - -### 4.3 插件系统 - -**加载。** `.xpkgindex.json`: - -```json -"plugins": ["./.xpkgindex/plugins/mcpp.py"] -``` - -- 仓内 Python 文件为主(零发布流程,跟描述符一起演进)。 -- 同时支持 pip `entry_points` 组 `xpkgindex.plugins`,供通用插件分发。 -- 信任模型不变:构建本来就在索引仓自己的 workflow 里执行仓内的 `.lua` 描述符。 - -**钩子(API v1,六个)。** - -```python -class Plugin: - api_version = 1 - name = "mcpp" - - def on_index(self, ctx: IndexContext) -> None: ... - # 仓库级:读 index.toml / mcpp.toml,写 ctx.index_meta - - def identity(self, raw: dict, path: str) -> Identity | None: ... - # 规范 ID / slug / 安装命令片段;返回 None 表示沿用核心默认 - - def on_package(self, draft: PackageDraft, raw: dict) -> None: ... - # 写 draft.extensions["mcpp"]、draft.facets、draft.deps - - def facets(self) -> list[Facet]: ... - # 声明分面轴及其取值标签(供列表页与搜索) - - def detail_blocks(self, pkg: Package) -> list[Block]: ... - # 详情页结构化区块 - - def enrich_remote(self, pkgs: list[Package], http: HttpCache) -> None: ... - # 构建期外部补全;必须可跳过、可缓存 -``` - -**Block 模型(插件不写 HTML)。** - -```python -Block = { - "kind": "kv" | "code" | "table" | "list" | "graph" | "callout", - "title": str, "collapsed": bool, "data": {...}, - "template": str | None, # 逃生舱:插件自带 Jinja 片段的相对路径 - "styles": str | None, # 逃生舱:scoped CSS -} -``` - -默认走核心的设计系统统一渲染,保证跨索引站视觉一致,且 Block 原样进 JSON; -**逃生舱必须显式声明** `template` / `styles`,核心以 `data-plugin="<name>"` 作用域包裹注入的 CSS, -避免污染全局。 - -**失败降级。** 插件任一钩子抛异常:记录 warning、跳过该插件此次产出、构建继续。 -唯一例外是 `identity()` 返回重复 slug —— 走 4.1 的硬失败。 -`api_version` 不匹配时拒绝加载并 warning。 - -### 4.4 git 派生数据 - -一次 `git log --reverse --name-status --find-renames --date=iso -- <pkgs_dir>` 遍历,回放 `A/D/R` 维护活跃集合,同时产出三样东西: - -1. **增长曲线** —— 每日活跃包数时间序列。 -2. **history line** —— 全局活动流与每包历史(added / bumped / removed)。 -3. **贡献者** —— 每个描述符的作者集合与首次提交人。 - -**必须回放 A/D/R,不能只数 A。** 已验证:朴素累加 `--diff-filter=A` 得 86,实际 81 —— 差的 5 个来自删除与重命名。 - -**自校验断言:** 时间序列终值必须等于解析出的包数;不等则构建失败并打印差异清单。 - -**CI 要求:** `actions/checkout` 需 `fetch-depth: 0`。浅克隆时降级(跳过曲线与历史,warning),不使构建失败。 - -**身份归并。** git 里 `SPeak <speakshen@163.com>` / `sunrisepeak <speakshen@163.com>` / -`sunrisepeak <x.d2learn.org@gmail.com>` 是同一人(10 个 git 身份 → 约 8 人)。归并顺序: - -1. GitHub commits API 的 `author.login`(权威,同时给头像); -2. `<id>+<login>@users.noreply.github.com` 邮箱解析(134 个提交中 13 个可解析); -3. 配置文件 `.xpkgindex/identities.json` 手工映射(兜底); -4. 都失败则按 `name <email>` 原样保留。 - -### 4.5 GitHub 补全 - -- 输入:每个包的 `repo` 字段(**100% 覆盖**)+ 配置的 ecosystem 仓库清单。 -- **并非全部在 GitHub**:81 个包的 repo 分布为 GitHub 61、`gitlab.freedesktop.org` 15(X11 那批)、 - `gitlab.com` 1、`sourceware.org` 1。补全按主机分派,非 GitHub 主机当前不拉取(仅显示链接与 owner 名), - 不得静默把它们当作"无上游信息"。GitLab 主机日后可加同形接口。 -- 拉取:上游 owner/org、头像、描述、star、主语言、topics、license、contributors。 -- 用途:补 0% 覆盖的元数据(topics → 分类候选、description → 卡片补充),以及"上游致谢"段。 -- **描述符里手写的字段永远优先**,拉取结果只填空。 -- 结果写入**可提交的缓存文件** `.xpkgindex/cache/github.json`(带 `fetched_at` 与 ETag)。 -- 无 token / 限流 / 离线:使用旧缓存,warning,不失败。缓存缺失则该区块不渲染。 - -### 4.6 guides(贡献指南页) - -配置声明 markdown 源,核心渲染成站内页: - -```json -"guides": { - "nav_label": "贡献", - "entries": [ - { "slug": "contributing", "title": "如何新增一个包", "path": "docs/README.md", - "translations": { "zh": "docs/zh/README.md" } }, - { "slug": "package-types", "title": "四种库形态", "path": "docs/package-types.md", - "translations": { "zh": "docs/zh/package-types.md" } }, - { "slug": "cn-mirror", "title": "CN 镜像闭环", "path": "docs/cn-mirror.md", - "translations": { "zh": "docs/zh/cn-mirror.md" } }, - { "slug": "repository-and-schema", "title": "仓库结构与 schema", "path": "docs/repository-and-schema.md", - "translations": { "zh": "docs/zh/repository-and-schema.md" } } - ] -} -``` - -- **单一真源:** 渲染仓库既有文档,不在站点另写一份,避免分叉。mcpp-index 已有完整双语文档集。 -- 渲染:标题锚点 + 右侧目录 + 代码块高亮 + 语言切换 + 相对链接改写(指向站内 guide 或 GitHub)。 -- 入口:主导航、贡献者页顶部、包详情页"描述符源码"旁的"照着加一个包"。 -- Markdown 依赖:`markdown-it-py`(纯 Python,无 Node)。 - -### 4.7 搜索 - -- 构建期产出精简索引(id / name / namespace / description / facets / 关键词),挂在 `/search-index.json`。 -- 客户端:前缀 + 子串 + namespace 感知匹配(搜 `json` 必须命中 `nlohmann.json`;搜 `nlohmann` 列出其全部包)。 -- 无 JS 时:列表页仍是完整静态 HTML,分面退化为普通链接页(`/?surface=module` 形态由构建期生成静态页)。 -- 服务端化时,同一份查询接口换成 API,前端不改。 - -### 4.8 设计系统与主题 - -- CSS 自定义属性 token 化:颜色 / 间距 / 字号 / 圆角 / 边框 / 阴影,一处定义。 -- 深浅双主题:`prefers-color-scheme` + 手动切换(`data-theme`),两个方向都要显式覆盖。 -- 语义色(接入方式轴):`module`=紫、`header`=石墨、`tool`=琥珀。色不是唯一信息载体,同时带文字标签。 -- 主题可由配置覆盖强调色。 - -### 4.9 SEO / 分发 - -- 每页 `<title>` / `description` / OG / Twitter card。 -- `/sitemap.xml`、`/feed.xml`(新增包与版本更新的 Atom 流)。 -- 旧 URL 兼容见 §7。 - ---- - -## 5. mcpp 插件规格 - -位置:`mcpp-index/.xpkgindex/plugins/mcpp.py`。 - -| 钩子 | 行为 | -|---|---| -| `on_index` | 读 `index.toml` → `spec` / `min_mcpp` / `latest_mcpp`;读根 `mcpp.toml` → workspace 成员清单,建立"包 → 用例工程"索引 | -| `identity` | `id = namespace + "." + name`;安装命令 `mcpp add {id}@{version}` | -| `on_package` | 解析 `mcpp` 字段。**注意它有两种形态**:table(Form B 内联,63 个)或 string(Form A,值是归档内 `mcpp.toml` 的 glob,如 `*/plugin/mcpp.toml`,2 个)。提取 modules / targets / sources / include_dirs / language / import_std / c_standard / features / generated_files / deps。合并 `mcpp.deps` 与 `xpm.<platform>.deps` 两处依赖来源(修复 1.2 的漏报) | -| `facets` | `surface` 轴:`module`(有 `modules`,7)/ `tool`(target kind 含 bin,或无 mcpp 块的工具环境包,18)/ `header`(有 include_dirs 无 modules,53)/ `other`(3)。`namespace` 轴(16 个,compat 56 / mcpplibs 10) | -| `detail_blocks` | ① 接入方式与接口代码(`import X;` / `#include <X>` / `$ tool`)② 用法示例(见下)③ 构建语义 kv ④ features 表 ⑤ sources / include_dirs / generated_files(默认折叠)⑥ 镜像与 sha256 ⑦ min mcpp 兼容性 | -| `enrich_remote` | 不使用(上游补全由核心统一做) | - -**用法示例来源(本设计的关键增量):** 仓库自带 64 个 CI 跑绿的用例工程 -(`tests/examples/*/`,各有 `mcpp.toml` + `tests/*.cpp`)。通过用例工程 `mcpp.toml` 的 -`[dependencies.<ns>]` 反查回包,**36/81 个包能直接关联到至少一个用例**(其余多为 X11/GL 类传递依赖, -本就无独立用例)。详情页展示的是仓库里真在跑的代码,不是编造的片段: - -```cpp -// tests/examples/nlohmann.json/tests/roundtrip.cpp -import std; -import nlohmann.json; -``` - -有用例的包在列表行与详情页标 `✓ 用例`。 - ---- - -## 6. xim 插件规格 - -位置:`xpkgindex` 仓内置示例插件(或 `xim-pkgindex` 自带)。 - -- 迁移核心现有的 `xvm_enable` / `programs` / `archs`,渲染为 kv Block。 -- `identity` 返回 `None`(xim 包无 namespace,行为与今天一致)。 -- 安装命令模板保持 `xlings install {name}@{version}`。 -- 验收:`xim-pkgindex` 的 24 个包生成结果与重构前**逐字节等价**(golden 对比)。 - ---- - -## 7. 页面规格 - -### 7.1 首页(三段式) - -``` -[ nav: packages · stats · contributors · 贡献 · about · 搜索 ] -───────────────────────────────────────────────────────────── -段一 hero 统计(81 packages / 16 namespaces / 119 versions / 36 有用例) - 安装块(见 7.5) -───────────────────────────────────────────────────────────── -段二 脉搏带 [ 增长曲线 ] [ history line ] - 贡献者头像带 → /contributors/ -───────────────────────────────────────────────────────────── -段三 列表 分面:接入方式(import 7 / #include 53 / tool 18)× namespace(compat 56 …) - B3 代码优先行(见 7.2) -``` - -窄屏:脉搏带上下堆叠。 - -### 7.2 列表行(B3 代码优先) - -``` -┌──────────────────────────────────────────────────────────┐ -│ // nlohmann.json 3.12.0 — JSON for Modern C++ │ ← 注释行:包名/版本/描述 -│ import nlohmann.json; │ ← 主体:真正能写的那行代码 -│ ───────────────────────────────────────────────────── │ -│ mcpp add nlohmann.json@3.12.0 MIT · 3 平台 · ✓ 用例 │ ← 脚:安装命令 + 事实 -└──────────────────────────────────────────────────────────┘ -``` - -三档接入方式对应三种主体行:`import X;` / `#include <X>` / `$ tool`,左侧语义色。 -**B3 的已知代价是对新人不友好**(描述退为注释),缓解手段:注释行完整给出包名 + 版本 + 描述, -分面条常驻,搜索框在导航常驻。 - -### 7.3 详情页(两栏) - -| 主栏(叙事) | 侧栏(速查) | -|---|---| -| 标题 `nlohmann.` + `json` + 接入方式徽章 | 最新版本 | -| 接口代码 + `mcpp add …` | License | -| 用法示例(✓ CI 绿) | 平台 | -| 构建语义 Block(mcpp 插件) | 依赖 / 被依赖 | -| 版本 × 平台 × 镜像 × sha256 | min mcpp | -| | 人:上游 / 描述符维护者 | -| | 链接:GitHub · `.lua` 源码 · 用例工程 | -| | 该包 history | - -多版本包(grpc / opencv):版本表默认显示最近 5 个 + "展开全部"; -`sources` / `include_dirs` / `generated_files` 默认折叠。窄屏侧栏折到底部。 - -### 7.4 stats / contributors - -- `/stats/`:大图增长曲线(包数 / 版本条目数 / 命名空间数)+ 完整 history line + 按 namespace 的构成。 -- `/contributors/` 三段(均为**核心能力**,不属于任何插件): - 1. **索引贡献者** —— 谁写了 `pkgs/` 里的描述符。归并后约 8 人,含提交数、贡献包数、参与的包。 - 2. **上游致谢** —— 文案定稿: - > **上游致谢** - > 这些库由上游的作者与团队写就,索引只是把它们接入 mcpp。 - > 81 个包 ← 41 个上游项目 - > - > 点开看这个上游项目在索引里被打成了哪几个包。 - - 计数口径:按 `repo` 的 owner 去重得 45 个,减去本生态自有的 4 个 - (`mcpplibs` 14 包 / `mcpp-community` / `openxlings` / `Sunrisepeak` 2 包)得 41。 - 自有 owner 归入"生态贡献者"段,不进致谢段,避免自我致谢。该分界由配置的 `ecosystem.owners` 决定。 - 3. **生态贡献者(并集)** —— 跨仓去重合并,仓库清单来自配置 `ecosystem` - (`openxlings/xlings` 599★ / `mcpp-community/mcpp` 91★ / `mcpplibs/mcpp-index` / `mcpplibs/*`), - 每人标出参与了哪几个仓。 - -### 7.5 安装块(渐进披露) - -主命令 + 折叠区,与 mcpp README 严格一致: - -``` -安装 mcpp -┌────────────────────────────────────────┐ -│ xlings install mcpp -y [copy] │ ← 主路径 -└────────────────────────────────────────┘ -▸ 还没有 xlings? - Linux / macOS curl -fsSL https://d2learn.org/xlings-install.sh | bash - Windows · PS irm https://d2learn.org/xlings-install.ps1.txt | iex -``` - -配置形态(通用): - -```json -"install": { - "primary": { "label": "安装 mcpp", "command": "xlings install mcpp -y" }, - "fallback": { - "summary": "还没有 xlings?", - "commands": [ - { "os": "Linux / macOS", "command": "curl -fsSL https://d2learn.org/xlings-install.sh | bash" }, - { "os": "Windows · PowerShell", "command": "irm https://d2learn.org/xlings-install.ps1.txt | iex" } - ] - } -} -``` - -### 7.6 旧 URL 兼容 - -现有 `packages/<short>.html` 已被外部引用。为每个旧短名生成 `meta refresh` + `<link rel=canonical>` 的 -alias 页,指向新 URL;短名冲突时(imgui / ffmpeg / lua / opencv / llamacpp)alias 页改为 -**消歧页**,列出该短名下的全部包。alias 不进 sitemap。 - ---- - -## 8. mcpp-index 侧改动 - -1. `.xpkgindex.json` 重写:`plugins`、`install`(7.5)、`guides`(4.6)、`ecosystem`、修正陈旧链接。 -2. 新增 `.xpkgindex/plugins/mcpp.py`(§5)。 -3. `deploy-site.yml`:`fetch-depth: 0`;传入 `GITHUB_TOKEN` 供补全;缓存文件回写策略(拉取失败不中断)。 -4. `.gitignore`:忽略 `.superpowers/`。 -5. 描述符**不改**。0% 覆盖的元数据由构建期补全(§4.5),手写字段永远优先。 - ---- - -## 9. 错误处理与降级矩阵 - -| 情况 | 行为 | -|---|---| -| slug 冲突 | **构建失败**,列出冲突包与路径 | -| 曲线终值 ≠ 包数 | **构建失败**,打印差异清单 | -| 描述符解析失败 | warning + 跳过该包(现状保持),并在构建摘要中汇总条数 | -| 插件加载失败 / 钩子抛异常 | warning + 跳过该插件产出,构建继续 | -| 插件 `api_version` 不匹配 | 拒绝加载 + warning | -| GitHub 无 token / 限流 / 离线 | 用旧缓存;无缓存则不渲染相关区块;不失败 | -| 浅克隆(无完整 git 历史) | 跳过曲线 / history / 贡献者,warning;不失败 | -| guide markdown 缺失 | 跳过该条目 + warning | - -原则:**数据正确性问题硬失败,外部依赖问题软降级。** - ---- - -## 10. 测试策略 - -`xpkgindex` 当前**零测试**,这正是 1.1 的 bug 能活到线上的原因。 - -1. **身份单测**:`(namespace, name)` → `id` / `slug` / 安装命令,覆盖有无 namespace、多段 namespace(`mcpplibs.capi`)。 -2. **唯一性回归**:构造 `compat.imgui` / `ocornut.imgui` / `mcpplibs.imgui` 三包,断言生成三个不同页面。 -3. **曲线自校验**:合成 git 历史(含删除与重命名),断言终值等于包数。 -4. **插件契约**:假插件覆盖全部六个钩子;抛异常的插件不使构建失败。 -5. **golden 站点**:对 `xim-pkgindex` 24 个包生成结果做黄金文件对比(§6 等价性)。 -6. **降级路径**:无 token、浅克隆、缺 guide 各跑一次,断言产物仍完整。 -7. **契约快照**:`index.json` schema 快照测试,字段增删必须显式改基线。 - ---- - -## 11. 分期 - -| 阶段 | 内容 | 产出 | -|---|---|---| -| **P1 修正性** | 身份模型 + 唯一性断言 + `mcpp.deps` 合并 + 安装块修正 + 旧 URL alias + 上述 1/2 号测试 | **#170 关闭**,5 个丢失的详情页回来 | -| **P2 框架化** | 四层拆分 + `index.json` 契约 + 插件系统 + mcpp/xim 插件 + golden 测试 | 通用框架成型,生态字段全部出核心 | -| **P3 站点重做** | 设计系统 + 首页三段式 + B3 列表 + 两栏详情 + 搜索 + SEO | 视觉与信息架构落地 | -| **P4 演进数据** | git 派生数据 + GitHub 补全 + stats / contributors / guides 页 | 曲线、history line、三类贡献者、指南页 | - -P1 可独立发布,不必等待后续阶段。 - ---- - -## 12. 风险与未决 - -| 项 | 说明 | 处置 | -|---|---|---| -| B3 对新人不友好 | 描述退为注释行 | 7.2 的缓解手段;上线后观察,必要时给列表加"详细/紧凑"切换 | -| GitHub API 限流 | 未认证 60 次/时 | CI 用 `GITHUB_TOKEN`;缓存可提交;软降级 | -| `fetch-depth: 0` 成本 | 全量历史克隆 | 当前仓体量可忽略;历史增大后可改用 `--filter=blob:none` | -| 上游 owner ≠ 真实作者 | 镜像仓 / 组织仓 | 文案已按"作者与团队"表述;支持描述符手工覆盖 | -| 页面数增长 | alias + guides 多语言 | 远低于 Pages 1 GB 限制 | -| 插件逃生舱滥用 | 各站视觉分叉 | 逃生舱须显式声明;CSS 作用域隔离;文档中标注为例外路径 | - ---- - -## 13. 设计决策记录 - -| 决策 | 选择 | 理由 | -|---|---|---| -| 构建架构 | Python 单仓静态多页 | Pages 无服务端/无重写规则;SPA 深链对爬虫与分享不可见;避免双工具链 | -| 演进路径 | 数据即 API + 服务端形态 URL | 未来自建服务器时零改链接、前端不重写 | -| 缺失元数据 | 构建期 GitHub 补全 + 描述符可覆盖 | `repo` 100% 覆盖;不必人工回填 81 个描述符 | -| 插件能力 | 结构化 Block + 显式模板逃生舱 | 视觉一致性默认成立,极端定制仍有出口 | -| 视觉基调 | B3 代码优先 | 与"模块/接入方式"主题一致;仓库自带真实用例可支撑 | -| 首页布局 | C 三段式 | 曲线与 history line 都有足够宽度可读 | -| 详情页 | B 两栏 | 事实速查恒定位置;单页保持可加子页 | -| 上游段文案 | 致谢型 | 归属清晰:库属于上游,索引只做接入 | diff --git a/tests/examples/grpc-codegen/build.mcpp b/tests/examples/grpc-codegen/build.mcpp index 562eb9fa..9ec8d70f 100644 --- a/tests/examples/grpc-codegen/build.mcpp +++ b/tests/examples/grpc-codegen/build.mcpp @@ -1,10 +1,23 @@ // One line, and it finds every .proto under proto/ by itself. // // The rule comes from the `grpcgen` package, which this project does NOT -// declare: `grpc = { features = ["codegen"] }` re-exports it, along with -// protoc and grpc_cpp_plugin. Which tools gRPC codegen needs is the gRPC -// package's knowledge. +// declare on linux/macOS: `grpc = { features = ["codegen"] }` re-exports it, +// along with protoc and grpc_cpp_plugin. Which tools gRPC codegen needs is the +// gRPC package's knowledge. +import std; import mcpp; import grpcgen; -int main() { return grpcgen::generate_all() ? 0 : 1; } +int main() { + // No gRPC on windows — compat.openssl has no windows xpm entry, so the + // grpc package is linux/macOS only and there is nothing to generate. The + // guard is on the CALL, not the import: `import grpcgen;` is a + // compile-time dependency and cannot be conditional (docs/01 §4.2 forbids + // #if-guarded imports), so the module is declared on windows too — it is a + // pure C++23 rule package with no platform-limited dependencies of its own. + // + // Same shape as tests/examples/protobuf-protoc: return 0 so the member + // still builds, and let the test compile to a visible skip. + if (std::string_view(mcpp::target_os()) == "windows") return 0; + return grpcgen::generate_all() ? 0 : 1; +} diff --git a/tests/examples/grpc-codegen/mcpp.toml b/tests/examples/grpc-codegen/mcpp.toml index 41de3ec8..c7493f4e 100644 --- a/tests/examples/grpc-codegen/mcpp.toml +++ b/tests/examples/grpc-codegen/mcpp.toml @@ -39,3 +39,10 @@ grpc = { version = "1.83.0", features = ["codegen"] } [target.'cfg(macos)'.build] cxxflags = ["-DHAVE_GRPC=1"] + +# windows carries the RULE but not gRPC itself, so build.mcpp compiles (its +# `import grpcgen;` cannot be conditional) and then returns early. grpcgen is a +# pure C++23 rule package — no openssl, no platform-limited dependency — which +# is why it is available here while grpc is not. +[target.'cfg(windows)'.dependencies.mcpplibs] +grpcgen = { version = "1.83.0", host-module = true } From a016090201402b183dbf2c5164fc3e82989193f3 Mon Sep 17 00:00:00 2001 From: sunrisepeak <speakshen@163.com> Date: Thu, 6 Aug 2026 23:59:14 +0800 Subject: [PATCH 03/13] =?UTF-8?q?ci:=20=E5=9C=A8=20xim-pkgindex#463(gcc=20?= =?UTF-8?q?alias=20=E8=BF=90=E8=A1=8C=E6=97=B6=E5=B1=95=E5=BC=80=20subos?= =?UTF-8?q?=20sysroot)=E5=8F=91=E5=B8=83=E5=90=8E=E9=87=8D=E8=B7=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit openssl 的 `assert.h: No such file or directory` 根因是 gcc alias 把安装机的 subos sysroot 写死了;#463 已合入并进入已发布索引(artifact xim-index-5f383b5 里可见 XLINGS_DYNAMIC_SUBOS_DIR)。此前那轮 job 起于修复传播之前。 From 147117c4ff6782865f9d4d7b74d8d53fae218477 Mon Sep 17 00:00:00 2001 From: sunrisepeak <speakshen@163.com> Date: Fri, 7 Aug 2026 00:13:03 +0800 Subject: [PATCH 04/13] =?UTF-8?q?ci:=20=E9=87=8D=E8=B7=91=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E4=B8=8A=E4=B8=80=E8=BD=AE=E6=AD=BB=E4=BA=8E=20Git?= =?UTF-8?q?Hub=20Actions=20=E5=9F=BA=E7=A1=80=E8=AE=BE=E6=96=BD=E6=95=85?= =?UTF-8?q?=E9=9A=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Failed to resolve action download info. Error: Service Unavailable 与改动无关;`gh run rerun` 对该 run 不可用,故以空提交触发。 From 510fdc96cddadc64392fcc28ae936f721a40cff3 Mon Sep 17 00:00:00 2001 From: sunrisepeak <speakshen@163.com> Date: Fri, 7 Aug 2026 00:29:24 +0800 Subject: [PATCH 05/13] =?UTF-8?q?ci:=20=E5=86=8D=E6=AC=A1=E9=87=8D?= =?UTF-8?q?=E8=B7=91=20=E2=80=94=E2=80=94=20GitHub=20Actions=20=E4=BB=8D?= =?UTF-8?q?=E5=A4=84=E4=BA=8E=20partial=5Foutage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 官方状态页确认 Actions partial_outage;上一轮 select 死于 `Failed to resolve action download info: Service Unavailable`,lint 被连带取消, workspace 因此全部 skip。与改动无关。 From f0663ad54bc39bfe339fdef4737f6bd363eddba7 Mon Sep 17 00:00:00 2001 From: sunrisepeak <speakshen@163.com> Date: Fri, 7 Aug 2026 08:39:57 +0800 Subject: [PATCH 06/13] =?UTF-8?q?ci:=20=E9=87=8D=E8=B7=91=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E4=B8=8A=E4=B8=80=E8=BD=AE=E5=85=A8=E9=83=A8?= =?UTF-8?q?=E6=AD=BB=E4=BA=8E=20GitHub=20Actions=20major=5Foutage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 官方状态页已恢复 operational。 From a35381c153fde74544b44d90a3eb7aa2f16958d3 Mon Sep 17 00:00:00 2001 From: sunrisepeak <speakshen@163.com> Date: Fri, 7 Aug 2026 10:06:13 +0800 Subject: [PATCH 07/13] =?UTF-8?q?ci:=20=E5=B7=A5=E5=85=B7=E9=93=BE?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E6=98=AF=20job=20=E7=9A=84=E5=87=86=E5=A4=87?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C,=E4=B8=8D=E8=AF=A5=E7=94=B1=E7=A2=B0?= =?UTF-8?q?=E5=B7=A7=E6=8E=92=E7=AC=AC=E4=B8=80=E7=9A=84=E6=88=90=E5=91=98?= =?UTF-8?q?=E6=89=BF=E6=8B=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grpc-codegen 在 linux 上死于 compat.openssl 的 install(): `stdlib.h: No such file or directory`。同一个成员在 macOS(那里 openssl 用 /usr/bin/cc)和 windows 上都过,而它的姊妹 grpc-module 排在第二、工具链已就位,构建同一个 openssl 毫无问题。 差别只有一个:它是本 shard 第一个成员,于是承担了 mcpp 的一次性 工具链自举 —— 并且是在**它自己的**环境里承担的。带自定义 `[indices]` 的成员用的是项目局部 xlings home (<member>/.mcpp/.xlings),而 openssl 走自己的 Makefile、用裸 gcc, 头文件搜索完全来自 xvm shim 的 --sysroot;那个 sysroot 指到项目局部 subos,它的 usr/include 是空的。 于是 job 的成败取决于成员**顺序**。这一步用一个没有 [indices] 的 最小工程把工具链装进共享 home,一次,单独一个进程,在任何成员开始 之前。这本身也更诚实:job 该显式准备自己的环境,而不是继承上一个 测试留下的副作用。 --- .github/workflows/validate.yml | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index d118a3da..66c0c41a 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -575,6 +575,41 @@ jobs: run: | "$MCPP" index update + # ── The toolchain is job setup, not the first member's problem ────── + # mcpp installs a default toolchain on first use ("First run no + # toolchain configured — installing gcc@16.1.0"). Without this step + # that one-time, job-wide install lands inside whichever member the + # shard happens to run first, and it lands there under THAT member's + # environment — for a member with custom `[indices]`, a project-local + # xlings home under <member>/.mcpp/.xlings. + # + # That made the outcome depend on member ORDER. tests/examples/ + # grpc-codegen, first in its shard, died in compat.openssl's install() + # with `stdlib.h: No such file or directory`: openssl builds through + # its own Makefile with a bare `gcc`, so it takes the header search + # from the xvm shim's `--sysroot`, which resolved to the project-local + # subos — a tree with an empty usr/include. The very same member + # passed on macOS (openssl uses /usr/bin/cc there) and on windows, and + # its sibling grpc-module, running SECOND with the toolchain already + # in place, built the same openssl fine. + # + # A trivial project with no `[indices]` puts the toolchain in the + # shared home, once, in its own process, before any member runs. It is + # also just honest CI: a job should provision its environment + # explicitly rather than inherit it from a test that ran earlier. + - name: Install the toolchain (so no member absorbs the first-run install) + shell: bash + env: + MCPP_INDEX_MIRROR: GLOBAL + run: | + # mktemp -d, not $RUNNER_TEMP: on windows that variable is a native + # path (D:\a\_temp) and this step runs under bash. + warm="$(mktemp -d)" + mkdir -p "$warm/src" + printf '[package]\nname = "toolchain-warmup"\nversion = "0.1.0"\n' > "$warm/mcpp.toml" + printf 'int main() { return 0; }\n' > "$warm/src/main.cpp" + cd "$warm" && "$MCPP" build + - name: mcpp test (workspace or affected members) shell: bash env: From f00a6189e932abde2c6e08cd5f79ac33e7be6d1b Mon Sep 17 00:00:00 2001 From: sunrisepeak <speakshen@163.com> Date: Fri, 7 Aug 2026 12:44:55 +0800 Subject: [PATCH 08/13] =?UTF-8?q?bump(grpc):=20=E4=B8=89=E4=B8=AA=E6=8F=8F?= =?UTF-8?q?=E8=BF=B0=E7=AC=A6=E6=8C=87=E5=90=91=20v1.83.0-4=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20grpcgen=20=E5=88=86=E5=B1=82=E6=8E=A7=E5=88=B6=20L0?= =?UTF-8?q?=E2=80=93L3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grpcgen 从「两个旋钮 + 断崖」变成四层,每层是下一层的默认值: generate_all(opt) ≡ submit(plan_all(opt)),.grpc=true ≡ .plugins={cpp()}。 新增 extra_dirs(既生成又搜索)/imports(只搜索)/mock/protoc_args, plan_entries((root,name)) 公开,description 自述用了哪些旋钮。 sha256 991e9928…(GitCode 镜像已回探核验,与 GitHub 源码包逐字节一致)。 --- pkgs/g/grpc-plugin.lua | 18 +++++++++--------- pkgs/g/grpc.lua | 14 +++++++------- pkgs/g/grpcgen.lua | 18 +++++++++--------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/pkgs/g/grpc-plugin.lua b/pkgs/g/grpc-plugin.lua index ae9da6dc..7cc49c64 100644 --- a/pkgs/g/grpc-plugin.lua +++ b/pkgs/g/grpc-plugin.lua @@ -31,28 +31,28 @@ package = { linux = { ["1.83.0"] = { url = { - GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz", - CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz", + GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", + CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz", }, - sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f", + sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa", }, }, macosx = { ["1.83.0"] = { url = { - GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz", - CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz", + GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", + CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz", }, - sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f", + sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa", }, }, windows = { ["1.83.0"] = { url = { - GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz", - CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz", + GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", + CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz", }, - sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f", + sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa", }, }, }, diff --git a/pkgs/g/grpc.lua b/pkgs/g/grpc.lua index 1d0a4034..c33d7b9c 100644 --- a/pkgs/g/grpc.lua +++ b/pkgs/g/grpc.lua @@ -6,7 +6,7 @@ -- 索引(#151),满打满算一天,唯一的消费者是本仓的 tests/examples/grpc-module。 -- 为一天的历史留一份永久重复条目不划算。 -- --- 归档现为 v1.83.0-3。历次重打包中 `src/` / `include/` / `third_party/` 与 +-- 归档现为 v1.83.0-4。历次重打包中 `src/` / `include/` / `third_party/` 与 -- v1.83.0 逐字节相同,变的只有 plugin/(grpc_cpp_plugin)、rules/(grpcgen) -- 与本包 manifest 的 codegen feature。已发布过的 sha256 不能靠移动 tag 复用 -- (mcpp#349:发布数据不得让程序失效),所以每次重打包都另起一个 tag。 @@ -59,19 +59,19 @@ package = { linux = { ["1.83.0"] = { url = { - GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz", - CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz", + GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", + CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz", }, - sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f", + sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa", }, }, macosx = { ["1.83.0"] = { url = { - GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz", - CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz", + GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", + CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz", }, - sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f", + sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa", }, }, -- No windows block, and the reason is a DEPENDENCY rather than gRPC: diff --git a/pkgs/g/grpcgen.lua b/pkgs/g/grpcgen.lua index eb7d01f3..be94ded1 100644 --- a/pkgs/g/grpcgen.lua +++ b/pkgs/g/grpcgen.lua @@ -31,28 +31,28 @@ package = { linux = { ["1.83.0"] = { url = { - GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz", - CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz", + GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", + CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz", }, - sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f", + sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa", }, }, macosx = { ["1.83.0"] = { url = { - GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz", - CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz", + GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", + CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz", }, - sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f", + sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa", }, }, windows = { ["1.83.0"] = { url = { - GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz", - CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz", + GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", + CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz", }, - sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f", + sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa", }, }, }, From 2645f24e85bfdf8ac08d68fcd7a8929cdbbebe35 Mon Sep 17 00:00:00 2001 From: sunrisepeak <speakshen@163.com> Date: Fri, 7 Aug 2026 12:59:39 +0800 Subject: [PATCH 09/13] =?UTF-8?q?Revert=20"ci:=20=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E9=93=BE=E5=AE=89=E8=A3=85=E6=98=AF=20job=20=E7=9A=84=E5=87=86?= =?UTF-8?q?=E5=A4=87=E5=B7=A5=E4=BD=9C,=E4=B8=8D=E8=AF=A5=E7=94=B1?= =?UTF-8?q?=E7=A2=B0=E5=B7=A7=E6=8E=92=E7=AC=AC=E4=B8=80=E7=9A=84=E6=88=90?= =?UTF-8?q?=E5=91=98=E6=89=BF=E6=8B=85"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit a35381c153fde74544b44d90a3eb7aa2f16958d3. --- .github/workflows/validate.yml | 35 ---------------------------------- 1 file changed, 35 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 66c0c41a..d118a3da 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -575,41 +575,6 @@ jobs: run: | "$MCPP" index update - # ── The toolchain is job setup, not the first member's problem ────── - # mcpp installs a default toolchain on first use ("First run no - # toolchain configured — installing gcc@16.1.0"). Without this step - # that one-time, job-wide install lands inside whichever member the - # shard happens to run first, and it lands there under THAT member's - # environment — for a member with custom `[indices]`, a project-local - # xlings home under <member>/.mcpp/.xlings. - # - # That made the outcome depend on member ORDER. tests/examples/ - # grpc-codegen, first in its shard, died in compat.openssl's install() - # with `stdlib.h: No such file or directory`: openssl builds through - # its own Makefile with a bare `gcc`, so it takes the header search - # from the xvm shim's `--sysroot`, which resolved to the project-local - # subos — a tree with an empty usr/include. The very same member - # passed on macOS (openssl uses /usr/bin/cc there) and on windows, and - # its sibling grpc-module, running SECOND with the toolchain already - # in place, built the same openssl fine. - # - # A trivial project with no `[indices]` puts the toolchain in the - # shared home, once, in its own process, before any member runs. It is - # also just honest CI: a job should provision its environment - # explicitly rather than inherit it from a test that ran earlier. - - name: Install the toolchain (so no member absorbs the first-run install) - shell: bash - env: - MCPP_INDEX_MIRROR: GLOBAL - run: | - # mktemp -d, not $RUNNER_TEMP: on windows that variable is a native - # path (D:\a\_temp) and this step runs under bash. - warm="$(mktemp -d)" - mkdir -p "$warm/src" - printf '[package]\nname = "toolchain-warmup"\nversion = "0.1.0"\n' > "$warm/mcpp.toml" - printf 'int main() { return 0; }\n' > "$warm/src/main.cpp" - cd "$warm" && "$MCPP" build - - name: mcpp test (workspace or affected members) shell: bash env: From c2745bedc0a50e4088d0feb918945772d48b2085 Mon Sep 17 00:00:00 2001 From: sunrisepeak <speakshen@163.com> Date: Fri, 7 Aug 2026 16:00:54 +0800 Subject: [PATCH 10/13] =?UTF-8?q?fix(openssl):=20=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E5=99=A8=E4=B9=9F=E8=A6=81=E7=94=B1=E6=9C=AC=E6=8F=8F=E8=BF=B0?= =?UTF-8?q?=E7=AC=A6=E8=A7=A3=E6=9E=90,=E8=80=8C=E4=B8=8D=E6=98=AF?= =?UTF-8?q?=E4=BB=8E=E7=8E=AF=E5=A2=83=E9=87=8C=E6=8D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit linux 上 CC 一直留给 PATH,理由写在注释里:「on linux the xim gcc carries its own payload and is the right compiler to use」。二进制 是对的,「它自足」这半不对。 PATH 上的 gcc 是 xvm shim,shim 会注入 `--sysroot=<调用进程解析到的 subos>`(xim-pkgindex pkgs/g/gcc.lua) —— 而同一段注释写着契约的另一半: Consumers that bypass the shim supply their own header flags. OpenSSL 的 Makefile 调裸 `cc`,正是那一类。 这个 hook 的 cwd 在**消费方项目**的 xlings home 里 (<proj>/.mcpp/.xlings/data/runtimedir/openssl-<v>),于是 subos 解析 到项目那个 —— 它只装了该项目自己的东西,没有 usr/include,通常连 usr/ 都没有。而 `--sysroot` 指向空树不会回落,它会**关掉**默认搜索: include/internal/common.h:14:11: fatal error: stdlib.h: No such file .../xim-x-gcc/16.1.0/lib/gcc/.../limits.h:210:15: fatal error: limits.h (后一条是 gcc 自己的 #include_next,读起来像编译器坏了,并不是。) 所以按 make 和 perl 已有的做法办:从声明的 build dep 解析 C 库载荷, 把 payload gcc 需要的三样显式交给它 —— 头(-isystem)、crt(-B)、 -lc(-L)。--sysroot 重指到 glibc 载荷根,它不是 FHS 形状因而不贡献 任何搜索路径:既压掉 shim 注入的那个,又不给宿主 /usr/include 开门。 deps 用 xim:gcc 自己的区间(glibc@>=2.39 / linux-headers@5.11.1)而非 @latest:openssl 的产物要和该 gcc 链在一起,@latest 可能解析出比工具链 更新的 glibc。两边解析同一个节点,而且 gcc 在哪它们就已经在哪。 验证(本机 + xlings subos,非 docker): * 裸载荷 gcc 无 flag → fatal error: stdlib.h: No such file(精确复现) * 加上本次这组 flag → 编译+链接+运行通过 * openssl 成员端到端 → configdata.pm / Makefile 均带上构造出的 CC, test result ok. 1 passed --- pkgs/c/compat.openssl.lua | 91 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 4 deletions(-) diff --git a/pkgs/c/compat.openssl.lua b/pkgs/c/compat.openssl.lua index 46a4fd3d..52a7a3de 100644 --- a/pkgs/c/compat.openssl.lua +++ b/pkgs/c/compat.openssl.lua @@ -59,7 +59,23 @@ package = { xpm = { linux = { - deps = { "xim:make@latest", "xim:perl@latest" }, + -- glibc + linux-headers are here for the same reason make and perl + -- are: this package builds through its own Makefile with a bare + -- `cc`, so every tool it uses has to be something this descriptor + -- resolved, not something it hopes to find. See cc_override(). + -- + -- The specs are xim:gcc's own, character for character, and NOT + -- `@latest`. openssl's objects are linked into projects built by + -- that gcc, so the C library it compiles against has to be the one + -- the toolchain uses; `@latest` would be free to resolve a NEWER + -- glibc than the toolchain's and introduce symbols the final link + -- cannot satisfy. Matching the ranges means both resolve the same + -- node -- already on disk wherever gcc is, so this costs a + -- resolution rather than a download. + deps = { + "xim:make@latest", "xim:perl@latest", + "xim:glibc@>=2.39", "xim:linux-headers@5.11.1", + }, ["3.5.1"] = { url = { GLOBAL = "https://github.com/openssl/openssl/releases/download/openssl-3.5.1/openssl-3.5.1.tar.gz", @@ -161,13 +177,80 @@ end -- inherit the resolved toolchain's sysroot flags — it just runs `cc`. On macOS -- the toolchain in PATH is xim's llvm, which has no macOS SDK wired up, so -- every compile would fail on <stdio.h>. Pin Apple's own driver, which finds --- the SDK by itself. Left alone elsewhere: on linux the xim gcc carries its --- own payload and is the right compiler to use. +-- the SDK by itself. +-- +-- On linux the compiler used to be left to PATH, on the strength of "the xim +-- gcc carries its own payload and is the right compiler to use". The binary is +-- right; the assumption that it is SELF-SUFFICIENT is not. PATH reaches it +-- through its xvm shim, and the shim injects `--sysroot=<subos>` resolved +-- against **the subos the calling process resolved to** (xim-pkgindex +-- pkgs/g/gcc.lua) -- whose own comment states the other half of the contract: +-- +-- Consumers that bypass the shim supply their own header flags. +-- +-- OpenSSL's Makefile is exactly such a consumer: it calls a bare `cc`. So the +-- flags are this hook's business, the same way `make` and `perl` already are. +-- +-- Leaving it ambient breaks whenever the resolved subos is not the one holding +-- the toolchain. This hook runs with its cwd inside the CONSUMING PROJECT's +-- xlings home (<proj>/.mcpp/.xlings/data/runtimedir/openssl-<v>), so the subos +-- resolves to the PROJECT one, which holds only what that project installed -- +-- no usr/include, usually no usr/ at all. A `--sysroot` at an empty tree does +-- not fall back to the default search, it SUPPRESSES it, and every compile +-- dies a long way from the cause: +-- +-- include/internal/common.h:14:11: fatal error: stdlib.h: No such file +-- .../xim-x-gcc/16.1.0/lib/gcc/.../limits.h:210:15: fatal error: limits.h +-- +-- (the second is gcc's own `#include_next`, which reads like a broken compiler +-- and is not). +-- +-- So resolve the C library the way make and perl are resolved -- from declared +-- build deps -- and hand openssl the three things the payload gcc needs: +-- headers (-isystem), crt objects (-B) and -lc (-L). `--sysroot` is re-pointed +-- at the glibc payload root, which is NOT FHS-shaped and therefore contributes +-- no search path of its own: it neutralises whatever the shim injected without +-- opening a door to the host's /usr/include. A later --sysroot wins, so this +-- needs no cooperation from the shim. +-- +-- This is a local restatement of what mcpp's own link model does +-- (src/build/flags.cppm, "payload-first, --sysroot fallback"). Duplicating a +-- decision is a real cost; the alternative is worse, because openssl builds +-- outside mcpp's compile rules by construction and has no other way to be told. +-- If xim ever hands install() hooks a ready CC/CFLAGS, this should become that. +local function libc_payloads() + local glibc = pkginfo.build_dep("xim:glibc") or pkginfo.build_dep("glibc") + local kern = pkginfo.build_dep("xim:linux-headers") + or pkginfo.build_dep("linux-headers") + local groot = glibc and glibc.path + local kroot = kern and kern.path + if not (groot and os.isfile(path.join(groot, "include", "stdlib.h"))) then + return nil + end + return groot, kroot +end + local function cc_override() if os.host() == "macosx" and os.isfile("/usr/bin/cc") then return "CC=/usr/bin/cc " end - return "" + if os.host() ~= "linux" then return "" end + + local groot, kroot = libc_payloads() + if not groot then + log.warn("openssl: no xim:glibc payload resolved; leaving CC to PATH" + .. " (fails if the active subos carries no libc headers)") + return "" + end + local libdir = os.isdir(path.join(groot, "lib64")) + and path.join(groot, "lib64") or path.join(groot, "lib") + local cc = "gcc --sysroot=" .. groot + .. " -isystem " .. path.join(groot, "include") + if kroot and os.isdir(path.join(kroot, "include")) then + cc = cc .. " -isystem " .. path.join(kroot, "include") + end + cc = cc .. " -B " .. libdir .. " -L " .. libdir + return "CC=" .. sh_quote(cc) .. " " end -- Does this perl actually RUN, with the core modules Configure opens with? From 191cc4a63b474f085d92ca6e957794dff75db02d Mon Sep 17 00:00:00 2001 From: sunrisepeak <speakshen@163.com> Date: Fri, 7 Aug 2026 16:16:26 +0800 Subject: [PATCH 11/13] =?UTF-8?q?refactor(tests):=20=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E5=86=99=E4=B8=80=E6=AC=A1=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20cfg(linux)+cfg(macos)=20=E5=90=88=E6=88=90=20cfg(un?= =?UTF-8?q?ix)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 两个 grpc 成员各有一对逐字相同的 [target.'cfg(linux)'] 与 [target.'cfg(macos)'] 段。它们表达的是**一个**事实——compat.openssl 没有 windows xpm 条目,所以 gRPC 在 windows 之外都可解析——一个事实 写两遍,就是下一次只改一处的机会。 mcpp 的谓词语言本来就够用:any()/all()/not() 加 unix 别名 (family == "unix",见 src/build/prepare.cppm 的 match_alias)。 本机核验:cfg(unix) 下 grpc-codegen 照常解析出 c-ares/openssl/re2/zlib。 --- tests/examples/grpc-codegen/mcpp.toml | 14 ++++++-------- tests/examples/grpc-module/mcpp.toml | 17 ++++++++--------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/tests/examples/grpc-codegen/mcpp.toml b/tests/examples/grpc-codegen/mcpp.toml index c7493f4e..db817a2c 100644 --- a/tests/examples/grpc-codegen/mcpp.toml +++ b/tests/examples/grpc-codegen/mcpp.toml @@ -28,16 +28,14 @@ name = "grpc-codegen-tests" version = "0.1.0" -[target.'cfg(linux)'.dependencies.grpc] +# `cfg(unix)`, not one block per OS: the condition is "everywhere gRPC +# resolves", and what excludes windows is compat.openssl having no xpm entry +# there — a single fact, so a single predicate. mcpp evaluates `unix` as +# family == "unix" (src/build/prepare.cppm, alongside any()/all()/not()). +[target.'cfg(unix)'.dependencies.grpc] grpc = { version = "1.83.0", features = ["codegen"] } -[target.'cfg(linux)'.build] -cxxflags = ["-DHAVE_GRPC=1"] - -[target.'cfg(macos)'.dependencies.grpc] -grpc = { version = "1.83.0", features = ["codegen"] } - -[target.'cfg(macos)'.build] +[target.'cfg(unix)'.build] cxxflags = ["-DHAVE_GRPC=1"] # windows carries the RULE but not gRPC itself, so build.mcpp compiles (its diff --git a/tests/examples/grpc-module/mcpp.toml b/tests/examples/grpc-module/mcpp.toml index 5ed62037..d11bd9a3 100644 --- a/tests/examples/grpc-module/mcpp.toml +++ b/tests/examples/grpc-module/mcpp.toml @@ -18,20 +18,19 @@ name = "grpc-module-tests" version = "0.1.0" -# linux + macOS only: the package has no windows xpm entry (its compat.openssl +# unix only: the package has no windows xpm entry (its compat.openssl # dependency has none yet), so on windows this member carries no dependency at # all and the test compiles to a no-op main() — the same shape # tests/examples/openssl uses for the same underlying reason. -[target.'cfg(linux)'.dependencies.grpc] -grpc = "1.83.0" - -[target.'cfg(linux)'.build] -cxxflags = ["-DHAVE_GRPC=1"] - -[target.'cfg(macos)'.dependencies.grpc] +# +# One `cfg(unix)` rather than a linux block and an identical macos block: the +# condition is a single fact ("gRPC resolves everywhere except windows"), so it +# should be written once. mcpp evaluates `unix` as family == "unix" +# (src/build/prepare.cppm, alongside any()/all()/not()). +[target.'cfg(unix)'.dependencies.grpc] grpc = "1.83.0" -[target.'cfg(macos)'.build] +[target.'cfg(unix)'.build] cxxflags = ["-DHAVE_GRPC=1"] [indices] From d46afa72c73de0f4891dc3dca5bececc6a5201bf Mon Sep 17 00:00:00 2001 From: sunrisepeak <speakshen@163.com> Date: Fri, 7 Aug 2026 17:52:42 +0800 Subject: [PATCH 12/13] =?UTF-8?q?ci:=20=E5=88=86=E7=89=87=E6=95=B0?= =?UTF-8?q?=E8=B7=9F=E7=9D=80=E5=B7=A5=E4=BD=9C=E9=87=8F=E8=B5=B0,?= =?UTF-8?q?=E8=80=8C=E4=B8=8D=E6=98=AF=E8=B7=9F=E7=9D=80=E3=80=8C=E6=98=AF?= =?UTF-8?q?=E5=90=A6=E5=85=A8=E9=87=8F=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit linux 分片在 1h30m17s 被取消——正好是 job 上限。没有任何东西坏掉: 一片里排了 grpc-codegen(3563s)与 grpc-module(1701s),装不下。 分片数原本是二元的(全量 linux 3 片,否则 1 片),那等于断言「非全量 就是小活」。并不是:改一个被广泛消费的描述符会选中所有消费者——本次 改 compat.openssl.lua 就选中了四个成员。 tests/member-timings.tsv 早就记着每个成员的实测耗时,plan_shards.lua 也早就按它做 LPT 装箱。所以扇出数就从同一张表来:把本次成员的 linux 耗时求和,每 ~45 分钟一片,上限 3(再多会被 runner 并发与每片固定开销 吃掉,见上方既有测量)。macOS 保持 1 片——它并发就是 1,多分只会串行。 同时把 grpc-codegen 的实测值补进表里(linux 3563 / macos 1715 / windows 31)。新成员此前没有条目,按中位数估算,正是它被低估的原因。 本机核验:四成员求和 5313s → 2 片,LPT 切成 shard 0: grpc-codegen (59min) shard 1: grpc-module asio-ssl openssl (29min) --- .github/workflows/validate.yml | 35 +++++++++++++++++++++++++++++++++- tests/member-timings.tsv | 3 +++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index d118a3da..6974fb23 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -404,7 +404,40 @@ jobs: "$1" "$2" "$3" "$4" "$5" "$6" "$i" "$7" done } - if [ "$full" = 1 ]; then ln=3; mn=1; wn=2; else ln=1; mn=1; wn=1; fi + # Shard count follows the WORK, not the full/partial flag. + # + # It used to be binary — full run: linux 3, anything else: linux 1 — + # which reads "a partial run is small". It is not: touching a widely + # consumed descriptor selects every member that consumes it. This + # PR's `pkgs/c/compat.openssl.lua` selected four (grpc-codegen, + # grpc-module, asio-ssl, openssl), one shard got all of them, and + # linux was cancelled at exactly 1h30m — the job cap — with + # grpc-codegen (3563s) and grpc-module (1701s) back to back. Nothing + # was broken; the plan just could not fit. + # + # tests/member-timings.tsv already holds the measured cost of every + # member, and plan_shards.lua already packs by it (LPT). So size the + # fan-out from the same table: sum this run's members and give linux + # a shard per ~45 minutes of work, capped at 3 (above that, runner + # concurrency and the per-shard setup eat the gain — see the + # measurement in the comment above). macOS stays at 1: its + # concurrency is 1, so extra shards there run back to back. + if [ "$full" = 1 ]; then + ln=3; mn=1; wn=2 + else + secs=$(lua5.4 -e ' + local want = {} + for m in (os.getenv("MEMBERS") or ""):gmatch("%S+") do want[m] = true end + local total = 0 + for line in io.lines("tests/member-timings.tsv") do + local p, m, s = line:match("^(%S+)\t(%S+)\t(%d+)$") + if p == "linux" and m and want[m] then total = total + tonumber(s) end + end + print(total)') + ln=$(( secs / 2700 + 1 )); [ "$ln" -gt 3 ] && ln=3 + mn=1; wn=1 + echo "linux work: ${secs}s (measured) -> $ln shard(s)" + fi { printf '{"include":[' emit linux ubuntu-latest linux-x86_64 tar.gz bin/mcpp registry/bin/xlings "$ln" diff --git a/tests/member-timings.tsv b/tests/member-timings.tsv index 2c8a912e..8c844db4 100644 --- a/tests/member-timings.tsv +++ b/tests/member-timings.tsv @@ -28,6 +28,7 @@ linux godot-cpp 573 linux godot-cpp-module 549 linux godot-cpp-module-v10 392 linux godot-cpp-v10 504 +linux grpc-codegen 3563 linux grpc-module 1701 linux gui-stack 81 linux imgui 6 @@ -89,6 +90,7 @@ macos godot-cpp 333 macos godot-cpp-module 256 macos godot-cpp-module-v10 215 macos godot-cpp-v10 230 +macos grpc-codegen 1715 macos grpc-module 880 macos gui-stack 1 macos imgui 4 @@ -152,6 +154,7 @@ windows godot-cpp 742 windows godot-cpp-module 636 windows godot-cpp-module-v10 684 windows godot-cpp-v10 566 +windows grpc-codegen 31 windows grpc-module 1 windows gui-stack 1 windows imgui 6 From d1a9f34cb603bfd3d8e8e8c7e524e9ee8abc86a2 Mon Sep 17 00:00:00 2001 From: sunrisepeak <speakshen@163.com> Date: Fri, 7 Aug 2026 19:26:37 +0800 Subject: [PATCH 13/13] =?UTF-8?q?ci:=20macOS=20=E5=85=A8=E9=87=8F=E4=B9=9F?= =?UTF-8?q?=E8=A3=85=E4=B8=8D=E4=B8=8B=E4=B8=80=E7=89=87=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E6=89=87=E5=87=BA=E6=94=B9=E4=B8=BA=E9=80=90?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E6=8C=89=E5=AE=9E=E6=B5=8B=E6=80=BB=E9=87=8F?= =?UTF-8?q?=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一轮 linux 三片全过(1h17/1h09/44m),macOS 单片在 1h30m20s 撞顶。 原注释只从墙钟论证 macOS 不该分片(并发为 1,分了也串行),漏了另 一半:**每个分片是独立 job、各有各的 timeout-minutes**,所以分片同时 也是「让活装得下」的手段。串行不要紧,跑不完才要紧。 macOS 全量实测 6922s = 115 分钟,本来就超 90 上限;它此前能过,只是 因为总量刚好卡在线下,而 grpc-codegen(macOS 1715s)把它推了过去。 改为三平台同一条规则:从 tests/member-timings.tsv 求本次成员在该平台 的实测总和,每 ~70 分钟一片(在 90 上限下留 ~20 分钟冷缓存余量), 上限按各自 runner 并发定(linux 3 / macOS 2 / windows 2)。 本机核验: 全量 linux 3 片 ~75min · macOS 2 片 ~57min · windows 2 片 ~67min 本 PR 四成员 linux 2 片 ~44min · macOS 1 片 · windows 1 片 linux 全量是最紧的一档(实测最慢片 77/90),注释已写明后续两个杠杆。 --- .github/workflows/validate.yml | 79 ++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 6974fb23..1d77f48b 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -404,40 +404,65 @@ jobs: "$1" "$2" "$3" "$4" "$5" "$6" "$i" "$7" done } - # Shard count follows the WORK, not the full/partial flag. + # Shard count follows the WORK, per platform, measured. # - # It used to be binary — full run: linux 3, anything else: linux 1 — - # which reads "a partial run is small". It is not: touching a widely - # consumed descriptor selects every member that consumes it. This - # PR's `pkgs/c/compat.openssl.lua` selected four (grpc-codegen, - # grpc-module, asio-ssl, openssl), one shard got all of them, and - # linux was cancelled at exactly 1h30m — the job cap — with - # grpc-codegen (3563s) and grpc-module (1701s) back to back. Nothing - # was broken; the plan just could not fit. + # It used to be binary — full run: linux 3 / macos 1 / windows 2, + # anything else: 1 each — which asserts two things that are not + # true. "A partial run is small": touching a widely consumed + # descriptor selects every member that consumes it, and this PR's + # `pkgs/c/compat.openssl.lua` selected four; one linux shard took + # grpc-codegen (3563s) and grpc-module (1701s) back to back and was + # cancelled at exactly 1h30m, the job cap. "A full macOS run fits in + # one shard": at 6922s measured it does not — it fit only while the + # total sat just under the cap, and the run that added grpc-codegen + # (1715s on macOS) pushed it to 1h30m20s. # - # tests/member-timings.tsv already holds the measured cost of every - # member, and plan_shards.lua already packs by it (LPT). So size the - # fan-out from the same table: sum this run's members and give linux - # a shard per ~45 minutes of work, capped at 3 (above that, runner - # concurrency and the per-shard setup eat the gain — see the - # measurement in the comment above). macOS stays at 1: its - # concurrency is 1, so extra shards there run back to back. - if [ "$full" = 1 ]; then - ln=3; mn=1; wn=2 - else - secs=$(lua5.4 -e ' - local want = {} - for m in (os.getenv("MEMBERS") or ""):gmatch("%S+") do want[m] = true end + # Sharding is not only a wall-clock lever. Each shard is its own job + # with its own `timeout-minutes`, so it is also how the work is made + # to FIT. That is the half the "macOS concurrency is 1, so extra + # shards run back to back" note above left out: back to back is fine + # when the alternative is not finishing. + # + # tests/member-timings.tsv already holds every member's measured + # cost and plan_shards.lua already packs by it (LPT), so the fan-out + # comes from the same table: sum this run's members for that + # platform and take one shard per ~70 minutes, which leaves ~20 + # minutes of headroom under the 90-minute cap for a cold cache. + # + # The caps are where runner concurrency comes back in: linux 3 (the + # measured concurrency — a 4th shard would queue), macOS 2, windows + # 2. Past those, more shards buy fit that is already there and pay + # another checkout + mcpp download + cache restore. + # + # Full run, from the table: linux 13570s over 3 -> ~75min/shard + # (observed slowest 77), macOS 6922s over 2 -> ~57, windows 8043s + # over 2 -> ~67. linux is the tight one — its cap binds before the + # ~70-minute target does, so it is the first place to look if a + # cold full run starts brushing 90 again. The levers, in order: + # raise the linux cap to 4 (costs a queued runner), then the job + # timeout. + shards_for() { # platform cap -> shard count + local secs + secs=$(XPLAT="$1" lua5.4 -e ' + local plat = os.getenv("XPLAT") + local sel, all = {}, (os.getenv("MEMBERS") == "__ALL__") + if not all then + for m in (os.getenv("MEMBERS") or ""):gmatch("%S+") do sel[m] = true end + end local total = 0 for line in io.lines("tests/member-timings.tsv") do local p, m, s = line:match("^(%S+)\t(%S+)\t(%d+)$") - if p == "linux" and m and want[m] then total = total + tonumber(s) end + if p == plat and m and (all or sel[m]) then total = total + tonumber(s) end end print(total)') - ln=$(( secs / 2700 + 1 )); [ "$ln" -gt 3 ] && ln=3 - mn=1; wn=1 - echo "linux work: ${secs}s (measured) -> $ln shard(s)" - fi + local n=$(( secs / 4200 + 1 )) + [ "$n" -gt "$2" ] && n="$2" + echo "$1 work: ${secs}s (measured) -> $n shard(s)" >&2 + echo "$n" + } + ln=$(shards_for linux 3) + mn=$(shards_for macos 2) + wn=$(shards_for windows 2) { printf '{"include":[' emit linux ubuntu-latest linux-x86_64 tar.gz bin/mcpp registry/bin/xlings "$ln"