Skip to content

Commit 276d247

Browse files
committed
Relax restriction of preferences to top-level packages
When preferences were first added, we originally did not have any preference merging across load path [0]. We later added that [1], but retained the requirement that for each individual element in the load path, preferences must have an entry in `Project.toml` listing the relevant package. This was partly on purpose (immediately binding the name to the UUID prevents confusion when a UUID<->name binding is not readily apparent) and partly just inheriting the way things worked back when preferences was written with just a single Project.toml in mind. This PR breaks this assumption to specifically allow an entry in the Julia load path that contains only a single `LocalPreferences.toml` file that sets preferences for packages that may or may not be in the current environment stack. The usecase and desire for this is well-motivated in [2], but basically boils down to "system admin provided preferences that should be used globally". In fact, one such convenient method that now works is to drop a `LocalPreferences.toml` file in the `stdlib` directory of your Julia distribution, as that is almost always a part of a Julia process's load path. [0] #37595 [1] #38044 [2] JuliaPackaging/Preferences.jl#33
1 parent 100e305 commit 276d247

File tree

1 file changed

+64
-18
lines changed

1 file changed

+64
-18
lines changed

base/loading.jl

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3663,42 +3663,41 @@ end
36633663

36643664
# If we've asked for a specific UUID, this function will extract the prefs
36653665
# for that particular UUID. Otherwise, it returns all preferences.
3666-
function filter_preferences(prefs::Dict{String, Any}, pkg_name)
3667-
if pkg_name === nothing
3666+
function filter_preferences(toml_path::String, prefs::Dict{String, Any}, pkg_names::Set{String})
3667+
if isempty(pkg_names)
36683668
return prefs
36693669
else
3670-
return get(Dict{String, Any}, prefs, pkg_name)::Dict{String, Any}
3670+
present_pkg_names = filter(n -> n keys(prefs), pkg_names)
3671+
if length(present_pkg_names) > 1
3672+
@warn("""
3673+
$(toml_path) contains preference mappings that refer to the same UUID!
3674+
""", pkg_names=present_pkg_names)
3675+
end
3676+
if length(present_pkg_names) == 1
3677+
return prefs[only(present_pkg_names)]::Dict{String, Any}
3678+
end
3679+
return Dict{String,Any}()
36713680
end
36723681
end
36733682

3674-
function collect_preferences(project_toml::String, uuid::Union{UUID,Nothing})
3683+
function collect_preferences(project_toml::String, uuid::Union{UUID,Nothing}, uuid_name_map::Dict{UUID,Set{String}})
36753684
# We'll return a list of dicts to be merged
36763685
dicts = Dict{String, Any}[]
36773686

36783687
project = parsed_toml(project_toml)
3679-
pkg_name = nothing
3680-
if uuid !== nothing
3681-
# If we've been given a UUID, map that to the name of the package as
3682-
# recorded in the preferences section. If we can't find that mapping,
3683-
# exit out, as it means there's no way preferences can be set for that
3684-
# UUID, as we only allow actual dependencies to have preferences set.
3685-
pkg_name = get_uuid_name(project, uuid)
3686-
if pkg_name === nothing
3687-
return dicts
3688-
end
3689-
end
3688+
pkg_names = get(Set{String}, uuid_name_map, uuid)::Set{String}
36903689

36913690
# Look first inside of `Project.toml` to see we have preferences embedded within there
36923691
proj_preferences = get(Dict{String, Any}, project, "preferences")::Dict{String, Any}
3693-
push!(dicts, filter_preferences(proj_preferences, pkg_name))
3692+
push!(dicts, filter_preferences(project_toml, proj_preferences, pkg_names))
36943693

36953694
# Next, look for `(Julia)LocalPreferences.toml` files next to this `Project.toml`
36963695
project_dir = dirname(project_toml)
36973696
for name in preferences_names
36983697
toml_path = joinpath(project_dir, name)
36993698
if isfile(toml_path)
37003699
prefs = parsed_toml(toml_path)
3701-
push!(dicts, filter_preferences(prefs, pkg_name))
3700+
push!(dicts, filter_preferences(toml_path, prefs, pkg_names))
37023701

37033702
# If we find `JuliaLocalPreferences.toml`, don't look for `LocalPreferences.toml`
37043703
break
@@ -3750,6 +3749,48 @@ function get_projects_workspace_to_root(project_file)
37503749
end
37513750
end
37523751

3752+
function build_uuid_name_map(envs::Vector{String})
3753+
uuid_map = Dict{UUID,Set{String}}()
3754+
name_map = Dict{String,UUID}()
3755+
disabled_names = Set{String}()
3756+
for env in envs
3757+
project_toml = env_project_file(env)
3758+
if !isa(project_toml, String)
3759+
continue
3760+
end
3761+
3762+
manifest_toml = project_file_manifest_path(project_toml)
3763+
if manifest_toml === nothing
3764+
continue
3765+
end
3766+
deps = get_deps(parsed_toml(manifest_toml))
3767+
for (name, entries) in deps
3768+
if name disabled_names
3769+
continue
3770+
end
3771+
3772+
uuid = UUID(only(entries)["uuid"])
3773+
# We keep the `name_map` just to ensure that our mapping of name -> uuid
3774+
# is unique, and therefore LocalPreferences.toml files are non-ambiguous
3775+
if get(name_map, name, uuid) != uuid
3776+
push!(disabled_names, name)
3777+
@warn("""
3778+
Two different UUIDs mapped to the same name in this environment!
3779+
Preferences are disabled for these packages due to this ambiguity.
3780+
""", name, uuid1=uuid, uuid2=name_map[name])
3781+
delete!(uuid_map, name_map[name])
3782+
else
3783+
if !haskey(uuid_map, uuid)
3784+
uuid_map[uuid] = Set{String}()
3785+
end
3786+
push!(uuid_map[uuid], name)
3787+
name_map[name] = uuid
3788+
end
3789+
end
3790+
end
3791+
return uuid_map
3792+
end
3793+
37533794
function get_preferences(uuid::Union{UUID,Nothing} = nothing)
37543795
merged_prefs = Dict{String,Any}()
37553796
loadpath = load_path()
@@ -3759,14 +3800,19 @@ function get_preferences(uuid::Union{UUID,Nothing} = nothing)
37593800
prepend!(projects_to_merge_prefs, get_projects_workspace_to_root(first(loadpath)))
37603801
end
37613802

3803+
# Build a mapping of UUIDs to names across our entire load path.
3804+
# This allows us to look up the name(s) of a UUID for a LocalPreferences.jl
3805+
# file that may not even have the package added as a direct dependency.
3806+
uuid_name_map = build_uuid_name_map(projects_to_merge_prefs)
3807+
37623808
for env in reverse(projects_to_merge_prefs)
37633809
project_toml = env_project_file(env)
37643810
if !isa(project_toml, String)
37653811
continue
37663812
end
37673813

37683814
# Collect all dictionaries from the current point in the load path, then merge them in
3769-
dicts = collect_preferences(project_toml, uuid)
3815+
dicts = collect_preferences(project_toml, uuid, uuid_name_map)
37703816
merged_prefs = recursive_prefs_merge(merged_prefs, dicts...)
37713817
end
37723818
return merged_prefs

0 commit comments

Comments
 (0)