lib/chant/src/preprocess/system.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const sys = @import("sys");
3 const types = @import("types.zig");
4
5 const Error = types.Error;
6 const Options = types.Options;
7 const Prepared = types.Prepared;
8
9 pub fn run(
10 arena: std.mem.Allocator,
11 environ: std.process.Environ,
12 source_path: []const u8,
13 source_dir: []const u8,
14 from_temporary: bool,
15 options: Options,
16 diagnostic: ?*types.Diagnostic,
17 ) Error![]const u8 {
18 var argv = std.ArrayListUnmanaged([]const u8).empty;
19 defer argv.deinit(arena);
20 try argv.appendSlice(arena, &.{ "cc", "-std=c2x", "-E", "-nostdinc" });
21 if (from_temporary) {
22 try argv.append(arena, "-iquote");
23 try argv.append(arena, source_dir);
24 }
25 for (options.include_dirs) |dir| {
26 try argv.append(arena, "-I");
27 try argv.append(arena, dir);
28 }
29 for (options.defines) |define| {
30 try argv.append(arena, "-D");
31 try argv.append(arena, define);
32 }
33 try argv.append(arena, source_path);
34
35 var io_state = std.Io.Threaded.init(arena, .{ .environ = environ });
36 defer io_state.deinit();
37
38 const result = sys.process.run(arena, io_state.io(), .{
39 .argv = argv.items,
40 .stdout_limit = .limited(16 * 1024 * 1024),
41 .stderr_limit = .limited(256 * 1024),
42 }) catch |err| switch (err) {
43 error.FileNotFound => return error.PreprocessorUnavailable,
44 error.OutOfMemory => return error.OutOfMemory,
45 else => {
46 recordFailure(diagnostic, source_path, @errorName(err));
47 return error.PreprocessFailed;
48 },
49 };
50 switch (result.term) {
51 .exited => |code| if (code != 0) {
52 recordFailure(diagnostic, source_path, failureText(result.stderr, result.stdout));
53 return error.PreprocessFailed;
54 },
55 else => {
56 recordFailure(diagnostic, source_path, failureText(result.stderr, result.stdout));
57 return error.PreprocessFailed;
58 },
59 }
60 return result.stdout;
61 }
62
63 const failure_text_limit = 512;
64
65 fn recordFailure(diagnostic: ?*types.Diagnostic, source_path: []const u8, message: []const u8) void {
66 const out = diagnostic orelse return;
67 out.* = .{ .message = message, .file = source_path };
68 }
69
70 fn failureText(stderr: []const u8, stdout: []const u8) []const u8 {
71 const text = if (std.mem.trim(u8, stderr, " \t\r\n").len != 0) stderr else stdout;
72 const trimmed = std.mem.trim(u8, text, " \t\r\n");
73 return trimmed[0..@min(trimmed.len, failure_text_limit)];
74 }
75
76 pub fn writeTemporary(arena: std.mem.Allocator, source: []const u8) Error!Prepared {
77 const path = try std.fmt.allocPrint(arena, "/tmp/chant-preprocess-{d}.c", .{sys.time.nanoTimestamp()});
78 sys.fs.writeFile(path, source) catch return error.PreprocessFailed;
79 return .{ .path = path, .temporary = true };
80 }
81
82 test "preprocess failure carries the compiler stderr" {
83 var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
84 defer arena_state.deinit();
85 const arena = arena_state.allocator();
86
87 const source_path = "/tmp/chant-preprocess-diagnostic.c";
88 sys.fs.writeFile(source_path,
89 \\#include <chant-no-such-header.h>
90 \\int x;
91 \\
92 ) catch return;
93
94 var diagnostic: types.Diagnostic = .{};
95 const failed = run(arena, sys.env.current(), source_path, "/tmp", false, .{}, &diagnostic);
96 if (failed) |_| {
97 return error.TestUnexpectedResult;
98 } else |err| switch (err) {
99 error.PreprocessorUnavailable => return,
100 error.PreprocessFailed => {
101 try std.testing.expect(diagnostic.message.len != 0);
102 try std.testing.expect(std.mem.indexOf(u8, diagnostic.message, "chant-no-such-header.h") != null);
103 try std.testing.expectEqualStrings(source_path, diagnostic.file);
104 },
105 else => return err,
106 }
107 }
108
109 test "failure text prefers stderr and bounds the excerpt" {
110 try std.testing.expectEqualStrings("fatal error", failureText(" fatal error\n", "ignored"));
111 try std.testing.expectEqualStrings("stdout text", failureText(" \n", "stdout text\n"));
112 const long = @as([((failure_text_limit + 64)) * ("e").len]u8, @bitCast(@as([(failure_text_limit + 64)][("e").len]u8, @splat(("e")[0..("e").len].*))));
113 try std.testing.expectEqual(failure_text_limit, failureText(&long, "").len);
114 }