news 2026/9/28 8:42:04

Leiningen Profiles 完全指南:声明、合并、激活与调试项目配置

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Leiningen Profiles 完全指南:声明、合并、激活与调试项目配置
  • 构建工具
  • CLI

【免费下载链接】leiningen

Moved to Codeberg; this is a temporary convenience mirror

项目地址:https://gitcode.com/gh_mirrors/le/leiningen
点击查看免费下载

Leiningen 的 Profiles(配置文件/配置档)机制让你可以按场景灵活切换项目配置——例如在开发期把额外的测试数据目录挂到 classpath 上而不打进 jar,或在每个项目里都注入开发工具而不改动任何project.clj。本文基于 doc/PROFILES.md 展开,结合leiningen-core源码与测试,系统讲解 Profile 的声明位置、默认行为、合并规则、激活方式与调试手段,读完即可在生产项目中熟练运用with-profile、profiles.clj与 profile 元数据。

什么是 Profile

Profile 本质上是一个包含任意defproject支持键值对的映射(map)。当某个 profile 被激活时,它的内容会被合并进项目 map(project map),从而改变项目的配置。其典型用法是:开发时需要额外的测试数据目录和测试依赖,但又不希望它们进入发布产物:

(defproject myproject "0.5.0-SNAPSHOT" :description "A project for doing things." :dependencies [[org.clojure/clojure "1.4.0"]] :profiles {:dev {:resource-paths ["dummy-data"] :dependencies [[expectations "1.4.41"]]}})

上面的:devprofile 在开发期间把dummy-data目录加入资源路径,并引入测试框架expectations;因为:dev属于默认激活的 profile,它不会污染最终发布的 jar。

用show-profiles任务可以列出项目可用的全部 profile:

$ lein show-profiles

带参数时则显示单个 profile 的完整内容(该任务的实现在 src/leiningen/show_profiles.clj,其无参形式会列出所有已读取 profile 的名字,带参形式打印对应 profile map)。

声明 Profile

Profile 可以在多个层级声明,其优先级(从高到低)为:项目根目录的profiles.clj>project.clj> 用户级 profile > 系统级 profile。

项目内声明:project.clj 与 profiles.clj

在project.clj中通过:profiles键声明是最常见的方式(见上面的例子)。此外,还可以在项目根目录放置profiles.clj文件,其中的 profile 会通过合并逻辑覆盖project.clj中同名 profile,因此适合存放不想提交进版本控制的项目级覆盖配置。源码层面,read-profiles会依次读取系统级、用户级、project.clj与项目根profiles.clj,并用merge让后者覆盖前者,参见 leiningen-core/src/leiningen/core/project.clj 与 project-profiles。

用户级与系统级声明

用户级 profile 放在~/.lein/profiles.clj,对所有项目生效,但会被项目内同名 profile 覆盖。系统级 profile 放在/etc/leiningen/profiles.clj(Windows 上为AllUsersProfile\Leiningen),与用户级语义相同但优先级更低。

还可以在~/.lein/profiles.d目录下放置若干.clj文件定义用户级 profile。这里的语义与其他文件略有不同:文件顶层直接就是 profile map(而不是 map of maps),profile 的名字取自文件名(去掉.clj后缀)。例如~/.lein/profiles.d/devtools.clj的内容直接写成:

{:plugins [[lein-pprint "1.1.1"]]}

注意:同一个用户级 profile 若同时定义在~/.lein/profiles.clj和~/.lein/profiles.d中会被视为错误。这一行为的源码依据在 leiningen-core/src/leiningen/core/user.clj:profiles-d-profiles以文件名(去掉.clj)作为 profile 关键字,profiles函数用merge-with error-fn合并两个来源,重复定义会抛出异常并设置退出码 1。

用户级 profile 示例

如果你希望某些开发工具在每个项目里都可用,把它们放进~/.lein/profiles.clj的:userprofile:

{:user {:plugins [[lein-pprint "1.1.1"]] :dependencies [[slamhound "1.3.1"]]}}

默认 Profile

不通过with-profile指定其他 profile 集合时,以下 profile 默认处于激活状态。它们各自语义不同:

  • :user:用户级开发工具。想在任何项目里访问的依赖或插件都放这里(见上面示例)。
  • :dev:项目级开发工具。放构建或测试必需的东西,而不仅仅是便利工具。它与:user相互独立;为避免冲突,项目不应定义:userprofile,用户也不应定义全局:devprofile。
  • :system:类似:user,但作用范围是整个系统而非单个用户;系统级 profile 应使用:system,同样不应定义:user或:dev。
  • :base:Leiningen 自带的基底 profile,提供 REPL 基础功能所需的依赖、把dev-resources加入:resource-paths,并为:jvm-opts、:checkout-deps-shares、:test-selectors设置默认值。通常不需要修改它。
  • :provided:声明"假定由运行环境提供"的依赖——这些依赖在 jar 创建时需要,但不会传递给依赖你项目的下游代码。典型场景是 Hadoop 这类自带部分库的框架。
  • :default:指定运行lein任务时默认激活的 profile 集合;若未覆盖,其值为:leiningen/default,一个组合 profile,内容为[:base :system :user :provided :dev]。

以上默认 profile 在开发期间生效,但在生成 jar 和 pom 之前会被解除合并(unmerge),从而对依赖你项目的代码不可见。这部分默认 profile 的完整定义(包括:leiningen/default、:base、:leiningen/test、:uberjar、:update、:offline、:debug以及各 profile 的默认元数据)可查看 default-profiles 与 default-profile-metadata。

任务特定的 Profile

某些任务在运行时若检测到对应 profile 会自动合并:

  • test任务会自动合并:testprofile;
  • repl任务会自动合并:replprofile。

repl任务的源码确认了这一行为:在 src/leiningen/repl.clj 中,任务通过profiles-with-matching-meta找出带:repl元数据的 profile 并执行merge-profiles;该行为也记录在lein help repl的文档串中("the :repl profile is implicitly activated for this task")。注意:testprofile 是强烈不建议使用的:把东西放进去可能导致测试无法在 REPL 中运行。

Profile 元数据

Profile 可以携带元数据(metadata)来改变其行为:

  • ^:leaky:标记为 leaky 的 profile 在生成 pom 和 jar 时不会被剥离。其反向语义即non-leaky-profiles函数所实现的:默认情况下,非 leaky 的 profile 会在发布产物生成阶段被移除,参见 leiningen-core/src/leiningen/core/project.clj。:uberjarprofile 在默认元数据中被标记为:leaky true。
  • ^{:pom-scope :test}:profile 激活时,其:dependencies会以testscope 写入生成的 pom 和 jar。:dev、:test、:base三个 profile 已自动带此元数据。
  • ^{:pom-scope :provided}:profile 激活时,其:dependencies会以providedscope 写入 pom 和 jar。:providedprofile 已自动带此元数据。

这些默认元数据在 default-profile-metadata 中定义;将:pom-scope实际写入依赖的底层逻辑是set-dependencies-pom-scope(为每个依赖追加:scope <scope>),见 leiningen-core/src/leiningen/core/project.clj。

合并(Merging)

Profile 的合并规则是:对项目 map / profile map 中的每个键,如果值是集合则合并,否则替换。后指定的 profile 在替换时优先(与clojure.core/merge语义一致),例如:dev默认优先于:user。具体地:

  • map 递归合并;
  • set 用clojure.set/union合并;
  • list/vector 拼接(concatenated)。

合并逻辑的源码实现是meta-merge(见 leiningen-core/src/leiningen/core/project.clj):map 递归merge-with,set 取并集,集合按元数据决定拼接方向,类型不匹配时给出警告并取右侧值。

用元数据控制合并方向

默认的合并逻辑可以通过元数据提示来覆盖:

{:profiles {:dev {:prep-tasks ^:replace ["clean" "compile"] :aliases ^:displace {"launch" "run"}}}}
  • ^:replace:当前值优先(替换其他 profile 的值);
  • ^:displace:让位给其他 profile 的值。

^:top-displace则用于在更高优先级合并时被替换(源码中的pick-prioritized与different-priority?完整实现了这套元数据判定,见 leiningen-core/src/leiningen/core/project.clj)。

一个例外::plugins和:dependencies有自定义去重逻辑——虽然它们必须以向量形式声明,但行为上更像 map(同一依赖同一时刻只能存在一个版本)。不过 replace/displace 元数据提示仍然生效。这在源码中体现为empty-dependencies携带的:reduce归约函数reduce-dep-step(以 group/artifact/classifier/extension 为唯一键去重合并,见 leiningen-core/src/leiningen/core/project.clj)。

同名 profile 不合并

记住:同一个名字的 profile 出现在多个位置时,只会选取优先级最高的那一个,不做合并。优先级从高到低为:profiles.clj、project.clj、用户级 profile、系统级 profile。

如果需要个人化覆盖某个 profile 的部分内容,可以用组合 profile 拆分公共部分与个人覆盖部分,例如:dev [:dev-common :dev-overrides]:在project.clj里只放:dev-overrides {},然后在profiles.clj中覆盖它。

用 Profile 测试多版本依赖

Profile 常用于针对不同依赖组合运行测试:

(defproject swank-clojure "1.5.0-SNAPSHOT" :description "Swank server connecting Clojure to Emacs SLIME" :dependencies [[org.clojure/clojure "1.2.1"] [clj-stacktrace "0.2.4"] [cdt "1.2.6.2"]] :profiles {:1.3 {:dependencies [[org.clojure/clojure "1.3.0"]]} :1.4 {:dependencies [[org.clojure/clojure "1.4.0-beta1"]]}})

然后通过lein with-profile 1.3 test或lein with-profile 1.4 test切换 Clojure 版本。sample.project.clj也展示了类似做法(:1.4、:1.5分别引入不同 Clojure 版本),见 sample.project.clj。

激活 Profile

要为一个任务切换 profile 集合,使用with-profile这个高阶任务(higher-order task,其实现位于 src/leiningen/with_profile.clj)。

指定一个 profile:

$ lein with-profile 1.3 test :database

多个 profile 用逗号组合(合并后执行一次任务):

$ lein with-profile qa,user test :database

多个 profile 用冒号串联(依次执行多次任务,每次激活一组 profile;若某组失败会累计错误并在最后 abort,且会打印Performing task '...' with profile(s): '...'提示):

$ lein with-profile 1.3:1.4 test :database

上面的调用是替换默认 profile 集合;若要在默认集合之上追加,用+前缀;用-前缀可以停用某些 profile:

$ lein with-profile +server,+fast run

with-profile的源码还规定了一个约束:profile 要么全部带+/-前缀,要么全部不带,混用会抛出"Profiles in with-profile must either all be qualified, or none qualified"异常(见 src/leiningen/with_profile.clj)。

关于 target-path 的建议

默认情况下所有 profile 共享同一个:target-path,这可能导致某个 profile 的设置泄漏到另一个 profile。建议把:target-path设为"target/%s",这样每个 profile 集合被隔离到独立子目录,防止相互污染:

(defproject myproject "0.5.0-SNAPSHOT" :target-path "target/%s" ...)

源码中profile-scope-target-path正是用target/%s中的%s填充激活 profile 的规范化名称(并对 map 类 profile 计算 SHA1 前缀),见 leiningen-core/src/leiningen/core/project.clj。

组合 Profile(Composite Profiles)

可以把 profile 定义为其他 profile 的组合——将 profile 值写成向量而非 map,向量内放引用其他 profile 的关键字,合并时按顺序把各 profile 合并到一起。这能避免重复定义:

{:shared {:port 9229, :protocol "https"} :qa-servers {:servers ["qa.mycorp.com"]} :prod-servers {:servers ["prod1.mycorp.com", "prod1.mycorp.com"]} :qa [:shared :qa-servers] :production [:shared :prod-servers]}

注意两个限制:

  1. 虽然目前允许组合 profile 中混入 map(源码会给出"Composite profiles containing maps are strongly recommended against"的警告,见 leiningen-core/src/leiningen/core/project.clj),但这在未来版本中会成为错误。建议把 map 拆成独立命名的顶层 profile。
  2. 组合 profile 无法传播某些类型的元数据,因此与:providedprofile不兼容。如果遇到 "Composite profiles are incompatible with :provided." 错误(该异常由lookup-profile*抛出,见 leiningen-core/src/leiningen/core/project.clj),改为给包含依赖的那个 profile map 加上^{:pom-scope :provided}元数据。

动态求值(Dynamic Eval)

有时你想在 profile 中读取环境变量或执行函数来获取值。在profiles.clj中需要借助 read-eval 语法#=(eval ...):

{:user {:compile-path #=(eval (System/getenv "ci.compile-path")), :target-path #=(eval (System/getenv "ci.target-path"))}}

调试 Profile

要查看某个 profile 如何影响项目 map,使用lein-pprint插件(本仓库即自带实现 lein-pprint/src/leiningen/pprint.clj,lein-pprint也是文档调试示例中使用的插件):

$ lein with-profile 1.4 pprint {:compile-path "/home/phil/src/leiningen/lein-pprint/classes", :group "lein-pprint", :source-path ("/home/phil/src/leiningen/lein-pprint/src"), :dependencies ([nrepl "0.8.3" :exclusions [org.clojure/clojure]] [incomplete "0.1.0" :exclusions [org.clojure/clojure]] [org.thnetos/cd-client "0.3.3" :exclusions [org.clojure/clojure]]), :target-path "/home/phil/src/leiningen/lein-pprint/target", :name "lein-pprint", [...] :description "Pretty-print a representation of the project map."}

pprint任务支持--no-pretty --前缀以关闭美化输出,也支持传入 selector(如lein pprint :dependencies)只打印项目 map 中的指定键,参见 lein-pprint/src/leiningen/pprint.clj。

发布产物中的 Profile 处理

为了防止 profile 设置泄漏给依赖你项目的其他工程,在生成 pom、jar 和 uberjar 时,:default系列 profile 会被从项目中移除;而创建 uberjar 时,若存在:uberjarprofile 则会包含进来(这在想为 uberjar 指定:main命名空间、又不想在常规开发中触发 AOT 编译时很有用)。通过显式with-profile激活的 profile 会被保留。

这一"剥离"逻辑的源码基础是non-leaky-profiles(移除所有未带:leaky元数据的已激活 profile)与pom-scope-profiles(按:pom-scope分类返回 profile),见 leiningen-core/src/leiningen/core/project.clj;profile 元数据在测试与 dev-resources 中也有印证,例如 leiningen-core/dev-resources/profile-metadata.clj 用^:replace、^:displace元数据验证合并行为,测试用例 leiningen-core/test/leiningen/core/test/project.clj 覆盖了 profile 合并、解合并与优先级等场景。

小结

Leiningen 的 profile 体系覆盖了从用户级工具注入到项目级开发配置、从多版本依赖测试到发布产物隔离的完整需求。掌握四个要点即可熟练使用:声明位置决定优先级(profiles.clj>project.clj> 用户级 > 系统级)、合并规则默认集合拼接(map 递归、set 并集、向量拼接,必要时用^:replace/^:displace干预)、激活方式灵活(with-profile的逗号合并、冒号串联、+/-增删),以及用元数据控制发布行为(^:leaky与^{:pom-scope ...})。遇到配置不生效时,lein show-profiles和lein with-profile <name> pprint是你最趁手的两个调试工具。

  • 构建工具
  • CLI

【免费下载链接】leiningen

Moved to Codeberg; this is a temporary convenience mirror

项目地址:https://gitcode.com/gh_mirrors/le/leiningen
点击查看免费下载

相关推荐

上一篇:Macro开发者指南:用Nix开发环境5分钟跑通前端项目
下一篇:OpenCV hdf 模块实战:基于 libhdf5 的分层数据读写、分组与属性操作指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 8:40:46

机器人DH参数选择:标准型与改进型的本质区别与工程选型指南

1. 这不是参数表填空题&#xff0c;而是机器人运动学建模的“第一道生死关”刚带完三届本科毕设&#xff0c;每年都有学生卡在DH参数这一步——不是不会算&#xff0c;是根本不知道自己算出来的结果能不能用。有位同学给松灵Piper机械臂建模&#xff0c;标准DH参数表填得工工整…

作者头像 李华
网站建设 2026/9/28 8:40:46

Node.js + Vue 红色旅游网站开发实践:接口设计、视频播放与地图标记

做这个基于 Node.js Vue 的红色旅游网站之前&#xff0c;我以为它无非是“景点介绍站”的换皮版本。真正把需求理完才发现&#xff0c;游客要的不只是列表和详情页&#xff0c;还有路线推荐、地图标记、宣传片播放、预约留资和心得体会提交这类交互功能。这篇文章把我从环境配…

作者头像 李华
网站建设 2026/9/28 8:40:00

C#读取WINCC归档数据库实战指南:从连接串到断点续读

简介&#xff1a;面向工业自动化开发者的C#读取WinCC归档数据库示例工程&#xff0c;聚焦S7-300过程归档与历史数据查询场景。压缩包内含完整Visual Studio项目&#xff0c;共39个文件&#xff1a;10个C#源文件、3个可直接运行的exe、2个dll动态库&#xff0c;另有配置、资源、…

作者头像 李华
网站建设 2026/9/28 8:39:42

Python数字类型与运算全解析:从int到浮点数精度

1. 数字类型全景&#xff1a;Python的数字远比你想的更有意思很多人都觉得数字有什么好讲的&#xff1f;不就是整数、小数嘛。但Python里的数字类型&#xff0c;从设计思路上就和C、Java这些语言不太一样。如果你是从C语言转过来的&#xff0c;第一次看到Python的整数没有位数上…

作者头像 李华
网站建设 2026/9/28 8:39:12

C语言工具链升级后bug排查:从未定义行为到内存越界的实战方法

1. “更新后出bug”到底是谁的锅&#xff1f;一提到“C语言更新后bug”&#xff0c;很多人的第一反应是&#xff1a;“C语言还会更新&#xff1f;”确实&#xff0c;语言标准本身的节奏很慢&#xff0c;C17还没捂热&#xff0c;C23就来了。但我们在实际开发里说的“C语言更新”…

作者头像 李华
网站建设 2026/9/28 8:39:06

Node.js + Vue 导师双选分配系统:开发实践与踩坑复盘

临近毕业季&#xff0c;各个学院的导师双选又开始一年一度的"人肉大战"。我所在的项目组接到的任务很明确&#xff1a;学院现有导师几十位&#xff0c;研究生新生过百&#xff0c;往年靠辅导员手动收集志愿表、再逐一核对名额和方向&#xff0c;反复沟通协调至少耗掉…

作者头像 李华