Skip to content

Commit

Permalink
Avoid depending on child process execution when not supported by host OS
Browse files Browse the repository at this point in the history
In accordance with the requesting issue (ziglang#10750):
- `zig test` skips any tests that it cannot spawn, returning success
- `zig run` and `zig build` exit with failure, reporting the command the cannot be run
- `zig clang`, `zig ar`, etc. already punt directly to the appropriate clang/lld main(), even before this change
- Native `libc` Detection is not supported

Additionally, `exec()` and related Builder functions error at run-time, reporting the command that cannot be run
  • Loading branch information
topolarity authored and andrewrk committed Feb 7, 2022
1 parent 069dd01 commit 5065830
Show file tree
Hide file tree
Showing 15 changed files with 464 additions and 276 deletions.
7 changes: 7 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ pub fn build(b: *Builder) !void {
const version = if (opt_version_string) |version| version else v: {
const version_string = b.fmt("{d}.{d}.{d}", .{ zig_version.major, zig_version.minor, zig_version.patch });

if (!std.process.can_spawn) {
std.debug.print("error: version info cannot be retrieved from git. Zig version must be provided using -Dversion-string\n", .{});
std.process.exit(1);
}
var code: u8 = undefined;
const git_describe_untrimmed = b.execAllowFail(&[_][]const u8{
"git", "-C", b.build_root, "describe", "--match", "*.*.*", "--tags",
Expand Down Expand Up @@ -542,6 +546,9 @@ fn addCxxKnownPath(
errtxt: ?[]const u8,
need_cpp_includes: bool,
) !void {
if (!std.process.can_spawn)
return error.RequiredLibraryNotFound;

const path_padded = try b.exec(&[_][]const u8{
ctx.cxx_compiler,
b.fmt("-print-file-name={s}", .{objname}),
Expand Down
38 changes: 34 additions & 4 deletions lib/std/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,14 @@ pub const Builder = struct {
/// Information about the native target. Computed before build() is invoked.
host: NativeTargetInfo,

const PkgConfigError = error{
pub const ExecError = error{
ReadFailure,
ExitCodeFailure,
ProcessTerminated,
ExecNotSupported,
} || std.ChildProcess.SpawnError;

pub const PkgConfigError = error{
PkgConfigCrashed,
PkgConfigFailed,
PkgConfigNotInstalled,
Expand Down Expand Up @@ -959,6 +966,9 @@ pub const Builder = struct {
printCmd(cwd, argv);
}

if (!std.process.can_spawn)
return error.ExecNotSupported;

const child = std.ChildProcess.init(argv, self.allocator) catch unreachable;
defer child.deinit();

Expand Down Expand Up @@ -1168,9 +1178,12 @@ pub const Builder = struct {
argv: []const []const u8,
out_code: *u8,
stderr_behavior: std.ChildProcess.StdIo,
) ![]u8 {
) ExecError![]u8 {
assert(argv.len != 0);

if (!std.process.can_spawn)
return error.ExecNotSupported;

const max_output_size = 400 * 1024;
const child = try std.ChildProcess.init(argv, self.allocator);
defer child.deinit();
Expand All @@ -1182,7 +1195,9 @@ pub const Builder = struct {

try child.spawn();

const stdout = try child.stdout.?.reader().readAllAlloc(self.allocator, max_output_size);
const stdout = child.stdout.?.reader().readAllAlloc(self.allocator, max_output_size) catch {
return error.ReadFailure;
};
errdefer self.allocator.free(stdout);

const term = try child.wait();
Expand All @@ -1208,8 +1223,21 @@ pub const Builder = struct {
printCmd(null, argv);
}

if (!std.process.can_spawn) {
if (src_step) |s| warn("{s}...", .{s.name});
warn("Unable to spawn the following command: cannot spawn child process\n", .{});
printCmd(null, argv);
std.os.abort();
}

var code: u8 = undefined;
return self.execAllowFail(argv, &code, .Inherit) catch |err| switch (err) {
error.ExecNotSupported => {
if (src_step) |s| warn("{s}...", .{s.name});
warn("Unable to spawn the following command: cannot spawn child process\n", .{});
printCmd(null, argv);
std.os.abort();
},
error.FileNotFound => {
if (src_step) |s| warn("{s}...", .{s.name});
warn("Unable to spawn the following command: file not found\n", .{});
Expand Down Expand Up @@ -1260,7 +1288,7 @@ pub const Builder = struct {
) catch unreachable;
}

fn execPkgConfigList(self: *Builder, out_code: *u8) ![]const PkgConfigPkg {
fn execPkgConfigList(self: *Builder, out_code: *u8) (PkgConfigError || ExecError)![]const PkgConfigPkg {
const stdout = try self.execAllowFail(&[_][]const u8{ "pkg-config", "--list-all" }, out_code, .Ignore);
var list = ArrayList(PkgConfigPkg).init(self.allocator);
errdefer list.deinit();
Expand All @@ -1287,6 +1315,7 @@ pub const Builder = struct {
} else |err| {
const result = switch (err) {
error.ProcessTerminated => error.PkgConfigCrashed,
error.ExecNotSupported => error.PkgConfigFailed,
error.ExitCodeFailure => error.PkgConfigFailed,
error.FileNotFound => error.PkgConfigNotInstalled,
error.InvalidName => error.PkgConfigNotInstalled,
Expand Down Expand Up @@ -1929,6 +1958,7 @@ pub const LibExeObjStep = struct {
"--libs",
}, &code, .Ignore)) |stdout| stdout else |err| switch (err) {
error.ProcessTerminated => return error.PkgConfigCrashed,
error.ExecNotSupported => return error.PkgConfigFailed,
error.ExitCodeFailure => return error.PkgConfigFailed,
error.FileNotFound => return error.PkgConfigNotInstalled,
else => return err,
Expand Down
20 changes: 20 additions & 0 deletions lib/std/build/RunStep.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const mem = std.mem;
const process = std.process;
const ArrayList = std.ArrayList;
const BufMap = std.BufMap;
const Allocator = mem.Allocator;
const ExecError = build.Builder.ExecError;

const max_stdout_size = 1 * 1024 * 1024; // 1 MiB

Expand Down Expand Up @@ -136,6 +138,17 @@ pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8
) catch unreachable;
}

fn argvCmd(allocator: Allocator, argv: []const []const u8) ![]u8 {
var cmd = std.ArrayList(u8).init(allocator);
defer cmd.deinit();
for (argv[0 .. argv.len - 1]) |arg| {
try cmd.appendSlice(arg);
try cmd.append(' ');
}
try cmd.appendSlice(argv[argv.len - 1]);
return cmd.toOwnedSlice();
}

pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void {
self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) };
}
Expand Down Expand Up @@ -175,6 +188,13 @@ fn make(step: *Step) !void {

const argv = argv_list.items;

if (!std.process.can_spawn) {
const cmd = try argvCmd(self.builder.allocator, argv);
std.debug.print("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(builtin.os.tag), cmd });
self.builder.allocator.free(cmd);
return ExecError.ExecNotSupported;
}

const child = std.ChildProcess.init(argv, self.builder.allocator) catch unreachable;
defer child.deinit();

Expand Down
4 changes: 4 additions & 0 deletions lib/std/child_process.zig
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ pub const ChildProcess = struct {

/// On success must call `kill` or `wait`.
pub fn spawn(self: *ChildProcess) SpawnError!void {
if (!std.process.can_spawn) {
@compileError("the target operating system cannot spawn processes");
}

if (builtin.os.tag == .windows) {
return self.spawnWindows();
} else {
Expand Down
8 changes: 7 additions & 1 deletion lib/std/process.zig
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,13 @@ pub fn getSelfExeSharedLibPaths(allocator: Allocator) error{OutOfMemory}![][:0]u

/// Tells whether calling the `execv` or `execve` functions will be a compile error.
pub const can_execv = switch (builtin.os.tag) {
.windows, .haiku => false,
.windows, .haiku, .wasi => false,
else => true,
};

/// Tells whether spawning child processes is supported (e.g. via ChildProcess)
pub const can_spawn = switch (builtin.os.tag) {
.wasi => false,
else => true,
};

Expand Down
101 changes: 59 additions & 42 deletions src/Compilation.zig
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const libunwind = @import("libunwind.zig");
const libcxx = @import("libcxx.zig");
const wasi_libc = @import("wasi_libc.zig");
const fatal = @import("main.zig").fatal;
const clangMain = @import("main.zig").clangMain;
const Module = @import("Module.zig");
const Cache = @import("Cache.zig");
const stage1 = @import("stage1.zig");
Expand Down Expand Up @@ -3667,55 +3668,71 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
dump_argv(argv.items);
}

const child = try std.ChildProcess.init(argv.items, arena);
defer child.deinit();
if (std.process.can_spawn) {
const child = try std.ChildProcess.init(argv.items, arena);
defer child.deinit();

if (comp.clang_passthrough_mode) {
child.stdin_behavior = .Inherit;
child.stdout_behavior = .Inherit;
child.stderr_behavior = .Inherit;
if (comp.clang_passthrough_mode) {
child.stdin_behavior = .Inherit;
child.stdout_behavior = .Inherit;
child.stderr_behavior = .Inherit;

const term = child.spawnAndWait() catch |err| {
return comp.failCObj(c_object, "unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
};
switch (term) {
.Exited => |code| {
if (code != 0) {
std.process.exit(code);
}
if (comp.clang_preprocessor_mode == .stdout)
std.process.exit(0);
},
else => std.process.abort(),
}
} else {
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Ignore;
child.stderr_behavior = .Pipe;
const term = child.spawnAndWait() catch |err| {
return comp.failCObj(c_object, "unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
};
switch (term) {
.Exited => |code| {
if (code != 0) {
std.process.exit(code);
}
if (comp.clang_preprocessor_mode == .stdout)
std.process.exit(0);
},
else => std.process.abort(),
}
} else {
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Ignore;
child.stderr_behavior = .Pipe;

try child.spawn();
try child.spawn();

const stderr_reader = child.stderr.?.reader();
const stderr_reader = child.stderr.?.reader();

const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024);
const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024);

const term = child.wait() catch |err| {
return comp.failCObj(c_object, "unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
};
const term = child.wait() catch |err| {
return comp.failCObj(c_object, "unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
};

switch (term) {
.Exited => |code| {
if (code != 0) {
// TODO parse clang stderr and turn it into an error message
// and then call failCObjWithOwnedErrorMsg
log.err("clang failed with stderr: {s}", .{stderr});
return comp.failCObj(c_object, "clang exited with code {d}", .{code});
}
},
else => {
log.err("clang terminated with stderr: {s}", .{stderr});
return comp.failCObj(c_object, "clang terminated unexpectedly", .{});
},
switch (term) {
.Exited => |code| {
if (code != 0) {
// TODO parse clang stderr and turn it into an error message
// and then call failCObjWithOwnedErrorMsg
log.err("clang failed with stderr: {s}", .{stderr});
return comp.failCObj(c_object, "clang exited with code {d}", .{code});
}
},
else => {
log.err("clang terminated with stderr: {s}", .{stderr});
return comp.failCObj(c_object, "clang terminated unexpectedly", .{});
},
}
}
} else {
const exit_code = try clangMain(arena, argv.items);
if (exit_code != 0) {
if (comp.clang_passthrough_mode) {
std.process.exit(exit_code);
} else {
return comp.failCObj(c_object, "clang exited with code {d}", .{exit_code});
}
}
if (comp.clang_passthrough_mode and
comp.clang_preprocessor_mode == .stdout)
{
std.process.exit(0);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/ThreadPool.zig
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ pub fn init(self: *ThreadPool, allocator: std.mem.Allocator) !void {
}

fn destroyWorkers(self: *ThreadPool, spawned: usize) void {
if (builtin.single_threaded)
return;

for (self.workers[0..spawned]) |*worker| {
worker.thread.join();
worker.idle_node.data.deinit();
Expand Down
4 changes: 3 additions & 1 deletion src/libc_installation.zig
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ pub const LibCInstallation = struct {
self.crt_dir = try args.allocator.dupeZ(u8, "/system/develop/lib");
break :blk batch.wait();
};
} else {
} else if (std.process.can_spawn) {
try blk: {
var batch = Batch(FindError!void, 2, .auto_async).init();
errdefer batch.wait() catch {};
Expand All @@ -229,6 +229,8 @@ pub const LibCInstallation = struct {
}
break :blk batch.wait();
};
} else {
return error.LibCRuntimeNotFound;
}
return self;
}
Expand Down
Loading

0 comments on commit 5065830

Please sign in to comment.