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

欢迎!请参阅关于页面,了解更多关于如何操作的信息。

0
tools.namespace
编辑

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

长篇大论

我们在 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是一个静态分析器/代码检查工具。为了能够正确分析自定义宏,它允许库将描述这些宏应该如何分析的clj文件(作为资源)分布在一个特定的目录下。

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

目前没有方法通知tools.namespace不加载某些文件,所以我不得不想到一种稍微有点繁琐的解决方法,试图将刷新目录设置为类路径目录减去有问题的一个,但如果可以用黑名单或谓词设置这会更好。

解决方案,以防任何人在将来遇到

(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的含义是“包含要放在类路径上的源文件的路径”。如果这些源文件不是源文件,它们似乎不应该在那里。项目可以创建别名,如果需要针对特定工具的某些目的添加这些路径的话。
by
谢谢,这对我来说很有意义。我将回过头来了解问题工具的作者。

话虽如此,我还是喜欢在deps.edn中设置 :exclusions 的可能性,以防库调入了我不想/需要的依赖项 - 这有时可以帮助保护我的工作流程/产品免受库作者所犯的错误的影响。如果 tools.namespace也有这种能力那就太好了,但我理解你可能认为当前案例不够强烈以支持这一点。
by
关于“为什么它们在路径上”的问题,如果有疑问,请查看这个Clojurians Slack线程: https://clojurians.slack.com/archives/CHY97NXE2/p1641423463308400?thread_ts=1641398954.304500&cid=CHY97NXE2

“要求是在 clj-kondo.exports 目录下将这些文件放在 classpath 上,以便 clj-kondo 能够知道需要复制到本地配置目录的内容”
by
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 定义的类路径)都在类路径上,并且其中的任何 clj 文件都可以由 Clojure 加载。

如果你想要分发其他东西,你可以在 Maven 中通过不同的 classifier 发布 artefact(不是“jar”,但是与 Java 库发布“source”和“javadoc”artefact 的机制相同)。如果你想不将它们作为 clj 源文件加载,你还可以使用不同的文件扩展名(如 .edn 或你想要的任何内容 - 你仍然可以选择通过 reader 将它们作为 Clojure 加载,但它们不会被看作 Clojure 命名空间)。
谢谢,Alex。
0

现在有一个 clj-kondo 问题 试图从这个方面解决这个问题。

...