请在Clojure 2024状态调查!中分享您的想法。

欢迎!请参阅关于页面以获取更多关于这是如何运作的信息。

0投票
tools.namespace
编辑

TL;DR
目前影响哪些源文件被clojure.tools.namespace.repl/refresh加载的方法只有设置刷新目录,这是一个基于允许列表的系统。当前没有方法可以阻止特定目录/文件/模式加载,但其允许其他所有内容。这当存在一些不应加载的Clojure源文件在classpath的源目录中时,会引发问题。

长篇大论

我们在使用clojure.tools.namespace/refreshclj-kondo钩子时遇到了一个有趣的问题。

一个单行副本案例

clj -Srepro -Sdeps '{:deps {org.clojure/tools.namespace {:mvn/version "1.2.0"} seancorfield/next.jdbc {:git/url "https://github.com/seancorfield/next-jdbc/" :git/sha "24bf1dbaa441d62461f980e9f880df5013f295dd"}}}' -M -e "((requiring-resolve 'clojure.tools.namespace.repl/refresh-all))"

这将失败

:error-while-loading hooks.com.github.seancorfield.next-jdbc
Could not locate hooks/com/github/seancorfield/next_jdbc__init.class, hooks/com/github/seancorfield/next_jdbc.clj or hooks/com/github/seancorfield/next_jdbc.cljc on classpath. Please check that namespaces with dashes use underscores in the Clojure file name.

关于情况,clj-kondo是一个静态分析器/linter。为了正确分析自定义宏,它允许库在特定目录下作为资源分发clj文件(描述这些宏应该如何分析)。

上述示例由于以下原因而失败

  • 通过git坐标引用库,这意味着它将作为一个目录出现在classpath上
  • 该库导出clj-kondo钩子
  • clj-kondo钩子本身是.clj文件
  • 由于这些是为clj-kondo使用而设计的,它们不应被库使用的应用程序加载
  • 结果,这些文件通常包含一个在正确代码中不出现的命名空间,例如:[链接](https://github.com/seancorfield/next-jdbc/blob/24bf1dbaa441d62461f980e9f880df5013f295dd/resources/clj-kondo.exports/com.github.seancorfield/next.jdbc/hooks/com/github/seancorfield/next_jdbc.clj#L1)
  • tools.namespace默认重新加载在classpath目录中找到的所有clj源文件(参见第一点)

目前没有方法告诉tools.namespace不要加载某些文件,所以我得想出一个相当糟糕的解决方案,尝试将刷新目录设置为由classpath目录减去有问题的目录,但这将更好,如果能够通过黑名单或谓词来设置此选项。

解决方案以防任何其他人遇到

(defn remove-clj-kondo-exports-from-tools-ns-refresh-dirs
  "A potential issue from using this is that if the directory containing the clj-kondo.exports folder
  also directly contains to-be-reloaded clojure source files, those will no longer be reloaded."
  []
  (->> (clojure.java.classpath/classpath-directories)
       (mapcat
        (fn [^File classpath-directory]
          (let [children   (.listFiles classpath-directory)
                directory? #(.isDirectory ^File %)
                clj-kondo-exports?
                           #(= "clj-kondo.exports" (.getName ^File %))
                has-clj-kondo-exports
                           (some (every-pred clj-kondo-exports? directory?) children)]
            (if has-clj-kondo-exports
              (->> children
                   (filter directory?)
                   (remove clj-kondo-exports?))
              [classpath-directory]))))
       (apply clojure.tools.namespace.repl/set-refresh-dirs)))

;; call in user.clj
(remove-clj-kondo-exports-from-tools-ns-refresh-dirs)

2个答案

0投票

当类路径上的源目录包含实际上并不打算加载的clojure源文件时,会出现这个问题。

这似乎是你的个人构建问题?源目录中的源文件难道不是天生就应该加载的吗?你为什么不能不这样做呢?

实际上,这并不是我自己的问题;我只是在尝试刷新一个项目,该项目通过git依赖项拉取了next.jdbc... 这是next.jdbc为clj-kondo提供提示的方式。

虽然我同意在源目录中的clj文件的主要情况是要被加载的,但我也可以想象其他场景,比如以这种方式分发示例代码文件等,这可能不会在应用程序本身中加载。

编辑
但是,为什么它们在:paths里?:paths的全称是“要放在类路径上的源路径”。如果它们不是源代码,这些源文件似乎不应该在那里。项目可以为某些特定于工具的用途创建别名来添加这些路径。
谢谢,这个解释对我来说很有道理。我会再次联系这个工具的作者。

尽管如此,我确实喜欢在deps.edn中设置:exclusions的可能性,以防某个库引入了我不需要/想要依赖 - 这有时有助于保护我的工作流程/产品不受库作者犯下的某些错误的影响。如果tools.namespace也有这种能力那就太好了,但我理解你如果认为当前情况不足以支持这一点也行。
关于“为什么它们在类路径上”的答案,如果有人好奇的话,就在这个Clojurians slack线程中:https://clojurians.slack.com/archives/CHY97NXE2/p1641423463308400?thread_ts=1641398954.304500&cid=CHY97NXE2

“要求”是把这些文件放在clj-kondo.exports目录下,以便clj-kondo知道要复制到本地配置目录中的内容”
Alex,你认为提供一个提供这个功能的补丁会被接受吗?

我正在考虑向这个函数的末尾添加基于谓词的路径过滤: https://github.com/clojure/tools.namespace/blob/c0b333e127e14c2ac6d5b04d14d0e714d08bfdbb/src/main/clojure/clojure/tools/namespace/dir.clj#L28

类似于set-refresh-dirs的工作方式: https://github.com/clojure/tools.namespace/blob/master/src/main/clojure/clojure/tools/namespace/repl.clj#L164

这不会专门针对我的问题,而是一种表示“在所有在refresh-dirs中找到的文件中,不考虑这个谓词适用的文件”的方法
不,这对我还说不通。
> 源文件不是在源目录中固有的吗?

> :paths 整个含义是“包含源文件并将其放在类路径上的路径”。似乎如果这些文件不是源文件,那么它们就不应该在那里。

我的理解似乎存在一些差距,回归到您之前的一些评论;

如果我是一个库的作者,并且我想将一些 .clj 文件与我库一起分发(重要的是这些文件应包含在库中,而不仅仅是辅助性的),而且这些文件并不打算被加载,那么当我的库以以下方式分发时,应该怎么最好地做:(a) 作为 jar 文件业务逻辑的用途;(b) 作为 git 依赖项?
所有 jar 文件(以及 git 依赖项中定义的 :paths 类路径)都位于类路径上,并可以由 Clojure 加载。

如果您想分发其他东西,您可以用 Maven 通过不同的分类器发布工件(不是“jar”,但机制与 Java 库发布“源”和“javadoc”工件相同)。如果您不想将它们作为 clj 源代码加载,您还可以使用不同的文件扩展名(例如 .edn 或您想要的任何其他扩展名 - 您仍然可以选择通过读取器将它们作为 Clojure 加载,但它们不会被视为 Clojure 命名空间)。
感谢您的回答,Alex。
0投票

目前有一个clj-kondo 问题,试图从该方面解决这个问题。

...