文章摘要
文章探讨了依赖管理中的锁文件问题,认为在确定性的依赖解析算法下,只需记录顶层依赖版本即可,无需额外锁文件。通过libpupa和liblupa的版本依赖关系,说明依赖树是确定且空间高效的,锁文件显得多余。
文章总结
我们本不需要锁文件
想象一下,你在编写一个项目时需要引入一个库,假设它叫libpupa。你查到了它的当前版本是1.2.3,并将其添加到项目的依赖中:
json
"libpupa": "1.2.3"
而libpupa的开发者在其版本1.2.3中也需要另一个库:liblupa。于是他们做了同样的事情:查到了当时的版本0.7.8,并将其添加到libpupa 1.2.3的依赖中:
json
"liblupa": "0.7.8"
这样一来,liblupa的版本0.7.8就永远被锁定在libpupa 1.2.3的依赖中。无论liblupa或libpupa发布多少新版本,libpupa 1.2.3始终依赖liblupa 0.7.8。
依赖解析的算法如下:
- 获取顶层的依赖版本。
- 查找这些依赖所依赖的库的版本。
- 递归查找这些库的依赖版本。
这个算法的关键点在于它是完全确定性的。只要给定顶层的依赖,它每次都会生成相同的依赖树。同时,它也是空间高效的:你只需要指定顶层的依赖版本,而不需要列出所有依赖。例如,给定libpupa 1.2.3,我们总能推导出liblupa 0.7.8。既然如此,为什么还要将这些信息单独写在一个文件中呢?
理论上,你只需要写下顶层的依赖,计算机就能推导出传递依赖。由于一切都是不可变的,构建过程是完全可复现的。然而,人们却发明了锁文件(lockfiles)。
锁文件的出现,本质上是因为人们引入了版本范围(version ranges),使得构建过程依赖于时间。例如,如果你现在构建应用,可能会得到libpupa 1.2.3和liblupa 0.7.8;但如果你在10分钟后重复同样的构建,可能会得到liblupa 0.7.9。这种不确定性显然会带来混乱。
版本范围的问题在于,它并不是在发布时确定的,而是在构建时确定的。例如,libpupa 1.2.3的作者可能在一年前发布了这个版本,而你现在拉取它时,可能会使用一个在发布时甚至不存在的liblupa版本。这显然是不可靠的,因为作者无法预知未来的版本是否兼容。
有趣的是,这些版本范围最终往往并没有被真正使用。你只需在锁文件中锁定一次依赖,它们就会保持不变。你甚至无法享受到版本范围带来的“好处”。
有人可能会说,锁文件可以帮助解决版本冲突。但实际上,版本冲突并不是由依赖文件中的内容引起的。你的库可能兼容新版本的依赖,也可能不兼容,这并不取决于库作者的猜测。无论怎样,解决方案都是一样的:你需要选择一个兼容的版本。而锁文件并不能真正解决这个问题。
还有人认为,既然锁文件存在,那一定有它的理由。然而,在IT领域,人们常常做一些没有实际意义的事情。以Maven为例,Java库生态系统已经运行了20年,从未需要锁文件。Maven通过选择离根节点最近的版本来解决冲突,这种方式仍然是确定性的,且不需要锁文件。
总之,锁文件是一个完全没有必要的概念,它只会让事情变得更复杂,而没有带来实质性的好处。依赖管理器完全可以像Maven一样,在没有锁文件的情况下正常工作。
评论总结
评论内容主要围绕锁文件(lockfiles)的必要性展开,观点分为支持和反对两派,以下是总结:
支持锁文件的观点:
确保构建的可重复性:锁文件可以确保开发、测试和生产环境使用相同的依赖版本,避免因依赖更新引入新问题。
- 引用:
- "Lockfiles are essential for somewhat reproducible builds." (andix)
- "Lockfiles don’t guarantee that new versions are compatible, but it guarantees that if your code works in development, it will work in production." (boscillator)
- 引用:
解决依赖冲突:锁文件通过版本范围解析依赖冲突,确保所有依赖的版本兼容。
- 引用:
- "The reason we have dependency ranges and lockfiles is so that library a1.0 can declare 'I need >2.1' and b1.0 can declare 'I need >2.3'." (lalaithion)
- "Lockfiles are about giving the top-level application control over their dependency tree." (epage)
- 引用:
安全更新:锁文件允许开发者手动控制依赖更新,避免自动更新引入安全风险。
- 引用:
- "There is absolutely a good reason for version ranges: security updates." (trjordan)
- "Lockfiles help resolve version conflicts!" (zahlman)
- 引用:
反对锁文件的观点:
增加复杂性:锁文件增加了依赖管理的复杂性,尤其是在库开发中,可能导致与其他依赖的冲突。
- 引用:
- "For libraries you shouldn’t be locking exact versions because it will inevitably pay havoc with other dependencies." (simonw)
- "Pinned (non-ranged) versions result in users being unable to use your library in an environment with other libraries." (freetonik)
- 引用:
依赖版本范围的灵活性:版本范围允许自动更新依赖,避免手动维护锁文件的负担。
- 引用:
- "Version ranges give me multiple possible universes, and then for reproducibility I use a lockfile to define one." (shadowgovt)
- "Semantic versioning is a hint, but it has never been a guarantee." (zahlman)
- 引用:
其他语言的替代方案:某些语言(如Java、Go)通过其他机制(如Maven的依赖管理、Go的minimal version selection)实现类似功能,无需锁文件。
- 引用:
- "Maven, by default, does not check your transitive dependencies for version conflicts." (hyperpape)
- "Go includes a 'sum' file, which basically tells you the checksums of the versions of the modules." (numbsafari)
- 引用:
其他相关讨论:
网站设计问题:多位评论者批评文章底部的动画图标干扰阅读体验。
- 引用:
- "anyone find a way to get rid of the constantly shifting icons at the bottom of the screen?" (ratelimitsteve)
- "I can’t read your article because of that animation at the bottom." (andy99)
- 引用:
依赖管理的复杂性:不同语言和生态系统对依赖管理的需求不同,锁文件的适用性因语言而异。
- 引用:
- "The proposed dependency resolution algorithm... would fail in languages like Python where dependencies are shared." (palotasb)
- "npm’s dependencies-of-dependencies design introduces its own risks and sharp edges." (shadowgovt)
- 引用:
总结:
锁文件在确保构建可重复性和解决依赖冲突方面具有重要作用,尤其在应用开发中。然而,在库开发中,锁文件可能导致与其他依赖的冲突,增加复杂性。不同语言和生态系统对锁文件的需求和实现方式各异,部分语言通过其他机制实现类似功能。