Audacity 的 CI 如何校验 muse_framework 子模块指向上游 main 分支?
【免费下载链接】audacityAudio Editor项目地址: https://gitcode.com/GitHub_Trending/au/audacity
Audacity 4(master 分支)把 MuseScore 的muse_framework仓库作为 git 子模块(挂载在仓库根目录的muse路径下)来消费,构建代码依赖子模块所固定的那个 commit。子模块指针是很容易"漂"的:有人在自己的 fork 上调试后,.gitmodules的 URL 或muse指向的 SHA 会残留 fork 地址或游离 commit,直接进 PR。为避免 CI 构建到错误的框架代码,Audacity 用一个独立的 GitHub Actions 工作流 au4_check_submodules.yml 做两道检查:子模块 URL 必须等于上游地址,且固定 commit 必须位于上游仓库main分支的历史中。
本文说明这条校验链的触发条件、每一步的具体判断逻辑,以及如何在本地用同一条命令序列预检,避免 PR 被 CI 打回。
触发条件与检查对象
工作流的触发与任务定义如下(来自 au4_check_submodules.yml):
on: pull_request: workflow_dispatch: jobs: check_muse_framework: runs-on: ubuntu-latest env: EXPECTED_URL: https://github.com/musescore/muse_framework.git- 任何 PR 以及手动触发(
workflow_dispatch)都会运行该工作流; - 校验常量
EXPECTED_URL写死为https://github.com/musescore/muse_framework.git,即上游 muse_framework 的 git 地址; - 与构建类工作流不同,这个校验工作流没有
workflow_call输入、也没有覆盖子模块的机制——它只校验当前 PR 仓库里的配置,不可参数化绕过。
被校验的两个对象记录在 .gitmodules:
[submodule "muse"] path = muse url = https://github.com/musescore/muse_framework.git [submodule "muse_deps"] path = muse_deps url = https://github.com/musescore/muse_deps.git本工作流只检查muse(muse_framework)这一项。
第一步:校验子模块 URL 仍指向上游
工作流先actions/checkout@v6克隆 PR 仓库,然后执行:
ACTUAL_URL=$(git config -f .gitmodules submodule.muse.url) echo "Expected: ${EXPECTED_URL}" echo "Actual: ${ACTUAL_URL}" if [ "${ACTUAL_URL}" != "${EXPECTED_URL}" ]; then echo "::error file=.gitmodules::muse_framework submodule URL must be ${EXPECTED_URL}, found ${ACTUAL_URL}. Did you accidentally leave it pointing at a fork?" exit 1 fi做法是从.gitmodules文件里直接读出muse子模块的url,与EXPECTED_URL做字符串比较。不相等就输出::error并exit 1,该步骤失败、整个 job 失败。错误信息里特意点出常见原因:"Did you accidentally leave it pointing at a fork?"——即指针 URL 被改成了个人 fork。
第二步:校验固定 commit 在上游 main 分支上
URL 对之后,还要确认子模块指向的 commit 真实存在于上游main分支。这一步的逻辑:
SHA=$(git ls-tree HEAD muse | awk '{print $3}') if [ -z "${SHA}" ]; then echo "::error::Could not read muse_framework submodule SHA from index" exit 1 fi echo "Pinned commit: ${SHA}" TMP=$(mktemp -d) git clone --quiet --filter=tree:0 --no-checkout --single-branch --branch main "${EXPECTED_URL}" "${TMP}" if ! git -C "${TMP}" merge-base --is-ancestor "${SHA}" origin/main; then echo "::error::muse_framework is pinned to ${SHA}, which is not on the main branch of ${EXPECTED_URL}" exit 1 fi各行的作用:
git ls-tree HEAD muse:从当前仓库的 git index 中读出muse路径指向的子模块 commit SHA(子模块在父仓库里就是一个 gitlink,记录的正是这个 SHA)。读不到则直接报错失败。git clone --filter=tree:0 --no-checkout --single-branch --branch main:只克隆上游仓库的main分支引用,不做 checkout,目的是拿到足够做祖先判定(origin/main历史)的最小克隆,避免为一次校验拉取全量对象。git merge-base --is-ancestor "${SHA}" origin/main:判断固定 SHA 是否是上游origin/main的祖先。退出码非 0 表示该 commit 不在上游 main 分支上,CI 报错:muse_framework is pinned to ${SHA}, which is not on the main branch of ${EXPECTED_URL}
两步全部通过,check_muse_frameworkjob 才算成功。
本地预检:PR 前先跑同一套判断
CI 里的判断就是普通 git 命令,在 clone 下来的 Audacity 4 源码目录里可以原样执行(BUILDING.md 建议用git clone --recurse-submodules https://github.com/audacity/audacity.git获取源码,见 BUILDING.md)。假设当前目录是仓库根:
# 1. URL 是否与上游一致 git config -f .gitmodules submodule.muse.url # 期望输出: https://github.com/musescore/muse_framework.git # 2. 当前 index 固定的 commit git ls-tree HEAD muse # 3. 固定 commit 是否在上游 main 分支 TMP=$(mktemp -d) git clone --quiet --filter=tree:0 --no-checkout --single-branch --branch main \ https://github.com/musescore/muse_framework.git "${TMP}" git -C "${TMP}" merge-base --is-ancestor "$(git ls-tree HEAD muse | awk '{print $3}')" origin/main echo "exit code: $?"merge-base --is-ancestor返回 0 表示固定 SHA 在上游 main 的历史中,非 0 表示不在(通常是 fork 上的 commit 或已 rebase 掉的提交),PR 会在 CI 中失败。第 3 步会实际 clone 上游仓库到临时目录,本地执行前留意这一点。
CI 失败时如何定位
工作流失败时会留下两类明确错误,对应不同修法:
| 报错信息 | 含义 | 修正位置 |
|---|---|---|
muse_framework submodule URL must be …, found … | .gitmodules中muse的 URL 不是上游地址(常见为指向 fork) | 改回 .gitmodules 中submodule "muse"的url |
Could not read muse_framework submodule SHA from index | 读不到muse的 gitlink SHA | 检查muse路径的子模块状态是否完整 |
muse_framework is pinned to ${SHA}, which is not on the main branch of … | 固定 commit 不在上游main上 | 把muse子模块更新为上游main上的 commit 后提交新的指针 |
与构建工作流的关系:override 不能代替校验
各平台构建工作流(如 au4_build_linux.yml、au4_build_macos.yml、au4_build_windows.yml)支持通过workflow_call输入framework_repo和framework_ref覆盖子模块来源,用于跨仓库/调试构建:
framework_repo: description: 'owner/repo to source muse_framework from (default: pinned submodule)' framework_ref: description: 'Ref to check out into the muse_framework submodule (default: pinned SHA)'默认情况下构建使用仓库里固定的子模块(submodules: recursive克隆);只有传入了framework_ref,或事件是schedule定时构建时,才执行actions/checkout把musescore/muse_framework的main检出到muse路径覆盖固定指针。也就是说,override 只影响构建 job 临时消费哪个版本的框架,不改变au4_check_submodules.yml对 PR 内容的要求:合入前的子模块 URL 和固定 SHA 必须满足上述两项校验。
限制说明
- 校验只覆盖
muse(muse_framework)子模块,.gitmodules中的muse_deps不在此工作流检查范围内。 - 判定标准是"固定 SHA 是上游
main的祖先",因此上游main之后删除该 commit(例如 force-push 重写历史)会让原本通过的指针在之后的 PR 中失败,需要重新把子模块指到main上存在的 commit。 - 该工作流运行在
ubuntu-latest上,只做 git 层面的校验,不编译任何代码,因此失败原因只可能来自.gitmodules、musegitlink 与上游main历史三者的不一致。
【免费下载链接】audacityAudio Editor项目地址: https://gitcode.com/GitHub_Trending/au/audacity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考