diff --git a/compiler+runtime/include/cpp/jank/runtime/module/loader.hpp b/compiler+runtime/include/cpp/jank/runtime/module/loader.hpp index 29851079..53d4d60a 100644 --- a/compiler+runtime/include/cpp/jank/runtime/module/loader.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/module/loader.hpp @@ -27,9 +27,19 @@ namespace jank::runtime::module latest, }; + enum class module_type : uint8_t + { + o, + cpp, + jank, + cljc + }; + struct file_entry { object_ptr to_runtime_data() const; + native_bool exists() const; + std::time_t last_modified_at() const; /* If the file is within a JAR, this will be the path to the JAR. */ option archive_path; @@ -68,6 +78,14 @@ namespace jank::runtime::module option cljc; }; + struct find_result + { + /* All the sources for a module */ + entry sources; + /* On the basis of origin, source that should be loaded. */ + option to_load; + }; + /* These separators match what the JVM does on each system. */ #ifdef _WIN32 static constexpr char module_separator{ ';' }; @@ -80,6 +98,7 @@ namespace jank::runtime::module native_bool is_loaded(native_persistent_string_view const &) const; void set_loaded(native_persistent_string_view const &); + string_result find(native_persistent_string_view const &module, origin const ori); string_result load(native_persistent_string_view const &module, origin const ori); string_result diff --git a/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp b/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp index 52dcdd9c..bf004367 100644 --- a/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -139,7 +140,7 @@ namespace jank::runtime::module } auto const &zip_entry(zf.getEntry(std::string{ entry.path })); - fn(zip_entry.readAsText()); + fn(zip_entry); } static void register_entry(native_unordered_map &entries, @@ -202,11 +203,11 @@ namespace jank::runtime::module if(registered) { - //fmt::println("register_entry {} {} {} {}", - // entry.archive_path, - // entry.path, - // module_path.string(), - // path_to_module(module_path)); + // fmt::println("register_entry {} {} {} {}", + // entry.archive_path.unwrap_or("None"), + // entry.path, + // module_path.string(), + // path_to_module(module_path)); } } @@ -317,6 +318,31 @@ namespace jank::runtime::module make_box(path)); } + native_bool file_entry::exists() const + { + auto const is_archive{ archive_path.is_some() }; + if(is_archive && !boost::filesystem::exists(native_transient_string{ archive_path.unwrap() })) + { + return false; + } + else + { + native_bool source_exists{}; + if(is_archive) + { + visit_jar_entry(*this, [&](auto const &zip_entry) { source_exists = zip_entry.isFile(); }); + } + + return source_exists || boost::filesystem::exists(native_transient_string{ path }); + } + } + + std::time_t file_entry::last_modified_at() const + { + auto const source_path{ archive_path.unwrap_or(path) }; + return boost::filesystem::last_write_time(native_transient_string{ source_path }); + } + native_bool loader::is_loaded(native_persistent_string_view const &module) const { return loaded.contains(module); @@ -327,7 +353,8 @@ namespace jank::runtime::module loaded.emplace(module); } - string_result loader::load(native_persistent_string_view const &module, origin const ori) + string_result + loader::find(native_persistent_string_view const &module, origin const ori) { static std::regex const underscore{ "_" }; native_transient_string patched_module{ module }; @@ -338,53 +365,119 @@ namespace jank::runtime::module return err(fmt::format("unable to find module: {}", module)); } - string_result res{ err(fmt::format("no sources for registered module: {}", module)) }; - - //fmt::println("loading nested module {}", module); - - /* When we're compiling, we always load from source. Otherwise, we - * load from source if we specifically chose to do so. */ - bool const compiling{ truthy(rt_ctx.compile_files_var->deref()) }; - if(compiling || ori == origin::source) + if(ori == origin::source) { if(entry->second.jank.is_some()) { - res = load_jank(entry->second.jank.unwrap()); + return find_result{ entry->second, module_type::jank }; } else if(entry->second.cljc.is_some()) { - res = load_cljc(entry->second.cljc.unwrap()); + return find_result{ entry->second, module_type::cljc }; } } else { - /* TODO: origin must be latest, so we need to find that. Even if there - * is a binary, we need to only choose it if it's as new, or newer, than - * the source. */ - if(entry->second.o.is_some()) + /* Ignoring object files from the archives here for security and portability + * reasons. + * + * Security: + * A dependency can include a binary version of a module that doesn't belong + * to it. + * + * Portability: + * Unlike class files, object files are tied to the OS, architecture, c++ stdlib etc, + * making it hard to share them. */ + if(entry->second.o.is_some() && entry->second.o.unwrap().archive_path.is_none() + && (entry->second.jank.is_some() || entry->second.cljc.is_some())) { - res = load_o(module, entry->second.o.unwrap()); + auto const o_file_path{ native_transient_string{ entry->second.o.unwrap().path } }; + + std::time_t source_modified_time{}; + module_type module_type{}; + + if(entry->second.jank.is_some() && entry->second.jank.unwrap().exists()) + { + source_modified_time = entry->second.jank.unwrap().last_modified_at(); + module_type = module_type::jank; + } + else if(entry->second.cljc.is_some() && entry->second.cljc.unwrap().exists()) + { + source_modified_time = entry->second.cljc.unwrap().last_modified_at(); + module_type = module_type::cljc; + } + else + { + return err( + fmt::format("Found a binary ({}), without a source", entry->second.o.unwrap().path)); + } + + if(boost::filesystem::last_write_time(o_file_path) >= source_modified_time) + { + return find_result{ entry->second, module_type::o }; + } + else + { + return find_result{ entry->second, module_type }; + } } else if(entry->second.cpp.is_some()) { - res = load_cpp(entry->second.cpp.unwrap()); + return find_result{ entry->second, module_type::cpp }; } else if(entry->second.jank.is_some()) { - res = load_jank(entry->second.jank.unwrap()); + return find_result{ entry->second, module_type::jank }; } else if(entry->second.cljc.is_some()) { - res = load_cljc(entry->second.cljc.unwrap()); + return find_result{ entry->second, module_type::cljc }; } } + return err(fmt::format("no sources for registered module: {}", module)); + } + + string_result loader::load(native_persistent_string_view const &module, origin const ori) + { + if(loader::is_loaded(module)) + { + return ok(); + } + + auto const &found_module{ loader::find(module, ori) }; + if(found_module.is_err()) + { + return err(found_module.expect_err()); + } + + string_result res(err(fmt::format("Couldn't load module: {}", module))); + + auto const module_type_to_load{ found_module.expect_ok().to_load.unwrap() }; + auto const &module_sources{ found_module.expect_ok().sources }; + + switch(module_type_to_load) + { + case module_type::jank: + res = load_jank(module_sources.jank.unwrap()); + break; + case module_type::o: + res = load_o(module, module_sources.o.unwrap()); + break; + case module_type::cpp: + res = load_cpp(module_sources.cpp.unwrap()); + break; + case module_type::cljc: + res = load_cljc(module_sources.cljc.unwrap()); + break; + } + if(res.is_err()) { return res; } - loaded.emplace(module); + loader::set_loaded(module); return ok(); } @@ -413,7 +506,9 @@ namespace jank::runtime::module { if(entry.archive_path.is_some()) { - visit_jar_entry(entry, [&](auto const &str) { rt_ctx.jit_prc.eval_string(str); }); + visit_jar_entry(entry, [&](auto const &zip_entry) { + rt_ctx.jit_prc.eval_string(zip_entry.readAsText()); + }); } else { @@ -433,7 +528,8 @@ namespace jank::runtime::module { if(entry.archive_path.is_some()) { - visit_jar_entry(entry, [&](auto const &str) { rt_ctx.eval_string(str); }); + visit_jar_entry(entry, + [&](auto const &zip_entry) { rt_ctx.eval_string(zip_entry.readAsText()); }); } else { diff --git a/compiler+runtime/src/cpp/main.cpp b/compiler+runtime/src/cpp/main.cpp index b28f0208..9151462f 100644 --- a/compiler+runtime/src/cpp/main.cpp +++ b/compiler+runtime/src/cpp/main.cpp @@ -101,7 +101,10 @@ namespace jank using namespace jank; using namespace jank::runtime; - //__rt_ctx->load_module("/clojure.core", module::origin::latest).expect_ok(); + if(opts.target_ns != "clojure.core") + { + __rt_ctx->load_module("/clojure.core", module::origin::latest).expect_ok(); + } __rt_ctx->compile_module(opts.target_ns).expect_ok(); }