Skip to content

Commit a30c46c

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 c558d26 commit a30c46c

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
@@ -1528,6 +1526,7 @@ function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, Any}
15281526
end
15291527

15301528
loading_extension::Bool = false
1529+
loadable_extensions::Union{Nothing,Vector{PkgId}} = nothing
15311530
precompiling_extension::Bool = false
15321531
function run_extension_callbacks(extid::ExtensionId)
15331532
assert_havelock(require_lock)
@@ -1558,7 +1557,7 @@ function run_extension_callbacks(pkgid::PkgId)
15581557
for extid in extids
15591558
@assert extid.ntriggers > 0
15601559
extid.ntriggers -= 1
1561-
if extid.ntriggers == 0
1560+
if extid.ntriggers == 0 && (loadable_extensions === nothing || extid.id in loadable_extensions)
15621561
push!(extids_to_load, extid)
15631562
end
15641563
end
@@ -2868,7 +2867,7 @@ end
28682867
const PRECOMPILE_TRACE_COMPILE = Ref{String}()
28692868
function create_expr_cache(pkg::PkgId, input::String, output::String, output_o::Union{Nothing, String},
28702869
concrete_deps::typeof(_concrete_dependencies), flags::Cmd=``, cacheflags::CacheFlags=CacheFlags(),
2871-
internal_stderr::IO = stderr, internal_stdout::IO = stdout, isext::Bool=false)
2870+
internal_stderr::IO = stderr, internal_stdout::IO = stdout, loadable_exts::Union{Vector{PkgId},Nothing}=nothing)
28722871
@nospecialize internal_stderr internal_stdout
28732872
rm(output, force=true) # Remove file if it exists
28742873
output_o === nothing || rm(output_o, force=true)
@@ -2942,7 +2941,8 @@ function create_expr_cache(pkg::PkgId, input::String, output::String, output_o::
29422941
write(io.in, """
29432942
empty!(Base.EXT_DORMITORY) # If we have a custom sysimage with `EXT_DORMITORY` prepopulated
29442943
Base.track_nested_precomp($precomp_stack)
2945-
Base.precompiling_extension = $(loading_extension | isext)
2944+
Base.loadable_extensions = $(loadable_exts)
2945+
Base.precompiling_extension = $(loading_extension)
29462946
Base.include_package_for_output($(pkg_str(pkg)), $(repr(abspath(input))), $(repr(depot_path)), $(repr(dl_load_path)),
29472947
$(repr(load_path)), $deps, $(repr(source_path(nothing))))
29482948
""")
@@ -2999,18 +2999,18 @@ This can be used to reduce package load times. Cache files are stored in
29992999
`DEPOT_PATH[1]/compiled`. See [Module initialization and precompilation](@ref)
30003000
for important notes.
30013001
"""
3002-
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)
3002+
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)
30033003
@nospecialize internal_stderr internal_stdout
30043004
path = locate_package(pkg)
30053005
path === nothing && throw(ArgumentError("$(repr("text/plain", pkg)) not found during precompilation"))
3006-
return compilecache(pkg, path, internal_stderr, internal_stdout; flags, reasons, isext)
3006+
return compilecache(pkg, path, internal_stderr, internal_stdout; flags, reasons, loadable_exts)
30073007
end
30083008

30093009
const MAX_NUM_PRECOMPILE_FILES = Ref(10)
30103010

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

30153015
@nospecialize internal_stderr internal_stdout
30163016
# decide where to put the resulting cache file
@@ -3050,7 +3050,7 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in
30503050
close(tmpio_o)
30513051
close(tmpio_so)
30523052
end
3053-
p = create_expr_cache(pkg, path, tmppath, tmppath_o, concrete_deps, flags, cacheflags, internal_stderr, internal_stdout, isext)
3053+
p = create_expr_cache(pkg, path, tmppath, tmppath_o, concrete_deps, flags, cacheflags, internal_stderr, internal_stdout, loadable_exts)
30543054

30553055
if success(p)
30563056
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)