- 图形学
- 图像处理
- 跨平台
【免费下载链接】SkiaSharp
SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.
导读
本文围绕 SkiaSharp 仓库中.agents/skills/update-skia技能所定义的更新流程第 04–05 阶段展开:如何在父仓库(mono/SkiaSharp)与externals/skia子模块(mono/skia fork)中创建精确对齐的功能分支,以及如何对上游 Google Skia 完成一次"真正的双亲合并"(genuine two-parent merge),并在合并过程中对 fork 补丁与依赖逐项分类、记录处置结论。读完本文,你将掌握prepare_branches.py与audit_fork_patches.py两个确定性辅助脚本的完整使用方式、冲突文件的分类审计方法、DEPS依赖决策表的标准,以及合并提交前的全部 Gate 检查项。
前置条件:Phase 03 分析与评审产物
执行本阶段前,必须已通过 Phase 01–03 的 Gate(详见 .agents/skills/update-skia/references/phases/01-03-research.md),并且只允许读取known-gotchas.md中与合并直接相关的四个章节:DEPS: Fork-Customized Dependencies、Genuine Merge Required、Conflict Resolution by File Category、Verify-Upstream-or-Reapply,文件位于 .agents/skills/update-skia/references/known-gotchas.md。
同时需要准备好以下运行时变量(由自动化导出,或本地按 .agents/skills/update-skia/SKILL.md 中的SKIA_SYNC_*变量表解析):
| 运行时值 | 环境变量 | 含义 |
|---|---|---|
{UPSTREAM_REF} | SKIA_SYNC_UPSTREAM_REF | chrome/m{TARGET}或main |
{BASE_BRANCH} | SKIA_SYNC_BASE_BRANCH | 父 PR 基准分支 |
{PARENT_BASE_SHA} | SKIA_SYNC_PARENT_BASE_SHA | 父基准精确提交 |
{SKIA_BASE_BRANCH} | SKIA_SYNC_SKIA_BASE_BRANCH | mono/skia PR 基准分支 |
{SKIA_BASE_SHA} | SKIA_SYNC_SKIA_BASE_SHA | 父基准记录的精确 mono/skia 提交 |
{HEAD_BRANCH} | SKIA_SYNC_HEAD_BRANCH | 两个仓库共用的功能分支名 |
并初始化产物目录:
ARTIFACT_DIR="${SKIA_SYNC_ARTIFACT_DIR:-${TMPDIR:-/tmp}/skia-sync-agent}" mkdir -p "$ARTIFACT_DIR"Phase 04:使用确定性辅助脚本创建匹配的功能分支
4.1 为什么必须用脚本而非手动操作
新更新任务必须使用prepare_branches.py这一确定性分支辅助脚本。它消费的是 Phase 01 解析出的精确父基准 SHA 与 Skia SHA,而不是当前分支 tip;文档明确要求"不要用当前分支 tip 替换它们,也不要手工复现其 Git 操作"。
python3 "${SKIA_SYNC_SKILL_DIR:-.agents/skills/update-skia}/scripts/prepare_branches.py" \ --repo-root "${GITHUB_WORKSPACE:-$PWD}"脚本源码位于 .agents/skills/update-skia/scripts/prepare_branches.py,其关键行为包括:
- 失败优先于破坏:如果目标功能分支已存在、工作区不干净,脚本会直接报错退出,而不会 reset 已有分支或脏文件;
- 父分支从精确 SHA 出发:
git checkout -b {HEAD_BRANCH} {PARENT_BASE_SHA},随后git submodule sync --recursive与git submodule update --init --recursive,使每个递归子模块都精确对齐父提交; - gitlink 一致性校验:通过
git ls-tree {PARENT_BASE_SHA} -- externals/skia验证父提交记录的子模块指针必须等于{SKIA_BASE_SHA},否则报Resolved Skia base does not match the parent gitlink; - 子模块状态校验:
git submodule status --recursive中任何以-、+、U开头的行都会触发失败;并核对externals/skia的实际HEAD是否等于{SKIA_BASE_SHA}; - 分支名合法性校验:对父仓库与 skia 子模块都执行
git check-ref-format --branch {HEAD_BRANCH}。
4.2 分支已存在时的处理
如果任一功能分支已存在,必须停止并检查其基准与提交内容:只有当它是预期延续时才可复用,绝不 reset 或覆盖无关工作。这一点也体现在脚本的branch_exists检查中——已存在的同名分支直接导致PreparationError。
4.3 脚本行为的测试佐证
仓库提供了对应的单元测试 .agents/skills/update-skia/scripts/prepare_branches_test.py,其中的test_release_branch_realigns_all_submodules_before_branching_skia验证了 release 场景下脚本会:将父仓库切到{HEAD_BRANCH}并停在release_parent_sha、将docs子模块回退到release_docs_sha、将externals/skia切到同名分支且停在release_skia_sha、最终工作区干净且子模块无-+U失配。这印证了"脚本负责把所有递归子模块与父提交对齐后再切 Skia 分支"的行为。
Phase 05:真正的上游合并
5.1 合并前先快照 fork 补丁
合并开始前,先进入子模块并生成合并基准(merge-base)到当前 fork 基准分支之间的补丁清单,作为后续冲突审计的对照底稿:
cd externals/skia MB=$(git merge-base "origin/{SKIA_BASE_BRANCH}" "upstream/{UPSTREAM_REF}") git log --oneline "$MB..origin/{SKIA_BASE_BRANCH}" \ > "$ARTIFACT_DIR/fork-patches-before.txt" git merge --no-commit --no-ff "upstream/{UPSTREAM_REF}"git merge --no-commit --no-ff意味着合并会停在冲突/待提交状态,方便逐个文件审计后再统一提交——这正是 .agents/skills/update-skia/references/known-gotchas.md 中 gotcha #13 强调的"绝不用 tree-override 合并(git merge -s ours、git read-tree --reset),它摧毁全部 C API 文件的 git blame 归因"。
5.2 冲突审计:先批量收集,再逐个分类
发生冲突时,先批量审计,再解析任何文件:
git diff --name-only --diff-filter=U > "$ARTIFACT_DIR/conflicted-files.txt" while IFS= read -r file; do printf '\n## %s\n' "$file" git log --oneline "$MB..origin/{SKIA_BASE_BRANCH}" -- "$file" done < "$ARTIFACT_DIR/conflicted-files.txt" \ > "$ARTIFACT_DIR/conflict-fork-history.md"即:先收集所有未合并(Unmerged)路径,再对每个冲突文件列出 fork 侧触及它的提交历史,之后才开始逐个分类处置。
5.3 fork 补丁的三分类处置
每个触及冲突的 fork 补丁必须归类为以下三种之一:
- Upstreamed(已上游化):保留上游等价的(或更优的)形态,并记录其 SHA;
- Not upstreamed(未上游化):在目标变更之上重新应用 fork 行为,绝不能丢弃;
- Obsolete by design(设计上已过时):仅当替代行为已被证实时才使用,并记录证据。
判定"是否算 fork 补丁"有严格口径:只有MB..origin/{SKIA_BASE_BRANCH}中的某个提交引入或修改过的内容才算 fork 补丁。仅仅因为 fork 依赖了旧上游里程碑继承下来的行为,不构成 fork 补丁;如果目标移除了该行为,应作为后续的兼容性适配(compatibility adaptation)处理,而不是在合并冲突阶段强行保留。
严禁在分类完一个源文件/构建文件/依赖文件中的所有 fork 变更之前,用一刀切的ours/theirs解析整个文件。gotcha #14 给出了按文件类别的推荐策略:
| 文件类别 | 策略 |
|---|---|
BUILD.gn | 两边合并——最复杂,既要保留上游结构也要保留 SkiaSharp 的平台 flag/目标 |
DEPS | 合并——对每个差异应用有证据支持的 preserve/accept/compatibility 决策 |
RELEASE_NOTES.md、infra/ | 取上游 |
C API 头文件(include/c/) | 保留 SkiaSharp——上游不存在这些文件 |
C API 源码(src/c/) | 保留 SkiaSharp + 适配——在合并后提交中修复 include 与 API 调用 |
其他上游源码(src/、include/) | 先查历史——见 gotcha #15 |
gotcha #15(Verify-Upstream-or-Reapply)进一步指出:冲突文件中的 fork 补丁只可能处于两种状态之一,必须先判定再解析,--theirs/--ours的 blanket 用法永远不可接受:
# 1. 找出触及该文件的 fork 补丁 git log --oneline {SKIA_BASE_BRANCH} -- <conflicted-file> # e.g. skiasharp # 2. 对每个 fork 补丁,检查上游是否已包含同样变更 git log --oneline upstream/{UPSTREAM_REF} --grep "<补丁主题中的关键短语>" git log -S "<补丁中具有辨识度的代码行>" --oneline upstream/{UPSTREAM_REF} -- <conflicted-file>| 补丁状态 | 解析方式 |
|---|---|
| Upstreamed——上游已含等价(或精炼后)形态 | 采用上游形态,记录"<subject>" upstreamed as <sha> |
| Not upstreamed——只有 fork 携带 | 在上游改动之上重放 fork 变更,记录"<subject>" re-applied,绝不丢弃 |
5.4 以 fork 基准审计依赖(DEPS)
把完整的 fork 与目标DEPS文件导出并 diff:
git show "origin/{SKIA_BASE_BRANCH}:DEPS" > "$ARTIFACT_DIR/deps-fork.txt" git show "upstream/{UPSTREAM_REF}:DEPS" > "$ARTIFACT_DIR/deps-target.txt" diff -u "$ARTIFACT_DIR/deps-fork.txt" "$ARTIFACT_DIR/deps-target.txt"注意:比较基准是fork 基准而非旧上游里程碑——gotcha #8 明确 SkiaSharp fork 常常持有更新的依赖版本或刻意不同的启用状态,不能盲目采纳上游的 hash 或注释状态。每个 revision 差异与启用/注释状态差异都必须有对应决策:
| 决策 | 必需证据 |
|---|---|
| Preserve fork(保留 fork 版本) | 有 fork 补丁、构建文件、安全更新或产品选择依赖它 |
| Accept target(采纳目标版本) | 没有 fork 定制依赖旧状态 |
| Compatibility roll(兼容性滚动) | 目标源码使用了 fork 旧版本缺失的依赖 API |
对于兼容性敏感的版本,要检查上游 roll 提交及其耦合的源码改动,然后验证所选 revision 是否包含目标源码需要的每个字段/函数。文档特别强调:无条件保留所有 pin 与无条件采纳所有目标 pin 一样危险。
最终把合并后的处置写入$ARTIFACT_DIR/skia-dependency-decisions.md:替换临时行而不是追加第二种结论;若某行写的是 accept-target 但合并后的DEPS仍保留 fork revision,这是 Gate 失败。该文件在 Phase 02 已写入临时决策(见 .agents/skills/update-skia/references/phases/01-03-research.md),Phase 05 在此完成最终化。
5.5 完成合并:双亲合并提交
提交前逐项验证:
- 新增/删除的源文件与 GN 构建列表一致;
- C API 与 fork 构建目标完好;
- 每个冲突与 fork 补丁都有记录的处置结论;
- 将最终
DEPS与 fork 基准、目标两方比较,确认每个依赖决策行指出的 revision 与启用/注释状态与合并后文件实际一致; - 运行
git diff --check,确认没有残留的未解析路径。
然后创建恰好两个父提交的合并提交,父提交必须是所选的 mono/skia 基准与目标上游提交。构建驱动的依赖或 C API 适配属于合并之后的独立提交——只有在需求被证实后才添加,不能顺手塞进合并提交里。
5.6 diff-of-diffs 审计:冲突解析不等于完整审计
整文件解析可能悄悄丢掉另一个不冲突的 fork 补丁,因此合并后必须回到父仓库根目录,运行audit_fork_patches.py生成"差异的差异"审计:
python3 "${SKIA_SYNC_SKILL_DIR:-.agents/skills/update-skia}/scripts/audit_fork_patches.py" \ --old-upstream "$SKIA_SYNC_BASE_UPSTREAM_SHA" \ --new-upstream "$SKIA_SYNC_TARGET_UPSTREAM_SHA" \ --fork-base "$SKIA_SYNC_SKIA_BASE_SHA" \ --merged-head HEAD \ --output "$ARTIFACT_DIR/skia-fork-patch-audit.md"该脚本源码位于 .agents/skills/update-skia/scripts/audit_fork_patches.py,核心原理:
- 计算两侧 fork delta:旧 delta 为
{old_upstream}..{fork_base},新 delta 为{new_upstream}..{merged_head},得到每个文件的补丁; - 规范化补丁:
normalize_patch去除diff --git、index、---/+++等位置元数据,但保留 hunk 上下文与二进制载荷,从而"位置移动不算变更、内容变化才算"; - 分类:按
added(新增补丁)、removed(丢失补丁)、changed(内容变化)、unchanged(原样保留)四类输出,并为每个变更行生成 16 位 SHA-256 指纹; - 渲染决策表:每一行
| Path | Change | Patch ID | Disposition | Evidence |,新变更默认填TODO;允许的处置集合按类别受限——removed允许upstreamed/obsolete/renamed,changed允许preserved/adapted/upstreamed/obsolete/renamed,added允许intentional-addition/adapted/renamed; --validate严格校验:每个变更补丁必须存在决策、类别与指纹必须与当前审计一致、处置必须在允许集合内、证据不能为空或TODO,否则以非零退出码失败并输出ERROR列表。
对每个表行同时检查新旧补丁,把每个TODO替换为有效处置与具体源码/提交证据,然后加--validate重跑。凡是既未 upstreamed 也非有意 obsolete 的补丁,必须重新应用或适配。永远不要仅凭提交数或文件数推断完整性。
脚本的判定行为由 .agents/skills/update-skia/scripts/audit_fork_patches_test.py 中的测试直接佐证,例如:
test_classifies_fork_delta_changes:正确区分added/removed/changed/unchanged四类;test_ignores_diff_metadata_but_keeps_hunk_context:两个补丁仅行号偏移时指纹一致(视为未变),hunk 上下文变化时指纹不同(视为已变);test_rejects_todo_and_wrong_disposition:TODO证据与非法处置都会被validate拒绝;test_rejects_stale_category_and_fingerprint:记录类别过期或指纹过期(补丁在评审后又被改动)都会被拒绝。
5.7 与后续阶段的衔接
Phase 05 的产物会继续驱动后续流程(见 .agents/skills/update-skia/references/phases/06-07-update-and-build.md 与 .agents/skills/update-skia/references/phases/08-10-bindings-and-tests.md):Phase 07 每次 mono/skia 适配提交后、Phase 10 最终确定性对账时,都要用相同的 Phase 05 参数重跑audit_fork_patches.py --validate。合并完成后,父仓库侧需要更新的典型文件(详见 .agents/skills/update-skia/references/typical-changes.md)包括.gitmodules子模块分支名、externals/skia子模块指针、scripts/VERSIONS.txt 版本号、cgmanifest.json 安全追踪、native/*/build.cake平台 GN flag、binding/SkiaSharp/Generated绑定产物与tests/Tests/SkiaSharp/*.cs测试。例如当前仓库的.gitmodules记录externals/skia指向https://github.com/mono/skia.git且分支为skiasharp,cgmanifest.json中 Skia 注册的chrome_milestone为 156、upstream_ref为chrome/m156,这些正是更新流程需要随里程碑推进同步的对象。
Phase 05 Gate:最终检查清单
完成上述全部工作后,逐一核对:
- 父仓库与 mono/skia 功能分支均具有已解析的基准 SHA;
- 合并提交恰好有两个父提交;
- 每个 fork 补丁与依赖差异均已分类并有最终处置;
audit_fork_patches.py --validate通过,无任何临时性决策残留;- C API 与构建配置完好无损;
- 分析产物(breaking-change 分析、依赖决策、独立评审)与合并后的树保持一致。
只有全部 Gate 通过,才能进入 Phase 06 的版本更新与本机原生构建阶段。需要特别强调的是:本阶段的所有合并操作都发生在 mono/skia fork 内部,任何构建驱动的依赖或 C API 适配都应作为合并后独立的、带解释性提交信息的提交存在,而不是混入双亲合并提交本身。
- 图形学
- 图像处理
- 跨平台
【免费下载链接】SkiaSharp
SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.
相关推荐
SkiaSharp 原生依赖更新实战指南:从 DEPS 到 PR 合并的完整流程
SkiaSharp 原生依赖更新实战指南:从 DEPS 到 PR 合并的完整流程 本指南讲解如何安全、合规地更新 SkiaSharp 所依赖的原生库(libpn
图形学图像处理跨平台0.7.5 (2026-05-04)
0.7.5 2026 05 04 Feat: graphify extract now runs incrementally — auto detects pr
人工智能知识图谱RAGAI 技能开发工具MCP 服务klogg便签窗口使用教程:Base64解码、JSON/XML格式化等实用功能
klogg便签窗口使用教程:Base64解码、JSON/XML格式化等实用功能 klogg是一款基于glogg项目开发的快速日志浏览器,其内置的便签窗口(Scr
桌面应用开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考