Skip to content

Commit 79ae5c0

Browse files
committed
add "#pragma compile [true]" for opt-in to automatic compilation when a module is required (closes JuliaLang#12462)
1 parent b78659e commit 79ae5c0

File tree

4 files changed

+70
-3
lines changed

4 files changed

+70
-3
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ New language features
3232
but instead of loading it into the current session saves the result of compiling it in
3333
`~/.julia/lib/v0.4` ([#8745]).
3434

35+
* Put `#pragma compilable` at the top of your module file to automatically compile it when it is imported ([#12475]).
36+
3537
* See manual section on `Module initialization and precompilation` (under `Modules`) for details and errata.
3638

3739
* 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.
15571559
[#12137]: https://github.com/JuliaLang/julia/issues/12137
15581560
[#12162]: https://github.com/JuliaLang/julia/issues/12162
15591561
[#12393]: https://github.com/JuliaLang/julia/issues/12393
1562+
[#12475]: https://github.com/JuliaLang/julia/issues/12475

base/loading.jl

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,45 @@ function find_all_in_cache_path(mod::Symbol)
5353
paths
5454
end
5555

56+
const r_compilable = r"^#\s*pragma\s+compile(\s+true)?\s*$"
57+
const r_ncompilable = r"^#\s*pragma\s+compile\s+false\s*$"
58+
59+
# return true if "#pragma compilable [true]" appears in the file before
60+
# any Julia code, false for "#pragma compile false", default otherwise
61+
function compilable(path::AbstractString, default::Bool=false)
62+
return open(path, "r") do f
63+
for line in eachline(f)
64+
s = lstrip(line)
65+
if !isempty(s)
66+
if s[1] == '#'
67+
ismatch(r_compilable, s) && return true
68+
ismatch(r_ncompilable, s) && return false
69+
else
70+
return default
71+
end
72+
end
73+
end
74+
return default
75+
end
76+
end
77+
78+
# compile path on node 1 if path is #pragma compilable,
79+
# returning the cachefile path, or nothing otherwise
80+
function autocompile_on_node1(mod::Symbol, path::AbstractString)
81+
if myid() == 1
82+
if compilable(path)
83+
if isinteractive()
84+
info("Compiling module $mod from $path...")
85+
end
86+
return compile(mod)
87+
else
88+
return nothing
89+
end
90+
else
91+
return remotecall_fetch(1, autocompile_on_node1, mod, path)
92+
end
93+
end
94+
5695
function _include_from_serialized(content::Vector{UInt8})
5796
return ccall(:jl_restore_incremental_from_buf, Any, (Ptr{Uint8},Int), content, sizeof(content))
5897
end
@@ -166,6 +205,19 @@ function require(mod::Symbol)
166205
name = string(mod)
167206
path = find_in_node_path(name, source_dir(), 1)
168207
path === nothing && throw(ArgumentError("$name not found in path"))
208+
209+
if last || nprocs() == 1
210+
cachefile = autocompile_on_node1(mod, path)
211+
if cachefile !== nothing
212+
if nothing === _require_from_serialized(1, cachefile, last)
213+
warn("require failed to create a precompiled cache file")
214+
else
215+
return
216+
end
217+
end
218+
end
219+
220+
# could not compile, just include(path)
169221
if last && myid() == 1 && nprocs() > 1
170222
# broadcast top-level import/using from node 1 (only)
171223
content = open(readall, path)
@@ -289,6 +341,7 @@ function compile(name::ByteString)
289341
myid() == 1 || error("can only compile from node 1")
290342
path = find_in_path(name)
291343
path === nothing && throw(ArgumentError("$name not found in path"))
344+
!compilable(path, true) && throw(ArgumentError("$name has #pragma compile false"))
292345
cachepath = LOAD_CACHE_PATH[1]
293346
if !isdir(cachepath)
294347
mkpath(cachepath)

doc/manual/modules.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,15 @@ To create a custom system image that can be used to start julia with the -J opti
264264
recompile Julia after modifying the file ``base/userimg.jl`` to require the desired modules.
265265

266266
To create an incremental precompiled module file,
267-
call ``Base.compile(modulename::Symbol)``.
267+
you can call ``Base.compile(modulename::Symbol)``. Alternatively, if you
268+
put ``#pragma compile`` at the top of your module file (before any
269+
non-comment code), then the module will be automatically compiled the
270+
first time it is imported. Compiling a module also recursively compiles
271+
any modules that are imported therein. If you know that it is *not*
272+
safe to compile your module, you should put ``#pragma compile false``
273+
at the top of its file, which cause ``Base.compile`` to throw an error
274+
(and thereby prevent the module from being imported any other compiled
275+
module).
268276
The resulting cache files will be stored in ``Base.LOAD_CACHE_PATH[1]``.
269277

270278
In order to make your module work with precompilation,

test/compile.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ try
1111

1212
open(file, "w") do f
1313
print(f, """
14+
# Auto-compile this module:
15+
# pragma compile
1416
module $Foo_module
1517
@doc "foo function" foo(x) = x + 1
1618
include_dependency("foo.jl")
@@ -22,7 +24,9 @@ try
2224
""")
2325
end
2426

25-
cachefile = Base.compile(Foo_module)
27+
# make sure the "#pragma compile" causes Foo to be compiled
28+
cachefile = Base.autocompile_on_node1(Foo_module, file)
29+
@test nothing !== cachefile
2630

2731
# use _require_from_serialized to ensure that the test fails if
2832
# the module doesn't load from the image:
@@ -41,7 +45,6 @@ try
4145
[:Base,:Core,:Main])
4246
@test sort(deps[2]) == [file,joinpath(dir,"bar.jl"),joinpath(dir,"foo.jl")]
4347
end
44-
4548
finally
4649
splice!(Base.LOAD_CACHE_PATH, 1)
4750
splice!(LOAD_PATH, 1)

0 commit comments

Comments
 (0)