zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 72fb2443e0e5dd0c8c3a0265428832796b4ff548 (tree)
parent b46344fd01db28384b51d9898ef25c4825bb19b0
Author: Andrew Kelley <superjoe30@gmail.com>
Date:   Tue,  4 Apr 2017 00:17:24 -0400

API for command line args

closes #300

Diffstat:
MCMakeLists.txt | 1+
Mexample/cat/main.zig | 16+++++++++-------
Mexample/guess_number/main.zig | 2+-
Mexample/hello_world/hello.zig | 2+-
Mstd/build.zig | 9++++++---
Astd/os/child_process.zig | 280+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mstd/os/index.zig | 313+++++++------------------------------------------------------------------------
Mstd/special/bootstrap.zig | 10++--------
Mstd/special/build_runner.zig | 11++++++-----
Mstd/special/test_runner.zig | 2+-
Mtest/run_tests.cpp | 109+++++++++++++++++++++++++++++++++----------------------------------------------
11 files changed, 381 insertions(+), 374 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -223,6 +223,7 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/list.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/math.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/mem.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/net.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/os/child_process.zig" DESTINATION "${ZIG_STD_DEST}/os") install(FILES "${CMAKE_SOURCE_DIR}/std/os/darwin.zig" DESTINATION "${ZIG_STD_DEST}/os") install(FILES "${CMAKE_SOURCE_DIR}/std/os/darwin_x86_64.zig" DESTINATION "${ZIG_STD_DEST}/os") install(FILES "${CMAKE_SOURCE_DIR}/std/os/errno.zig" DESTINATION "${ZIG_STD_DEST}/os") diff --git a/example/cat/main.zig b/example/cat/main.zig @@ -1,23 +1,25 @@ const std = @import("std"); const io = std.io; const mem = std.mem; +const os = std.os; -pub fn main(args: [][]u8) -> %void { - const exe = args[0]; +pub fn main() -> %void { + const exe = os.args.at(0); var catted_anything = false; - for (args[1...]) |arg| { + var arg_i: usize = 1; + while (arg_i < os.args.count(); arg_i += 1) { + const arg = os.args.at(arg_i); if (mem.eql(u8, arg, "-")) { catted_anything = true; %return cat_stream(&io.stdin); } else if (arg[0] == '-') { return usage(exe); } else { - var is: io.InStream = undefined; - is.open(arg) %% |err| { + var is = io.InStream.open(arg, null) %% |err| { %%io.stderr.printf("Unable to open file: {}\n", @errorName(err)); return err; }; - defer %%is.close(); + defer is.close(); catted_anything = true; %return cat_stream(&is); @@ -29,7 +31,7 @@ pub fn main(args: [][]u8) -> %void { %return io.stdout.flush(); } -fn usage(exe: []u8) -> %void { +fn usage(exe: []const u8) -> %void { %%io.stderr.printf("Usage: {} [FILE]...\n", exe); return error.Invalid; } diff --git a/example/guess_number/main.zig b/example/guess_number/main.zig @@ -4,7 +4,7 @@ const fmt = std.fmt; const Rand = std.rand.Rand; const os = std.os; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { %%io.stdout.printf("Welcome to the Guess Number Game in Zig.\n"); var seed_bytes: [@sizeOf(usize)]u8 = undefined; diff --git a/example/hello_world/hello.zig b/example/hello_world/hello.zig @@ -1,5 +1,5 @@ const io = @import("std").io; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { %%io.stdout.printf("Hello, world!\n"); } diff --git a/std/build.zig b/std/build.zig @@ -39,11 +39,13 @@ pub const Builder = struct { return exe; } - pub fn make(self: &Builder, cli_args: []const []const u8) -> %void { + pub fn make(self: &Builder, leftover_arg_index: usize) -> %void { var env_map = %return os.getEnvMap(self.allocator); var verbose = false; - for (cli_args) |arg| { + var arg_i: usize = leftover_arg_index; + while (arg_i < os.args.count(); arg_i += 1) { + const arg = os.args.at(arg_i); if (mem.eql(u8, arg, "--verbose")) { verbose = true; } else { @@ -95,7 +97,8 @@ pub const Builder = struct { } printInvocation(self.zig_exe, zig_args); - var child = %return os.ChildProcess.spawn(self.zig_exe, zig_args.toSliceConst(), env_map, + // TODO issue #301 + var child = %return os.ChildProcess.spawn(self.zig_exe, zig_args.toSliceConst(), &env_map, StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, self.allocator); const term = %return child.wait(); switch (term) { diff --git a/std/os/child_process.zig b/std/os/child_process.zig @@ -0,0 +1,280 @@ +const io = @import("../io.zig"); +const os = @import("index.zig"); +const posix = os.posix; +const mem = @import("../mem.zig"); +const Allocator = mem.Allocator; +const errno = @import("errno.zig"); +const debug = @import("../debug.zig"); +const assert = debug.assert; + +pub const ChildProcess = struct { + pid: i32, + err_pipe: [2]i32, + + stdin: ?io.OutStream, + stdout: ?io.InStream, + stderr: ?io.InStream, + + pub const Term = enum { + Clean: i32, + Signal: i32, + Stopped: i32, + Unknown: i32, + }; + + pub const StdIo = enum { + Inherit, + Ignore, + Pipe, + Close, + }; + + pub fn spawn(exe_path: []const u8, args: []const []const u8, env_map: &const os.EnvMap, + stdin: StdIo, stdout: StdIo, stderr: StdIo, allocator: &Allocator) -> %ChildProcess + { + switch (@compileVar("os")) { + Os.linux, Os.macosx, Os.ios, Os.darwin => { + return spawnPosix(exe_path, args, env_map, stdin, stdout, stderr, allocator); + }, + else => @compileError("Unsupported OS"), + } + } + + pub fn wait(self: &ChildProcess) -> %Term { + defer { + os.posixClose(self.err_pipe[0]); + os.posixClose(self.err_pipe[1]); + }; + + var status: i32 = undefined; + while (true) { + const err = posix.getErrno(posix.waitpid(self.pid, &status, 0)); + if (err > 0) { + switch (err) { + errno.EINVAL, errno.ECHILD => unreachable, + errno.EINTR => continue, + else => { + if (const *stdin ?= self.stdin) { stdin.close(); } + if (const *stdout ?= self.stdin) { stdout.close(); } + if (const *stderr ?= self.stdin) { stderr.close(); } + return error.Unexpected; + }, + } + } + break; + } + + if (const *stdin ?= self.stdin) { stdin.close(); } + if (const *stdout ?= self.stdin) { stdout.close(); } + if (const *stderr ?= self.stdin) { stderr.close(); } + + // Write @maxValue(ErrInt) to the write end of the err_pipe. This is after + // waitpid, so this write is guaranteed to be after the child + // pid potentially wrote an error. This way we can do a blocking + // read on the error pipe and either get @maxValue(ErrInt) (no error) or + // an error code. + %return writeIntFd(self.err_pipe[1], @maxValue(ErrInt)); + const err_int = %return readIntFd(self.err_pipe[0]); + // Here we potentially return the fork child's error + // from the parent pid. + if (err_int != @maxValue(ErrInt)) { + return error(err_int); + } + + return statusToTerm(status); + } + + fn statusToTerm(status: i32) -> Term { + return if (posix.WIFEXITED(status)) { + Term.Clean { posix.WEXITSTATUS(status) } + } else if (posix.WIFSIGNALED(status)) { + Term.Signal { posix.WTERMSIG(status) } + } else if (posix.WIFSTOPPED(status)) { + Term.Stopped { posix.WSTOPSIG(status) } + } else { + Term.Unknown { status } + }; + } + + fn spawnPosix(exe_path: []const u8, args: []const []const u8, env_map: &const os.EnvMap, + stdin: StdIo, stdout: StdIo, stderr: StdIo, allocator: &Allocator) -> %ChildProcess + { + // TODO issue #295 + //const stdin_pipe = if (stdin == StdIo.Pipe) %return makePipe() else undefined; + var stdin_pipe: [2]i32 = undefined; + if (stdin == StdIo.Pipe) + stdin_pipe = %return makePipe(); + %defer if (stdin == StdIo.Pipe) { destroyPipe(stdin_pipe); }; + + // TODO issue #295 + //const stdout_pipe = if (stdout == StdIo.Pipe) %return makePipe() else undefined; + var stdout_pipe: [2]i32 = undefined; + if (stdout == StdIo.Pipe) + stdout_pipe = %return makePipe(); + %defer if (stdout == StdIo.Pipe) { destroyPipe(stdout_pipe); }; + + // TODO issue #295 + //const stderr_pipe = if (stderr == StdIo.Pipe) %return makePipe() else undefined; + var stderr_pipe: [2]i32 = undefined; + if (stderr == StdIo.Pipe) + stderr_pipe = %return makePipe(); + %defer if (stderr == StdIo.Pipe) { destroyPipe(stderr_pipe); }; + + const any_ignore = (stdin == StdIo.Ignore or stdout == StdIo.Ignore or stderr == StdIo.Ignore); + // TODO issue #295 + //const dev_null_fd = if (any_ignore) { + // %return os.posixOpen("/dev/null", posix.O_RDWR, 0, null) + //} else { + // undefined + //}; + var dev_null_fd: i32 = undefined; + if (any_ignore) + dev_null_fd = %return os.posixOpen("/dev/null", posix.O_RDWR, 0, null); + + // This pipe is used to communicate errors between the time of fork + // and execve from the child process to the parent process. + const err_pipe = %return makePipe(); + %defer destroyPipe(err_pipe); + + const pid = posix.fork(); + const pid_err = posix.getErrno(pid); + if (pid_err > 0) { + return switch (pid_err) { + errno.EAGAIN, errno.ENOMEM, errno.ENOSYS => error.SysResources, + else => error.Unexpected, + }; + } + if (pid == 0) { + // we are the child + setUpChildIo(stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) %% + |err| forkChildErrReport(err_pipe[1], err); + setUpChildIo(stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) %% + |err| forkChildErrReport(err_pipe[1], err); + setUpChildIo(stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) %% + |err| forkChildErrReport(err_pipe[1], err); + + const err = posix.getErrno(%return os.posixExecve(exe_path, args, env_map, allocator)); + assert(err > 0); + forkChildErrReport(err_pipe[1], switch (err) { + errno.EFAULT => unreachable, + errno.E2BIG, errno.EMFILE, errno.ENAMETOOLONG, errno.ENFILE, errno.ENOMEM => error.SysResources, + errno.EACCES, errno.EPERM => error.AccessDenied, + errno.EINVAL, errno.ENOEXEC => error.InvalidExe, + errno.EIO, errno.ELOOP => error.FileSystem, + errno.EISDIR => error.IsDir, + errno.ENOENT, errno.ENOTDIR => error.FileNotFound, + errno.ETXTBSY => error.FileBusy, + else => error.Unexpected, + }); + } + + // we are the parent + if (stdin == StdIo.Pipe) { os.posixClose(stdin_pipe[0]); } + if (stdout == StdIo.Pipe) { os.posixClose(stdout_pipe[1]); } + if (stderr == StdIo.Pipe) { os.posixClose(stderr_pipe[1]); } + if (any_ignore) { os.posixClose(dev_null_fd); } + + return ChildProcess { + .pid = i32(pid), + .err_pipe = err_pipe, + + .stdin = if (stdin == StdIo.Pipe) { + io.OutStream { + .fd = stdin_pipe[1], + .buffer = undefined, + .index = 0, + } + } else { + null + }, + .stdout = if (stdout == StdIo.Pipe) { + io.InStream { + .fd = stdout_pipe[0], + } + } else { + null + }, + .stderr = if (stderr == StdIo.Pipe) { + io.InStream { + .fd = stderr_pipe[0], + } + } else { + null + }, + }; + } + + fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) -> %void { + switch (stdio) { + StdIo.Pipe => %return os.posixDup2(pipe_fd, std_fileno), + StdIo.Close => os.posixClose(std_fileno), + StdIo.Inherit => {}, + StdIo.Ignore => %return os.posixDup2(dev_null_fd, std_fileno), + } + } +}; + +fn makePipe() -> %[2]i32 { + var fds: [2]i32 = undefined; + const err = posix.getErrno(posix.pipe(&fds)); + if (err > 0) { + return switch (err) { + errno.EMFILE, errno.ENFILE => error.SysResources, + else => error.Unexpected, + } + } + return fds; +} + +fn destroyPipe(pipe: &const [2]i32) { + os.posixClose((*pipe)[0]); + os.posixClose((*pipe)[1]); +} + +// Child of fork calls this to report an error to the fork parent. +// Then the child exits. +fn forkChildErrReport(fd: i32, err: error) -> noreturn { + _ = writeIntFd(fd, ErrInt(err)); + posix.exit(1); +} + +const ErrInt = @intType(false, @sizeOf(error) * 8); +fn writeIntFd(fd: i32, value: ErrInt) -> %void { + var bytes: [@sizeOf(ErrInt)]u8 = undefined; + mem.writeInt(bytes[0...], value, true); + + var index: usize = 0; + while (index < bytes.len) { + const amt_written = posix.write(fd, &bytes[index], bytes.len - index); + const err = posix.getErrno(amt_written); + if (err > 0) { + switch (err) { + errno.EINTR => continue, + errno.EINVAL => unreachable, + else => return error.SysResources, + } + } + index += amt_written; + } +} + +fn readIntFd(fd: i32) -> %ErrInt { + var bytes: [@sizeOf(ErrInt)]u8 = undefined; + + var index: usize = 0; + while (index < bytes.len) { + const amt_written = posix.read(fd, &bytes[index], bytes.len - index); + const err = posix.getErrno(amt_written); + if (err > 0) { + switch (err) { + errno.EINTR => continue, + errno.EINVAL => unreachable, + else => return error.SysResources, + } + } + index += amt_written; + } + + return mem.readInt(bytes[0...], ErrInt, true); +} + diff --git a/std/os/index.zig b/std/os/index.zig @@ -9,6 +9,7 @@ pub const posix = switch(@compileVar("os")) { }; pub const max_noalloc_path_len = 1024; +pub const ChildProcess = @import("child_process.zig").ChildProcess; const debug = @import("../debug.zig"); const assert = debug.assert; @@ -20,7 +21,6 @@ const c = @import("../c/index.zig"); const mem = @import("../mem.zig"); const Allocator = mem.Allocator; -const io = @import("../io.zig"); const HashMap = @import("../hash_map.zig").HashMap; const cstr = @import("../cstr.zig"); @@ -96,23 +96,6 @@ pub coldcc fn abort() -> noreturn { } } -fn makePipe() -> %[2]i32 { - var fds: [2]i32 = undefined; - const err = posix.getErrno(posix.pipe(&fds)); - if (err > 0) { - return switch (err) { - errno.EMFILE, errno.ENFILE => error.SysResources, - else => error.Unexpected, - } - } - return fds; -} - -fn destroyPipe(pipe: &const [2]i32) { - posixClose((*pipe)[0]); - posixClose((*pipe)[1]); -} - /// Calls POSIX close, and keeps trying if it gets interrupted. pub fn posixClose(fd: i32) { while (true) { @@ -202,54 +185,7 @@ pub fn posixOpen(path: []const u8, flags: usize, perm: usize, allocator: ?&Alloc } } -const ErrInt = @intType(false, @sizeOf(error) * 8); -fn writeIntFd(fd: i32, value: ErrInt) -> %void { - var bytes: [@sizeOf(ErrInt)]u8 = undefined; - mem.writeInt(bytes[0...], value, true); - - var index: usize = 0; - while (index < bytes.len) { - const amt_written = posix.write(fd, &bytes[index], bytes.len - index); - const err = posix.getErrno(amt_written); - if (err > 0) { - switch (err) { - errno.EINTR => continue, - errno.EINVAL => unreachable, - else => return error.SysResources, - } - } - index += amt_written; - } -} - -fn readIntFd(fd: i32) -> %ErrInt { - var bytes: [@sizeOf(ErrInt)]u8 = undefined; - - var index: usize = 0; - while (index < bytes.len) { - const amt_written = posix.read(fd, &bytes[index], bytes.len - index); - const err = posix.getErrno(amt_written); - if (err > 0) { - switch (err) { - errno.EINTR => continue, - errno.EINVAL => unreachable, - else => return error.SysResources, - } - } - index += amt_written; - } - - return mem.readInt(bytes[0...], ErrInt, true); -} - -// Child of fork calls this to report an error to the fork parent. -// Then the child exits. -fn forkChildErrReport(fd: i32, err: error) -> noreturn { - _ = writeIntFd(fd, ErrInt(err)); - posix.exit(1); -} - -fn dup2NoIntr(old_fd: i32, new_fd: i32) -> %void { +pub fn posixDup2(old_fd: i32, new_fd: i32) -> %void { while (true) { const err = posix.getErrno(posix.dup2(old_fd, new_fd)); if (err > 0) { @@ -264,218 +200,13 @@ fn dup2NoIntr(old_fd: i32, new_fd: i32) -> %void { } } -pub const ChildProcess = struct { - pid: i32, - err_pipe: [2]i32, - - stdin: ?io.OutStream, - stdout: ?io.InStream, - stderr: ?io.InStream, - - pub const Term = enum { - Clean: i32, - Signal: i32, - Stopped: i32, - Unknown: i32, - }; - - pub const StdIo = enum { - Inherit, - Ignore, - Pipe, - Close, - }; - - pub fn spawn(exe_path: []const u8, args: []const []const u8, env_map: &const EnvMap, - stdin: StdIo, stdout: StdIo, stderr: StdIo, allocator: &Allocator) -> %ChildProcess - { - switch (@compileVar("os")) { - Os.linux, Os.macosx, Os.ios, Os.darwin => { - return spawnPosix(exe_path, args, env_map, stdin, stdout, stderr, allocator); - }, - else => @compileError("Unsupported OS"), - } - } - - pub fn wait(self: &ChildProcess) -> %Term { - defer { - posixClose(self.err_pipe[0]); - posixClose(self.err_pipe[1]); - }; - - var status: i32 = undefined; - while (true) { - const err = posix.getErrno(posix.waitpid(self.pid, &status, 0)); - if (err > 0) { - switch (err) { - errno.EINVAL, errno.ECHILD => unreachable, - errno.EINTR => continue, - else => { - if (const *stdin ?= self.stdin) { stdin.close(); } - if (const *stdout ?= self.stdin) { stdout.close(); } - if (const *stderr ?= self.stdin) { stderr.close(); } - return error.Unexpected; - }, - } - } - break; - } - - if (const *stdin ?= self.stdin) { stdin.close(); } - if (const *stdout ?= self.stdin) { stdout.close(); } - if (const *stderr ?= self.stdin) { stderr.close(); } - - // Write @maxValue(ErrInt) to the write end of the err_pipe. This is after - // waitpid, so this write is guaranteed to be after the child - // pid potentially wrote an error. This way we can do a blocking - // read on the error pipe and either get @maxValue(ErrInt) (no error) or - // an error code. - %return writeIntFd(self.err_pipe[1], @maxValue(ErrInt)); - const err_int = %return readIntFd(self.err_pipe[0]); - // Here we potentially return the fork child's error - // from the parent pid. - if (err_int != @maxValue(ErrInt)) { - return error(err_int); - } - - return statusToTerm(status); - } - - fn statusToTerm(status: i32) -> Term { - return if (posix.WIFEXITED(status)) { - Term.Clean { posix.WEXITSTATUS(status) } - } else if (posix.WIFSIGNALED(status)) { - Term.Signal { posix.WTERMSIG(status) } - } else if (posix.WIFSTOPPED(status)) { - Term.Stopped { posix.WSTOPSIG(status) } - } else { - Term.Unknown { status } - }; - } - - fn spawnPosix(exe_path: []const u8, args: []const []const u8, env_map: &const EnvMap, - stdin: StdIo, stdout: StdIo, stderr: StdIo, allocator: &Allocator) -> %ChildProcess - { - // TODO issue #295 - //const stdin_pipe = if (stdin == StdIo.Pipe) %return makePipe() else undefined; - var stdin_pipe: [2]i32 = undefined; - if (stdin == StdIo.Pipe) - stdin_pipe = %return makePipe(); - %defer if (stdin == StdIo.Pipe) { destroyPipe(stdin_pipe); }; - - // TODO issue #295 - //const stdout_pipe = if (stdout == StdIo.Pipe) %return makePipe() else undefined; - var stdout_pipe: [2]i32 = undefined; - if (stdout == StdIo.Pipe) - stdout_pipe = %return makePipe(); - %defer if (stdout == StdIo.Pipe) { destroyPipe(stdout_pipe); }; - - // TODO issue #295 - //const stderr_pipe = if (stderr == StdIo.Pipe) %return makePipe() else undefined; - var stderr_pipe: [2]i32 = undefined; - if (stderr == StdIo.Pipe) - stderr_pipe = %return makePipe(); - %defer if (stderr == StdIo.Pipe) { destroyPipe(stderr_pipe); }; - - const any_ignore = (stdin == StdIo.Ignore or stdout == StdIo.Ignore or stderr == StdIo.Ignore); - // TODO issue #295 - //const dev_null_fd = if (any_ignore) { - // %return posixOpen("/dev/null", posix.O_RDWR, 0, null) - //} else { - // undefined - //}; - var dev_null_fd: i32 = undefined; - if (any_ignore) - dev_null_fd = %return posixOpen("/dev/null", posix.O_RDWR, 0, null); - - // This pipe is used to communicate errors between the time of fork - // and execve from the child process to the parent process. - const err_pipe = %return makePipe(); - %defer destroyPipe(err_pipe); - - const pid = posix.fork(); - const pid_err = linux.getErrno(pid); - if (pid_err > 0) { - return switch (pid_err) { - errno.EAGAIN, errno.ENOMEM, errno.ENOSYS => error.SysResources, - else => error.Unexpected, - }; - } - if (pid == 0) { - // we are the child - setUpChildIo(stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) %% - |err| forkChildErrReport(err_pipe[1], err); - setUpChildIo(stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) %% - |err| forkChildErrReport(err_pipe[1], err); - setUpChildIo(stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) %% - |err| forkChildErrReport(err_pipe[1], err); - - const err = posix.getErrno(%return execve(exe_path, args, env_map, allocator)); - assert(err > 0); - forkChildErrReport(err_pipe[1], switch (err) { - errno.EFAULT => unreachable, - errno.E2BIG, errno.EMFILE, errno.ENAMETOOLONG, errno.ENFILE, errno.ENOMEM => error.SysResources, - errno.EACCES, errno.EPERM => error.AccessDenied, - errno.EINVAL, errno.ENOEXEC => error.InvalidExe, - errno.EIO, errno.ELOOP => error.FileSystem, - errno.EISDIR => error.IsDir, - errno.ENOENT, errno.ENOTDIR => error.FileNotFound, - errno.ETXTBSY => error.FileBusy, - else => error.Unexpected, - }); - } - - // we are the parent - if (stdin == StdIo.Pipe) { posixClose(stdin_pipe[0]); } - if (stdout == StdIo.Pipe) { posixClose(stdout_pipe[1]); } - if (stderr == StdIo.Pipe) { posixClose(stderr_pipe[1]); } - if (any_ignore) { posixClose(dev_null_fd); } - - return ChildProcess { - .pid = i32(pid), - .err_pipe = err_pipe, - - .stdin = if (stdin == StdIo.Pipe) { - io.OutStream { - .fd = stdin_pipe[1], - .buffer = undefined, - .index = 0, - } - } else { - null - }, - .stdout = if (stdout == StdIo.Pipe) { - io.InStream { - .fd = stdout_pipe[0], - } - } else { - null - }, - .stderr = if (stderr == StdIo.Pipe) { - io.InStream { - .fd = stderr_pipe[0], - } - } else { - null - }, - }; - } - - fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) -> %void { - switch (stdio) { - StdIo.Pipe => %return dup2NoIntr(pipe_fd, std_fileno), - StdIo.Close => posixClose(std_fileno), - StdIo.Inherit => {}, - StdIo.Ignore => %return dup2NoIntr(dev_null_fd, std_fileno), - } - } -}; - /// This function must allocate memory to add a null terminating bytes on path and each arg. /// It must also convert to KEY=VALUE\0 format for environment variables, and include null /// pointers after the args and after the environment variables. /// Also make the first arg equal to path. -fn execve(path: []const u8, argv: []const []const u8, env_map: &const EnvMap, allocator: &Allocator) -> %usize { +pub fn posixExecve(path: []const u8, argv: []const []const u8, env_map: &const EnvMap, + allocator: &Allocator) -> %usize +{ const path_buf = %return allocator.alloc(u8, path.len + 1); defer allocator.free(path_buf); @memcpy(&path_buf[0], &path[0], path.len); @@ -604,6 +335,19 @@ pub const EnvMap = struct { mem.copy(u8, result, value); return result; } + + fn hash_slice_u8(k: []const u8) -> u32 { + // FNV 32-bit hash + var h: u32 = 2166136261; + for (k) |b| { + h = (h ^ b) *% 16777619; + } + return h; + } + + fn eql_slice_u8(a: []const u8, b: []const u8) -> bool { + return mem.eql(u8, a, b); + } }; pub fn getEnvMap(allocator: &Allocator) -> %EnvMap { @@ -641,15 +385,14 @@ pub fn getEnv(key: []const u8) -> ?[]const u8 { return null; } -fn hash_slice_u8(k: []const u8) -> u32 { - // FNV 32-bit hash - var h: u32 = 2166136261; - for (k) |b| { - h = (h ^ b) *% 16777619; - } - return h; -} +pub const args = struct { + pub var raw: []&u8 = undefined; -fn eql_slice_u8(a: []const u8, b: []const u8) -> bool { - return mem.eql(u8, a, b); -} + pub fn count() -> usize { + return raw.len; + } + pub fn at(i: usize) -> []const u8 { + const s = raw[i]; + return s[0...cstr.len(s)]; + } +}; diff --git a/std/special/bootstrap.zig b/std/special/bootstrap.zig @@ -37,20 +37,14 @@ fn callMainAndExit() -> noreturn { exit(0); } -var args_data: [32][]u8 = undefined; fn callMain(argc: usize, argv: &&u8, envp: &?&u8) -> %void { - // TODO create args API to make it work with > 32 args - const args = args_data[0...argc]; - for (args) |_, i| { - const ptr = argv[i]; - args[i] = ptr[0...std.cstr.len(ptr)]; - } + std.os.args.raw = argv[0...argc]; var env_count: usize = 0; while (envp[env_count] != null; env_count += 1) {} std.os.environ_raw = @ptrcast(&&u8, envp)[0...env_count]; - return root.main(args); + return root.main(); } export fn main(c_argc: i32, c_argv: &&u8, c_envp: &?&u8) -> i32 { diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig @@ -1,18 +1,19 @@ const root = @import("@build"); const std = @import("std"); const io = std.io; +const os = std.os; const Builder = std.build.Builder; const mem = std.mem; error InvalidArgs; -pub fn main(args: [][]u8) -> %void { - if (args.len < 2) { +pub fn main() -> %void { + if (os.args.count() < 2) { %%io.stderr.printf("Expected first argument to be path to zig compiler\n"); return error.InvalidArgs; } - const zig_exe = args[1]; - const leftover_args = args[2...]; + const zig_exe = os.args.at(1); + const leftover_arg_index = 2; // TODO use a more general purpose allocator here var inc_allocator = %%mem.IncrementingAllocator.init(10 * 1024 * 1024); @@ -20,5 +21,5 @@ pub fn main(args: [][]u8) -> %void { var builder = Builder.init(zig_exe, &inc_allocator.allocator); root.build(&builder); - %return builder.make(leftover_args); + %return builder.make(leftover_arg_index); } diff --git a/std/special/test_runner.zig b/std/special/test_runner.zig @@ -7,7 +7,7 @@ const TestFn = struct { extern var zig_test_fn_list: []TestFn; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { for (zig_test_fn_list) |testFn, i| { %%io.stderr.printf("Test {}/{} {}...", i + 1, zig_test_fn_list.len, testFn.name); diff --git a/test/run_tests.cpp b/test/run_tests.cpp @@ -215,7 +215,7 @@ export fn main(argc: c_int, argv: &&u8) -> c_int { use @import("std").io; use @import("foo.zig"); -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { privateFunction(); %%stdout.printf("OK 2\n"); } @@ -245,7 +245,7 @@ pub fn printText() { use @import("foo.zig"); use @import("bar.zig"); -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { foo_function(); bar_function(); } @@ -281,7 +281,7 @@ pub fn foo_function() -> bool { TestCase *tc = add_simple_case("two files use import each other", R"SOURCE( use @import("a.zig"); -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { ok(); } )SOURCE", "OK\n"); @@ -309,7 +309,7 @@ pub const b_text = a_text; add_simple_case("hello world without libc", R"SOURCE( const io = @import("std").io; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { %%io.stdout.printf("Hello, world!\n{d4} {x3} {c}\n", u32(12), u16(0x12), u8('a')); } )SOURCE", "Hello, world!\n0012 012 a\n"); @@ -446,7 +446,7 @@ const io = @import("std").io; const z = io.stdin_fileno; const x : @typeOf(y) = 1234; const y : u16 = 5678; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { var x_local : i32 = print_ok(x); } fn print_ok(val: @typeOf(x)) -> @typeOf(foo) { @@ -471,7 +471,7 @@ export fn compare_fn(a: ?&const c_void, b: ?&const c_void) -> c_int { } } -export fn main(args: c_int, argv: &&u8) -> c_int { +export fn main() -> c_int { var array = []u32 { 1, 7, 3, 2, 0, 9, 4, 8, 6, 5 }; c.qsort(@ptrcast(&c_void, &array[0]), c_ulong(array.len), @sizeOf(i32), compare_fn); @@ -516,7 +516,7 @@ const Bar = struct { fn method(b: &const Bar) -> bool { true } }; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { const bar = Bar {.field2 = 13,}; const foo = Foo {.field1 = bar,}; if (!foo.method()) { @@ -532,7 +532,7 @@ pub fn main(args: [][]u8) -> %void { add_simple_case("defer with only fallthrough", R"SOURCE( const io = @import("std").io; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { %%io.stdout.printf("before\n"); defer %%io.stdout.printf("defer1\n"); defer %%io.stdout.printf("defer2\n"); @@ -544,11 +544,12 @@ pub fn main(args: [][]u8) -> %void { add_simple_case("defer with return", R"SOURCE( const io = @import("std").io; -pub fn main(args: [][]u8) -> %void { +const os = @import("std").os; +pub fn main() -> %void { %%io.stdout.printf("before\n"); defer %%io.stdout.printf("defer1\n"); defer %%io.stdout.printf("defer2\n"); - if (args.len == 1) return; + if (os.args.count() == 1) return; defer %%io.stdout.printf("defer3\n"); %%io.stdout.printf("after\n"); } @@ -557,7 +558,7 @@ pub fn main(args: [][]u8) -> %void { add_simple_case("%defer and it fails", R"SOURCE( const io = @import("std").io; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { do_test() %% return; } fn do_test() -> %void { @@ -577,7 +578,7 @@ fn its_gonna_fail() -> %void { add_simple_case("%defer and it passes", R"SOURCE( const io = @import("std").io; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { do_test() %% return; } fn do_test() -> %void { @@ -597,7 +598,7 @@ fn its_gonna_pass() -> %void { } const foo_txt = @embedFile("foo.txt"); const io = @import("std").io; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { %%io.stdout.printf(foo_txt); } )SOURCE", "1234\nabcd\n"); @@ -1388,9 +1389,13 @@ fn something() -> %void { } ".tmp_source.zig:3:5: error: expected type 'void', found 'error'"); add_compile_fail_case("wrong return type for main", R"SOURCE( -pub fn main(args: [][]u8) { } - )SOURCE", 1, ".tmp_source.zig:2:27: error: expected return type of main to be '%void', instead is 'void'"); +pub fn main() { } + )SOURCE", 1, ".tmp_source.zig:2:15: error: expected return type of main to be '%void', instead is 'void'"); + add_compile_fail_case("double ?? on main return value", R"SOURCE( +pub fn main() -> ??void { +} + )SOURCE", 1, ".tmp_source.zig:2:18: error: expected return type of main to be '%void', instead is '??void'"); add_compile_fail_case("invalid pointer for var type", R"SOURCE( extern fn ext() -> usize; @@ -1689,11 +1694,6 @@ fn bar(a: i32, b: []const u8) { ".tmp_source.zig:8:5: error: found compile log statement", ".tmp_source.zig:3:17: note: called from here"); - add_compile_fail_case("double ?? on main return value", R"SOURCE( -pub fn main(args: [][]u8) -> ??void { -} - )SOURCE", 1, ".tmp_source.zig:2:30: error: expected return type of main to be '%void', instead is '??void'"); - add_compile_fail_case("casting bit offset pointer to regular pointer", R"SOURCE( const u2 = @intType(false, 2); const u3 = @intType(false, 3); @@ -1844,7 +1844,7 @@ pub fn panic(message: []const u8) -> noreturn { @breakpoint(); while (true) {} } -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { if (!@compileVar("is_release")) { @panic("oh no"); } @@ -1856,7 +1856,7 @@ pub fn panic(message: []const u8) -> noreturn { @breakpoint(); while (true) {} } -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { const a = []i32{1, 2, 3, 4}; baz(bar(a)); } @@ -1872,7 +1872,7 @@ pub fn panic(message: []const u8) -> noreturn { while (true) {} } error Whatever; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { const x = add(65530, 10); if (x == 0) return error.Whatever; } @@ -1887,7 +1887,7 @@ pub fn panic(message: []const u8) -> noreturn { while (true) {} } error Whatever; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { const x = sub(10, 20); if (x == 0) return error.Whatever; } @@ -1902,7 +1902,7 @@ pub fn panic(message: []const u8) -> noreturn { while (true) {} } error Whatever; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { const x = mul(300, 6000); if (x == 0) return error.Whatever; } @@ -1917,7 +1917,7 @@ pub fn panic(message: []const u8) -> noreturn { while (true) {} } error Whatever; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { const x = neg(-32768); if (x == 32767) return error.Whatever; } @@ -1932,7 +1932,7 @@ pub fn panic(message: []const u8) -> noreturn { while (true) {} } error Whatever; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { const x = div(-32768, -1); if (x == 32767) return error.Whatever; } @@ -1947,7 +1947,7 @@ pub fn panic(message: []const u8) -> noreturn { while (true) {} } error Whatever; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { const x = shl(-16385, 1); if (x == 0) return error.Whatever; } @@ -1962,7 +1962,7 @@ pub fn panic(message: []const u8) -> noreturn { while (true) {} } error Whatever; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { const x = shl(0b0010111111111111, 3); if (x == 0) return error.Whatever; } @@ -1977,7 +1977,7 @@ pub fn panic(message: []const u8) -> noreturn { while (true) {} } error Whatever; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { const x = div0(999, 0); } fn div0(a: i32, b: i32) -> i32 { @@ -1991,7 +1991,7 @@ pub fn panic(message: []const u8) -> noreturn { while (true) {} } error Whatever; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { const x = divExact(10, 3); if (x == 0) return error.Whatever; } @@ -2006,7 +2006,7 @@ pub fn panic(message: []const u8) -> noreturn { while (true) {} } error Whatever; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { const x = widenSlice([]u8{1, 2, 3, 4, 5}); if (x.len == 0) return error.Whatever; } @@ -2021,7 +2021,7 @@ pub fn panic(message: []const u8) -> noreturn { while (true) {} } error Whatever; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { const x = shorten_cast(200); if (x == 0) return error.Whatever; } @@ -2036,7 +2036,7 @@ pub fn panic(message: []const u8) -> noreturn { while (true) {} } error Whatever; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { const x = unsigned_cast(-10); if (x == 0) return error.Whatever; } @@ -2051,7 +2051,7 @@ pub fn panic(message: []const u8) -> noreturn { while (true) {} } error Whatever; -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { %%bar(); } fn bar() -> %void { @@ -2064,7 +2064,7 @@ pub fn panic(message: []const u8) -> noreturn { @breakpoint(); while (true) {} } -pub fn main(args: [][]u8) -> %void { +pub fn main() -> %void { _ = bar(9999); } fn bar(x: u32) -> error { @@ -2500,25 +2500,13 @@ static void run_test(TestCase *test_case) { } } -static void run_all_tests(bool reverse) { - if (reverse) { - for (size_t i = test_cases.length;;) { - TestCase *test_case = test_cases.at(i); - printf("Test %zu/%zu %s...", i + 1, test_cases.length, test_case->case_name); - fflush(stdout); - run_test(test_case); - printf("OK\n"); - if (i == 0) break; - i -= 1; - } - } else { - for (size_t i = 0; i < test_cases.length; i += 1) { - TestCase *test_case = test_cases.at(i); - printf("Test %zu/%zu %s...", i + 1, test_cases.length, test_case->case_name); - fflush(stdout); - run_test(test_case); - printf("OK\n"); - } +static void run_all_tests(void) { + for (size_t i = 0; i < test_cases.length; i += 1) { + TestCase *test_case = test_cases.at(i); + printf("Test %zu/%zu %s...", i + 1, test_cases.length, test_case->case_name); + fflush(stdout); + run_test(test_case); + printf("OK\n"); } printf("%zu tests passed.\n", test_cases.length); } @@ -2530,18 +2518,13 @@ static void cleanup(void) { } static int usage(const char *arg0) { - fprintf(stderr, "Usage: %s [--reverse]\n", arg0); + fprintf(stderr, "Usage: %s\n", arg0); return 1; } int main(int argc, char **argv) { - bool reverse = false; for (int i = 1; i < argc; i += 1) { - if (strcmp(argv[i], "--reverse") == 0) { - reverse = true; - } else { - return usage(argv[0]); - } + return usage(argv[0]); } add_compiling_test_cases(); add_debug_safety_test_cases(); @@ -2549,6 +2532,6 @@ int main(int argc, char **argv) { add_parseh_test_cases(); add_self_hosted_tests(); add_std_lib_tests(); - run_all_tests(reverse); + run_all_tests(); cleanup(); }