Make process-spawning take environments and working directories, remove procsrv task from compiletest.

This commit is contained in:
Graydon Hoare 2012-02-07 18:55:02 -08:00
parent 5131216fa6
commit 93450abb4b
18 changed files with 328 additions and 286 deletions

View file

@ -28,5 +28,3 @@ type config =
runtool: option<str>, runtool: option<str>,
rustcflags: option<str>, rustcflags: option<str>,
verbose: bool}; verbose: bool};
type cx = {config: config, procsrv: procsrv::handle};

View file

@ -17,7 +17,6 @@ import comm::chan;
import comm::send; import comm::send;
import comm::recv; import comm::recv;
import common::cx;
import common::config; import common::config;
import common::mode_run_pass; import common::mode_run_pass;
import common::mode_run_fail; import common::mode_run_fail;
@ -113,10 +112,8 @@ fn mode_str(mode: mode) -> str {
fn run_tests(config: config) { fn run_tests(config: config) {
let opts = test_opts(config); let opts = test_opts(config);
let cx = {config: config, procsrv: procsrv::mk()}; let tests = make_tests(config);
let tests = make_tests(cx);
let res = test::run_tests_console(opts, tests); let res = test::run_tests_console(opts, tests);
procsrv::close(cx.procsrv);
if !res { fail "Some tests failed"; } if !res { fail "Some tests failed"; }
} }
@ -129,14 +126,14 @@ fn test_opts(config: config) -> test::test_opts {
run_ignored: config.run_ignored} run_ignored: config.run_ignored}
} }
fn make_tests(cx: cx) -> [test::test_desc] { fn make_tests(config: config) -> [test::test_desc] {
#debug("making tests from %s", cx.config.src_base); #debug("making tests from %s", config.src_base);
let tests = []; let tests = [];
for file: str in fs::list_dir(cx.config.src_base) { for file: str in fs::list_dir(config.src_base) {
let file = file; let file = file;
#debug("inspecting file %s", file); #debug("inspecting file %s", file);
if is_test(cx.config, file) { if is_test(config, file) {
tests += [make_test(cx, file)] tests += [make_test(config, file)]
} }
} }
ret tests; ret tests;
@ -162,12 +159,12 @@ fn is_test(config: config, testfile: str) -> bool {
ret valid; ret valid;
} }
fn make_test(cx: cx, testfile: str) -> fn make_test(config: config, testfile: str) ->
test::test_desc { test::test_desc {
{ {
name: make_test_name(cx.config, testfile), name: make_test_name(config, testfile),
fn: make_test_closure(cx, testfile), fn: make_test_closure(config, testfile),
ignore: header::is_test_ignored(cx.config, testfile), ignore: header::is_test_ignored(config, testfile),
should_fail: false should_fail: false
} }
} }
@ -176,24 +173,12 @@ fn make_test_name(config: config, testfile: str) -> str {
#fmt["[%s] %s", mode_str(config.mode), testfile] #fmt["[%s] %s", mode_str(config.mode), testfile]
} }
fn make_test_closure(cx: cx, testfile: str) -> test::test_fn { fn make_test_closure(config: config, testfile: str) -> test::test_fn {
let config = cx.config;
let chan = cx.procsrv.chan;
ret {|| ret {||
run_test_task(config, chan, testfile); runtest::run(config, copy testfile);
}; };
} }
fn run_test_task(config: common::config,
procsrv_chan: procsrv::reqchan,
testfile: str) {
let procsrv = procsrv::from_chan(procsrv_chan);
let cx = {config: config, procsrv: procsrv};
runtest::run(cx, copy testfile);
}
// Local Variables: // Local Variables:
// fill-column: 78; // fill-column: 78;
// indent-tabs-mode: nil // indent-tabs-mode: nil

View file

@ -1,71 +1,60 @@
// So when running tests in parallel there's a potential race on environment
// variables if we let each task spawn its own children - between the time the
// environment is set and the process is spawned another task could spawn its
// child process. Because of that we have to use a complicated scheme with a
// dedicated server for spawning processes.
import std::generic_os::setenv;
import std::generic_os::getenv;
import std::os;
import std::run; import std::run;
import std::run::spawn_process;
import std::io; import std::io;
import std::os;
import io::writer_util; import io::writer_util;
import comm::chan;
import comm::port;
import comm::send;
import comm::recv;
import ctypes::{pid_t, fd_t}; import ctypes::{pid_t, fd_t};
export handle;
export mk;
export from_chan;
export run; export run;
export close;
export reqchan;
type reqchan = chan<request>; #[cfg(target_os = "win32")]
fn target_env(lib_path: str, prog: str) -> option<[(str,str)]> {
type handle = let env = std::generic_os::env();
{task: option<(task::task, port<task::task_notification>)>,
chan: reqchan};
enum request { exec([u8], [u8], [[u8]], chan<response>), stop, } env = vec::map(env) {|pair|
let (k,v) = pair;
type response = {pid: pid_t, infd: fd_t, if k == "PATH" { ("PATH", v + ";" + lib_path) }
outfd: fd_t, errfd: fd_t}; else { (k,v) }
fn mk() -> handle {
let setupport = port();
let setupchan = chan(setupport);
let task = task::spawn_joinable {||
let reqport = port();
let reqchan = chan(reqport);
send(setupchan, reqchan);
worker(reqport);
}; };
ret {task: option::some(task), chan: recv(setupport)}; if str::ends_with(prog, "rustc.exe") {
env += [("RUST_THREADS", "1")]
}
ret some(env);
} }
fn from_chan(ch: reqchan) -> handle { {task: option::none, chan: ch} } #[cfg(target_os = "linux")]
#[cfg(target_os = "macos")]
fn close(handle: handle) { #[cfg(target_os = "freebsd")]
send(handle.chan, stop); fn target_env(lib_path: str, prog: str) -> option<[(str,str)]> {
task::join(option::get(handle.task)); none
} }
fn run(handle: handle, lib_path: str, prog: str, args: [str],
fn run(lib_path: str, prog: str, args: [str],
input: option<str>) -> {status: int, out: str, err: str} { input: option<str>) -> {status: int, out: str, err: str} {
let p = port();
let ch = chan(p);
send(handle.chan,
exec(str::bytes(lib_path), str::bytes(prog), clone_vecstr(args),
ch));
let resp = recv(p);
writeclose(resp.infd, input); let pipe_in = os::pipe();
let output = readclose(resp.outfd); let pipe_out = os::pipe();
let errput = readclose(resp.errfd); let pipe_err = os::pipe();
let status = run::waitpid(resp.pid); let pid = spawn_process(prog, args, target_env(lib_path, prog), none,
pipe_in.in, pipe_out.out, pipe_err.out);
os::close(pipe_in.in);
os::close(pipe_out.out);
os::close(pipe_err.out);
if pid == -1i32 {
os::close(pipe_in.out);
os::close(pipe_out.in);
os::close(pipe_err.in);
fail;
}
writeclose(pipe_in.out, input);
let output = readclose(pipe_out.in);
let errput = readclose(pipe_err.in);
let status = run::waitpid(pid);
ret {status: status, out: output, err: errput}; ret {status: status, out: output, err: errput};
} }
@ -90,106 +79,3 @@ fn readclose(fd: fd_t) -> str {
os::fclose(file); os::fclose(file);
ret buf; ret buf;
} }
fn worker(p: port<request>) {
// FIXME (787): If we declare this inside of the while loop and then
// break out of it before it's ever initialized (i.e. we don't run
// any tests), then the cleanups will puke.
let execparms;
while true {
// FIXME: Sending strings across channels seems to still
// leave them refed on the sender's end, which causes problems if
// the receiver's poniters outlive the sender's. Here we clone
// everything and let the originals go out of scope before sending
// a response.
execparms =
{
// FIXME (785): The 'discriminant' of an alt expression has
// the same scope as the alt expression itself, so we have to
// put the entire alt in another block to make sure the exec
// message goes out of scope. Seems like the scoping rules for
// the alt discriminant are wrong.
alt recv(p) {
exec(lib_path, prog, args, respchan) {
{lib_path: str::from_bytes(lib_path),
prog: str::from_bytes(prog),
args: clone_vecu8str(args),
respchan: respchan}
}
stop { ret }
}
};
// This is copied from run::start_program
let pipe_in = os::pipe();
let pipe_out = os::pipe();
let pipe_err = os::pipe();
let spawnproc =
bind run::spawn_process(execparms.prog, execparms.args,
pipe_in.in, pipe_out.out, pipe_err.out);
let pid = maybe_with_lib_path(execparms.lib_path, spawnproc);
os::close(pipe_in.in);
os::close(pipe_out.out);
os::close(pipe_err.out);
if pid == -1i32 {
os::close(pipe_in.out);
os::close(pipe_out.in);
os::close(pipe_err.in);
fail;
}
send(execparms.respchan,
{pid: pid,
infd: pipe_in.out,
outfd: pipe_out.in,
errfd: pipe_err.in});
}
}
// Only windows needs to set the library path
#[cfg(target_os = "win32")]
fn maybe_with_lib_path<T>(path: str, f: fn@() -> T) -> T {
with_lib_path(path, f)
}
#[cfg(target_os = "linux")]
#[cfg(target_os = "macos")]
#[cfg(target_os = "freebsd")]
fn maybe_with_lib_path<T>(_path: str, f: fn@() -> T) -> T {
f()
}
fn with_lib_path<T>(path: str, f: fn@() -> T) -> T {
let maybe_oldpath = getenv(util::lib_path_env_var());
append_lib_path(path);
let res = f();
if option::is_some(maybe_oldpath) {
export_lib_path(option::get(maybe_oldpath));
} else {
// FIXME: This should really be unset but we don't have that yet
export_lib_path("");
}
ret res;
}
fn append_lib_path(path: str) { export_lib_path(util::make_new_path(path)); }
fn export_lib_path(path: str) { setenv(util::lib_path_env_var(), path); }
fn clone_vecstr(v: [str]) -> [[u8]] {
let r = [];
for t: str in vec::slice(v, 0u, vec::len(v)) { r += [str::bytes(t)]; }
ret r;
}
fn clone_vecu8str(v: [[u8]]) -> [str] {
let r = [];
for t in vec::slice(v, 0u, vec::len(v)) {
r += [str::from_bytes(t)];
}
ret r;
}

View file

@ -7,7 +7,6 @@ import common::mode_run_pass;
import common::mode_run_fail; import common::mode_run_fail;
import common::mode_compile_fail; import common::mode_compile_fail;
import common::mode_pretty; import common::mode_pretty;
import common::cx;
import common::config; import common::config;
import header::load_props; import header::load_props;
import header::test_props; import header::test_props;
@ -15,23 +14,23 @@ import util::logv;
export run; export run;
fn run(cx: cx, testfile: str) { fn run(config: config, testfile: str) {
if cx.config.verbose { if config.verbose {
// We're going to be dumping a lot of info. Start on a new line. // We're going to be dumping a lot of info. Start on a new line.
io::stdout().write_str("\n\n"); io::stdout().write_str("\n\n");
} }
#debug("running %s", testfile); #debug("running %s", testfile);
let props = load_props(testfile); let props = load_props(testfile);
alt cx.config.mode { alt config.mode {
mode_compile_fail { run_cfail_test(cx, props, testfile); } mode_compile_fail { run_cfail_test(config, props, testfile); }
mode_run_fail { run_rfail_test(cx, props, testfile); } mode_run_fail { run_rfail_test(config, props, testfile); }
mode_run_pass { run_rpass_test(cx, props, testfile); } mode_run_pass { run_rpass_test(config, props, testfile); }
mode_pretty { run_pretty_test(cx, props, testfile); } mode_pretty { run_pretty_test(config, props, testfile); }
} }
} }
fn run_cfail_test(cx: cx, props: test_props, testfile: str) { fn run_cfail_test(config: config, props: test_props, testfile: str) {
let procres = compile_test(cx, props, testfile); let procres = compile_test(config, props, testfile);
if procres.status == 0 { if procres.status == 0 {
fatal_procres("compile-fail test compiled successfully!", procres); fatal_procres("compile-fail test compiled successfully!", procres);
@ -50,12 +49,12 @@ fn run_cfail_test(cx: cx, props: test_props, testfile: str) {
} }
} }
fn run_rfail_test(cx: cx, props: test_props, testfile: str) { fn run_rfail_test(config: config, props: test_props, testfile: str) {
let procres = compile_test(cx, props, testfile); let procres = compile_test(config, props, testfile);
if procres.status != 0 { fatal_procres("compilation failed!", procres); } if procres.status != 0 { fatal_procres("compilation failed!", procres); }
procres = exec_compiled_test(cx, props, testfile); procres = exec_compiled_test(config, props, testfile);
// The value our Makefile configures valgrind to return on failure // The value our Makefile configures valgrind to return on failure
const valgrind_err: int = 100; const valgrind_err: int = 100;
@ -78,21 +77,21 @@ fn check_correct_failure_status(procres: procres) {
} }
} }
fn run_rpass_test(cx: cx, props: test_props, testfile: str) { fn run_rpass_test(config: config, props: test_props, testfile: str) {
let procres = compile_test(cx, props, testfile); let procres = compile_test(config, props, testfile);
if procres.status != 0 { fatal_procres("compilation failed!", procres); } if procres.status != 0 { fatal_procres("compilation failed!", procres); }
procres = exec_compiled_test(cx, props, testfile); procres = exec_compiled_test(config, props, testfile);
if procres.status != 0 { fatal_procres("test run failed!", procres); } if procres.status != 0 { fatal_procres("test run failed!", procres); }
} }
fn run_pretty_test(cx: cx, props: test_props, testfile: str) { fn run_pretty_test(config: config, props: test_props, testfile: str) {
if option::is_some(props.pp_exact) { if option::is_some(props.pp_exact) {
logv(cx.config, "testing for exact pretty-printing"); logv(config, "testing for exact pretty-printing");
} else { logv(cx.config, "testing for converging pretty-printing"); } } else { logv(config, "testing for converging pretty-printing"); }
let rounds = let rounds =
alt props.pp_exact { option::some(_) { 1 } option::none { 2 } }; alt props.pp_exact { option::some(_) { 1 } option::none { 2 } };
@ -101,8 +100,8 @@ fn run_pretty_test(cx: cx, props: test_props, testfile: str) {
let round = 0; let round = 0;
while round < rounds { while round < rounds {
logv(cx.config, #fmt["pretty-printing round %d", round]); logv(config, #fmt["pretty-printing round %d", round]);
let procres = print_source(cx, testfile, srcs[round]); let procres = print_source(config, testfile, srcs[round]);
if procres.status != 0 { if procres.status != 0 {
fatal_procres(#fmt["pretty-printing failed in round %d", round], fatal_procres(#fmt["pretty-printing failed in round %d", round],
@ -134,7 +133,7 @@ fn run_pretty_test(cx: cx, props: test_props, testfile: str) {
compare_source(expected, actual); compare_source(expected, actual);
// Finally, let's make sure it actually appears to remain valid code // Finally, let's make sure it actually appears to remain valid code
let procres = typecheck_source(cx, testfile, actual); let procres = typecheck_source(config, testfile, actual);
if procres.status != 0 { if procres.status != 0 {
fatal_procres("pretty-printed source does not typecheck", procres); fatal_procres("pretty-printed source does not typecheck", procres);
@ -142,9 +141,9 @@ fn run_pretty_test(cx: cx, props: test_props, testfile: str) {
ret; ret;
fn print_source(cx: cx, testfile: str, src: str) -> procres { fn print_source(config: config, testfile: str, src: str) -> procres {
compose_and_run(cx, testfile, make_pp_args, compose_and_run(config, testfile, make_pp_args,
cx.config.compile_lib_path, option::some(src)) config.compile_lib_path, option::some(src))
} }
fn make_pp_args(config: config, _testfile: str) -> procargs { fn make_pp_args(config: config, _testfile: str) -> procargs {
@ -173,9 +172,9 @@ actual:\n\
} }
} }
fn typecheck_source(cx: cx, testfile: str, src: str) -> procres { fn typecheck_source(config: config, testfile: str, src: str) -> procres {
compose_and_run(cx, testfile, make_typecheck_args, compose_and_run(config, testfile, make_typecheck_args,
cx.config.compile_lib_path, option::some(src)) config.compile_lib_path, option::some(src))
} }
fn make_typecheck_args(config: config, _testfile: str) -> procargs { fn make_typecheck_args(config: config, _testfile: str) -> procargs {
@ -286,22 +285,24 @@ type procargs = {prog: str, args: [str]};
type procres = {status: int, stdout: str, stderr: str, cmdline: str}; type procres = {status: int, stdout: str, stderr: str, cmdline: str};
fn compile_test(cx: cx, props: test_props, testfile: str) -> procres { fn compile_test(config: config, props: test_props,
compose_and_run(cx, testfile, bind make_compile_args(_, props, _), testfile: str) -> procres {
cx.config.compile_lib_path, option::none) compose_and_run(config, testfile, bind make_compile_args(_, props, _),
config.compile_lib_path, option::none)
} }
fn exec_compiled_test(cx: cx, props: test_props, testfile: str) -> procres { fn exec_compiled_test(config: config, props: test_props,
compose_and_run(cx, testfile, bind make_run_args(_, props, _), testfile: str) -> procres {
cx.config.run_lib_path, option::none) compose_and_run(config, testfile, bind make_run_args(_, props, _),
config.run_lib_path, option::none)
} }
fn compose_and_run(cx: cx, testfile: str, fn compose_and_run(config: config, testfile: str,
make_args: fn@(config, str) -> procargs, lib_path: str, make_args: fn@(config, str) -> procargs, lib_path: str,
input: option<str>) -> procres { input: option<str>) -> procres {
let procargs = make_args(cx.config, testfile); let procargs = make_args(config, testfile);
ret program_output(cx, testfile, lib_path, procargs.prog, procargs.args, ret program_output(config, testfile, lib_path,
input); procargs.prog, procargs.args, input);
} }
fn make_compile_args(config: config, props: test_props, testfile: str) -> fn make_compile_args(config: config, props: test_props, testfile: str) ->
@ -354,16 +355,16 @@ fn split_maybe_args(argstr: option<str>) -> [str] {
} }
} }
fn program_output(cx: cx, testfile: str, lib_path: str, prog: str, fn program_output(config: config, testfile: str, lib_path: str, prog: str,
args: [str], input: option<str>) -> procres { args: [str], input: option<str>) -> procres {
let cmdline = let cmdline =
{ {
let cmdline = make_cmdline(lib_path, prog, args); let cmdline = make_cmdline(lib_path, prog, args);
logv(cx.config, #fmt["executing %s", cmdline]); logv(config, #fmt["executing %s", cmdline]);
cmdline cmdline
}; };
let res = procsrv::run(cx.procsrv, lib_path, prog, args, input); let res = procsrv::run(lib_path, prog, args, input);
dump_output(cx.config, testfile, res.out, res.err); dump_output(config, testfile, res.out, res.err);
ret {status: res.status, ret {status: res.status,
stdout: res.out, stdout: res.out,
stderr: res.err, stderr: res.err,

View file

@ -1298,15 +1298,6 @@ const tag_five_b: uint = 248u;
const max_five_b: uint = 67108864u; const max_five_b: uint = 67108864u;
const tag_six_b: uint = 252u; const tag_six_b: uint = 252u;
// NB: This is intentionally unexported because it's easy to misuse (there's
// no guarantee that the string is rooted). Instead, use as_buf below.
unsafe fn buf(s: str) -> sbuf {
let saddr = ptr::addr_of(s);
let vaddr: *[u8] = ::unsafe::reinterpret_cast(saddr);
let buf = vec::to_ptr(*vaddr);
ret buf;
}
/* /*
Function: as_buf Function: as_buf
@ -1319,7 +1310,10 @@ Example:
*/ */
fn as_buf<T>(s: str, f: fn(sbuf) -> T) -> T unsafe { fn as_buf<T>(s: str, f: fn(sbuf) -> T) -> T unsafe {
let buf = buf(s); f(buf) let v: [u8] = ::unsafe::reinterpret_cast(s);
let r = vec::as_buf(v, f);
::unsafe::leak(v);
r
} }
/* /*

View file

@ -1077,6 +1077,18 @@ FIXME: We don't need this wrapper
*/ */
unsafe fn to_ptr<T>(v: [T]) -> *T { ret unsafe::to_ptr(v); } unsafe fn to_ptr<T>(v: [T]) -> *T { ret unsafe::to_ptr(v); }
/*
Function: as_buf
Work with the buffer of a vector. Allows for unsafe manipulation
of vector contents, which is useful for native interop.
*/
fn as_buf<E,T>(v: [const E], f: fn(*E) -> T) -> T unsafe {
let buf = unsafe::to_ptr(v); f(buf)
}
/* /*
Module: unsafe Module: unsafe
*/ */

View file

@ -20,6 +20,7 @@ export target_os;
export dylib_filename; export dylib_filename;
export get_exe_path; export get_exe_path;
export fsync_fd; export fsync_fd;
export rustrt;
// FIXME Somehow merge stuff duplicated here and macosx_os.rs. Made difficult // FIXME Somehow merge stuff duplicated here and macosx_os.rs. Made difficult
// by https://github.com/graydon/rust/issues#issue/268 // by https://github.com/graydon/rust/issues#issue/268
@ -116,6 +117,7 @@ fn waitpid(pid: pid_t) -> i32 {
#[abi = "cdecl"] #[abi = "cdecl"]
native mod rustrt { native mod rustrt {
fn rust_env_pairs() -> [str];
fn rust_getcwd() -> str; fn rust_getcwd() -> str;
} }

View file

@ -11,21 +11,23 @@ import core::option;
// Wow, this is an ugly way to write doc comments // Wow, this is an ugly way to write doc comments
#[cfg(bogus)] #[cfg(bogus)]
/* #[doc = "Get the value of an environment variable"]
Function: getenv
Get the value of an environment variable
*/
fn getenv(n: str) -> option<str> { } fn getenv(n: str) -> option<str> { }
#[cfg(bogus)] #[cfg(bogus)]
/* #[doc = "Set the value of an environment variable"]
Function: setenv
Set the value of an environment variable
*/
fn setenv(n: str, v: str) { } fn setenv(n: str, v: str) { }
fn env() -> [(str,str)] {
let pairs = [];
for p in os::rustrt::rust_env_pairs() {
let vs = str::split(p, '=' as u8);
assert vec::len(vs) == 2u;
pairs += [(vs[0], vs[1])];
}
ret pairs;
}
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
#[cfg(target_os = "freebsd")] #[cfg(target_os = "freebsd")]

View file

@ -20,6 +20,7 @@ export target_os;
export dylib_filename; export dylib_filename;
export get_exe_path; export get_exe_path;
export fsync_fd; export fsync_fd;
export rustrt;
// FIXME Somehow merge stuff duplicated here and macosx_os.rs. Made difficult // FIXME Somehow merge stuff duplicated here and macosx_os.rs. Made difficult
// by https://github.com/graydon/rust/issues#issue/268 // by https://github.com/graydon/rust/issues#issue/268
@ -112,6 +113,7 @@ fn waitpid(pid: pid_t) -> i32 {
#[abi = "cdecl"] #[abi = "cdecl"]
native mod rustrt { native mod rustrt {
fn rust_env_pairs() -> [str];
fn rust_getcwd() -> str; fn rust_getcwd() -> str;
} }

View file

@ -14,6 +14,7 @@ export target_os;
export dylib_filename; export dylib_filename;
export get_exe_path; export get_exe_path;
export fsync_fd; export fsync_fd;
export rustrt;
// FIXME Refactor into unix_os module or some such. Doesn't // FIXME Refactor into unix_os module or some such. Doesn't
// seem to work right now. // seem to work right now.
@ -115,6 +116,7 @@ fn fsync_fd(fd: fd_t, level: io::fsync::level) -> c_int {
#[abi = "cdecl"] #[abi = "cdecl"]
native mod rustrt { native mod rustrt {
fn rust_env_pairs() -> [str];
fn rust_getcwd() -> str; fn rust_getcwd() -> str;
} }

View file

@ -5,7 +5,7 @@ Process spawning
*/ */
import option::{some, none}; import option::{some, none};
import str::sbuf; import str::sbuf;
import ctypes::{fd_t, pid_t}; import ctypes::{fd_t, pid_t, void};
export program; export program;
export run_program; export run_program;
@ -16,8 +16,9 @@ export waitpid;
#[abi = "cdecl"] #[abi = "cdecl"]
native mod rustrt { native mod rustrt {
fn rust_run_program(argv: *sbuf, in_fd: fd_t, fn rust_run_program(argv: *sbuf, envp: *void, dir: sbuf,
out_fd: fd_t, err_fd: fd_t) -> pid_t; in_fd: fd_t, out_fd: fd_t, err_fd: fd_t)
-> pid_t;
} }
/* Section: Types */ /* Section: Types */
@ -82,13 +83,6 @@ iface program {
/* Section: Operations */ /* Section: Operations */
fn arg_vec(prog: str, args: [@str]) -> [sbuf] {
let argptrs = str::as_buf(prog, {|buf| [buf] });
for arg in args { argptrs += str::as_buf(*arg, {|buf| [buf] }); }
argptrs += [ptr::null()];
ret argptrs;
}
/* /*
Function: spawn_process Function: spawn_process
@ -98,6 +92,8 @@ Parameters:
prog - The path to an executable prog - The path to an executable
args - Vector of arguments to pass to the child process args - Vector of arguments to pass to the child process
env - optional env-modification for child
dir - optional dir to run child in (default current dir)
in_fd - A file descriptor for the child to use as std input in_fd - A file descriptor for the child to use as std input
out_fd - A file descriptor for the child to use as std output out_fd - A file descriptor for the child to use as std output
err_fd - A file descriptor for the child to use as std error err_fd - A file descriptor for the child to use as std error
@ -106,18 +102,90 @@ Returns:
The process id of the spawned process The process id of the spawned process
*/ */
fn spawn_process(prog: str, args: [str], in_fd: fd_t, fn spawn_process(prog: str, args: [str],
out_fd: fd_t, err_fd: fd_t) env: option<[(str,str)]>,
dir: option<str>,
in_fd: fd_t, out_fd: fd_t, err_fd: fd_t)
-> pid_t unsafe { -> pid_t unsafe {
// Note: we have to hold on to these vector references while we hold a with_argv(prog, args) {|argv|
// pointer to their buffers with_envp(env) { |envp|
let prog = prog; with_dirp(dir) { |dirp|
let args = vec::map(args, {|arg| @arg }); rustrt::rust_run_program(argv, envp, dirp,
let argv = arg_vec(prog, args); in_fd, out_fd, err_fd)
let pid = }
rustrt::rust_run_program(vec::unsafe::to_ptr(argv), in_fd, out_fd, }
err_fd); }
ret pid; }
fn with_argv<T>(prog: str, args: [str],
cb: fn(*sbuf) -> T) -> T unsafe {
let argptrs = str::as_buf(prog) {|b| [b] };
let tmps = [];
for arg in args {
let t = @arg;
tmps += [t];
argptrs += str::as_buf(*t) {|b| [b] };
}
argptrs += [ptr::null()];
vec::as_buf(argptrs, cb)
}
#[cfg(target_os = "macos")]
#[cfg(target_os = "linux")]
#[cfg(target_os = "freebsd")]
fn with_envp<T>(env: option<[(str,str)]>,
cb: fn(*void) -> T) -> T unsafe {
// On posixy systems we can pass a char** for envp, which is
// a null-terminated array of "k=v\n" strings.
alt env {
some (es) {
let tmps = [];
let ptrs = [];
for (k,v) in es {
let t = @(#fmt("%s=%s", k, v));
vec::push(tmps, t);
ptrs += str::as_buf(*t) {|b| [b]};
}
ptrs += [ptr::null()];
vec::as_buf(ptrs) { |p| cb(::unsafe::reinterpret_cast(p)) }
}
none {
cb(ptr::null())
}
}
}
#[cfg(target_os = "win32")]
fn with_envp<T>(env: option<[(str,str)]>,
cb: fn(*void) -> T) -> T unsafe {
// On win32 we pass an "environment block" which is not a char**, but
// rather a concatenation of null-terminated k=v\0 sequences, with a final
// \0 to terminate.
alt env {
some (es) {
let blk : [u8] = [];
for (k,v) in es {
let t = #fmt("%s=%s", k, v);
let v : [u8] = ::unsafe::reinterpret_cast(t);
blk += v;
::unsafe::leak(v);
}
blk += [0_u8];
vec::as_buf(blk) {|p| cb(::unsafe::reinterpret_cast(p)) }
}
none {
cb(ptr::null())
}
}
}
fn with_dirp<T>(d: option<str>,
cb: fn(sbuf) -> T) -> T unsafe {
alt d {
some(dir) { str::as_buf(dir, cb) }
none { cb(ptr::null()) }
}
} }
/* /*
@ -135,7 +203,8 @@ Returns:
The process id The process id
*/ */
fn run_program(prog: str, args: [str]) -> int { fn run_program(prog: str, args: [str]) -> int {
ret waitpid(spawn_process(prog, args, 0i32, 0i32, 0i32)); ret waitpid(spawn_process(prog, args, none, none,
0i32, 0i32, 0i32));
} }
/* /*
@ -161,7 +230,8 @@ fn start_program(prog: str, args: [str]) -> program {
let pipe_output = os::pipe(); let pipe_output = os::pipe();
let pipe_err = os::pipe(); let pipe_err = os::pipe();
let pid = let pid =
spawn_process(prog, args, pipe_input.in, pipe_output.out, spawn_process(prog, args, none, none,
pipe_input.in, pipe_output.out,
pipe_err.out); pipe_err.out);
if pid == -1i32 { fail; } if pid == -1i32 { fail; }
@ -316,7 +386,8 @@ mod tests {
let pid = let pid =
run::spawn_process( run::spawn_process(
"cat", [], pipe_in.in, pipe_out.out, pipe_err.out); "cat", [], none, none,
pipe_in.in, pipe_out.out, pipe_err.out);
os::close(pipe_in.in); os::close(pipe_in.in);
os::close(pipe_out.out); os::close(pipe_out.out);
os::close(pipe_err.out); os::close(pipe_err.out);
@ -356,7 +427,9 @@ mod tests {
#[test] #[test]
fn waitpid() { fn waitpid() {
let pid = run::spawn_process("false", [], 0i32, 0i32, 0i32); let pid = run::spawn_process("false", [],
none, none,
0i32, 0i32, 0i32);
let status = run::waitpid(pid); let status = run::waitpid(pid);
assert status == 1; assert status == 1;
} }

View file

@ -227,10 +227,19 @@ fn run_tests(opts: test_opts, tests: [test_desc],
} }
} }
// Windows tends to dislike being overloaded with threads.
#[cfg(target_os = "win32")]
const sched_overcommit : uint = 1u;
#[cfg(target_os = "linux")]
#[cfg(target_os = "freebsd")]
#[cfg(target_os = "macos")]
const sched_overcommit : uint = 4u;
fn get_concurrency() -> uint { fn get_concurrency() -> uint {
let threads = rustrt::sched_threads(); let threads = rustrt::sched_threads();
if threads == 1u { 1u } if threads == 1u { 1u }
else { threads * 4u } else { threads * sched_overcommit }
} }
fn filter_tests(opts: test_opts, fn filter_tests(opts: test_opts,

View file

@ -106,6 +106,7 @@ fn fsync_fd(_fd: fd_t, _level: io::fsync::level) -> c_int {
#[abi = "cdecl"] #[abi = "cdecl"]
native mod rustrt { native mod rustrt {
fn rust_env_pairs() -> [str];
fn rust_process_wait(handle: c_int) -> c_int; fn rust_process_wait(handle: c_int) -> c_int;
fn rust_getcwd() -> str; fn rust_getcwd() -> str;
} }

View file

@ -41,16 +41,7 @@ command_line_args : public kernel_owned<command_line_args>
LocalFree(wargv); LocalFree(wargv);
#endif #endif
args = (rust_vec *) args = make_str_vec(kernel, argc, argv);
kernel->malloc(vec_size<rust_vec*>(argc),
"command line arg interior");
args->fill = args->alloc = sizeof(rust_vec*) * argc;
for (int i = 0; i < argc; ++i) {
rust_str *str = make_str(kernel, argv[i],
strlen(argv[i]),
"command line arg");
((rust_str**)&args->data)[i] = str;
}
} }
~command_line_args() { ~command_line_args() {

View file

@ -7,6 +7,10 @@
#include "rust_scheduler.h" #include "rust_scheduler.h"
#include "sync/timer.h" #include "sync/timer.h"
#ifdef __APPLE__
#include <crt_externs.h>
#endif
#if !defined(__WIN32__) #if !defined(__WIN32__)
#include <sys/time.h> #include <sys/time.h>
#endif #endif
@ -73,6 +77,49 @@ rust_getcwd() {
return make_str(task->kernel, cbuf, strlen(cbuf), "rust_str(getcwd"); return make_str(task->kernel, cbuf, strlen(cbuf), "rust_str(getcwd");
} }
#if defined(__WIN32__)
extern "C" CDECL rust_vec *
rust_env_pairs() {
rust_task *task = rust_task_thread::get_task();
size_t envc = 0;
LPTCH ch = GetEnvironmentStringsA();
LPTCH c;
for (c = ch; *c; c += strlen(c) + 1) {
++envc;
}
c = ch;
rust_vec *v = (rust_vec *)
task->kernel->malloc(vec_size<rust_vec*>(envc),
"str vec interior");
v->fill = v->alloc = sizeof(rust_vec*) * envc;
for (size_t i = 0; i < envc; ++i) {
size_t n = strlen(c);
rust_str *str = make_str(task->kernel, c, n, "str");
((rust_str**)&v->data)[i] = str;
c += n + 1;
}
if (ch) {
FreeEnvironmentStrings(ch);
}
return v;
}
#else
extern "C" CDECL rust_vec *
rust_env_pairs() {
rust_task *task = rust_task_thread::get_task();
#ifdef __APPLE__
char **environ = *_NSGetEnviron();
#endif
char **e = environ;
size_t envc = 0;
while (*e) {
++envc; ++e;
}
return make_str_vec(task->kernel, envc, environ);
}
#endif
// TODO: Allow calling native functions that return double results. // TODO: Allow calling native functions that return double results.
extern "C" CDECL extern "C" CDECL
void squareroot(double *input, double *output) { void squareroot(double *input, double *output) {

View file

@ -1,5 +1,9 @@
#include "rust_internal.h" #include "rust_internal.h"
#ifdef __APPLE__
#include <crt_externs.h>
#endif
#if defined(__WIN32__) #if defined(__WIN32__)
#include <process.h> #include <process.h>
@ -64,7 +68,10 @@ void append_arg(char *& buf, char const *arg, bool last) {
} }
extern "C" CDECL int extern "C" CDECL int
rust_run_program(const char* argv[], int in_fd, int out_fd, int err_fd) { rust_run_program(const char* argv[],
void* envp,
const char* dir,
int in_fd, int out_fd, int err_fd) {
STARTUPINFO si; STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO)); ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO); si.cb = sizeof(STARTUPINFO);
@ -99,7 +106,7 @@ rust_run_program(const char* argv[], int in_fd, int out_fd, int err_fd) {
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
BOOL created = CreateProcess(NULL, cmd, NULL, NULL, TRUE, BOOL created = CreateProcess(NULL, cmd, NULL, NULL, TRUE,
0, NULL, NULL, &si, &pi); 0, envp, dir, &si, &pi);
CloseHandle(si.hStdInput); CloseHandle(si.hStdInput);
CloseHandle(si.hStdOutput); CloseHandle(si.hStdOutput);
@ -130,7 +137,10 @@ rust_process_wait(int proc) {
#include <termios.h> #include <termios.h>
extern "C" CDECL int extern "C" CDECL int
rust_run_program(char* argv[], int in_fd, int out_fd, int err_fd) { rust_run_program(const char* argv[],
void* envp,
const char* dir,
int in_fd, int out_fd, int err_fd) {
int pid = fork(); int pid = fork();
if (pid != 0) return pid; if (pid != 0) return pid;
@ -143,7 +153,18 @@ rust_run_program(char* argv[], int in_fd, int out_fd, int err_fd) {
if (err_fd) dup2(err_fd, 2); if (err_fd) dup2(err_fd, 2);
/* Close all other fds. */ /* Close all other fds. */
for (int fd = getdtablesize() - 1; fd >= 3; fd--) close(fd); for (int fd = getdtablesize() - 1; fd >= 3; fd--) close(fd);
execvp(argv[0], argv); if (dir) { chdir(dir); }
#ifdef __APPLE__
if (envp) {
*_NSGetEnviron() = (char **)envp;
}
execvp(argv[0], (char * const *)argv);
#else
if (!envp) { envp = environ; }
execvpe(argv[0], (char * const *)argv, (char * const *)envp);
#endif
exit(1); exit(1);
} }

View file

@ -204,6 +204,21 @@ make_str(rust_kernel* kernel, const char* c, size_t strlen, const char* name) {
return str; return str;
} }
inline rust_vec *
make_str_vec(rust_kernel* kernel, size_t nstrs, char **strs) {
rust_vec *v = (rust_vec *)
kernel->malloc(vec_size<rust_vec*>(nstrs),
"str vec interior");
v->fill = v->alloc = sizeof(rust_vec*) * nstrs;
for (size_t i = 0; i < nstrs; ++i) {
rust_str *str = make_str(kernel, strs[i],
strlen(strs[i]),
"str");
((rust_str**)&v->data)[i] = str;
}
return v;
}
// //
// Local Variables: // Local Variables:
// mode: C++ // mode: C++

View file

@ -42,6 +42,7 @@ rust_run_program
rust_set_exit_status rust_set_exit_status
rust_start rust_start
rust_getcwd rust_getcwd
rust_env_pairs
rust_task_yield rust_task_yield
rust_task_is_unwinding rust_task_is_unwinding
rust_get_task rust_get_task