Skip to content

Commit

Permalink
LOAD_PATH: major simplification
Browse files Browse the repository at this point in the history
This makes LOAD_PATH just a vector of strings again. Some
special syntaxes in JULIA_LOAD_PATH are handled specially:

- split on `:` or `;` (on Windows)
- replace the first empty entry with DEFAULT_LOAD_PATH
- ignore the remaining empty entries
- replace `@` with `current_env()`

Other special syntaxes are left alone and expanded during
load path lookup:

- occurrences of `#` in `@...` entries to version digits
- `@name` is looked up in the depot path
- `@` is replaced with `current_env()`

The last functionality is not accessible via JULIA_LOAD_PATH
in this version since `@` in that is expanded early. This does
allow putting a literal `@` in LOAD_PATH to get late expansion
to `current_env()` instead of early expansion.

Fixes #27411
  • Loading branch information
StefanKarpinski committed Jun 19, 2018
1 parent b2eef2e commit e1b0965
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 142 deletions.
104 changes: 50 additions & 54 deletions base/initdefs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,77 +54,73 @@ function init_depot_path(BINDIR::String = Sys.BINDIR)
end
end

## load-path types ##
## LOAD_PATH ##

abstract type AbstractEnv end
# split on `:` (or `;` on Windows)
# first empty entry is replaced with DEFAULT_LOAD_PATH, the rest are skipped
# entries starting with `@` are named environments:
# - the first three `#`s in a named environment are replaced with version numbers
# - `@stdlib` is a special name for the standard library and expands to its path

struct CurrentEnv <: AbstractEnv
create::Bool
CurrentEnv(; create::Bool=false) = new(create)
end

struct NamedEnv <: AbstractEnv
name::String
create::Bool
NamedEnv(name::String; create::Bool=false) = new(name, create)
end

function show(io::IO, env::CurrentEnv)
print(io, CurrentEnv, "(")
env.create && print(io, "create=true")
print(io, ")")
end
# if you want a current env setup, use direnv and
# have your .envrc do something like this:
#
# export JULIA_LOAD_PATH="$(pwd):$JULIA_LOAD_PATH"
#
# this will inherit an existing JULIA_LOAD_PATH value or if there is none, leave
# a trailing empty entry in JULIA_LOAD_PATH which will be replaced with defaults.

function show(io::IO, env::NamedEnv)
print(io, NamedEnv, "(", repr(env.name))
env.create && print(io, ", create=true")
print(io, ")")
end

function parse_env(env::Union{String,SubString{String}})
isempty(env) && return Any[]
env == "@" && return CurrentEnv()
env == "@!" && return CurrentEnv(create=true)
if env[1] == '@'
create = env[2] == '!'
name = env[2+create:end]
name = replace(name, '#' => VERSION.major, count=1)
name = replace(name, '#' => VERSION.minor, count=1)
name = replace(name, '#' => VERSION.patch, count=1)
return NamedEnv(name, create=create)
end
return env # literal path
end
const DEFAULT_LOAD_PATH = ["@v#.#", "@stdlib"]

"""
LOAD_PATH
An array of paths as strings or custom loader objects for the `require`
function and `using` and `import` statements to consider when loading
code.
An array of paths for `using` and `import` statements to consdier as project
environments or package directories when loading code. See Code Loading.
"""
const LOAD_PATH = Any[]
const LOAD_PATH = copy(DEFAULT_LOAD_PATH)

function current_env(dir::AbstractString = pwd())
# look for project file in current dir and parents
home = homedir()
while true
for proj in project_names
file = joinpath(dir, proj)
isfile_casesensitive(file) && return file
end
# bail at home directory or top of git repo
(dir == home || ispath(joinpath(dir, ".git"))) && break
old, dir = dir, dirname(dir)
dir == old && break
end
end

function parse_load_path(str::String)
envs = Any[split(str, Sys.iswindows() ? ';' : ':');]
for (i, env) in enumerate(envs)
if '|' in env
envs[i] = Any[parse_env(e) for e in split(env, '|')]
envs = String[]
isempty(str) && return envs
first_empty = true
for env in split(str, Sys.iswindows() ? ';' : ':')
if isempty(env)
first_empty && append!(envs, DEFAULT_LOAD_PATH)
first_empty = false
elseif env == "@"
dir = current_env()
dir !== nothing && push!(envs, dir)
else
envs[i] = parse_env(env)
push!(envs, env)
end
end
return envs
end

const default_named = parse_load_path("@v#.#.#|@v#.#|@v#|@default|@!v#.#")

function init_load_path(BINDIR::String = Sys.BINDIR)
vers = "v$(VERSION.major).$(VERSION.minor)"
stdlib = abspath(BINDIR, "..", "share", "julia", "stdlib", vers)
load_path = Base.creating_sysimg ? Any[stdlib] :
haskey(ENV, "JULIA_LOAD_PATH") ? parse_load_path(ENV["JULIA_LOAD_PATH"]) :
Any[CurrentEnv(); default_named; stdlib]
if Base.creating_sysimg
load_path = ["@stdlib"]
elseif haskey(ENV, "JULIA_LOAD_PATH")
load_path = parse_load_path(ENV["JULIA_LOAD_PATH"])
else
load_path = DEFAULT_LOAD_PATH
end
append!(empty!(LOAD_PATH), load_path)
end

Expand Down
68 changes: 27 additions & 41 deletions base/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -147,58 +147,44 @@ end

## load path expansion: turn LOAD_PATH entries into concrete paths ##

function find_env(envs::Vector)
for env in envs
path = find_env(env)
path != nothing && return path
function load_path_expand(env::AbstractString)::Union{String, Nothing}
# named environment?
if startswith(env, '@')
# `@` in JULIA_LOAD_PATH is expanded early (at startup time)
# if you put a `@` in LOAD_PATH manually, it's expanded late
env == "@" && return current_env()
env == "@stdlib" && return Sys.STDLIB
env = replace(env, '#' => VERSION.major, count=1)
env = replace(env, '#' => VERSION.minor, count=1)
env = replace(env, '#' => VERSION.patch, count=1)
name = env[2:end]
# look for named env in each depot
for depot in DEPOT_PATH
path = joinpath(depot, "environments", name)
isdir(path) || continue
for proj in project_names
file = abspath(path, proj)
isfile_casesensitive(file) && return file
end
return path
end
isempty(DEPOT_PATH) && return nothing
return abspath(DEPOT_PATH[1], "environments", name, project_names[end])
end
end

function find_env(env::AbstractString)
# otherwise, it's a path
path = abspath(env)
if isdir(path)
# directory with a project file?
for name in project_names
file = abspath(path, name)
for proj in project_names
file = joinpath(path, proj)
isfile_casesensitive(file) && return file
end
end
# package dir or path to project file
return path
end

function find_env(env::NamedEnv)
# look for named env in each depot
for depot in DEPOT_PATH
isdir(depot) || continue
file = nothing
for name in project_names
file = abspath(depot, "environments", env.name, name)
isfile_casesensitive(file) && return file
end
file != nothing && env.create && return file
end
end

function find_env(env::CurrentEnv, dir::AbstractString = pwd())
# look for project file in current dir and parents
home = homedir()
while true
for name in project_names
file = joinpath(dir, name)
isfile_casesensitive(file) && return file
end
# bail at home directory or top of git repo
(dir == home || ispath(joinpath(dir, ".git"))) && break
old, dir = dir, dirname(dir)
dir == old && break
end
env.create ? joinpath(pwd(), project_names[end]) : nothing
end

find_env(env::Function) = find_env(env())

load_path() = String[env for env in map(find_env, LOAD_PATH) if env nothing]
load_path() = String[env for env in map(load_path_expand, LOAD_PATH) if env nothing]

## package identification: determine unique identity of package to be loaded ##

Expand Down
15 changes: 0 additions & 15 deletions base/precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,12 @@ end
precompile(Tuple{Type{Array{Base.StackTraces.StackFrame, 1}}, UndefInitializer, Int64})
precompile(Tuple{Type{Array{Union{Nothing, String}, 1}}, UndefInitializer, Int64})
precompile(Tuple{Type{Base.CoreLogging.LogState}, Logging.ConsoleLogger})
precompile(Tuple{Type{Base.CurrentEnv}})
precompile(Tuple{Type{Base.GC_Diff}, Base.GC_Num, Base.GC_Num})
precompile(Tuple{Type{Base.Generator{I, F} where F where I}, typeof(Base.names), Array{Any, 1}})
precompile(Tuple{Type{Base.IOContext{IO_t} where IO_t<:IO}, Base.GenericIOBuffer{Array{UInt8, 1}}, Base.IOStream})
precompile(Tuple{Type{Base.IOContext{IO_t} where IO_t<:IO}, REPL.Terminals.TTYTerminal, Base.Pair{Symbol, Bool}})
precompile(Tuple{Type{Base.MIME{Symbol("text/plain")}}})
precompile(Tuple{Type{Base.Multimedia.TextDisplay}, Base.TTY})
precompile(Tuple{Type{Base.NamedEnv}, String})
precompile(Tuple{Type{Base.Order.Perm{O, V} where V<:(AbstractArray{T, 1} where T) where O<:Base.Order.Ordering}, Base.Order.ForwardOrdering, Array{Tuple{Float64, Int64}, 1}})
precompile(Tuple{Type{Base.Pair{A, B} where B where A}, Base.PkgId, UInt64})
precompile(Tuple{Type{Base.Pair{A, B} where B where A}, Int64, Int64})
Expand Down Expand Up @@ -67,7 +65,6 @@ precompile(Tuple{getfield(Base.Cartesian, Symbol("#@ncall")), LineNumberNode, Mo
precompile(Tuple{getfield(Base.Cartesian, Symbol("#@nexprs")), LineNumberNode, Module, Int64, Expr})
precompile(Tuple{getfield(Base.Meta, Symbol("#kw##parse")), NamedTuple{(:raise, :depwarn), Tuple{Bool, Bool}}, typeof(Base.Meta.parse), String, Int64})
precompile(Tuple{getfield(Core, Symbol("#@doc")), LineNumberNode, Module, Symbol})
precompile(Tuple{getfield(Core, Symbol("#kw#Type")), NamedTuple{(:create,), Tuple{Bool}}, Type{Base.NamedEnv}, String})
precompile(Tuple{getfield(Core, Symbol("#kw#Type")), NamedTuple{(:prompt_prefix, :prompt_suffix, :complete, :sticky), Tuple{String, String, Pkg.REPLMode.PkgCompletionProvider, Bool}}, Type{REPL.LineEdit.Prompt}, typeof(Pkg.REPLMode.promptf)})
precompile(Tuple{getfield(Core, Symbol("#kw#Type")), NamedTuple{(:prompt_prefix, :prompt_suffix, :repl, :complete, :on_enter), Tuple{String, typeof(Base.input_color), REPL.LineEditREPL, REPL.REPLCompletionProvider, typeof(REPL.return_callback)}}, Type{REPL.LineEdit.Prompt}, String})
precompile(Tuple{getfield(REPL, Symbol("#@repl")), LineNumberNode, Module, Base.TTY, Symbol})
Expand Down Expand Up @@ -136,10 +133,8 @@ precompile(Tuple{typeof(Base.bytesavailable), Base.GenericIOBuffer{Array{UInt8,
precompile(Tuple{typeof(Base.check_open), Base.TTY})
precompile(Tuple{typeof(Base.close), Base.Pipe})
precompile(Tuple{typeof(Base.collect_similar), Array{Any, 1}, Base.Generator{Array{Any, 1}, typeof(Base.names)}})
precompile(Tuple{typeof(Base.collect_to!), Array{Union{Nothing, String}, 1}, Base.Generator{Array{Any, 1}, typeof(Base.find_env)}, Int64, Int64})
precompile(Tuple{typeof(Base.collect_to_with_first!), Array{Array{Base.StackTraces.StackFrame, 1}, 1}, Array{Base.StackTraces.StackFrame, 1}, Base.Generator{Array{Union{Ptr{Nothing}, Base.InterpreterIP}, 1}, typeof(Base.StackTraces.lookup)}, Int64})
precompile(Tuple{typeof(Base.collect_to_with_first!), Array{Markdown.MD, 1}, Markdown.MD, Base.Generator{Array{Base.Docs.DocStr, 1}, typeof(Base.Docs.parsedoc)}, Int64})
precompile(Tuple{typeof(Base.collect_to_with_first!), Array{Nothing, 1}, Nothing, Base.Generator{Array{Any, 1}, typeof(Base.find_env)}, Int64})
precompile(Tuple{typeof(Base.collect_to_with_first!), Array{String, 1}, String, Base.Generator{Array{Any, 1}, typeof(Base.string)}, Int64})
precompile(Tuple{typeof(Base.convert), Type{Array{String, 1}}, Array{Any, 1}})
precompile(Tuple{typeof(Base.convert), Type{Base.Dict{Char, V} where V}, Base.Dict{Char, Any}})
Expand Down Expand Up @@ -168,10 +163,6 @@ precompile(Tuple{typeof(Base.divrem), Int64, Int64})
precompile(Tuple{typeof(Base.empty!), Array{Base.Pair{Base.PkgId, UInt64}, 1}})
precompile(Tuple{typeof(Base.eof), Base.PipeEndpoint})
precompile(Tuple{typeof(Base.eof), Base.TTY})
precompile(Tuple{typeof(Base.find_env), Array{Any, 1}})
precompile(Tuple{typeof(Base.find_env), Base.CurrentEnv})
precompile(Tuple{typeof(Base.find_env), Base.NamedEnv})
precompile(Tuple{typeof(Base.find_env), typeof(OldPkg.dir)})
precompile(Tuple{typeof(Base.findlast), String, String})
precompile(Tuple{typeof(Base.findprev), String, String, Int64})
precompile(Tuple{typeof(Base.first), Base.OneTo{Int64}})
Expand All @@ -191,8 +182,6 @@ precompile(Tuple{typeof(Base.getindex), Tuple{Int64, Int64}, Int64})
precompile(Tuple{typeof(Base.getindex), Tuple{Symbol, Symbol, Symbol}, Base.UnitRange{Int64}})
precompile(Tuple{typeof(Base.getindex), Tuple{Symbol}, Base.UnitRange{Int64}})
precompile(Tuple{typeof(Base.getindex), Tuple{Symbol}, Int64})
precompile(Tuple{typeof(Base.getindex), Type{Any}, Base.CurrentEnv, Array{Any, 1}, String, typeof(OldPkg.dir)})
precompile(Tuple{typeof(Base.getindex), Type{Any}, Base.NamedEnv, Base.NamedEnv, Base.NamedEnv, Base.NamedEnv, Base.NamedEnv})
precompile(Tuple{typeof(Base.getproperty), Base.CoreLogging.LogState, Symbol})
precompile(Tuple{typeof(Base.getproperty), Base.GC_Diff, Symbol})
precompile(Tuple{typeof(Base.getproperty), Base.GenericIOBuffer{Array{UInt8, 1}}, Symbol})
Expand Down Expand Up @@ -294,7 +283,6 @@ precompile(Tuple{typeof(Base.print), Base.IOContext{Base.GenericIOBuffer{Array{U
precompile(Tuple{typeof(Base.print), Base.IOContext{Base.GenericIOBuffer{Array{UInt8, 1}}}, Module, String, Symbol})
precompile(Tuple{typeof(Base.print), Base.IOContext{Base.GenericIOBuffer{Array{UInt8, 1}}}, String})
precompile(Tuple{typeof(Base.print), Base.IOContext{Base.GenericIOBuffer{Array{UInt8, 1}}}, Symbol})
precompile(Tuple{typeof(Base.print), Base.IOContext{Base.GenericIOBuffer{Array{UInt8, 1}}}, Type{Base.CurrentEnv}})
precompile(Tuple{typeof(Base.print), Base.IOContext{REPL.Terminals.TTYTerminal}, Char, String, String})
precompile(Tuple{typeof(Base.print), Base.IOContext{REPL.Terminals.TTYTerminal}, String, Type{Module}})
precompile(Tuple{typeof(Base.print), Base.IOContext{REPL.Terminals.TTYTerminal}, String})
Expand Down Expand Up @@ -370,11 +358,8 @@ precompile(Tuple{typeof(Base.show), Base.GenericIOBuffer{Array{UInt8, 1}}, Strin
precompile(Tuple{typeof(Base.show), Base.GenericIOBuffer{Array{UInt8, 1}}, Type{Any}})
precompile(Tuple{typeof(Base.show), Base.GenericIOBuffer{Array{UInt8, 1}}, UInt64})
precompile(Tuple{typeof(Base.show), Base.IOContext{Base.GenericIOBuffer{Array{UInt8, 1}}}, Array{Any, 1}})
precompile(Tuple{typeof(Base.show), Base.IOContext{Base.GenericIOBuffer{Array{UInt8, 1}}}, Base.CurrentEnv})
precompile(Tuple{typeof(Base.show), Base.IOContext{Base.GenericIOBuffer{Array{UInt8, 1}}}, Base.NamedEnv})
precompile(Tuple{typeof(Base.show), Base.IOContext{Base.GenericIOBuffer{Array{UInt8, 1}}}, Int64})
precompile(Tuple{typeof(Base.show), Base.IOContext{Base.GenericIOBuffer{Array{UInt8, 1}}}, String})
precompile(Tuple{typeof(Base.show), Base.IOContext{Base.GenericIOBuffer{Array{UInt8, 1}}}, Type{Base.CurrentEnv}})
precompile(Tuple{typeof(Base.show), Base.IOContext{Base.GenericIOBuffer{Array{UInt8, 1}}}, typeof(OldPkg.dir)})
precompile(Tuple{typeof(Base.show), Base.IOContext{REPL.Terminals.TTYTerminal}, Base.MIME{Symbol("text/plain")}, Array{Float64, 1}})
precompile(Tuple{typeof(Base.show), Base.IOContext{REPL.Terminals.TTYTerminal}, Base.MIME{Symbol("text/plain")}, Array{Float64, 2}})
Expand Down
2 changes: 1 addition & 1 deletion base/sysinfo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ A string containing the full path to the directory containing the `julia` execut
A string containing the full path to the directory containing the `stdlib` packages.
"""
:STDLIB
STDLIB = "$BINDIR/../../stdlib" # for bootstrap

# helper to avoid triggering precompile warnings

Expand Down
2 changes: 1 addition & 1 deletion stdlib/OldPkg/test/pkg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ temp_pkg_dir() do

OldPkg.add(package)
msg = read(ignorestatus(`$(Base.julia_cmd()) --startup-file=no -e
"import OldPkg; push!(LOAD_PATH, OldPkg.dir); redirect_stderr(stdout); using Logging; global_logger(SimpleLogger(stdout)); using Example; OldPkg.update(\"$package\")"`), String)
"import OldPkg; push!(LOAD_PATH, OldPkg.dir()); redirect_stderr(stdout); using Logging; global_logger(SimpleLogger(stdout)); using Example; OldPkg.update(\"$package\")"`), String)
@test occursin(Regex("- $package.*Restart Julia to use the updated versions","s"), msg)
end

Expand Down
14 changes: 7 additions & 7 deletions stdlib/Pkg/src/Types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ using ..TOML
import ..Pkg
import Pkg: GitTools, depots, logdir

import Base: SHA1, AbstractEnv
import Base: SHA1
using SHA

export UUID, pkgID, SHA1, VersionRange, VersionSpec, empty_versionspec,
Expand Down Expand Up @@ -207,7 +207,7 @@ const default_envs = [

mutable struct EnvCache
# environment info:
env::Union{Nothing,String,AbstractEnv}
env::Union{Nothing,String}
git::Union{Nothing,LibGit2.GitRepo}

# paths for files:
Expand All @@ -225,26 +225,26 @@ mutable struct EnvCache
uuids::Dict{String,Vector{UUID}}
paths::Dict{UUID,Vector{String}}

function EnvCache(env::Union{Nothing,String,AbstractEnv}=nothing)
function EnvCache(env::Union{Nothing,String}=nothing)
if env isa Nothing
project_file = nothing
for entry in LOAD_PATH
project_file = Base.find_env(entry)
project_file = Base.load_path_expand(entry)
project_file isa String && !isdir(project_file) && break
project_file = nothing
end
if project_file == nothing
project_dir = nothing
for entry in LOAD_PATH
project_dir = Base.find_env(entry)
project_dir = Base.load_path_expand(entry)
project_dir isa String && isdir(project_dir) && break
project_dir = nothing
end
project_dir == nothing && error("No Pkg environment found in LOAD_PATH")
project_file = joinpath(project_dir, Base.project_names[end])
end
elseif env isa AbstractEnv
project_file = Base.find_env(env)
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)
Expand Down
4 changes: 2 additions & 2 deletions stdlib/Pkg/test/pkg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ temp_pkg_dir() do project_path
cp(joinpath(@__DIR__, "test_packages", "UnregisteredWithProject"), joinpath(dir, "UnregisteredWithProject"))
cd(joinpath(dir, "UnregisteredWithProject")) do
try
pushfirst!(LOAD_PATH, Base.parse_load_path("@"))
pushfirst!(LOAD_PATH, Base.current_env())
Pkg.up()
@test haskey(Pkg.installed(), "Example")
finally
Expand Down Expand Up @@ -278,7 +278,7 @@ temp_pkg_dir() do project_path
cp(joinpath(@__DIR__, "test_packages", "UnregisteredWithProject"), joinpath(dir, "UnregisteredWithProject"))
cd(joinpath(dir, "UnregisteredWithProject")) do
try
pushfirst!(LOAD_PATH, Base.parse_load_path("@"))
pushfirst!(LOAD_PATH, Base.current_env())
Pkg.add("Test") # test https://github.com/JuliaLang/Pkg.jl/issues/324
Pkg.test()
finally
Expand Down
Loading

0 comments on commit e1b0965

Please sign in to comment.