diff --git a/base/initdefs.jl b/base/initdefs.jl index d17c7e9e1e02b..d715030c904d6 100644 --- a/base/initdefs.jl +++ b/base/initdefs.jl @@ -302,15 +302,15 @@ function load_path_expand(env::AbstractString)::Union{String, Nothing} path = joinpath(depot, "environments", name) isdir(path) || continue for proj in project_names - file = abspath(path, proj) + file = joinpath(path, proj) isfile_casesensitive(file) && return file end end isempty(DEPOT_PATH) && return nothing - return abspath(DEPOT_PATH[1], "environments", name, project_names[end]) + return abspath(DEPOT_PATH[1], "environments", name, project_names[end]; safe=true) end # otherwise, it's a path - path = abspath(env) + path = abspath(env; safe=true) if isdir(path) # directory with a project file? for proj in project_names @@ -337,7 +337,7 @@ function active_project(search_load_path::Bool=true) # there are backedges here from abspath(::AbstractString, ::String) project = project::String if !isfile_casesensitive(project) && basename(project) ∉ project_names - project = abspath(project, "Project.toml") + project = abspath(project, "Project.toml"; safe=true) end return project end diff --git a/base/loading.jl b/base/loading.jl index 28f3c510e920e..9b4c5f8c689bd 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -964,7 +964,7 @@ function project_file_manifest_path(project_file::String)::Union{Nothing,String} manifest_path = get(cache.project_file_manifest_path, project_file, missing) manifest_path === missing || return manifest_path end - dir = abspath(dirname(project_file)) + dir = abspath(dirname(project_file); safe=true) isfile_casesensitive(project_file) || return nothing d = parsed_toml(project_file) base_manifest = workspace_manifest(project_file) @@ -974,7 +974,7 @@ function project_file_manifest_path(project_file::String)::Union{Nothing,String} explicit_manifest = get(d, "manifest", nothing)::Union{String, Nothing} manifest_path = nothing if explicit_manifest !== nothing - manifest_file = normpath(joinpath(dir, explicit_manifest)) + manifest_file = joinpath(dir, explicit_manifest) if isfile_casesensitive(manifest_file) manifest_path = manifest_file end @@ -998,10 +998,10 @@ end # given a directory (implicit env from LOAD_PATH) and a name, # check if it is an implicit package function entry_point_and_project_file_inside(dir::String, name::String)::Union{Tuple{Nothing,Nothing},Tuple{String,Nothing},Tuple{String,String}} - path = normpath(joinpath(dir, "src", "$name.jl")) + path = joinpath(dir, "src", "$name.jl") isfile_casesensitive(path) || return nothing, nothing for proj in project_names - project_file = normpath(joinpath(dir, proj)) + project_file = joinpath(dir, proj) isfile_casesensitive(project_file) || continue return path, project_file end @@ -1018,7 +1018,7 @@ function entry_point_and_project_file(dir::String, name::String)::Union{Tuple{No path, project_file = entry_point_and_project_file_inside(dir_jl, name) path === nothing || return path, project_file # check for less likely case with a bare file and no src directory last to minimize stat calls - path = normpath(joinpath(dir, "$name.jl")) + path = joinpath(dir, "$name.jl") isfile_casesensitive(path) && return path, nothing return nothing, nothing end @@ -1038,9 +1038,9 @@ end # given a path, name, and possibly an entryfile, return the entry point function entry_path(path::String, name::String, entryfile::Union{Nothing,String})::String - isfile_casesensitive(path) && return normpath(path) + isfile_casesensitive(path) && return path entrypoint = entryfile === nothing ? joinpath("src", "$name.jl") : entryfile - return normpath(joinpath(path, entrypoint)) + return joinpath(path, entrypoint) end ## explicit project & manifest API ## @@ -1204,7 +1204,7 @@ function explicit_manifest_uuid_load_spec(project_file::String, pkg::PkgId)::Uni error("failed to find source of parent package: \"$name\"") end parent_path = parent_load_spec.path - p = normpath(dirname(parent_path), "..") + p = joinpath(dirname(parent_path), "..") return PkgLoadSpec(find_ext_path(p, pkg.name), parent_load_spec.julia_syntax_version) end end @@ -1228,7 +1228,7 @@ function explicit_manifest_entry_load_spec(manifest_file::String, pkg::PkgId, en path = get(entry, "path", nothing)::Union{Nothing, String} entryfile = get(entry, "entryfile", nothing)::Union{Nothing, String} if path !== nothing - path = entry_path(normpath(abspath(dirname(manifest_file), path)), pkg.name, entryfile) + path = entry_path(joinpath(dirname(manifest_file), path), pkg.name, entryfile) return PkgLoadSpec(path, syntax_version) end hash = get(entry, "git-tree-sha1", nothing)::Union{Nothing, String} @@ -1248,7 +1248,7 @@ function explicit_manifest_entry_load_spec(manifest_file::String, pkg::PkgId, en for slug in (version_slug(uuid, hash), version_slug(uuid, hash, 4)) for depot in DEPOT_PATH path = joinpath(depot, "packages", pkg.name, slug) - ispath(path) && return PkgLoadSpec(entry_path(abspath(path), pkg.name, entryfile), syntax_version) + ispath(path) && return PkgLoadSpec(entry_path(abspath(path; safe=true), pkg.name, entryfile), syntax_version) end end # no depot contains the package, return missing to stop looking @@ -2394,9 +2394,9 @@ function _include_dependency!(dep_list::Vector{Any}, track_dependencies::Bool, track_content::Bool, path_may_be_dir::Bool) prev = source_path(nothing) if prev === nothing - path = abspath(_path) + path = abspath(_path; safe=true) else - path = normpath(joinpath(dirname(prev), _path)) + path = normpath(joinpath(dirname(prev), _path); safe=true) end if !track_dependencies[] if !path_may_be_dir && !isfile(path) @@ -3003,9 +3003,9 @@ function require_stdlib(package_uuidkey::PkgId, ext::Union{Nothing, String}, fro if from_stdlib # first since this is a stdlib, try to look there directly first if ext === nothing - sourcepath = normpath(env, this_uuidkey.name, "src", this_uuidkey.name * ".jl") + sourcepath = joinpath(env, this_uuidkey.name, "src", this_uuidkey.name * ".jl") else - sourcepath = find_ext_path(normpath(joinpath(env, package_uuidkey.name)), ext) + sourcepath = find_ext_path(joinpath(env, package_uuidkey.name), ext) end set_pkgorigin_version_path(this_uuidkey, sourcepath) newm = _require_search_from_serialized(this_uuidkey, PkgLoadSpec(sourcepath, VERSION), UInt128(0), false; DEPOT_PATH=depot_path) @@ -3191,13 +3191,14 @@ function evalfile(path::AbstractString, args::Vector{String}=String[]) end evalfile(path::AbstractString, args::Vector) = evalfile(path, String[args...]) +# Used in Pkg.jl function load_path_setup_code(load_path::Bool=true) code = """ - append!(empty!(Base.DEPOT_PATH), $(repr(map(abspath, DEPOT_PATH)))) - append!(empty!(Base.DL_LOAD_PATH), $(repr(map(abspath, DL_LOAD_PATH)))) + append!(empty!(Base.DEPOT_PATH), $(repr(map(x -> abspath(x; safe=true), DEPOT_PATH)))) + append!(empty!(Base.DL_LOAD_PATH), $(repr(map(x -> abspath(x; safe=true), DL_LOAD_PATH)))) """ if load_path - load_path = map(abspath, Base.load_path()) + load_path = map(x -> abspath(x; safe=true), Base.load_path()) path_sep = Sys.iswindows() ? ';' : ':' any(path -> path_sep in path, load_path) && error("LOAD_PATH entries cannot contain $(repr(path_sep))") @@ -3283,9 +3284,9 @@ function create_expr_cache(pkg::PkgId, input::PkgLoadSpec, output::String, outpu @nospecialize internal_stderr internal_stdout rm(output, force=true) # Remove file if it exists output_o === nothing || rm(output_o, force=true) - depot_path = String[abspath(x) for x in DEPOT_PATH] - dl_load_path = String[abspath(x) for x in DL_LOAD_PATH] - load_path = String[abspath(x) for x in Base.load_path()] + depot_path = String[abspath(x; safe=true) for x in DEPOT_PATH] + dl_load_path = String[abspath(x; safe=true) for x in DL_LOAD_PATH] + load_path = String[abspath(x; safe=true) for x in Base.load_path()] # if pkg is a stdlib, append its parent Project.toml to the load path triggers = get(EXT_PRIMED, pkg, nothing) if triggers !== nothing @@ -3343,7 +3344,7 @@ function create_expr_cache(pkg::PkgId, input::PkgLoadSpec, output::String, outpu Base.track_nested_precomp($(_pkg_str(vcat(Base.precompilation_stack, pkg)))) Base.loadable_extensions = $(_pkg_str(loadable_exts)) Base.precompiling_extension = $(loading_extension) - Base.include_package_for_output($(_pkg_str(pkg)), $(repr(abspath(input.path))), $(repr(input.julia_syntax_version)), $(repr(depot_path)), $(repr(dl_load_path)), + Base.include_package_for_output($(_pkg_str(pkg)), $(repr(abspath(input.path; safe=true))), $(repr(input.julia_syntax_version)), $(repr(depot_path)), $(repr(dl_load_path)), $(repr(load_path)), $(_pkg_str(concrete_deps)), $(repr(source_path(nothing)))) """) close(io.in) @@ -3372,7 +3373,7 @@ function compilecache_path(pkg::PkgId, prefs_hash::UInt64; flags::CacheFlags=Cac cachepath = joinpath(DEPOT_PATH[1], entrypath) isdir(cachepath) || mkpath(cachepath) if pkg.uuid === nothing - abspath(cachepath, entryfile) * ".ji" + joinpath(cachepath, entryfile) * ".ji" else crc = _crc32c(project) crc = _crc32c(unsafe_string(JLOptions().image_file), crc) @@ -3387,7 +3388,7 @@ function compilecache_path(pkg::PkgId, prefs_hash::UInt64; flags::CacheFlags=Cac crc = _crc32c(prefs_hash, crc) project_precompile_slug = slug(crc, 5) - abspath(cachepath, string(entryfile, "_", project_precompile_slug, ".ji")) + joinpath(cachepath, string(entryfile, "_", project_precompile_slug, ".ji")) end end @@ -3594,9 +3595,26 @@ function CacheHeaderIncludes(dep_tuple::Tuple{Module, String, UInt64, UInt32, Fl return CacheHeaderIncludes(PkgId(dep_tuple[1]), dep_tuple[2:end]..., String[]) end -function replace_depot_path(path::AbstractString, depots::Vector{String}=normalize_depots_for_relocation()) +function replace_depot_path(path::AbstractString, depots::Vector{Union{String, Tuple{String, String}}}=normalize_depots_for_relocation()) + # We must handle several cases: + # 1. The depot itself is in a symlink'ed path + # 2. The files were symlinked into the depot + # 3. A combination of both + this_realpath = Filesystem.realpath_default(path, nothing) + realpath_differs = this_realpath !== path for depot in depots - if startswith(path, string(depot, Filesystem.pathsep())) || path == depot + if isa(depot, Tuple{String, String}) + depotrealpath = depot[2] + depot = depot[1] + else + depotrealpath = depot + end + if this_realpath !== nothing && (startswith(this_realpath, string(depotrealpath, Filesystem.pathsep())) || this_realpath == depotrealpath) + # Simplest case: The file's realpath is within the depot's realpath, simply replace and go on + path = replace(this_realpath, depotrealpath => "@depot"; count=1) + break + elseif realpath_differs && startswith(path, string(depot, Filesystem.pathsep())) || path == depot + # The file's original path was loaded from symlinks within the depot - n.b.: the path may still have `..` components path = replace(path, depot => "@depot"; count=1) break end @@ -3605,14 +3623,20 @@ function replace_depot_path(path::AbstractString, depots::Vector{String}=normali end function normalize_depots_for_relocation() - depots = String[] + depots = Union{String, Tuple{String, String}}[] sizehint!(depots, length(DEPOT_PATH)) for d in DEPOT_PATH isdir(d) || continue if isdirpath(d) d = dirname(d) end - push!(depots, abspath(d)) + depotrealpath = realpath(d) + depotabspath = abspath(d) + if depotrealpath != depotabspath + push!(depots, (depotabspath, depotrealpath)) + else + push!(depots, depotrealpath) + end end return depots end diff --git a/base/path.jl b/base/path.jl index 00dded2bf6841..af728ed436cea 100644 --- a/base/path.jl +++ b/base/path.jl @@ -373,38 +373,55 @@ julia> joinpath(["/home/myuser", "example.jl"]) joinpath """ - normpath(path::AbstractString)::String + normpath(path::AbstractString; safe=false)::String -Normalize a path, removing "." and ".." entries and changing "/" to the canonical path separator -for the system. +Normalize a path, removing "." and changing "/" to the canonical path separator for the system. +If `safe` is false, also removes ".." entries. + +!!! warning + Normalization with `safe==false` may change the meaning of a path if the + directory immediately preceding a ".." entry is a symbolic link. + For example, if `a` is a symbolic link in `a/../b`, then this path will + resolve to `b` in the link target's parent directory. However, + `normpath("a/../b")` will return `b`, which will resolve `b` in the + current working directory. + +!!! compat "Julia 1.14" + The `safe` keyword argument was added in Julia 1.14. # Examples ```jldoctest julia> normpath("/home/myuser/../example.jl") "/home/example.jl" +julia> normpath("/home/myuser/../example.jl"; safe=true) +"/home/myuser/../example.jl" + julia> normpath("Documents/Julia") == joinpath("Documents", "Julia") true ``` """ -function normpath(path::String) +function normpath(path::String; safe=false) isabs = isabspath(path) isdir = isdirpath(path) drive, path = splitdrive(path) parts = split(path, path_separator_re; keepempty=false) filter!(!=("."), parts) - while true - clean = true - for j = 1:length(parts)-1 - if parts[j] != ".." && parts[j+1] == ".." - deleteat!(parts, j:j+1) - clean = false - break + if !safe + while true + clean = true + for j = 1:length(parts)-1 + if parts[j] != ".." && parts[j+1] == ".." + deleteat!(parts, j:j+1) + clean = false + break + end end + clean && break end - clean && break end if isabs + # N.B.: `..` at the beginning of a path may always be removed while !isempty(parts) && parts[1] == ".." popfirst!(parts) end @@ -427,13 +444,16 @@ end Convert a set of paths to a normalized path by joining them together and removing "." and ".." entries. Equivalent to `normpath(joinpath(path, paths...))`. """ -normpath(a::AbstractString, b::AbstractString...) = normpath(joinpath(a,b...)) +normpath(a::AbstractString, b::AbstractString...; kwargs...) = normpath(joinpath(a,b...); kwargs...) """ - abspath(path::AbstractString)::String + abspath(path::AbstractString; safe=false)::String Convert a path to an absolute path by adding the current directory if necessary. -Also normalizes the path as in [`normpath`](@ref). + +The resulting path is normalized as in normpath. If `safe` is false, `..` entries +are also removed, but the resulting path may not refer to the same location. See +[`normpath`](@ref) for details. # Examples @@ -444,8 +464,11 @@ If you are in a directory called `JuliaExample` and the data you are using is tw Which gives a path like `"/home/JuliaUser/data/"`. See also [`joinpath`](@ref), [`pwd`](@ref), [`expanduser`](@ref). + +!!! compat "Julia 1.14" + The `safe` keyword argument was added in Julia 1.14. """ -function abspath(a::String)::String +function abspath(a::String; safe=false)::String if !isabspath(a) cwd = pwd() a_drive, a_nodrive = splitdrive(a) @@ -456,7 +479,7 @@ function abspath(a::String)::String a = joinpath(cwd, a) end end - return normpath(a) + return normpath(a; safe) end """ @@ -465,7 +488,7 @@ end Convert a set of paths to an absolute path by joining them together and adding the current directory if necessary. Equivalent to `abspath(joinpath(path, paths...))`. """ -abspath(a::AbstractString, b::AbstractString...) = abspath(joinpath(a,b...)) +abspath(a::AbstractString, b::AbstractString...; kwargs...) = abspath(joinpath(a,b...); kwargs...) if Sys.iswindows() @@ -513,6 +536,33 @@ function realpath(path::AbstractString) end end +""" + realpath_default(path::AbstractString, default)::String + +Like `realpath(path)`, expect returns `default` instead of throwing an exception +if `path` does not exist in the filesystem. +""" +function realpath_default(path::AbstractString, @nospecialize(default)) + req = Libc.malloc(_sizeof_uv_fs) + try + ret = ccall(:uv_fs_realpath, Cint, + (Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Ptr{Cvoid}), + C_NULL, req, path, C_NULL) + if ret < 0 + uv_fs_req_cleanup(req) + if ret == Base.UV_ENOENT + return default + end + uv_error("realpath_default($(repr(path)))", ret) + end + path = unsafe_string(ccall(:jl_uv_fs_t_ptr, Cstring, (Ptr{Cvoid},), req)) + uv_fs_req_cleanup(req) + return path + finally + Libc.free(req) + end +end + if Sys.iswindows() # on windows, ~ means "temporary file" expanduser(path::AbstractString) = path diff --git a/src/precompile.c b/src/precompile.c index 16cff13c60316..45f4e7fcd73ba 100644 --- a/src/precompile.c +++ b/src/precompile.c @@ -59,10 +59,12 @@ void write_srctext(ios_t *f, jl_array_t *udeps, int64_t srctextpos) { // because these may not be Julia code (and could be huge) JL_TYPECHK(write_srctext, deptuple, deptuple); if (depmod != (jl_value_t*)jl_main_module) { - jl_value_t *abspath = jl_fieldref_noalloc(deptuple, 1); // file abspath - const char *abspathstr = jl_string_data(abspath); - if (!abspathstr[0]) + jl_value_t *abspath_or_uuid = jl_fieldref_noalloc(deptuple, 1); // file abspath + const char *abspathstr = jl_string_data(abspath_or_uuid); + if (!abspathstr[0]) { + // UUID or empty string continue; + } ios_t *srctp = ios_file(&srctext, abspathstr, 1, 0, 0, 0); if (!srctp) { jl_printf(JL_STDERR, "WARNING: could not cache source text for \"%s\".\n", @@ -72,7 +74,7 @@ void write_srctext(ios_t *f, jl_array_t *udeps, int64_t srctextpos) { jl_value_t *replace_depot_args[3]; replace_depot_args[0] = replace_depot_func; - replace_depot_args[1] = abspath; + replace_depot_args[1] = abspath_or_uuid; replace_depot_args[2] = depots; jl_value_t *depalias = (jl_value_t*)jl_apply(replace_depot_args, 3); diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index 16ebcd4b4ee12..e18cab92f8681 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -760,20 +760,22 @@ static int64_t write_dependency_list(ios_t *s, jl_array_t* worklist, jl_array_t for (i = 0; i < l; i++) { jl_value_t *deptuple = jl_array_ptr_ref(udeps, i); JL_TYPECHK(write_dependency_list, deptuple, deptuple); - jl_value_t *deppath = jl_fieldref_noalloc(deptuple, 1); + jl_value_t *deppath_or_uuid = jl_fieldref_noalloc(deptuple, 1); + const char *deppathstr = jl_string_data(deppath_or_uuid); - if (replace_depot_func) { + if (replace_depot_func && deppathstr[0]) { + // deppathstr[0] == '\0' indicates a binpack'ed uuid, not a path jl_value_t *replace_depot_args[3]; replace_depot_args[0] = replace_depot_func; - replace_depot_args[1] = deppath; + replace_depot_args[1] = deppath_or_uuid; replace_depot_args[2] = depots; - deppath = (jl_value_t*)jl_apply(replace_depot_args, 3); - JL_TYPECHK(write_dependency_list, string, deppath); + deppath_or_uuid = (jl_value_t*)jl_apply(replace_depot_args, 3); + JL_TYPECHK(write_dependency_list, string, deppath_or_uuid); } - size_t slen = jl_string_len(deppath); + size_t slen = jl_string_len(deppath_or_uuid); write_int32(s, slen); - ios_write(s, jl_string_data(deppath), slen); + ios_write(s, jl_string_data(deppath_or_uuid), slen); write_uint64(s, jl_unbox_uint64(jl_fieldref(deptuple, 2))); // fsize write_uint32(s, jl_unbox_uint32(jl_fieldref(deptuple, 3))); // hash write_float64(s, jl_unbox_float64(jl_fieldref(deptuple, 4))); // mtime diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index 5da669b920138..5159bb33bf778 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -294,25 +294,25 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` # ~ expansion in --project and JULIA_PROJECT if !Sys.iswindows() - let expanded = abspath(expanduser("~/foo/Project.toml")) + let expanded = abspath(expanduser("~/foo/Project.toml"); safe=true) @test expanded == readchomp(`$exename --project='~/foo' -e 'println(Base.active_project())'`) @test expanded == readchomp(setenv(`$exename -e 'println(Base.active_project())'`, "JULIA_PROJECT" => "~/foo", "HOME" => homedir())) end end # handling of @projectname in --project and JULIA_PROJECT - let expanded = abspath(Base.load_path_expand("@foo")) + let expanded = abspath(Base.load_path_expand("@foo"); safe=true) @test expanded == readchomp(`$exename --project='@foo' -e 'println(Base.active_project())'`) @test expanded == readchomp(addenv(`$exename -e 'println(Base.active_project())'`, "JULIA_PROJECT" => "@foo", "HOME" => homedir())) end # --project=@script handling - let expanded = abspath(joinpath(@__DIR__, "project", "ScriptProject")) + let expanded = abspath(joinpath(@__DIR__, "project", "ScriptProject"); safe=true) script = joinpath(expanded, "bin", "script.jl") # Check running julia with --project=@script both within and outside the script directory @testset "--@script from $name" for (name, dir) in [("project", expanded), ("outside", pwd())] - @test joinpath(expanded, "Project.toml") == readchomp(Cmd(`$exename --project=@script $script`; dir)) - @test joinpath(expanded, "SubProject", "Project.toml") == readchomp(Cmd(`$exename --project=@script/../SubProject $script`; dir)) + @test samefile(joinpath(expanded, "Project.toml"), readchomp(Cmd(`$exename --project=@script $script`; dir))) + @test samefile(joinpath(expanded, "SubProject", "Project.toml"), readchomp(Cmd(`$exename --project=@script/../SubProject $script`; dir))) end end diff --git a/test/loading.jl b/test/loading.jl index 23d40cf21be30..2df668f89b7ad 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -274,7 +274,7 @@ end n = map(String, split(names, '.')) pkg = recurse_package(n...) @test pkg == PkgId(UUID(uuid), n[end]) - @test joinpath(@__DIR__, normpath(path)) == locate_package(pkg) + @test samefile(joinpath(@__DIR__, path), locate_package(pkg)) @test Base.compilecache_path(pkg, UInt64(0)) == Base.compilecache_path(pkg, UInt64(0)) end @test identify_package("Baz") === nothing @@ -350,19 +350,19 @@ module NotPkgModule; end @test Foo.which == "path" @testset "pathof" begin - @test pathof(Foo) == normpath(abspath(@__DIR__, "project/deps/Foo1/src/Foo.jl")) + @test samefile(pathof(Foo), joinpath(@__DIR__, "project/deps/Foo1/src/Foo.jl")) @test pathof(NotPkgModule) === nothing end @testset "pkgdir" begin - @test pkgdir(Foo) == normpath(abspath(@__DIR__, "project/deps/Foo1")) - @test pkgdir(Foo.SubFoo1) == normpath(abspath(@__DIR__, "project/deps/Foo1")) - @test pkgdir(Foo.SubFoo2) == normpath(abspath(@__DIR__, "project/deps/Foo1")) + @test samefile(pkgdir(Foo), joinpath(@__DIR__, "project/deps/Foo1")) + @test samefile(pkgdir(Foo.SubFoo1), joinpath(@__DIR__, "project/deps/Foo1")) + @test samefile(pkgdir(Foo.SubFoo2), joinpath(@__DIR__, "project/deps/Foo1")) @test pkgdir(NotPkgModule) === nothing - @test pkgdir(Foo, "src") == normpath(abspath(@__DIR__, "project/deps/Foo1/src")) - @test pkgdir(Foo.SubFoo1, "src") == normpath(abspath(@__DIR__, "project/deps/Foo1/src")) - @test pkgdir(Foo.SubFoo2, "src") == normpath(abspath(@__DIR__, "project/deps/Foo1/src")) + @test samefile(pkgdir(Foo, "src"), joinpath(@__DIR__, "project/deps/Foo1/src")) + @test samefile(pkgdir(Foo.SubFoo1, "src"), joinpath(@__DIR__, "project/deps/Foo1/src")) + @test samefile(pkgdir(Foo.SubFoo2, "src"), joinpath(@__DIR__, "project/deps/Foo1/src")) @test pkgdir(NotPkgModule, "src") === nothing end @@ -1146,7 +1146,7 @@ end _ext = Base.get_extension(parent, ext) _ext isa Module || error("expected extension \$ext to be loaded") _pkgdir = pkgdir(_ext) - _pkgdir == pkgdir(parent) != nothing || error("unexpected extension \$ext pkgdir path: \$_pkgdir") + samefile(_pkgdir, pkgdir(parent)) || error("unexpected extension \$ext pkgdir path: \$_pkgdir") _pkgversion = pkgversion(_ext) _pkgversion == pkgversion(parent) || error("unexpected extension \$ext version: \$_pkgversion") end @@ -1915,3 +1915,47 @@ module M58272_to end copy!(LOAD_PATH, old_load_path) end end + +# Test loading when project or depot paths contain the symlink/.. pattern +if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER + @testset "symlink/.. in project or depot paths" begin + mktempdir() do dir + old_load_path = copy(LOAD_PATH) + old_active_project = Base.ACTIVE_PROJECT[] + + # Create a symlinked subdirectory + symlink_dir = joinpath(dir, "project_symlink") + target_dir = joinpath(@__DIR__, "project", "deps", "Foo1", "src") + mkpath(target_dir) + symlink(target_dir, symlink_dir) + symlink_parent = joinpath(symlink_dir, "..") + try + # test symlink'd project path + Base.set_active_project(symlink_parent) + push!(empty!(LOAD_PATH), "@") + + Foo1_uuid = Base.UUID("1a6589dc-c33c-4d54-9a54-f7fc4b3ff616") + id = Base.identify_package("Foo") + @test id.uuid == Foo1_uuid + located = Base.locate_package(id) + @test located !== nothing + @test samefile(located, joinpath(target_dir, "Foo.jl")) + + # Test symlink'd depot path + symlink_depot = joinpath(dir, "depot_symlink") + target_dir = joinpath(@__DIR__, "depot", "packages") + symlink(target_dir, symlink_depot) + push!(empty!(LOAD_PATH), joinpath(@__DIR__, "project")) + push!(empty!(DEPOT_PATH), joinpath(symlink_depot, "..")) + pkg = Base.identify_package( + Base.PkgId(Base.UUID("2a550a13-6bab-4a91-a4ee-dff34d6b99d0"), "Bar"), "Baz") + @test pkg !== nothing + @test samefile(Base.locate_package(pkg), joinpath(target_dir, "Baz", "81oLe", "src", "Baz.jl")) + finally + Base.ACTIVE_PROJECT[] = old_active_project + copy!(LOAD_PATH, old_load_path) + copy!(DEPOT_PATH, original_depot_path) + end + end + end +end diff --git a/test/relocatedepot.jl b/test/relocatedepot.jl index e8758365e3ff4..046e0b4967082 100644 --- a/test/relocatedepot.jl +++ b/test/relocatedepot.jl @@ -53,8 +53,7 @@ if !test_relocated_depot mkdir(jl) push!(DEPOT_PATH, jl) @test Base.replace_depot_path(jl) == "@depot" - @test Base.replace_depot_path(string(jl,Base.Filesystem.pathsep())) == - string("@depot",Base.Filesystem.pathsep()) + @test Base.replace_depot_path(string(jl,Base.Filesystem.pathsep())) == "@depot" @test Base.replace_depot_path(jlrc) != "@depot-rc2" @test Base.replace_depot_path(jlrc) == jlrc end