模型和机制
Dep 具有许多分立组件和运动部件,所有这些部件围绕中心模型旋转。这个文档解释了模型,然后探讨了 Dep 在该模型的背景下的主要机制。
状态与流程
Dep 的核心思想是"四状态系统"——用于对包管理器与之交互的磁盘盘上状态进行分类和组织的模型。这首先是作为一个连贯的、通用的模型来表达的。 这篇(长)文章,虽然四种状态模型 中的许多原理是从现有的包管理器中派生出来的。
简言之,这四种状态是:
- 这个 当前项目 源代码。
- manifest -描述当前项目的依赖性要求的文件。在 Dep 中,这是
Gopkg.toml文件. - lock -一个包含传递完整的、可再现的依赖图描述的文件.在 Dep 中,这是
Gopkg.lock文件。 - 源代码本身的依赖性。在 Dep 的当前设计中,这是
vendor/目录。
我们可以直观地表示这四个状态如下:

功能流
认为 Dep 是一种系统,它在这些状态之间的关系上施加单向的、功能性的流动.这些函数将上述状态作为输入和输出,将它们从左向右移动.具体来说,有两个功能:
- 一求解函数将当前项目中的导入集和规则作为输入
Gopkg.toml并作为其输出返回一个传递完整的、不可变的依赖图—— A 中的信息Gopkg.lock. - 一拍卖功能在 A 中获取信息
Gopkg.lock作为其输入,并确保源文件的磁盘安排,使编译器将使用锁定中指定的版本。
我们可以直观地表示这两个函数:

这是 dep ensure -典型的流程,用于 Gopkg.toml 已经存在。当一个项目还没有 Gopkg.toml,dep init 可以生成一个。 必要的流程保持不变,但改变了输入:而不是从现有的阅读。Gopkg.toml 文件,dep init 构造从用户的 GopAs 推断出的数据中的一个, 和/或来自另一个工具的元数据文件。 换言之,dep init 自动将项目从其他方法迁移到组织依赖关系。
此图也直接对应于代码。解决函数实际上被分解成一个构造函数和一个方法——我们首先创 建一个 Solver 类型,然后调用它的 Solve() 方法。 构造函数的输入被封装在一个 SolveParameters 看起来应该很熟悉:
type SolveParameters struct {
RootPackageTree pkgtree.PackageTree // Parsed project src; contains lists of imports
Manifest gps.RootManifest // Gopkg.toml
...
}
授权功能是 gps.WriteDepTree()。虽然需要少量的参数, 但相关的参数是 gps.Lock -表示在 A 中保存的数据的抽象形式的接口。Gopkg.lock。
四态系统和这些功能流是所有DEP行为建立的基础。如果你想了解 Dep 的机制,把这个模型放在你的思维的最前沿。
保持同步
dep 的设计目标之一是它的两个"功能"最小化了它们所做的工作,以及它们在各自的输出中引起的变化。因此,这两个函数在预先存在的输出处窥视,以了解实际需要做什么工作:
- 解决函数检查现有的
Gopkg.lock以确定其所有输入是否满足。如果是,则可以完全绕过求解函数。如果不是,则解决函数继续进行,但尝试更改为很少的选择。Gopkg.lock尽可能。 - Debug 函数已经对每个离散项目进行了哈希处理。
vendor/看看磁盘上的代码是什么Gopkg.lock指示应该是.只有哈希错配的项目才被重写.
具体地说,Dep 定义了一些必须满足的不变量:
| Sync invariant | Resolution when desynced | Func |
|---|---|---|
All required statements in Gopkg.toml must be present in the input-imports list in Gopkg.lock. | Re-solve, update Gopkg.lock and vendor/ for projects that changed | Solving |
All import statements in the current project's non-ignored, non-hidden packages must be present in input-imports list in Gopkg.lock. | Re-solve, update Gopkg.lock and vendor/ for projects that changed | Solving |
All versions in Gopkg.lock must be acceptable with respect to the [[constraint]] or [[override]] declarations made in Gopkg.toml. | Re-solve, update Gopkg.lock and vendor/ for projects that changed | Solving |
The pruneopts of each [[project]] in Gopkg.lock must equal the declaration in Gopkg.toml. | Update Gopkg.lock and vendor/ | Vendoring* |
The digest of each [[project]] in Gopkg.lock must equal the value derived from hashing the current contents of vendor/ | Regenerate the projects in vendor/, and update Gopkg.lock with the new hash digest if necessary | Vendoring |
(*pruneopts 有点奇怪,因为两者之间 Gopkg.toml 和 Gopkg.lock,但这并不能解决问题)。
如果向前查看显示同步不变量已经满足,那么相应的函数不需要做任何工作;如果不满足,则 dep 采取解析步骤。无论哪种方式, 什么时候 dep ensure 完成后,我们可以确信,我们处于"已知良好状态",所有的不变量保持不变。
dep check 将计算所有上述关系,如果任何不变量不成立,它将打印对去同步和出口 1 的描述。此行为可以在每个项目基础上禁用, 使用 noverify Gopkg.toml 字段。
dep ensure 标志与行为变异
每一个 dep ensure 各种标志会影响解决和发布功能的行为,甚至影响它们是否运行。一些标志也会暂时导致项目不同步。在 DEP 的基本模型的背景下思考这些效应是 理解正在发生的事情的最快路径。
-no-vendor 和 -vendor-only
这两个标志是互斥的,并决定哪一个 dep ensure 这两个函数实际上是被执行的。经过 -no-vendor 只会导致解决函数的运行,导致创建一个新的 Gopkg.lock; -vendor-only将跳过只解决版本管理功能,导致 vendor/ 被重新填充 Gopkg.lock。

经过 -no-vendor 有助于使解决功能无条件地运行,绕过通常进行的预检查。Gopkg.lock 看看它是否已经满足所有输入.
-add
通用的目的 dep ensure -add 是为了便于将新的依赖项引入到 DEPGRAM 中.反之 -update 限于 源根, (例如 github.com/foo/bar) -add 可以以任何包导入路径作为参数 (例如 github.com/foo/bar 或 github.com/foo/bar/baz)
从概念上讲,有两种可能的东西 -add 可能是介绍。任何 dep ensure -add 运行将至少其中之一:
- 运行解决函数以生成新的
Gopkg.lock用新的依赖(IES) - 将版本约束附加到
Gopkg.toml
这意味着两个前提条件. dep ensure -add 其中至少有一个必须满足:
- 命名导入路径当前不在项目的导入语句中,或者在
Gopkg.toml的required列表 - 有
[[constraint]]中Gopkg.toml对于与命名导入路径相对应的项目根
还可以显式指定版本约束:
$ dep ensure -add github.com/foo/bar@v1.0.0
当参数中不包含版本约束时,求解函数将选择工作的最新版本(通常,最新的 semver 版本,如果没有 semver 版本,则选择默认的分支)。 如果求解成功,那么参数指定的版本,或者如果没有,那么由求解器选择的版本将被附加到 Gopkg.toml。
由输入和当前项目状态的各种差异引起的行为变化最好用矩阵表示:
Argument to dep ensure -add | Has [[constraint]] stanza in Gopkg.toml | In imports or required | Result |
|---|---|---|---|
github.com/foo/bar | N | N | Added temporarily to Gopkg.lock & vendor/; inferred version constraint appended to Gopkg.toml |
github.com/foo/bar@v1.0.0 | N | N | Added temporarily to Gopkg.lock & vendor/; specified version constraint appended to Gopkg.toml |
github.com/foo/bar | Y | N | Added temporarily to Gopkg.lock & vendor/ |
github.com/foo/bar@v1.0.0 | Y | - | Immediate error: constraint already present in Gopkg.toml |
github.com/foo/bar | N | Y | Infer version constraint from Gopkg.lock and add to Gopkg.toml |
github.com/foo/bar | Y | Y | Immediate error: nothing to do |
对于任何路径 dep ensure -add 需要运行解决函数以便生成更新 Gopkg.lock 将来自 CLI 参数的相关信息应用于内存的表示。Gopkg.toml:

需要添加的导入路径参数通过 required 列表,如果指定了明确版本要求,则等于 [[constraint]] 创建。
虽然这些规则最终会被坚持,如果解决成功,它们至少是短暂的,直到解决成功。从求解者的角度来看,短暂的规则与源于磁盘的规则是难以区分的。因此, 对求解器,dep ensure -add foo@v1.0.0 与修改相同 Gopkg.toml 通过添加 "foo" 到 required 列表, 加上 [[constraint]] 节与 version = "v1.0.0" 然后运行 dep ensure。
然而,因为这些修改是短暂的,是成功的。dep ensure -add 可能实际上推动项目不同步。约束修改通常没有, 但是如果 required 列表被修改,然后该项目将不同步。用户被警告:
$ dep ensure -add github.com/foo/bar
"github.com/foo/bar" is not imported by your project, and has been temporarily added to Gopkg.lock and vendor/.
If you run "dep ensure" again before actually importing it, it will disappear from Gopkg.lock and vendor/.
-update
行为 dep ensure -update 与解决者自身的行为密切相关。关于这一点的完整细节是 求解参考材料,但为了理解的目的 -update 我们可以简化一点。
第一,巩固讨论中的含义。功能优化 求解函数实际上考虑了预先存在的问题。Gopkg.lock 运行时:

注射 Gopkg.lock 进入解决者是必要的。如果我们希望解决方案默认保存先前选定的版本,那么求解器必须了解现有的。Gopkg.lock 从某处。否则,它就不知道该保存什么了!
这样,锁是另一个编码到属性的属性。先前讨论 SolveParameters 结构。这加上另外两个属性,是最显著的。-update:
type SolveParameters struct {
...
Lock gps.Lock // Gopkg.lock
ToChange []gps.ProjectRoot // args to -update
ChangeAll bool // true if no -update args passed
...
}
通常,当求解器遇到一个项目名称时,它有一个条目 Gopkg.lock 它把这个版本拉出来,放到这个项目的可能版本队列的头上。当特定的依赖性传 递给 dep ensure -update 然而,它被添加到 ToChange 当解决方案遇到一个列出的项目时 ToChange 它只是跳过锁中的版本。
"跳过从锁中拉出版本"意味着 dep ensure -update github.com/foo/bar 等同于移除 [[project]]节 github.com/foo/bar 从你 Gopkg.lock 然后运行 dep ensure。 然而,确实不推荐使用这种方法,而且将来可能会引入一些细微的改变,使等效性复杂化。
如果 -update 没有参数传递,则 ChangeAll 设置为 true 导致解算器忽略 Gopkg.lock 对于所有新遇到的项目名称。这相当于将所有依赖项显式传递为参 数。dep ensure -update 以及 rm Gopkg.lock && dep ensure。 然而,这两种方法都不被推荐,未来的变化可能带来微妙的差异。
当版本提示从 Gopkg.lock 不是放在版本队列的最前面,这意味着 dep 将探索特定依赖项的可能版本集。这种探索是按照A来进行的。 固定排序顺序,首先尝试更新版本,导致更新。
比如说有一个项目,github.com/foo/bar,以下版本:
v1.2.0, v1.1.1, v1.1.0, v1.0.0, master
如果我们依赖那个项目 ^1.1.0 并拥有 v1.1.0 在我们 Gopkg.lock 这意味着有三个版本与我们的约束相匹配,其中两个版本比当前选择的版本要更 新。(还有一个较旧的版本,v1.0.0 和 master 分支,但这些是不允许的 ^1.1.0 约束)一个普通的 dep ensure 运行将复制和推 v1.1.0 排在队伍前面的其他人:
[v1.1.0, v1.2.0, v1.1.1, v1.1.0, v1.0.0, master]
和 v1.1.0 将再次被选中,除非出现一些其他条件迫使解算器丢弃它。跑步时 dep ensure -update github.com/foo/bar 然而,锁定的版本没有准备好:
[v1.2.0, v1.1.1, v1.1.0, v1.0.0, master]
所以,除了一些其他的冲突,v1.2.0 被选择,导致期望的更新。
-update 约束类型
继续我们的例子,重要的是要注意更新 -update 顺便说一下,解决者从来没有明确地针对更新的版本。它只是跳过从锁中添加一个提示,然后在队列中选择 满足约束的第一个版本。因此,-update 只对某些类型的约束有效。
它确实适用于分支约束,我们可以通过包括基础修订来观察。如果用户已受到限制 branch = "master" 和 Gopkg.lock 点在一个拓扑旧版 本(例如,aabbccd)比规范源的顶端 master 分支(如: bbccdde ) dep ensure 最终将构建一个看起来像这样的队列:
[master@aabbccd, v1.1.0, v1.2.0, v1.1.1, v1.1.0, v1.0.0, master@bbccdde]
用 -update 头部的暗示将被省略; branch = "master" 将导致求解器拒绝所有语义版本,并最终解决。master@bbccdde。
版本队列中的所有版本都跟踪底层修订,这意味着如果,例如,一些上游项目强制推送 git 标记,那么情况也是如此:
[v1.1.0@aabbccd, v1.1.0, v1.2.0, v1.1.1, v1.1.0@bbccdde, v1.0.0, master]
因此,即使上游标记在项目的一个依赖项中被强制推送,dep 将保留原始修订,直到你显式地允许它通过 dep ensure -update。
这里的关键是 -update 的行为受指定的约束类型的约束:
Gopkg.toml version constraint type | Constraint example | dep ensure -update behavior |
|---|---|---|
version (semver range) | "^1.0.0" | Tries to get the latest version allowed by the range |
branch | "master" | Tries to move to the current tip of the named branch |
version (non-range semver) | "=1.0.0" | Change can only occur if the upstream release was moved (e.g. git push --force <tag>) |
version (non-semver) | "foo" | Change can only occur if the upstream release was moved |
revision | aabbccd... | No change is possible |
| (none) | (none) | The first version that works, according to the sort order |