- 编程语言
- 编译器
- 语言运行时
【免费下载链接】ponyc
Pony is an open-source, actor-model, capabilities-secure, high performance programming language
ponyc 编译器内置(vendor)了 LLVM、LLD 与 clang,从源码构建这些依赖动辄耗费数小时,是 CI 中最昂贵的一环。本文基于仓库 .ci-scripts/libs-cache/README.md 及同目录下的全部实现脚本,完整讲解 ponyc 如何用「主缓存 + 分支缓存」的双层 GHCR OCI 工件缓存体系消除重复构建:包括包命名与内容哈希寻址、warmer 的三段式预热、从不冷构建的消费方策略、BSD VM 的缓存进出、保留清理策略以及双 token 权限拆分。读完本文,你将能理解该缓存体系的每一个脚本、每一条工作流门控与每一次缓存命中的底层原理,并可直接复用到其他「vendor 重型依赖」的 CI 场景。
为什么需要一套独立的 libs 缓存
ponyc 将 LLVM、LLD、clang 以源码形式 vendored 进仓库,每次干净构建都需要从源码编译它们。由于构建耗时以小时计,CI 必须缓存构建产物——具体而言是以下两条路径:
build/libs(LLVM/LLD/clang 的构建产物树)lib/llvm/src/compiler-rt/lib/builtins(compiler-rt 的内建库)
这两条路径在 .ci-scripts/libs-cache/registry.py 中被定义为CACHED_PATHS,正是旧版actions/cache步骤曾经存储的内容。与 GitHub Actions 内置缓存不同,ponyc 将工件以GHCR OCI artifact(单个 tar+gzip blob + OCI manifest)形式存放,原因是该缓存需要跨工作流、跨平台、跨 fork 共享,且要支持「先查存在性再决定是否构建」的廉价探测,这些能力 GitHub Actions cache 无法满足。
关键设计决策是:缓存逻辑完全不进入构建本身。cmake -P lib/build-libs.cmake只知道如何构建 libs,对缓存一无所知;所有缓存原语与编排逻辑都收敛在.ci-scripts/libs-cache/目录的脚本里,围绕这条构建命令做序列化。这套脚本全部使用 Python 标准库实现,CI runner 上无需任何 pip 安装。
双缓存架构总览:main cache 与 branch cache
体系内存在两个相互独立、职责分明的缓存:
| 缓存 | 命名空间 | 写入者 | 读取者 | 保留策略 |
|---|---|---|---|---|
| 主缓存 | ponyc-libs-cache/ | 仅 warmer(预热工作流) | 所有消费方工作流 | 按包保留最新 N 个版本(keep-N) |
| 分支缓存 | ponyc-branch-libs-cache/ | PR 作业、weekly 消费方,以及 tier2/tier3/stress 在 off-main 时 | PR 作业、tier 作业、warmer(用于 promote) | 按年龄(两周) |
主缓存只由 warmer 写入,保证任何消费方「拉取或自建」都不会污染主缓存;分支缓存则是 PR 与分支构建的「草稿区」,让一次 LLVM 变更触发的构建只发生一次,之后可被重复复用,并在合入 main 后被**提升(promote)**进主缓存。
脚本体系:薄入口脚本 + 共享支持库
整个.ci-scripts/libs-cache/目录由两类文件组成:可直接运行的入口脚本,和它们共同 import 的共享支持库。
入口脚本
oci_libs_cache.py— 主缓存的 push/pull/exists 原语(registry v2 API)branch_libs_cache.py— 分支缓存的同名原语promote_libs_cache.py— 将分支缓存工件以同一 tag 复制进主缓存(registry.copy)resolve_libs_cache.py— 非 PR 构建(消费方、warmer、require-cache-hit)的编排入口pr_libs_cache.py— PR 作业的编排入口prune_libs_cache.py/prune_branch_libs_cache.py/clear_libs_cache.py— 保留与清理
共享支持库
registry.py— registry-v2 客户端:request、鉴权、blob、manifest、归档、copy、derive_platform、IMAGE_RE、repo_rootghpackages.py— GitHub Packages REST 管道:gh_request、paginate、encodecache_arch.py— 架构名称规范化common.py—die/info打印器、编排辅助函数,以及共享的入口脚本路径常量MAIN_CACHE、BRANCH_CACHE、PROMOTE
正是这种分层,让oci_libs_cache.py与branch_libs_cache.py退化为「一个命名空间 + 一个 cache_package + 一个调用registry.dispatch的 main」;promote_libs_cache.py则解析两个命名空间后调用registry.copy。修改构建镜像命名只需在registry.py中改一处——这正是共享库存在的意义,参见 .ci-scripts/libs-cache/oci_libs_cache.py 与 .ci-scripts/libs-cache/branch_libs_cache.py。
包命名与缓存身份
完整包名只在一个地方组装——cache_package():
ponyc-libs-cache/<platform>-<arch> ponyc-branch-libs-cache/<platform>-<arch>而 tag 是工作流中hashFiles(...)的内容哈希。package:tag与旧actions/cache的 key 基于同一组输入:任何改变 LLVM 决定输入的改动都会得到不同 tag(文件变化)或不同 platform(构建镜像变化),因此绝不会服务到过期工件——结果只可能是精确命中或未命中。
命名空间的隔离作用
ponyc-libs-cache/这一路径命名空间把缓存包与可分发容器(nightly/、releases/)隔开,同时为保留与清理脚本的 glob 匹配划定了边界:ponyc-libs-cache/*永远不可能扫到无关容器。分支缓存的ponyc-branch-libs-cache/同理。
platform 的两种来源
<platform>要么由derive_platform从构建镜像引用推导,要么是调用方显式传入的裸标签:
- 容器作业传
--image:镜像引用必须匹配IMAGE_RE正则^ghcr\.io/ponylang/ponyc-ci-(.+):(\d{8})$(.ci-scripts/libs-cache/registry.py)。名称 + 日期(YYYYMMDD)共同构成 platform 字符串(:被替换为_),所以两个不同版本的构建镜像会落在不同的包中;格式不符会立即失败并提示更新IMAGE_RE(.ci-scripts/libs-cache/registry.py)。 - 非容器平台(macOS/Windows/BSD)传
--platform裸标签,如x86-macos-15-intel。脚本负责附加命名空间与 arch,工作流只传标签本身。
arch 规范化:为什么是双重必需
<arch>取自构建机上的platform.machine(),经cache_arch.canonical归一为每种 ISA 的唯一拼写:amd64→x86_64,aarch64→arm64(.ci-scripts/libs-cache/cache_arch.py)。未识别架构是硬错误而非静默透传——新增 ISA 必须显式加入ARCH_ALIASES。
规范化之所以必要,有两个原因:
- 多架构镜像防冲突:alpine 与 ubuntu26.04 构建镜像是多架构的,同时在 x86-64 与 arm64 的 warmer 作业上构建。若 arch 不在包名里,两个架构会推送到同一个
package:tag互相覆盖,arm64 消费方甚至会拉到 x86-64 的 libs。 - 跨系统同名:同一 ISA 在不同 OS 上拼写不同——Linux 写
x86_64,FreeBSD/OpenBSD 写amd64。BSD warmer 的宿主机侧存在性检查跑在 Linux runner 上,而推送发生在 VM 内,两者必须归一为同一名称,否则宿主机永远看不到 VM 写入的工件。
重命名即孤儿化
改变某个 ISA 的规范拼写——或包名形状的任何部分——都会重命名包,旧名的主缓存工件随即成为孤儿:prune(按包保留 N 个)永远不会回收一个已停止接收新版本的包。此时需要手动运行一次clear-libs-cache.yml删除这些流浪包。分支缓存无此问题,因为它的保留策略基于年龄,能自我愈合;主缓存则不能。
主缓存:warmer 与其三段式预热
update-lib-cache.yml(warmer)是主缓存的唯一写入者,在 push 到 main 时运行。未命中时它先尝试用promote_libs_cache.py提升一个匹配的分支缓存工件(registry copy,直接复用某个 PR 或临时 dispatch 已完成的构建),只有两者皆无时才冷构建。
由于其他所有工作流都只「拉取或构建」,warmer 的作业必须覆盖每一个消费方会拉取的平台与标签:若某个消费方的构建镜像或 runner 标签 warmer 不构建,它每次运行都会未命中。因此,新增 libs 消费方或给既有消费方加平台时,必须同步在 warmer 的正确阶段补上对应平台。
三阶段顺序与并发上限
一次冷推送——比如 LLVM 输入变更导致所有平台同时冷构建——会瞬间占满整个组织的 runner 池,用低频构建抢占最高频缓存的资源。因此预热分三个串行阶段进行:
- PR 平台(
x86_64-linux-pr,即 ubuntu26.04 x86-64;arm64-macos;x86_64-windows)——pr.yml拉取的平台。无门控,立即开始。 - Release 与 nightly 平台(
x86_64-linux-release、arm64-linux、x86_64-macos、arm64-windows)——release.yml与nightlies.yml拉取的其余平台。 - 其他所有平台(
x86_64-linux-other,即 fedora 加交叉编译镜像;freebsd;openbsd;dragonflybsd)——tier2、tier3 与仅周更平台。
第三阶段的后七个构建——四个x86_64-linux-other矩阵分支加三个 BSD VM——每个都要两到三个小时,所以阶段 3 并发上限为 2:x86_64-linux-other携带max-parallel: 2;BSD 作业needs:x86_64-linux-other,保证 VM 从不与容器分支重叠;dragonflybsd还needs:freebsd。移除其中任何一个门控都会重新放开阶段并发。
每个后一阶段只needs:前一阶段的快速作业(Linux 与 macOS),绝不等待 Windows——Windows 构建在其阶段内启动,但因最慢而绝不能门控下一阶段。阶段作业携带if: ${{ !cancelled() }},使前一阶段构建失败只会推迟后续阶段而不会取消它们。
新平台应放入最早会拉取它的阶段。x86-64 Linux 构建之所以拆成三个作业(-pr、-release、-other),是因为needs:是作业级而非矩阵条目级,而这些构建镜像横跨全部三个阶段;每个镜像必须且只能放在三个作业中的一个。
从不冷构建的消费方
stress-test 工作流(stress-test-*.yml)、ponyc-tier2.yml与ponyc-tier3.yml依据被测代码是否为 main 来选择模式。步骤的分支检查为github.event_name == 'workflow_dispatch' && <ref> != 'main',其中<ref>是检出 ref:tier2/tier3 用inputs.ref,stress 用github.event.inputs.sha。两者默认都是main;定时运行两者皆无,因此按 on-main 处理。
On main(定时运行,或留在 main 上的 dispatch)时,它们传--require-cache-hit --skip-on-miss,永不冷构建。未命中时写入.libs-cache-miss标记并以 0 退出;后续每个构建/运行步骤都以if: hashFiles('.libs-cache-miss') == ''门控,于是作业在这些步骤被跳过的情况下保持绿色,stress 作业的Send alert on failureZulip 步骤(以failure() && github.event_name == 'schedule'门控)保持静默。这是刻意为之:warmer 独占 main,而持续 stress 循环的运行时间交错,可能与空缓存或回填中的缓存重叠,因此定时未命中是预期现象而非覆盖缺陷。
Off main(对某个分支的手动workflow_dispatch)时,它们以消费方模式运行:--branch-cache -- cmake … -P lib/build-libs.cmake。先拉主缓存,再拉分支缓存;全部未命中才构建 LLVM 并推送分支缓存——与 PR 行为完全一致,使 LLVM 变更的手动测试不被阻塞,且同一分支的再次 dispatch 会复用该构建。这正是这些工作流持有packages: write而非packages: read的原因。
模式的拼写方式:bash 与 sh 站点用两个环境变量——LIBS_MODE承载--image之前的旗标,LIBS_BUILD承载末尾的-- cmake … -P lib/build-libs.cmake(该段仅 off-main 存在,on-main 时不添加多余参数)。PowerShell 无法对变量值做词分割,所以 Windows 站点在字面if ($env:LIBS_OFF_MAIN -eq 'true')下把两种模式完整展开。未命中标记写入GITHUB_WORKSPACE——回退到工作目录(即 arm64-linux docker-in-docker 作业的工作区挂载点)——使宿主机侧hashFiles门控可见,实现见 .ci-scripts/libs-cache/resolve_libs_cache.py。
已接受的权衡:warmer 覆盖上的永久缺口不再被这些 on-main 作业大声暴露(定时未命中只是跳过)。它仍会通过拉取同平台的非 stress 消费方浮现。tier2/tier3 的Send alert on failure保持普通failure()而非像 stress 那样以schedule门控——on-main 跳过永不失败所以保持静默,但手动运行的构建或测试失败仍会告警。
构建镜像与 BSD VM
容器平台名由IMAGE_RE(经derive_platform)从构建镜像引用推导,契约是ghcr.io/ponylang/ponyc-ci-<name>:<YYYYMMDD>。名称或 tag 破坏该格式的构建镜像会立即失败;命名约定变化时更新IMAGE_RE即可。
BSD VM 没有构建镜像,因此使用显式的按版本标签(freebsd-15.1、openbsd-7.9、dragonfly-6.4.2等)。warmer 启动这些 VM 构建并推送;ponyc-tier3.yml的 BSD 作业拉取。两者都将GITHUB_TOKEN通过 ssh 传入 VM,与其他消费方一致。
VM 供给是共享的而非复制的:两个工作流都从单一Provision VM步骤调用.ci-scripts/bsd/{freebsd,openbsd,dragonfly}-provision.bash。这些脚本释放磁盘、安装 QEMU、下载镜像、启动 VM、安装依赖并将检出 rsync 进去。DragonFly 的脚本再调用.ci-scripts/bsd/dfly_configure_vm.py(QEMUsendkey控制台自动化,从PUB_KEY读取 ssh 公钥)。修改 VM 配置应改脚本而非两份 YAML。freebsd-provision.bash接受FREEBSD_VERSION并无条件安装doas与doas.conf——对 warmer 无害,且简化任何需要 VM 内 root 的操作。
VM 内的 libs 处理按平台共享,方式与供给相同:两个工作流都调用.ci-scripts/bsd/{freebsd,openbsd,dragonfly}-libs-cache.bash <operation>完成恢复与构建/推送步骤,而不是各作业内嵌自己的ssh远程执行。每个脚本持有该平台的 ssh 选项、VM 用户、仓库目录、构建环境、编译器旗标与构建树清理逻辑。<operation>是restore、build-push-branch、build-push-main之一:分别对应分支缓存恢复、分支缓存构建与推送(tier3 off-main)、主缓存构建与推送(warmer)。
两套脚本都是基于文件的,因此 Super-Linter 的 shellcheck 可以覆盖它们——这是内嵌run:块做不到的。dfly_configure_vm_test.py守护KEYMAP反转义、DFLY_MONITOR_SOCK监视器套接字路径契约(与dragonfly-provision.bash一致),以及让重新键入的配置尝试从干净提示符开始的控制台重置。
两个调用方仅在对脚本的调用方式上不同:
- warmer先做宿主机侧
oci_libs_cache.py exists检查,再尝试宿主机侧 promote 步骤(branch_libs_cache.py exists,然后promote_libs_cache.py,不启动 VM)复用已存在的分支工件。它用if: steps.check.outputs.hit != 'true' && steps.promote.outputs.promoted != 'true'门控Provision VM步骤与终端的构建推送步骤,因此主缓存命中或成功 promote 会完全跳过 QEMU 启动。promote 失败非致命:promoted=false会落入 VM 构建。作业在这些步骤被跳过时仍成功而非失败,从而满足prune的needs:。 - tier3同样以宿主机侧缓存检查门控 QEMU 启动,并与容器分支一致地按 main/off-main 分流。宿主机侧
Check libs cache步骤(id: check)设置hit输出,并且仅在 on-main 时于未命中处写.libs-cache-miss标记,使Provision VM及其后所有步骤跳过——定时未命中完全跳过 VM 而非冷构建。Off-main 时它从不写标记:主缓存命中或分支缓存命中都设hit=true;完全 off-main 未命中设hit=false,VM 内步骤按该输出门控。Restore libs(hit == 'true')用--require-cache-hit --branch-cache拉取且不构建;Build libs(hit == 'false')在 VM 内构建 LLVM 并用branch_libs_cache.py push推送分支缓存,镜像 warmer 的 VM 内构建但写分支而非主缓存。因此 off-main 时 tier3 确实会把 BSD 构建捕获进分支缓存,同一分支的再次 dispatch 可复用——这正是 tier3 持有packages: write的原因。
保留与清理
主缓存保留:按包 keep-N
warmer 的prune作业运行prune_libs_cache.py --keep 2,每个包保留最新的两个版本(.ci-scripts/libs-cache/prune_libs_cache.py)。平台在包名中而非 tag 中,因此 keep-N 按平台计数;若把平台移入 tag,keep-N 就会删除属于其他平台的活跃工件。
clear:唯一的失效手段
clear-libs-cache.yml与clear_libs_cache.py是逃生舱。它们整包删除所有ponyc-libs-cache/*包(REST API,/编码为%2F)并重新 dispatch warmer。由于 tag 是内容哈希,不存在「触碰以续期」——删除是唯一的失效方式。clear 工作流还需要actions: write权限用于重新预热 dispatch。
为什么删除需要两个 token
每个 token 只能完成其作用域允许的一半:
- 组织级
PONYLANG_MAIN_READ_PACKAGE_TOKEN(经典 PAT,read:packages)是唯一能枚举组织包的 token——仓库作用域的GITHUB_TOKEN在组织包列表端点会得到 400。 - 但只有
GITHUB_TOKEN(packages: write)能删除这些包,因为它们是仓库作用域的,组织 PAT 无论 scopes 如何删除时都会得到 404。
因此clear_libs_cache.py与prune_libs_cache.py用PONYLANG_MAIN_READ_PACKAGE_TOKEN枚举、用GITHUB_TOKEN删除,两个工作流都传入两个 secret。这个拆分也正是保留逻辑写成自定义脚本而非snok/container-retention-policy的原因——后者只拿一个 token,在此场景无法「枚举并删除」。
分支缓存:跨 PR 去重的关键
分支缓存是独立缓存,拥有自己的命名空间ponyc-branch-libs-cache/<platform>-<arch>、自己的 push/pull 脚本(branch_libs_cache.py)与自己的保留(prune_branch_libs_cache.py)。
它是tag 可寻址的:包名只是平台与架构,版本是同一个hashFiles内容哈希,因此一个分支包正是主缓存的名字换了命名空间前缀。没有-pr<N>组件——早期那个分区设计因不做正确性工作而被移除,移除后换来的是跨 PR、跨 tier 的免费去重:共享同一 tag 的两个构建内容必然相同。同时这也意味着 warmer 可以手工构造名字,用一次existsHEAD 请求找到可提升工件,无需枚举。
这个缓存存在的意义:改变了 LLVM 决定输入的构建——非 fork 的 PR,或对分支的 ad-hocworkflow_dispatch(如 weekly)——只需构建一次 LLVM,后续运行直接复用;warmer 在合入后可将该构建提升进主缓存而非重建。
写入者:PR 作业(pr_libs_cache.py,仅非 fork)与 weekly 消费方(resolve_libs_cache.py --branch-cache)总是推送;tier2、tier3 与 stress 也推送,但仅 off-main(对分支的手动workflow_dispatch)。这些工作流没有pull_request触发器,所以推送者总是仓库授权的。
warmer 只读分支缓存(用于提升),从不推送分支包;提升只写主缓存,从而保持 warmer 是主缓存的唯一写入者。分支脚本不 import 主缓存脚本,主缓存ponyc-libs-cache始终是事实来源。
pr_libs_cache.py:消费方模式与 ensure 模式
pr_libs_cache.py拥有 PR 作业流程,resolve_libs_cache.py的--branch-cache消费方模式为 tier 作业做同样的事。两者都只是围绕工作流在--之后交给它们的构建命令序列化既有缓存原语(split_build_command,见 .ci-scripts/libs-cache/common.py)。两种模式:
- 消费方模式(默认):先查主缓存,再(带
--branch-cache时)经pull查分支缓存——命中即下载 blob;未命中则运行构建;然后(带--branch-cache时)尽力推送分支缓存。推送失败仅记录警告并降级为「下次运行重建」,不失败作业。 - ensure 模式(
--ensure):相同序列,但用exists子命令检查——不下载 blob,因为该作业只需知道是否要构建——且分支推送失败会硬失败作业,使 registry 写入问题在此暴露而非让每个消费方各自冷构建。--ensure要求--branch-cache。
exists子命令同时存在于oci_libs_cache.py与branch_libs_cache.py(.ci-scripts/libs-cache/registry.py)。它只检查 manifest、不下载 blob,存在时退出 0、缺失时退出 1。任何 HTTP 或网络错误都经die以退出 1 结束,从而失败安全地退化为「构建」。
主缓存命中在任何推送之前短路,warmer 因此保持为ponyc-libs-cache的唯一推送者。
pr.yml 如何驱动分支缓存
只有一个工作流驱动分支缓存:pr.yml——合并后的 PR 工作流,取代了pr-ponyc.yml、pr-pony-compiler.yml与pr-tools.yml。合并正是跨工作流去重的前提:三个套件此前是三个并发工作流,各自在缓存未命中时冷构建同一个共享 LLVM 平台,而needs:只在工作流内有效。现在,对于被两个或更多套件共享的每个平台(ubuntu glibc、macOS、Windows),一个maybe-build-<plat>作业运行一次pr_libs_cache.py --ensure,消费方作业needs:它并拉取。
消费方门控为!cancelled() && needs.changes.outputs.<suite> == 'true' && needs.maybe-build-<plat>.result != 'failure':
!cancelled()允许消费方在 maybe-build 被跳过时运行——这是 fork 路径,消费方随后不带--branch-cache地拉取或构建。result != 'failure'在共享构建确实失败时跳过消费方,使 LLVM 构建失败只报告一次而非三次。
Fork 安全
--branch-cache在工作流表达式本身就被门控为非 fork。消费方的LIBS_BRANCH_CACHE环境变量是${{ head.repo.full_name == github.repository && '--branch-cache' || '' }}——非 fork 得旗标,fork 得空串,从而抑制分支拉取与推送。bash 站点上run:行不加引号地引用它($LIBS_BRANCH_CACHE),使 fork 的空值消失而非作为多余空参数传入。加引号会破坏 fork:argparse 会看到一个空位置参数并退出 2。
PowerShell 不会那样丢弃空变量——它把''作为真实参数传入——所以 Windows 站点把旗标构建成数组:$bc = if ($env:LIBS_BRANCH_CACHE) { @($env:LIBS_BRANCH_CACHE) } else { @() },空数组不贡献任何东西。
maybe-build 作业的if:已经要求非 fork,所以它们字面传递--branch-cache。--branch-cache是布尔值,也是脚本读取的唯一非 fork 信号;它不携带 PR 号,因为缓存是 tag 可寻址的。
pr.yml为非 fork 分支推送携带permissions: packages: write。fork 的pull_requesttoken 无论如何都是只读的。绝不要把它切换为pull_request_target——那会把写 token 交给 fork 代码。
分支缓存的保留:基于年龄
保留基于年龄,刻意区别于主缓存的 keep-N。prune-branch-libs-cache.yml(每日schedule加workflow_dispatch)运行prune_branch_libs_cache.py,删除超过两周的分支缓存工件,并在一个包的所有版本都过期后丢弃该包——例如退役构建镜像的平台。现在按平台的包是长期存在的而非按 PR,keep-N 永远不会删除闲置包,因此需要独立脚本。
它使用与主缓存保留相同的双 token 拆分:用PONYLANG_MAIN_READ_PACKAGE_TOKEN枚举、GITHUB_TOKEN删除。它只在自己的命名空间内枚举与删除,碰不到主缓存;主缓存的prune_libs_cache.py同样过滤ponyc-libs-cache/,永远看不到分支包——两个 prune 不会交叉。
分支缓存没有clear 或逃生舱工作流:基于年龄的 prune 是唯一的回收手段。
实践要点速览
- 新增消费平台:先确认 warmer 在正确阶段覆盖了该平台,否则每次运行必然未命中。
- 新增 ISA:必须显式加入 .ci-scripts/libs-cache/cache_arch.py 的
ARCH_ALIASES,否则硬失败。 - 变更镜像命名:同步更新
IMAGE_RE,否则所有容器作业立即失败。 - 仓库外复用的最小入口:消费方模式一条命令即可——
resolve_libs_cache.py [--branch-cache] (--image <ref> | --platform <label>) --tag <hash> -- <build command...>;构建命令示例为cmake -DJOBS=4 -P lib/build-libs.cmake(Windows 上为cmake -DPRESET=libs-windows-x86-64 -P lib/build-libs.cmake)。 - 关注点分离:构建命令
lib/build-libs.cmake只负责构建;所有缓存决策都在.ci-scripts/libs-cache/的编排脚本中,这套分层让缓存逻辑的演进(如本文中的 promote、ensure、skip-on-miss 模式)无需触碰构建本身。
整个体系的可运行验证不仅存在于工作流中,也沉淀为脚本同目录的单元测试(如pr_libs_cache_test.py、resolve_libs_cache_test.py、promote_libs_cache_test.py、cache_arch_test.py、package_name_test.py、repo_root_test.py以及 BSD 侧的dfly_configure_vm_test.py),它们守护着包命名、repo 根路径推导与编排序列等最易出错的环节。
- 编程语言
- 编译器
- 语言运行时
【免费下载链接】ponyc
Pony is an open-source, actor-model, capabilities-secure, high performance programming language
相关推荐
ponyc CI 辅助脚本工程化指南:`.ci-scripts` 的代码约定、自包含测试与 libs 缓存实战
ponyc CI 辅助脚本工程化指南: .ci scripts 的代码约定、自包含测试与 libs 缓存实战 .ci scripts 是 ponyc 仓库中供
编程语言编译器语言运行时BAML 发布 CI 的 GCP 容器镜像缓存:基于 Artifact Registry 远程仓库的 GHCR 拉取缓存实践
BAML 发布 CI 的 GCP 容器镜像缓存:基于 Artifact Registry 远程仓库的 GHCR 拉取缓存实践 本指南系统讲解 BAML Lang
编程语言AI Agent编译器CLI人工智能Bisheng缓存优化:多级缓存架构的设计实现
Bisheng缓存优化:多级缓存架构的设计实现 引言:企业级LLM应用的缓存挑战 在大规模语言模型(LLM)应用的企业场景中,缓存系统面临着前所未有的挑战。Bi
人工智能大模型AI 应用LLMOpsRAGAI Agent工作流自动化后端前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考