Skip to content

Commit 7f66398

Browse files
vtjnashKristofferC
authored andcommitted
loading: provide code for reifying an load-path result
Allows asking (and storing) queries about what total set of modules might need to be loaded, before starting any load work.
1 parent d425ca8 commit 7f66398

File tree

1 file changed

+226
-25
lines changed

1 file changed

+226
-25
lines changed

base/loading.jl

Lines changed: 226 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -234,12 +234,14 @@ end
234234
## package identification: determine unique identity of package to be loaded ##
235235

236236
# Used by Pkg but not used in loading itself
237-
function find_package(arg)
238-
pkg = identify_package(arg)
237+
find_package(name::String) = find_package(PkgId(""), name) # `where` without a uuid will be ignored
238+
function find_package(where::Union{Module,PkgId}, name::String)
239+
pkg = identify_package(where, name)
239240
pkg === nothing && return nothing
240241
return locate_package(pkg)
241242
end
242243

244+
243245
## package identity: given a package name and a context, try to return its identity ##
244246
identify_package(where::Module, name::String) = identify_package(PkgId(where), name)
245247

@@ -267,6 +269,29 @@ function identify_package(name::String)::Union{Nothing,PkgId}
267269
return nothing
268270
end
269271

272+
# identify_package computes the list of packages that can be loaded by where
273+
function identify_package_deps(where::PkgId)::Vector{PkgId}
274+
where.uuid === nothing && return identify_package_deps()
275+
deps = PkgId[]
276+
for env in load_path()
277+
deps = manifest_deps_list(env, where)
278+
deps === nothing || return deps # found--return it
279+
end
280+
return PkgId[where]
281+
end
282+
283+
# identify_package lists all packages that can be loaded from any toplevel context
284+
# by looking through the Project.toml files and directories
285+
function identify_package_deps()::Vector{PkgId}
286+
deps = PkgId[]
287+
for env in load_path()
288+
add = project_deps_list(env)
289+
add === nothing || append!(deps, add)
290+
end
291+
return deps
292+
end
293+
294+
270295
## package location: given a package identity, find file to load ##
271296
function locate_package(pkg::PkgId)::Union{Nothing,String}
272297
if pkg.uuid === nothing
@@ -281,7 +306,7 @@ function locate_package(pkg::PkgId)::Union{Nothing,String}
281306
return implicit_manifest_uuid_path(env, pkg)
282307
end
283308
@assert found.uuid !== nothing
284-
return locate_package(found) # restart search now that we know the uuid for pkg
309+
return locate_package(found) # restart search now that we know the uuid for pkg (TODO: the existance of this line of code is probably a bug)
285310
end
286311
else
287312
for env in load_path()
@@ -362,9 +387,18 @@ function project_deps_get(env::String, name::String)::Union{Nothing,PkgId}
362387
return nothing
363388
end
364389

390+
function project_deps_list(env::String)::Union{Nothing,Vector{PkgId}}
391+
project_file = env_project_file(env)
392+
if project_file isa String
393+
return explicit_project_deps_list(project_file)
394+
elseif project_file
395+
return implicit_project_deps_list(env)
396+
end
397+
return nothing
398+
end
399+
365400
function manifest_deps_get(env::String, where::PkgId, name::String)::Union{Nothing,PkgId}
366-
uuid = where.uuid
367-
@assert uuid !== nothing
401+
@assert where.uuid !== nothing
368402
project_file = env_project_file(env)
369403
if project_file isa String
370404
# first check if `where` names the Project itself
@@ -375,14 +409,34 @@ function manifest_deps_get(env::String, where::PkgId, name::String)::Union{Nothi
375409
return PkgId(pkg_uuid, name)
376410
end
377411
# look for manifest file and `where` stanza
378-
return explicit_manifest_deps_get(project_file, uuid, name)
412+
return explicit_manifest_deps_get(project_file, where, name)
379413
elseif project_file
380414
# if env names a directory, search it
381415
return implicit_manifest_deps_get(env, where, name)
382416
end
383417
return nothing
384418
end
385419

420+
function manifest_deps_list(env::String, where::PkgId)::Union{Nothing,Vector{PkgId}}
421+
@assert where.uuid !== nothing
422+
project_file = env_project_file(env)
423+
if project_file isa String
424+
# first check if `where` names the Project itself
425+
proj = project_file_name_uuid(project_file, where.name)
426+
if proj == where
427+
# if `where` matches the project, use [deps] section as manifest, and stop searching
428+
return explicit_project_deps_list(project_file)
429+
end
430+
# look for manifest file and `where` stanza
431+
return explicit_manifest_deps_list(project_file, where)
432+
elseif project_file
433+
# if env names a directory, search it
434+
return implicit_manifest_deps_list(env, where)
435+
end
436+
return nothing
437+
end
438+
439+
386440
function manifest_uuid_path(env::String, pkg::PkgId)::Union{Nothing,String}
387441
project_file = env_project_file(env)
388442
if project_file isa String
@@ -402,10 +456,9 @@ end
402456

403457
# find project file's top-level UUID entry (or nothing)
404458
function project_file_name_uuid(project_file::String, name::String)::PkgId
405-
uuid = dummy_uuid(project_file)
406459
d = parsed_toml(project_file)
407460
uuid′ = get(d, "uuid", nothing)::Union{String, Nothing}
408-
uuid′ === nothing || (uuid = UUID(uuid′))
461+
uuid = uuid=== nothing ? dummy_uuid(project_file) : UUID(uuid′)
409462
name = get(d, "name", name)::String
410463
return PkgId(uuid, name)
411464
end
@@ -433,6 +486,7 @@ end
433486

434487
# given a directory (implicit env from LOAD_PATH) and a name,
435488
# check if it is an implicit package
489+
# TODO: aren't we supposed to first check for the Project file first and see if it declares a path?
436490
function entry_point_and_project_file_inside(dir::String, name::String)::Union{Tuple{Nothing,Nothing},Tuple{String,Nothing},Tuple{String,String}}
437491
path = normpath(joinpath(dir, "src", "$name.jl"))
438492
isfile_casesensitive(path) || return nothing, nothing
@@ -472,10 +526,9 @@ end
472526
# return `nothing` if `name` is not found
473527
function explicit_project_deps_get(project_file::String, name::String)::Union{Nothing,UUID}
474528
d = parsed_toml(project_file)
475-
root_uuid = dummy_uuid(project_file)
476529
if get(d, "name", nothing)::Union{String, Nothing} === name
477530
uuid = get(d, "uuid", nothing)::Union{String, Nothing}
478-
return uuid === nothing ? root_uuid : UUID(uuid)
531+
return uuid === nothing ? dummy_uuid(project_file) : UUID(uuid)
479532
end
480533
deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
481534
if deps !== nothing
@@ -485,29 +538,57 @@ function explicit_project_deps_get(project_file::String, name::String)::Union{No
485538
return nothing
486539
end
487540

541+
function explicit_project_deps_list(project_file::String)::Union{Nothing,Vector{PkgId}}
542+
d = parsed_toml(project_file)
543+
list = PkgId[]
544+
name = get(d, "name", nothing)::Union{String, Nothing}
545+
if name !== nothing
546+
uuid = get(d, "uuid", nothing)::Union{String, Nothing}
547+
uuid = uuid === nothing ? dummy_uuid(project_file) : UUID(uuid)
548+
push!(list, PkgId(uuid, name))
549+
end
550+
deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
551+
if deps !== nothing
552+
for (name, uuid) in deps
553+
push!(list, PkgId(UUID(uuid), name))
554+
end
555+
end
556+
return list
557+
end
558+
488559
# find `where` stanza and return the PkgId for `name`
489560
# return `nothing` if it did not find `where` (indicating caller should continue searching)
490-
function explicit_manifest_deps_get(project_file::String, where::UUID, name::String)::Union{Nothing,PkgId}
561+
function explicit_manifest_deps_get(project_file::String, where::PkgId, name::String)::Union{Nothing,PkgId}
491562
manifest_file = project_file_manifest_path(project_file)
492563
manifest_file === nothing && return nothing # manifest not found--keep searching LOAD_PATH
493564
d = parsed_toml(manifest_file)
494565
found_where = false
495-
found_name = false
496-
for (dep_name, entries) in d
497-
entries::Vector{Any}
566+
entries = get(d, where.name, nothing)::Union{Vector{Any}, Nothing}
567+
if entries !== nothing
498568
for entry in entries
499569
entry = entry::Dict{String, Any}
500570
uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
501571
uuid === nothing && continue
502-
if UUID(uuid) === where
572+
if UUID(uuid) === where.uuid
503573
found_where = true
504574
# deps is either a list of names (deps = ["DepA", "DepB"]) or
505575
# a table of entries (deps = {"DepA" = "6ea...", "DepB" = "55d..."}
506576
deps = get(entry, "deps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing}
507577
deps === nothing && continue
508578
if deps isa Vector{String}
509579
found_name = name in deps
510-
break
580+
if found_name
581+
# we have a unique name for each dep
582+
name_deps = get(d, name, nothing)::Union{Nothing, Vector{Any}}
583+
if name_deps === nothing || length(name_deps) != 1
584+
error("expected a single entry for $(repr(name)) in $(repr(project_file))")
585+
end
586+
entry = first(name_deps::Vector{Any})::Dict{String, Any}
587+
uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
588+
uuid === nothing && return nothing
589+
return PkgId(UUID(uuid), name)
590+
end
591+
break # TODO: it seems wrong that we use all of break, continue, and return nothing to handle the failure cases here
511592
else
512593
deps = deps::Dict{String, Any}
513594
for (dep, uuid) in deps
@@ -521,18 +602,54 @@ function explicit_manifest_deps_get(project_file::String, where::UUID, name::Str
521602
end
522603
end
523604
found_where || return nothing
524-
found_name || return PkgId(name)
525-
# Only reach here if deps was not a dict which mean we have a unique name for the dep
526-
name_deps = get(d, name, nothing)::Union{Nothing, Vector{Any}}
527-
if name_deps === nothing || length(name_deps) != 1
528-
error("expected a single entry for $(repr(name)) in $(repr(project_file))")
605+
return PkgId(name)
606+
end
607+
608+
# find `where` stanzas
609+
function explicit_manifest_deps_list(project_file::String, where::PkgId)::Union{Nothing,Vector{PkgId}}
610+
manifest_file = project_file_manifest_path(project_file)
611+
manifest_file === nothing && return nothing # manifest not found--keep searching LOAD_PATH
612+
d = parsed_toml(manifest_file)
613+
found_where = false
614+
entries = get(d, where.name, nothing)::Union{Vector{Any}, Nothing}
615+
if entries !== nothing
616+
for entry in entries
617+
entry::Dict{String, Any}
618+
uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
619+
uuid === nothing && continue
620+
if UUID(uuid) === where.uuid
621+
found_where = true
622+
# deps is either a list of names (deps = ["DepA", "DepB"]) or
623+
# a table of entries (deps = {"DepA" = "6ea...", "DepB" = "55d..."}
624+
deps = get(entry, "deps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing}
625+
deps === nothing && continue
626+
list = [where]
627+
if deps isa Vector{String}
628+
for name in deps
629+
# we have a unique name for each dep
630+
name_deps = get(d, name, nothing)::Union{Nothing, Vector{Any}}
631+
if name_deps === nothing || length(name_deps) != 1
632+
error("expected a single entry for $(repr(name)) in $(repr(project_file))")
633+
end
634+
entry = first(name_deps::Vector{Any})::Dict{String, Any}
635+
uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
636+
uuid === nothing && continue
637+
push!(list, PkgId(UUID(uuid), name))
638+
end
639+
else
640+
for (dep, uuid) in deps::Dict{String, Any}
641+
push!(list, PkgId(UUID(uuid::String), dep))
642+
end
643+
end
644+
return list
645+
end
646+
end
529647
end
530-
entry = first(name_deps::Vector{Any})::Dict{String, Any}
531-
uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
532-
uuid === nothing && return nothing
533-
return PkgId(UUID(uuid), name)
648+
found_where || return nothing
649+
return [where]
534650
end
535651

652+
536653
# find `uuid` stanza, return the corresponding path
537654
function explicit_manifest_uuid_path(project_file::String, pkg::PkgId)::Union{Nothing,String}
538655
manifest_file = project_file_manifest_path(project_file)
@@ -587,6 +704,40 @@ function implicit_project_deps_get(dir::String, name::String)::Union{Nothing,Pkg
587704
return proj
588705
end
589706

707+
# look for entry points accessible to names in a top-level package (no environment)
708+
# from an implicit environment (e.g. stdlib)
709+
function implicit_project_deps_list(dir::String)::Vector{PkgId}
710+
list = PkgId[]
711+
function add_path(name, project_file::Nothing)
712+
push!(list, PkgId(name))
713+
nothing
714+
end
715+
function add_path(name, project_file)
716+
id = project_file_name_uuid(project_file, name, cache)
717+
if id.name == name
718+
push!(list, id)
719+
end
720+
nothing
721+
end
722+
for fname in readdir(dir)
723+
fpath = joinpath(dir, fname)
724+
s = stat(fpath)
725+
isjl = endswith(fname, ".jl")
726+
name = isjl ? fname[1:prevind(fname, end - 2)] : fname
727+
if isjl && isfile(s)
728+
add_path(name, nothing)
729+
elseif isdir(s)
730+
if isjl
731+
path, project_file = entry_point_and_project_file_inside(fpath, name)
732+
path === nothing || add_path(name, project_file)
733+
end
734+
path, project_file = entry_point_and_project_file_inside(fpath, fname)
735+
path === nothing || add_path(fname, project_file)
736+
end
737+
end
738+
return list
739+
end
740+
590741
# look for an entry-point for `name`, check that UUID matches
591742
# if there's a project file, look up `name` in its deps and return that
592743
# otherwise return `nothing` to indicate the caller should keep searching
@@ -601,6 +752,18 @@ function implicit_manifest_deps_get(dir::String, where::PkgId, name::String)::Un
601752
return PkgId(pkg_uuid, name)
602753
end
603754

755+
# look for entry-point for pkg names, if if there's a project file,
756+
# otherwise return `nothing` to indicate the caller should keep searching
757+
function implicit_manifest_deps_list(dir::String, where::PkgId)::Union{Nothing,Vector{PkgId}}
758+
@assert where.uuid !== nothing
759+
project_file = entry_point_and_project_file(dir, where.name)[2]
760+
project_file === nothing && return nothing # a project file is mandatory for a package with a uuid
761+
proj = project_file_name_uuid(project_file, where.name)
762+
proj == where || return nothing # verify that this is the correct project file
763+
# this is the correct project, so stop searching here
764+
return explicit_project_deps_list(project_file)
765+
end
766+
604767
# look for an entry-point for `pkg` and return its path if UUID matches
605768
function implicit_manifest_uuid_path(dir::String, pkg::PkgId)::Union{Nothing,String}
606769
path, project_file = entry_point_and_project_file(dir, pkg.name)
@@ -1792,6 +1955,44 @@ function stale_cachefile(modpath::String, cachefile::String)
17921955
end
17931956
end
17941957

1958+
1959+
# starting with a particular `pkg` as the root, list all packages that can possibly be loaded
1960+
function identify_all_deps(pkg::PkgId)
1961+
lists = Dict{PkgId,Vector{PkgId}}()
1962+
q = [pkg]
1963+
while !isempty(q)
1964+
pkg = pop!(q)
1965+
get!(lists, pkg) do
1966+
deps = identify_package_deps(pkg)
1967+
append!(q, deps)
1968+
deps
1969+
end
1970+
end
1971+
return lists
1972+
end
1973+
1974+
# map locate_package across a list of pkgs
1975+
function locate_all_packages(pkgs)
1976+
pths = Dict{PkgId,String}()
1977+
for pkg in pkgs
1978+
pth = locate_package(pkg)
1979+
pth === nothing || (pths[pkg] = pth)
1980+
end
1981+
return pths
1982+
end
1983+
1984+
# get a tree of the information for all loadable packages starting from a
1985+
# particular `pkg` as the root
1986+
function find_all_packages(pkg::PkgId)
1987+
deps = identify_all_deps(pkg)
1988+
return locate_all_packages(keys(deps)) => deps
1989+
end
1990+
find_all_packages(name::String) =
1991+
find_all_packages(PkgId(""), name) # `where` without a uuid will be ignored
1992+
find_all_packages(where::Union{Module,PkgId}, name::String) =
1993+
find_all_packages(something(identify_package(where, name)))
1994+
1995+
17951996
"""
17961997
@__FILE__ -> AbstractString
17971998

0 commit comments

Comments
 (0)