Skip to content

Commit f01c6e0

Browse files
committed
Allow ext → ext dependency if triggers are a strict superset
This allows for one extension to depend on another if its triggers are a strict superset of the other's. For example, in a Project.toml: ```toml [extensions] PlottingExt = "Plots" StatisticsPlottingExt = ["Plots", "Statistics"] ``` Here `StatisticsPlottingExt` is allowed to depend on `PlottingExt` This provides a way to declare `ext → ext` dependencies while still avoiding any extension cycles. The same trick can also be used to make an extension in one package depend on an extension provided in another. Also requires something like JuliaLang#49891, so that we guarantee these load in the right order.
1 parent 05182dc commit f01c6e0

File tree

3 files changed

+40
-19
lines changed

3 files changed

+40
-19
lines changed

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ Language changes
3737
omit the default user depot ([#51448]).
3838
* Precompilation cache files are now relocatable and their validity is now verified through
3939
a content hash of their source files instead of their `mtime` ([#49866]).
40+
* Extensions may now depend on other extensions, if their triggers include all of the triggers
41+
of any extension they wish to depend upon (+ at least one other trigger). In contrast to prior
42+
versions, ext-to-ext dependencies that don't meet this requirement are now blocked during pre-
43+
compilation to prevent extension cycles [#55557].
4044

4145
Compiler/Runtime improvements
4246
-----------------------------

base/loading.jl

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,9 +1387,7 @@ function run_module_init(mod::Module, i::Int=1)
13871387
end
13881388

13891389
function run_package_callbacks(modkey::PkgId)
1390-
if !precompiling_extension
1391-
run_extension_callbacks(modkey)
1392-
end
1390+
run_extension_callbacks(modkey)
13931391
assert_havelock(require_lock)
13941392
unlock(require_lock)
13951393
try
@@ -1527,6 +1525,7 @@ function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, Any}
15271525
end
15281526

15291527
loading_extension::Bool = false
1528+
loadable_extensions::Union{Nothing,Vector{PkgId}} = nothing
15301529
precompiling_extension::Bool = false
15311530
function run_extension_callbacks(extid::ExtensionId)
15321531
assert_havelock(require_lock)
@@ -1565,7 +1564,7 @@ function run_extension_callbacks(pkgid::PkgId)
15651564
else
15661565
succeeded = true
15671566
end
1568-
if extid.ntriggers == 0
1567+
if extid.ntriggers == 0 && (loadable_extensions === nothing || extid.id in loadable_extensions)
15691568
# actually load extid, now that all dependencies are met,
15701569
# and record the result
15711570
succeeded = succeeded && run_extension_callbacks(extid)
@@ -2870,7 +2869,7 @@ end
28702869
const PRECOMPILE_TRACE_COMPILE = Ref{String}()
28712870
function create_expr_cache(pkg::PkgId, input::String, output::String, output_o::Union{Nothing, String},
28722871
concrete_deps::typeof(_concrete_dependencies), flags::Cmd=``, cacheflags::CacheFlags=CacheFlags(),
2873-
internal_stderr::IO = stderr, internal_stdout::IO = stdout, isext::Bool=false)
2872+
internal_stderr::IO = stderr, internal_stdout::IO = stdout, loadable_exts::Union{Vector{PkgId},Nothing}=nothing)
28742873
@nospecialize internal_stderr internal_stdout
28752874
rm(output, force=true) # Remove file if it exists
28762875
output_o === nothing || rm(output_o, force=true)
@@ -2944,7 +2943,8 @@ function create_expr_cache(pkg::PkgId, input::String, output::String, output_o::
29442943
write(io.in, """
29452944
empty!(Base.EXT_DORMITORY) # If we have a custom sysimage with `EXT_DORMITORY` prepopulated
29462945
Base.track_nested_precomp($precomp_stack)
2947-
Base.precompiling_extension = $(loading_extension | isext)
2946+
Base.loadable_extensions = $(loadable_exts)
2947+
Base.precompiling_extension = $(loading_extension)
29482948
Base.include_package_for_output($(pkg_str(pkg)), $(repr(abspath(input))), $(repr(depot_path)), $(repr(dl_load_path)),
29492949
$(repr(load_path)), $deps, $(repr(source_path(nothing))))
29502950
""")
@@ -3001,18 +3001,18 @@ This can be used to reduce package load times. Cache files are stored in
30013001
`DEPOT_PATH[1]/compiled`. See [Module initialization and precompilation](@ref)
30023002
for important notes.
30033003
"""
3004-
function compilecache(pkg::PkgId, internal_stderr::IO = stderr, internal_stdout::IO = stdout; flags::Cmd=``, reasons::Union{Dict{String,Int},Nothing}=Dict{String,Int}(), isext::Bool=false)
3004+
function compilecache(pkg::PkgId, internal_stderr::IO = stderr, internal_stdout::IO = stdout; flags::Cmd=``, reasons::Union{Dict{String,Int},Nothing}=Dict{String,Int}(), loadable_exts::Union{Vector{PkgId},Nothing}=nothing)
30053005
@nospecialize internal_stderr internal_stdout
30063006
path = locate_package(pkg)
30073007
path === nothing && throw(ArgumentError("$(repr("text/plain", pkg)) not found during precompilation"))
3008-
return compilecache(pkg, path, internal_stderr, internal_stdout; flags, reasons, isext)
3008+
return compilecache(pkg, path, internal_stderr, internal_stdout; flags, reasons, loadable_exts)
30093009
end
30103010

30113011
const MAX_NUM_PRECOMPILE_FILES = Ref(10)
30123012

30133013
function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, internal_stdout::IO = stdout,
30143014
keep_loaded_modules::Bool = true; flags::Cmd=``, cacheflags::CacheFlags=CacheFlags(),
3015-
reasons::Union{Dict{String,Int},Nothing}=Dict{String,Int}(), isext::Bool=false)
3015+
reasons::Union{Dict{String,Int},Nothing}=Dict{String,Int}(), loadable_exts::Union{Vector{PkgId},Nothing}=nothing)
30163016

30173017
@nospecialize internal_stderr internal_stdout
30183018
# decide where to put the resulting cache file
@@ -3052,7 +3052,7 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in
30523052
close(tmpio_o)
30533053
close(tmpio_so)
30543054
end
3055-
p = create_expr_cache(pkg, path, tmppath, tmppath_o, concrete_deps, flags, cacheflags, internal_stderr, internal_stdout, isext)
3055+
p = create_expr_cache(pkg, path, tmppath, tmppath_o, concrete_deps, flags, cacheflags, internal_stderr, internal_stdout, loadable_exts)
30563056

30573057
if success(p)
30583058
if cache_objects

base/precompilation.jl

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ function _precompilepkgs(pkgs::Vector{String},
419419
return name
420420
end
421421

422+
triggers = Dict{Base.PkgId,Vector{Base.PkgId}}()
422423
for (dep, deps) in env.deps
423424
pkg = Base.PkgId(dep, env.names[dep])
424425
Base.in_sysimage(pkg) && continue
@@ -427,25 +428,22 @@ function _precompilepkgs(pkgs::Vector{String},
427428
# add any extensions
428429
pkg_exts = Dict{Base.PkgId, Vector{Base.PkgId}}()
429430
for (ext_name, extdep_uuids) in env.extensions[dep]
430-
ext_deps = Base.PkgId[]
431-
push!(ext_deps, pkg) # depends on parent package
431+
ext_uuid = Base.uuid5(pkg.uuid, ext_name)
432+
ext = Base.PkgId(ext_uuid, ext_name)
433+
triggers[ext] = Base.PkgId[pkg] # depends on parent package
432434
all_extdeps_available = true
433435
for extdep_uuid in extdep_uuids
434436
extdep_name = env.names[extdep_uuid]
435437
if extdep_uuid in keys(env.deps)
436-
push!(ext_deps, Base.PkgId(extdep_uuid, extdep_name))
438+
push!(triggers[ext], Base.PkgId(extdep_uuid, extdep_name))
437439
else
438440
all_extdeps_available = false
439441
break
440442
end
441443
end
442444
all_extdeps_available || continue
443-
ext_uuid = Base.uuid5(pkg.uuid, ext_name)
444-
ext = Base.PkgId(ext_uuid, ext_name)
445-
filter!(!Base.in_sysimage, ext_deps)
446-
depsmap[ext] = ext_deps
447445
exts[ext] = pkg.name
448-
pkg_exts[ext] = ext_deps
446+
pkg_exts[ext] = depsmap[ext] = filter(!Base.in_sysimage, triggers[ext])
449447
end
450448
if !isempty(pkg_exts)
451449
pkg_exts_map[pkg] = collect(keys(pkg_exts))
@@ -461,6 +459,16 @@ function _precompilepkgs(pkgs::Vector{String},
461459
append!(direct_deps, keys(filter(d->last(d) in keys(env.project_deps), exts)))
462460

463461
@debug "precompile: deps collected"
462+
463+
# An extension effectively depends on another extension if it has a strict superset of its triggers
464+
for ext_a in keys(exts)
465+
for ext_b in keys(exts)
466+
if triggers[ext_a] triggers[ext_b]
467+
push!(depsmap[ext_a], ext_b)
468+
end
469+
end
470+
end
471+
464472
# this loop must be run after the full depsmap has been populated
465473
for (pkg, pkg_exts) in pkg_exts_map
466474
# find any packages that depend on the extension(s)'s deps and replace those deps in their deps list with the extension(s),
@@ -817,7 +825,16 @@ function _precompilepkgs(pkgs::Vector{String},
817825
t = @elapsed ret = precompile_pkgs_maybe_cachefile_lock(io, print_lock, fancyprint, pkg_config, pkgspidlocked, hascolor) do
818826
Base.with_logger(Base.NullLogger()) do
819827
# The false here means we ignore loaded modules, so precompile for a fresh session
820-
Base.compilecache(pkg, sourcepath, std_pipe, std_pipe, false; flags, cacheflags, isext = haskey(exts, pkg))
828+
keep_loaded_modules = false
829+
if haskey(exts, pkg)
830+
# any extension in our direct dependencies is one we have a right to load
831+
loadable_exts = filter((dep)->haskey(exts, dep), depsmap[pkg])
832+
Base.compilecache(pkg, sourcepath, std_pipe, std_pipe, keep_loaded_modules;
833+
flags, cacheflags, loadable_exts)
834+
else
835+
Base.compilecache(pkg, sourcepath, std_pipe, std_pipe, keep_loaded_modules;
836+
flags, cacheflags)
837+
end
821838
end
822839
end
823840
if ret isa Base.PrecompilableError

0 commit comments

Comments
 (0)