Clojure:如何判断代码是在REPL中运行还是在JAR中运行?

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

我正在用Clojure编写一个名为的CLI框架。这个框架的主要核心是一个名为
go
,它“为您”解析命令行、环境变量和配置文件,并根据这些输入中提供的内容运行几个不同的用户提供的函数之一

通常,
go-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
    文件,但在“生产”中运行时将其删除。这样做的好处是更显式,但代价是开发人员必须更显式。

    而不是尝试使用一个函数神奇地检测