diff --git a/stdlib/Pkg/src/REPLMode.jl b/stdlib/Pkg/src/REPLMode.jl index bac307e7283268..b3ba754d27598b 100644 --- a/stdlib/Pkg/src/REPLMode.jl +++ b/stdlib/Pkg/src/REPLMode.jl @@ -899,7 +899,7 @@ function complete_remote_package(s, i1, i2) cmp = String[] julia_version = VERSION for reg in Types.registries(;clone_default=false) - data = TOML.parsefile(joinpath(reg, "Registry.toml")) + data = Types.read_registry(joinpath(reg, "Registry.toml")) for (uuid, pkginfo) in data["packages"] name = pkginfo["name"] if startswith(name, s) diff --git a/stdlib/Pkg/src/Types.jl b/stdlib/Pkg/src/Types.jl index 9ab3e123bdffc4..33abc972317929 100644 --- a/stdlib/Pkg/src/Types.jl +++ b/stdlib/Pkg/src/Types.jl @@ -216,6 +216,29 @@ end # trynames global manifestfile_path = trynames(Base.manifest_names) end # let +function find_project_file(env::Union{Nothing,String}=nothing) + project_file = nothing + if env isa Nothing + project_file = Base.active_project() + project_file == nothing && error("no active project") + elseif startswith(env, '@') + project_file = Base.load_path_expand(env) + project_file === nothing && error("package environment does not exist: $env") + elseif env isa String + if isdir(env) + isempty(readdir(env)) || error("environment is a package directory: $env") + project_file = joinpath(env, Base.project_names[end]) + else + project_file = endswith(env, ".toml") ? abspath(env) : + abspath(env, Base.project_names[end]) + end + end + @assert project_file isa String && + (isfile(project_file) || !ispath(project_file) || + isdir(project_file) && isempty(readdir(project_file))) + return project_file +end + mutable struct EnvCache # environment info: env::Union{Nothing,String} @@ -235,60 +258,46 @@ mutable struct EnvCache # registered package info: uuids::Dict{String,Vector{UUID}} paths::Dict{UUID,Vector{String}} + names::Dict{UUID,Vector{String}} +end - function EnvCache(env::Union{Nothing,String}=nothing) - if env isa Nothing - project_file = Base.active_project() - project_file == nothing && error("no active project") - elseif startswith(env, '@') - project_file = Base.load_path_expand(env) - project_file === nothing && error("package environment does not exist: $env") - elseif env isa String - if isdir(env) - isempty(readdir(env)) || error("environment is a package directory: $env") - project_file = joinpath(env, Base.project_names[end]) - else - project_file = endswith(env, ".toml") ? abspath(env) : - abspath(env, Base.project_names[end]) - end - end - @assert project_file isa String && - (isfile(project_file) || !ispath(project_file) || - isdir(project_file) && isempty(readdir(project_file))) - project_dir = dirname(project_file) - git = ispath(joinpath(project_dir, ".git")) ? project_dir : nothing - - project = read_project(project_file) - if any(haskey.((project,), ["name", "uuid", "version"])) - project_package = PackageSpec( - get(project, "name", ""), - UUID(get(project, "uuid", 0)), - VersionNumber(get(project, "version", "0.0")), - ) - else - project_package = nothing - end - # determine manifest_file name - dir = abspath(dirname(project_file)) - manifest_file = haskey(project, "manifest") ? - abspath(project["manifest"]) : - manifestfile_path(dir) - # use default name if still not determined - (manifest_file === nothing) && (manifest_file = joinpath(dir, "Manifest.toml")) - write_env_usage(manifest_file) - manifest = read_manifest(manifest_file) - uuids = Dict{String,Vector{UUID}}() - paths = Dict{UUID,Vector{String}}() - return new(env, - git, - project_file, - manifest_file, - project_package, - project, - manifest, - uuids, - paths,) +function EnvCache(env::Union{Nothing,String}=nothing) + project_file = find_project_file(env) + project_dir = dirname(project_file) + git = ispath(joinpath(project_dir, ".git")) ? project_dir : nothing + + project = read_project(project_file) + if any(haskey.((project,), ["name", "uuid", "version"])) + project_package = PackageSpec( + get(project, "name", ""), + UUID(get(project, "uuid", 0)), + VersionNumber(get(project, "version", "0.0")), + ) + else + project_package = nothing end + # determine manifest_file name + dir = abspath(dirname(project_file)) + manifest_file = haskey(project, "manifest") ? + abspath(project["manifest"]) : + manifestfile_path(dir) + # use default name if still not determined + (manifest_file === nothing) && (manifest_file = joinpath(dir, "Manifest.toml")) + write_env_usage(manifest_file) + manifest = read_manifest(manifest_file) + uuids = Dict{String,Vector{UUID}}() + paths = Dict{UUID,Vector{String}}() + names = Dict{UUID,Vector{String}}() + return EnvCache(env, + git, + project_file, + manifest_file, + project_package, + project, + manifest, + uuids, + paths, + names,) end collides_with_project(env::EnvCache, pkg::PackageSpec) = @@ -845,14 +854,20 @@ function registries(; clone_default=true)::Vector{String} return [r for d in depots() for r in registries(d)] end -const line_re = r""" - ^ \s* - ([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}) - \s* = \s* \{ - \s* name \s* = \s* "([^"]*)" \s*, - \s* path \s* = \s* "([^"]*)" \s*,? - \s* \} \s* $ -"""x +# path -> (mtime, TOML Dict) +const REGISTRY_CACHE = Dict{String, Tuple{Float64, Dict{String, Any}}}() + +function read_registry(reg_file) + t = mtime(reg_file) + if haskey(REGISTRY_CACHE, reg_file) + prev_t, registry = REGISTRY_CACHE[reg_file] + t == prev_t && return registry + end + registry = TOML.parsefile(reg_file) + REGISTRY_CACHE[reg_file] = (t, registry) + return registry +end + # Lookup package names & uuids in a single pass through registries function find_registered!(env::EnvCache, @@ -886,62 +901,26 @@ function find_registered!(env::EnvCache, end # if there's still nothing to look for, return early isempty(names) && isempty(uuids) && return - - # build regexs for names and uuids - uuid_re = sprint() do io - if !isempty(uuids) - print(io, raw"^( ") - for (i, uuid) in enumerate(uuids) - 1 < i && print(io, " | ") - print(io, raw"\Q", uuid, raw"\E") - end - print(io, raw" )\b") - end - end - name_re = sprint() do io - if !isempty(names) - print(io, raw"\bname \s* = \s* \"( ") - for (i, name) in enumerate(names) - 1 < i && print(io, " | ") - print(io, raw"\Q", name, raw"\E") - end - print(io, raw" )\"") - end - end - regex = if !isempty(uuids) && !isempty(names) - Regex("( $uuid_re | $name_re )", "x") - elseif !isempty(uuids) - Regex(uuid_re, "x") - elseif !isempty(names) - Regex(name_re, "x") - else - error("this should not happen") - end - # initialize env entries for names and uuids for name in names; env.uuids[name] = UUID[]; end for uuid in uuids; env.paths[uuid] = String[]; end - # note: empty vectors will be left for names & uuids that aren't found + for uuid in uuids; env.names[uuid] = String[]; end - # search through all registries + # note: empty vectors will be left for names & uuids that aren't found for registry in registries() - open(joinpath(registry, "Registry.toml")) do io - # skip forward until [packages] section - for line in eachline(io) - occursin(r"^ \s* \[ \s* packages \s* \] \s* $"x, line) && break - end - # find lines with uuid or name we're looking for - for line in eachline(io) - occursin(regex, line) || continue - m = match(line_re, line) - m == nothing && - error("misformatted registry.toml package entry: $line") - uuid = UUID(m.captures[1]) - name = Base.unescape_string(m.captures[2]) - path = abspath(registry, Base.unescape_string(m.captures[3])) - push!(get!(env.uuids, name, typeof(uuid)[]), uuid) - push!(get!(env.paths, uuid, typeof(path)[]), path) + data = read_registry(joinpath(registry, "Registry.toml")) + for (_uuid, pkgdata) in data["packages"] + uuid = UUID(_uuid) + name = pkgdata["name"] + path = abspath(registry, pkgdata["path"]) + push!(get!(env.uuids, name, UUID[]), uuid) + push!(get!(env.paths, uuid, String[]), path) + push!(get!(env.names, uuid, String[]), name) end + end + for d in (env.uuids, env.paths, env.names) + for (k, v) in d + unique!(v) end end end @@ -968,7 +947,7 @@ end #Get registered names associated with a package uuid function registered_names(env::EnvCache, uuid::UUID)::Vector{String} find_registered!(env, String[], [uuid]) - String[n for (n, uuids) in env.uuids for u in uuids if u == uuid] + return env.names[uuid] end # Determine a single UUID for a given name, prompting if needed