Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[backports-release-1.10] allow extensions to trigger from packages in [deps] (#54009) #56383

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 50 additions & 38 deletions base/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -878,14 +878,14 @@ function explicit_manifest_deps_get(project_file::String, where::PkgId, name::St
entry = entry::Dict{String, Any}
uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
uuid === nothing && continue
# deps is either a list of names (deps = ["DepA", "DepB"]) or
# a table of entries (deps = {"DepA" = "6ea...", "DepB" = "55d..."}
deps = get(entry, "deps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing}
if UUID(uuid) === where.uuid
found_where = true
# deps is either a list of names (deps = ["DepA", "DepB"]) or
# a table of entries (deps = {"DepA" = "6ea...", "DepB" = "55d..."}
deps = get(entry, "deps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing}
if deps isa Vector{String}
found_name = name in deps
break
found_name && @goto done
elseif deps isa Dict{String, Any}
deps = deps::Dict{String, Any}
for (dep, uuid) in deps
Expand All @@ -904,30 +904,33 @@ function explicit_manifest_deps_get(project_file::String, where::PkgId, name::St
return PkgId(UUID(uuid), name)
end
exts = extensions[where.name]::Union{String, Vector{String}}
weakdeps = get(entry, "weakdeps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing}
if (exts isa String && name == exts) || (exts isa Vector{String} && name in exts)
weakdeps = get(entry, "weakdeps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing}
if weakdeps !== nothing
if weakdeps isa Vector{String}
found_name = name in weakdeps
break
elseif weakdeps isa Dict{String, Any}
weakdeps = weakdeps::Dict{String, Any}
for (dep, uuid) in weakdeps
uuid::String
if dep === name
return PkgId(UUID(uuid), name)
for deps′ in [weakdeps, deps]
if deps′ !== nothing
if deps′ isa Vector{String}
found_name = name in deps′
found_name && @goto done
elseif deps′ isa Dict{String, Any}
deps′ = deps′::Dict{String, Any}
for (dep, uuid) in deps′
uuid::String
if dep === name
return PkgId(UUID(uuid), name)
end
end
end
end
end
end
end
# `name` is not an ext, do standard lookup as if this was the parent
return identify_package(PkgId(UUID(uuid), dep_name), name)
end
end
end
end
end
@label done
found_where || return nothing
found_name || return PkgId(name)
# Only reach here if deps was not a dict which mean we have a unique name for the dep
Expand Down Expand Up @@ -1262,12 +1265,13 @@ function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missi
proj_pkg = project_file_name_uuid(project_file, pkg.name)
if pkg == proj_pkg
d_proj = parsed_toml(project_file)
weakdeps = get(d_proj, "weakdeps", nothing)::Union{Nothing, Vector{String}, Dict{String,Any}}
extensions = get(d_proj, "extensions", nothing)::Union{Nothing, Dict{String, Any}}
extensions === nothing && return
weakdeps === nothing && return
if weakdeps isa Dict{String, Any}
return _insert_extension_triggers(pkg, extensions, weakdeps)
weakdeps = get(Dict{String, Any}, d_proj, "weakdeps")::Union{Vector{String}, Dict{String,Any}}
deps = get(Dict{String, Any}, d_proj, "deps")::Union{Vector{String}, Dict{String,Any}}
if weakdeps isa Dict{String,Any} && deps isa Dict{String,Any}
total_deps = merge(weakdeps, deps)
return _insert_extension_triggers(pkg, extensions, total_deps)
end
end

Expand All @@ -1282,35 +1286,43 @@ function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missi
uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
uuid === nothing && continue
if UUID(uuid) == pkg.uuid
weakdeps = get(entry, "weakdeps", nothing)::Union{Nothing, Vector{String}, Dict{String,Any}}
extensions = get(entry, "extensions", nothing)::Union{Nothing, Dict{String, Any}}
extensions === nothing && return
weakdeps === nothing && return
if weakdeps isa Dict{String, Any}
return _insert_extension_triggers(pkg, extensions, weakdeps)
weakdeps = get(Dict{String, Any}, entry, "weakdeps")::Union{Vector{String}, Dict{String,Any}}
deps = get(Dict{String, Any}, entry, "deps")::Union{Vector{String}, Dict{String,Any}}

function expand_deps_list(deps′::Vector{String})
deps′_expanded = Dict{String, Any}()
for (dep_name, entries) in d
dep_name in deps′ || continue
entries::Vector{Any}
if length(entries) != 1
error("expected a single entry for $(repr(dep_name)) in $(repr(project_file))")
end
entry = first(entries)::Dict{String, Any}
uuid = entry["uuid"]::String
deps′_expanded[dep_name] = uuid
end
return deps′_expanded
end

d_weakdeps = Dict{String, Any}()
for (dep_name, entries) in d
dep_name in weakdeps || continue
entries::Vector{Any}
if length(entries) != 1
error("expected a single entry for $(repr(dep_name)) in $(repr(project_file))")
end
entry = first(entries)::Dict{String, Any}
uuid = entry["uuid"]::String
d_weakdeps[dep_name] = uuid
if weakdeps isa Vector{String}
weakdeps = expand_deps_list(weakdeps)
end
@assert length(d_weakdeps) == length(weakdeps)
return _insert_extension_triggers(pkg, extensions, d_weakdeps)
if deps isa Vector{String}
deps = expand_deps_list(deps)
end

total_deps = merge(weakdeps, deps)
return _insert_extension_triggers(pkg, extensions, total_deps)
end
end
end
end
return nothing
end

function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, Any}, weakdeps::Dict{String, Any})
function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, Any}, totaldeps::Dict{String, Any})
for (ext, triggers) in extensions
triggers = triggers::Union{String, Vector{String}}
triggers isa String && (triggers = [triggers])
Expand All @@ -1324,7 +1336,7 @@ function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, Any}
push!(trigger1, gid)
for trigger in triggers
# TODO: Better error message if this lookup fails?
uuid_trigger = UUID(weakdeps[trigger]::String)
uuid_trigger = UUID(totaldeps[trigger]::String)
trigger_id = PkgId(uuid_trigger, trigger)
if !haskey(Base.loaded_modules, trigger_id) || haskey(package_locks, trigger_id)
trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, trigger_id)
Expand Down
12 changes: 6 additions & 6 deletions doc/src/manual/code-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ Since the primary environment is typically the environment of a project you're w

### [Package Extensions](@id man-extensions)

A package "extension" is a module that is automatically loaded when a specified set of other packages (its "extension dependencies") are loaded in the current Julia session. Extensions are defined under the `[extensions]` section in the project file. The extension dependencies of an extension are a subset of those packages listed under the `[weakdeps]` section of the project file. Those packages can have compat entries like other packages.
A package "extension" is a module that is automatically loaded when a specified set of other packages (its "triggers") are loaded in the current Julia session. Extensions are defined under the `[extensions]` section in the project file. The triggers of an extension are a subset of those packages listed under the `[weakdeps]` (and possibly, but uncommonly the `[deps]`) section of the project file. Those packages can have compat entries like other packages.

```toml
name = "MyPackage"
Expand All @@ -371,27 +371,27 @@ FooExt = "ExtDep"
```

The keys under `extensions` are the names of the extensions.
They are loaded when all the packages on the right hand side (the extension dependencies) of that extension are loaded.
If an extension only has one extension dependency the list of extension dependencies can be written as just a string for brevity.
They are loaded when all the packages on the right hand side (the triggers) of that extension are loaded.
If an extension only has one trigger the list of triggers can be written as just a string for brevity.
The location for the entry point of the extension is either in `ext/FooExt.jl` or `ext/FooExt/FooExt.jl` for
extension `FooExt`.
The content of an extension is often structured as:

```
module FooExt

# Load main package and extension dependencies
# Load main package and triggers
using MyPackage, ExtDep

# Extend functionality in main package with types from the extension dependencies
# Extend functionality in main package with types from the triggers
MyPackage.func(x::ExtDep.SomeStruct) = ...

end
```

When a package with extensions is added to an environment, the `weakdeps` and `extensions` sections
are stored in the manifest file in the section for that package. The dependency lookup rules for
a package are the same as for its "parent" except that the listed extension dependencies are also considered as
a package are the same as for its "parent" except that the listed triggers are also considered as
dependencies.

### [Package/Environment Preferences](@id preferences)
Expand Down
5 changes: 5 additions & 0 deletions test/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,9 @@ end
using ExtDep2
$ew using ExtDep2
$ew HasExtensions.ext_folder_loaded || error("ext_folder_loaded not set")
using ExtDep3
$ew using ExtDep3
$ew HasExtensions.ext_dep_loaded || error("ext_dep_loaded not set")
end
"""
return `$(Base.julia_cmd()) $compile --startup-file=no -e $cmd`
Expand Down Expand Up @@ -1088,6 +1091,8 @@ end
test_ext(HasExtensions, :Extension)
using ExtDep2
test_ext(HasExtensions, :ExtensionFolder)
using ExtDep3
test_ext(HasExtensions, :ExtensionDep)
end
"""
for compile in (`--compiled-modules=no`, ``)
Expand Down
9 changes: 8 additions & 1 deletion test/project/Extensions/EnvWithHasExtensions/Manifest.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is machine-generated - editing it directly is not advised

julia_version = "1.9.0-beta4"
julia_version = "1.10.6"
manifest_format = "2.0"
project_hash = "caa716752e6dff3d77c3de929ebbb5d2024d04ef"

Expand All @@ -10,13 +10,20 @@ path = "../ExtDep.jl"
uuid = "fa069be4-f60b-4d4c-8b95-f8008775090c"
version = "0.1.0"

[[deps.ExtDep3]]
path = "../ExtDep3.jl"
uuid = "a5541f1e-a556-4fdc-af15-097880d743a1"
version = "0.1.0"

[[deps.HasExtensions]]
deps = ["ExtDep3"]
path = "../HasExtensions.jl"
uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8"
version = "0.1.0"

[deps.HasExtensions.extensions]
Extension = "ExtDep"
ExtensionDep = "ExtDep3"
ExtensionFolder = ["ExtDep", "ExtDep2"]

[deps.HasExtensions.weakdeps]
Expand Down
4 changes: 4 additions & 0 deletions test/project/Extensions/ExtDep3.jl/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name = "ExtDep3"
uuid = "a5541f1e-a556-4fdc-af15-097880d743a1"
version = "0.1.0"
authors = ["Kristoffer <[email protected]>"]
5 changes: 5 additions & 0 deletions test/project/Extensions/ExtDep3.jl/src/ExtDep3.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module ExtDep3

greet() = print("Hello World!")

end # module ExtDep3
11 changes: 9 additions & 2 deletions test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# This file is machine-generated - editing it directly is not advised

julia_version = "1.10.0-DEV"
julia_version = "1.10.6"
manifest_format = "2.0"
project_hash = "d523b3401f72a1ed34b7b43749fd2655c6b78542"
project_hash = "eed4f16fdd2e22799229e480394c255a569eb19c"

[[deps.ExtDep]]
deps = ["SomePackage"]
Expand All @@ -15,14 +15,21 @@ path = "../ExtDep2"
uuid = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d"
version = "0.1.0"

[[deps.ExtDep3]]
path = "../ExtDep3.jl"
uuid = "a5541f1e-a556-4fdc-af15-097880d743a1"
version = "0.1.0"

[[deps.HasExtensions]]
deps = ["ExtDep3"]
path = "../HasExtensions.jl"
uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8"
version = "0.1.0"
weakdeps = ["ExtDep", "ExtDep2"]

[deps.HasExtensions.extensions]
Extension = "ExtDep"
ExtensionDep = "ExtDep3"
ExtensionFolder = ["ExtDep", "ExtDep2"]

[[deps.SomePackage]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ version = "0.1.0"
[deps]
ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c"
ExtDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d"
ExtDep3 = "a5541f1e-a556-4fdc-af15-097880d743a1"
HasExtensions = "4d3288b3-3afc-4bb6-85f3-489fffe514c8"
9 changes: 6 additions & 3 deletions test/project/Extensions/HasExtensions.jl/Manifest.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# This file is machine-generated - editing it directly is not advised

julia_version = "1.10.0-DEV"
julia_version = "1.10.6"
manifest_format = "2.0"
project_hash = "c87947f1f1f070eea848950c304d668a112dec3d"
project_hash = "0948477fbecf27074f82e46d6fe927b1e66ada5b"

[deps]
[[deps.ExtDep3]]
path = "../ExtDep3.jl"
uuid = "a5541f1e-a556-4fdc-af15-097880d743a1"
version = "0.1.0"
4 changes: 4 additions & 0 deletions test/project/Extensions/HasExtensions.jl/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ name = "HasExtensions"
uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8"
version = "0.1.0"

[deps]
ExtDep3 = "a5541f1e-a556-4fdc-af15-097880d743a1"

[weakdeps]
ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c"
ExtDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d"

[extensions]
Extension = "ExtDep"
ExtensionDep = "ExtDep3"
ExtensionFolder = ["ExtDep", "ExtDep2"]
9 changes: 9 additions & 0 deletions test/project/Extensions/HasExtensions.jl/ext/ExtensionDep.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module ExtensionDep

using HasExtensions, ExtDep3

function __init__()
HasExtensions.ext_dep_loaded = true
end

end
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ foo(::HasExtensionsStruct) = 1

ext_loaded = false
ext_folder_loaded = false
ext_dep_loaded = false

end # module