diff --git a/.agents/docs/2026-07-18-add-ffmpeg-module-plan.md b/.agents/docs/2026-07-18-add-ffmpeg-module-plan.md new file mode 100644 index 0000000..5503fc0 --- /dev/null +++ b/.agents/docs/2026-07-18-add-ffmpeg-module-plan.md @@ -0,0 +1,52 @@ +# add `ffmpeg`(公开模块包,Form A)+ 收编 compat.ffmpeg 描述符流水线 + +日期:2026-07-18 · 关联:pkgs/f/ffmpeg.lua、tools/compat-ffmpeg/、tests/smoke_ffmpeg_module.sh +前置:#74(compat.ffmpeg 8.1.2 全源码直编)已合并;ffmpeg-m v0.0.1 已发布 tag。 + +## 形态判定 + +- **Form A 原生模块库**(同 `imgui`):上游 [mcpplibs/ffmpeg-m](https://github.com/mcpplibs/ffmpeg-m) + 自带 `mcpp.toml`,描述符只声明元数据 + tag tarball 下载地址,默认查找 + `/*/mcpp.toml`。 +- 包内容 = 纯 C++23 模块层(`import ffmpeg.av;` 及 7 个分库模块),FFmpeg 全部 + 源码经其 `compat.ffmpeg = "8.1.2"` 依赖到达消费者(全源码直编形态,零预构建)。 + C API 名称与用法完全不变。 +- **linux-only**(先例 `compat.x11`):compat.ffmpeg 目前仅有 linux-x86_64 configure + 快照;macOS 阻塞于 mcpp#229(依赖侧 cfg 条件源)。 +- license:模块层 MIT;上游经 compat.ffmpeg 为 LGPL-2.1-or-later(无 GPL/nonfree 组件)。 + +## CN 镜像 + +gitcode `mcpp-res/ffmpeg`(与 compat 上游 tarball 同仓,tag 不冲突): +release `v0.0.1`,资产 `ffmpeg-m-0.0.1.tar.gz` = GitHub +`archive/refs/tags/v0.0.1.tar.gz` 同字节,`gtc release publish` 上传,sha256 一致。 + +## 验证方式(imgui-module 先例) + +默认命名空间(namespace = "")的包尚不能作为 workspace 成员指向本地 path index, +故沿用 `imgui-module` 的例外通道:`tests/smoke_ffmpeg_module.sh` 重播默认 index +后以 `[dependencies] ffmpeg = "0.0.1"` 消费,gcc@16.1.0,断言 libavutil 主版本、 +4 个解码器存在、demuxer 数 > 300。仅 Linux(非 Linux 直接 SKIP)。 +smoke 内先 `mcpp index update` 刷新沙箱包索引(nasm 自举,mcpp#232 规避;置于 +重播默认 index 之前,避免刷新覆盖重播结果)。validate.yml 新增 `ffmpeg-module` +job(ubuntu-only,镜像 imgui-module 结构)。 + +## 描述符流水线收编(tools/compat-ffmpeg/) + +`compat.ffmpeg.lua` 为生成物(329 KB,禁止手改)。生成器原在 ffmpeg-m +(`tools/gen_config.sh`/`gen_descriptor.py`),随本次 PR 迁至本仓 +`tools/compat-ffmpeg/`(fetch_upstream.sh + gen_config.sh + gen_descriptor.py), +与其生成的描述符同仓维护;ffmpeg-m 只保留模块层与导出面生成器(gen_exports.py)。 +迁移回归:迁移后重跑流水线,与已合并描述符 diff 仅头注释 2 行(2281 源 + 15 +生成文件字节一致;host gcc 13 与原生成环境结果一致,可复现)。 +注意:宿主 `cc` 若被 xlings subos 切到交叉工具链会导致 configure 失败,用 +`CC=/usr/bin/gcc` 显式指定。 + +## FFmpeg 版本升级 SOP + +1. 本仓:改 `tools/compat-ffmpeg/fetch_upstream.sh` 的 version/sha256 pin → + `sh tools/compat-ffmpeg/gen_config.sh` 重生成描述符 → 上传新上游 tarball 到 + CN 镜像 → PR。 +2. ffmpeg-m:bump `compat.ffmpeg` 依赖 + `tools/fetch_upstream.sh` pin → + `python3 tools/gen_exports.py` 重生成导出面 → 审阅 diff → 发新 tag → + 本仓登记新版本条目。 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index eec7233..8c5c60f 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -354,3 +354,40 @@ jobs: run: | "$MCPP" --version timeout 1800 bash tests/smoke_imgui_module.sh + + # ── Exception: the public `ffmpeg` C++23-module package (namespace "") ── + # Same default-namespace limitation as imgui-module above — validated via a + # reseeding smoke driver instead of a workspace member. Linux-only: its + # compat.ffmpeg dependency ships a linux-x86_64 configure snapshot (macOS is + # blocked on mcpp#229 dependency cfg-conditional sources). + ffmpeg-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_EFFECTIVE }}-${{ hashFiles('pkgs/**/*.lua', 'tests/**', '.github/workflows/validate.yml') }} + restore-keys: | + mcpp-registry-${{ runner.os }}-${{ env.MCPP_EFFECTIVE }}- + - name: Download mcpp + run: | + curl -L -fsS -o mcpp.tar.gz \ + "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/mcpp-${MCPP_VERSION}-linux-x86_64.tar.gz" + tar -xzf mcpp.tar.gz + root="$PWD/mcpp-${MCPP_VERSION}-linux-x86_64" + mkdir -p "$HOME/.mcpp/registry" + cp -a "$root/registry/." "$HOME/.mcpp/registry/" + echo "MCPP=$root/bin/mcpp" >> "$GITHUB_ENV" + echo "MCPP_VENDORED_XLINGS=$root/registry/bin/xlings" >> "$GITHUB_ENV" + echo "$root/bin" >> "$GITHUB_PATH" + - name: ffmpeg module package smoke + env: + MCPP_INDEX_MIRROR: GLOBAL + run: | + "$MCPP" --version + timeout 2700 bash tests/smoke_ffmpeg_module.sh diff --git a/README.md b/README.md index b55e282..9187f92 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 本仓收录两类包: -- **原生 mcpp 模块库**:以 C++23 模块发布、`import` 即用,包括 `mcpplibs.*`、`nlohmann.json`、`imgui`,以及由 +- **原生 mcpp 模块库**:以 C++23 模块发布、`import` 即用,包括 `mcpplibs.*`、`nlohmann.json`、`imgui`、`ffmpeg`,以及由 用户基于 mcpp 开发并登记进索引的库(如 `tensorvia-cpu`)。其上游通常自带 `mcpp.toml`,描述文件(Form A)只声明 元数据与下载地址。 - **第三方 C/C++ 库(`compat`)**:其上游不提供 mcpp 支持,描述文件(Form B)内联构建信息。该类库存在 @@ -32,7 +32,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 | 形态 | 示例 | |------|------| -| 原生模块库(Form A) | [`mcpplibs.xpkg`](pkgs/x/xpkg.lua) · [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](pkgs/t/tensorvia-cpu.lua) | +| 原生模块库(Form A) | [`mcpplibs.xpkg`](pkgs/x/xpkg.lua) · [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](pkgs/t/tensorvia-cpu.lua) · [`ffmpeg`](pkgs/f/ffmpeg.lua)(模块层,源码经 `compat.ffmpeg` 直编) | | 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) | diff --git a/pkgs/c/compat.ffmpeg.lua b/pkgs/c/compat.ffmpeg.lua index 7abb7e8..8d0c7d6 100644 --- a/pkgs/c/compat.ffmpeg.lua +++ b/pkgs/c/compat.ffmpeg.lua @@ -1,4 +1,4 @@ --- Auto-generated by ffmpeg-m/tools/gen_descriptor.py — do not edit by hand. +-- Auto-generated by tools/compat-ffmpeg/gen_descriptor.py — do not edit by hand. -- Recipe: FFmpeg 8.1.2 built FROM SOURCE by mcpp (no configure/make at -- build time). Full internal-component hermetic profile, LGPL-2.1+ (configure: -- --disable-autodetect --disable-programs --disable-doc; everything else @@ -6,7 +6,7 @@ -- PATH → sandbox → xlings). The config snapshot below is deterministic for -- (linux-x86_64, this profile) and shipped as generated_files; the source -- list is the `make -n` ground truth of the same configure run. --- Regenerate: ffmpeg-m tools/gen_descriptor.py (see that repo's docs). +-- Regenerate: sh tools/compat-ffmpeg/gen_config.sh (this repo). package = { spec = "1", namespace = "compat", diff --git a/pkgs/f/ffmpeg.lua b/pkgs/f/ffmpeg.lua new file mode 100644 index 0000000..69442bd --- /dev/null +++ b/pkgs/f/ffmpeg.lua @@ -0,0 +1,34 @@ +-- Form A descriptor: the public ffmpeg module package ships its own +-- mcpp.toml. mcpp's default lookup finds /*/mcpp.toml inside +-- the GitHub source tarball wrap. +-- +-- The package is the thin C++23 module layer (import ffmpeg.av / per-lib +-- ffmpeg.avcodec, …) over FFmpeg's unchanged C API; the FFmpeg sources +-- themselves arrive through its compat.ffmpeg dependency (full source +-- build, config snapshot — see pkgs/c/compat.ffmpeg.lua). Linux-only for +-- now: compat.ffmpeg carries a linux-x86_64 configure snapshot (macOS +-- blocked on mcpp#229 dependency cfg-conditional sources). +-- +package = { + spec = "1", + name = "ffmpeg", + namespace = "", + description = "C++23 module package for FFmpeg (import ffmpeg.av) — full source build via compat.ffmpeg, C API unchanged", + licenses = {"MIT"}, -- module layer; upstream via compat.ffmpeg is LGPL-2.1-or-later + repo = "https://github.com/mcpplibs/ffmpeg-m", + type = "package", + + xpm = { + linux = { + ["0.0.1"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/ffmpeg-m/archive/refs/tags/v0.0.1.tar.gz", + CN = "https://gitcode.com/mcpp-res/ffmpeg/releases/download/v0.0.1/ffmpeg-m-0.0.1.tar.gz", + }, + sha256 = "e47dc9683f62466e9bd1c962a5f6ee83fbe3670fdb20c834bd9e8ee3f30e49fb", + }, + }, + }, + + -- (no `mcpp` field -- default lookup will find /*/mcpp.toml) +} diff --git a/tests/smoke_ffmpeg_module.sh b/tests/smoke_ffmpeg_module.sh new file mode 100755 index 0000000..1cef89c --- /dev/null +++ b/tests/smoke_ffmpeg_module.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# Smoke-test the public ffmpeg module package through this checkout as a local +# mcpp path index. This validates user-facing import-only consumption: +# `[dependencies] ffmpeg = "0.0.1"` → `import ffmpeg.av;`. Linux-only (the +# package's compat.ffmpeg dependency ships a linux-x86_64 config snapshot). +set -euo pipefail + +if [[ "$(uname -s)" != "Linux" ]]; then + echo "SKIP: ffmpeg module package is linux-only for now." + exit 0 +fi + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +MCPP_BIN="${MCPP:-}" +if [[ -z "$MCPP_BIN" ]]; then + MCPP_BIN="$(command -v mcpp || true)" +fi +if [[ -z "$MCPP_BIN" || ! -x "$MCPP_BIN" ]]; then + echo "FATAL: set MCPP=/path/to/mcpp or put mcpp on PATH" >&2 + exit 1 +fi + +TMP="$(mktemp -d)" +if [[ "${MCPP_INDEX_KEEP_SMOKE_TMP:-0}" == "1" ]]; then + echo "KEEP: $TMP" +else + trap 'rm -rf "$TMP"' EXIT +fi + +if [[ -n "${MCPP_INDEX_SMOKE_MCPP_HOME:-}" ]]; then + export MCPP_HOME="$MCPP_INDEX_SMOKE_MCPP_HOME" +else + export MCPP_HOME="$TMP/mcpp-home" +fi +mkdir -p "$MCPP_HOME/registry/data/xpkgs" + +USER_MCPP="${HOME}/.mcpp" +link_xpkgs() { + local src="$1" + [[ -d "$src" ]] || return 0 + find "$src" -mindepth 1 -maxdepth 1 -type d | while read -r pkg; do + ln -s "$pkg" "$MCPP_HOME/registry/data/xpkgs/$(basename "$pkg")" 2>/dev/null || true + done +} +link_xpkgs "${MCPP_INDEX_SMOKE_XPKGS_DIR:-}" +link_xpkgs "$USER_MCPP/registry/data/xpkgs" +if [[ -d "$USER_MCPP/registry/data/xim-pkgindex" ]]; then + mkdir -p "$MCPP_HOME/registry/data/xim-pkgindex" + cp -a "$USER_MCPP/registry/data/xim-pkgindex/." "$MCPP_HOME/registry/data/xim-pkgindex/" 2>/dev/null || true + rm -f "$MCPP_HOME/registry/data/xim-pkgindex/.xlings-index-cache.json" +fi +if [[ -d "$USER_MCPP/registry/bin" ]]; then + mkdir -p "$MCPP_HOME/registry" + ln -s "$USER_MCPP/registry/bin" "$MCPP_HOME/registry/bin" 2>/dev/null || true +fi +if [[ -f "$USER_MCPP/config.toml" ]]; then + cp -f "$USER_MCPP/config.toml" "$MCPP_HOME/config.toml" 2>/dev/null || true +fi + +# compat.ffmpeg carries NASM .asm sources; mcpp bootstraps nasm from the +# sandbox package index. Release archives vendor a pre-xim-pkgindex#398 +# snapshot whose nasm.lua "installs" empty (mcpp#232) — refresh BEFORE +# reseeding the default index below so the update can't clobber the reseed. +"$MCPP_BIN" index update + +default_index="$MCPP_HOME/registry/data/mcpplibs" +# Reseed cleanly (see smoke_imgui_module.sh for why .git is skipped). +rm -rf "$default_index" +mkdir -p "$default_index" +( cd "$ROOT" && find . -mindepth 1 -maxdepth 1 ! -name .git -exec cp -a {} "$default_index/" \; ) +rm -f "$default_index/.xlings-index-cache.json" +printf 'ok\n' > "$default_index/.mcpp-index-updated" + +"$MCPP_BIN" self config --mirror "${MCPP_INDEX_MIRROR:-GLOBAL}" >/dev/null + +mkdir -p "$TMP/ffmpeg-module-smoke/src" +cd "$TMP/ffmpeg-module-smoke" +cat > mcpp.toml < src/main.cpp <<'EOF' +import std; +import ffmpeg.av; + +int main() { + unsigned v { avutil_version() }; + std::println("libavutil {}.{} module package ok", v >> 16, (v >> 8) & 0xff); + if ((v >> 16) != 60u) { // libavutil major of the FFmpeg 8.1.x train + return 1; + } + + for (auto name : { "h264", "hevc", "av1", "aac" }) { + const AVCodec* dec { avcodec_find_decoder_by_name(name) }; + if (dec == nullptr) { + std::println("decoder {} MISSING", name); + return 2; + } + } + + void* it { nullptr }; + long demuxers { 0 }; + while (av_demuxer_iterate(&it)) ++demuxers; + std::println("{} demuxers registered", demuxers); + return demuxers > 300 ? 0 : 3; +} +EOF + +"$MCPP_BIN" build +"$MCPP_BIN" run diff --git a/tools/compat-ffmpeg/fetch_upstream.sh b/tools/compat-ffmpeg/fetch_upstream.sh new file mode 100755 index 0000000..b6b6a26 --- /dev/null +++ b/tools/compat-ffmpeg/fetch_upstream.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# fetch_upstream.sh — download + extract the pinned official FFmpeg tarball +# for maintainer-time regeneration (gen_config/gen_exports/gen_descriptor). +# Consumers never need this: they get FFmpeg via the compat.ffmpeg package. +# +# Prints the extracted source root on stdout. +set -eu + +version="${FFMPEG_VERSION:-8.1.2}" +sha256="${FFMPEG_SHA256:-32faba5ef67340d54724941eae1425580791195312a4fd13bf6f820a2818bf22}" +cache="${XDG_CACHE_HOME:-$HOME/.cache}/ffmpeg-m" +tarball="$cache/ffmpeg-$version.tar.gz" +srcdir="$cache/ffmpeg-$version" + +mkdir -p "$cache" +if [ ! -f "$tarball" ]; then + curl -L -fsS -o "$tarball" "https://ffmpeg.org/releases/ffmpeg-$version.tar.gz" >&2 +fi +echo "$sha256 $tarball" | sha256sum -c - >&2 +if [ ! -d "$srcdir" ]; then + tar -xzf "$tarball" -C "$cache" +fi +echo "$srcdir" diff --git a/tools/compat-ffmpeg/gen_config.sh b/tools/compat-ffmpeg/gen_config.sh new file mode 100755 index 0000000..8d67dac --- /dev/null +++ b/tools/compat-ffmpeg/gen_config.sh @@ -0,0 +1,50 @@ +#!/bin/sh +# gen_config.sh — maintainer-time descriptor pipeline for compat.ffmpeg. +# +# Fetches the pinned OFFICIAL FFmpeg tarball (fetch_upstream.sh alongside), runs +# ./configure ONCE (out-of-tree, hermetic full profile), captures every +# generated file plus the `make -n` source list, and emits the mcpp-index +# descriptor via gen_descriptor.py alongside. Consumers never run this: FFmpeg +# reaches them as the compat.ffmpeg package. +# +# Usage: tools/compat-ffmpeg/gen_config.sh [target] [out.lua] +# target default: autodetected - (only linux-x86_64 supported yet) +# out.lua default: /pkgs/c/compat.ffmpeg.lua +set -eu + +target="${1:-$(uname -s | tr 'A-Z' 'a-z' | sed s/darwin/macos/)-$(uname -m | sed s/arm64/aarch64/)}" +here="$(cd "$(dirname "$0")" && pwd)" +index_root="$(cd "$here/../.." && pwd)" +version="${FFMPEG_VERSION:-8.1.2}" +sha256="${FFMPEG_SHA256:-32faba5ef67340d54724941eae1425580791195312a4fd13bf6f820a2818bf22}" +default_out="$index_root/pkgs/c/compat.ffmpeg.lua" +out="${2:-$default_out}" + +src="$(sh "$here/fetch_upstream.sh")" +bld="$(mktemp -d /tmp/ffmpeg-m-cfg.XXXXXX)" +trap 'rm -rf "$bld"' EXIT + +# Full-feature hermetic profile: every INTERNAL component on; nothing probed +# from the host (--disable-autodetect) so the snapshot is reproducible and the +# consumer build needs no system libraries. programs/doc are irrelevant to a +# library package. +cd "$bld" +"$src/configure" \ + --cc="${CC:-cc}" \ + --disable-autodetect \ + --disable-programs \ + --disable-doc + +# ffversion.h is normally a make-time product; generate it here with a RELATIVE +# output path (an absolute path would leak into the include guard). +sh "$src/ffbuild/version.sh" "$src" libavutil/ffversion.h + +# The `make -n` dry run is the ground truth for which .c/.S/.asm files this +# frozen configuration compiles (CONFIG_* gating + _select closures resolved). +make -n > make-n.log 2>/dev/null || true +grep -oE '(\.\./[^ ]+|src)/[A-Za-z0-9_/.+-]+\.(c|S|asm)\b' make-n.log \ + | sed "s|^\.\./ffmpeg-$version/|src/|; s|^$src/|src/|" \ + | grep '^src/' | sort -u > sources.txt + +python3 "$here/gen_descriptor.py" "$bld" "$version" "$sha256" "$out" +echo "descriptor written: $out (configured on: $target)" diff --git a/tools/compat-ffmpeg/gen_descriptor.py b/tools/compat-ffmpeg/gen_descriptor.py new file mode 100755 index 0000000..946a659 --- /dev/null +++ b/tools/compat-ffmpeg/gen_descriptor.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +"""gen_descriptor.py — emit the mcpp-index compat.ffmpeg.lua descriptor. + +Inputs are the products of one hermetic configure run made from the OFFICIAL +release tarball (tools/gen_config.sh logic, out-of-tree): + + /config.h, config_components.h, config.asm, config_components.asm + /libavutil/{avconfig.h, ffversion.h} + /lib{avcodec,avformat,avfilter,avdevice}/*_list.c + /sources.txt (make -n derivation, `src/...` prefixed) + +The descriptor inlines the config snapshot as `generated_files` (they are +deterministic pure text for a frozen (target, profile)) and the exact source +list, so consumers build FFmpeg from source with zero configure/make at any +point — the compat package is the single source of truth for the recipe. + +Usage: gen_descriptor.py +""" + +import sys +from pathlib import Path + +GEN_FILES = [ + "config.h", + "config_components.h", + "config.asm", + "config_components.asm", + "libavutil/avconfig.h", + "libavutil/ffversion.h", + "libavcodec/codec_list.c", + "libavcodec/parser_list.c", + "libavcodec/bsf_list.c", + "libavformat/demuxer_list.c", + "libavformat/muxer_list.c", + "libavformat/protocol_list.c", + "libavfilter/filter_list.c", + "libavdevice/indev_list.c", + "libavdevice/outdev_list.c", +] + +INCLUDE_DIRS = [ + "mcpp_generated", + "mcpp_generated/libavcodec", + "mcpp_generated/libavformat", + "mcpp_generated/libavfilter", + "mcpp_generated/libavdevice", + "*", + # libavcodec root for the sub-Makefile dirs' quote-includes (aac/bsf/hevc/ + # opus/vvc include "mathops.h" etc. from their library root). Safe: no + # system-header names are shadowed from here. + "*/libavcodec", + # NASM receives every include dir automatically (-I), which is how the + # .asm units find x86inc/x86util and config.asm. + "*/libavutil/x86", + "*/libavcodec/x86", + "*/libavfilter/x86", + "*/libswscale/x86", + "*/libswresample/x86", +] + +CFLAGS = [ + "-DHAVE_AV_CONFIG_H", + "-D_ISOC11_SOURCE", "-D_FILE_OFFSET_BITS=64", "-D_LARGEFILE_SOURCE", + "-D_POSIX_C_SOURCE=200112", "-D_XOPEN_SOURCE=600", "-DPIC", + "-fomit-frame-pointer", "-fno-math-errno", "-fno-signed-zeros", + "-pthread", "-w", +] + +BUILDING = ["avutil", "avcodec", "avformat", "avfilter", "avdevice", + "swscale", "swresample"] + + +def lua_str_list(items, indent): + pad = " " * indent + return ("{\n" + "".join(f'{pad} "{i}",\n' for i in items) + pad + "}") + + +def main() -> None: + builddir, version, sha256, out_path = (Path(sys.argv[1]), sys.argv[2], + sys.argv[3], Path(sys.argv[4])) + + sources = ["*/" + line.strip().removeprefix("src/") + for line in (builddir / "sources.txt").read_text().splitlines() + if line.strip()] + n_asm = sum(1 for s in sources if s.endswith(".asm")) + + gen_entries = [] + for rel in GEN_FILES: + text = (builddir / rel).read_text() + if "]==]" in text: + sys.exit(f"gen_descriptor: ']==]' inside {rel} — pick another delimiter") + gen_entries.append(f' ["mcpp_generated/{rel}"] = [==[\n{text}]==],') + + flags_entries = [ + f' {{ glob = "*/lib{lib}/**", defines = {{ "BUILDING_{lib}" }} }},' + for lib in BUILDING + ] + flags_entries.append( + ' { glob = "**/*.asm", asmflags = { "-Pconfig.asm" } },') + + url_global = f"https://ffmpeg.org/releases/ffmpeg-{version}.tar.gz" + url_cn = (f"https://gitcode.com/mcpp-res/ffmpeg/releases/download/" + f"{version}/ffmpeg-{version}.tar.gz") + + lua = f"""-- Auto-generated by tools/compat-ffmpeg/gen_descriptor.py — do not edit by hand. +-- Recipe: FFmpeg {version} built FROM SOURCE by mcpp (no configure/make at +-- build time). Full internal-component hermetic profile, LGPL-2.1+ (configure: +-- --disable-autodetect --disable-programs --disable-doc; everything else +-- default, x86 NASM asm included — {n_asm} .asm units, nasm resolved +-- PATH → sandbox → xlings). The config snapshot below is deterministic for +-- (linux-x86_64, this profile) and shipped as generated_files; the source +-- list is the `make -n` ground truth of the same configure run. +-- Regenerate: sh tools/compat-ffmpeg/gen_config.sh (this repo). +package = {{ + spec = "1", + namespace = "compat", + name = "compat.ffmpeg", + description = "FFmpeg {version} multimedia libraries, full source build (LGPL profile)", + licenses = {{"LGPL-2.1-or-later"}}, + repo = "https://ffmpeg.org", + type = "package", + + xpm = {{ + -- linux-x86_64 only for now: the config snapshot + source list are + -- target-specific; macOS/Windows land with their own snapshots. + linux = {{ + ["{version}"] = {{ + url = {{ + GLOBAL = "{url_global}", + CN = "{url_cn}", + }}, + sha256 = "{sha256}", + }}, + }}, + }}, + + mcpp = {{ + c_standard = "c17", + include_dirs = {lua_str_list(INCLUDE_DIRS, 8)}, + cflags = {lua_str_list(CFLAGS, 8)}, + flags = {{ +{chr(10).join(flags_entries)} + }}, + targets = {{ + ffmpeg = {{ kind = "lib" }}, + }}, + linux = {{ + ldflags = {{ "-lpthread", "-lm" }}, + }}, + sources = {lua_str_list(sources, 8)}, + generated_files = {{ +{chr(10).join(gen_entries)} + }}, + }}, +}} +""" + out_path.write_text(lua) + print(f"gen_descriptor: {out_path} written — {len(sources)} sources " + f"({n_asm} .asm), {len(GEN_FILES)} generated files, " + f"{len(lua) // 1024} KiB") + + +if __name__ == "__main__": + main()