@@ -348,11 +348,14 @@ function identify_package_env(where::PkgId, name::String)
348348 else
349349 for env in load_path ()
350350 pkgid = manifest_deps_get (env, where , name)
351- pkgid === nothing && continue # not found--keep looking
351+ # If we didn't find `where` at all, keep looking through the environment stack
352+ pkgid === nothing && continue
352353 if pkgid. uuid != = nothing
353- pkg_env = pkgid, env # found in explicit environment--use it
354+ pkg_env = pkgid, env
354355 end
355- break # found in implicit environment--return "not found"
356+ # If we don't have pkgid.uuid, still break here - this is a sentinel that indicates
357+ # that we've found `where` but it did not have the required dependency. We terminate the search.
358+ break
356359 end
357360 if pkg_env === nothing && is_stdlib (where )
358361 # if not found it could be that manifests are from a different julia version/commit
@@ -698,51 +701,77 @@ function project_deps_get(env::String, name::String)::Union{Nothing,PkgId}
698701 return nothing
699702end
700703
704+ function package_get_here (project_file, name:: String )
705+ # if `where` matches the project, use [deps] section as manifest, and stop searching
706+ pkg_uuid = explicit_project_deps_get (project_file, name)
707+ pkg_uuid === nothing && return PkgId (name)
708+ return PkgId (pkg_uuid, name)
709+ end
710+
701711function package_get (project_file, where :: PkgId , name:: String )
702712 proj = project_file_name_uuid (project_file, where . name)
703- if proj == where
704- # if `where` matches the project, use [deps] section as manifest, and stop searching
705- pkg_uuid = explicit_project_deps_get (project_file, name)
706- return PkgId (pkg_uuid, name)
707- end
708- return nothing
713+ proj != where && return nothing
714+ return package_get_here (project_file, name)
709715end
710716
711- function manifest_deps_get (env:: String , where :: PkgId , name:: String ):: Union{Nothing,PkgId}
712- uuid = where . uuid
713- @assert uuid != = nothing
714- project_file = env_project_file (env)
715- if project_file isa String
716- pkg = package_get (project_file, where , name)
717- pkg === nothing || return pkg
718- d = parsed_toml (project_file)
719- exts = get (d, " extensions" , nothing ):: Union{Dict{String, Any}, Nothing}
720- if exts != = nothing
721- proj = project_file_name_uuid (project_file, where . name)
722- # Check if `where` is an extension of the project
723- if where . name in keys (exts) && where . uuid == uuid5 (proj. uuid:: UUID , where . name)
724- # Extensions can load weak deps...
717+ ext_may_load_weakdep (exts:: String , name:: String ) = exts == name
718+ ext_may_load_weakdep (exts:: Vector{String} , name:: String ) = name in exts
719+
720+ function package_extension_get (project_file, where :: PkgId , name:: String )
721+ d = parsed_toml (project_file)
722+ exts = get (d, " extensions" , nothing ):: Union{Dict{String, Any}, Nothing}
723+ if exts != = nothing
724+ proj = project_file_name_uuid (project_file, where . name)
725+ # Check if `where` is an extension of the project
726+ if where . name in keys (exts) && where . uuid == uuid5 (proj. uuid:: UUID , where . name)
727+ # Extensions can load weak deps if they are an extension trigger
728+ if ext_may_load_weakdep (exts[where . name]:: Union{String, Vector{String}} , name)
725729 weakdeps = get (d, " weakdeps" , nothing ):: Union{Dict{String, Any}, Nothing}
726730 if weakdeps != = nothing
727731 wuuid = get (weakdeps, name, nothing ):: Union{String, Nothing}
728732 if wuuid != = nothing
729733 return PkgId (UUID (wuuid), name)
730734 end
731735 end
732- # ... and they can load same deps as the project itself
733- mby_uuid = explicit_project_deps_get (project_file, name)
734- mby_uuid === nothing || return PkgId (mby_uuid, name)
735736 end
737+ # ... and they can load same deps as the project itself
738+ return package_get_here (project_file, name)
736739 end
737- # look for manifest file and `where` stanza
738- return explicit_manifest_deps_get (project_file, where , name)
739- elseif project_file
740- # if env names a directory, search it
741- return implicit_manifest_deps_get (env, where , name)
742740 end
743741 return nothing
744742end
745743
744+ function manifest_deps_get (env:: String , where :: PkgId , name:: String ):: Union{Nothing,PkgId}
745+ @assert where . uuid != = nothing
746+ project_file = env_project_file (env)
747+ implicit_manifest = ! (project_file isa String)
748+ if implicit_manifest
749+ project_file || return nothing
750+ project_file = implicit_manifest_project (env, where )
751+ project_file === nothing && return nothing
752+ end
753+
754+ # 1. Are we loading into the top-level project itself? dependencies come from [deps]
755+ # N.B.: Here "top-level" includes package loaded from an implicit manifest, which
756+ # uses the same code path.
757+ pkg = package_get (project_file, where , name)
758+ pkg === nothing || return pkg
759+
760+ # 2. Are we an extension of the top-level project? dependencies come from [weakdeps] and [deps]
761+ pkg = package_extension_get (project_file, where , name)
762+ pkg === nothing || return pkg
763+
764+ if implicit_manifest
765+ # With an implicit manifest, getting here means that our (implicit) environment
766+ # *has* the package `where`. If we don't find it, it just means that `where` doesn't
767+ # have `name` as a dependency - c.f. the analogous case in `explicit_manifest_deps_get`.
768+ return PkgId (name)
769+ end
770+
771+ # All other cases, dependencies come from the (top-level) manifest
772+ return explicit_manifest_deps_get (project_file, where , name)
773+ end
774+
746775function manifest_uuid_path (env:: String , pkg:: PkgId ):: Union{Nothing,String,Missing}
747776 project_file = env_project_file (env)
748777 if project_file isa String
913942# find project file root or deps `name => uuid` mapping
914943# `ext` is the name of the extension if `name` is loaded from one
915944# return `nothing` if `name` is not found
916- function explicit_project_deps_get (project_file:: String , name:: String , ext :: Union{String,Nothing} = nothing ):: Union{Nothing,UUID}
945+ function explicit_project_deps_get (project_file:: String , name:: String ):: Union{Nothing,UUID}
917946 d = parsed_toml (project_file)
918947 if get (d, " name" , nothing ):: Union{String, Nothing} === name
919948 root_uuid = dummy_uuid (project_file)
@@ -925,19 +954,6 @@ function explicit_project_deps_get(project_file::String, name::String, ext::Unio
925954 uuid = get (deps, name, nothing ):: Union{String, Nothing}
926955 uuid === nothing || return UUID (uuid)
927956 end
928- if ext != = nothing
929- extensions = get (d, " extensions" , nothing )
930- extensions === nothing && return nothing
931- ext_data = get (extensions, ext, nothing )
932- ext_data === nothing && return nothing
933- if (ext_data isa String && name == ext_data) || (ext_data isa Vector{String} && name in ext_data)
934- weakdeps = get (d, " weakdeps" , nothing ):: Union{Dict{String, Any}, Nothing}
935- weakdeps === nothing && return nothing
936- wuuid = get (weakdeps, name, nothing ):: Union{String, Nothing}
937- wuuid === nothing && return nothing
938- return UUID (wuuid)
939- end
940- end
941957 return nothing
942958end
943959
@@ -964,14 +980,27 @@ function get_deps(raw_manifest::Dict)
964980 end
965981end
966982
967- # find `where` stanza and return the PkgId for `name`
968- # return `nothing` if it did not find `where` (indicating caller should continue searching)
983+ function dep_stanza_get (stanza:: Dict{String, Any} , name:: String ):: Union{Nothing, PkgId}
984+ for (dep, uuid) in stanza
985+ uuid:: String
986+ if dep === name
987+ return PkgId (UUID (uuid), name)
988+ end
989+ end
990+ return nothing
991+ end
992+
993+ function dep_stanza_get (stanza:: Vector{String} , name:: String ):: Union{Nothing, PkgId}
994+ name in stanza && return PkgId (name)
995+ return nothing
996+ end
997+
998+ dep_stanza_get (stanza:: Nothing , name:: String ) = nothing
999+
9691000function explicit_manifest_deps_get (project_file:: String , where :: PkgId , name:: String ):: Union{Nothing,PkgId}
9701001 manifest_file = project_file_manifest_path (project_file)
9711002 manifest_file === nothing && return nothing # manifest not found--keep searching LOAD_PATH
9721003 d = get_deps (parsed_toml (manifest_file))
973- found_where = false
974- found_name = false
9751004 for (dep_name, entries) in d
9761005 entries:: Vector{Any}
9771006 for entry in entries
@@ -981,67 +1010,62 @@ function explicit_manifest_deps_get(project_file::String, where::PkgId, name::St
9811010 # deps is either a list of names (deps = ["DepA", "DepB"]) or
9821011 # a table of entries (deps = {"DepA" = "6ea...", "DepB" = "55d..."}
9831012 deps = get (entry, " deps" , nothing ):: Union{Vector{String}, Dict{String, Any}, Nothing}
1013+ local dep:: Union{Nothing, PkgId}
9841014 if UUID (uuid) === where . uuid
985- found_where = true
986- if deps isa Vector{String}
987- found_name = name in deps
988- found_name && @goto done
989- elseif deps isa Dict{String, Any}
990- deps = deps:: Dict{String, Any}
991- for (dep, uuid) in deps
992- uuid:: String
993- if dep === name
994- return PkgId (UUID (uuid), name)
995- end
996- end
997- end
998- else # Check for extensions
1015+ dep = dep_stanza_get (deps, name)
1016+
1017+ # We found `where` in this environment, but it did not have a deps entry for
1018+ # `name`. This is likely because the dependency was modified without a corresponding
1019+ # change to dependency's Project or our Manifest. Return a sentinel here indicating
1020+ # that we know the package, but do not know its UUID. The caller will terminate the
1021+ # search and provide an appropriate error to the user.
1022+ dep === nothing && return PkgId (name)
1023+ else
1024+ # Check if we're trying to load into an extension of this package
9991025 extensions = get (entry, " extensions" , nothing )
10001026 if extensions != = nothing
10011027 if haskey (extensions, where . name) && where . uuid == uuid5 (UUID (uuid), where . name)
1002- found_where = true
10031028 if name == dep_name
1029+ # Extension loads its base package
10041030 return PkgId (UUID (uuid), name)
10051031 end
10061032 exts = extensions[where . name]:: Union{String, Vector{String}}
1007- weakdeps = get (entry, " weakdeps" , nothing ):: Union{Vector{String}, Dict{String, Any}, Nothing}
1008- if (exts isa String && name == exts) || (exts isa Vector{String} && name in exts)
1009- for deps′ in [weakdeps, deps]
1010- if deps′ != = nothing
1011- if deps′ isa Vector{String}
1012- found_name = name in deps′
1013- found_name && @goto done
1014- elseif deps′ isa Dict{String, Any}
1015- deps′ = deps′:: Dict{String, Any}
1016- for (dep, uuid) in deps′
1017- uuid:: String
1018- if dep === name
1019- return PkgId (UUID (uuid), name)
1020- end
1021- end
1022- end
1023- end
1024- end
1025- end
1026- # `name` is not an ext, do standard lookup as if this was the parent
1027- return identify_package (PkgId (UUID (uuid), dep_name), name)
1033+ # Extensions are allowed to load:
1034+ # 1. Any ordinary dep of the parent package
1035+ # 2. Any weakdep of the parent package declared as an extension trigger
1036+ for deps′ in (ext_may_load_weakdep (exts, name) ?
1037+ (get (entry, " weakdeps" , nothing ):: Union{Vector{String}, Dict{String, Any}, Nothing} , deps) :
1038+ (deps,))
1039+ dep = dep_stanza_get (deps′, name)
1040+ dep === nothing && continue
1041+ @goto have_dep
1042+ end
1043+ return PkgId (name)
10281044 end
10291045 end
1046+ continue
10301047 end
1048+
1049+ @label have_dep
1050+ dep. uuid != = nothing && return dep
1051+
1052+ # We have the dep, but it did not specify a UUID. In this case,
1053+ # it must be that the name is unique in the manifest - so lookup
1054+ # the UUID at the lop level by name
1055+ name_deps = get (d, name, nothing ):: Union{Nothing, Vector{Any}}
1056+ if name_deps === nothing || length (name_deps) != 1
1057+ error (" expected a single entry for $(repr (name)) in $(repr (project_file)) " )
1058+ end
1059+ entry = first (name_deps:: Vector{Any} ):: Dict{String, Any}
1060+ uuid = get (entry, " uuid" , nothing ):: Union{String, Nothing}
1061+ uuid === nothing && return PkgId (name)
1062+ return PkgId (UUID (uuid), name)
10311063 end
10321064 end
1033- @label done
1034- found_where || return nothing
1035- found_name || return PkgId (name)
1036- # Only reach here if deps was not a dict which mean we have a unique name for the dep
1037- name_deps = get (d, name, nothing ):: Union{Nothing, Vector{Any}}
1038- if name_deps === nothing || length (name_deps) != 1
1039- error (" expected a single entry for $(repr (name)) in $(repr (project_file)) " )
1040- end
1041- entry = first (name_deps:: Vector{Any} ):: Dict{String, Any}
1042- uuid = get (entry, " uuid" , nothing ):: Union{String, Nothing}
1043- uuid === nothing && return nothing
1044- return PkgId (UUID (uuid), name)
1065+
1066+ # We did not find `where` in this environment, either as a package or as an extension.
1067+ # The caller should continue searching the environment stack.
1068+ return nothing
10451069end
10461070
10471071# find `uuid` stanza, return the corresponding path
@@ -1124,35 +1148,16 @@ function implicit_project_deps_get(dir::String, name::String)::Union{Nothing,Pkg
11241148 return proj
11251149end
11261150
1127- # look for an entry-point for `name`, check that UUID matches
1128- # if there's a project file, look up `name` in its deps and return that
1129- # otherwise return `nothing` to indicate the caller should keep searching
1130- function implicit_manifest_deps_get (dir:: String , where :: PkgId , name:: String ):: Union{Nothing,PkgId}
1131- @assert where . uuid != = nothing
1132- project_file = entry_point_and_project_file (dir, where . name)[2 ]
1151+ function implicit_manifest_project (dir, pkg:: PkgId ):: Union{Nothing, String}
1152+ @assert pkg. uuid != = nothing
1153+ project_file = entry_point_and_project_file (dir, pkg. name)[2 ]
11331154 if project_file === nothing
11341155 # `where` could be an extension
1135- project_file = implicit_env_project_file_extension (dir, where )[2 ]
1136- project_file === nothing && return nothing
1137- end
1138- proj = project_file_name_uuid (project_file, where . name)
1139- ext = nothing
1140- if proj != = where
1141- # `where` could be an extension in `proj`
1142- d = parsed_toml (project_file)
1143- exts = get (d, " extensions" , nothing ):: Union{Dict{String, Any}, Nothing}
1144- if exts != = nothing && where . name in keys (exts)
1145- if where . uuid != = uuid5 (proj. uuid, where . name)
1146- return nothing
1147- end
1148- ext = where . name
1149- else
1150- return nothing
1151- end
1156+ return implicit_env_project_file_extension (dir, pkg)[2 ]
11521157 end
1153- # this is the correct project, so stop searching here
1154- pkg_uuid = explicit_project_deps_get (project_file, name, ext)
1155- return PkgId (pkg_uuid, name)
1158+ proj = project_file_name_uuid (project_file, pkg . name)
1159+ proj == pkg || return nothing
1160+ return project_file
11561161end
11571162
11581163# look for an entry-point for `pkg` and return its path if UUID matches
0 commit comments