构建工具CLI【免费下载链接】leiningenMoved to Codeberg; this is a temporary convenience mirror项目地址https://gitcode.com/gh_mirrors/le/leiningen点击查看免费下载Leiningenlein在 Maven Dependency Management 理念的基础上通过project.clj中的:managed-dependencies配置把公共依赖的版本号集中到一处再由多个独立项目复用从而解决上游库一升级、全项目组跟着打地鼠式改版本的痛点。本文以 doc/MANAGED_DEPS.md 为骨架结合 leiningen-core/src/leiningen/core/classpath.clj、leiningen-core/src/leiningen/core/pedantic.clj 等源码实现讲解托管依赖的两种核心能力、修饰符modifier兼容语法、通过lein-parent插件跨项目共享版本号以及其在 POM 生成与:pedantic? :abort场景中的实际作用读完即可在自己的项目中落地这套集中化版本管理方案。为什么要用托管依赖在 Clojure 生态中一个大型项目组往往维护着十几个甚至几十个 lein 项目它们可能同时依赖clj-time、me.raynes/fs、ring/ring-codec等公共库。如果每个项目的project.clj都各自写死版本号那么当某个公共库发布新版本时你就需要逐个打开每个项目手工升级版本——这正是文档中描述的dependency version whack-a-mole依赖版本打地鼠游戏。更麻烦的是与:pedantic? :abort的组合使用。Leiningen 的 pedantic 模式leiningen-core/src/leiningen/core/pedantic.clj会在依赖解析后检查依赖树一旦发现版本范围version range或存在被覆盖的依赖即某个依赖的版本被冲突解析悄悄替换就会依据配置打印警告甚至直接abort。当上游库悄悄升级了它的某个依赖时这种意外覆盖会频繁触发让人疲于应付。:managed-dependencies的价值就在于把版本号集中收口让所有项目对公共依赖使用同一份、可预测的版本来源。从源码看这一机制在依赖解析的入口就已生效leiningen-core/src/leiningen/core/classpath.clj 中的resolve-managed-dependencies会把:managed-dependencies作为:managed-coordinates传入底层 Aether 解析器get-dependencies*中:managed-coordinates (get project managed-dependencies-key)由解析器统一完成缺省版本回填。这意味着托管依赖不是 lein 在project.clj层面的字符串拼接而是真正参与 Maven/Aether 的依赖解析语义。:managed-dependencies的两种能力:managed-dependencies段的写法与普通:dependencies段几乎一样唯一的区别体现在两点它不引入任何实际依赖。它只表达一种约定lein如果你在后面遇到这些依赖且没有显式写版本号就用这里声明的版本兜底。它允许:dependencies段省略版本号。凡是出现在:managed-dependencies里的坐标都可以在:dependencies中只写[group/artifact]而不带版本。文档中的典型示例(defproject superfun/happyslide 1.0.0-SNAPSHOT :description A Clojure project with managed dependencies :min-lein-version 2.7.0 :managed-dependencies [[clj-time 0.12.0] [me.raynes/fs 1.4.6] [ring/ring-codec 1.0.1]] :dependencies [[clj-time] [me.raynes/fs]])在这个例子中最终解析出的项目会使用clj-time 0.12.0与me.raynes/fs 1.4.6而ring/ring-codec虽然出现在:managed-dependencies中但因为没有出现在真正的:dependencies段不会被加入项目依赖。这一点在 test/leiningen/test/deps.clj 的test-managed-deps测试中得到了直接验证测试先删除所有托管依赖的本地 artifacts运行deps后逐一检查——真正用到的依赖会被下载而未使用的托管依赖的 artifacts不应被下载。仓库中 test_projects/managed-deps/project.clj 给出了一个更贴近真实工程的全景示例同时混用了多种写法省略版本号[org.clojure/clojure]、显式nil[rome/rome nil]、带修饰符的省略[commons-math nil :classifier sources]、以及带:exclusions的省略[ring/ring-headers :exclusions [ring/ring-core]]。你可以把它当作一份语法速查表。与普通写法并列时并无优势文档特别提醒superfun/happyslide这种把:managed-dependencies和:dependencies写在同一个project.clj里的做法本身价值有限——版本号就近写在:dependencies里反而更直白。托管依赖的真正威力来自跨项目共享:managed-dependencies段的构建工作流详见下文Lein parent 项目一节。修饰符:exclusions、:classifier 等的两类合法语法托管依赖对:exclusions、:classifier等修饰符是兼容的共有两种合法写法显式给版本写nil或者直接省略版本字符串。;; 写法一直接省略版本号修饰符跟在坐标后面 (defproject superfun/happyslide 1.0.0-SNAPSHOT :description A Clojure project with managed dependencies :min-lein-version 2.7.0 :managed-dependencies [[clj-time 0.12.0]] :dependencies [[clj-time :exclusions [foo]]]);; 写法二显式写 nil 占位版本 (defproject superfun/happyslide 1.0.0-SNAPSHOT :description A Clojure project with managed dependencies :min-lein-version 2.7.0 :managed-dependencies [[clj-time 0.12.0]] :dependencies [[clj-time nil :exclusions [foo]]])两种写法等价。底层实现上leiningen-core/src/leiningen/core/classpath.clj 的normalize-dep-vector/normalize-dep-vectors会把省略版本号的奇数长度依赖向量自动注入nil占位并保留原 metadata 以用于 profile 合并之后交给 Aether 的merge-versions-from-managed-coords从托管坐标中回填版本。文档注释中那句defproject是宏插件可能晚些时候把关键字替换成版本字符串正是normalize-dep-vector对偶数长度向量直接放行的原因。:classifier 需要两边同时声明需要特别注意的是:classifier属于 Maven 坐标的一部分而不是可选修饰符。因此对于带 classifier 的构件你必须在:managed-dependencies和:dependencies两处都写出:classifier值(defproject superfun/happyslide 1.0.0-SNAPSHOT :description A Clojure project with managed dependencies :min-lein-version 2.7.0 :managed-dependencies [[commons-math 1.2 :classifier sources]] :dependencies [[commons-math :classifier sources]])也就是说:managed-dependencies中带:classifier sources的commons-math 1.2只有在:dependencies中也写出同样的:classifier sources时才能正确匹配。仓库测试项目 test_projects/managed-deps/project.clj 中的[commons-math nil :classifier sources]与[org.apache.commons/commons-csv :classifier sources]就是这种两处声明写法的实际用例。Lein parent 项目跨项目共享版本号既然托管依赖的价值在于共享文档给出的第一种落地方式是 lein-parent 插件。该插件允许定义一个父项目被多个子项目继承;; 父项目集中声明公共依赖版本 (defproject superfun/myparent 1.0.0 :managed-dependencies [[clj-time 0.12.0] [me.raynes/fs 1.4.6] [ring/ring-codec 1.0.1]]) ;; 子项目 A (defproject superfun/kid-a 1.0.0-SNAPSHOT :parent-project [:coords [superfun/myparent 1.0.0] :inherit [:managed-dependencies]] :dependencies [[clj-time] [me.raynes/fs]]) ;; 子项目 B (defproject superfun/kid-b 1.0.0-SNAPSHOT :parent-project [:coords [superfun/myparent 1.0.0] :inherit [:managed-dependencies]] :dependencies [[clj-time] [ring/ring-codec]])在这个例子里父项目superfun/myparent收口了三个公共库的版本kid-a与kid-b通过:parent-project [:coords ... :inherit [:managed-dependencies]]继承这些版本子项目自身的:dependencies中不再出现任何版本号。由于两个子项目共享同一份托管坐标全组项目会始终使用相同版本的公共依赖uberjar构建也因此更可预测、可重复——这正对应文档强调的 more predictable and repeatable。其他共享方式与未来的核心集成除lein-parent外文档还指出由于defproject本身是一个宏理论上完全可以写出动态生成:managed-dependencies值的插件——例如从某个配置文件、内部仓库或构建系统读取版本清单再注入到每个项目的project.clj中从而避免在每个项目里手工维护该段。文档亦明确说明将lein-parent的功能并入 leiningen 核心是未来可能的方向截至目前leiningen 核心只加入了:managed-dependencies这一能力因为它正是该插件得以工作的必要基础。从 NEWS.md 的发布记录可以看到这条功能演进线先是添加:managed-dependencies支持随后修复了快照SNAPSHOT依赖在托管依赖场景下的 bug并放宽了托管依赖中的版本不必写nil的要求。换言之nil占位在早期版本是强制的如今可以省略——这也解释了文档为何给出两种等价写法。托管依赖与 POM 生成、pedantic 模式的配合POM 中的反映:managed-dependencies不只是 classpath 层面的行为它还会反映到生成的pom.xml中。在 src/leiningen/pom.clj 中pom任务会读取(:managed-dependencies project)并将托管坐标以 Maven 的dependencyManagement语义写入 POM 的 dependencies 区段(xml-tags :dependencies (distinct-key dep-key managed-deps))。这意味着下游 Maven/lein 项目在消费你的构件时同样能感知到你声明的托管版本——这是它和 Maven Dependency Management 语义对齐的又一个证据。与 :pedantic? :abort 的协同回到文档开篇的动机:managed-dependencies是为了缓解:pedantic? :abort下的版本打地鼠。从 leiningen-core/src/leiningen/core/pedantic.clj 的实现看pedantic 检查会在依赖解析的图变换阶段收集版本范围ranges与被覆盖的依赖overrides并在:abort或true模式下直接调用leiningen.core.main/abort。当上游库 bump 版本导致冲突时这个机制会频繁打断构建而把公共依赖版本统一收口到托管坐标后你的项目对每个公共库只请求一个明确版本冲突面被大幅收敛构建的一致性和可预测性自然随之提升。此外托管坐标的解析也受 profile 影响测试 test/leiningen/test/deps.clj 验证了在:add-deps、:replace-deps等 profile 场景下托管依赖仍能正确生效包括^:replace元数据覆盖默认依赖的行为。这说明托管版本是基线而 profile 仍可按需覆盖二者可以组合使用。版本约定与适用前提最后给出几条基于文档与源码的实践约定defproject的版本与语法示例统一使用:min-lein-version 2.7.0这是文档示例给出的最低版本标注托管依赖在后续版本中不断完善如放宽nil要求、修复 SNAPSHOT bug实际使用建议采用更新版本的 lein。版本号省略只在托管清单内有效如果某个坐标没出现在:managed-dependencies中却在:dependencies里省略了版本号依赖解析将无法回填——normalize-dep-vector注入的nil最终会在 Aether 层报错。因此托管清单必须覆盖所有想省略版本号的依赖。:classifier必须两处声明这是 Maven 坐标语义决定的容易踩坑。托管清单不会引入依赖出现在:managed-dependencies但未出现在:dependencies的构件不会被下载、也不会进入 classpath测试 test/leiningen/test/deps.clj 已给出明确验证。跨项目共享是发挥价值的正确姿势单独在项目内使用托管依赖收益有限配合lein-parent或自定义宏插件才能把集中管理、一处升级的红利扩散到整个项目组。如果想在本地亲手验证可以阅读仓库中的 test_projects/managed-deps/project.clj含 SNAPSHOT 变体 test_projects/managed-deps-snapshot/project.clj与对应测试 test/leiningen/test/deps.clj它们完整覆盖了托管依赖的解析、修饰符、profile 与未使用托管依赖不下载等关键行为是学习与回归验证的一手资料。赞分享构建工具CLI【免费下载链接】leiningenMoved to Codeberg; this is a temporary convenience mirror项目地址https://gitcode.com/gh_mirrors/le/leiningen点击查看免费下载相关推荐Leiningen依赖版本管理范围、通配符与动态版本Leiningen依赖版本管理范围、通配符与动态版本 在Clojure项目开发中依赖版本管理是确保构建稳定性和团队协作效率的关键环节。你是否还在为多项目版本构建工具CLI告别依赖地狱Selenium多语言项目版本管理实战指南告别依赖地狱Selenium多语言项目版本管理实战指南 你是否还在为Selenium项目中Python、Java、Ruby等多语言依赖版本冲突而头疼是否经历测试开发工具InternVL3_5-14B的OCR与文档理解实战表格、公式、图表精准提取完全指南InternVL3_5 14B的OCR与文档理解实战表格、公式、图表精准提取完全指南 本文是 InternVL3_5 14B OCR 文字识别与文档理解 的完上一篇3步掌握PUBG-Logitech开源罗技鼠标宏压枪工具的终极指南下一篇League Akari英雄联盟玩家如何3步告别手动操作的终极效率革命创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考