diff --git a/base/loading.jl b/base/loading.jl index 355ada1fac751..5601825f94b68 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -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 @@ -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[] diff --git a/doc/make.jl b/doc/make.jl index 39f4b34b14d0d..dc931c98ca9bb 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -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", diff --git a/doc/src/devdocs/require.md b/doc/src/devdocs/require.md new file mode 100644 index 0000000000000..65b72f6cf7db5 --- /dev/null +++ b/doc/src/devdocs/require.md @@ -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 +``` + diff --git a/doc/src/index.md b/doc/src/index.md index deca430af0046..7434a4d78cfbc 100644 --- a/doc/src/index.md +++ b/doc/src/index.md @@ -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) diff --git a/test/compile.jl b/test/compile.jl index 740cf5beca532..caf55a6c1592a 100644 --- a/test/compile.jl +++ b/test/compile.jl @@ -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")