Merge pull request #10782 from topolarity/gate-child-processes

Avoid depending on child process execution when not supported by host OS
This commit is contained in:
Andrew Kelley
2022-02-07 00:31:17 -05:00
committed by GitHub
15 changed files with 460 additions and 301 deletions

View File

@@ -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",
@@ -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}),

View File

@@ -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,
@@ -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();
@@ -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();
@@ -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();
@@ -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", .{});
@@ -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();
@@ -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,
@@ -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,

View File

@@ -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
@@ -175,6 +177,13 @@ fn make(step: *Step) !void {
const argv = argv_list.items;
if (!std.process.can_spawn) {
const cmd = try std.mem.join(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();

View File

@@ -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 {

View File

@@ -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,
};

View File

@@ -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");
@@ -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(),
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();
const stderr_reader = child.stderr.?.reader();
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) });
};
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 {
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Ignore;
child.stderr_behavior = .Pipe;
try child.spawn();
const stderr_reader = child.stderr.?.reader();
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) });
};
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", .{});
},
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);
}
}

View File

@@ -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();

View File

@@ -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 {};
@@ -229,6 +229,8 @@ pub const LibCInstallation = struct {
}
break :blk batch.wait();
};
} else {
return error.LibCRuntimeNotFound;
}
return self;
}

View File

@@ -9,6 +9,7 @@ const fs = std.fs;
const allocPrint = std.fmt.allocPrint;
const mem = std.mem;
const lldMain = @import("../main.zig").lldMain;
const trace = @import("../tracy.zig").trace;
const Module = @import("../Module.zig");
const Compilation = @import("../Compilation.zig");
@@ -1358,60 +1359,71 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void {
Compilation.dump_argv(argv.items[1..]);
}
// Sadly, we must run LLD as a child process because it does not behave
// properly as a library.
const child = try std.ChildProcess.init(argv.items, arena);
defer child.deinit();
if (std.process.can_spawn) {
// If possible, we run LLD as a child process because it does not always
// behave properly as a library, unfortunately.
// https://github.com/ziglang/zig/issues/3825
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| {
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
return error.UnableToSpawnSelf;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
// TODO https://github.com/ziglang/zig/issues/6342
std.process.exit(1);
}
},
else => std.process.abort(),
const term = child.spawnAndWait() catch |err| {
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
return error.UnableToSpawnSelf;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
std.process.exit(code);
}
},
else => std.process.abort(),
}
} else {
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Ignore;
child.stderr_behavior = .Pipe;
try child.spawn();
const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024);
const term = child.wait() catch |err| {
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
return error.UnableToSpawnSelf;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
// TODO parse this output and surface with the Compilation API rather than
// directly outputting to stderr here.
std.debug.print("{s}", .{stderr});
return error.LLDReportedFailure;
}
},
else => {
log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr });
return error.LLDCrashed;
},
}
if (stderr.len != 0) {
log.warn("unexpected LLD stderr:\n{s}", .{stderr});
}
}
} else {
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Ignore;
child.stderr_behavior = .Pipe;
try child.spawn();
const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024);
const term = child.wait() catch |err| {
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
return error.UnableToSpawnSelf;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
// TODO parse this output and surface with the Compilation API rather than
// directly outputting to stderr here.
std.debug.print("{s}", .{stderr});
return error.LLDReportedFailure;
}
},
else => {
log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr });
return error.LLDCrashed;
},
}
if (stderr.len != 0) {
log.warn("unexpected LLD stderr:\n{s}", .{stderr});
const exit_code = try lldMain(arena, argv.items, false);
if (exit_code != 0) {
if (comp.clang_passthrough_mode) {
std.process.exit(exit_code);
} else {
return error.LLDReportedFailure;
}
}
}
}

View File

@@ -14,6 +14,7 @@ const leb128 = std.leb;
const Module = @import("../Module.zig");
const Compilation = @import("../Compilation.zig");
const codegen = @import("../codegen.zig");
const lldMain = @import("../main.zig").lldMain;
const trace = @import("../tracy.zig").trace;
const Package = @import("../Package.zig");
const Value = @import("../value.zig").Value;
@@ -1950,60 +1951,71 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
Compilation.dump_argv(argv.items[1..]);
}
// Sadly, we must run LLD as a child process because it does not behave
// properly as a library.
const child = try std.ChildProcess.init(argv.items, arena);
defer child.deinit();
if (std.process.can_spawn) {
// If possible, we run LLD as a child process because it does not always
// behave properly as a library, unfortunately.
// https://github.com/ziglang/zig/issues/3825
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| {
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
return error.UnableToSpawnSelf;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
// TODO https://github.com/ziglang/zig/issues/6342
std.process.exit(1);
}
},
else => std.process.abort(),
const term = child.spawnAndWait() catch |err| {
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
return error.UnableToSpawnSelf;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
std.process.exit(code);
}
},
else => std.process.abort(),
}
} else {
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Ignore;
child.stderr_behavior = .Pipe;
try child.spawn();
const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024);
const term = child.wait() catch |err| {
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
return error.UnableToSpawnSelf;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
// TODO parse this output and surface with the Compilation API rather than
// directly outputting to stderr here.
std.debug.print("{s}", .{stderr});
return error.LLDReportedFailure;
}
},
else => {
log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr });
return error.LLDCrashed;
},
}
if (stderr.len != 0) {
log.warn("unexpected LLD stderr:\n{s}", .{stderr});
}
}
} else {
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Ignore;
child.stderr_behavior = .Pipe;
try child.spawn();
const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024);
const term = child.wait() catch |err| {
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
return error.UnableToSpawnSelf;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
// TODO parse this output and surface with the Compilation API rather than
// directly outputting to stderr here.
std.debug.print("{s}", .{stderr});
return error.LLDReportedFailure;
}
},
else => {
log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr });
return error.LLDCrashed;
},
}
if (stderr.len != 0) {
log.warn("unexpected LLD stderr:\n{s}", .{stderr});
const exit_code = try lldMain(arena, argv.items, false);
if (exit_code != 0) {
if (comp.clang_passthrough_mode) {
std.process.exit(exit_code);
} else {
return error.LLDReportedFailure;
}
}
}
}

View File

@@ -15,6 +15,7 @@ const Module = @import("../Module.zig");
const Compilation = @import("../Compilation.zig");
const CodeGen = @import("../arch/wasm/CodeGen.zig");
const link = @import("../link.zig");
const lldMain = @import("../main.zig").lldMain;
const trace = @import("../tracy.zig").trace;
const build_options = @import("build_options");
const wasi_libc = @import("../wasi_libc.zig");
@@ -1486,60 +1487,71 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
Compilation.dump_argv(argv.items[1..]);
}
// Sadly, we must run LLD as a child process because it does not behave
// properly as a library.
const child = try std.ChildProcess.init(argv.items, arena);
defer child.deinit();
if (std.process.can_spawn) {
// If possible, we run LLD as a child process because it does not always
// behave properly as a library, unfortunately.
// https://github.com/ziglang/zig/issues/3825
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| {
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
return error.UnableToSpawnSelf;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
// TODO https://github.com/ziglang/zig/issues/6342
std.process.exit(1);
}
},
else => std.process.abort(),
const term = child.spawnAndWait() catch |err| {
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
return error.UnableToSpawnSelf;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
std.process.exit(code);
}
},
else => std.process.abort(),
}
} else {
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Ignore;
child.stderr_behavior = .Pipe;
try child.spawn();
const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024);
const term = child.wait() catch |err| {
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
return error.UnableToSpawnSelf;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
// TODO parse this output and surface with the Compilation API rather than
// directly outputting to stderr here.
std.debug.print("{s}", .{stderr});
return error.LLDReportedFailure;
}
},
else => {
log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr });
return error.LLDCrashed;
},
}
if (stderr.len != 0) {
log.warn("unexpected LLD stderr:\n{s}", .{stderr});
}
}
} else {
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Ignore;
child.stderr_behavior = .Pipe;
try child.spawn();
const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024);
const term = child.wait() catch |err| {
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
return error.UnableToSpawnSelf;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
// TODO parse this output and surface with the Compilation API rather than
// directly outputting to stderr here.
std.debug.print("{s}", .{stderr});
return error.LLDReportedFailure;
}
},
else => {
log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr });
return error.LLDCrashed;
},
}
if (stderr.len != 0) {
log.warn("unexpected LLD stderr:\n{s}", .{stderr});
const exit_code = try lldMain(arena, argv.items, false);
if (exit_code != 0) {
if (comp.clang_passthrough_mode) {
std.process.exit(exit_code);
} else {
return error.LLDReportedFailure;
}
}
}
}

View File

@@ -221,7 +221,7 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
mem.eql(u8, cmd, "lib") or
mem.eql(u8, cmd, "ar"))
{
return punt_to_llvm_ar(arena, args);
return process.exit(try llvmArMain(arena, args));
} else if (mem.eql(u8, cmd, "cc")) {
return buildOutputType(gpa, arena, args, .cc);
} else if (mem.eql(u8, cmd, "c++")) {
@@ -231,12 +231,12 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
} else if (mem.eql(u8, cmd, "clang") or
mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as"))
{
return punt_to_clang(arena, args);
return process.exit(try clangMain(arena, args));
} else if (mem.eql(u8, cmd, "ld.lld") or
mem.eql(u8, cmd, "lld-link") or
mem.eql(u8, cmd, "wasm-ld"))
{
return punt_to_lld(arena, args);
return process.exit(try lldMain(arena, args, true));
} else if (mem.eql(u8, cmd, "build")) {
return cmdBuild(gpa, arena, cmd_args);
} else if (mem.eql(u8, cmd, "fmt")) {
@@ -1347,7 +1347,7 @@ fn buildOutputType(
.ignore => {},
.driver_punt => {
// Never mind what we're doing, just pass the args directly. For example --help.
return punt_to_clang(arena, all_args);
return process.exit(try clangMain(arena, all_args));
},
.pic => want_pic = true,
.no_pic => want_pic = false,
@@ -1866,7 +1866,7 @@ fn buildOutputType(
// An error message is generated when there is more than 1 C source file.
if (c_source_files.items.len != 1) {
// For example `zig cc` and no args should print the "no input files" message.
return punt_to_clang(arena, all_args);
return process.exit(try clangMain(arena, all_args));
}
if (out_path) |p| {
emit_bin = .{ .yes = p };
@@ -1882,7 +1882,7 @@ fn buildOutputType(
{
// For example `zig cc` and no args should print the "no input files" message.
// There could be other reasons to punt to clang, for example, --help.
return punt_to_clang(arena, all_args);
return process.exit(try clangMain(arena, all_args));
}
},
}
@@ -2881,9 +2881,9 @@ fn runOrTest(
// execv releases the locks; no need to destroy the Compilation here.
const err = std.process.execv(gpa, argv.items);
try warnAboutForeignBinaries(gpa, arena, arg_mode, target_info, link_libc);
const cmd = try argvCmd(arena, argv.items);
const cmd = try std.mem.join(arena, " ", argv.items);
fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });
} else {
} else if (std.process.can_spawn) {
const child = try std.ChildProcess.init(argv.items, gpa);
defer child.deinit();
@@ -2900,7 +2900,7 @@ fn runOrTest(
const term = child.spawnAndWait() catch |err| {
try warnAboutForeignBinaries(gpa, arena, arg_mode, target_info, link_libc);
const cmd = try argvCmd(arena, argv.items);
const cmd = try std.mem.join(arena, " ", argv.items);
fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd });
};
switch (arg_mode) {
@@ -2931,18 +2931,21 @@ fn runOrTest(
if (code == 0) {
if (!watch) return cleanExit();
} else {
const cmd = try argvCmd(arena, argv.items);
const cmd = try std.mem.join(arena, " ", argv.items);
fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd });
}
},
else => {
const cmd = try argvCmd(arena, argv.items);
const cmd = try std.mem.join(arena, " ", argv.items);
fatal("the following test command crashed:\n{s}", .{cmd});
},
}
},
else => unreachable,
}
} else {
const cmd = try std.mem.join(arena, " ", argv.items);
fatal("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(builtin.os.tag), cmd });
}
}
@@ -3553,43 +3556,38 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
break :argv child_argv.items;
};
const child = try std.ChildProcess.init(child_argv, gpa);
defer child.deinit();
child.stdin_behavior = .Inherit;
child.stdout_behavior = .Inherit;
child.stderr_behavior = .Inherit;
if (std.process.can_spawn) {
const child = try std.ChildProcess.init(child_argv, gpa);
defer child.deinit();
const term = try child.spawnAndWait();
switch (term) {
.Exited => |code| {
if (code == 0) return cleanExit();
child.stdin_behavior = .Inherit;
child.stdout_behavior = .Inherit;
child.stderr_behavior = .Inherit;
if (prominent_compile_errors) {
fatal("the build command failed with exit code {d}", .{code});
} else {
const cmd = try argvCmd(arena, child_argv);
fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });
}
},
else => {
const cmd = try argvCmd(arena, child_argv);
fatal("the following build command crashed:\n{s}", .{cmd});
},
const term = try child.spawnAndWait();
switch (term) {
.Exited => |code| {
if (code == 0) return cleanExit();
if (prominent_compile_errors) {
fatal("the build command failed with exit code {d}", .{code});
} else {
const cmd = try std.mem.join(arena, " ", child_argv);
fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });
}
},
else => {
const cmd = try std.mem.join(arena, " ", child_argv);
fatal("the following build command crashed:\n{s}", .{cmd});
},
}
} else {
const cmd = try std.mem.join(arena, " ", child_argv);
fatal("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(builtin.os.tag), cmd });
}
}
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();
}
fn readSourceFileToEndAlloc(
allocator: mem.Allocator,
input: *const fs.File,
@@ -4080,65 +4078,87 @@ pub const info_zen =
extern "c" fn ZigClang_main(argc: c_int, argv: [*:null]?[*:0]u8) c_int;
extern "c" fn ZigLlvmAr_main(argc: c_int, argv: [*:null]?[*:0]u8) c_int;
/// TODO https://github.com/ziglang/zig/issues/3257
fn punt_to_clang(arena: Allocator, args: []const []const u8) error{OutOfMemory} {
if (!build_options.have_llvm)
fatal("`zig cc` and `zig c++` unavailable: compiler built without LLVM extensions", .{});
// Convert the args to the format Clang expects.
const argv = try arena.alloc(?[*:0]u8, args.len + 1);
fn argsCopyZ(alloc: Allocator, args: []const []const u8) ![:null]?[*:0]u8 {
var argv = try alloc.allocSentinel(?[*:0]u8, args.len, null);
for (args) |arg, i| {
argv[i] = try arena.dupeZ(u8, arg); // TODO If there was an argsAllocZ we could avoid this allocation.
argv[i] = try alloc.dupeZ(u8, arg); // TODO If there was an argsAllocZ we could avoid this allocation.
}
argv[args.len] = null;
const exit_code = ZigClang_main(@intCast(c_int, args.len), argv[0..args.len :null].ptr);
process.exit(@bitCast(u8, @truncate(i8, exit_code)));
return argv;
}
/// TODO https://github.com/ziglang/zig/issues/3257
fn punt_to_llvm_ar(arena: Allocator, args: []const []const u8) error{OutOfMemory} {
pub fn clangMain(alloc: Allocator, args: []const []const u8) error{OutOfMemory}!u8 {
if (!build_options.have_llvm)
fatal("`zig cc` and `zig c++` unavailable: compiler built without LLVM extensions", .{});
var arena_instance = std.heap.ArenaAllocator.init(alloc);
defer arena_instance.deinit();
const arena = arena_instance.allocator();
// Convert the args to the null-terminated format Clang expects.
const argv = try argsCopyZ(arena, args);
const exit_code = ZigClang_main(@intCast(c_int, argv.len), argv.ptr);
return @bitCast(u8, @truncate(i8, exit_code));
}
pub fn llvmArMain(alloc: Allocator, args: []const []const u8) error{OutOfMemory}!u8 {
if (!build_options.have_llvm)
fatal("`zig ar`, `zig dlltool`, `zig ranlib', and `zig lib` unavailable: compiler built without LLVM extensions", .{});
var arena_instance = std.heap.ArenaAllocator.init(alloc);
defer arena_instance.deinit();
const arena = arena_instance.allocator();
// Convert the args to the format llvm-ar expects.
// We subtract 1 to shave off the zig binary from args[0].
const argv = try arena.allocSentinel(?[*:0]u8, args.len - 1, null);
for (args[1..]) |arg, i| {
// TODO If there was an argsAllocZ we could avoid this allocation.
argv[i] = try arena.dupeZ(u8, arg);
}
const argc = @intCast(c_int, argv.len);
const exit_code = ZigLlvmAr_main(argc, argv.ptr);
process.exit(@bitCast(u8, @truncate(i8, exit_code)));
// We intentionally shave off the zig binary at args[0].
const argv = try argsCopyZ(arena, args[1..]);
const exit_code = ZigLlvmAr_main(@intCast(c_int, argv.len), argv.ptr);
return @bitCast(u8, @truncate(i8, exit_code));
}
/// The first argument determines which backend is invoked. The options are:
/// * `ld.lld` - ELF
/// * `lld-link` - COFF
/// * `wasm-ld` - WebAssembly
/// TODO https://github.com/ziglang/zig/issues/3257
pub fn punt_to_lld(arena: Allocator, args: []const []const u8) error{OutOfMemory} {
pub fn lldMain(
alloc: Allocator,
args: []const []const u8,
can_exit_early: bool,
) error{OutOfMemory}!u8 {
if (!build_options.have_llvm)
fatal("`zig {s}` unavailable: compiler built without LLVM extensions", .{args[0]});
// Convert the args to the format LLD expects.
// We subtract 1 to shave off the zig binary from args[0].
const argv = try arena.allocSentinel(?[*:0]const u8, args.len - 1, null);
for (args[1..]) |arg, i| {
argv[i] = try arena.dupeZ(u8, arg); // TODO If there was an argsAllocZ we could avoid this allocation.
// Print a warning if lld is called multiple times in the same process,
// since it may misbehave
// https://github.com/ziglang/zig/issues/3825
const CallCounter = struct {
var count: usize = 0;
};
if (CallCounter.count == 1) { // Issue the warning on the first repeat call
warn("invoking LLD for the second time within the same process because the host OS ({s}) does not support spawning child processes. This sometimes activates LLD bugs", .{@tagName(builtin.os.tag)});
}
CallCounter.count += 1;
var arena_instance = std.heap.ArenaAllocator.init(alloc);
defer arena_instance.deinit();
const arena = arena_instance.allocator();
// Convert the args to the format llvm-ar expects.
// We intentionally shave off the zig binary at args[0].
const argv = try argsCopyZ(arena, args[1..]);
const exit_code = rc: {
const llvm = @import("codegen/llvm/bindings.zig");
const argc = @intCast(c_int, argv.len);
if (mem.eql(u8, args[1], "ld.lld")) {
break :rc llvm.LinkELF(argc, argv.ptr, true);
break :rc llvm.LinkELF(argc, argv.ptr, can_exit_early);
} else if (mem.eql(u8, args[1], "lld-link")) {
break :rc llvm.LinkCOFF(argc, argv.ptr, true);
break :rc llvm.LinkCOFF(argc, argv.ptr, can_exit_early);
} else if (mem.eql(u8, args[1], "wasm-ld")) {
break :rc llvm.LinkWasm(argc, argv.ptr, true);
break :rc llvm.LinkWasm(argc, argv.ptr, can_exit_early);
} else {
unreachable;
}
};
process.exit(@bitCast(u8, @truncate(i8, exit_code)));
return @bitCast(u8, @truncate(i8, exit_code));
}
const clang_args = @import("clang_options.zig").list;

View File

@@ -5,6 +5,7 @@ const path = std.fs.path;
const assert = std.debug.assert;
const log = std.log.scoped(.mingw);
const builtin = @import("builtin");
const target_util = @import("target.zig");
const Compilation = @import("Compilation.zig");
const build_options = @import("build_options");
@@ -367,39 +368,43 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
Compilation.dump_argv(&args);
}
const child = try std.ChildProcess.init(&args, arena);
defer child.deinit();
if (std.process.can_spawn) {
const child = try std.ChildProcess.init(&args, arena);
defer child.deinit();
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Pipe;
child.stderr_behavior = .Pipe;
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Pipe;
child.stderr_behavior = .Pipe;
try child.spawn();
try child.spawn();
const stderr_reader = child.stderr.?.reader();
const stderr_reader = child.stderr.?.reader();
// TODO https://github.com/ziglang/zig/issues/6343
const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024);
// TODO https://github.com/ziglang/zig/issues/6343
const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024);
const term = child.wait() catch |err| {
// TODO surface a proper error here
log.err("unable to spawn {s}: {s}", .{ args[0], @errorName(err) });
return error.ClangPreprocessorFailed;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
// TODO surface a proper error here
log.err("clang exited with code {d} and stderr: {s}", .{ code, stderr });
return error.ClangPreprocessorFailed;
}
},
else => {
const term = child.wait() catch |err| {
// TODO surface a proper error here
log.err("clang terminated unexpectedly with stderr: {s}", .{stderr});
log.err("unable to spawn {s}: {s}", .{ args[0], @errorName(err) });
return error.ClangPreprocessorFailed;
},
};
switch (term) {
.Exited => |code| {
if (code != 0) {
// TODO surface a proper error here
log.err("clang exited with code {d} and stderr: {s}", .{ code, stderr });
return error.ClangPreprocessorFailed;
}
},
else => {
// TODO surface a proper error here
log.err("clang terminated unexpectedly with stderr: {s}", .{stderr});
return error.ClangPreprocessorFailed;
},
}
} else {
log.err("unable to spawn {s}: spawning child process not supported on {s}", .{ args[0], @tagName(builtin.os.tag) });
return error.ClangPreprocessorFailed;
}
const lib_final_path = try comp.global_cache_directory.join(comp.gpa, &[_][]const u8{

View File

@@ -730,6 +730,12 @@ pub const TestContext = struct {
// * cannot handle updates
// because of this we must spawn a child process rather than
// using Compilation directly.
if (!std.process.can_spawn) {
print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)});
return; // Pass test.
}
assert(case.updates.items.len == 1);
const update = case.updates.items[0];
try tmp.dir.writeFile(tmp_src_path, update.src);
@@ -1104,6 +1110,11 @@ pub const TestContext = struct {
}
},
.Execution => |expected_stdout| {
if (!std.process.can_spawn) {
print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)});
return; // Pass test.
}
update_node.setEstimatedTotalItems(4);
var argv = std.ArrayList([]const u8).init(allocator);

View File

@@ -10,6 +10,8 @@ const fmt = std.fmt;
const ArrayList = std.ArrayList;
const Mode = std.builtin.Mode;
const LibExeObjStep = build.LibExeObjStep;
const Allocator = mem.Allocator;
const ExecError = build.Builder.ExecError;
// Cases
const compare_output = @import("compare_output.zig");
@@ -722,6 +724,13 @@ pub const StackTracesContext = struct {
std.debug.print("Test {d}/{d} {s}...", .{ self.test_index + 1, self.context.test_index, self.name });
if (!std.process.can_spawn) {
const cmd = try std.mem.join(b.allocator, " ", args.items);
std.debug.print("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(builtin.os.tag), cmd });
b.allocator.free(cmd);
return ExecError.ExecNotSupported;
}
const child = std.ChildProcess.init(args.items, b.allocator) catch unreachable;
defer child.deinit();