Skip to content

Commit

Permalink
Merge pull request JuliaLang#13780 from stevengj/windows_cmd_flags
Browse files Browse the repository at this point in the history
expose libuv windows verbatim and hidden process spawning
  • Loading branch information
vtjnash committed Nov 24, 2015
2 parents e8f1b1a + 300a868 commit 003f415
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 42 deletions.
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ Library improvements
* New `foreach` function for calling a function on every element of a collection when
the results are not needed.

* `Cmd(cmd; ...)` now accepts new Windows-specific options `windows_verbatim`
(to alter Windows command-line generation) and `windows_hide` (to
suppress creation of new console windows) ([#13780]).

Deprecated or removed
---------------------

Expand Down
14 changes: 0 additions & 14 deletions base/docs/helpdb.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1331,13 +1331,6 @@ Get a backtrace object for the current program point.
"""
backtrace

doc"""
ignorestatus(command)
Mark a command object so that running it will not throw an error if the result code is non-zero.
"""
ignorestatus

doc"""
reducedim(f, A, dims[, initial])
Expand Down Expand Up @@ -5987,13 +5980,6 @@ Commit all currently buffered writes to the given stream.
"""
flush

doc"""
detach(command)
Mark a command object so that it will be run in a new process group, allowing it to outlive the julia process, and not have Ctrl-C interrupts passed to it.
"""
detach

doc"""
precompile(f,args::Tuple{Vararg{Any}})
Expand Down
108 changes: 85 additions & 23 deletions base/process.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,76 @@

abstract AbstractCmd

# libuv process option flags
const UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS = UInt8(1 << 2)
const UV_PROCESS_DETACHED = UInt8(1 << 3)
const UV_PROCESS_WINDOWS_HIDE = UInt8(1 << 4)

immutable Cmd <: AbstractCmd
exec::Vector{ByteString}
ignorestatus::Bool
detach::Bool
flags::UInt32 # libuv process flags
env::Union{Array{ByteString},Void}
dir::UTF8String
Cmd(exec::Vector{ByteString}) =
new(exec, false, false, nothing, "")
Cmd(cmd::Cmd, ignorestatus, detach, env, dir) =
new(cmd.exec, ignorestatus, detach, env,
new(exec, false, 0x00, nothing, "")
Cmd(cmd::Cmd, ignorestatus, flags, env, dir) =
new(cmd.exec, ignorestatus, flags, env,
dir === cmd.dir ? dir : cstr(dir))
Cmd(cmd::Cmd; ignorestatus=cmd.ignorestatus, detach=cmd.detach, env=cmd.env, dir=cmd.dir) =
new(cmd.exec, ignorestatus, detach, env,
function Cmd(cmd::Cmd; ignorestatus::Bool=cmd.ignorestatus, env=cmd.env, dir::AbstractString=cmd.dir,
detach::Bool=Bool(cmd.flags & UV_PROCESS_DETACHED),
windows_verbatim::Bool=Bool(cmd.flags & UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS),
windows_hide::Bool=Bool(cmd.flags & UV_PROCESS_WINDOWS_HIDE))
flags = detach*UV_PROCESS_DETACHED |
windows_verbatim*UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS |
windows_hide*UV_PROCESS_WINDOWS_HIDE
new(cmd.exec, ignorestatus, flags, byteenv(env),
dir === cmd.dir ? dir : cstr(dir))
end
end

doc"""
Cmd(cmd::Cmd; ignorestatus, detach, windows_verbatim, windows_hide,
env, dir)
Construct a new `Cmd` object, representing an external program and
arguments, from `cmd`, while changing the settings of the optional
keyword arguments:
* `ignorestatus::Bool`: If `true` (defaults to `false`), then the `Cmd`
will not throw an error if the return code is nonzero.
* `detach::Bool`: If `true` (defaults to `false`), then the `Cmd` will be
run in a new process group, allowing it to outlive the `julia` process
and not have Ctrl-C passed to it.
* `windows_verbatim::Bool`: If `true` (defaults to `false`), then on Windows
the `Cmd` will send a command-line string to the process with no quoting
or escaping of arguments, even arguments containing spaces. (On Windows,
arguments are sent to a program as a single "command-line" string, and
programs are responsible for parsing it into arguments. By default,
empty arguments and arguments with spaces or tabs are quoted with double
quotes `"` in the command line, and `\` or `"` are preceded by backslashes.
`windows_verbatim=true` is useful for launching programs that parse their
command line in nonstandard ways.) Has no effect on non-Windows systems.
* `windows_hide::Bool`: If `true` (defaults to `false`), then on Windows no
new console window is displayed when the `Cmd` is executed. This has
no effect if a console is already open or on non-Windows systems.
* `env`: Set environment variables to use when running the `Cmd`. `env`
is either a dictionary mapping strings to strings, an array
of strings of the form `"var=val"`, an array or tuple of `"var"=>val`
pairs, or `nothing`. In order to modify (rather than replace)
the existing environment, create `env` by `copy(ENV)` and then
set `env["var"]=val` as desired.
* `dir::AbstractString`: Specify a working directory for the command (instead
of the current directory).
For any keywords that are not specified, the current settings from `cmd` are
used. Normally, to create a `Cmd` object in the first place, one uses
backticks, e.g.
Cmd(`echo "Hello world"`, ignorestatus=true, detach=false)
"""
Cmd

immutable OrCmds <: AbstractCmd
a::AbstractCmd
b::AbstractCmd
Expand Down Expand Up @@ -136,11 +190,21 @@ function show(io::IO, cr::CmdRedirect)
print(io, ")")
end

"""
ignorestatus(command)
Mark a command object so that running it will not throw an error if the result code is non-zero.
"""
ignorestatus(cmd::Cmd) = Cmd(cmd, ignorestatus=true)
ignorestatus(cmd::Union{OrCmds,AndCmds}) =
typeof(cmd)(ignorestatus(cmd.a), ignorestatus(cmd.b))
detach(cmd::Cmd) = Cmd(cmd, detach=true)

"""
detach(command)
Mark a command object so that it will be run in a new process group, allowing it to outlive the julia process, and not have Ctrl-C interrupts passed to it.
"""
detach(cmd::Cmd) = Cmd(cmd; detach=true)

# like bytestring(s), but throw an error if s contains NUL, since
# libuv requires NUL-terminated strings
Expand All @@ -151,21 +215,19 @@ function cstr(s)
return bytestring(s)
end

function setenv{S<:ByteString}(cmd::Cmd, env::Array{S}; dir="")
byteenv = ByteString[cstr(x) for x in env]
return Cmd(cmd; env = byteenv, dir = dir)
end
function setenv(cmd::Cmd, env::Associative; dir="")
byteenv = ByteString[cstr(string(k)*"="*string(v)) for (k,v) in env]
return Cmd(cmd; env = byteenv, dir = dir)
end
function setenv{T<:AbstractString}(cmd::Cmd, env::Pair{T}...; dir="")
byteenv = ByteString[cstr(k*"="*string(v)) for (k,v) in env]
return Cmd(cmd; env = byteenv, dir = dir)
end
function setenv(cmd::Cmd; dir="")
return Cmd(cmd; dir = dir)
end
# convert various env representations into an array of "key=val" strings
byteenv{S<:AbstractString}(env::AbstractArray{S}) =
ByteString[cstr(x) for x in env]
byteenv(env::Associative) =
ByteString[cstr(string(k)*"="*string(v)) for (k,v) in env]
byteenv(env::Void) = nothing
byteenv{T<:AbstractString}(env::Union{AbstractVector{Pair{T}}, Tuple{Vararg{Pair{T}}}}) =
ByteString[cstr(k*"="*string(v)) for (k,v) in env]

setenv(cmd::Cmd, env; dir="") = Cmd(cmd; env=byteenv(env), dir=dir)
setenv{T<:AbstractString}(cmd::Cmd, env::Pair{T}...; dir="") =
setenv(cmd, env; dir=dir)
setenv(cmd::Cmd; dir="") = Cmd(cmd; dir=dir)

(&)(left::AbstractCmd, right::AbstractCmd) = AndCmds(left, right)
redir_out(src::AbstractCmd, dest::AbstractCmd) = OrCmds(src, dest)
Expand Down Expand Up @@ -255,7 +317,7 @@ function _jl_spawn(cmd, argv, loop::Ptr{Void}, pp::Process,
Ptr{Void}, Int32, Ptr{Void}, Int32, Ptr{Void}, Int32, Ptr{Ptr{UInt8}}, Ptr{UInt8}, Ptr{Void}),
cmd, argv, loop, proc, pp, uvtype(in),
uvhandle(in), uvtype(out), uvhandle(out), uvtype(err), uvhandle(err),
pp.cmd.detach, pp.cmd.env === nothing ? C_NULL : pp.cmd.env, isempty(pp.cmd.dir) ? C_NULL : pp.cmd.dir,
pp.cmd.flags, pp.cmd.env === nothing ? C_NULL : pp.cmd.env, isempty(pp.cmd.dir) ? C_NULL : pp.cmd.dir,
uv_jl_return_spawn::Ptr{Void})
if error != 0
ccall(:jl_forceclose_uv, Void, (Ptr{Void},), proc)
Expand Down
20 changes: 20 additions & 0 deletions doc/stdlib/base.rst
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,26 @@ System
Mark a command object so that it will be run in a new process group, allowing it to outlive the julia process, and not have Ctrl-C interrupts passed to it.

.. function:: Cmd(cmd::Cmd; ignorestatus, detach, windows_verbatim, windows_hide,
env, dir)

.. Docstring generated from Julia source
Construct a new ``Cmd`` object, representing an external program and arguments, from ``cmd``\ , while changing the settings of the optional keyword arguments:

* ``ignorestatus::Bool``\ : If ``true`` (defaults to ``false``\ ), then the ``Cmd`` will not throw an error if the return code is nonzero.
* ``detach::Bool``\ : If ``true`` (defaults to ``false``\ ), then the ``Cmd`` will be run in a new process group, allowing it to outlive the ``julia`` process and not have Ctrl-C passed to it.
* ``windows_verbatim::Bool``\ : If ``true`` (defaults to ``false``\ ), then on Windows the ``Cmd`` will send a command-line string to the process with no quoting or escaping of arguments, even arguments containing spaces. (On Windows, arguments are sent to a program as a single "command-line" string, and programs are responsible for parsing it into arguments. By default, empty arguments and arguments with spaces or tabs are quoted with double quotes ``"`` in the command line, and ``\`` or ``"`` are preceded by backslashes. ``windows_verbatim=true`` is useful for launching programs that parse their command line in nonstandard ways.) Has no effect on non-Windows systems.
* ``windows_hide::Bool``\ : If ``true`` (defaults to ``false``\ ), then on Windows no new console window is displayed when the ``Cmd`` is executed. This has no effect if a console is already open or on non-Windows systems.
* ``env``\ : Set environment variables to use when running the ``Cmd``\ . ``env`` is either a dictionary mapping strings to strings, an array of strings of the form ``"var=val"``\ , an array or tuple of ``"var"=>val`` pairs, or ``nothing``\ . In order to modify (rather than replace) the existing environment, create ``env`` by ``copy(ENV)`` and then set ``env["var"]=val`` as desired.
* ``dir::AbstractString``\ : Specify a working directory for the command (instead of the current directory).

For any keywords that are not specified, the current settings from ``cmd`` are used. Normally, to create a ``Cmd`` object in the first place, one uses backticks, e.g.

.. code-block:: julia
Cmd(`echo "Hello world"`, ignorestatus=true, detach=false)
.. function:: setenv(command, env; dir=working_dir)

.. Docstring generated from Julia source
Expand Down
8 changes: 3 additions & 5 deletions src/jl_uv.c
Original file line number Diff line number Diff line change
Expand Up @@ -212,22 +212,20 @@ DLLEXPORT int jl_spawn(char *name, char **argv, uv_loop_t *loop,
uv_handle_type stdin_type, uv_pipe_t *stdin_pipe,
uv_handle_type stdout_type, uv_pipe_t *stdout_pipe,
uv_handle_type stderr_type, uv_pipe_t *stderr_pipe,
int detach, char **env, char *cwd, uv_exit_cb cb)
int flags, char **env, char *cwd, uv_exit_cb cb)
{
uv_process_options_t opts;
uv_stdio_container_t stdio[3];
int error;
opts.file = name;
opts.env = env;
#ifdef _OS_WINDOWS_
opts.flags = 0;
opts.flags = flags;
#else
opts.flags = UV_PROCESS_RESET_SIGPIPE;
opts.flags = flags | UV_PROCESS_RESET_SIGPIPE;
#endif
opts.cwd = cwd;
opts.args = argv;
if (detach)
opts.flags |= UV_PROCESS_DETACHED;
opts.stdio = stdio;
opts.stdio_count = 3;
stdio[0].type = stdin_type;
Expand Down
3 changes: 3 additions & 0 deletions test/spawn.jl
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,6 @@ end

# issue #13616
@test_throws ErrorException collect(eachline(`cat _doesnt_exist__111_`))

# make sure windows_verbatim strips quotes
@windows_only readall(`cmd.exe /c dir /b spawn.jl`) == readall(Cmd(`cmd.exe /c dir /b "\"spawn.jl\""`, windows_verbatim=true))

0 comments on commit 003f415

Please sign in to comment.