Clojure:如何判断代码是在REPL中运行还是在JAR中运行?
我正在用Clojure编写一个名为的CLI框架。这个框架的主要核心是一个名为Clojure:如何判断代码是在REPL中运行还是在JAR中运行?,clojure,read-eval-print-loop,leiningen,deps-edn,Clojure,Read Eval Print Loop,Leiningen,Deps Edn,我正在用Clojure编写一个名为的CLI框架。这个框架的主要核心是一个名为go,它“为您”解析命令行、环境变量和配置文件,并根据这些输入中提供的内容运行几个不同的用户提供的函数之一 通常,go。例如,我在另一个名为uberjar的“uberjar”风格的应用程序中使用自己的库。函数go调用System/exit,作为其运行的一部分,向其传递来自用户提供的函数结果的退出代码。这在“生产环境”中非常有效,但也意味着我不能从REPL运行zic.cli/-main函数,因为每当我这样做时,它调用Sys
go
,它“为您”解析命令行、环境变量和配置文件,并根据这些输入中提供的内容运行几个不同的用户提供的函数之一
通常,go通过用户调用Clojure程序的-main
函数调用code>。例如,我在另一个名为uberjar的“uberjar”风格的应用程序中使用自己的库。函数go
调用System/exit
,作为其运行的一部分,向其传递来自用户提供的函数结果的退出代码。这在“生产环境”中非常有效,但也意味着我不能从REPL运行zic.cli/-main
函数,因为每当我这样做时,它调用System/exit
并退出REPL
在您询问之前,在raspberry pi上开发时从REPL运行它可以避免运行lein uberjar
/1分30秒所需的昂贵的45秒运行clj-X:depstar uberjar:jar…
我的问题是:作为Clojure标准库的一部分,我是否可以检查一些变量或值,告诉我的OneCLI代码是从REPL运行还是从JAR运行?
这样一个变量将使我在OneCLI中能够检测到我们正在从REPL运行,这样它就可以避免调用System/exit
每个Java JAR文件都必须具有文件META-INF/MANIFEST.MF
补充。如果它不存在,则不能在(普通)JAR文件中运行。虽然您可以通过在类路径上放置一个虚假文件(例如,在/resources
中)来愚弄这个检测器,但这是检测普通JAR文件的可靠方法
问题:
依赖项JAR文件有时很松散,并且会用它们自己的META-INF/MANIFEST.MF
文件污染类路径,因此任何随机的META-INF/MANIFEST.MF
都不足以确定存在“噪音”文件时的答案。因此,您需要检查您自己的特定META-INF/MANIFEST.MF
文件是否存在。如果您知道ArtifactId
和GroupId
的Maven值,那么这很容易做到
在Leiningen项目中,project.clj
的第一行如下所示
(defproject demo-grp/demo-art "0.1.0-SNAPSHOT"
对于组ID为demo grp
和工件ID为demo art
。如果您的文件如下所示:
(defproject demo "0.1.0-SNAPSHOT"
然后组ID和工件ID都将是demo
。您的特定MANIFEST.MF如下所示
> cat META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: Leiningen 2.9.1
Built-By: alan
Build-Jdk: 15
Leiningen-Project-ArtifactId: demo-art
Leiningen-Project-GroupId: demo-grp
Leiningen-Project-Version: 0.1.0-SNAPSHOT
Main-Class: demo.core
使用to ID字符串设置函数,以检测特定项目MANIFEST.MF的存在:
(ns demo.core
(:require [clojure.java.io :as io])
(:gen-class))
(def ArtifactId "demo-art")
(def GroupId "demo-grp")
(defn jar-file? []
(let [re-ArtifactId (re-pattern (str ".*ArtifactId.*" ArtifactId))
re-GroupId (re-pattern (str ".*GroupId.*" GroupId))
manifest (slurp (io/resource "META-INF/MANIFEST.MF"))
f1 (re-find re-ArtifactId manifest)
f2 (re-find re-GroupId manifest)
found? (boolean (and f1 f2))]
found?))
(defn -main []
(println "main - enter")
(println "Detected JAR file: " (jar-file?))
)
现在可以测试代码:
~/expr/demo > lein clean ; lein run
main - enter
Detected JAR file: false
~/expr/demo > lein clean ; lein uberjar
Compiling demo.core
Created /home/alan/expr/demo/target/uberjar/demo-art-0.1.0-SNAPSHOT.jar
Created /home/alan/expr/demo/target/uberjar/demo-art-0.1.0-SNAPSHOT-standalone.jar
~/expr/demo > java -jar /home/alan/expr/demo/target/uberjar/demo-art-0.1.0-SNAPSHOT-standalone.jar
main - enter
Detected JAR file: true
示例“noise”JAR文件:如果我们执行lein-clean;lein运行
,并在主程序中添加一行
(println (slurp (io/resource "META-INF/MANIFEST.MF")))
我们走出去:
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: jenkins
Created-By: Apache Maven 3.2.5
Build-Jdk: 1.8.0_111
我不知道这是从哪里来的
Leiningen JAR文件的p.S.
使用lein
构建JAR文件时,它总是将project.clj
文件的副本放在以下位置:
META-INF/leiningen/demo-grp/demo-art/project.clj
因此,您还可以使用此文件的存在/不存在作为检测器
更新
好的,MANIFEST.MF文件似乎高度依赖于您的构建工具。看
因此,您的选择似乎是:
对于lein
,您可以使用上述技术
您可以使用另一个答案中的REPL技巧*1
您可以始终让构建工具在清单中包含一个自定义键值对,然后检测它
更新#2
另一个可能更简单的答案是使用lein-environ
插件和environ
库(两者都需要)来检测环境(假设您使用lein
创建REPL)。您的项目.clj
应该如下所示:
:dependencies [
[clojure.java-time "0.3.2"]
[environ "1.2.0"]
[org.clojure/clojure "1.10.2-alpha1"]
[prismatic/schema "1.1.12"]
[tupelo "21.01.05"]
]
:plugins [[com.jakemccrary/lein-test-refresh "0.24.1"]
[lein-ancient "0.6.15"]
[lein-codox "0.10.7"]
[lein-environ "1.2.0"]
]
您需要一个配置文件。clj
:
{:dev {:env {:env-mode "dev"}}
:test {:env {:env-mode "test"}}
:prod {:env {:env-mode "prod"}}}
和命名空间demo.config
类似:
(ns demo.config
(:require
[environ.core :as environ]
))
(def ^:dynamic *env-mode* (environ/env :env-mode))
(println " *env-mode* => " *env-mode*)
然后你会得到如下结果:
*env-mode* => dev ; for `lein run`
*env-mode* => test ; for `lein test`
*env-mode* => nil ; from `java -jar ...`
您需要键入:
lein with-profile :prod run
产生
*env-mode* => prod
我不知道如何检测您是否正在运行REPL。我快速浏览了一遍,但与通过clojure-m
运行的内容相比,我没有看到任何钩子来检测您是否在REPL中
如果您正在使用AOT(就像您在zic
),那么您可以检查是否绑定了任何“REPL”变量(*1
、*2
、*3
和*e
)
;;在REPL和'clojure-m'中返回true,并且
;; 在使用java-jar运行的AOT jar文件中返回false
(绑定?#’*1)
这就解决了你的问题,但我不喜欢这种猜测程序员意图的“神奇”机制。它可能适用于您的用例(考虑到我认为AOT可以节省启动时间,而且CLI工具可能希望快速启动),但我处理的所有项目都不使用AOT
解决clojure-m
案例中的问题的另一个选择是要求开发人员明确选择退出“完成时退出”行为。一种方法是使用属性
(defn可能退出[退出代码]
(续)
(=(系统/getProperty“onecli.oncompletion”)“保留”)(系统/退出代码)
(=退出代码0)无
:else(throw(exinfo)命令未成功完成“{:退出代码退出代码}”))
使用此代码,您可以在开发环境中添加
:jvm选择[“-Donecli.oncompletion=remaine”]
添加到deps.edn
或project.clj
文件,但在“生产”中运行时将其删除。这样做的好处是更显式,但代价是开发人员必须更显式。而不是尝试使用一个函数神奇地检测