Skip to content

Commit

Permalink
Merge pull request #21441 from JuliaLang/vc/loading_callbacks
Browse files Browse the repository at this point in the history
Add a callback to Base.require
  • Loading branch information
JeffBezanson authored May 1, 2017
2 parents 1732bb3 + 29f8959 commit 3ea63a4
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 0 deletions.
13 changes: 13 additions & 0 deletions base/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,11 @@ const DEBUG_LOADING = Ref(false)
# to synchronize multiple tasks trying to import/using something
const package_locks = Dict{Symbol,Condition}()

# to notify downstream consumers that a module was successfully loaded
# Callbacks take the form (mod::Symbol) -> nothing.
# WARNING: This is an experimental feature and might change later, without deprecation.
const package_callbacks = Any[]

# used to optionally track dependencies when requiring a module:
const _concrete_dependencies = Any[] # these dependency versions are "set in stone", and the process should try to avoid invalidating them
const _require_dependencies = Any[] # a list of (path, mtime) tuples that are the file dependencies of the module currently being precompiled
Expand Down Expand Up @@ -378,6 +383,14 @@ all platforms, including those with case-insensitive filesystems like macOS and
Windows.
"""
function require(mod::Symbol)
_require(mod::Symbol)
# After successfully loading notify downstream consumers
for callback in package_callbacks
invokelatest(callback, mod)
end
end

function _require(mod::Symbol)
# dependency-tracking is only used for one top-level include(path),
# and is not applied recursively to imported modules:
old_track_dependencies = _track_dependencies[]
Expand Down
1 change: 1 addition & 0 deletions doc/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ const PAGES = [
"devdocs/locks.md",
"devdocs/offset-arrays.md",
"devdocs/libgit2.md",
"devdocs/require.md",
],
"Developing/debugging Julia's C code" => [
"devdocs/backtraces.md",
Expand Down
40 changes: 40 additions & 0 deletions doc/src/devdocs/require.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Module loading

`Base.require`[@ref] is responsible for loading modules and it also manages the
precompilation cache. It is the implementation of the `import` statement.

## Experimental features
The features below are experimental and not part of the stable Julia API.
Before building upon them inform yourself about the current thinking and whether they might change soon.

### Module loading callbacks

It is possible to listen to the modules loaded by `Base.require`, by registering a callback.

```julia
loaded_packages = Channel{Symbol}()
callback = (mod::Symbol) -> put!(loaded_packages, mod)
push!(Base.package_callbacks, callback)
```

Please note that the symbol given to the callback is a non-unique identifier and
it is the responsibility of the callback provider to walk the module chain to
determine the fully qualified name of the loaded binding.

The callback below is an example of how to do that:

```julia
# Get the fully-qualified name of a module.
function module_fqn(name::Symbol)
fqn = Symbol[name]
mod = getfield(Main, name)
parent = Base.module_parent(mod)
while parent !== Main
push!(fqn, Base.module_name(parent))
parent = Base.module_parent(parent)
end
fqn = reverse!(fqn)
return join(fqn, '.')
end
```

1 change: 1 addition & 0 deletions doc/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
* [Proper maintenance and care of multi-threading locks](@ref)
* [Arrays with custom indices](@ref)
* [Base.LibGit2](@ref)
* [Module loading](@ref)
* Developing/debugging Julia's C code
* [Reporting and analyzing crashes (segfaults)](@ref)
* [gdb debugging tips](@ref)
Expand Down
50 changes: 50 additions & 0 deletions test/compile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,56 @@ let dir = mktempdir()
end
end

let dir = mktempdir()
try
insert!(LOAD_PATH, 1, dir)
insert!(Base.LOAD_CACHE_PATH, 1, dir)

loaded_modules = Channel{Symbol}(32)
callback = (mod::Symbol) -> put!(loaded_modules, mod)
push!(Base.package_callbacks, callback)

Test1_module = :Teste4095a81
Test2_module = :Teste4095a82
Test3_module = :Teste4095a83

write(joinpath(dir, "$(Test1_module).jl"),
"""
module $(Test1_module)
__precompile__(true)
end
""")

Base.compilecache("$(Test1_module)")
write(joinpath(dir, "$(Test2_module).jl"),
"""
module $(Test2_module)
__precompile__(true)
using $(Test1_module)
end
""")
Base.compilecache("$(Test2_module)")
@test !Base.isbindingresolved(Main, Test2_module)
Base.require(Test2_module)
@test Base.isbindingresolved(Main, Test2_module)
@test take!(loaded_modules) == Test1_module
@test take!(loaded_modules) == Test2_module
write(joinpath(dir, "$(Test3_module).jl"),
"""
module $(Test3_module)
using $(Test3_module)
end
""")
Base.require(Test3_module)
@test take!(loaded_modules) == Test3_module
finally
pop!(Base.package_callbacks)
splice!(Base.LOAD_CACHE_PATH, 1)
splice!(LOAD_PATH, 1)
rm(dir, recursive=true)
end
end

let module_name = string("a",randstring())
insert!(LOAD_PATH, 1, pwd())
file_name = string(module_name, ".jl")
Expand Down

0 comments on commit 3ea63a4

Please sign in to comment.