|
| 1 | +const std = @import("std"); |
| 2 | +const Step = std.Build.Step; |
| 3 | +const LazyPath = std.Build.LazyPath; |
| 4 | +const fs = std.fs; |
| 5 | +const mem = std.mem; |
| 6 | + |
| 7 | +const TranslateC = @This(); |
| 8 | + |
| 9 | +pub const base_id: Step.Id = .custom; |
| 10 | + |
| 11 | +step: Step, |
| 12 | +translate_c_exe: *Step.Compile, |
| 13 | +source: std.Build.LazyPath, |
| 14 | +include_dirs: std.ArrayList(std.Build.Module.IncludeDir), |
| 15 | +target: std.Build.ResolvedTarget, |
| 16 | +optimize: std.builtin.OptimizeMode, |
| 17 | +output_file: std.Build.GeneratedFile, |
| 18 | +link_libc: bool, |
| 19 | + |
| 20 | +pub const Options = struct { |
| 21 | + root_source_file: std.Build.LazyPath, |
| 22 | + target: std.Build.ResolvedTarget, |
| 23 | + optimize: std.builtin.OptimizeMode, |
| 24 | + link_libc: bool = true, |
| 25 | + translate_c_dep_name: []const u8 = "translate-c", |
| 26 | +}; |
| 27 | + |
| 28 | +pub fn create(owner: *std.Build, options: Options) *TranslateC { |
| 29 | + const translate_c_exe = owner.dependency(options.translate_c_dep_name, .{ |
| 30 | + .optimize = .ReleaseFast, |
| 31 | + }).artifact("translate-c"); |
| 32 | + |
| 33 | + const translate_c = owner.allocator.create(TranslateC) catch @panic("OOM"); |
| 34 | + const source = options.root_source_file.dupe(owner); |
| 35 | + translate_c.* = .{ |
| 36 | + .step = Step.init(.{ |
| 37 | + .id = base_id, |
| 38 | + .name = "translate-c", |
| 39 | + .owner = owner, |
| 40 | + .makeFn = make, |
| 41 | + }), |
| 42 | + .translate_c_exe = translate_c_exe, |
| 43 | + .source = source, |
| 44 | + .include_dirs = .init(owner.allocator), |
| 45 | + .target = options.target, |
| 46 | + .optimize = options.optimize, |
| 47 | + .output_file = .{ .step = &translate_c.step }, |
| 48 | + .link_libc = options.link_libc, |
| 49 | + }; |
| 50 | + source.addStepDependencies(&translate_c.step); |
| 51 | + translate_c.step.dependOn(&translate_c_exe.step); |
| 52 | + return translate_c; |
| 53 | +} |
| 54 | + |
| 55 | +pub const AddExecutableOptions = struct { |
| 56 | + name: ?[]const u8 = null, |
| 57 | + version: ?std.SemanticVersion = null, |
| 58 | + target: ?std.Build.ResolvedTarget = null, |
| 59 | + optimize: ?std.builtin.OptimizeMode = null, |
| 60 | + linkage: ?std.builtin.LinkMode = null, |
| 61 | +}; |
| 62 | + |
| 63 | +pub fn getOutput(translate_c: *TranslateC) std.Build.LazyPath { |
| 64 | + return .{ .generated = .{ .file = &translate_c.output_file } }; |
| 65 | +} |
| 66 | + |
| 67 | +/// Creates a step to build an executable from the translated source. |
| 68 | +pub fn addExecutable(translate_c: *TranslateC, options: AddExecutableOptions) *Step.Compile { |
| 69 | + return translate_c.step.owner.addExecutable(.{ |
| 70 | + .root_source_file = translate_c.getOutput(), |
| 71 | + .name = options.name orelse "translated_c", |
| 72 | + .version = options.version, |
| 73 | + .target = options.target orelse translate_c.target, |
| 74 | + .optimize = options.optimize orelse translate_c.optimize, |
| 75 | + .linkage = options.linkage, |
| 76 | + }); |
| 77 | +} |
| 78 | + |
| 79 | +/// Creates a module from the translated source and adds it to the package's |
| 80 | +/// module set making it available to other packages which depend on this one. |
| 81 | +/// `createModule` can be used instead to create a private module. |
| 82 | +pub fn addModule(translate_c: *TranslateC, name: []const u8) *std.Build.Module { |
| 83 | + return translate_c.step.owner.addModule(name, .{ |
| 84 | + .root_source_file = translate_c.getOutput(), |
| 85 | + }); |
| 86 | +} |
| 87 | + |
| 88 | +/// Creates a private module from the translated source to be used by the |
| 89 | +/// current package, but not exposed to other packages depending on this one. |
| 90 | +/// `addModule` can be used instead to create a public module. |
| 91 | +pub fn createModule(translate_c: *TranslateC) *std.Build.Module { |
| 92 | + return translate_c.step.owner.createModule(.{ |
| 93 | + .root_source_file = translate_c.getOutput(), |
| 94 | + .target = translate_c.target, |
| 95 | + .optimize = translate_c.optimize, |
| 96 | + .link_libc = translate_c.link_libc, |
| 97 | + }); |
| 98 | +} |
| 99 | + |
| 100 | +pub fn addCheckFile(translate_c: *TranslateC, expected_matches: []const []const u8) *Step.CheckFile { |
| 101 | + return Step.CheckFile.create( |
| 102 | + translate_c.step.owner, |
| 103 | + translate_c.getOutput(), |
| 104 | + .{ .expected_matches = expected_matches }, |
| 105 | + ); |
| 106 | +} |
| 107 | + |
| 108 | +fn make(step: *Step, options: Step.MakeOptions) !void { |
| 109 | + _ = options; |
| 110 | + const b = step.owner; |
| 111 | + const translate_c: *TranslateC = @fieldParentPtr("step", step); |
| 112 | + |
| 113 | + var argv_list = std.ArrayList([]const u8).init(b.allocator); |
| 114 | + try argv_list.append(translate_c.translate_c_exe.getEmittedBin().getPath(b)); |
| 115 | + |
| 116 | + var man = b.graph.cache.obtain(); |
| 117 | + defer man.deinit(); |
| 118 | + |
| 119 | + // Random bytes to make TranslateC unique. Refresh this with new |
| 120 | + // random bytes when TranslateC implementation is modified in a |
| 121 | + // non-backwards-compatible way. |
| 122 | + man.hash.add(@as(u32, 0x2701BED2)); |
| 123 | + |
| 124 | + if (!translate_c.target.query.isNative()) { |
| 125 | + const triple = try translate_c.target.query.zigTriple(b.allocator); |
| 126 | + try argv_list.append(b.fmt("--target={s}", .{triple})); |
| 127 | + man.hash.addBytes(triple); |
| 128 | + } |
| 129 | + |
| 130 | + const c_source_path = translate_c.source.getPath3(b, step); |
| 131 | + _ = try man.addFilePath(c_source_path, null); |
| 132 | + const resolved_source_path = b.pathResolve(&.{ c_source_path.root_dir.path orelse ".", c_source_path.sub_path }); |
| 133 | + try argv_list.append(resolved_source_path); |
| 134 | + |
| 135 | + const out_name = b.fmt("{s}.zig", .{std.fs.path.stem(c_source_path.sub_path)}); |
| 136 | + if (try step.cacheHit(&man)) { |
| 137 | + const digest = man.final(); |
| 138 | + translate_c.output_file.path = try b.cache_root.join(b.allocator, &.{ |
| 139 | + "o", &digest, out_name, |
| 140 | + }); |
| 141 | + return; |
| 142 | + } |
| 143 | + |
| 144 | + const digest = man.final(); |
| 145 | + |
| 146 | + const sub_path = b.pathJoin(&.{ "o", &digest, out_name }); |
| 147 | + const sub_path_dirname = std.fs.path.dirname(sub_path).?; |
| 148 | + const out_path = try b.cache_root.join(b.allocator, &.{sub_path}); |
| 149 | + |
| 150 | + b.cache_root.handle.makePath(sub_path_dirname) catch |err| { |
| 151 | + return step.fail("unable to make path '{}{s}': {s}", .{ |
| 152 | + b.cache_root, sub_path_dirname, @errorName(err), |
| 153 | + }); |
| 154 | + }; |
| 155 | + try argv_list.append("-o"); |
| 156 | + try argv_list.append(out_path); |
| 157 | + |
| 158 | + var child = std.process.Child.init(argv_list.items, b.allocator); |
| 159 | + child.cwd = b.build_root.path; |
| 160 | + child.cwd_dir = b.build_root.handle; |
| 161 | + child.env_map = &b.graph.env_map; |
| 162 | + |
| 163 | + child.stdin_behavior = .Ignore; |
| 164 | + child.stdout_behavior = .Ignore; |
| 165 | + child.stderr_behavior = .Pipe; |
| 166 | + |
| 167 | + try child.spawn(); |
| 168 | + const stderr = try child.stderr.?.reader().readAllAlloc(b.allocator, 10 * 1024 * 1024); |
| 169 | + const term = try child.wait(); |
| 170 | + |
| 171 | + switch (term) { |
| 172 | + .Exited => |code| { |
| 173 | + if (code != 0) { |
| 174 | + return step.fail( |
| 175 | + "failed to translate {s}:\n{s}", |
| 176 | + .{ resolved_source_path, stderr }, |
| 177 | + ); |
| 178 | + } |
| 179 | + }, |
| 180 | + .Signal, .Stopped, .Unknown => { |
| 181 | + return step.fail( |
| 182 | + "command to translate {s} failed unexpectedly", |
| 183 | + .{resolved_source_path}, |
| 184 | + ); |
| 185 | + }, |
| 186 | + } |
| 187 | + |
| 188 | + translate_c.output_file.path = out_path; |
| 189 | + try man.writeManifest(); |
| 190 | +} |
0 commit comments