diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 17429c0370..4fc6784684 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -31,6 +31,16 @@ pub const Directory = struct { } } + pub fn tmpFilePath(self: Directory, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 { + const s = std.fs.path.sep_str; + const rand_int = std.crypto.random.int(u64); + if (self.path) |p| { + return std.fmt.allocPrint(ally, "{s}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix }); + } else { + return std.fmt.allocPrint(ally, "tmp" ++ s ++ "{x}-{s}", .{ rand_int, suffix }); + } + } + /// Whether or not the handle should be closed, or the path should be freed /// is determined by usage, however this function is provided for convenience /// if it happens to be what the caller needs. diff --git a/src/Compilation.zig b/src/Compilation.zig index 1b6d805bb3..3ae43773e3 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3981,7 +3981,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P // We can't know the digest until we do the C compiler invocation, // so we need a temporary filename. - const out_obj_path = try comp.tmpFilePath(arena, o_basename); + const out_obj_path = try comp.local_cache_directory.tmpFilePath(arena, o_basename); var zig_cache_tmp_dir = try comp.local_cache_directory.handle.makeOpenPath("tmp", .{}); defer zig_cache_tmp_dir.close(); @@ -4129,16 +4129,6 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P }; } -pub fn tmpFilePath(comp: *Compilation, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 { - const s = std.fs.path.sep_str; - const rand_int = std.crypto.random.int(u64); - if (comp.local_cache_directory.path) |p| { - return std.fmt.allocPrint(ally, "{s}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix }); - } else { - return std.fmt.allocPrint(ally, "tmp" ++ s ++ "{x}-{s}", .{ rand_int, suffix }); - } -} - pub fn addTranslateCCArgs( comp: *Compilation, arena: Allocator, @@ -4597,6 +4587,14 @@ pub const FileExt = enum { => false, }; } + + // maximum length of @tagName(ext: FileExt) + pub const max_len = blk: { + var max: u16 = 0; + inline for (std.meta.tags(FileExt)) |ext| + max = std.math.max(@tagName(ext).len, max); + break :blk max; + }; }; pub fn hasObjectExt(filename: []const u8) bool { diff --git a/src/main.zig b/src/main.zig index a680a5d89e..a1f95aaf21 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3020,6 +3020,41 @@ fn buildOutputType( break :l global_cache_directory; }; + var temp_stdin_file: ?[]const u8 = null; + defer { + if (temp_stdin_file) |file| { + // some garbage may stay in the file system if removal fails. + // Alternatively, we could tell the user that the removal failed, + // but it's not as much of a deal: it's a temporary cache directory + // at all. + local_cache_directory.handle.deleteFile(file) catch {}; + } + } + + for (c_source_files.items) |*src| { + if (!mem.eql(u8, src.src_path, "-")) continue; + + const ext = src.ext orelse + fatal("-E or -x is required when reading from a non-regular file", .{}); + + // "-" is stdin. Dump it to a real file. + const new_file = blk: { + var buf: ["stdin.".len + Compilation.FileExt.max_len]u8 = undefined; + const fname = try std.fmt.bufPrint(&buf, "stdin.{s}", .{@tagName(ext)}); + const new_name = try local_cache_directory.tmpFilePath(arena, fname); + + try local_cache_directory.handle.makePath("tmp"); + var outfile = try local_cache_directory.handle.createFile(new_name, .{}); + defer outfile.close(); + errdefer local_cache_directory.handle.deleteFile(new_name) catch {}; + + try outfile.writeFileAll(io.getStdIn(), .{}); + break :blk new_name; + }; + temp_stdin_file = new_file; + src.src_path = new_file; + } + if (build_options.have_llvm and emit_asm != .no) { // LLVM has no way to set this non-globally. const argv = [_][*:0]const u8{ "zig (LLVM option parsing)", "--x86-asm-syntax=intel" }; @@ -3873,7 +3908,7 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, fancy_output: ?*Translate const c_src_basename = fs.path.basename(c_source_file.src_path); const dep_basename = try std.fmt.allocPrint(arena, "{s}.d", .{c_src_basename}); - const out_dep_path = try comp.tmpFilePath(arena, dep_basename); + const out_dep_path = try comp.local_cache_directory.tmpFilePath(arena, dep_basename); break :blk out_dep_path; }; diff --git a/test/tests.zig b/test/tests.zig index 641914aabe..26d81ead12 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -777,6 +777,52 @@ pub fn addCliTests(b: *std.Build) *Step { step.dependOn(&cleanup.step); } + // Test `zig cc -x c -` + // Test author was not able to figure out how to start a child process and + // give an fd to it's stdin. fork/exec works, but we are limiting ourselves + // to POSIX. + // + // TODO: the "zig cc <..." step should be a RunStep.create(...) + // However, how do I create a command (RunStep) that invokes a command + // with a specific file descriptor in the stdin? + //if (builtin.os.tag != .windows) { + // const tmp_path = b.makeTempPath(); + // var dir = std.fs.cwd().openDir(tmp_path, .{}) catch @panic("unhandled"); + // dir.writeFile("truth.c", "int main() { return 42; }") catch @panic("unhandled"); + // var infile = dir.openFile("truth.c", .{}) catch @panic("unhandled"); + + // const outfile = std.fs.path.joinZ( + // b.allocator, + // &[_][]const u8{ tmp_path, "truth" }, + // ) catch @panic("unhandled"); + + // const pid_result = std.os.fork() catch @panic("unhandled"); + // if (pid_result == 0) { // child + // std.os.dup2(infile.handle, std.os.STDIN_FILENO) catch @panic("unhandled"); + // const argv = &[_:null]?[*:0]const u8{ + // b.zig_exe, "cc", + // "-o", outfile, + // "-x", "c", + // "-", + // }; + // const envp = &[_:null]?[*:0]const u8{ + // std.fmt.allocPrintZ(b.allocator, "ZIG_GLOBAL_CACHE_DIR={s}", .{tmp_path}) catch @panic("unhandled"), + // }; + // const err = std.os.execveZ(b.zig_exe, argv, envp); + // std.debug.print("execve error: {any}\n", .{err}); + // std.os.exit(1); + // } + + // const res = std.os.waitpid(pid_result, 0); + // assert(0 == res.status); + + // // run the compiled executable and check if it's telling the truth. + // _ = exec(b.allocator, tmp_path, 42, &[_][]const u8{outfile}) catch @panic("unhandled"); + + // const cleanup = b.addRemoveDirTree(tmp_path); + // step.dependOn(&cleanup.step); + //} + { // Test `zig fmt`. // This test must use a temporary directory rather than a cache @@ -1154,3 +1200,50 @@ pub fn addCases( check_case_exe, ); } + +fn exec( + allocator: std.mem.Allocator, + cwd: []const u8, + expect_code: u8, + argv: []const []const u8, +) !std.ChildProcess.ExecResult { + const max_output_size = 100 * 1024; + const result = std.ChildProcess.exec(.{ + .allocator = allocator, + .argv = argv, + .cwd = cwd, + .max_output_bytes = max_output_size, + }) catch |err| { + std.debug.print("The following command failed:\n", .{}); + printCmd(cwd, argv); + return err; + }; + switch (result.term) { + .Exited => |code| { + if (code != expect_code) { + std.debug.print( + "The following command exited with error code {}, expected {}:\n", + .{ code, expect_code }, + ); + printCmd(cwd, argv); + std.debug.print("stderr:\n{s}\n", .{result.stderr}); + return error.CommandFailed; + } + }, + else => { + std.debug.print("The following command terminated unexpectedly:\n", .{}); + printCmd(cwd, argv); + std.debug.print("stderr:\n{s}\n", .{result.stderr}); + return error.CommandFailed; + }, + } + return result; +} + +fn printCmd(cwd: []const u8, argv: []const []const u8) void { + std.debug.print("cd {s} && ", .{cwd}); + for (argv) |arg| { + std.debug.print("{s} ", .{arg}); + } + std.debug.print("\n", .{}); +}