Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
66 changes: 66 additions & 0 deletions .agents/docs/2026-07-18-add-ffmpeg-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# 收录 compat.ffmpeg 8.1.2 — 全源码直编形态(新形态首例)

> 日期: 2026-07-18
> 来源: mcpplibs/ffmpeg-m 项目派生(该仓调研+POC 文档为完整背景:
> `ffmpeg-m/.agents/docs/2026-07-18-ffmpeg-m-{research-and-plan,implementation}.md`)

## 形态判定:第五种形态——"全源码直编"(config 快照 + 源列表)

FFmpeg 属第三方上游 (a),但不落入既有四形态:它**不用 install() 跑外部构建系统**
(openblas/opencv 形态),而是把上游 `./configure` 的产物一次性冻结进描述符,由
mcpp 直接编译全部源码:

- `generated_files`:15 个 configure 产物(config.h、config_components.{h,asm}、
config.asm、avconfig.h、ffversion.h、9 个 `*_list.c`)≈ 240KB 内联 —— 对冻结的
(target, profile) 是确定性纯文本;
- `sources`:2281 条精确清单(2124 `.c` + **157 NASM `.asm`**),`make -n` 推导
(ground truth,含 `_select` 闭包);
- `flags`(mcpp 0.0.95 per-glob):每库 `BUILDING_<lib>` define + `.asm` 的
`-Pconfig.asm`;NASM 的 `-I` 由 mcpp 自动从 include_dirs 馈送;
- 消费者构建期**零 configure/make/cmake**;nasm 按 PATH → sandbox → xlings 惰性
解析(xim 已收录)。

描述符由 `ffmpeg-m/tools/gen_descriptor.py` 自动生成(329KB,勿手改);升级
FFmpeg = 重跑生成器 + review diff。

## 配置档位

`--disable-autodetect --disable-programs --disable-doc`,其余全默认:全部内部组件
(600+ decoders / 272 encoders / 358 demuxers / muxers / filters / 7 库全build),
**LGPL-2.1+**(无 gpl/version3/nonfree),零宿主库探测(hermetic)。

## 平台

**linux(x86_64)only**(先例:compat.x11)。config 快照与源列表是 target 相关的
(x86 编 NASM,aarch64 编 GAS `.S`);macOS/Windows 待各自快照生成后追加
(ffmpeg-m 的 macOS CI job 已在产 gen/macos-aarch64 artifact)。

## 镜像

- GLOBAL: `https://ffmpeg.org/releases/ffmpeg-8.1.2.tar.gz`(官方发行物,
sha256 `32faba5e…8bf22`,两次计算一致)
- CN: `https://gitcode.com/mcpp-res/ffmpeg/releases/download/8.1.2/ffmpeg-8.1.2.tar.gz`
(gtc 建仓+发布,已验证 200 + 字节一致)

## feature 评估

v1 无 feature。组件裁剪(slim 档)需要**成套**的 config 快照 + 源列表(features
仅门控 sources,无法切换 generated_files),属"档位=独立版本/包"问题,与
compat.opencv 的结论一致;待需求出现再议。

## 验证(mcpp 0.0.95,与本 PR bump 后的 CI pin 一致)

- `tests/examples/ffmpeg` 构建 16.3s(32 核),运行输出
`ffmpeg 60.26: decoders+demuxers(358) ok`;
- 断言:三库 major 版本(60/62/62)、h264/hevc/av1/aac 解码器存在、demuxer>300;
- .asm 验证:157 个 `.asm.o` 全部产出,最终二进制含 3260 个 ssse3/avx2 符号;
- 本地 lint 五项全过(语法/必填/无前导v/镜像表/`mcpp xpkg parse`)。

## 注意事项

- **本 PR 将 `MCPP_VERSION` 0.0.94 → 0.0.95**:描述符的 per-glob `flags` 键是
0.0.95(#223)语法,0.0.94 的 resolver grammar lint 会拒绝(这正是"floor first,
new grammar after"规则的预期行为)。
- 上游源列表与 git tag n8.1.2 逐字节一致(已比对);ffversion.h 冻结为 "8.1.2"。
- 相关 mcpp issue(ffmpeg-m 项目产出):#226(-iquote 相对路径)、#227(flags
数组表)、#228(glob 花括号)、#229(依赖包 cfg 条件 sources——macOS 支持前置)。
130 changes: 116 additions & 14 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ on:
workflow_dispatch:

env:
# Bumped to 0.0.94: fixes feature-gated `sources` never being compiled under
# `mcpp test` (mcpp#218) — required by the tests/examples/spdlog-compiled
# member, which links against spdlog's feature-compiled src/*.cpp. Also
# carries standard = "c++fly" in the resolver grammar (0.0.91), so c++fly
# 0.0.96: carries the windows scanner symlink-escape crash fix (mcpp#230),
# so all platforms ride the same version again. Older floors of note:
# 0.0.94 fixed feature-gated `sources` under `mcpp test` (mcpp#218);
# 0.0.91 added standard = "c++fly" to the resolver grammar, so c++fly
# descriptors get the lint WARN below, not a hard grammar-parse rejection.
MCPP_VERSION: "0.0.94"
MCPP_VERSION: "0.0.96"

jobs:
lint:
Expand Down Expand Up @@ -156,37 +156,46 @@ jobs:
ext: tar.gz
mcpp: bin/mcpp
xlings: registry/bin/xlings
mcpp_version: "0.0.96" # keep in sync with env.MCPP_VERSION
- platform: macos
os: macos-15
suffix: macosx-arm64
ext: tar.gz
mcpp: bin/mcpp
xlings: registry/bin/xlings
mcpp_version: "0.0.96" # keep in sync with env.MCPP_VERSION
- platform: windows
os: windows-latest
suffix: windows-x86_64
ext: zip
mcpp: bin/mcpp.exe
xlings: registry/bin/xlings.exe
mcpp_version: "0.0.96" # keep in sync with env.MCPP_VERSION
env:
MCPP_EFFECTIVE: ${{ matrix.mcpp_version }}
steps:
# Full history: the member-selection step below diffs against the PR
# base to decide which workspace members to test.
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Restore mcpp registry cache
uses: actions/cache@v4
with:
# Holds toolchains AND the built compat packages (data/xpkgs), so a
# repeat `mcpp test` rebuilds little.
path: ~/.mcpp/registry
key: mcpp-registry-${{ runner.os }}-${{ env.MCPP_VERSION }}-${{ hashFiles('pkgs/**/*.lua', 'tests/**', '.github/workflows/validate.yml') }}
key: mcpp-registry-${{ runner.os }}-${{ env.MCPP_EFFECTIVE }}-${{ hashFiles('pkgs/**/*.lua', 'tests/**', '.github/workflows/validate.yml') }}
restore-keys: |
mcpp-registry-${{ runner.os }}-${{ env.MCPP_VERSION }}-
mcpp-registry-${{ runner.os }}-${{ env.MCPP_EFFECTIVE }}-
- name: Download mcpp
shell: bash
env:
MCPP_ARCHIVE: mcpp-${{ env.MCPP_VERSION }}-${{ matrix.suffix }}.${{ matrix.ext }}
MCPP_ROOT: mcpp-${{ env.MCPP_VERSION }}-${{ matrix.suffix }}
MCPP_ARCHIVE: mcpp-${{ env.MCPP_EFFECTIVE }}-${{ matrix.suffix }}.${{ matrix.ext }}
MCPP_ROOT: mcpp-${{ env.MCPP_EFFECTIVE }}-${{ matrix.suffix }}
run: |
curl -L -fsS -o "$MCPP_ARCHIVE" \
"https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/${MCPP_ARCHIVE}"
"https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_EFFECTIVE}/${MCPP_ARCHIVE}"
case "$MCPP_ARCHIVE" in
*.zip) powershell -NoProfile -Command "Expand-Archive -Force -Path '${MCPP_ARCHIVE}' -DestinationPath '.'" ;;
*) tar -xzf "$MCPP_ARCHIVE" ;;
Expand All @@ -203,14 +212,105 @@ jobs:
echo "MCPP_VENDORED_XLINGS=$root/${{ matrix.xlings }}" >> "$GITHUB_ENV"
echo "$root/bin" >> "$GITHUB_PATH"
fi
- name: mcpp test --workspace
# compat.ffmpeg carries NASM .asm sources. No host install needed:
# mcpp >= 0.0.95 resolves nasm itself (PATH → sandbox → auto
# `xlings install nasm`), lazily, only when the plan has .asm units —
# same self-bootstrap contract as the llvm toolchain. The sandbox copy
# lands in ~/.mcpp/registry, so the cache carries it across runs.
# The refresh below is needed because release archives vendor an index
# snapshot and the bootstrap is offline-first: 0.0.96 shipped with a
# nasm.lua deployed engines can't resolve — it "installs" empty
# (mcpp#232; fixed in xim-pkgindex#398 after the release was cut).
# One `mcpp index update` (native wrapper over `xlings update` with
# the sandbox env + transient-network retry) lets the bootstrap see
# the fixed descriptor. Drop once a mcpp release vendors a post-#398
# index snapshot.
- name: Refresh sandbox package index (nasm bootstrap, mcpp#232)
if: runner.os == 'Linux'
shell: bash
run: '"$MCPP" index update'

# ── Selective member testing ──────────────────────────────────────
# `mcpp test --workspace` builds every member (opencv, ffmpeg, …) and
# dominates CI wall-clock, while a PR almost always touches one
# package. Map changed files → affected members and test only those:
# pkgs/<x>/<lib>.lua → members whose mcpp.toml references <lib>
# tests/examples/<m>/** → member <m>
# Run the FULL workspace when the change can affect everything:
# non-PR events (push to main, the nightly cron, dispatch), this
# workflow file (it carries the mcpp version pins, so a version bump
# always re-validates every package), the workspace manifest, or
# shared test scripts. Docs-only changes select nothing.
# Note: bash 3.2 on macOS runners — no associative arrays here.
- name: Select affected workspace members
shell: bash
run: |
full() { echo "MEMBERS=__ALL__" >> "$GITHUB_ENV"; echo "full run: $1"; exit 0; }
[ "${{ github.event_name }}" = "pull_request" ] || full "event=${{ github.event_name }}"
base="origin/${{ github.base_ref }}"
changed=$(git diff --name-only "$base"...HEAD)
printf 'changed files vs %s:\n%s\n' "$base" "$changed"
sel=""
add() { case " $sel " in *" $1 "*) ;; *) sel="$sel $1" ;; esac; }
while IFS= read -r f; do
[ -n "$f" ] || continue
case "$f" in
.github/workflows/validate.yml|tests/*.sh|tools/*) full "$f" ;;
mcpp.toml)
# Workspace manifest. Every new-package PR appends to the
# members list, so that alone must NOT force a full run:
# select the added members; anything else in this file
# (indices, settings) affects everyone → full.
if ! diff -q <(git show "$base:mcpp.toml" | grep -v 'tests/examples/') \
<(grep -v 'tests/examples/' mcpp.toml) >/dev/null; then
full "mcpp.toml non-member change"
fi
for p in $(comm -13 <(git show "$base:mcpp.toml" | grep -o 'tests/examples/[A-Za-z0-9._-]*' | sort -u) \
<(grep -o 'tests/examples/[A-Za-z0-9._-]*' mcpp.toml | sort -u)); do
add "${p#tests/examples/}"
done ;;
tests/examples/*)
m=${f#tests/examples/}; m=${m%%/*}
# A deleted/renamed member dir implies a mcpp.toml edit,
# which already forces a full run above.
[ -d "tests/examples/$m" ] && add "$m" ;;
pkgs/*.lua|pkgs/*/*.lua)
lib=$(basename "$f" .lua); lib=${lib#compat.}
hit=0
for mt in tests/examples/*/mcpp.toml; do
if grep -q "$lib" "$mt"; then add "$(basename "$(dirname "$mt")")"; hit=1; fi
done
[ "$hit" = 1 ] || echo "note: no workspace member exercises $f" ;;
*.md|docs/*|.agents/*|.github/*) : ;;
*) full "unclassified change: $f" ;;
esac
done <<EOF
$changed
EOF
sel=${sel# }
echo "MEMBERS=$sel" >> "$GITHUB_ENV"
echo "selected members: ${sel:-<none>}"

- name: mcpp test (workspace or affected members)
shell: bash
env:
MCPP_INDEX_MIRROR: GLOBAL
run: |
"$MCPP" --version
# No `timeout` wrapper: absent on macOS runners; job-level timeout-minutes bounds it.
"$MCPP" test --workspace
if [ "$MEMBERS" = "__ALL__" ]; then
"$MCPP" test --workspace
elif [ -z "$MEMBERS" ]; then
echo "No workspace member affected by this change — nothing to test."
else
rc=0
for m in $MEMBERS; do
echo "::group::mcpp test -p $m"
"$MCPP" test -p "$m" || rc=1
echo "::endgroup::"
done
exit $rc
fi
- name: tinyhttps module package smoke
shell: bash
env:
Expand All @@ -226,15 +326,17 @@ jobs:
imgui-module:
runs-on: ubuntu-latest
timeout-minutes: 60
env:
MCPP_EFFECTIVE: "0.0.96" # keep in sync with env.MCPP_VERSION
steps:
- uses: actions/checkout@v4
- name: Restore mcpp registry cache
uses: actions/cache@v4
with:
path: ~/.mcpp/registry
key: mcpp-registry-${{ runner.os }}-${{ env.MCPP_VERSION }}-${{ hashFiles('pkgs/**/*.lua', 'tests/**', '.github/workflows/validate.yml') }}
key: mcpp-registry-${{ runner.os }}-${{ env.MCPP_EFFECTIVE }}-${{ hashFiles('pkgs/**/*.lua', 'tests/**', '.github/workflows/validate.yml') }}
restore-keys: |
mcpp-registry-${{ runner.os }}-${{ env.MCPP_VERSION }}-
mcpp-registry-${{ runner.os }}-${{ env.MCPP_EFFECTIVE }}-
- name: Download mcpp
run: |
curl -L -fsS -o mcpp.tar.gz \
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上
| C 源码 compat(含 `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) |
| header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) |
| 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](pkgs/c/compat.openblas.lua)(Make) · [`compat.opencv`](pkgs/c/compat.opencv.lua)(CMake) |
| 全源码直编(config 快照 + 源列表,零外部构建系统) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua)(2281 TU 含 NASM 汇编) |
| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) |

### 新增一个包
Expand Down
1 change: 1 addition & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ members = [
"tests/examples/cjson",
"tests/examples/core",
"tests/examples/eigen",
"tests/examples/ffmpeg",
"tests/examples/fmtlib.fmt",
"tests/examples/gui-stack",
"tests/examples/imgui",
Expand Down
Loading
Loading