场景:给定两个文件
src/dispatch/core.clj
(ns dispatch.core (:require [dispatch.dispatch]))
src/dispatch/dispatch.clj
(ns dispatch.dispatch) (deftype T []) (def t (->T)) (println "T = (class t):" (= T (class t)))
首先编译 core,然后编译 dispatch
java -cp src:target/classes:clojure.jar -Dclojure.compile.path=target/classes clojure.main user=> (compile 'dispatch.core) T = (class t): true dispatch.core user=> (compile 'dispatch.dispatch) T = (class t): false ;; expected true dispatch.dispatch
这种场景在 {{:aot :all}} 的 leiningen 项目中更为常见。文件按字母顺序编译。在这种情况下,将首先编译 dispatch.core,然后是 dispatch.dispatch。
原因
(compile 'dispatch.core)
- 递归编译 dispatch.dispatch
- 将 .class 文件写入 compile-path(它在类路径上)
- 断言通过
(compile 'dispatch.dispatch)
- 由于之前的编译,dispatch.dispatch__init 通过 appclassloader 加载
- ->T 构造函数将使用新的字节码来实例化 T 实例 - 这使用 appclassloader,从磁盘上编译的 T 加载
- 然而,T 类文字是用 RT.classForName 解析的,它检查动态类加载器缓存,因此使用旧的运行时版本 T,而不是磁盘版本
在 1.6 中,RT.classForName() 没有检查动态类加载器缓存,因此像实例一样从磁盘加载 T。这已经被 CLJ-979 改变,以支持其他重定义和 AOT 混合使用。
方法
1) 按反向依赖顺序编译以避免重复编译。
要么交换第一个示例中编译的顺序,要么在 project.clj 中指定顺序
:aot [dispatch.dispatch dispatch.core]
这是一个短期解决方案。
2) 将 deftype 移入到与其使用不同的命名空间中,以便在第二次编译时不会重新定义它。这是另一种短期解决方案。
3) 不要将 compile-path 放在类路径上(这违反了当前期望,但可以避免加载 dispatch__init)
(set! *compile-path* "foo") (compile 'dispatch.core) (compile 'dispatch.dispatch)
目前很难通过 Leiningen 设置。
4) 使用独立的 Clojure 运行时编译每个文件 - 避免在 DCL 中对类文字使用缓存的类。
可能太令人烦恼了,目前在 Leiningen 或其他方式下难以实现。
5) 使编译非递归。这类似于 CLJ-322,这是一个另一个难题。但我们可能应该朝这个方向发展。
筛选:我不认为提出的补丁是一个好主意 - 它只是掩饰了症状而没有解决根本原因。我认为我们需要重新评估关于编译路径(#3)和递归性(CLJ-322)(#5)的编译方式,但在 1.7 后我们应该这样做 - Alex
另请参阅:CLJ-1650