Skip to content
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

expose libuv windows verbatim and hidden process spawning #13780

Merged
merged 1 commit into from
Nov 24, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
expose libuv windows verbatim and hidden process spawning (fix #13776)
  • Loading branch information
stevengj committed Nov 4, 2015
commit 300a868b49afc33bcb1e7d968125bb54b33a9337
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,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 @@ -1355,13 +1355,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 @@ -6180,13 +6173,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
2 changes: 1 addition & 1 deletion doc/stdlib/io-network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ General I/O

.. Docstring generated from Julia source

Write an arbitrary value to a stream in an opaque format, such that it can be read back by ``deserialize``\ . The read-back value will be as identical as possible to the original. In general, this process will not work if the reading and writing are done by different versions of Julia, or an instance of Julia with a different system image.
Write an arbitrary value to a stream in an opaque format, such that it can be read back by ``deserialize``\ . The read-back value will be as identical as possible to the original. In general, this process will not work if the reading and writing are done by different versions of Julia, or an instance of Julia with a different system image. ``Ptr`` values are serialized as all-zero bit patterns (``NULL``\ ).

.. function:: deserialize(stream)

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))