diff --git a/NEWS.md b/NEWS.md index 458bd5d60d89f..17a3de40e1d59 100644 --- a/NEWS.md +++ b/NEWS.md @@ -32,6 +32,8 @@ New language features but instead of loading it into the current session saves the result of compiling it in `~/.julia/lib/v0.4` ([#8745]). + * Put `#pragma compilable` at the top of your module file to automatically compile it when it is imported ([#12475]). + * See manual section on `Module initialization and precompilation` (under `Modules`) for details and errata. * New option `--output-incremental={yes|no}` added to invoke the equivalent of ``Base.compile`` from the command line. @@ -1557,3 +1559,4 @@ Too numerous to mention. [#12137]: https://github.com/JuliaLang/julia/issues/12137 [#12162]: https://github.com/JuliaLang/julia/issues/12162 [#12393]: https://github.com/JuliaLang/julia/issues/12393 +[#12475]: https://github.com/JuliaLang/julia/issues/12475 diff --git a/base/loading.jl b/base/loading.jl index 1eaff03d785b8..dc0f1461a8cf0 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -53,6 +53,45 @@ function find_all_in_cache_path(mod::Symbol) paths end +const r_compilable = r"^#\s*pragma\s+compile(\s+true)?\s*$" +const r_ncompilable = r"^#\s*pragma\s+compile\s+false\s*$" + +# return true if "#pragma compilable [true]" appears in the file before +# any Julia code, false for "#pragma compile false", default otherwise +function compilable(path::AbstractString, default::Bool=false) + return open(path, "r") do f + for line in eachline(f) + s = lstrip(line) + if !isempty(s) + if s[1] == '#' + ismatch(r_compilable, s) && return true + ismatch(r_ncompilable, s) && return false + else + return default + end + end + end + return default + end +end + +# compile path on node 1 if path is #pragma compilable, +# returning the cachefile path, or nothing otherwise +function autocompile_on_node1(mod::Symbol, path::AbstractString) + if myid() == 1 + if compilable(path) + if isinteractive() + info("Compiling module $mod from $path...") + end + return compile(mod) + else + return nothing + end + else + return remotecall_fetch(1, autocompile_on_node1, mod, path) + end +end + function _include_from_serialized(content::Vector{UInt8}) return ccall(:jl_restore_incremental_from_buf, Any, (Ptr{Uint8},Int), content, sizeof(content)) end @@ -166,6 +205,19 @@ function require(mod::Symbol) name = string(mod) path = find_in_node_path(name, source_dir(), 1) path === nothing && throw(ArgumentError("$name not found in path")) + + if last || nprocs() == 1 + cachefile = autocompile_on_node1(mod, path) + if cachefile !== nothing + if nothing === _require_from_serialized(1, cachefile, last) + warn("require failed to create a precompiled cache file") + else + return + end + end + end + + # could not compile, just include(path) if last && myid() == 1 && nprocs() > 1 # broadcast top-level import/using from node 1 (only) content = open(readall, path) @@ -289,6 +341,7 @@ function compile(name::ByteString) myid() == 1 || error("can only compile from node 1") path = find_in_path(name) path === nothing && throw(ArgumentError("$name not found in path")) + !compilable(path, true) && throw(ArgumentError("$name has #pragma compile false")) cachepath = LOAD_CACHE_PATH[1] if !isdir(cachepath) mkpath(cachepath) diff --git a/doc/manual/modules.rst b/doc/manual/modules.rst index 2b81c0158fe2d..d5379b0714a84 100644 --- a/doc/manual/modules.rst +++ b/doc/manual/modules.rst @@ -264,7 +264,15 @@ To create a custom system image that can be used to start julia with the -J opti recompile Julia after modifying the file ``base/userimg.jl`` to require the desired modules. To create an incremental precompiled module file, -call ``Base.compile(modulename::Symbol)``. +you can call ``Base.compile(modulename::Symbol)``. Alternatively, if you +put ``#pragma compile`` at the top of your module file (before any +non-comment code), then the module will be automatically compiled the +first time it is imported. Compiling a module also recursively compiles +any modules that are imported therein. If you know that it is *not* +safe to compile your module, you should put ``#pragma compile false`` +at the top of its file, which cause ``Base.compile`` to throw an error +(and thereby prevent the module from being imported any other compiled +module). The resulting cache files will be stored in ``Base.LOAD_CACHE_PATH[1]``. In order to make your module work with precompilation, diff --git a/test/compile.jl b/test/compile.jl index 4f70e1877d1af..007e77b651356 100644 --- a/test/compile.jl +++ b/test/compile.jl @@ -11,6 +11,8 @@ try open(file, "w") do f print(f, """ + # Auto-compile this module: + # pragma compile module $Foo_module @doc "foo function" foo(x) = x + 1 include_dependency("foo.jl") @@ -22,7 +24,9 @@ try """) end - cachefile = Base.compile(Foo_module) + # make sure the "#pragma compile" causes Foo to be compiled + cachefile = Base.autocompile_on_node1(Foo_module, file) + @test nothing !== cachefile # use _require_from_serialized to ensure that the test fails if # the module doesn't load from the image: @@ -41,7 +45,6 @@ try [:Base,:Core,:Main]) @test sort(deps[2]) == [file,joinpath(dir,"bar.jl"),joinpath(dir,"foo.jl")] end - finally splice!(Base.LOAD_CACHE_PATH, 1) splice!(LOAD_PATH, 1)