feat: emit LLVM IR and build native executables
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import "@std/io"
|
||||
import "@ffi/c"
|
||||
import "@std/mem"
|
||||
|
||||
Init :: struct {
|
||||
io io.Io
|
||||
@@ -9,3 +11,56 @@ Init :: struct {
|
||||
Args :: struct {
|
||||
values [][;0]u8
|
||||
}
|
||||
|
||||
RunError :: enum {
|
||||
invalid_arguments
|
||||
spawn_failed
|
||||
wait_failed
|
||||
}
|
||||
|
||||
#! run a program synchronously without a shell, searching PATH for `arguments[0]`.
|
||||
#! arguments and zero-terminated strings are borrowed until this call returns.
|
||||
#! an empty argument vector, empty executable name, or embedded NUL is invalid.
|
||||
#! the child inherits the current environment and stdin/stdout/stderr.
|
||||
#! temporary argument descriptors use `allocator` and are freed on every return.
|
||||
#! return the exit code, or 128 plus the terminating signal number.
|
||||
run proc(allocator mem.Allocator, arguments [][;0]u8) i32 ! (mem.AllocError | RunError) {
|
||||
if (arguments.len == 0) return .invalid_arguments
|
||||
if (arguments[0].len == 0) return .invalid_arguments
|
||||
for arguments |argument| {
|
||||
for (argument) |byte| if (byte == 0) return .invalid_arguments
|
||||
}
|
||||
if (arguments.len == maxval!(usize)) return .out_of_memory
|
||||
|
||||
descriptors :: try mem.alloc(?*mut c_char, allocator, arguments.len + 1)
|
||||
defer mem.free(allocator, descriptors)
|
||||
for (0..arguments.len) |index| {
|
||||
descriptors[index] = ptrcast!(constcast!(arguments[index]).ptr)
|
||||
}
|
||||
descriptors[arguments.len] = null
|
||||
|
||||
pid [1]mut c_int := [0]
|
||||
result :: c.posix_spawnp(
|
||||
pid[..].ptr,
|
||||
ptrcast!(arguments[0].ptr),
|
||||
null,
|
||||
null,
|
||||
descriptors.ptr,
|
||||
c._NSGetEnviron()?^,
|
||||
)
|
||||
if (result != 0) return .spawn_failed
|
||||
|
||||
status [1]mut c_int := [0]
|
||||
while true {
|
||||
waited :: c.waitpid(pid[0], status[..].ptr, 0)
|
||||
if (waited < 0) {
|
||||
if (c.__error()?^ == c.EINTR) continue
|
||||
return .wait_failed
|
||||
}
|
||||
# Darwin wait status: low seven bits hold the signal, next byte the exit code.
|
||||
signal :: status[0] & 127
|
||||
if (signal == 0) return i32(divtrunc!(status[0], 256) & 255)
|
||||
if (signal != 127) return i32(128 + signal)
|
||||
# A traced child can report a stop even without WUNTRACED; wait for termination.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import "@std/testing"
|
||||
import "@std/mem"
|
||||
|
||||
runs_without_shell_and_preserves_exit_status test {
|
||||
arguments [4][;0]u8 :: ["/bin/test", "a b;$(exit 99)", "=", "a b;$(exit 99)"]
|
||||
status :: run(mem.c_allocator, arguments[..]) catch |_| { return .expectation_failed }
|
||||
try testing.expect_equal(i32(0), status)
|
||||
|
||||
failing [2][;0]u8 :: ["/bin/test", ""]
|
||||
failure :: run(mem.c_allocator, failing[..]) catch |_| { return .expectation_failed }
|
||||
try testing.expect_equal(i32(1), failure)
|
||||
}
|
||||
|
||||
rejects_invalid_arguments_and_reports_spawn_failure test {
|
||||
empty [0][;0]u8 :: []
|
||||
_ = run(mem.c_allocator, empty[..]) catch |err| {
|
||||
try testing.expect(err == .invalid_arguments)
|
||||
missing [1][;0]u8 :: ["/nonexistent/honey-process-test"]
|
||||
_ = run(mem.c_allocator, missing[..]) catch |spawn_error| {
|
||||
try testing.expect(spawn_error == .spawn_failed)
|
||||
return
|
||||
}
|
||||
return .expectation_failed
|
||||
}
|
||||
return .expectation_failed
|
||||
}
|
||||
Reference in New Issue
Block a user