JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
44.95k stars 5.42k forks source link

Problem with `isfile_casesensitive()` on Windows #54799

Open kimikage opened 2 weeks ago

kimikage commented 2 weeks ago

An internal function isfile_casesensitive() uses Win32API GetLongPathNameW on Windows. https://github.com/JuliaLang/julia/blob/222231f047462bcb2cef3e402c86cf41df82414d/base/loading.jl#L11-L16

However, the API may not be able to handle non-existent directories.

julia> dir = joinpath(DEPOT_PATH[1], "environments", "v1.10") # just an example
"C:\\Users\\username\\.julia\\environments\\v1.10"

julia> Base.Filesystem.longpath(joinpath(dir, "Project.toml"))
"C:\\Users\\username\\.julia\\environments\\v1.10\\Project.toml"

julia> Base.Filesystem.longpath(joinpath(dir, "nonexistent", "..", "Project.toml"))
ERROR: SystemError: longpath: The system cannot find the file specified.
Stacktrace:
 [1] windowserror(p::Symbol, code::UInt32; extrainfo::Nothing)
   @ Base .\error.jl:200
 [2] windowserror
   @ .\error.jl:199 [inlined]
 [3] longpath(path::String)
   @ Base.Filesystem .\path.jl:479
 [4] top-level scope
   @ REPL[3]:1

julia> versioninfo()
Julia Version 1.12.0-DEV.727
Commit 3054c00d33 (2024-06-14 05:13 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: 8 × 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
  WORD_SIZE: 64
  LLVM: libLLVM-17.0.6 (ORCJIT, tigerlake)
Threads: 1 default, 0 interactive, 1 GC (on 8 virtual cores)

This problem can also be reproduced on Julia v1.6.7. Perhaps `normpath()' would mitigate this problem.

Incidentally, parent directories of non-existent directories seem to be invalid on Linux from the beginning.

kimikage commented 2 weeks ago

Or, (on Julia v1.11 and later)

function Base.isfile_casesensitive(path)
    Base.isaccessiblefile(path) || return false  # Fail fast
    name = basename(path)
    try
        for entry in Base.Filesystem._readdirx(dirname(path), sort = false)
            entry.name == name && return true
        end
    catch err
        isa(err, Base.IOError) || rethrow()
    end
    return false
end