Dagger 引擎中 Dang SDK 的多大版本支持:版本路由、冻结快照与维护策略
Dagger 引擎中 Dang SDK 的多大版本支持版本路由、冻结快照与维护策略【免费下载链接】daggerAutomation engine to build, test and ship any codebase. Runs locally, in CI, or directly in the cloud项目地址: https://gitcode.com/GitHub_Trending/da/daggerDagger 引擎对 Dang 语言模块的运行时支持采用每个受支持的大版本各内嵌一份运行时 按模块engineVersion路由的架构使得旧模块永远保持其编写时的语言语义。本文基于引擎源码core/sdk/dang目录下的维护说明与实现完整讲解这套版本分发机制的目录布局、版本门控常量、冻结frozen与活跃living实现的双轨维护策略、新增一个 Dang 大版本的标准流程以及若干关键边界条件帮助深入理解 Dagger 引擎如何在不破坏存量模块的前提下演进其解释型 SDK。核心思想一份引擎内嵌多份 Dang 运行时Dang 是 Dagger 的一种解释型 SDK模块以.dang源码直接由引擎内置解释器求值不经过容器化执行。由于解释型 SDK 直接绑定语言语义一旦语言本身发生不兼容变更例如运算符含义改变旧模块就会跑偏。为此Dagger 的设计是引擎为每个受支持的 Dang 大版本内嵌一份独立的运行时包每个模块的engineVersion声明于其模块配置中决定它被路由到哪一份运行时已冻结的大版本不再获得任何新功能仅接受编译器强制的修复与关键 bug 回移。这一设计的直接好处是新功能总是要求新的engineVersion而新的engineVersion必然路由到最新的大版本——因此冻结的大版本永远不需要再做功能工作见 core/sdk/dang/README.md。目录布局四个组件的职责划分core/sdk/dang目录及其外围由四个部分组成各自职责明确组件Go 包职责core/sdk/dang_sdk.gosdk调度器dispatcher。dangSDK类型承载所有与版本无关的core.SDK行为每次调用通过dangImplFor选择一个dangImpl实现core/sdk/dang/shared/dangshared版本无关的基础设施nested-client 代理服务器、客户端元数据、错误转换。严禁导入任何大版本的github.com/vito/dangcore/sdk/dang/v2/dangv2living活跃实现导入github.com/vito/dang/v2。所有新功能都落在这里core/sdk/dang/v1/dangv1frozen冻结快照服务于engineVersion v0.21.5的模块Dang v1 语义.{ }表示选择。在快照时点与 living 包逐字节一致仅包名、文档注释与 dang 导入路径不同shared包禁止导入任何大版本的 Dang 库这一约束看似严格实际是整套复制策略成立的前提正因为所有版本相关的东西都留在vN/包内部冻结一份大版本只需复制 living 包 机械改写导入路径而shared中的公共代码永远只有一份。core/sdk/dang/shared/shared.go 的包注释明确写明了这条红线。版本路由dangImplFor与版本门控常量调度逻辑集中在 core/sdk/dang_sdk.go 中。每个支持的大版本实现同一个dangImpl接口// dangImpl is implemented by each supported Dang major version // (core/sdk/dang/v1, v2, ...). Unlike core.ModuleTypes, ModuleTypes takes // already-scoped values: the scoping helpers are unexported in this package // and the version packages cant import it (cycle), so the dispatcher scopes // before delegating. type dangImpl interface { ModuleTypes(ctx context.Context, deps *core.SchemaBuilder, scopedSrc dagql.ObjectResult[*core.ModuleSource], scopedMod dagql.ObjectResult[*core.Module]) (dagql.ObjectResult[*core.Module], error) Runtime(ctx context.Context, deps *core.SchemaBuilder, source dagql.ObjectResult[*core.ModuleSource]) (core.ModuleRuntime, error) }路由函数是一个最新优先的阶梯当前只有两级func dangImplFor(src *core.ModuleSource) dangImpl { if engine.CheckVersionCompatibility( engine.BaseVersion(engine.NormalizeVersion(src.EngineVersion)), engine.MinimumDangV2ModuleVersion, ) { return dangv2.Impl{} } return dangv1.Impl{} }门控常量定义在 engine/version.go// MinimumDangV2ModuleVersion is the minimum module engine version that gets // Dang v2 semantics (.{ } is dot-block application, .{{ }} is // selection); older modules keep Dang v1 semantics (.{ } is selection). MinimumDangV2ModuleVersion v0.21.5即模块的engineVersion≥ v0.21.5 获得 Dang v2 语义.{ }是 dot-block 应用、.{{ }}是选择更老的模块保持 Dang v1 语义.{ }是 GraphQL 选择。注意阶梯的注释——newest-first ladder; adding a future major is one case——新增一个未来大版本时只需在最前面加一个if分支。dangSDK本身还负责所有版本无关的 SDK 能力开关。例如AsRuntime/AsModuleTypes/AsCodeGenerator/AsClientGenerator都返回自身而AsModule返回false因为 Dang SDK 是打包进引擎的不像其他 SDK 那样作为模块加载所以不暴露生成器函数AlwaysEnablesSelfCalls返回true因为解释器在运行期按名字对 schema 解析自己的类型模块自身类型必须出现在它查询的 schema 中。每个As*入口最终都通过dangImplFor(source.Self())把具体工作委托给对应大版本。living 与 frozen 实现逐行同构只有语义差异v2/sdk.goliving与 v1/sdk.gofrozen结构几乎一一对应两者都提供Impl.ModuleTypes声明期产出模块的 TypeDef与Impl.Runtime运行期返回一个原生、不使用容器的runtime其AsContainer显式返回false。两处包注释把各自的身份交代得很清楚v1 的包注释This is a FROZEN snapshot of the living implementation, taken when Dang v2 shipped: it gets no feature work, only compiler-forced updates when engine internals change and critical bugfix backports.v2 的包注释This is the LIVING implementation: new Dang SDK features land here only. Older majors are frozen snapshots of this package.两处也存在真实的、值得注意的差异。在ModuleTypes声明阶段选择完整求值还是仅声明运行器时v1 检查原始实验特性标志src.Self().SDK.ExperimentalFeatureEnabled(core.ModuleSourceExperimentalFeatureSelfCalls)v2 检查src.Self().SelfCallsEnabled()其源码注释解释因为 Dang SDK 总是启用 self-callsAlwaysEnablesSelfCalls且 self-call 字段把返回值标注为运行时 schema 中的Dagger.T形态若对自引用函数体做完整推断会去解析尚不含本模块 API 的 deps schema所以声明期必须使用仅声明运行器runDangDirForModuleTypes对应 v2/helpers.go 中调用dang.DeclareDir的实现。两版的Runtime().Call都遵循同样的收尾模式defer中把任何错误经dangshared.ConvertError转换为携带 GraphQL 错误扩展的*core.Error然后fnCall.ReturnValue(ctx, core.JSON(outputBytes))将 Dang 求值结果序列化为 JSON 返回给引擎。shared 包版本无关的三块基础设施core/sdk/dang/shared/shared.go 提供三份被所有大版本共用的管线WithNestedClientServer—— 在127.0.0.1的随机端口上起一个临时 HTTP 服务器把 Dagger API 暴露给嵌套客户端构造一个指向http://addr/query的 GraphQL 客户端交给回调函数执行并在回调返回后关闭服务器10 秒超时。Dang 模块对Dagger命名空间的一切 GraphQL 调用都经由这个本地代理转发回引擎查询。请求处理中会注入 OpenTelemetry 传播头并透传moduleContext与fnCall支撑 host service 代理等语义。NewNestedClientMetadata—— 从调用方上下文取出客户端元数据并生成一份全新的嵌套客户端元数据新的ClientID、ClientSecretToken、ClientStableID继承SessionID与AllowedLLMModules让 Dang 代码在隔离的客户端身份下求值。ConvertError—— 将 Dang 求值产生的错误转换为*core.Error若错误是*gqlerror.Error则按排序后的键把Extensions逐个序列化为JSON值挂回错误上保留 GraphQL 错误扩展信息。v2/helpers.go 中还有两个体现引擎侧深度的细节两版各自持有同构拷贝evalDangSource把模块的ContextDirectory挂载到临时路径后执行dang.RunDir并把程序 stdout/stderr 显式重定向到用户可见的 trace spandagql.UserFacingSpanContext——因为引擎内运行时不像容器化 SDK 那样由执行器自动注入正确的 traceparentensureModuleSelfTypes则在声明期为模块自身声明的类型合成最小化的Dagger.T形态类型条目使 self-call 返回值在 deps-only schema 阶段也能解析。维护策略四类变更各自的落地路径core/sdk/dang/README.md 定义的维护策略按变更类型划分了四条路径新功能只进 living 包当前即v2/。引擎侧胶合层类型转换、调用分发的 bug 修复先修 living 包仅当 bug 影响旧模块时才回移到冻结包。Dang 语言本身的 bug 修复面向旧模块在上游vito/dang的对应维护分支如release/v1落地、打补丁标签如v1.0.x、再在引擎侧 bumpgo.mod。引擎内部重构core、dagql、engine的 API 变动会在编译期打破冻结包——这是设计上的快速失败信号对策是对所有拷贝施加同一套机械修复。从源码结构看这套策略是自洽的冻结包与 living 包逐字节同构除包名/导入路径/注释因此机械修复是可枚举、可批量执行的操作而编译期破坏保证了重构不会在某一份拷贝里静默漂移。新增一个大版本vN1的标准流程README 给出六步流程结合当前仓库代码可以逐条对应到具体动作上游在带新大版本后缀的模块路径上打vN1.0.0标签并为刚冻结的前一个大版本保留维护分支。在该维护分支上把 tree-sitter 语法导出的 C 符号加_vN后缀——因为 C 符号共享全局命名空间两个大版本若导出同名符号将无法链接进同一个二进制规范名tree_sitter_dang永远跟随 living 大版本保证编辑器集成持续可用。复制cp -r v2 v3然后在v3/中把 dang 导入路径改写为新大版本、包名改为dangv3v2/就此冻结——按 v1/sdk.go 的措辞更新其包文档为 FROZEN。门控常量在 engine/version.go 添加MinimumDangVN1ModuleVersion next unreleased dagger version。路由阶梯在 dang_sdk.go 的dangImplFor最前面加一个新 case。依赖go get github.com/vito/dang/vN1vN1.0.0。测试把core/integration/testdata/modules/dang/下的 dang 测试模块的engineVersion门控 bump 到新版本让主测试套件覆盖 living 大版本并为任何语义发生变化的语法添加一个固定版本pinned的回归模块。第 6 步在仓库中已有实例core/integration/testdata/modules/dang/legacy-selection/ 正是为 v1/v2 边界添加的回归模块。其配置固定engineVersion: v0.20.6早于 v0.21.5 门控源码注释明确要求Do not bump this modules engineVersion函数体使用 v1 语义的.{contents, size}选择语法验证钉在 v0.21.5 之前的模块必须保持.{ }作为 GraphQL 选择。同目录下还有dot-block、self-calls、test-interface、test-directives等大量按特性命名的回归模块构成整条大版本语义边界的测试护栏。边界条件与注意事项README 列出两条必须知晓的 caveat它们直接影响仓库内模块的版本行为仓库内 Dang 模块钉在已发布引擎版本上。.dagger/modules/*、cmd/codegen、modules/markdownlint等仓库内模块固定于上一个已发布版本的engineVersion已发布 CLI 会拒绝比自己更新的配置因此在下一个 dagger 版本发布前它们会被路由到前一个大版本。实践要求是跨越大版本边界时保持这些模块语法中立syntax-neutral或在发布后立即 bump。engineVersion缺省值的归一化。ModuleSource中空/缺失的engineVersion归一化为当前引擎版本从而路由到最新大版本而早于 semver 的配置归一化为v0.11.9从而路由到最老的大版本。engine/version.go 中presemverModuleVersion v0.11.9常量即对应后者。小结Dagger 对 Dang SDK 的多大版本支持是复制 机械改写 编译期校验三者的组合dang_sdk.go的dangImplFor用MinimumDangV2ModuleVersion v0.21.5一个常量把语义边界钉死在engineVersion上v1/与v2/同构的双包保证了冻结与活跃实现的漂移只能发生在编译期被发现的位置shared/的导入禁令让复制策略成本恒定上游 C 符号命名空间冲突的规避_vN后缀解决了两个大版本共存于单二进制的链接问题legacy-selection这类 pinned 回归模块则为每条语义边界提供了可执行的验证。阅读 core/sdk/dang/README.md 与上文引用的 dang_sdk.go、engine/version.go、v2/helpers.go 源码可以完整复现从版本路由到模块求值的全链路。【免费下载链接】daggerAutomation engine to build, test and ship any codebase. Runs locally, in CI, or directly in the cloud项目地址: https://gitcode.com/GitHub_Trending/da/dagger创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考