diff --git a/compiler+runtime/include/cpp/jank/runtime/context.hpp b/compiler+runtime/include/cpp/jank/runtime/context.hpp index 0f23616f1..eee94c75b 100644 --- a/compiler+runtime/include/cpp/jank/runtime/context.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/context.hpp @@ -144,6 +144,11 @@ namespace jank::runtime /* TODO: This needs to be a dynamic var. */ native_unordered_map> module_dependencies; + + /* TODO: Remove this in favor of calling module's `jank_load` functions + * on demand. At the moment it is being used to load all the compiled modules + * in ahead of time compiled binaries at startup (which is not an ideal way to + * achieve that). */ folly::Synchronized> loaded_modules_in_order; jtl::immutable_string binary_cache_dir; module::loader module_loader; diff --git a/compiler+runtime/src/cpp/jank/environment/check_health.cpp b/compiler+runtime/src/cpp/jank/environment/check_health.cpp index 8bd1f8cfb..2bc521998 100644 --- a/compiler+runtime/src/cpp/jank/environment/check_health.cpp +++ b/compiler+runtime/src/cpp/jank/environment/check_health.cpp @@ -390,12 +390,13 @@ namespace jank::environment [=] { util::cli::opts = saved_opts; } }; + runtime::__rt_ctx->module_loader.set_is_loaded("/clojure.core"); #ifdef JANK_PHASE_2 jank_load_clojure_core(); - runtime::__rt_ctx->module_loader.set_is_loaded("/clojure.core"); #else runtime::__rt_ctx->load_module("/clojure.core", runtime::module::origin::latest).expect_ok(); #endif + runtime::__rt_ctx->module_loader.add_path(path_tmp); runtime::__rt_ctx->compile_module(util::cli::opts.target_module).expect_ok(); diff --git a/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp b/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp index 2687ff978..598180a36 100644 --- a/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp @@ -791,10 +791,6 @@ namespace jank::runtime::module return find_result{ entry, module_type }; } } - else if(entry.cpp.is_some()) - { - return find_result{ entry, module_type::cpp }; - } else if(entry.jank.is_some()) { return find_result{ entry, module_type::jank }; @@ -803,6 +799,10 @@ namespace jank::runtime::module { return find_result{ entry, module_type::cljc }; } + else if(entry.cpp.is_some()) + { + return find_result{ entry, module_type::cpp }; + } } return error::internal_runtime_failure( @@ -912,11 +912,6 @@ namespace jank::runtime::module jtl::result loader::load(jtl::immutable_string const &module, origin const ori) { - if(ori != origin::source && loader::is_loaded(module)) - { - return ok(); - } - auto const &found_module{ loader::find(module, ori) }; if(found_module.is_err()) { @@ -954,11 +949,11 @@ namespace jank::runtime::module return res; } - loader::set_is_loaded(module); { auto const locked_ordered_modules{ __rt_ctx->loaded_modules_in_order.wlock() }; locked_ordered_modules->push_back(module); } + return ok(); } diff --git a/compiler+runtime/test/bash/module/compiled-modules/pass-test b/compiler+runtime/test/bash/module/compiled-modules/pass-test new file mode 100755 index 000000000..5b7ad9e09 --- /dev/null +++ b/compiler+runtime/test/bash/module/compiled-modules/pass-test @@ -0,0 +1,91 @@ +#!/usr/bin/env bb + +^{:clj-kondo/ignore [:namespace-name-mismatch]} +(ns jank.test.module.compiled-module + (:require [babashka.fs :as fs] + [babashka.process :as proc] + [clojure.string :as string] + [clojure.test :as t :refer [deftest is testing use-fixtures]])) + +(def this-nsym (ns-name *ns*)) + +(def module-a "jank-test.a") +(def module-a-path "./src/jank_test/a.cljc") +(defn compiled-a-path [] + (first (fs/glob "./target" "**/a.o"))) + +(defn jank [command module-name & {:keys [extra-module-path]}] + (proc/sh ["jank" "--module-path" + (str "src" (when-not (empty? extra-module-path) + (str ":" extra-module-path))) + command module-name])) + +(defn create-jar-with-a [] + (let [jar-name "test.jar" + [compilation-dir rest-of-module-path] (->> (fs/glob "target" "**/a.o") + (first) + (fs/components) + (split-at 2) + (map (partial apply fs/path)) + (map str))] + (proc/sh {:dir compilation-dir} "jar" "cf" jar-name rest-of-module-path) + (fs/path compilation-dir jar-name))) + +(defmacro with-hidden-file + "Temporarily renames `path` to `path.bak`, evaluates body, + and restores the file in a `finally` block." + [path & body] + `(let [path# ~path + bak# (str path# ".bak")] + (fs/move path# bak#) + (try + ~@body + (finally + (fs/move bak# path#))))) + +(use-fixtures + :each + (fn [f] + (jank "compile-module" module-a) + (f) + (fs/delete-tree "./target"))) + +(deftest compiled-module-gets-loaded + (testing "loads a compiled module instead of the source" + (is (false? (-> (jank "run-main" module-a) + :out + (string/includes? ":only-during-read"))))) + + (testing "Source fresher than the compiled module, loads from source" + (proc/sh ["touch" module-a-path]) + (-> (jank "run-module" module-a) + :out + println) + (is (true? (-> (jank "run-main" module-a) + :out + (string/includes? ":only-during-read")))))) + +(deftest compiled-module-loadability + (testing "Doesn't load a module from archive" + (fs/with-temp-dir [tmp-dir] + (let [jar (-> (create-jar-with-a) + (fs/move tmp-dir))] + (with-hidden-file "target" + (is (true? (-> (jank "run-main" module-a :extra-module-path jar) + :out + (string/includes? ":only-during-read")))))))) + + (testing "Doesn't load module if source not found" + (with-hidden-file module-a-path + (is (true? (-> (jank "run-main" module-a) + :out + (string/includes? "No sources for registered module"))))))) + +(defn -main [] + (System/exit + (if (t/successful? (t/run-tests this-nsym)) + 0 + 1))) + +(when (= *file* (System/getProperty "babashka.file")) + (apply -main *command-line-args*)) diff --git a/compiler+runtime/test/bash/module/compiled-modules/src/jank_test/a.cljc b/compiler+runtime/test/bash/module/compiled-modules/src/jank_test/a.cljc new file mode 100644 index 000000000..4ea03148e --- /dev/null +++ b/compiler+runtime/test/bash/module/compiled-modules/src/jank_test/a.cljc @@ -0,0 +1,9 @@ +(ns jank-test.a) + +(defmacro with-prelog [& body] + (println :only-during-read) + `(do ~@body)) + +(with-prelog + (defn -main [] + (println :success))) diff --git a/compiler+runtime/test/bash/module/cpp/pass-test b/compiler+runtime/test/bash/module/cpp/pass-test new file mode 100755 index 000000000..fe92f4f76 --- /dev/null +++ b/compiler+runtime/test/bash/module/cpp/pass-test @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +jank --module-path src run-main jank-test.test-runner | grep '^:success$' diff --git a/compiler+runtime/test/bash/module/cpp/src/jank_test/a.cpp b/compiler+runtime/test/bash/module/cpp/src/jank_test/a.cpp new file mode 100644 index 000000000..850df22aa --- /dev/null +++ b/compiler+runtime/test/bash/module/cpp/src/jank_test/a.cpp @@ -0,0 +1,13 @@ +using jank_object_ref = void *; + +extern "C" void jank_module_set_loaded(char const *module); +extern "C" jank_object_ref jank_eval(jank_object_ref s); +extern "C" jank_object_ref jank_string_create(char const *s); + +extern "C" void jank_load_jank_test_a() +{ + jank_eval(jank_string_create("(ns jank-test.a)")); + jank_module_set_loaded("jank-test.a"); + jank_eval(jank_string_create("(defn -main [] \"a.cpp\")")); +} + diff --git a/compiler+runtime/test/bash/module/cpp/src/jank_test/a.jank b/compiler+runtime/test/bash/module/cpp/src/jank_test/a.jank new file mode 100644 index 000000000..315263851 --- /dev/null +++ b/compiler+runtime/test/bash/module/cpp/src/jank_test/a.jank @@ -0,0 +1,2 @@ +(ns jank-test.a) +(defn -main [] "a.jank") diff --git a/compiler+runtime/test/bash/module/cpp/src/jank_test/b.cljc b/compiler+runtime/test/bash/module/cpp/src/jank_test/b.cljc new file mode 100644 index 000000000..9d9c48750 --- /dev/null +++ b/compiler+runtime/test/bash/module/cpp/src/jank_test/b.cljc @@ -0,0 +1,2 @@ +(ns jank-test.b) +(defn -main [] "b.cljc") diff --git a/compiler+runtime/test/bash/module/cpp/src/jank_test/b.cpp b/compiler+runtime/test/bash/module/cpp/src/jank_test/b.cpp new file mode 100644 index 000000000..d32850a95 --- /dev/null +++ b/compiler+runtime/test/bash/module/cpp/src/jank_test/b.cpp @@ -0,0 +1,12 @@ +using jank_object_ref = void *; + +extern "C" void jank_module_set_loaded(char const *module); +extern "C" jank_object_ref jank_eval(jank_object_ref s); +extern "C" jank_object_ref jank_string_create(char const *s); + +extern "C" void jank_load_jank_test_b() +{ + jank_eval(jank_string_create("(ns jank-test.b)")); + jank_module_set_loaded("jank-test.b"); + jank_eval(jank_string_create("(defn -main [] \"b.cpp\")")); +} diff --git a/compiler+runtime/test/bash/module/cpp/src/jank_test/cljc.cljc b/compiler+runtime/test/bash/module/cpp/src/jank_test/cljc.cljc new file mode 100644 index 000000000..e41fbcdd7 --- /dev/null +++ b/compiler+runtime/test/bash/module/cpp/src/jank_test/cljc.cljc @@ -0,0 +1,2 @@ +(ns jank-test.cljc) +(defn -main [] #?(:clj :clj :jank :jank :default :default)) diff --git a/compiler+runtime/test/bash/module/cpp/src/jank_test/cpp.cpp b/compiler+runtime/test/bash/module/cpp/src/jank_test/cpp.cpp new file mode 100644 index 000000000..6b7c15ab6 --- /dev/null +++ b/compiler+runtime/test/bash/module/cpp/src/jank_test/cpp.cpp @@ -0,0 +1,12 @@ +using jank_object_ref = void *; + +extern "C" void jank_module_set_loaded(char const *module); +extern "C" jank_object_ref jank_eval(jank_object_ref s); +extern "C" jank_object_ref jank_string_create(char const *s); + +extern "C" void jank_load_jank_test_cpp() +{ + jank_eval(jank_string_create("(ns jank-test.cpp)")); + jank_module_set_loaded("jank-test.cpp"); + jank_eval(jank_string_create("(defn -main [] :cpp)")); +} diff --git a/compiler+runtime/test/bash/module/cpp/src/jank_test/test_runner.cljc b/compiler+runtime/test/bash/module/cpp/src/jank_test/test_runner.cljc new file mode 100644 index 000000000..8ecfea90a --- /dev/null +++ b/compiler+runtime/test/bash/module/cpp/src/jank_test/test_runner.cljc @@ -0,0 +1,11 @@ +(ns jank-test.test-runner + (:require jank-test.a + jank-test.b + jank-test.cpp)) + +(defn -main [] + (assert (= "a.jank" (jank-test.a/-main)) ".jank overrides .cpp") + (assert (= "b.cljc" (jank-test.b/-main)) ".cljc overrides .cpp") + (assert (= :cpp (jank-test.cpp/-main)) "cpp module should be loaded correctly") + ;; exit code not reliable enough https://github.com/jank-lang/jank/issues/181 + (println :success)) diff --git a/compiler+runtime/test/bash/module/reload/pass-test b/compiler+runtime/test/bash/module/reload/pass-test new file mode 100755 index 000000000..e60b4bdf9 --- /dev/null +++ b/compiler+runtime/test/bash/module/reload/pass-test @@ -0,0 +1,170 @@ +#!/usr/bin/env bb + +^{:clj-kondo/ignore [:namespace-name-mismatch]} +(ns jank.test.module.reload + (:require [babashka.fs :as fs] + [babashka.process :as ps] + [clojure.string :as string] + [clojure.java.io :as io] + [clojure.test :as t :refer [deftest is testing]])) + +(def this-nsym (ns-name *ns*)) + +(defn send-command [writer command] + (doto writer + (.write command) + (.flush))) + +(defn read-available [reader] + (let [sb (StringBuilder.)] + (while (.ready reader) + (.append sb (char (.read reader)))) + (str sb))) + +(defmacro with-backup [path & body] + `(fs/with-temp-dir [dir#] + (let [tmp-path# (fs/path dir# ~path)] + (fs/copy-tree ~path tmp-path#) + (try + ~@body + (finally + (fs/delete-tree ~path) + (fs/copy-tree tmp-path# ~path)))))) + +(deftest a-single-module-reload + (with-backup "src/jank_test/reload" + (let [proc (ps/process ["jank" "--module-path" "src" "repl"]) + dependent-module-path "src/jank_test/reload/dependent.cljc" + dependency-module-path "src/jank_test/reload/dependency.cljc" + original-dependent-content (slurp dependent-module-path)] + (with-open [repl-writer (io/writer (:in proc)) + repl-reader (io/reader (:out proc))] + (doto repl-writer + (send-command "(require '[jank-test.reload.dependent :as d])\n") + (send-command "(d/first-fn)\n")) + + ;; Wait for repl to start + (Thread/sleep 2000) + (is (string/includes? + (read-available repl-reader) + "Hello")) + + (send-command repl-writer "(d/hi)\n") + + (Thread/sleep 500) + (is (string/includes? + (read-available repl-reader) + "Unable to resolve symbol 'jank-test.reload.dependent/hi'")) + + (testing "new var should be added to the module" + ;; Add a new var + (doto dependent-module-path + (spit "\n" :append true) + (spit (pr-str '(defn hi [] "Hi!")) :append true)) + + (doto repl-writer + (send-command "(require '[jank-test.reload.dependent :as d] :reload)\n") + (send-command "(d/hi)\n")) + + (Thread/sleep 500) + (is (string/includes? + (read-available repl-reader) + "Hi!"))) + + (testing "var removed from the source should still available" + (spit dependent-module-path original-dependent-content) + (doto repl-writer + (send-command "(require '[jank-test.reload.dependent :as d] :reload)\n") + (send-command "(d/hi)\n")) + + (Thread/sleep 500) + (is (string/includes? + (read-available repl-reader) + "Hi!"))) + (testing "dependency module should not reload" + (doto dependency-module-path + (spit "\n" :append true) + (spit (pr-str '(defn hi [] "Hi in dependency!")) :append true)) + (doto repl-writer + (send-command "(require '[jank-test.reload.dependency :as dep])\n") + (send-command "(require '[jank-test.reload.dependent :as d] :reload)\n") + (send-command "(dep/hi)\n")) + + (Thread/sleep 500) + + (is (string/includes? + (read-available repl-reader) + "Unable to resolve symbol 'jank-test.reload.dependency/hi'")))) + (ps/destroy proc)))) + +(deftest reload-all + (with-backup "src/jank_test/reload_all" + (let [proc (ps/process ["jank" "--module-path" "src" "repl"]) + dependent-module-path "src/jank_test/reload_all/dependent.cljc" + dependency-module-path "src/jank_test/reload_all/dependency.cljc"] + (with-open [repl-writer (io/writer (:in proc)) + repl-reader (io/reader (:out proc))] + (doto repl-writer + (send-command "(require '[jank-test.reload-all.dependent :as d])\n") + (send-command "(d/first-fn)\n")) + + ;; Wait for repl to start + (Thread/sleep 2000) + (is (string/includes? + (read-available repl-reader) + "Hello")) + + (send-command repl-writer "(d/hi)\n") + + (Thread/sleep 500) + (is (string/includes? + (read-available repl-reader) + "Unable to resolve symbol 'jank-test.reload-all.dependent/hi'")) + + (testing "module is reloaded" + ;; Add a new var + (doto dependent-module-path + (spit "\n" :append true) + (spit (pr-str '(defn hi [] "Hi!")) :append true)) + + (doto repl-writer + (send-command "(require '[jank-test.reload-all.dependent :as d] :reload-all)\n") + (send-command "(d/hi)\n")) + + (Thread/sleep 500) + (is (string/includes? + (read-available repl-reader) + "Hi!"))) + + (testing "module's dependency is also reloaded" + (doto dependency-module-path + (spit "\n" :append true) + (spit (pr-str '(defn hi [] "Hi in dependency!")) :append true)) + + (doto repl-writer + (send-command "(require '[jank-test.reload-all.dependency :as dep])\n") + (send-command "(dep/hi)\n")) + + (Thread/sleep 500) + (is (string/includes? + (read-available repl-reader) + "Unable to resolve symbol 'jank-test.reload-all.dependency/hi'")) + + (doto repl-writer + (send-command "(require '[jank-test.reload-all.dependent :as d] :reload-all)\n") + (send-command "(dep/hi)\n")) + + (Thread/sleep 500) + (is (string/includes? + (read-available repl-reader) + "Hi in dependency!")))) + (ps/destroy proc)))) + +(defn -main [] + (System/exit + (if (t/successful? (t/run-tests this-nsym)) + 0 + 1))) + +(when (= *file* (System/getProperty "babashka.file")) + (apply -main *command-line-args*)) diff --git a/compiler+runtime/test/bash/module/reload/src/jank_test/reload/dependency.cljc b/compiler+runtime/test/bash/module/reload/src/jank_test/reload/dependency.cljc new file mode 100644 index 000000000..b96cc15a4 --- /dev/null +++ b/compiler+runtime/test/bash/module/reload/src/jank_test/reload/dependency.cljc @@ -0,0 +1,4 @@ +(ns jank-test.reload.dependency) + +(defn say-hello! [] + (println "Hello")) diff --git a/compiler+runtime/test/bash/module/reload/src/jank_test/reload/dependent.cljc b/compiler+runtime/test/bash/module/reload/src/jank_test/reload/dependent.cljc new file mode 100644 index 000000000..b123de1f9 --- /dev/null +++ b/compiler+runtime/test/bash/module/reload/src/jank_test/reload/dependent.cljc @@ -0,0 +1,5 @@ +(ns jank-test.reload.dependent + (:require [jank-test.reload.dependency :as dep])) + +(defn first-fn [] + (dep/say-hello!)) diff --git a/compiler+runtime/test/bash/module/reload/src/jank_test/reload_all/dependency.cljc b/compiler+runtime/test/bash/module/reload/src/jank_test/reload_all/dependency.cljc new file mode 100644 index 000000000..3e256c97b --- /dev/null +++ b/compiler+runtime/test/bash/module/reload/src/jank_test/reload_all/dependency.cljc @@ -0,0 +1,4 @@ +(ns jank-test.reload-all.dependency) + +(defn say-hello! [] + (println "Hello")) diff --git a/compiler+runtime/test/bash/module/reload/src/jank_test/reload_all/dependent.cljc b/compiler+runtime/test/bash/module/reload/src/jank_test/reload_all/dependent.cljc new file mode 100644 index 000000000..315380ba3 --- /dev/null +++ b/compiler+runtime/test/bash/module/reload/src/jank_test/reload_all/dependent.cljc @@ -0,0 +1,5 @@ +(ns jank-test.reload-all.dependent + (:require [jank-test.reload-all.dependency :as dep])) + +(defn first-fn [] + (dep/say-hello!))