-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Sys.which()
, use that to find curl
in download()
#26559
Conversation
Maybe someone with FreeBSD can confirm, but I don't think |
Can confirm, it does not. |
Might be nice to have a portable |
@stevengj, Elliot and I were just talking about that offline. He intends to add such a function based off of https://github.com/ararslan/BuildUtils.jl/blob/master/src/BuildUtils.jl#L46-L74. 🙂 |
Yep, I'll tack it onto this PR, just writing up the tests now. It'll be called |
0d0a0b3
to
e908eb0
Compare
base/sysinfo.jl
Outdated
# If prog has a slash, we know the user wants to determine whether the given | ||
# file exists and is executable, and to not search the `PATH`. Note that | ||
# Windows can have either `\\` or `/` in its paths: | ||
dirseps = @static if is_windows() ["/", "\\"] else ["/"] end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_windows
is deprecated to iswindows
base/sysinfo.jl
Outdated
# Windows can have either `\\` or `/` in its paths: | ||
dirseps = @static if is_windows() ["/", "\\"] else ["/"] end | ||
|
||
if any(contains.(program_name, dirseps)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
contains(a, b)
is deprecated to occursin(b, a)
base/sysinfo.jl
Outdated
# We use `access()` and `X_OK` to determine if a given path is executable | ||
# by the current user. `X_OK` comes from `unistd.h`. | ||
X_OK = 1 << 0 | ||
can_execute(program_name) = @static if is_windows() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be moved out of the function
base/sysinfo.jl
Outdated
["/", "\\"] | ||
else | ||
["/"] | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably a tuple of chars here rather than an array of strings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are tuples preferred here for performance considerations?
base/sysinfo.jl
Outdated
# If we have been given just a program name (not a relative or absolute | ||
# path) then we should search `PATH` for it here: | ||
path = get(ENV, "PATH", "") | ||
pathsep = @static if iswindows() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pathsep = @static iswindows() ? ';' : ':'
would be shorter and probably more readable for conditionals like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, for some reason I thought @static
didn't work with the ternary operator. Nice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The @static
isn't entirely necessary
base/sysinfo.jl
Outdated
|
||
# On windows, we need to check both $(program_name) and $(program_name).exe | ||
program_matches(file, program_name) = @static if iswindows() | ||
file == program_name || file == "$(program_name).exe" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you need to check everything in get(ENV, "PATHEXT", "exe")
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, you presumably only want to check appending an extension if program_name
doesn't already have an extension (i.e. if it doesn't contain .
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No to the first (also .cmd
), yes to the second (with qualifications). We should probably try to return the same answer as our underlying spawn API (https://github.com/libuv/libuv/blob/edf05b97f03472def2837ceb2d6bf61a0d0dc248/src/win/process.c#L299)
base/sysinfo.jl
Outdated
file == program_name | ||
end | ||
|
||
for dir in split(path, pathsep), file in readdir(dir) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why walk the directory tree? This seems very inefficient if the directories contain lots of entries, as will be common. Instead, you could do something like:
for dir in split(path, pathsep)
file = joinpath(path, program_name)
isfile(file) && _can_execute(file) && return abspath(file)
.... check extensions on windows ....
end
right?
base/sysinfo.jl
Outdated
|
||
Returns `true` if the given `path` has executable permissions. | ||
""" | ||
function _can_execute(path::AbstractString) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isexecutable
seems more idiomatic.
base/sysinfo.jl
Outdated
["/"] | ||
end | ||
|
||
if any(occursin.(dirseps, program_name)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or just
if occursin(@static(iswindows() ? r"[/\\]" : '/'), program_name)
base/sysinfo.jl
Outdated
# by the current user. `X_OK` comes from `unistd.h`. | ||
X_OK = 1 << 0 | ||
@static if iswindows() | ||
ccall(:_access, Cint, (Ptr{UInt8}, Cint), path, X_OK) == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
access
on windows doesn't have an X_OK
parameter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that's an excellent point. I just spent half an hour trying to parse MSDN and figure out how GetNamedSecurityInfo
can help me figure out the right ACL values to see if something is executable by the current user, but it defeated me. I'm just going to return true
on Windows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the which
function on Windows probably shares fairly little with the same function on posix. I expect they likely should just be completely independent implementations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.........busybox-w32
uses access(path, X_OK)
. Sigh.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m not too surprised. It’s a posix emulation later, probably intended to approximate the result you’d get on busybox sh
or cygwin (similar to how the real which program on posix approximates the likely answer)
42c1909
to
5239a75
Compare
base/sysinfo.jl
Outdated
pathsep = @static iswindows() ? ';' : ':' | ||
|
||
# On windows, if the program name does not already have an extension, | ||
# we need to tack all the values in PATHEXT onto the end and check for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, see how libuv implements this function. IIRC, the distinction is that PATHEXT is for DOS, not the Shell32.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which function are you referring to? What should I look for in libuv
?
I'm not sure what you mean in stating that PATHEXT
is only for DOS; when I try to run()
a command, I expect it to work like I've typed it into cmd.exe
, and cmd.exe
does pay attention to PATHEXT
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my libuv link in the other comment thread
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vtjnash means that we want to match the semantics of search_path
in libuv's src/win/process.c
, I guess, since this is what run
uses.
Unfortunately, the search_path
function is not exported. But since we use our own libuv fork anyway, maybe we should just modify it to export a uv_win_search_path
function on Windows? But it doesn't look that complicated to implement libuv's semantics — it's actually somewhat simpler than what you are doing now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Libuv says:
Furthermore, it tries to follow the semantics that cmd.exe, with this exception that PATHEXT environment variable isn't used. Since CreateProcess can start only .com and .exe files, only those extensions are tried. This behavior equals that of msvcrt's spawn functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note also some differences with what you are doing now:
- If there's really only a filename, check the current directory for file, then search all path directories.
- If filename specified has any extension, search for the file with the specified extension first.
- If the literal filename is not found in a directory (even if the file already had an extension), try appending (not replacing)
.com
first and then.exe
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note also that libuv writes CMD does not trim leading/trailing whitespace from path/pathex entries
base/sysinfo.jl
Outdated
program_names = [program_name] | ||
@static if iswindows() | ||
if !isempty(splitext(program_name)[2]) | ||
pathexts = get(ENV, "PATHEXT", ".com; .exe; .bat; .cmd") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no spaces in the default
base/sysinfo.jl
Outdated
@static if iswindows() | ||
if !isempty(splitext(program_name)[2]) | ||
pathexts = get(ENV, "PATHEXT", ".com; .exe; .bat; .cmd") | ||
pathexts = strip.(split(pathexts, ';')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the strip
necessary? Can PATHEXT contain whitespace?
So looking through this, I guess we have to make the decision; do we want to behave like |
5239a75
to
5b500a6
Compare
I think |
base/sysinfo.jl
Outdated
""" | ||
function isexecutable(path::AbstractString) | ||
@static if iswindows() | ||
return true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isfile(path)
?
base/sysinfo.jl
Outdated
for program_name in program_names | ||
program_path = joinpath(path_dir, program_name) | ||
# If we find something that matches our name and we can execute | ||
if isfile(program_path) && isexecutable(program_path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you change isexecutable
to isfile
on Windows as suggested above, then you can omit the isfile
check here.
5b500a6
to
d79c028
Compare
which
, just invoke curl
directlySys.which()
, use that to find curl
in download()
Okay, I think I've addressed all the comments so far. |
base/sysinfo.jl
Outdated
""" | ||
function isexecutable(path::AbstractString) | ||
@static if iswindows() | ||
return isfile(path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't you determine whether it has executable permissions using _access
on Windows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. Windows doesn't have an X_OK
bit. :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah okay, sorry for the noise then.
base/sysinfo.jl
Outdated
# If this file does not even exist, fail out | ||
if !isfile(program_name) | ||
throw(ArgumentError("$program_name does not exist")) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this check is now redundant with isexecutable
end | ||
|
||
# If we couldn't find anything, complain | ||
error("$program_name not found") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be an ArgumentError
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see it originally was but Jameson said it shouldn't be. Just using error
here seems unfortunately general.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but I don't have a good feel for what errors should be what, so in the absence of a better idea, I'm going to keep it as error()
for now.
test/spawn.jl
Outdated
end | ||
|
||
mktempdir() do dir | ||
pathsep = @static if Sys.iswindows() ";" else ":" end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A ternary would be less weird here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pathsep = Sys.iswindows() ? ";" : ":"
is too much punctuation for me.
test/spawn.jl
Outdated
bar_path = joinpath(dir, "bin1", "bar.exe") | ||
touch(bar_path) | ||
chmod(bar_path, 0o777) | ||
cd("$(dir)") do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dir
is already a String
; it doesn't need to be interpolated.
test/spawn.jl
Outdated
|
||
@static if Sys.iswindows() | ||
# On windows, check that pwd() takes precedence, except when we provide a path | ||
cd("$(dir)/bin2") do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
joinpath(dir, "bin2")
base/sysinfo.jl
Outdated
""" | ||
Sys.isexecutable(path::String) | ||
|
||
Returns `true` if the given `path` has executable permissions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We word our docs in the imperative, i.e. this should be "return" rather than "returns." Same for the which
docstring below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hah, I was basing this off of the Sys.windows_version()
docstring above it. I'll fix that too.
base/sysinfo.jl
Outdated
@@ -23,7 +23,8 @@ export BINDIR, | |||
isbsd, | |||
islinux, | |||
isunix, | |||
iswindows | |||
iswindows, | |||
which |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe export isexecutable
as well?
e8b511b
to
db9e7f2
Compare
690f47b
to
9bc1aa4
Compare
9bc1aa4
to
3592250
Compare
3592250
to
dff0be4
Compare
dff0be4
to
3cf7dfc
Compare
3cf7dfc
to
4c7f2cf
Compare
This acts as a julia-native version of the `which` command found on most *nix systems; it searches the path for an executable of the given name, returning the absolute path if it exists, and throwing an `ArgumentError()` if it does not.
This fixes issues on systems that don't have `which` available
This passes all CI now. Win32 build failure is unrelated, the Absent any other comments, I'm going to merge this tomorrow. |
Probably should have been squash-merged. |
I intentionally didn't; each commit is intended to stand alone. |
As [discussed recently on discourse](https://discourse.julialang.org/t/weird-sys-which-function/58070), some programs require a certain executable name to function correctly, and behave badly if you call `realpath` to expand symbolic links (potentially changing the program name). c.f. discussion in #26559, implemented `Sys.which`
As [discussed recently on discourse](https://discourse.julialang.org/t/weird-sys-which-function/58070), some programs require a certain executable name to function correctly, and behave badly if you call `realpath` to expand symbolic links (potentially changing the program name). c.f. discussion in JuliaLang#26559, implemented `Sys.which`
As [discussed recently on discourse](https://discourse.julialang.org/t/weird-sys-which-function/58070), some programs require a certain executable name to function correctly, and behave badly if you call `realpath` to expand symbolic links (potentially changing the program name). c.f. discussion in JuliaLang#26559, implemented `Sys.which`
As [discussed recently on discourse](https://discourse.julialang.org/t/weird-sys-which-function/58070), some programs require a certain executable name to function correctly, and behave badly if you call `realpath` to expand symbolic links (potentially changing the program name). c.f. discussion in JuliaLang#26559, implemented `Sys.which`
It's better to invoke
curl
directly (e.g. viacurl --help
) in order to see if it exists than to usewhich curl
and see if that successfully passes. This fixes test errors on e.g. our CentOS 7.3 buildbots, which do not havewhich
available by default.