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

欢迎!有关本网站如何工作的更多信息,请参阅关于页面。

0
tools.namespace
编辑了

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

长话短说

我们在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是一个静态分析器/代码检查器。为了能够正确地分析自定义宏,它允许库在特定目录下分发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源文件,而这些文件实际上根本不应该被加载时,这会引发一个问题。

这看起来似乎是您自己的构建问题?源目录中的源文件不是天生就存在用于加载的吗?您就不能不那样做吗?

哦,这其实并不是我的事情;我只是尝试在一个项目中刷新next.jdbc通过git依赖项...这是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

"这些文件需要在classpath下clj-kondo.exports目录中,以便clj-kondo知道要将什么复制到本地config目录”
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 中通过不同的分类器发布工件(不是 "jar",但与 Java 库发布 "source" 和 "javadoc" 工件相同的机制)。如果你不希望它们作为 clj 源文件加载,你还可以使用不同的文件扩展名(例如 .edn 或你想要的任何东西 - 你仍然可以选择通过读取器将它们作为 Clojure 加载,但它们不会被看作是 Clojure 命名空间)。
感谢你,Alex。
0

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

...