1const Compile = @This();
2
3const std = @import("std");
4const Allocator = std.mem.Allocator;
5const mem = std.mem;
6const Configuration = std.Build.Configuration;
7const Dir = std.Io.Dir;
8const Path = std.Build.Cache.Path;
9const Module = std.Build.Configuration.Module;
10const Io = std.Io;
11const Sha256 = std.crypto.hash.sha2.Sha256;
12const assert = std.debug.assert;
13
14const Step = @import("../Step.zig");
15const Maker = @import("../../Maker.zig");
16const PkgConfig = @import("../PkgConfig.zig");
17
18/// Populated when there is compiler process that lives across multiple calls
19/// to `make`.
20zig_process: ?*Step.ZigProcess = null,
21/// Populated by InstallArtifact.
22installed_path: ?Path = null,
23/// Populated by `make`, used by `Run`.
24is_linking_libc: bool = false,
25
26pub fn make(
27 compile: *Compile,
28 compile_index: Configuration.Step.Index,
29 maker: *Maker,
30 progress_node: std.Progress.Node,
31) Step.ExtendedMakeError!void {
32 const graph = maker.graph;
33 const gpa = maker.gpa;
34 const conf = &maker.scanned_config.configuration;
35 const conf_step = compile_index.ptr(conf);
36 const conf_comp = conf_step.extended.get(conf.extra).compile;
37
38 var arena_allocator: std.heap.ArenaAllocator = .init(gpa);
39 defer arena_allocator.deinit();
40 const arena = arena_allocator.allocator();
41
42 var argv: std.ArrayList([]const u8) = .empty;
43 defer argv.deinit(gpa);
44
45 try lowerZigArgs(arena, compile, compile_index, maker, progress_node, &argv, false);
46
47 const incremental = conf_comp.flags4.incremental.toBool() orelse graph.incremental == true;
48
49 const maybe_output_dir = Step.evalZigProcess(
50 compile_index,
51 maker,
52 argv.items,
53 progress_node,
54 incremental and (maker.watch or maker.web_server != null),
55 ) catch |err| switch (err) {
56 error.NeedCompileErrorCheck => {
57 try checkCompileErrors(arena, maker, compile_index);
58 return;
59 },
60 else => |e| return e,
61 };
62
63 const root_module = conf_comp.root_module.get(conf);
64 const target = root_module.resolved_target.get(conf).?.result.get(conf);
65
66 // Update generated files
67 if (maybe_output_dir) |output_dir| {
68 if (conf_comp.emit_directory.value) |gf| maker.generatedPath(gf).* = output_dir;
69 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_bin.value, .bin);
70 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_pdb.value, .pdb);
71 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_implib.value, .implib);
72 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_h.value, .h);
73 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_docs.value, .docs);
74 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_asm.value, .@"asm");
75 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_llvm_ir.value, .llvm_ir);
76 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_llvm_bc.value, .llvm_bc);
77 }
78
79 if (conf_comp.flags3.kind == .lib and conf_comp.flags2.linkage == .dynamic and
80 conf_comp.version.value != null and target.flags.os_tag != .windows)
81 {
82 if (conf_comp.generated_bin.value) |generated_bin| {
83 const full_dest_path = maker.generatedPath(generated_bin).*;
84 try maker.installSymLinks(arena, full_dest_path, compile_index, compile_index);
85 }
86 }
87}
88
89fn updateGeneratedFile(
90 maker: *Maker,
91 arena: Allocator,
92 conf_comp: *const Configuration.Step.Compile,
93 out_path: std.Build.Cache.Path,
94 target: *const Configuration.TargetQuery,
95 opt_gf: ?Configuration.GeneratedFileIndex,
96 ea: std.zig.EmitArtifact,
97) Allocator.Error!void {
98 const gf = opt_gf orelse return;
99 const graph = maker.graph;
100 const conf = &maker.scanned_config.configuration;
101 const name = try ea.cacheName(arena, .{
102 .root_name = conf_comp.root_name.slice(conf),
103 .cpu_arch = target.flags.cpu_arch.unwrap().?,
104 .os_tag = target.flags.os_tag.unwrap().?,
105 .ofmt = target.flags.object_format.unwrap().?,
106 .abi = target.flags.abi.unwrap().?,
107 .output_mode = switch (conf_comp.flags3.kind) {
108 .lib => .Lib,
109 .obj, .test_obj => .Obj,
110 .exe, .@"test" => .Exe,
111 },
112 .link_mode = conf_comp.flags2.linkage.unwrap(),
113 .version = if (conf_comp.version.value) |v|
114 std.SemanticVersion.parse(v.slice(conf)) catch unreachable
115 else
116 null,
117 });
118 maker.generatedPath(gf).* = try out_path.join(graph.arena, name);
119}
120
121/// List of importable modules in a compilation's module graph, including
122/// the root module. The root module is guaranteed to be first.
123const ModuleList = std.array_hash_map.Auto(Configuration.Module.Index, Configuration.String);
124/// Keyed on the first key in the module list.
125pub const ModuleGraph = std.array_hash_map.Custom(ModuleList, void, ModuleListContext, false);
126
127const ModuleListContext = struct {
128 pub fn eql(ctx: @This(), a: ModuleList, b: ModuleList) bool {
129 _ = ctx;
130 return a.keys()[0] == b.keys()[0];
131 }
132
133 pub fn hash(ctx: @This(), key: ModuleList) u32 {
134 _ = ctx;
135 return std.hash.int(@backingInt(key.keys()[0]));
136 }
137
138 const Adapter = struct {
139 pub fn eql(ctx: @This(), a: Configuration.Module.Index, b: ModuleList, b_index: usize) bool {
140 _ = ctx;
141 _ = b_index;
142 return a == b.keys()[0];
143 }
144
145 pub fn hash(ctx: @This(), key: Configuration.Module.Index) u32 {
146 _ = ctx;
147 return std.hash.int(@backingInt(key));
148 }
149 };
150};
151
152fn lowerZigArgs(
153 arena: Allocator,
154 compile: *Compile,
155 compile_index: Configuration.Step.Index,
156 maker: *Maker,
157 progress_node: std.Progress.Node,
158 zig_args: *std.ArrayList([]const u8),
159 fuzz: bool,
160) Step.ExtendedMakeError!void {
161 const step = maker.stepByIndex(compile_index);
162 const graph = maker.graph;
163 const gpa = maker.gpa;
164 const conf = &maker.scanned_config.configuration;
165 const conf_step = compile_index.ptr(conf);
166 const conf_comp = conf_step.extended.get(conf.extra).compile;
167 const root_module_target = conf_comp.rootModuleTarget(conf);
168
169 try zig_args.append(gpa, graph.zig_exe);
170
171 const cmd = switch (conf_comp.flags3.kind) {
172 .lib => "build-lib",
173 .exe => "build-exe",
174 .obj => "build-obj",
175 .@"test" => "test",
176 .test_obj => "test-obj",
177 };
178 try zig_args.append(gpa, cmd);
179
180 if (graph.reference_trace) |some| {
181 try zig_args.append(gpa, try arena.print("-freference-trace={d}", .{some}));
182 }
183 try addFlag(gpa, zig_args, "allow-so-scripts", conf_comp.flags2.allow_so_scripts.toBool() orelse graph.allow_so_scripts);
184
185 try addFlag(gpa, zig_args, "llvm", conf_comp.flags2.use_llvm.toBool());
186 try addFlag(gpa, zig_args, "lld", conf_comp.flags2.use_lld.toBool());
187 try addFlag(gpa, zig_args, "new-linker", conf_comp.flags2.use_new_linker.toBool());
188
189 const root_module = conf_comp.root_module.get(conf);
190
191 if (root_module.resolved_target.get(conf).?.query.unwrap()) |query| {
192 if (query.get(conf).flags.object_format.unwrap()) |ofmt| {
193 try zig_args.append(gpa, try arena.print("-ofmt={t}", .{ofmt}));
194 }
195 }
196
197 switch (conf_comp.flags3.entry) {
198 .default => {},
199 .disabled => try zig_args.append(gpa, "-fno-entry"),
200 .enabled => try zig_args.append(gpa, "-fentry"),
201 .symbol_name => {
202 const symbol_name = conf_comp.entry.value.?.slice(conf);
203 try zig_args.append(gpa, try arena.print("-fentry={s}", .{symbol_name}));
204 },
205 }
206
207 for (conf_comp.force_undefined_symbols.slice) |symbol_name| {
208 try zig_args.appendSlice(gpa, &.{ "--force_undefined", symbol_name.slice(conf) });
209 }
210
211 if (conf_comp.stack_size.value) |stack_size| {
212 try zig_args.appendSlice(gpa, &.{ "--stack", try arena.print("{d}", .{stack_size}) });
213 }
214
215 try addBool(gpa, zig_args, "-ffuzz", fuzz);
216
217 {
218 var is_linking_libc = false;
219 var is_linking_libcpp = false;
220
221 // Stores system libraries that have already been seen for at least one
222 // module, along with any C compiler arguments that need to be passed
223 // to the compiler for each module individually as reported by
224 // pkg-config.
225 var seen_system_libs: std.array_hash_map.Auto(Configuration.String, []const []const u8) = .empty;
226 var frameworks: std.array_hash_map.Auto(Configuration.String, Configuration.Module.Framework.Flags) = .empty;
227 var module_graph: ModuleGraph = .empty;
228
229 var prev_has_cflags = false;
230 var prev_has_rcflags = false;
231 var prev_search_strategy: Configuration.SystemLib.SearchStrategy = .paths_first;
232 var prev_preferred_link_mode: std.builtin.LinkMode = .dynamic;
233 // Track the number of positional arguments so that a nice error can be
234 // emitted if there is nothing to link.
235 var total_linker_objects: usize = @intFromBool(root_module.root_source_file != .none);
236
237 // Fully recursive iteration including dynamic libraries to detect
238 // libc and libc++ linkage.
239 for (try getCompileDependencies(arena, &module_graph, conf, compile_index, true)) |some_compile_index| {
240 const some_compile = some_compile_index.ptr(conf).extended.get(conf.extra).compile;
241 const modules = try getModuleList(arena, &module_graph, some_compile.root_module, conf);
242 for (modules.keys()) |mod_index| {
243 const mod = mod_index.get(conf);
244 is_linking_libc = is_linking_libc or mod.flags2.link_libc == .true;
245 is_linking_libcpp = is_linking_libcpp or mod.flags2.link_libcpp == .true;
246 }
247 }
248
249 var cli_named_modules = try CliNamedModules.init(arena, &module_graph, compile_index, maker);
250
251 // For this loop, don't chase dynamic libraries because their link
252 // objects are already linked.
253 for (try getCompileDependencies(arena, &module_graph, conf, compile_index, false)) |dep_compile_index| {
254 const dep_compile = dep_compile_index.ptr(conf).extended.get(conf.extra).compile;
255 const modules = try getModuleList(arena, &module_graph, dep_compile.root_module, conf);
256 for (modules.keys()) |mod_index| {
257 const mod = mod_index.get(conf);
258 // While walking transitive dependencies, if a given link object is
259 // already included in a library, it should not redundantly be
260 // placed on the linker line of the dependee.
261 const my_responsibility = dep_compile_index == compile_index;
262 const already_linked = !my_responsibility and dep_compile.isDynamicLibrary();
263
264 // Inherit dependencies on darwin frameworks.
265 if (!already_linked) {
266 for (mod.frameworks.slice) |framework| {
267 try frameworks.put(arena, framework.name, framework.flags);
268 }
269 }
270
271 // Inherit dependencies on system libraries and static libraries.
272 for (0..mod.link_objects.len) |lo_i| switch (mod.link_objects.get(conf.extra, lo_i)) {
273 .static_path => |static_path| {
274 if (my_responsibility) {
275 try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, static_path, compile_index));
276 total_linker_objects += 1;
277 }
278 },
279 .system_lib => |system_lib_index| {
280 const system_lib = system_lib_index.get(conf);
281 const system_lib_name = system_lib.name.slice(conf);
282 const system_lib_gop = try seen_system_libs.getOrPut(arena, system_lib.name);
283 if (system_lib_gop.found_existing) {
284 try zig_args.appendSlice(gpa, system_lib_gop.value_ptr.*);
285 continue;
286 } else {
287 system_lib_gop.value_ptr.* = &.{};
288 }
289
290 if (already_linked)
291 continue;
292
293 if ((system_lib.flags.search_strategy != prev_search_strategy or
294 system_lib.flags.preferred_link_mode != prev_preferred_link_mode) and
295 conf_comp.flags2.linkage != .static)
296 {
297 try zig_args.ensureUnusedCapacity(gpa, 1);
298 switch (system_lib.flags.search_strategy) {
299 .no_fallback => switch (system_lib.flags.preferred_link_mode) {
300 .dynamic => zig_args.appendAssumeCapacity("-search_dylibs_only"),
301 .static => zig_args.appendAssumeCapacity("-search_static_only"),
302 },
303 .paths_first => switch (system_lib.flags.preferred_link_mode) {
304 .dynamic => zig_args.appendAssumeCapacity("-search_paths_first"),
305 .static => zig_args.appendAssumeCapacity("-search_paths_first_static"),
306 },
307 .mode_first => switch (system_lib.flags.preferred_link_mode) {
308 .dynamic => zig_args.appendAssumeCapacity("-search_dylibs_first"),
309 .static => zig_args.appendAssumeCapacity("-search_static_first"),
310 },
311 }
312 prev_search_strategy = system_lib.flags.search_strategy;
313 prev_preferred_link_mode = system_lib.flags.preferred_link_mode;
314 }
315
316 const prefix: []const u8 = prefix: {
317 if (system_lib.flags.needed) break :prefix "-needed-l";
318 if (system_lib.flags.weak) break :prefix "-weak-l";
319 break :prefix "-l";
320 };
321 l: {
322 pc: {
323 const force = switch (system_lib.flags.use_pkg_config) {
324 .no => break :pc,
325 .yes => false,
326 .force => true,
327 };
328
329 const pkg_conf_node = progress_node.start("pkg-config", 0);
330 defer pkg_conf_node.end();
331
332 if (PkgConfig.run(maker, step, arena, pkg_conf_node, system_lib_name, force)) |pc| {
333 try zig_args.appendSlice(gpa, pc.cflags);
334 try zig_args.appendSlice(gpa, pc.libs);
335 try seen_system_libs.put(arena, system_lib.name, pc.cflags);
336 break :l;
337 } else |err| switch (err) {
338 error.PkgConfigUnavailable,
339 error.PackageNotFound,
340 => {
341 // pkg-config failed, so fall back to linking the library by name directly.
342 assert(!force);
343 break :pc;
344 },
345 else => |e| return e,
346 }
347 }
348 try zig_args.append(gpa, try arena.print("{s}{s}", .{
349 prefix, system_lib_name,
350 }));
351 }
352 },
353 .other_step => |other_step_index| {
354 const other = other_step_index.ptr(conf);
355 const other_compile = other.extended.get(conf.extra).compile;
356 switch (other_compile.flags3.kind) {
357 .exe => return step.fail(maker, "cannot link with an executable build artifact", .{}),
358 .@"test" => return step.fail(maker, "cannot link with a test", .{}),
359 .obj, .test_obj => {
360 const included_in_lib_or_obj = switch (dep_compile.flags3.kind) {
361 .lib, .obj, .test_obj => !my_responsibility,
362 else => false,
363 };
364 if (!already_linked and !included_in_lib_or_obj) {
365 try zig_args.append(gpa, try maker.resolveLazyPathAbs(
366 arena,
367 .{ .generated = .{ .index = other_compile.generated_bin.value.? } },
368 compile_index,
369 ));
370 total_linker_objects += 1;
371 }
372 },
373 .lib => l: {
374 const other_produces_implib = other_compile.producesImplib(conf);
375 const other_is_static = other_produces_implib or other_compile.isStaticLibrary();
376
377 if (conf_comp.isStaticLibrary() and other_is_static) {
378 // Avoid putting a static library inside a static library.
379 break :l;
380 }
381
382 // For DLLs, we must link against the implib.
383 // For everything else, we directly link
384 // against the library file.
385 const full_path_lib = try maker.resolveLazyPathAbs(
386 arena,
387 .{ .generated = .{
388 .index = if (other_produces_implib)
389 other_compile.generated_implib.value.?
390 else
391 other_compile.generated_bin.value.?,
392 } },
393 compile_index,
394 );
395
396 try zig_args.append(gpa, full_path_lib);
397 total_linker_objects += 1;
398
399 if (other_compile.flags2.linkage == .dynamic and
400 root_module_target.flags.os_tag != .windows)
401 {
402 if (Dir.path.dirname(full_path_lib)) |dirname| {
403 try zig_args.appendSlice(gpa, &.{ "-rpath", dirname });
404 }
405 }
406 },
407 }
408 },
409 .assembly_file => |asm_file| l: {
410 if (!my_responsibility) break :l;
411
412 if (prev_has_cflags) {
413 try zig_args.appendSlice(gpa, &.{ "-cflags", "--" });
414 prev_has_cflags = false;
415 }
416 try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, asm_file, compile_index));
417 total_linker_objects += 1;
418 },
419
420 .c_source_file => |c_source_file_index| l: {
421 if (!my_responsibility) break :l;
422
423 const c_source_file = c_source_file_index.get(conf);
424
425 if (prev_has_cflags or c_source_file.args.slice.len != 0) {
426 try zig_args.ensureUnusedCapacity(gpa, 2 + c_source_file.args.slice.len);
427 zig_args.appendAssumeCapacity("-cflags");
428 for (c_source_file.args.slice) |arg| {
429 zig_args.appendAssumeCapacity(arg.slice(conf));
430 }
431 zig_args.appendAssumeCapacity("--");
432 }
433 prev_has_cflags = (c_source_file.args.slice.len != 0);
434
435 if (c_source_file.flags.lang.get()) |lang|
436 (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-x", lang.clangIdentifier() };
437
438 try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, c_source_file.file, compile_index));
439
440 if (c_source_file.flags.lang != .default)
441 (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-x", "none" };
442
443 total_linker_objects += 1;
444 },
445
446 .c_source_files => |c_source_files_index| l: {
447 if (!my_responsibility) break :l;
448
449 const c_source_files = c_source_files_index.get(conf);
450
451 if (prev_has_cflags or c_source_files.args.slice.len != 0) {
452 try zig_args.ensureUnusedCapacity(gpa, 2 + c_source_files.args.slice.len);
453 zig_args.appendAssumeCapacity("-cflags");
454 for (c_source_files.args.slice) |arg| {
455 zig_args.appendAssumeCapacity(arg.slice(conf));
456 }
457 zig_args.appendAssumeCapacity("--");
458 }
459 prev_has_cflags = (c_source_files.args.slice.len != 0);
460
461 if (c_source_files.flags.lang.get()) |lang|
462 (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-x", lang.clangIdentifier() };
463
464 const root_path = try maker.resolveLazyPathIndexAbs(arena, c_source_files.root, compile_index);
465 try zig_args.ensureUnusedCapacity(gpa, c_source_files.sub_paths.slice.len);
466 for (c_source_files.sub_paths.slice) |sub_path| {
467 zig_args.appendAssumeCapacity(try Dir.path.join(arena, &.{
468 root_path, sub_path.slice(conf),
469 }));
470 }
471
472 if (c_source_files.flags.lang != .default)
473 (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-x", "none" };
474
475 total_linker_objects += c_source_files.sub_paths.slice.len;
476 },
477
478 .win32_resource_file => |rc_source_file_index| l: {
479 if (!my_responsibility) break :l;
480
481 const rc_source_file = rc_source_file_index.get(conf);
482
483 if (rc_source_file.args.slice.len == 0 and rc_source_file.include_paths.slice.len == 0) {
484 if (prev_has_rcflags) {
485 (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-rcflags", "--" };
486 prev_has_rcflags = false;
487 }
488 } else {
489 try zig_args.ensureUnusedCapacity(gpa, 1 + rc_source_file.args.slice.len);
490 zig_args.appendAssumeCapacity("-rcflags");
491 for (rc_source_file.args.slice) |arg| {
492 zig_args.appendAssumeCapacity(arg.slice(conf));
493 }
494 try zig_args.ensureUnusedCapacity(gpa, 1 + 2 * rc_source_file.include_paths.slice.len);
495 for (rc_source_file.include_paths.slice) |include_path| {
496 zig_args.appendAssumeCapacity("/I");
497 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, include_path, compile_index));
498 }
499 zig_args.appendAssumeCapacity("--");
500 prev_has_rcflags = true;
501 }
502 try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, rc_source_file.file, compile_index));
503 total_linker_objects += 1;
504 },
505 };
506
507 // We need to emit the --mod argument here so that the above link objects
508 // have the correct parent module, but only if the module is part of
509 // this compilation.
510 if (!my_responsibility) continue;
511 if (cli_named_modules.modules.getIndex(mod_index)) |module_cli_index| {
512 const module_cli_name = cli_named_modules.names.keys()[module_cli_index];
513 const module_index = cli_named_modules.modules.keys()[module_cli_index];
514 try appendModuleFlags(arena, module_index, zig_args, compile_index, maker);
515
516 const imports = mod.import_table.get(conf).imports.mal;
517
518 // --dep arguments
519 try zig_args.ensureUnusedCapacity(gpa, imports.len * 2);
520 for (imports.items(.name), imports.items(.module)) |name, import| {
521 const import_index = cli_named_modules.modules.getIndex(import).?;
522 const import_cli_name = cli_named_modules.names.keys()[import_index];
523 zig_args.appendAssumeCapacity("--dep");
524 const name_slice = name.slice(conf);
525 if (mem.eql(u8, import_cli_name, name_slice)) {
526 zig_args.appendAssumeCapacity(import_cli_name);
527 } else {
528 zig_args.appendAssumeCapacity(try arena.print("{s}={s}", .{
529 name_slice, import_cli_name,
530 }));
531 }
532 }
533
534 // When the CLI sees a -M argument, it determines whether it
535 // implies the existence of a Zig compilation unit based on
536 // whether there is a root source file. If there is no root
537 // source file, then this is not a zig compilation unit - it is
538 // perhaps a set of linker objects, or C source files instead.
539 // Linker objects are added to the CLI globally, while C source
540 // files must have a module parent.
541 try zig_args.ensureUnusedCapacity(gpa, 1);
542 if (mod.root_source_file.unwrap()) |lp| {
543 const src = try maker.resolveLazyPathIndexAbs(arena, lp, compile_index);
544 zig_args.appendAssumeCapacity(try arena.print("-M{s}={s}", .{ module_cli_name, src }));
545 } else if (moduleNeedsCliArg(&mod, conf)) {
546 zig_args.appendAssumeCapacity(try arena.print("-M{s}", .{module_cli_name}));
547 }
548 }
549 }
550 }
551
552 if (total_linker_objects == 0) {
553 return step.fail(maker, "the linker needs one or more objects to link", .{});
554 }
555
556 for (frameworks.keys(), frameworks.values()) |name, info| {
557 try zig_args.ensureUnusedCapacity(gpa, 2);
558 if (info.needed) {
559 zig_args.appendAssumeCapacity("-needed_framework");
560 } else if (info.weak) {
561 zig_args.appendAssumeCapacity("-weak_framework");
562 } else {
563 zig_args.appendAssumeCapacity("-framework");
564 }
565 zig_args.appendAssumeCapacity(name.slice(conf));
566 }
567
568 try zig_args.ensureUnusedCapacity(gpa, 2);
569 if (is_linking_libcpp) zig_args.appendAssumeCapacity("-lc++");
570 if (is_linking_libc) zig_args.appendAssumeCapacity("-lc");
571
572 compile.is_linking_libc = is_linking_libc;
573 }
574
575 if (conf_comp.win32_manifest.value) |manifest_file| {
576 try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, manifest_file, compile_index));
577 }
578
579 if (conf_comp.win32_module_definition.value) |module_file| {
580 try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, module_file, compile_index));
581 }
582
583 if (conf_comp.image_base.value) |image_base| {
584 (try zig_args.addManyAsArray(gpa, 2)).* = .{
585 "--image-base", try arena.print("0x{x}", .{image_base}),
586 };
587 }
588
589 for (conf_comp.filters.slice) |filter| {
590 (try zig_args.addManyAsArray(gpa, 2)).* = .{ "--test-filter", filter.slice(conf) };
591 }
592
593 switch (conf_comp.test_runner.u) {
594 .default => {},
595 .simple, .server => |lp| (try zig_args.addManyAsArray(gpa, 2)).* = .{
596 "--test-runner", try maker.resolveLazyPathIndexAbs(arena, lp, compile_index),
597 },
598 }
599
600 for (graph.debug_log_scopes.items) |log_scope| {
601 (try zig_args.addManyAsArray(gpa, 2)).* = .{ "--debug-log", log_scope };
602 }
603
604 try addBool(gpa, zig_args, "--debug-compile-errors", graph.debug_compile_errors);
605 try addBool(gpa, zig_args, "--debug-incremental", graph.debug_incremental);
606 try addBool(gpa, zig_args, "--verbose-air", graph.verbose_air);
607 try addBool(gpa, zig_args, "--verbose-llvm-ir", graph.verbose_llvm_ir);
608 try addBool(gpa, zig_args, "--verbose-link", graph.verbose_link or conf_comp.flags.verbose_link);
609 try addBool(gpa, zig_args, "--verbose-cc", graph.verbose_cc or conf_comp.flags.verbose_cc);
610 try addBool(gpa, zig_args, "--verbose-llvm-cpu-features", graph.verbose_llvm_cpu_features);
611 try addBool(gpa, zig_args, "--time-report", graph.time_report);
612
613 if (conf_comp.generated_bin.value == null) try zig_args.append(gpa, "-fno-emit-bin");
614 if (conf_comp.generated_asm.value != null) try zig_args.append(gpa, "-femit-asm");
615 if (conf_comp.generated_docs.value != null) try zig_args.append(gpa, "-femit-docs");
616 if (conf_comp.generated_implib.value != null) try zig_args.append(gpa, "-femit-implib");
617 if (conf_comp.generated_llvm_bc.value != null) try zig_args.append(gpa, "-femit-llvm-bc");
618 if (conf_comp.generated_llvm_ir.value != null) try zig_args.append(gpa, "-femit-llvm-ir");
619 if (conf_comp.generated_h.value != null) try zig_args.append(gpa, "-femit-h");
620
621 try addFlag(gpa, zig_args, "formatted-panics", conf_comp.flags2.formatted_panics.toBool());
622
623 switch (conf_comp.flags3.compress_debug_sections) {
624 .none => {},
625 .zlib => try zig_args.append(gpa, "--compress-debug-sections=zlib"),
626 .zstd => try zig_args.append(gpa, "--compress-debug-sections=zstd"),
627 }
628
629 try addBool(gpa, zig_args, "--eh-frame-hdr", conf_comp.flags.link_eh_frame_hdr);
630 try addBool(gpa, zig_args, "--emit-relocs", conf_comp.flags.link_emit_relocs);
631 try addBool(gpa, zig_args, "-ffunction-sections", conf_comp.flags.link_function_sections);
632 try addBool(gpa, zig_args, "-fdata-sections", conf_comp.flags.link_data_sections);
633
634 if (conf_comp.flags2.link_gc_sections.toBool()) |x|
635 try zig_args.append(gpa, if (x) "--gc-sections" else "--no-gc-sections");
636
637 if (!conf_comp.flags.linker_dynamicbase)
638 try zig_args.append(gpa, "--no-dynamicbase");
639
640 try addFlag(gpa, zig_args, "allow-shlib-undefined", conf_comp.flags2.linker_allow_shlib_undefined.toBool());
641 if (conf_comp.flags.link_z_notext) (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-z", "notext" };
642 if (!conf_comp.flags.link_z_relro) (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-z", "norelro" };
643 if (conf_comp.flags.link_z_lazy) (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-z", "lazy" };
644 if (conf_comp.link_z_common_page_size.value) |size| (try zig_args.addManyAsArray(gpa, 2)).* = .{
645 "-z", try arena.print("common-page-size={d}", .{size}),
646 };
647 if (conf_comp.link_z_max_page_size.value) |size| (try zig_args.addManyAsArray(gpa, 2)).* = .{
648 "-z", try arena.print("max-page-size={d}", .{size}),
649 };
650 if (conf_comp.flags.link_z_defs) (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-z", "defs" };
651
652 try zig_args.ensureUnusedCapacity(gpa, 2);
653 if (conf_comp.libc_file.value) |libc_file| {
654 zig_args.appendAssumeCapacity("--libc");
655 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, libc_file, compile_index));
656 } else if (graph.libc_file) |libc_file| {
657 zig_args.appendAssumeCapacity("--libc");
658 zig_args.appendAssumeCapacity(libc_file);
659 }
660
661 (try zig_args.addManyAsArray(gpa, 6)).* = .{
662 "--cache-dir", graph.local_cache_root.path orelse ".",
663 "--global-cache-dir", graph.global_cache_root.path orelse ".",
664 "--build-root", graph.build_root_directory.path orelse ".",
665 };
666
667 try zig_args.ensureUnusedCapacity(gpa, 1);
668 if (graph.debug_compiler_runtime_libs) |mode| switch (mode) {
669 .debug => zig_args.appendAssumeCapacity("--debug-rt"),
670 else => zig_args.appendAssumeCapacity(try arena.print("--debug-rt={t}", .{mode})),
671 };
672
673 {
674 try zig_args.ensureUnusedCapacity(gpa, 7);
675
676 zig_args.addManyAsArrayAssumeCapacity(2).* = .{ "--name", conf_comp.root_name.slice(conf) };
677
678 switch (conf_comp.flags2.linkage) {
679 .dynamic => zig_args.appendAssumeCapacity("-dynamic"),
680 .static => zig_args.appendAssumeCapacity("-static"),
681 .default => {},
682 }
683
684 if (conf_comp.flags3.kind == .lib and conf_comp.flags2.linkage == .dynamic) {
685 if (conf_comp.version.value) |version| zig_args.addManyAsArrayAssumeCapacity(2).* = .{
686 "--version", version.slice(conf),
687 };
688
689 const os_tag = root_module_target.flags.os_tag.unwrap().?;
690 if (os_tag.isDarwin()) {
691 const abi = root_module_target.flags.abi.unwrap().?;
692 zig_args.addManyAsArrayAssumeCapacity(2).* = .{
693 "-install_name",
694 if (conf_comp.install_name.value) |s| s.slice(conf) else try arena.print("@rpath/{s}{s}{s}", .{
695 os_tag.libPrefix(abi), conf_comp.root_name.slice(conf), os_tag.dynamicLibSuffix(),
696 }),
697 };
698 }
699 }
700 }
701
702 if (conf_comp.entitlements.value) |entitlements| {
703 (try zig_args.addManyAsArray(gpa, 2)).* = .{
704 "--entitlements", try maker.resolveLazyPathIndexAbs(arena, entitlements, compile_index),
705 };
706 }
707 if (conf_comp.pagezero_size.value) |pagezero_size| {
708 (try zig_args.addManyAsArray(gpa, 2)).* = .{
709 "-pagezero_size", try arena.print("{x}", .{pagezero_size}),
710 };
711 }
712 if (conf_comp.headerpad_size.value) |headerpad_size| {
713 (try zig_args.addManyAsArray(gpa, 2)).* = .{
714 "-headerpad", try arena.print("{x}", .{headerpad_size}),
715 };
716 }
717 try addBool(gpa, zig_args, "-headerpad_max_install_names", conf_comp.flags.headerpad_max_install_names);
718 try addBool(gpa, zig_args, "-dead_strip_dylibs", conf_comp.flags.dead_strip_dylibs);
719 try addBool(gpa, zig_args, "-ObjC", conf_comp.flags.force_load_objc);
720 try addBool(gpa, zig_args, "--discard-all", conf_comp.flags.discard_local_symbols);
721
722 try addFlag(gpa, zig_args, "compiler-rt", conf_comp.flags2.bundle_compiler_rt.toBool());
723 try addFlag(gpa, zig_args, "ubsan-rt", conf_comp.flags2.bundle_ubsan_rt.toBool());
724 try addFlag(gpa, zig_args, "dll-export-fns", conf_comp.flags2.dll_export_fns.toBool());
725
726 try addBool(gpa, zig_args, "-rdynamic", conf_comp.flags.rdynamic);
727 try addBool(gpa, zig_args, "--import-memory", conf_comp.flags.import_memory);
728 try addBool(gpa, zig_args, "--export-memory", conf_comp.flags.export_memory);
729 try addBool(gpa, zig_args, "--import-symbols", conf_comp.flags.import_symbols);
730 try addBool(gpa, zig_args, "--import-table", conf_comp.flags.import_table);
731 try addBool(gpa, zig_args, "--export-table", conf_comp.flags.export_table);
732 try addBool(gpa, zig_args, "--growable-table", conf_comp.flags.growable_table);
733 try addBool(gpa, zig_args, "--shared-memory", conf_comp.flags.shared_memory);
734
735 {
736 try zig_args.ensureUnusedCapacity(gpa, 4);
737 if (conf_comp.initial_memory.value) |initial_memory| {
738 zig_args.appendAssumeCapacity(try arena.print("--initial-memory={d}", .{initial_memory}));
739 }
740 if (conf_comp.max_memory.value) |max_memory| {
741 zig_args.appendAssumeCapacity(try arena.print("--max-memory={d}", .{max_memory}));
742 }
743 if (conf_comp.global_base.value) |global_base| {
744 zig_args.appendAssumeCapacity(try arena.print("--global-base={d}", .{global_base}));
745 }
746 switch (conf_comp.flags3.wasi_exec_model) {
747 .default => {},
748 .command => zig_args.appendAssumeCapacity("-mexec-model=command"),
749 .reactor => zig_args.appendAssumeCapacity("-mexec-model=reactor"),
750 }
751 }
752
753 if (conf_comp.linker_script.value) |linker_script| (try zig_args.addManyAsArray(gpa, 2)).* = .{
754 "--script", try maker.resolveLazyPathIndexAbs(arena, linker_script, compile_index),
755 };
756 if (conf_comp.version_script.value) |version_script| (try zig_args.addManyAsArray(gpa, 2)).* = .{
757 "--version-script", try maker.resolveLazyPathIndexAbs(arena, version_script, compile_index),
758 };
759 if (conf_comp.flags2.linker_allow_undefined_version.toBool()) |x| {
760 try zig_args.append(gpa, if (x) "--undefined-version" else "--no-undefined-version");
761 }
762
763 if (conf_comp.flags2.linker_enable_new_dtags.toBool()) |enabled| {
764 try zig_args.append(gpa, if (enabled) "--enable-new-dtags" else "--disable-new-dtags");
765 }
766
767 if (graph.sysroot) |sysroot| try zig_args.appendSlice(gpa, &.{ "--sysroot", sysroot });
768
769 // -I and -L arguments that appear after the last --mod argument apply to all modules.
770 const cwd: Io.Dir = .cwd();
771 const io = graph.io;
772
773 for (graph.search_prefixes.items) |search_prefix| {
774 var prefix_dir = cwd.openDir(io, search_prefix, .{}) catch |err| {
775 return step.fail(maker, "unable to open prefix directory {q}: {t}", .{ search_prefix, err });
776 };
777 defer prefix_dir.close(io);
778
779 // Avoid passing -L and -I flags for nonexistent directories.
780 // This prevents a warning, that should probably be upgraded to an error in Zig's
781 // CLI parsing code, when the linker sees an -L directory that does not exist.
782
783 if (prefix_dir.access(io, "lib", .{})) |_| {
784 try zig_args.appendSlice(gpa, &.{
785 "-L", try Dir.path.join(arena, &.{ search_prefix, "lib" }),
786 });
787 } else |err| switch (err) {
788 error.FileNotFound => {},
789 else => |e| return step.fail(maker, "unable to access {s}/lib directory: {t}", .{ search_prefix, e }),
790 }
791
792 if (prefix_dir.access(io, "include", .{})) |_| {
793 try zig_args.appendSlice(gpa, &.{
794 "-I", try Dir.path.join(arena, &.{ search_prefix, "include" }),
795 });
796 } else |err| switch (err) {
797 error.FileNotFound => {},
798 else => |e| return step.fail(maker, "unable to access {s}/include directory: {t}", .{ search_prefix, e }),
799 }
800 }
801
802 if (conf_comp.flags3.rc_includes != .any) (try zig_args.addManyAsArray(gpa, 2)).* = .{
803 "-rcincludes", @tagName(conf_comp.flags3.rc_includes),
804 };
805
806 try addFlag(gpa, zig_args, "each-lib-rpath", conf_comp.flags2.each_lib_rpath.toBool());
807
808 if (conf_comp.flags3.build_id.unwrap(conf_comp.build_id.value, conf) orelse graph.build_id) |build_id| {
809 try zig_args.append(gpa, switch (build_id) {
810 .hexstring => |hs| try arena.print("--build-id=0x{x}", .{hs.toSlice()}),
811 .none, .fast, .uuid, .sha1, .md5 => try arena.print("--build-id={t}", .{build_id}),
812 });
813 }
814
815 const opt_zig_lib_dir: ?[]const u8 = if (conf_comp.zig_lib_dir.value) |dir|
816 try maker.resolveLazyPathIndexAbs(arena, dir, compile_index)
817 else if (graph.zig_lib_directory.path) |_|
818 try arena.print("{f}", .{graph.zig_lib_directory})
819 else
820 null;
821
822 if (opt_zig_lib_dir) |zig_lib_dir| (try zig_args.addManyAsArray(gpa, 2)).* = .{
823 "--zig-lib-dir", zig_lib_dir,
824 };
825
826 try addFlag(gpa, zig_args, "PIE", conf_comp.flags2.pie.toBool());
827
828 try zig_args.ensureUnusedCapacity(gpa, 1);
829 switch (conf_comp.flags3.lto) {
830 .full => zig_args.appendAssumeCapacity("-flto=full"),
831 .thin => zig_args.appendAssumeCapacity("-flto=thin"),
832 .none => zig_args.appendAssumeCapacity("-fno-lto"),
833 .default => {},
834 }
835
836 try addFlag(gpa, zig_args, "sanitize-coverage-trace-pc-guard", conf_comp.flags2.sanitize_coverage_trace_pc_guard.toBool());
837
838 switch (conf_comp.flags3.subsystem) {
839 .default => {},
840 else => |t| (try zig_args.addManyAsArray(gpa, 2)).* = .{ "--subsystem", @tagName(t) },
841 }
842
843 try addBool(gpa, zig_args, "-municode", conf_comp.flags.mingw_unicode_entry_point);
844
845 if (conf_comp.error_limit.value orelse graph.error_limit) |err_limit| (try zig_args.addManyAsArray(gpa, 2)).* = .{
846 "--error-limit", try arena.print("{d}", .{err_limit}),
847 };
848
849 try addFlag(gpa, zig_args, "incremental", conf_comp.flags4.incremental.toBool() orelse graph.incremental);
850
851 try zig_args.append(gpa, "--listen=-");
852
853 // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux
854 // 2,097,152. If our args exceed 30 KiB, we instead write them to a "response file" and
855 // pass that to zig, e.g. via 'zig build-lib @args.rsp'
856 // See @file syntax here: https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html
857 var args_length: usize = 0;
858 for (zig_args.items) |arg| {
859 args_length += arg.len + 1; // +1 to account for null terminator
860 }
861 if (args_length >= 30 * 1024) {
862 const local_cache_root = graph.local_cache_root;
863 const args_path: Path = .{ .root_dir = local_cache_root, .sub_path = "args" };
864 args_path.root_dir.handle.createDirPath(io, args_path.sub_path) catch |err|
865 return step.fail(maker, "failed creating directory {f}: {t}", .{ args_path, err });
866
867 const args_to_escape = zig_args.items[2..];
868 var escaped_args = try std.array_list.Managed([]const u8).initCapacity(arena, args_to_escape.len);
869 arg_blk: for (args_to_escape) |arg| {
870 for (arg, 0..) |c, arg_idx| {
871 if (c == '\\' or c == '"') {
872 // Slow path for arguments that need to be escaped. We'll need to allocate and copy
873 var escaped: std.ArrayList(u8) = .empty;
874 try escaped.ensureTotalCapacityPrecise(arena, arg.len + 1);
875 try escaped.appendSlice(arena, arg[0..arg_idx]);
876 for (arg[arg_idx..]) |to_escape| {
877 if (to_escape == '\\' or to_escape == '"') try escaped.append(arena, '\\');
878 try escaped.append(arena, to_escape);
879 }
880 escaped_args.appendAssumeCapacity(escaped.items);
881 continue :arg_blk;
882 }
883 }
884 escaped_args.appendAssumeCapacity(arg); // no escaping needed so just use original argument
885 }
886
887 // Write the args to zig-cache/args/<SHA256 hash of args> to avoid conflicts with
888 // other zig build commands running in parallel.
889 const partially_quoted = try mem.join(arena, "\" \"", escaped_args.items);
890 const args = try mem.concat(arena, u8, &[_][]const u8{ "\"", partially_quoted, "\"" });
891
892 var args_hash: [Sha256.digest_length]u8 = undefined;
893 Sha256.hash(args, &args_hash, .{});
894 var args_hex_hash: [Sha256.digest_length * 2]u8 = undefined;
895 _ = std.mem.print(&args_hex_hash, "{x}", .{&args_hash}) catch unreachable;
896
897 const args_file = "args" ++ Dir.path.sep_str ++ args_hex_hash;
898 local_cache_root.handle.access(io, args_file, .{}) catch {
899 var af = local_cache_root.handle.createFileAtomic(io, args_file, .{
900 .replace = false,
901 .make_path = true,
902 }) catch |e| return step.fail(maker, "failed creating tmp args file {f}{s}: {t}", .{
903 local_cache_root, args_file, e,
904 });
905 defer af.deinit(io);
906
907 af.file.writeStreamingAll(io, args) catch |e| {
908 return step.fail(maker, "failed writing args data to tmp file {f}{s}: {t}", .{
909 local_cache_root, args_file, e,
910 });
911 };
912 // Note we can't clean up this file, not even after build
913 // success, because that might interfere with another build
914 // process that needs the same file.
915 af.link(io) catch |e| switch (e) {
916 error.PathAlreadyExists => {
917 // The args file was created by another concurrent build process.
918 },
919 else => |other_err| return step.fail(maker, "failed linking tmp file {f}{s}: {t}", .{
920 local_cache_root, args_file, other_err,
921 }),
922 };
923 };
924
925 const resolved_args_file = try mem.concat(arena, u8, &.{
926 "@", try local_cache_root.join(arena, &.{args_file}),
927 });
928
929 zig_args.shrinkRetainingCapacity(2);
930 try zig_args.append(gpa, resolved_args_file);
931 }
932}
933
934pub fn rebuildInFuzzMode(
935 compile: *Compile,
936 maker: *Maker,
937 compile_index: Configuration.Step.Index,
938 progress_node: std.Progress.Node,
939) !Path {
940 const gpa = maker.gpa;
941 const step = maker.stepByIndex(compile_index);
942
943 var arena_allocator: std.heap.ArenaAllocator = .init(gpa);
944 defer arena_allocator.deinit();
945 const arena = arena_allocator.allocator();
946
947 step.result_error_msgs.clearRetainingCapacity();
948 step.clearResultStderr(gpa);
949 step.clearErrorBundle(gpa);
950 step.result_error_bundle.deinit(gpa);
951 step.result_error_bundle = std.zig.ErrorBundle.empty;
952
953 step.clearFailedCommand(gpa);
954
955 var argv: std.ArrayList([]const u8) = .empty;
956 defer argv.deinit(gpa);
957
958 try lowerZigArgs(arena, compile, compile_index, maker, progress_node, &argv, true);
959 const maybe_output_bin_path = try Step.evalZigProcess(compile_index, maker, argv.items, progress_node, false);
960 return maybe_output_bin_path.?;
961}
962
963fn addBool(gpa: Allocator, args: *std.ArrayList([]const u8), arg: []const u8, opt: bool) !void {
964 if (opt) try args.append(gpa, arg);
965}
966
967fn addFlag(gpa: Allocator, args: *std.ArrayList([]const u8), comptime name: []const u8, opt: ?bool) !void {
968 const cond = opt orelse return;
969 try args.append(gpa, if (cond) "-f" ++ name else "-fno-" ++ name);
970}
971
972fn addArchFlag(gpa: Allocator, args: *std.ArrayList([]const u8), comptime name: []const u8, opt: ?bool) !void {
973 const cond = opt orelse return;
974 try args.append(gpa, if (cond) "-m" ++ name else "-mno-" ++ name);
975}
976
977fn checkCompileErrors(arena: Allocator, maker: *Maker, step_index: Configuration.Step.Index) Step.ExtendedMakeError!void {
978 const step = maker.stepByIndex(step_index);
979 const conf = &maker.scanned_config.configuration;
980 const conf_step = step_index.ptr(conf);
981 const conf_comp = conf_step.extended.get(conf.extra).compile;
982
983 // Clear this field so that it does not get printed by the build runner.
984 var actual_eb = step.result_error_bundle;
985 step.result_error_bundle = .empty;
986 defer actual_eb.deinit(maker.gpa);
987
988 const actual_errors = ae: {
989 var aw: std.Io.Writer.Allocating = .init(arena);
990 defer aw.deinit();
991 actual_eb.renderToWriter(.{
992 .include_reference_trace = false,
993 .include_source_line = false,
994 }, &aw.writer) catch |err| switch (err) {
995 error.WriteFailed => return error.OutOfMemory,
996 };
997 break :ae try aw.toOwnedSlice();
998 };
999
1000 // Render the expected lines into a string that we can compare verbatim.
1001 var expected_generated: std.ArrayList(u8) = .empty;
1002 var actual_line_it = mem.splitScalar(u8, actual_errors, '\n');
1003
1004 switch (conf_comp.expect_errors.u) {
1005 .none => unreachable,
1006 .starts_with => |expect_starts_with_string| {
1007 const expect_starts_with = expect_starts_with_string.slice(conf);
1008 if (mem.startsWith(u8, actual_errors, expect_starts_with)) return;
1009 return step.fail(maker,
1010 \\
1011 \\========= should start with: ============
1012 \\{s}
1013 \\========= but not found: ================
1014 \\{s}
1015 \\=========================================
1016 , .{ expect_starts_with, actual_errors });
1017 },
1018 .contains => |expect_line_string| {
1019 const expect_line = expect_line_string.slice(conf);
1020 while (actual_line_it.next()) |actual_line| {
1021 if (!matchCompileError(actual_line, expect_line)) continue;
1022 return;
1023 }
1024
1025 return step.fail(maker,
1026 \\
1027 \\========= should contain: ===============
1028 \\{s}
1029 \\========= but not found: ================
1030 \\{s}
1031 \\=========================================
1032 , .{ expect_line, actual_errors });
1033 },
1034 .stderr_contains => |expect_line_string| {
1035 const expect_line = expect_line_string.slice(conf);
1036 const actual_stderr: []const u8 = if (step.result_error_msgs.items.len > 0)
1037 step.result_error_msgs.items[0]
1038 else
1039 &.{};
1040 step.result_error_msgs.clearRetainingCapacity();
1041
1042 var stderr_line_it = mem.splitScalar(u8, actual_stderr, '\n');
1043
1044 while (stderr_line_it.next()) |actual_line| {
1045 if (!matchCompileError(actual_line, expect_line)) continue;
1046 return;
1047 }
1048
1049 return step.fail(maker,
1050 \\
1051 \\========= should contain: ===============
1052 \\{s}
1053 \\========= but not found: ================
1054 \\{s}
1055 \\=========================================
1056 , .{ expect_line, actual_stderr });
1057 },
1058 .exact => |expect_lines| {
1059 for (expect_lines.slice) |expect_line_string| {
1060 const expect_line = expect_line_string.slice(conf);
1061 const actual_line = actual_line_it.next() orelse {
1062 try expected_generated.appendSlice(arena, expect_line);
1063 try expected_generated.append(arena, '\n');
1064 continue;
1065 };
1066 if (matchCompileError(actual_line, expect_line)) {
1067 try expected_generated.appendSlice(arena, actual_line);
1068 try expected_generated.append(arena, '\n');
1069 continue;
1070 }
1071 try expected_generated.appendSlice(arena, expect_line);
1072 try expected_generated.append(arena, '\n');
1073 }
1074
1075 if (mem.eql(u8, expected_generated.items, actual_errors)) return;
1076
1077 return step.fail(maker,
1078 \\
1079 \\========= expected: =====================
1080 \\{s}
1081 \\========= but found: ====================
1082 \\{s}
1083 \\=========================================
1084 , .{ expected_generated.items, actual_errors });
1085 },
1086 }
1087}
1088
1089fn matchCompileError(actual: []const u8, expected: []const u8) bool {
1090 if (mem.endsWith(u8, actual, expected)) return true;
1091 if (mem.startsWith(u8, expected, ":?:?: ")) {
1092 if (mem.endsWith(u8, actual, expected[":?:?: ".len..])) return true;
1093 }
1094 // We scan for /?/ in expected line and if there is a match, we match everything
1095 // up to and after /?/.
1096 const expected_trim = mem.trim(u8, expected, " ");
1097 if (mem.find(u8, expected_trim, "/?/")) |index| {
1098 const actual_trim = mem.trim(u8, actual, " ");
1099 const lhs = expected_trim[0..index];
1100 const rhs = expected_trim[index + "/?/".len ..];
1101 if (mem.startsWith(u8, actual_trim, lhs) and mem.endsWith(u8, actual_trim, rhs)) return true;
1102 }
1103 return false;
1104}
1105
1106fn moduleNeedsCliArg(mod: *const Configuration.Module, conf: *const Configuration) bool {
1107 return for (0..mod.link_objects.len) |i| switch (mod.link_objects.tag(conf.extra, i)) {
1108 .c_source_file, .c_source_files, .assembly_file, .win32_resource_file => break true,
1109 else => continue,
1110 } else false;
1111}
1112
1113const CliNamedModules = struct {
1114 modules: std.array_hash_map.Auto(Configuration.Module.Index, void),
1115 names: std.array_hash_map.String(void),
1116
1117 /// Traverse the whole dependency graph and give every module a unique
1118 /// name, ideally one named after what it's called somewhere in the graph.
1119 /// It will help here to have both a mapping from module to name and a set
1120 /// of all the currently-used names.
1121 fn init(
1122 arena: Allocator,
1123 module_graph: *ModuleGraph,
1124 compile_index: Configuration.Step.Index,
1125 maker: *const Maker,
1126 ) Allocator.Error!CliNamedModules {
1127 const conf = &maker.scanned_config.configuration;
1128 const conf_compile = compile_index.ptr(conf).extended.get(conf.extra).compile;
1129
1130 var result: CliNamedModules = .{
1131 .modules = .{},
1132 .names = .{},
1133 };
1134 const modules = try getModuleList(arena, module_graph, conf_compile.root_module, conf);
1135 {
1136 assert(conf_compile.root_module == modules.keys()[0]);
1137 try result.modules.put(arena, conf_compile.root_module, {});
1138 try result.names.put(arena, "root", {});
1139 }
1140 for (modules.keys()[1..], modules.values()[1..]) |mod, orig_name| {
1141 const orig_name_slice = orig_name.slice(conf);
1142 var name: []const u8 = orig_name_slice;
1143 var n: usize = 0;
1144 while (true) {
1145 const gop = try result.names.getOrPut(arena, name);
1146 if (!gop.found_existing) {
1147 try result.modules.putNoClobber(arena, mod, {});
1148 break;
1149 }
1150 name = try arena.print("{s}{d}", .{ orig_name_slice, n });
1151 n += 1;
1152 }
1153 }
1154 return result;
1155 }
1156};
1157
1158pub fn getCompileDependencies(
1159 arena: Allocator,
1160 module_graph: *ModuleGraph,
1161 conf: *const Configuration,
1162 start: Configuration.Step.Index,
1163 chase_dynamic: bool,
1164) ![]const Configuration.Step.Index {
1165 var compiles: std.array_hash_map.Auto(Configuration.Step.Index, void) = .empty;
1166 var compiles_i: usize = 0;
1167
1168 try compiles.putNoClobber(arena, start, {});
1169
1170 while (compiles_i < compiles.count()) : (compiles_i += 1) {
1171 const step = compiles.keys()[compiles_i].ptr(conf);
1172 const compile = step.extended.get(conf.extra).compile;
1173 const modules = try getModuleList(arena, module_graph, compile.root_module, conf);
1174
1175 for (modules.keys()) |mod_index| {
1176 const mod = mod_index.get(conf);
1177 for (0..mod.link_objects.len) |i| {
1178 switch (mod.link_objects.get(conf.extra, i)) {
1179 .other_step => |other_compile_index| {
1180 const other_compile = other_compile_index.ptr(conf).extended.get(conf.extra).compile;
1181 if (!chase_dynamic and other_compile.isDynamicLibrary()) continue;
1182 try compiles.put(arena, other_compile_index, {});
1183 },
1184 else => {},
1185 }
1186 }
1187 }
1188 }
1189
1190 return compiles.keys();
1191}
1192
1193/// Returned pointer expires upon next call to `getModuleList`.
1194fn getModuleList(
1195 arena: Allocator,
1196 module_graph: *ModuleGraph,
1197 root_module: Configuration.Module.Index,
1198 conf: *const Configuration,
1199) !*ModuleList {
1200 const gop = try module_graph.getOrPutAdapted(arena, root_module, @as(ModuleListContext.Adapter, .{}));
1201 const modules = gop.key_ptr;
1202
1203 if (gop.found_existing) return modules;
1204 modules.* = .empty;
1205 try modules.putNoClobber(arena, root_module, .root);
1206
1207 var i: usize = 0;
1208
1209 while (i < modules.entries.len) : (i += 1) {
1210 const dep_index = modules.keys()[i];
1211 const dep = dep_index.get(conf);
1212 const imports = dep.import_table.get(conf).imports;
1213 try modules.ensureUnusedCapacity(arena, imports.mal.len);
1214 for (imports.mal.items(.name), imports.mal.items(.module)) |import_name, other_mod|
1215 modules.putAssumeCapacity(other_mod, import_name);
1216 }
1217
1218 return modules;
1219}
1220
1221fn appendModuleFlags(
1222 arena: Allocator,
1223 module_index: Configuration.Module.Index,
1224 zig_args: *std.ArrayList([]const u8),
1225 asking_step: Configuration.Step.Index,
1226 maker: *const Maker,
1227) !void {
1228 const gpa = maker.gpa;
1229 const conf = &maker.scanned_config.configuration;
1230 const m = module_index.get(conf);
1231
1232 try addFlag(gpa, zig_args, "strip", m.flags.strip.toBool());
1233 try addFlag(gpa, zig_args, "single-threaded", m.flags.single_threaded.toBool());
1234 try addFlag(gpa, zig_args, "stack-check", m.flags.stack_check.toBool());
1235 try addFlag(gpa, zig_args, "stack-protector", m.flags.stack_protector.toBool());
1236 try addFlag(gpa, zig_args, "omit-frame-pointer", m.flags2.omit_frame_pointer.toBool());
1237 try addFlag(gpa, zig_args, "error-tracing", m.flags2.error_tracing.toBool());
1238 try addFlag(gpa, zig_args, "sanitize-thread", m.flags.sanitize_thread.toBool());
1239 try addFlag(gpa, zig_args, "fuzz", m.flags.fuzz.toBool());
1240 try addFlag(gpa, zig_args, "valgrind", m.flags2.valgrind.toBool());
1241 try addFlag(gpa, zig_args, "PIC", m.flags2.pic.toBool());
1242 try addFlag(gpa, zig_args, "no-builtin", m.flags2.no_builtin.toBool());
1243
1244 try addArchFlag(gpa, zig_args, "red-zone", m.flags2.red_zone.toBool());
1245 {
1246 try zig_args.ensureUnusedCapacity(gpa, 6);
1247
1248 switch (m.flags.sanitize_c) {
1249 .off => zig_args.appendAssumeCapacity("-fno-sanitize-c"),
1250 .trap => zig_args.appendAssumeCapacity("-fsanitize-c=trap"),
1251 .full => zig_args.appendAssumeCapacity("-fsanitize-c=full"),
1252 .default => {},
1253 }
1254
1255 switch (m.flags.dwarf_format) {
1256 .@"32" => zig_args.appendAssumeCapacity("-gdwarf32"),
1257 .@"64" => zig_args.appendAssumeCapacity("-gdwarf64"),
1258 .default => {},
1259 }
1260
1261 switch (m.flags.unwind_tables) {
1262 .none => zig_args.appendAssumeCapacity("-fno-unwind-tables"),
1263 .sync => zig_args.appendAssumeCapacity("-funwind-tables"),
1264 .async => zig_args.appendAssumeCapacity("-fasync-unwind-tables"),
1265 .default => {},
1266 }
1267
1268 switch (m.flags.optimize) {
1269 .debug => zig_args.appendAssumeCapacity("-Odebug"),
1270 .safe => zig_args.appendAssumeCapacity("-Osafe"),
1271 .fast => zig_args.appendAssumeCapacity("-Ofast"),
1272 .small => zig_args.appendAssumeCapacity("-Osmall"),
1273 .default => {},
1274 }
1275
1276 if (m.flags.code_model != .default) {
1277 zig_args.appendAssumeCapacity("-mcmodel");
1278 zig_args.appendAssumeCapacity(@tagName(m.flags.code_model));
1279 }
1280 }
1281
1282 if (m.resolved_target.get(conf)) |resolved_target| {
1283 // Communicate the query via CLI since it's more compact.
1284 if (resolved_target.unwrapQuery(conf)) |query| {
1285 try zig_args.ensureUnusedCapacity(gpa, 6);
1286
1287 zig_args.appendAssumeCapacity("-target");
1288 zig_args.appendAssumeCapacity(try query.zigTriple(arena));
1289
1290 zig_args.appendAssumeCapacity("-mcpu");
1291 zig_args.appendAssumeCapacity(try query.serializeCpuAlloc(arena));
1292
1293 if (query.dynamic_linker) |*dynamic_linker| {
1294 if (dynamic_linker.get()) |dynamic_linker_path| {
1295 zig_args.appendAssumeCapacity("--dynamic-linker");
1296 zig_args.appendAssumeCapacity(dynamic_linker_path);
1297 } else {
1298 zig_args.appendAssumeCapacity("--no-dynamic-linker");
1299 }
1300 }
1301 }
1302 }
1303
1304 for (m.export_symbol_names.slice) |symbol_name| {
1305 try zig_args.append(gpa, try arena.print("--export={s}", .{symbol_name.slice(conf)}));
1306 }
1307
1308 try zig_args.ensureUnusedCapacity(gpa, 2 * m.include_dirs.len);
1309 for (0..m.include_dirs.len) |i|
1310 try appendIncludeDirFlags(arena, m.include_dirs.get(conf.extra, i), zig_args, asking_step, maker);
1311
1312 try zig_args.ensureUnusedCapacity(gpa, m.c_macros.slice.len);
1313 for (m.c_macros.slice) |c_macro|
1314 zig_args.appendAssumeCapacity(c_macro.slice(conf));
1315
1316 try zig_args.ensureUnusedCapacity(gpa, 2 * m.lib_paths.slice.len);
1317 for (m.lib_paths.slice) |lib_path| {
1318 zig_args.appendAssumeCapacity("-L");
1319 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lib_path, asking_step));
1320 }
1321
1322 try zig_args.ensureUnusedCapacity(gpa, 2 * m.rpaths.len);
1323 for (0..m.rpaths.len) |i| switch (m.rpaths.get(conf.extra, i)) {
1324 .lazy_path => |lp| {
1325 zig_args.appendAssumeCapacity("-rpath");
1326 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step));
1327 },
1328 .special => |string| {
1329 zig_args.appendAssumeCapacity("-rpath");
1330 zig_args.appendAssumeCapacity(string.slice(conf));
1331 },
1332 };
1333}
1334
1335/// Assumes unused capacity for at least 2 items.
1336pub fn appendIncludeDirFlags(
1337 arena: Allocator,
1338 include_dir: Configuration.Module.IncludeDir,
1339 zig_args: *std.ArrayList([]const u8),
1340 asking_step: Configuration.Step.Index,
1341 maker: *const Maker,
1342) !void {
1343 const conf = &maker.scanned_config.configuration;
1344
1345 switch (include_dir) {
1346 .path => |lp| {
1347 zig_args.appendAssumeCapacity("-I");
1348 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step));
1349 },
1350 .path_system => |lp| {
1351 zig_args.appendAssumeCapacity("-isystem");
1352 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step));
1353 },
1354 .path_after => |lp| {
1355 zig_args.appendAssumeCapacity("-idirafter");
1356 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step));
1357 },
1358 .framework_path => |lp| {
1359 zig_args.appendAssumeCapacity("-F");
1360 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step));
1361 },
1362 .framework_path_system => |lp| {
1363 zig_args.appendAssumeCapacity("-iframework");
1364 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step));
1365 },
1366 .config_header_step => |ch_index| {
1367 const conf_ch = ch_index.ptr(conf).extended.get(conf.extra).config_header;
1368 const path = maker.generatedPath(conf_ch.generated_dir).*;
1369 zig_args.appendAssumeCapacity("-I");
1370 zig_args.appendAssumeCapacity(try path.toString(arena));
1371 },
1372 .embed_path => |lazy_path| {
1373 zig_args.appendAssumeCapacity(try arena.print("--embed-dir={f}", .{
1374 try maker.resolveLazyPathIndex(arena, lazy_path, asking_step),
1375 }));
1376 },
1377 }
1378}