authorgravatar for motiejus@jakstys.ltMotiejus Jakštys <motiejus@jakstys.lt> 2023-01-23 21:59:03+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-05-16 16:40:58-07:00
log2e692312f104e1e5533c9f389d73488ea4a1ef68
tree41c075be8388dc374bfd2f01973a6f8dfb612939
parentb754068fbc7492962953068d31386d4c04e37ae5

zig cc: support reading from non-files

echo 'some C program' | $CC -x c - Is a common pattern to test for compiler or linker features. This patch adds support for reading from non-regular files. This will make at least one more Go test to pass.

4 files changed, 148 insertions(+), 12 deletions(-)

lib/std/Build/Cache.zig+10
...@@ -31,6 +31,16 @@ pub const Directory = struct {...@@ -31,6 +31,16 @@ pub const Directory = struct {
31 }31 }
32 }32 }
3333
34 pub fn tmpFilePath(self: Directory, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 {
35 const s = std.fs.path.sep_str;
36 const rand_int = std.crypto.random.int(u64);
37 if (self.path) |p| {
38 return std.fmt.allocPrint(ally, "{s}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix });
39 } else {
40 return std.fmt.allocPrint(ally, "tmp" ++ s ++ "{x}-{s}", .{ rand_int, suffix });
41 }
42 }
43
34 /// Whether or not the handle should be closed, or the path should be freed44 /// Whether or not the handle should be closed, or the path should be freed
35 /// is determined by usage, however this function is provided for convenience45 /// is determined by usage, however this function is provided for convenience
36 /// if it happens to be what the caller needs.46 /// if it happens to be what the caller needs.
src/Compilation.zig+9-11
...@@ -3981,7 +3981,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P...@@ -3981,7 +3981,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
39813981
3982 // We can't know the digest until we do the C compiler invocation,3982 // We can't know the digest until we do the C compiler invocation,
3983 // so we need a temporary filename.3983 // so we need a temporary filename.
3984 const out_obj_path = try comp.tmpFilePath(arena, o_basename);3984 const out_obj_path = try comp.local_cache_directory.tmpFilePath(arena, o_basename);
3985 var zig_cache_tmp_dir = try comp.local_cache_directory.handle.makeOpenPath("tmp", .{});3985 var zig_cache_tmp_dir = try comp.local_cache_directory.handle.makeOpenPath("tmp", .{});
3986 defer zig_cache_tmp_dir.close();3986 defer zig_cache_tmp_dir.close();
39873987
...@@ -4129,16 +4129,6 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P...@@ -4129,16 +4129,6 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
4129 };4129 };
4130}4130}
41314131
4132pub fn tmpFilePath(comp: *Compilation, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 {
4133 const s = std.fs.path.sep_str;
4134 const rand_int = std.crypto.random.int(u64);
4135 if (comp.local_cache_directory.path) |p| {
4136 return std.fmt.allocPrint(ally, "{s}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix });
4137 } else {
4138 return std.fmt.allocPrint(ally, "tmp" ++ s ++ "{x}-{s}", .{ rand_int, suffix });
4139 }
4140}
4141
4142pub fn addTranslateCCArgs(4132pub fn addTranslateCCArgs(
4143 comp: *Compilation,4133 comp: *Compilation,
4144 arena: Allocator,4134 arena: Allocator,
...@@ -4597,6 +4587,14 @@ pub const FileExt = enum {...@@ -4597,6 +4587,14 @@ pub const FileExt = enum {
4597 => false,4587 => false,
4598 };4588 };
4599 }4589 }
4590
4591 // maximum length of @tagName(ext: FileExt)
4592 pub const max_len = blk: {
4593 var max: u16 = 0;
4594 inline for (std.meta.tags(FileExt)) |ext|
4595 max = std.math.max(@tagName(ext).len, max);
4596 break :blk max;
4597 };
4600};4598};
46014599
4602pub fn hasObjectExt(filename: []const u8) bool {4600pub fn hasObjectExt(filename: []const u8) bool {
src/main.zig+36-1
...@@ -3020,6 +3020,41 @@ fn buildOutputType(...@@ -3020,6 +3020,41 @@ fn buildOutputType(
3020 break :l global_cache_directory;3020 break :l global_cache_directory;
3021 };3021 };
30223022
3023 var temp_stdin_file: ?[]const u8 = null;
3024 defer {
3025 if (temp_stdin_file) |file| {
3026 // some garbage may stay in the file system if removal fails.
3027 // Alternatively, we could tell the user that the removal failed,
3028 // but it's not as much of a deal: it's a temporary cache directory
3029 // at all.
3030 local_cache_directory.handle.deleteFile(file) catch {};
3031 }
3032 }
3033
3034 for (c_source_files.items) |*src| {
3035 if (!mem.eql(u8, src.src_path, "-")) continue;
3036
3037 const ext = src.ext orelse
3038 fatal("-E or -x is required when reading from a non-regular file", .{});
3039
3040 // "-" is stdin. Dump it to a real file.
3041 const new_file = blk: {
3042 var buf: ["stdin.".len + Compilation.FileExt.max_len]u8 = undefined;
3043 const fname = try std.fmt.bufPrint(&buf, "stdin.{s}", .{@tagName(ext)});
3044 const new_name = try local_cache_directory.tmpFilePath(arena, fname);
3045
3046 try local_cache_directory.handle.makePath("tmp");
3047 var outfile = try local_cache_directory.handle.createFile(new_name, .{});
3048 defer outfile.close();
3049 errdefer local_cache_directory.handle.deleteFile(new_name) catch {};
3050
3051 try outfile.writeFileAll(io.getStdIn(), .{});
3052 break :blk new_name;
3053 };
3054 temp_stdin_file = new_file;
3055 src.src_path = new_file;
3056 }
3057
3023 if (build_options.have_llvm and emit_asm != .no) {3058 if (build_options.have_llvm and emit_asm != .no) {
3024 // LLVM has no way to set this non-globally.3059 // LLVM has no way to set this non-globally.
3025 const argv = [_][*:0]const u8{ "zig (LLVM option parsing)", "--x86-asm-syntax=intel" };3060 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...@@ -3873,7 +3908,7 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, fancy_output: ?*Translate
38733908
3874 const c_src_basename = fs.path.basename(c_source_file.src_path);3909 const c_src_basename = fs.path.basename(c_source_file.src_path);
3875 const dep_basename = try std.fmt.allocPrint(arena, "{s}.d", .{c_src_basename});3910 const dep_basename = try std.fmt.allocPrint(arena, "{s}.d", .{c_src_basename});
3876 const out_dep_path = try comp.tmpFilePath(arena, dep_basename);3911 const out_dep_path = try comp.local_cache_directory.tmpFilePath(arena, dep_basename);
3877 break :blk out_dep_path;3912 break :blk out_dep_path;
3878 };3913 };
38793914
test/tests.zig+93
...@@ -777,6 +777,52 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -777,6 +777,52 @@ pub fn addCliTests(b: *std.Build) *Step {
777 step.dependOn(&cleanup.step);777 step.dependOn(&cleanup.step);
778 }778 }
779779
780 // Test `zig cc -x c -`
781 // Test author was not able to figure out how to start a child process and
782 // give an fd to it's stdin. fork/exec works, but we are limiting ourselves
783 // to POSIX.
784 //
785 // TODO: the "zig cc <..." step should be a RunStep.create(...)
786 // However, how do I create a command (RunStep) that invokes a command
787 // with a specific file descriptor in the stdin?
788 //if (builtin.os.tag != .windows) {
789 // const tmp_path = b.makeTempPath();
790 // var dir = std.fs.cwd().openDir(tmp_path, .{}) catch @panic("unhandled");
791 // dir.writeFile("truth.c", "int main() { return 42; }") catch @panic("unhandled");
792 // var infile = dir.openFile("truth.c", .{}) catch @panic("unhandled");
793
794 // const outfile = std.fs.path.joinZ(
795 // b.allocator,
796 // &[_][]const u8{ tmp_path, "truth" },
797 // ) catch @panic("unhandled");
798
799 // const pid_result = std.os.fork() catch @panic("unhandled");
800 // if (pid_result == 0) { // child
801 // std.os.dup2(infile.handle, std.os.STDIN_FILENO) catch @panic("unhandled");
802 // const argv = &[_:null]?[*:0]const u8{
803 // b.zig_exe, "cc",
804 // "-o", outfile,
805 // "-x", "c",
806 // "-",
807 // };
808 // const envp = &[_:null]?[*:0]const u8{
809 // std.fmt.allocPrintZ(b.allocator, "ZIG_GLOBAL_CACHE_DIR={s}", .{tmp_path}) catch @panic("unhandled"),
810 // };
811 // const err = std.os.execveZ(b.zig_exe, argv, envp);
812 // std.debug.print("execve error: {any}\n", .{err});
813 // std.os.exit(1);
814 // }
815
816 // const res = std.os.waitpid(pid_result, 0);
817 // assert(0 == res.status);
818
819 // // run the compiled executable and check if it's telling the truth.
820 // _ = exec(b.allocator, tmp_path, 42, &[_][]const u8{outfile}) catch @panic("unhandled");
821
822 // const cleanup = b.addRemoveDirTree(tmp_path);
823 // step.dependOn(&cleanup.step);
824 //}
825
780 {826 {
781 // Test `zig fmt`.827 // Test `zig fmt`.
782 // This test must use a temporary directory rather than a cache828 // This test must use a temporary directory rather than a cache
...@@ -1154,3 +1200,50 @@ pub fn addCases(...@@ -1154,3 +1200,50 @@ pub fn addCases(
1154 check_case_exe,1200 check_case_exe,
1155 );1201 );
1156}1202}
1203
1204fn exec(
1205 allocator: std.mem.Allocator,
1206 cwd: []const u8,
1207 expect_code: u8,
1208 argv: []const []const u8,
1209) !std.ChildProcess.ExecResult {
1210 const max_output_size = 100 * 1024;
1211 const result = std.ChildProcess.exec(.{
1212 .allocator = allocator,
1213 .argv = argv,
1214 .cwd = cwd,
1215 .max_output_bytes = max_output_size,
1216 }) catch |err| {
1217 std.debug.print("The following command failed:\n", .{});
1218 printCmd(cwd, argv);
1219 return err;
1220 };
1221 switch (result.term) {
1222 .Exited => |code| {
1223 if (code != expect_code) {
1224 std.debug.print(
1225 "The following command exited with error code {}, expected {}:\n",
1226 .{ code, expect_code },
1227 );
1228 printCmd(cwd, argv);
1229 std.debug.print("stderr:\n{s}\n", .{result.stderr});
1230 return error.CommandFailed;
1231 }
1232 },
1233 else => {
1234 std.debug.print("The following command terminated unexpectedly:\n", .{});
1235 printCmd(cwd, argv);
1236 std.debug.print("stderr:\n{s}\n", .{result.stderr});
1237 return error.CommandFailed;
1238 },
1239 }
1240 return result;
1241}
1242
1243fn printCmd(cwd: []const u8, argv: []const []const u8) void {
1244 std.debug.print("cd {s} && ", .{cwd});
1245 for (argv) |arg| {
1246 std.debug.print("{s} ", .{arg});
1247 }
1248 std.debug.print("\n", .{});
1249}