mirror of
https://github.com/ziglang/zig.git
synced 2026-02-15 13:58:27 +00:00
expand argv[0] when spawning system C compiler
Some C compilers, such as Clang, are known to rely on argv[0] to find the path to their own executable, without even bothering to resolve PATH. This results in the message: error: unable to execute command: Executable "" doesn't exist! So we tell ChildProcess to expand argv[0] to the absolute path to give them a helping hand.
This commit is contained in:
parent
a5d47be5ad
commit
44c14749a1
@ -49,6 +49,10 @@ pub const ChildProcess = struct {
|
||||
|
||||
err_pipe: if (builtin.os == .windows) void else [2]os.fd_t,
|
||||
|
||||
expand_arg0: Arg0Expand,
|
||||
|
||||
pub const Arg0Expand = os.Arg0Expand;
|
||||
|
||||
pub const SpawnError = error{
|
||||
OutOfMemory,
|
||||
|
||||
@ -100,6 +104,7 @@ pub const ChildProcess = struct {
|
||||
.stdin_behavior = StdIo.Inherit,
|
||||
.stdout_behavior = StdIo.Inherit,
|
||||
.stderr_behavior = StdIo.Inherit,
|
||||
.expand_arg0 = .no_expand,
|
||||
};
|
||||
errdefer allocator.destroy(child);
|
||||
return child;
|
||||
@ -172,34 +177,56 @@ pub const ChildProcess = struct {
|
||||
|
||||
/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
|
||||
/// If it succeeds, the caller owns result.stdout and result.stderr memory.
|
||||
/// TODO deprecate in favor of exec2
|
||||
pub fn exec(
|
||||
allocator: *mem.Allocator,
|
||||
argv: []const []const u8,
|
||||
cwd: ?[]const u8,
|
||||
env_map: ?*const BufMap,
|
||||
max_output_size: usize,
|
||||
max_output_bytes: usize,
|
||||
) !ExecResult {
|
||||
const child = try ChildProcess.init(argv, allocator);
|
||||
return exec2(.{
|
||||
.allocator = allocator,
|
||||
.argv = argv,
|
||||
.cwd = cwd,
|
||||
.env_map = env_map,
|
||||
.max_output_bytes = max_output_bytes,
|
||||
});
|
||||
}
|
||||
|
||||
/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
|
||||
/// If it succeeds, the caller owns result.stdout and result.stderr memory.
|
||||
/// TODO rename to exec
|
||||
pub fn exec2(args: struct {
|
||||
allocator: *mem.Allocator,
|
||||
argv: []const []const u8,
|
||||
cwd: ?[]const u8 = null,
|
||||
env_map: ?*const BufMap = null,
|
||||
max_output_bytes: usize = 50 * 1024,
|
||||
expand_arg0: Arg0Expand = .no_expand,
|
||||
}) !ExecResult {
|
||||
const child = try ChildProcess.init(args.argv, args.allocator);
|
||||
defer child.deinit();
|
||||
|
||||
child.stdin_behavior = ChildProcess.StdIo.Ignore;
|
||||
child.stdout_behavior = ChildProcess.StdIo.Pipe;
|
||||
child.stderr_behavior = ChildProcess.StdIo.Pipe;
|
||||
child.cwd = cwd;
|
||||
child.env_map = env_map;
|
||||
child.stdin_behavior = .Ignore;
|
||||
child.stdout_behavior = .Pipe;
|
||||
child.stderr_behavior = .Pipe;
|
||||
child.cwd = args.cwd;
|
||||
child.env_map = args.env_map;
|
||||
child.expand_arg0 = args.expand_arg0;
|
||||
|
||||
try child.spawn();
|
||||
|
||||
var stdout = Buffer.initNull(allocator);
|
||||
var stderr = Buffer.initNull(allocator);
|
||||
var stdout = Buffer.initNull(args.allocator);
|
||||
var stderr = Buffer.initNull(args.allocator);
|
||||
defer Buffer.deinit(&stdout);
|
||||
defer Buffer.deinit(&stderr);
|
||||
|
||||
var stdout_file_in_stream = child.stdout.?.inStream();
|
||||
var stderr_file_in_stream = child.stderr.?.inStream();
|
||||
|
||||
try stdout_file_in_stream.stream.readAllBuffer(&stdout, max_output_size);
|
||||
try stderr_file_in_stream.stream.readAllBuffer(&stderr, max_output_size);
|
||||
try stdout_file_in_stream.stream.readAllBuffer(&stdout, args.max_output_bytes);
|
||||
try stderr_file_in_stream.stream.readAllBuffer(&stderr, args.max_output_bytes);
|
||||
|
||||
return ExecResult{
|
||||
.term = try child.wait(),
|
||||
@ -418,7 +445,7 @@ pub const ChildProcess = struct {
|
||||
os.setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err);
|
||||
}
|
||||
|
||||
const err = os.execvpe(self.allocator, self.argv, env_map);
|
||||
const err = os.execvpe_expandArg0(self.allocator, self.expand_arg0, self.argv, env_map);
|
||||
forkChildErrReport(err_pipe[1], err);
|
||||
}
|
||||
|
||||
|
||||
@ -916,10 +916,13 @@ pub const ExecveError = error{
|
||||
NameTooLong,
|
||||
} || UnexpectedError;
|
||||
|
||||
/// Deprecated in favor of `execveZ`.
|
||||
pub const execveC = execveZ;
|
||||
|
||||
/// Like `execve` except the parameters are null-terminated,
|
||||
/// matching the syscall API on all targets. This removes the need for an allocator.
|
||||
/// This function ignores PATH environment variable. See `execvpeC` for that.
|
||||
pub fn execveC(path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) ExecveError {
|
||||
/// This function ignores PATH environment variable. See `execvpeZ` for that.
|
||||
pub fn execveZ(path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) ExecveError {
|
||||
switch (errno(system.execve(path, child_argv, envp))) {
|
||||
0 => unreachable,
|
||||
EFAULT => unreachable,
|
||||
@ -942,11 +945,25 @@ pub fn execveC(path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, en
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `execvpe` except the parameters are null-terminated,
|
||||
/// matching the syscall API on all targets. This removes the need for an allocator.
|
||||
/// This function also uses the PATH environment variable to get the full path to the executable.
|
||||
/// If `file` is an absolute path, this is the same as `execveC`.
|
||||
pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) ExecveError {
|
||||
/// Deprecated in favor of `execvpeZ`.
|
||||
pub const execvpeC = execvpeZ;
|
||||
|
||||
pub const Arg0Expand = enum {
|
||||
expand,
|
||||
no_expand,
|
||||
};
|
||||
|
||||
/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable,
|
||||
/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall.
|
||||
pub fn execvpeZ_expandArg0(
|
||||
comptime arg0_expand: Arg0Expand,
|
||||
file: [*:0]const u8,
|
||||
child_argv: switch (arg0_expand) {
|
||||
.expand => [*:null]?[*:0]const u8,
|
||||
.no_expand => [*:null]const ?[*:0]const u8,
|
||||
},
|
||||
envp: [*:null]const ?[*:0]const u8,
|
||||
) ExecveError {
|
||||
const file_slice = mem.toSliceConst(u8, file);
|
||||
if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveC(file, child_argv, envp);
|
||||
|
||||
@ -962,7 +979,12 @@ pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, e
|
||||
mem.copy(u8, path_buf[search_path.len + 1 ..], file_slice);
|
||||
const path_len = search_path.len + file_slice.len + 1;
|
||||
path_buf[path_len] = 0;
|
||||
err = execveC(path_buf[0..path_len :0].ptr, child_argv, envp);
|
||||
const full_path = path_buf[0..path_len :0].ptr;
|
||||
switch (arg0_expand) {
|
||||
.expand => child_argv[0] = full_path,
|
||||
.no_expand => {},
|
||||
}
|
||||
err = execveC(full_path, child_argv, envp);
|
||||
switch (err) {
|
||||
error.AccessDenied => seen_eacces = true,
|
||||
error.FileNotFound, error.NotDir => {},
|
||||
@ -973,13 +995,24 @@ pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, e
|
||||
return err;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// `argv_slice[0]` is the executable path.
|
||||
/// Like `execvpe` except the parameters are null-terminated,
|
||||
/// matching the syscall API on all targets. This removes the need for an allocator.
|
||||
/// This function also uses the PATH environment variable to get the full path to the executable.
|
||||
pub fn execvpe(
|
||||
/// If `file` is an absolute path, this is the same as `execveC`.
|
||||
pub fn execvpeZ(
|
||||
file: [*:0]const u8,
|
||||
argv: [*:null]const ?[*:0]const u8,
|
||||
envp: [*:null]const ?[*:0]const u8,
|
||||
) ExecveError {
|
||||
return execvpeZ_expandArg0(.no_expand, file, argv, envp);
|
||||
}
|
||||
|
||||
/// This is the same as `execvpe` except if the `arg0_expand` parameter is set to `.expand`,
|
||||
/// then argv[0] will be replaced with the expanded version of it, after resolving in accordance
|
||||
/// with the PATH environment variable.
|
||||
pub fn execvpe_expandArg0(
|
||||
allocator: *mem.Allocator,
|
||||
arg0_expand: Arg0Expand,
|
||||
argv_slice: []const []const u8,
|
||||
env_map: *const std.BufMap,
|
||||
) (ExecveError || error{OutOfMemory}) {
|
||||
@ -1004,7 +1037,23 @@ pub fn execvpe(
|
||||
const envp_buf = try createNullDelimitedEnvMap(allocator, env_map);
|
||||
defer freeNullDelimitedEnvMap(allocator, envp_buf);
|
||||
|
||||
return execvpeC(argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr);
|
||||
switch (arg0_expand) {
|
||||
.expand => return execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr),
|
||||
.no_expand => return execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// `argv_slice[0]` is the executable path.
|
||||
/// This function also uses the PATH environment variable to get the full path to the executable.
|
||||
pub fn execvpe(
|
||||
allocator: *mem.Allocator,
|
||||
argv_slice: []const []const u8,
|
||||
env_map: *const std.BufMap,
|
||||
) (ExecveError || error{OutOfMemory}) {
|
||||
return execvpe_expandArg0(allocator, .no_expand, argv_slice, env_map);
|
||||
}
|
||||
|
||||
pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std.BufMap) ![:null]?[*:0]u8 {
|
||||
|
||||
@ -241,8 +241,16 @@ pub const LibCInstallation = struct {
|
||||
"-xc",
|
||||
dev_null,
|
||||
};
|
||||
const max_bytes = 1024 * 1024;
|
||||
const exec_res = std.ChildProcess.exec(allocator, &argv, null, null, max_bytes) catch |err| switch (err) {
|
||||
const exec_res = std.ChildProcess.exec2(.{
|
||||
.allocator = allocator,
|
||||
.argv = &argv,
|
||||
.max_output_bytes = 1024 * 1024,
|
||||
// Some C compilers, such as Clang, are known to rely on argv[0] to find the path
|
||||
// to their own executable, without even bothering to resolve PATH. This results in the message:
|
||||
// error: unable to execute command: Executable "" doesn't exist!
|
||||
// So we use the expandArg0 variant of ChildProcess to give them a helping hand.
|
||||
.expand_arg0 = .expand,
|
||||
}) catch |err| switch (err) {
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
else => return error.UnableToSpawnCCompiler,
|
||||
};
|
||||
@ -494,8 +502,16 @@ pub fn ccPrintFileName(
|
||||
defer allocator.free(arg1);
|
||||
const argv = [_][]const u8{ cc_exe, arg1 };
|
||||
|
||||
const max_bytes = 1024 * 1024;
|
||||
const exec_res = std.ChildProcess.exec(allocator, &argv, null, null, max_bytes) catch |err| switch (err) {
|
||||
const exec_res = std.ChildProcess.exec2(.{
|
||||
.allocator = allocator,
|
||||
.argv = &argv,
|
||||
.max_output_bytes = 1024 * 1024,
|
||||
// Some C compilers, such as Clang, are known to rely on argv[0] to find the path
|
||||
// to their own executable, without even bothering to resolve PATH. This results in the message:
|
||||
// error: unable to execute command: Executable "" doesn't exist!
|
||||
// So we use the expandArg0 variant of ChildProcess to give them a helping hand.
|
||||
.expand_arg0 = .expand,
|
||||
}) catch |err| switch (err) {
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
else => return error.UnableToSpawnCCompiler,
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user