TL;DR
目前影响哪些源文件被clojure.tools.namespace.repl/refresh
加载的方法只有设置刷新目录,这是一个基于允许列表的系统。当前没有方法可以阻止特定目录/文件/模式加载,但其允许其他所有内容。这当存在一些不应加载的Clojure源文件在classpath的源目录中时,会引发问题。
长篇大论
我们在使用clojure.tools.namespace/refresh
与clj-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)