Add: julia-0.6.2
Former-commit-id: ccc667cf67d569f3fb3df39aa57c2134755a7551
This commit is contained in:
802
julia-0.6.2/share/julia/base/process.jl
Normal file
802
julia-0.6.2/share/julia/base/process.jl
Normal file
@@ -0,0 +1,802 @@
|
||||
# This file is a part of Julia. License is MIT: https://julialang.org/license
|
||||
|
||||
abstract type AbstractCmd end
|
||||
|
||||
# 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)
|
||||
|
||||
struct Cmd <: AbstractCmd
|
||||
exec::Vector{String}
|
||||
ignorestatus::Bool
|
||||
flags::UInt32 # libuv process flags
|
||||
env::Union{Array{String},Void}
|
||||
dir::String
|
||||
Cmd(exec::Vector{String}) =
|
||||
new(exec, false, 0x00, nothing, "")
|
||||
Cmd(cmd::Cmd, ignorestatus, flags, env, dir) =
|
||||
new(cmd.exec, ignorestatus, flags, env,
|
||||
dir === cmd.dir ? dir : cstr(dir))
|
||||
function Cmd(cmd::Cmd; ignorestatus::Bool=cmd.ignorestatus, env=cmd.env, dir::AbstractString=cmd.dir,
|
||||
detach::Bool = 0 != cmd.flags & UV_PROCESS_DETACHED,
|
||||
windows_verbatim::Bool = 0 != cmd.flags & UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS,
|
||||
windows_hide::Bool = 0 != 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
|
||||
|
||||
"""
|
||||
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
|
||||
|
||||
hash(x::Cmd, h::UInt) = hash(x.exec, hash(x.env, hash(x.ignorestatus, hash(x.dir, hash(x.flags, h)))))
|
||||
==(x::Cmd, y::Cmd) = x.exec == y.exec && x.env == y.env && x.ignorestatus == y.ignorestatus &&
|
||||
x.dir == y.dir && isequal(x.flags, y.flags)
|
||||
|
||||
struct OrCmds <: AbstractCmd
|
||||
a::AbstractCmd
|
||||
b::AbstractCmd
|
||||
OrCmds(a::AbstractCmd, b::AbstractCmd) = new(a, b)
|
||||
end
|
||||
|
||||
struct ErrOrCmds <: AbstractCmd
|
||||
a::AbstractCmd
|
||||
b::AbstractCmd
|
||||
ErrOrCmds(a::AbstractCmd, b::AbstractCmd) = new(a, b)
|
||||
end
|
||||
|
||||
struct AndCmds <: AbstractCmd
|
||||
a::AbstractCmd
|
||||
b::AbstractCmd
|
||||
AndCmds(a::AbstractCmd, b::AbstractCmd) = new(a, b)
|
||||
end
|
||||
|
||||
hash(x::AndCmds, h::UInt) = hash(x.a, hash(x.b, h))
|
||||
==(x::AndCmds, y::AndCmds) = x.a == y.a && x.b == y.b
|
||||
|
||||
shell_escape(cmd::Cmd; special::AbstractString="") =
|
||||
shell_escape(cmd.exec..., special=special)
|
||||
|
||||
function show(io::IO, cmd::Cmd)
|
||||
print_env = cmd.env !== nothing
|
||||
print_dir = !isempty(cmd.dir)
|
||||
(print_env || print_dir) && print(io, "setenv(")
|
||||
esc = shell_escape(cmd, special=shell_special)
|
||||
print(io, '`')
|
||||
for c in esc
|
||||
if c == '`'
|
||||
print(io, '\\')
|
||||
end
|
||||
print(io, c)
|
||||
end
|
||||
print(io, '`')
|
||||
print_env && (print(io, ","); show(io, cmd.env))
|
||||
print_dir && (print(io, "; dir="); show(io, cmd.dir))
|
||||
(print_dir || print_env) && print(io, ")")
|
||||
end
|
||||
|
||||
function show(io::IO, cmds::Union{OrCmds,ErrOrCmds})
|
||||
print(io, "pipeline(")
|
||||
show(io, cmds.a)
|
||||
print(io, ", ")
|
||||
print(io, isa(cmds, ErrOrCmds) ? "stderr=" : "stdout=")
|
||||
show(io, cmds.b)
|
||||
print(io, ")")
|
||||
end
|
||||
|
||||
function show(io::IO, cmds::AndCmds)
|
||||
show(io, cmds.a)
|
||||
print(io, " & ")
|
||||
show(io, cmds.b)
|
||||
end
|
||||
|
||||
const STDIN_NO = 0
|
||||
const STDOUT_NO = 1
|
||||
const STDERR_NO = 2
|
||||
|
||||
struct FileRedirect
|
||||
filename::AbstractString
|
||||
append::Bool
|
||||
function FileRedirect(filename, append)
|
||||
if lowercase(filename) == (@static is_windows() ? "nul" : "/dev/null")
|
||||
warn_once("for portability use DevNull instead of a file redirect")
|
||||
end
|
||||
new(filename, append)
|
||||
end
|
||||
end
|
||||
|
||||
uvhandle(::DevNullStream) = C_NULL
|
||||
uvtype(::DevNullStream) = UV_STREAM
|
||||
|
||||
uvhandle(x::Ptr) = x
|
||||
uvtype(::Ptr) = UV_STREAM
|
||||
|
||||
# Not actually a pointer, but that's how we pass it through the C API so it's fine
|
||||
uvhandle(x::RawFD) = convert(Ptr{Void}, x.fd % UInt)
|
||||
uvtype(x::RawFD) = UV_RAW_FD
|
||||
|
||||
const Redirectable = Union{IO, FileRedirect, RawFD}
|
||||
const StdIOSet = NTuple{3, Union{Redirectable, Ptr{Void}}} # XXX: remove Ptr{Void} once libuv is refactored to use upstream release
|
||||
|
||||
struct CmdRedirect <: AbstractCmd
|
||||
cmd::AbstractCmd
|
||||
handle::Redirectable
|
||||
stream_no::Int
|
||||
end
|
||||
|
||||
function show(io::IO, cr::CmdRedirect)
|
||||
print(io, "pipeline(")
|
||||
show(io, cr.cmd)
|
||||
print(io, ", ")
|
||||
if cr.stream_no == STDOUT_NO
|
||||
print(io, "stdout=")
|
||||
elseif cr.stream_no == STDERR_NO
|
||||
print(io, "stderr=")
|
||||
elseif cr.stream_no == STDIN_NO
|
||||
print(io, "stdin=")
|
||||
end
|
||||
show(io, cr.handle)
|
||||
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(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 String(s), but throw an error if s contains NUL, since
|
||||
# libuv requires NUL-terminated strings
|
||||
function cstr(s)
|
||||
if Base.containsnul(s)
|
||||
throw(ArgumentError("strings containing NUL cannot be passed to spawned processes"))
|
||||
end
|
||||
return String(s)
|
||||
end
|
||||
|
||||
# convert various env representations into an array of "key=val" strings
|
||||
byteenv(env::AbstractArray{<:AbstractString}) =
|
||||
String[cstr(x) for x in env]
|
||||
byteenv(env::Associative) =
|
||||
String[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}}}}) =
|
||||
String[cstr(k*"="*string(v)) for (k,v) in env]
|
||||
|
||||
"""
|
||||
setenv(command::Cmd, env; dir="")
|
||||
|
||||
Set environment variables to use when running the given `command`. `env` is either a
|
||||
dictionary mapping strings to strings, an array of strings of the form `"var=val"`, or zero
|
||||
or more `"var"=>val` pair arguments. In order to modify (rather than replace) the existing
|
||||
environment, create `env` by `copy(ENV)` and then setting `env["var"]=val` as desired, or
|
||||
use `withenv`.
|
||||
|
||||
The `dir` keyword argument can be used to specify a working directory for the command.
|
||||
"""
|
||||
setenv(cmd::Cmd, env; dir="") = Cmd(cmd; env=byteenv(env), dir=dir)
|
||||
setenv(cmd::Cmd, env::Pair{<:AbstractString}...; 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)
|
||||
redir_err(src::AbstractCmd, dest::AbstractCmd) = ErrOrCmds(src, dest)
|
||||
Base.mr_empty(f, op::typeof(&), T::Type{<:Base.AbstractCmd}) =
|
||||
throw(ArgumentError("reducing over an empty collection of type $T with operator & is not allowed"))
|
||||
|
||||
# Stream Redirects
|
||||
redir_out(dest::Redirectable, src::AbstractCmd) = CmdRedirect(src, dest, STDIN_NO)
|
||||
redir_out(src::AbstractCmd, dest::Redirectable) = CmdRedirect(src, dest, STDOUT_NO)
|
||||
redir_err(src::AbstractCmd, dest::Redirectable) = CmdRedirect(src, dest, STDERR_NO)
|
||||
|
||||
# File redirects
|
||||
redir_out(src::AbstractCmd, dest::AbstractString) = CmdRedirect(src, FileRedirect(dest, false), STDOUT_NO)
|
||||
redir_out(src::AbstractString, dest::AbstractCmd) = CmdRedirect(dest, FileRedirect(src, false), STDIN_NO)
|
||||
redir_err(src::AbstractCmd, dest::AbstractString) = CmdRedirect(src, FileRedirect(dest, false), STDERR_NO)
|
||||
redir_out_append(src::AbstractCmd, dest::AbstractString) = CmdRedirect(src, FileRedirect(dest, true), STDOUT_NO)
|
||||
redir_err_append(src::AbstractCmd, dest::AbstractString) = CmdRedirect(src, FileRedirect(dest, true), STDERR_NO)
|
||||
|
||||
"""
|
||||
pipeline(command; stdin, stdout, stderr, append=false)
|
||||
|
||||
Redirect I/O to or from the given `command`. Keyword arguments specify which of the
|
||||
command's streams should be redirected. `append` controls whether file output appends to the
|
||||
file. This is a more general version of the 2-argument `pipeline` function.
|
||||
`pipeline(from, to)` is equivalent to `pipeline(from, stdout=to)` when `from` is a command,
|
||||
and to `pipeline(to, stdin=from)` when `from` is another kind of data source.
|
||||
|
||||
**Examples**:
|
||||
|
||||
```julia
|
||||
run(pipeline(`dothings`, stdout="out.txt", stderr="errs.txt"))
|
||||
run(pipeline(`update`, stdout="log.txt", append=true))
|
||||
```
|
||||
"""
|
||||
function pipeline(cmd::AbstractCmd; stdin=nothing, stdout=nothing, stderr=nothing, append::Bool=false)
|
||||
if append && stdout === nothing && stderr === nothing
|
||||
error("append set to true, but no output redirections specified")
|
||||
end
|
||||
if stdin !== nothing
|
||||
cmd = redir_out(stdin, cmd)
|
||||
end
|
||||
if stdout !== nothing
|
||||
cmd = append ? redir_out_append(cmd, stdout) : redir_out(cmd, stdout)
|
||||
end
|
||||
if stderr !== nothing
|
||||
cmd = append ? redir_err_append(cmd, stderr) : redir_err(cmd, stderr)
|
||||
end
|
||||
return cmd
|
||||
end
|
||||
|
||||
pipeline(cmd::AbstractCmd, dest) = pipeline(cmd, stdout=dest)
|
||||
pipeline(src::Union{Redirectable,AbstractString}, cmd::AbstractCmd) = pipeline(cmd, stdin=src)
|
||||
|
||||
"""
|
||||
pipeline(from, to, ...)
|
||||
|
||||
Create a pipeline from a data source to a destination. The source and destination can be
|
||||
commands, I/O streams, strings, or results of other `pipeline` calls. At least one argument
|
||||
must be a command. Strings refer to filenames. When called with more than two arguments,
|
||||
they are chained together from left to right. For example `pipeline(a,b,c)` is equivalent to
|
||||
`pipeline(pipeline(a,b),c)`. This provides a more concise way to specify multi-stage
|
||||
pipelines.
|
||||
|
||||
**Examples**:
|
||||
|
||||
```julia
|
||||
run(pipeline(`ls`, `grep xyz`))
|
||||
run(pipeline(`ls`, "out.txt"))
|
||||
run(pipeline("out.txt", `grep xyz`))
|
||||
```
|
||||
"""
|
||||
pipeline(a, b, c, d...) = pipeline(pipeline(a,b), c, d...)
|
||||
|
||||
mutable struct Process <: AbstractPipe
|
||||
cmd::Cmd
|
||||
handle::Ptr{Void}
|
||||
in::IO
|
||||
out::IO
|
||||
err::IO
|
||||
exitcode::Int64
|
||||
termsignal::Int32
|
||||
exitnotify::Condition
|
||||
closenotify::Condition
|
||||
function Process(cmd::Cmd, handle::Ptr{Void},
|
||||
in::Union{Redirectable, Ptr{Void}},
|
||||
out::Union{Redirectable, Ptr{Void}},
|
||||
err::Union{Redirectable, Ptr{Void}})
|
||||
if !isa(in, IO)
|
||||
in = DevNull
|
||||
end
|
||||
if !isa(out, IO)
|
||||
out = DevNull
|
||||
end
|
||||
if !isa(err, IO)
|
||||
err = DevNull
|
||||
end
|
||||
this = new(cmd, handle, in, out, err,
|
||||
typemin(fieldtype(Process, :exitcode)),
|
||||
typemin(fieldtype(Process, :termsignal)),
|
||||
Condition(), Condition())
|
||||
finalizer(this, uvfinalize)
|
||||
return this
|
||||
end
|
||||
end
|
||||
pipe_reader(p::Process) = p.out
|
||||
pipe_writer(p::Process) = p.in
|
||||
|
||||
struct ProcessChain <: AbstractPipe
|
||||
processes::Vector{Process}
|
||||
in::Redirectable
|
||||
out::Redirectable
|
||||
err::Redirectable
|
||||
ProcessChain(stdios::StdIOSet) = new(Process[], stdios[1], stdios[2], stdios[3])
|
||||
end
|
||||
pipe_reader(p::ProcessChain) = p.out
|
||||
pipe_writer(p::ProcessChain) = p.in
|
||||
|
||||
function _jl_spawn(cmd, argv, loop::Ptr{Void}, pp::Process,
|
||||
in, out, err)
|
||||
proc = Libc.malloc(_sizeof_uv_process)
|
||||
disassociate_julia_struct(proc)
|
||||
error = ccall(:jl_spawn, Int32,
|
||||
(Cstring, Ptr{Cstring}, Ptr{Void}, Ptr{Void}, Any, Int32,
|
||||
Ptr{Void}, Int32, Ptr{Void}, Int32, Ptr{Void}, Int32, Ptr{Cstring}, Cstring, Ptr{Void}),
|
||||
cmd, argv, loop, proc, pp, uvtype(in),
|
||||
uvhandle(in), uvtype(out), uvhandle(out), uvtype(err), uvhandle(err),
|
||||
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)
|
||||
throw(UVError("could not spawn "*string(pp.cmd), error))
|
||||
end
|
||||
associate_julia_struct(proc, pp)
|
||||
return proc
|
||||
end
|
||||
|
||||
function uvfinalize(proc::Process)
|
||||
if proc.handle != C_NULL
|
||||
disassociate_julia_struct(proc.handle)
|
||||
ccall(:jl_close_uv, Void, (Ptr{Void},), proc.handle)
|
||||
proc.handle = C_NULL
|
||||
end
|
||||
nothing
|
||||
end
|
||||
|
||||
function uv_return_spawn(p::Ptr{Void}, exit_status::Int64, termsignal::Int32)
|
||||
data = ccall(:jl_uv_process_data, Ptr{Void}, (Ptr{Void},), p)
|
||||
data == C_NULL && return
|
||||
proc = unsafe_pointer_to_objref(data)::Process
|
||||
proc.exitcode = exit_status
|
||||
proc.termsignal = termsignal
|
||||
ccall(:jl_close_uv, Void, (Ptr{Void},), proc.handle)
|
||||
notify(proc.exitnotify)
|
||||
nothing
|
||||
end
|
||||
|
||||
function _uv_hook_close(proc::Process)
|
||||
proc.handle = C_NULL
|
||||
notify(proc.closenotify)
|
||||
end
|
||||
|
||||
function spawn(redirect::CmdRedirect, stdios::StdIOSet; chain::Nullable{ProcessChain}=Nullable{ProcessChain}())
|
||||
spawn(redirect.cmd,
|
||||
(redirect.stream_no == STDIN_NO ? redirect.handle : stdios[1],
|
||||
redirect.stream_no == STDOUT_NO ? redirect.handle : stdios[2],
|
||||
redirect.stream_no == STDERR_NO ? redirect.handle : stdios[3]),
|
||||
chain=chain)
|
||||
end
|
||||
|
||||
function spawn(cmds::OrCmds, stdios::StdIOSet; chain::Nullable{ProcessChain}=Nullable{ProcessChain}())
|
||||
out_pipe = Libc.malloc(_sizeof_uv_named_pipe)
|
||||
in_pipe = Libc.malloc(_sizeof_uv_named_pipe)
|
||||
link_pipe(in_pipe, false, out_pipe, false)
|
||||
if isnull(chain)
|
||||
chain = Nullable(ProcessChain(stdios))
|
||||
end
|
||||
try
|
||||
spawn(cmds.a, (stdios[1], out_pipe, stdios[3]), chain=chain)
|
||||
spawn(cmds.b, (in_pipe, stdios[2], stdios[3]), chain=chain)
|
||||
finally
|
||||
close_pipe_sync(out_pipe)
|
||||
close_pipe_sync(in_pipe)
|
||||
Libc.free(out_pipe)
|
||||
Libc.free(in_pipe)
|
||||
end
|
||||
get(chain)
|
||||
end
|
||||
|
||||
function spawn(cmds::ErrOrCmds, stdios::StdIOSet; chain::Nullable{ProcessChain}=Nullable{ProcessChain}())
|
||||
out_pipe = Libc.malloc(_sizeof_uv_named_pipe)
|
||||
in_pipe = Libc.malloc(_sizeof_uv_named_pipe)
|
||||
link_pipe(in_pipe, false, out_pipe, false)
|
||||
if isnull(chain)
|
||||
chain = Nullable(ProcessChain(stdios))
|
||||
end
|
||||
try
|
||||
spawn(cmds.a, (stdios[1], stdios[2], out_pipe), chain=chain)
|
||||
spawn(cmds.b, (in_pipe, stdios[2], stdios[3]), chain=chain)
|
||||
finally
|
||||
close_pipe_sync(out_pipe)
|
||||
close_pipe_sync(in_pipe)
|
||||
Libc.free(out_pipe)
|
||||
Libc.free(in_pipe)
|
||||
end
|
||||
get(chain)
|
||||
end
|
||||
|
||||
function setup_stdio(stdio::PipeEndpoint, readable::Bool)
|
||||
closeafter = false
|
||||
if stdio.status == StatusUninit
|
||||
if readable
|
||||
link_pipe(io, false, stdio, true)
|
||||
else
|
||||
link_pipe(stdio, true, io, false)
|
||||
end
|
||||
closeafter = true
|
||||
end
|
||||
return (stdio.handle, closeafter)
|
||||
end
|
||||
|
||||
function setup_stdio(stdio::Pipe, readable::Bool)
|
||||
if stdio.in.status == StatusUninit && stdio.out.status == StatusUninit
|
||||
link_pipe(stdio)
|
||||
end
|
||||
io = readable ? stdio.out : stdio.in
|
||||
return (io, false)
|
||||
end
|
||||
|
||||
function setup_stdio(stdio::IOStream, readable::Bool)
|
||||
io = Filesystem.File(RawFD(fd(stdio)))
|
||||
return (io, false)
|
||||
end
|
||||
|
||||
function setup_stdio(stdio::FileRedirect, readable::Bool)
|
||||
if readable
|
||||
attr = JL_O_RDONLY
|
||||
perm = zero(S_IRUSR)
|
||||
else
|
||||
attr = JL_O_WRONLY | JL_O_CREAT
|
||||
attr |= stdio.append ? JL_O_APPEND : JL_O_TRUNC
|
||||
perm = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
|
||||
end
|
||||
io = Filesystem.open(stdio.filename, attr, perm)
|
||||
return (io, true)
|
||||
end
|
||||
|
||||
function setup_stdio(io, readable::Bool)
|
||||
# if there is no specialization,
|
||||
# assume that uvhandle and uvtype are defined for it
|
||||
return io, false
|
||||
end
|
||||
|
||||
function setup_stdio(stdio::Ptr{Void}, readable::Bool)
|
||||
return (stdio, false)
|
||||
end
|
||||
|
||||
function close_stdio(stdio::Ptr{Void})
|
||||
close_pipe_sync(stdio)
|
||||
Libc.free(stdio)
|
||||
end
|
||||
|
||||
function close_stdio(stdio)
|
||||
close(stdio)
|
||||
end
|
||||
|
||||
function setup_stdio(anon::Function, stdio::StdIOSet)
|
||||
in, close_in = setup_stdio(stdio[1], true)
|
||||
out, close_out = setup_stdio(stdio[2], false)
|
||||
err, close_err = setup_stdio(stdio[3], false)
|
||||
anon(in, out, err)
|
||||
close_in && close_stdio(in)
|
||||
close_out && close_stdio(out)
|
||||
close_err && close_stdio(err)
|
||||
end
|
||||
|
||||
function spawn(cmd::Cmd, stdios::StdIOSet; chain::Nullable{ProcessChain}=Nullable{ProcessChain}())
|
||||
if isempty(cmd.exec)
|
||||
throw(ArgumentError("cannot spawn empty command"))
|
||||
end
|
||||
loop = eventloop()
|
||||
pp = Process(cmd, C_NULL, stdios[1], stdios[2], stdios[3])
|
||||
setup_stdio(stdios) do in, out, err
|
||||
pp.handle = _jl_spawn(cmd.exec[1], cmd.exec, loop, pp,
|
||||
in, out, err)
|
||||
end
|
||||
if !isnull(chain)
|
||||
push!(get(chain).processes, pp)
|
||||
end
|
||||
pp
|
||||
end
|
||||
|
||||
function spawn(cmds::AndCmds, stdios::StdIOSet; chain::Nullable{ProcessChain}=Nullable{ProcessChain}())
|
||||
if isnull(chain)
|
||||
chain = Nullable(ProcessChain(stdios))
|
||||
end
|
||||
setup_stdio(stdios) do in, out, err
|
||||
spawn(cmds.a, (in,out,err), chain=chain)
|
||||
spawn(cmds.b, (in,out,err), chain=chain)
|
||||
end
|
||||
get(chain)
|
||||
end
|
||||
|
||||
# INTERNAL
|
||||
# returns stdios:
|
||||
# A set of up to 256 stdio instructions, where each entry can be either:
|
||||
# | - An IO to be passed to the child
|
||||
# | - DevNull to pass /dev/null
|
||||
# | - An Filesystem.File object to redirect the output to
|
||||
# \ - A string specifying a filename to be opened
|
||||
|
||||
spawn_opts_swallow(stdios::StdIOSet) = (stdios,)
|
||||
spawn_opts_swallow(in::Redirectable=DevNull, out::Redirectable=DevNull, err::Redirectable=DevNull, args...) =
|
||||
((in, out, err), args...)
|
||||
spawn_opts_inherit(stdios::StdIOSet) = (stdios,)
|
||||
# pass original descriptors to child processes by default, because we might
|
||||
# have already exhausted and closed the libuv object for our standard streams.
|
||||
# this caused issue #8529.
|
||||
spawn_opts_inherit(in::Redirectable=RawFD(0), out::Redirectable=RawFD(1), err::Redirectable=RawFD(2), args...) =
|
||||
((in, out, err), args...)
|
||||
|
||||
spawn(cmds::AbstractCmd, args...; chain::Nullable{ProcessChain}=Nullable{ProcessChain}()) =
|
||||
spawn(cmds, spawn_opts_swallow(args...)...; chain=chain)
|
||||
|
||||
function eachline(cmd::AbstractCmd, stdin; chomp::Bool=true)
|
||||
stdout = Pipe()
|
||||
processes = spawn(cmd, (stdin,stdout,STDERR))
|
||||
close(stdout.in)
|
||||
out = stdout.out
|
||||
# implicitly close after reading lines, since we opened
|
||||
return EachLine(out, chomp=chomp,
|
||||
ondone=()->(close(out); success(processes) || pipeline_error(processes)))::EachLine
|
||||
end
|
||||
eachline(cmd::AbstractCmd; chomp::Bool=true) = eachline(cmd, DevNull, chomp=chomp)
|
||||
|
||||
# return a Process object to read-to/write-from the pipeline
|
||||
"""
|
||||
open(command, mode::AbstractString="r", stdio=DevNull)
|
||||
|
||||
Start running `command` asynchronously, and return a tuple `(stream,process)`. If `mode` is
|
||||
`"r"`, then `stream` reads from the process's standard output and `stdio` optionally
|
||||
specifies the process's standard input stream. If `mode` is `"w"`, then `stream` writes to
|
||||
the process's standard input and `stdio` optionally specifies the process's standard output
|
||||
stream.
|
||||
"""
|
||||
function open(cmds::AbstractCmd, mode::AbstractString="r", other::Redirectable=DevNull)
|
||||
if mode == "r"
|
||||
in = other
|
||||
out = io = Pipe()
|
||||
processes = spawn(cmds, (in,out,STDERR))
|
||||
close(out.in)
|
||||
elseif mode == "w"
|
||||
in = io = Pipe()
|
||||
out = other
|
||||
processes = spawn(cmds, (in,out,STDERR))
|
||||
close(in.out)
|
||||
else
|
||||
throw(ArgumentError("mode must be \"r\" or \"w\", not \"$mode\""))
|
||||
end
|
||||
return (io, processes)
|
||||
end
|
||||
|
||||
"""
|
||||
open(f::Function, command, mode::AbstractString="r", stdio=DevNull)
|
||||
|
||||
Similar to `open(command, mode, stdio)`, but calls `f(stream)` on the resulting read or
|
||||
write stream, then closes the stream and waits for the process to complete. Returns the
|
||||
value returned by `f`.
|
||||
"""
|
||||
function open(f::Function, cmds::AbstractCmd, args...)
|
||||
io, P = open(cmds, args...)
|
||||
ret = try
|
||||
f(io)
|
||||
catch
|
||||
kill(P)
|
||||
rethrow()
|
||||
finally
|
||||
close(io)
|
||||
end
|
||||
success(P) || pipeline_error(P)
|
||||
return ret
|
||||
end
|
||||
|
||||
# TODO: deprecate this
|
||||
|
||||
"""
|
||||
readandwrite(command)
|
||||
|
||||
Starts running a command asynchronously, and returns a tuple (stdout,stdin,process) of the
|
||||
output stream and input stream of the process, and the process object itself.
|
||||
"""
|
||||
function readandwrite(cmds::AbstractCmd)
|
||||
in = Pipe()
|
||||
out, processes = open(cmds, "r", in)
|
||||
(out, in, processes)
|
||||
end
|
||||
|
||||
function read(cmd::AbstractCmd, stdin::Redirectable=DevNull)
|
||||
out, procs = open(cmd, "r", stdin)
|
||||
bytes = read(out)
|
||||
!success(procs) && pipeline_error(procs)
|
||||
return bytes
|
||||
end
|
||||
|
||||
function readstring(cmd::AbstractCmd, stdin::Redirectable=DevNull)
|
||||
return String(read(cmd, stdin))
|
||||
end
|
||||
|
||||
function writeall(cmd::AbstractCmd, stdin::AbstractString, stdout::Redirectable=DevNull)
|
||||
open(cmd, "w", stdout) do io
|
||||
write(io, stdin)
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
run(command, args...)
|
||||
|
||||
Run a command object, constructed with backticks. Throws an error if anything goes wrong,
|
||||
including the process exiting with a non-zero status.
|
||||
"""
|
||||
function run(cmds::AbstractCmd, args...)
|
||||
ps = spawn(cmds, spawn_opts_inherit(args...)...)
|
||||
success(ps) ? nothing : pipeline_error(ps)
|
||||
end
|
||||
|
||||
const SIGPIPE = 13
|
||||
function test_success(proc::Process)
|
||||
assert(process_exited(proc))
|
||||
if proc.exitcode < 0
|
||||
#TODO: this codepath is not currently tested
|
||||
throw(UVError("could not start process $(string(proc.cmd))", proc.exitcode))
|
||||
end
|
||||
proc.exitcode == 0 && (proc.termsignal == 0 || proc.termsignal == SIGPIPE)
|
||||
end
|
||||
|
||||
function success(x::Process)
|
||||
wait(x)
|
||||
kill(x)
|
||||
test_success(x)
|
||||
end
|
||||
success(procs::Vector{Process}) = mapreduce(success, &, procs)
|
||||
success(procs::ProcessChain) = success(procs.processes)
|
||||
|
||||
"""
|
||||
success(command)
|
||||
|
||||
Run a command object, constructed with backticks, and tell whether it was successful (exited
|
||||
with a code of 0). An exception is raised if the process cannot be started.
|
||||
"""
|
||||
success(cmd::AbstractCmd) = success(spawn(cmd))
|
||||
|
||||
function pipeline_error(proc::Process)
|
||||
if !proc.cmd.ignorestatus
|
||||
error("failed process: ", proc, " [", proc.exitcode, "]")
|
||||
end
|
||||
nothing
|
||||
end
|
||||
|
||||
function pipeline_error(procs::ProcessChain)
|
||||
failed = Process[]
|
||||
for p = procs.processes
|
||||
if !test_success(p) && !p.cmd.ignorestatus
|
||||
push!(failed, p)
|
||||
end
|
||||
end
|
||||
isempty(failed) && return nothing
|
||||
length(failed) == 1 && pipeline_error(failed[1])
|
||||
msg = "failed processes:"
|
||||
for proc in failed
|
||||
msg = string(msg, "\n ", proc, " [", proc.exitcode, "]")
|
||||
end
|
||||
error(msg)
|
||||
end
|
||||
|
||||
_jl_kill(p::Process, signum::Integer) = ccall(:uv_process_kill, Int32, (Ptr{Void},Int32), p.handle, signum)
|
||||
|
||||
"""
|
||||
kill(p::Process, signum=SIGTERM)
|
||||
|
||||
Send a signal to a process. The default is to terminate the process.
|
||||
"""
|
||||
function kill(p::Process, signum::Integer)
|
||||
if process_running(p)
|
||||
@assert p.handle != C_NULL
|
||||
_jl_kill(p, signum)
|
||||
else
|
||||
Int32(-1)
|
||||
end
|
||||
end
|
||||
kill(ps::Vector{Process}) = map(kill, ps)
|
||||
kill(ps::ProcessChain) = map(kill, ps.processes)
|
||||
kill(p::Process) = kill(p, 15) #SIGTERM
|
||||
|
||||
function _contains_newline(bufptr::Ptr{Void}, len::Int32)
|
||||
return (ccall(:memchr, Ptr{Void}, (Ptr{Void},Int32,Csize_t), bufptr, '\n', len) != C_NULL)
|
||||
end
|
||||
|
||||
## process status ##
|
||||
|
||||
"""
|
||||
process_running(p::Process)
|
||||
|
||||
Determine whether a process is currently running.
|
||||
"""
|
||||
process_running(s::Process) = s.exitcode == typemin(fieldtype(Process, :exitcode))
|
||||
process_running(s::Vector{Process}) = any(process_running, s)
|
||||
process_running(s::ProcessChain) = process_running(s.processes)
|
||||
|
||||
"""
|
||||
process_exited(p::Process)
|
||||
|
||||
Determine whether a process has exited.
|
||||
"""
|
||||
process_exited(s::Process) = !process_running(s)
|
||||
process_exited(s::Vector{Process}) = all(process_exited, s)
|
||||
process_exited(s::ProcessChain) = process_exited(s.processes)
|
||||
|
||||
process_signaled(s::Process) = (s.termsignal > 0)
|
||||
|
||||
#process_stopped (s::Process) = false #not supported by libuv. Do we need this?
|
||||
#process_stop_signal(s::Process) = false #not supported by libuv. Do we need this?
|
||||
|
||||
function process_status(s::Process)
|
||||
process_running(s) ? "ProcessRunning" :
|
||||
process_signaled(s) ? "ProcessSignaled("*string(s.termsignal)*")" :
|
||||
#process_stopped(s) ? "ProcessStopped("*string(process_stop_signal(s))*")" :
|
||||
process_exited(s) ? "ProcessExited("*string(s.exitcode)*")" :
|
||||
error("process status error")
|
||||
end
|
||||
|
||||
## implementation of `cmd` syntax ##
|
||||
|
||||
arg_gen() = String[]
|
||||
arg_gen(x::AbstractString) = String[cstr(x)]
|
||||
arg_gen(cmd::Cmd) = cmd.exec
|
||||
|
||||
function arg_gen(head)
|
||||
if applicable(start, head)
|
||||
vals = String[]
|
||||
for x in head
|
||||
push!(vals, cstr(string(x)))
|
||||
end
|
||||
return vals
|
||||
else
|
||||
return String[cstr(string(head))]
|
||||
end
|
||||
end
|
||||
|
||||
function arg_gen(head, tail...)
|
||||
head = arg_gen(head)
|
||||
tail = arg_gen(tail...)
|
||||
vals = String[]
|
||||
for h = head, t = tail
|
||||
push!(vals, cstr(string(h,t)))
|
||||
end
|
||||
return vals
|
||||
end
|
||||
|
||||
function cmd_gen(parsed)
|
||||
args = String[]
|
||||
for arg in parsed
|
||||
append!(args, arg_gen(arg...))
|
||||
end
|
||||
return Cmd(args)
|
||||
end
|
||||
|
||||
macro cmd(str)
|
||||
return :(cmd_gen($(esc(shell_parse(str, special=shell_special)[1]))))
|
||||
end
|
||||
|
||||
wait(x::Process) = if !process_exited(x); stream_wait(x, x.exitnotify); end
|
||||
wait(x::ProcessChain) = for p in x.processes; wait(p); end
|
||||
|
||||
show(io::IO, p::Process) = print(io, "Process(", p.cmd, ", ", process_status(p), ")")
|
||||
Reference in New Issue
Block a user