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 }