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

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

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

通常,从用户调用 Clojure 程序的 go! 函数调用 -main。例如,我在另一个名为 zic 的“uberjar”样式应用程序中使用我自己的库。函数 go! 调用 System/exit 作为其运行的一部分,向它传递来自用户提供函数结果的退出代码。这在“生产中”效果很好,但这也意味着我无法从 REPL 运行 zic.cli/-main 函数,因为每当我这样做时,它都会调用 System/exit 并且 REPL 退出。

在您询问之前,在树莓派上开发时从 REPL 运行它可以避免运行 lein uberjar/1 分 30 秒运行 clj -X:depstar uberjar :jar ... 所需的昂贵的 45 秒。

我的问题是:是否有一些 var 或值可以作为 Clojure 标准库的一部分进行检查,以告诉我的 OneCLI 代码它是从 REPL 运行还是从 JAR 运行? >

这样的变量将使我能够在 OneCLI 中检测到我们正在从 REPL 运行,从而避免调用 System/exit

解决方法

与其尝试让一个函数神奇地检测您正在运行的环境,不如让两个行为不同的函数变得非常简单。

  • 将共享行为提取到不属于 -main 的函数。称之为 run 或其他。
  • -main调用那个函数,然后调用System/exit
  • 如果您希望使用来自复制的程序,请调用 run 而不是 -main。它将正常结束,不会调用 System/exit
,

我不知道如何检测您是否在 REPL 上运行。我快速浏览了 Clojure's launching code (clojure.main),但与通过 clojure -m 运行的某些内容相比,我没有看到任何可以检测您是否处于 REPL 中的钩子。

如果您正在使用 AOT(就像您在 zic 中一样),那么您可以检查是否有任何“REPL”变量(*1*2*3,和 *e) 是绑定的。

;; returns true in a REPL and `clojure -m`,and
;; returns false in an AOT jar file run with java -jar
(bound? #'*1) 

这解决了您提出的问题,但我不喜欢这种猜测程序员意图的“神奇”机制。它可能适用于您的用例(鉴于我认为 AOT 可以节省启动时间,而且 CLI 工具可能希望快速启动),但我所从事的项目都没有使用 AOT。

clojure -m 情况下解决您的问题的另一种选择是要求开发人员明确选择退出“完成时退出”行为。一种方法是使用属性。

(defn maybe-exit [exit-code]
  (cond
    (= (System/getProperty "onecli.oncompletion") "remain") (System/exit exit-code)
    (= exit-code 0) nil
    :else (throw (ex-info "Command completed unsuccessfully" {:exit-code exit-code}))))

使用此代码,在开发环境中可以添加

:jvm-opts ["-Donecli.oncompletion=remain"]

到您的 deps.ednproject.clj 文件,但在“生产中”运行时将其保留。这样做的好处是更加明确,但代价是开发者必须更加明确。

,

每个 Java JAR 文件都必须有文件 META-INF/MANIFEST.MF 添加。如果它不存在,则不能在(普通)JAR 文件中运行。虽然您可以通过在类路径上放置一个虚假文件(例如在 ./resources 中)来欺骗这个检测器,但它是检测正常 JAR 文件的可靠方法。


问题:

Dependency JAR 文件有时很草率,会用自己的 META-INF/MANIFEST.MF 文件污染类路径,因此任何随机 META-INF/MANIFEST.MF 的存在都不足以在存在“噪音”文件的情况下确定答案.因此,您需要检查您自己的特定 META-INF/MANIFEST.MF 文件是否存在。如果您知道 ArtifactIdGroupId 的 Maven 值,这很容易做到。

在 Leiningen 项目中,project.clj 的第一行看起来像

(defproject demo-grp/demo-art "0.1.0-SNAPSHOT"

对于 demo-grp 的组 ID 和 demo-art 的工件 ID。如果您的文件如下所示:

(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

“噪音”JAR 文件示例:如果我们执行 lein clean; lein run,并在我们的主程序中添加一行

(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

我不知道这是从哪里进入 CLASSPATH。


P.S.用于 Leiningen JAR 文件

当使用 lein 构建 JAR 文件时,它总是在以下位置放置 project.clj 文件的副本:

META-INF/leiningen/demo-grp/demo-art/project.clj

所以你也可以使用这个文件的存在/不存在作为检测器。


更新

好的,看起来 MANIFEST.MF 文件高度依赖于您的构建工具。见

因此,您的选择似乎是:

  1. 对于 lein,您可以使用上述技术。
  2. 您可以使用另一个答案中 *1 的 REPL 技巧。
  3. 您始终可以让您的构建工具在清单中包含一个自定义键值对,然后检测它。

更新 #2

另一种可能更简单的答案是使用 lein-environ 插件和 environ 库(您需要两者)来检测环境(假设您使用 lein 来创建您的REPL)。您的 project.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"]
            ]

并且您需要一个 profiles.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
,

这是一个有趣的问题,因为将 JVM 关闭放入库中通常是可怕的,但另一方面,“真正的应用程序”涉及许多非常适合分享的样板……例如隐藏 jar 的飞溅 gif在正确的时间,或者(重新)打开 Windows 终端(如果应用需要 stdio)。

您的 uberjar 将包含 clojure.main,因此在您的 uberjar (java -cp my-whole-app.jar clojure.main) 中运行 REPL 很有可能(并且很有用)。因此,“检测”类路径上的线索可能无济于事。

相反,在您的 jar 清单声明为其 -main 的命名空间中的 Main-Class 中管理 JVM 关闭工作。也就是说:如果您以 java -jar my-whole-app.jar 的身份运行它,那么它应该正确关闭一切。

但我并不总是希望 -main 关闭一切,你说。那么你需要两个 -main。在不同的命名空间中创建第二个 -main。让 jar 的 Main-Class -main 除了 (1) 委托给第二个 main 和 (2) 最后关闭 JVM 之外什么都不做。当您在 REPL 中时,调用第二个 -main,它不会破坏 JVM。您可以将每个 -main 的大部分分解为一个库。如果你使用“完整框架”,你甚至可以让框架拥有超强的进程和主类。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)> insert overwrite table dwd_trade_cart_add_inc > select data.id, > data.user_id, > data.course_id, > date_format(
错误1 hive (edu)> insert into huanhuan values(1,'haoge'); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive> show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 <configuration> <property> <name>yarn.nodemanager.res