1const Module = @This();
2
3const std = @import("std");
4const assert = std.debug.assert;
5const LazyPath = std.Build.LazyPath;
6const Step = std.Build.Step;
7const ArrayList = std.ArrayList;
8
9/// The one responsible for creating this module.
10owner: *std.Build,
11root_source_file: ?LazyPath,
12/// The modules that are mapped into this module's import table.
13/// Use `addImport` rather than modifying this field directly in order to
14/// maintain step dependency edges.
15import_table: std.array_hash_map.String(*Module),
16
17resolved_target: ?std.Build.ResolvedTarget = null,
18optimize: ?std.builtin.Optimize = null,
19dwarf_format: ?std.dwarf.Format,
20
21c_macros: ArrayList([]const u8),
22include_dirs: ArrayList(IncludeDir),
23lib_paths: ArrayList(LazyPath),
24rpaths: ArrayList(RPath),
25frameworks: std.array_hash_map.String(LinkFrameworkOptions),
26link_objects: ArrayList(LinkObject),
27
28strip: ?bool,
29unwind_tables: ?std.builtin.UnwindTables,
30single_threaded: ?bool,
31stack_protector: ?bool,
32stack_check: ?bool,
33sanitize_c: ?std.zig.SanitizeC,
34sanitize_thread: ?bool,
35fuzz: ?bool,
36code_model: std.builtin.CodeModel,
37valgrind: ?bool,
38pic: ?bool,
39red_zone: ?bool,
40omit_frame_pointer: ?bool,
41error_tracing: ?bool,
42link_libc: ?bool,
43link_libcpp: ?bool,
44no_builtin: ?bool,
45
46/// Symbols to be exported when compiling to WebAssembly.
47export_symbol_names: []const []const u8 = &.{},
48
49/// Caches the result of `getGraph` when called multiple times.
50/// Use `getGraph` instead of accessing this field directly.
51cached_graph: Graph = .{ .modules = &.{}, .names = &.{} },
52
53pub const RPath = union(enum) {
54 lazy_path: LazyPath,
55 special: []const u8,
56};
57
58pub const LinkObject = union(enum) {
59 static_path: LazyPath,
60 other_step: *Step.Compile,
61 system_lib: SystemLib,
62 assembly_file: LazyPath,
63 c_source_file: *CSourceFile,
64 c_source_files: *CSourceFiles,
65 /// Deprecated. This functionality will be moved to an external package:
66 /// https://codeberg.org/ziglang/rc
67 win32_resource_file: *RcSourceFile,
68};
69
70pub const SystemLib = struct {
71 name: []const u8,
72 needed: bool,
73 weak: bool,
74 use_pkg_config: UsePkgConfig,
75 preferred_link_mode: std.builtin.LinkMode,
76 search_strategy: SystemLib.SearchStrategy,
77
78 pub const UsePkgConfig = std.Build.Configuration.SystemLib.UsePkgConfig;
79 pub const SearchStrategy = std.Build.Configuration.SystemLib.SearchStrategy;
80};
81
82pub const CSourceLanguage = enum {
83 c,
84 cpp,
85
86 objective_c,
87 objective_cpp,
88
89 /// Standard assembly
90 assembly,
91 /// Assembly with the C preprocessor
92 assembly_with_preprocessor,
93
94 /// The value passed to "-x" CLI flag of Clang.
95 pub fn clangIdentifier(self: CSourceLanguage) [:0]const u8 {
96 return switch (self) {
97 .c => "c",
98 .cpp => "c++",
99 .objective_c => "objective-c",
100 .objective_cpp => "objective-c++",
101 .assembly => "assembler",
102 .assembly_with_preprocessor => "assembler-with-cpp",
103 };
104 }
105};
106
107pub const CSourceFiles = struct {
108 root: LazyPath,
109 /// `files` is relative to `root`, which is
110 /// the build root by default
111 files: []const []const u8,
112 flags: []const []const u8,
113 /// By default, determines language of each file individually based on its file extension
114 language: ?CSourceLanguage,
115};
116
117pub const CSourceFile = struct {
118 file: LazyPath,
119 flags: []const []const u8 = &.{},
120 /// By default, determines language of each file individually based on its file extension
121 language: ?CSourceLanguage = null,
122
123 pub fn dupe(file: CSourceFile, graph: *const std.Build.Graph) CSourceFile {
124 return .{
125 .file = file.file.dupe(graph),
126 .flags = graph.dupeStrings(file.flags),
127 .language = file.language,
128 };
129 }
130};
131
132/// Deprecated. This functionality will be moved to an external package:
133/// https://codeberg.org/ziglang/rc
134pub const RcSourceFile = struct {
135 file: LazyPath,
136 /// Any option that rc.exe accepts will work here, with the exception of:
137 /// - `/fo`: The output filename is set by the build system
138 /// - `/p`: Only running the preprocessor is not supported in this context
139 /// - `/:no-preprocess` (non-standard option): Not supported in this context
140 /// - Any MUI-related option
141 /// https://learn.microsoft.com/en-us/windows/win32/menurc/using-rc-the-rc-command-line-
142 ///
143 /// Implicitly defined options:
144 /// /x (ignore the INCLUDE environment variable)
145 /// /D_DEBUG or /DNDEBUG depending on the optimization mode
146 flags: []const []const u8 = &.{},
147 /// Include paths that may or may not exist yet and therefore need to be
148 /// specified as a LazyPath. Each path will be appended to the flags
149 /// as `/I <resolved path>`.
150 include_paths: []const LazyPath = &.{},
151
152 pub fn dupe(file: RcSourceFile, graph: *const std.Build.Graph) RcSourceFile {
153 return .{
154 .file = file.file.dupe(graph),
155 .flags = graph.dupeStrings(file.flags),
156 .include_paths = LazyPath.dupeList(file.include_paths, graph),
157 };
158 }
159};
160
161pub const IncludeDir = union(enum) {
162 path: LazyPath,
163 path_system: LazyPath,
164 path_after: LazyPath,
165 framework_path: LazyPath,
166 framework_path_system: LazyPath,
167 other_step: *Step.Compile,
168 config_header_step: *Step.ConfigHeader,
169 embed_path: LazyPath,
170};
171
172pub const LinkFrameworkOptions = struct {
173 /// Causes dynamic libraries to be linked regardless of whether they are
174 /// actually depended on. When false, dynamic libraries with no referenced
175 /// symbols will be omitted by the linker.
176 needed: bool = false,
177 /// Marks all referenced symbols from this library as weak, meaning that if
178 /// a same-named symbol is provided by another compilation unit, instead of
179 /// emitting a "duplicate symbol" error, the linker will resolve all
180 /// references to the symbol with the strong version.
181 ///
182 /// When the linker encounters two weak symbols, the chosen one is
183 /// determined by the order compilation units are provided to the linker,
184 /// priority given to later ones.
185 weak: bool = false,
186};
187
188/// Unspecified options here will be inherited from parent `Module` when
189/// inserted into an import table.
190pub const CreateOptions = struct {
191 /// This could either be a generated file, in which case the module
192 /// contains exactly one file, or it could be a path to the root source
193 /// file of directory of files which constitute the module.
194 /// If `null`, it means this module is made up of only `link_objects`.
195 root_source_file: ?LazyPath = null,
196
197 /// The table of other modules that this module can access via `@import`.
198 /// Imports are allowed to be cyclical, so this table can be added to after
199 /// the `Module` is created via `addImport`.
200 imports: []const Import = &.{},
201
202 target: ?std.Build.ResolvedTarget = null,
203 optimize: ?std.builtin.Optimize = null,
204
205 /// `true` requires a compilation that includes this Module to link libc.
206 /// `false` causes a build failure if a compilation that includes this Module would link libc.
207 /// `null` neither requires nor prevents libc from being linked.
208 link_libc: ?bool = null,
209 /// `true` requires a compilation that includes this Module to link libc++.
210 /// `false` causes a build failure if a compilation that includes this Module would link libc++.
211 /// `null` neither requires nor prevents libc++ from being linked.
212 link_libcpp: ?bool = null,
213 single_threaded: ?bool = null,
214 strip: ?bool = null,
215 unwind_tables: ?std.builtin.UnwindTables = null,
216 dwarf_format: ?std.dwarf.Format = null,
217 code_model: std.builtin.CodeModel = .default,
218 stack_protector: ?bool = null,
219 stack_check: ?bool = null,
220 sanitize_c: ?std.zig.SanitizeC = null,
221 sanitize_thread: ?bool = null,
222 fuzz: ?bool = null,
223 /// Whether to emit machine code that integrates with Valgrind.
224 valgrind: ?bool = null,
225 /// Position Independent Code
226 pic: ?bool = null,
227 red_zone: ?bool = null,
228 /// Whether to omit the stack frame pointer. Frees up a register and makes it
229 /// more difficult to obtain stack traces. Has target-dependent effects.
230 omit_frame_pointer: ?bool = null,
231 error_tracing: ?bool = null,
232 no_builtin: ?bool = null,
233};
234
235pub const Import = struct {
236 name: []const u8,
237 module: *Module,
238};
239
240pub fn init(
241 m: *Module,
242 owner: *std.Build,
243 value: union(enum) { options: CreateOptions, existing: *const Module },
244) void {
245 const graph = owner.graph;
246 const arena = graph.arena;
247
248 switch (value) {
249 .options => |options| {
250 m.* = .{
251 .owner = owner,
252 .root_source_file = if (options.root_source_file) |lp| lp.dupe(graph) else null,
253 .import_table = .empty,
254 .resolved_target = options.target,
255 .optimize = options.optimize,
256 .link_libc = options.link_libc,
257 .link_libcpp = options.link_libcpp,
258 .dwarf_format = options.dwarf_format,
259 .c_macros = .empty,
260 .include_dirs = .empty,
261 .lib_paths = .empty,
262 .rpaths = .empty,
263 .frameworks = .empty,
264 .link_objects = .empty,
265 .strip = options.strip,
266 .unwind_tables = options.unwind_tables,
267 .single_threaded = options.single_threaded,
268 .stack_protector = options.stack_protector,
269 .stack_check = options.stack_check,
270 .sanitize_c = options.sanitize_c,
271 .sanitize_thread = options.sanitize_thread,
272 .fuzz = options.fuzz,
273 .code_model = options.code_model,
274 .valgrind = options.valgrind,
275 .pic = options.pic,
276 .red_zone = options.red_zone,
277 .omit_frame_pointer = options.omit_frame_pointer,
278 .error_tracing = options.error_tracing,
279 .export_symbol_names = &.{},
280 .no_builtin = options.no_builtin,
281 };
282
283 m.import_table.ensureUnusedCapacity(arena, options.imports.len) catch @panic("OOM");
284 for (options.imports) |dep| {
285 m.import_table.putAssumeCapacity(dep.name, dep.module);
286 }
287 },
288 .existing => |existing| {
289 m.* = existing.*;
290 },
291 }
292}
293
294pub fn create(owner: *std.Build, options: CreateOptions) *Module {
295 const graph = owner.graph;
296 const arena = graph.arena;
297 const m = arena.create(Module) catch @panic("OOM");
298 m.init(owner, .{ .options = options });
299 return m;
300}
301
302/// Adds an existing module to be used with `@import`.
303pub fn addImport(m: *Module, name: []const u8, module: *Module) void {
304 const graph = m.owner.graph;
305 const arena = graph.arena;
306 m.import_table.put(arena, graph.dupeString(name), module) catch @panic("OOM");
307}
308
309/// Creates a new module and adds it to be used with `@import`.
310pub fn addAnonymousImport(m: *Module, name: []const u8, options: CreateOptions) void {
311 const module = create(m.owner, options);
312 return addImport(m, name, module);
313}
314
315/// Converts a set of key-value pairs into a Zig source file, and then inserts it into
316/// the Module's import table with the specified name. This makes the options importable
317/// via `@import("module_name")`.
318pub fn addOptions(m: *Module, module_name: []const u8, options: *Step.Options) void {
319 addImport(m, module_name, options.createModule());
320}
321
322pub const LinkSystemLibraryOptions = struct {
323 /// Causes dynamic libraries to be linked regardless of whether they are
324 /// actually depended on. When false, dynamic libraries with no referenced
325 /// symbols will be omitted by the linker.
326 needed: bool = false,
327 /// Marks all referenced symbols from this library as weak, meaning that if
328 /// a same-named symbol is provided by another compilation unit, instead of
329 /// emitting a "duplicate symbol" error, the linker will resolve all
330 /// references to the symbol with the strong version.
331 ///
332 /// When the linker encounters two weak symbols, the chosen one is
333 /// determined by the order compilation units are provided to the linker,
334 /// priority given to later ones.
335 weak: bool = false,
336 use_pkg_config: SystemLib.UsePkgConfig = .yes,
337 preferred_link_mode: std.builtin.LinkMode = .dynamic,
338 search_strategy: SystemLib.SearchStrategy = .paths_first,
339};
340
341pub fn linkSystemLibrary(
342 m: *Module,
343 name: []const u8,
344 options: LinkSystemLibraryOptions,
345) void {
346 const graph = m.owner.graph;
347 const arena = graph.arena;
348
349 const target = m.requireKnownTarget();
350 if (std.zig.target.isLibCLibName(target, name)) {
351 m.link_libc = true;
352 return;
353 }
354 if (std.zig.target.isLibCxxLibName(target, name)) {
355 m.link_libcpp = true;
356 return;
357 }
358
359 m.link_objects.append(arena, .{
360 .system_lib = .{
361 .name = graph.dupeString(name),
362 .needed = options.needed,
363 .weak = options.weak,
364 .use_pkg_config = options.use_pkg_config,
365 .preferred_link_mode = options.preferred_link_mode,
366 .search_strategy = options.search_strategy,
367 },
368 }) catch @panic("OOM");
369}
370
371pub fn linkFramework(m: *Module, name: []const u8, options: LinkFrameworkOptions) void {
372 const graph = m.owner.graph;
373 const arena = graph.arena;
374 m.frameworks.put(arena, graph.dupeString(name), options) catch @panic("OOM");
375}
376
377pub const AddCSourceFilesOptions = struct {
378 /// When provided, `files` are relative to `root` rather than the
379 /// package that owns the `Compile` step.
380 root: ?LazyPath = null,
381 files: []const []const u8,
382 flags: []const []const u8 = &.{},
383 /// By default, determines language of each file individually based on its file extension
384 language: ?CSourceLanguage = null,
385};
386
387/// Handy when you have many non-Zig source files and want them all to have the same flags.
388pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void {
389 const b = m.owner;
390 const graph = m.owner.graph;
391 const arena = graph.arena;
392
393 for (options.files) |path| {
394 if (std.fs.path.isAbsolute(path)) {
395 std.debug.panic(
396 "file paths added with 'addCSourceFiles' must be relative, found absolute path '{s}'",
397 .{path},
398 );
399 }
400 }
401
402 const c_source_files = arena.create(CSourceFiles) catch @panic("OOM");
403 c_source_files.* = .{
404 .root = options.root orelse b.path(""),
405 .files = b.graph.dupeStrings(options.files),
406 .flags = b.graph.dupeStrings(options.flags),
407 .language = options.language,
408 };
409 m.link_objects.append(arena, .{ .c_source_files = c_source_files }) catch @panic("OOM");
410}
411
412pub fn addCSourceFile(m: *Module, source: CSourceFile) void {
413 const graph = m.owner.graph;
414 const arena = graph.arena;
415 const c_source_file = arena.create(CSourceFile) catch @panic("OOM");
416 c_source_file.* = source.dupe(graph);
417 m.link_objects.append(arena, .{ .c_source_file = c_source_file }) catch @panic("OOM");
418}
419
420/// Deprecated. This functionality will be moved to an external package:
421/// https://codeberg.org/ziglang/rc
422///
423/// Resource files must have the extension `.rc`.
424/// Can be called regardless of target. The .rc file will be ignored
425/// if the target object format does not support embedded resources.
426pub fn addWin32ResourceFile(m: *Module, source: RcSourceFile) void {
427 const graph = m.owner.graph;
428 const arena = graph.arena;
429 const target = m.requireKnownTarget();
430 // Only the PE/COFF format has a Resource Table, so for any other target
431 // the resource file is ignored.
432 if (target.ofmt != .coff) return;
433
434 const rc_source_file = arena.create(RcSourceFile) catch @panic("OOM");
435 rc_source_file.* = source.dupe(graph);
436 m.link_objects.append(arena, .{ .win32_resource_file = rc_source_file }) catch @panic("OOM");
437}
438
439pub fn addAssemblyFile(m: *Module, source: LazyPath) void {
440 const graph = m.owner.graph;
441 const arena = graph.arena;
442 m.link_objects.append(arena, .{ .assembly_file = source.dupe(graph) }) catch @panic("OOM");
443}
444
445pub fn addObjectFile(m: *Module, object: LazyPath) void {
446 const graph = m.owner.graph;
447 const arena = graph.arena;
448 m.link_objects.append(arena, .{ .static_path = object.dupe(graph) }) catch @panic("OOM");
449}
450
451pub fn addObject(m: *Module, object: *Step.Compile) void {
452 assert(object.kind == .obj or object.kind == .test_obj);
453 m.linkLibraryOrObject(object);
454}
455
456pub fn linkLibrary(m: *Module, library: *Step.Compile) void {
457 assert(library.kind == .lib);
458 m.linkLibraryOrObject(library);
459}
460
461pub fn addAfterIncludePath(m: *Module, lazy_path: LazyPath) void {
462 const graph = m.owner.graph;
463 const arena = graph.arena;
464 m.include_dirs.append(arena, .{ .path_after = lazy_path.dupe(graph) }) catch @panic("OOM");
465}
466
467pub fn addSystemIncludePath(m: *Module, lazy_path: LazyPath) void {
468 const graph = m.owner.graph;
469 const arena = graph.arena;
470 m.include_dirs.append(arena, .{ .path_system = lazy_path.dupe(graph) }) catch @panic("OOM");
471}
472
473pub fn addIncludePath(m: *Module, lazy_path: LazyPath) void {
474 const graph = m.owner.graph;
475 const arena = graph.arena;
476 m.include_dirs.append(arena, .{ .path = lazy_path.dupe(graph) }) catch @panic("OOM");
477}
478
479pub fn addConfigHeader(m: *Module, config_header: *Step.ConfigHeader) void {
480 const graph = m.owner.graph;
481 const arena = graph.arena;
482 m.include_dirs.append(arena, .{ .config_header_step = config_header }) catch @panic("OOM");
483}
484
485pub fn addSystemFrameworkPath(m: *Module, directory_path: LazyPath) void {
486 const graph = m.owner.graph;
487 const arena = graph.arena;
488 m.include_dirs.append(arena, .{ .framework_path_system = directory_path.dupe(graph) }) catch @panic("OOM");
489}
490
491pub fn addFrameworkPath(m: *Module, directory_path: LazyPath) void {
492 const graph = m.owner.graph;
493 const arena = graph.arena;
494 m.include_dirs.append(arena, .{ .framework_path = directory_path.dupe(graph) }) catch @panic("OOM");
495}
496
497pub fn addEmbedPath(m: *Module, lazy_path: LazyPath) void {
498 const graph = m.owner.graph;
499 const arena = graph.arena;
500 m.include_dirs.append(arena, .{ .embed_path = lazy_path.dupe(graph) }) catch @panic("OOM");
501}
502
503pub fn addLibraryPath(m: *Module, directory_path: LazyPath) void {
504 const graph = m.owner.graph;
505 const arena = graph.arena;
506 m.lib_paths.append(arena, directory_path.dupe(graph)) catch @panic("OOM");
507}
508
509pub fn addRPath(m: *Module, directory_path: LazyPath) void {
510 const graph = m.owner.graph;
511 const arena = graph.arena;
512 m.rpaths.append(arena, .{ .lazy_path = directory_path.dupe(graph) }) catch @panic("OOM");
513}
514
515pub fn addRPathSpecial(m: *Module, bytes: []const u8) void {
516 const graph = m.owner.graph;
517 const arena = graph.arena;
518 m.rpaths.append(arena, .{ .special = graph.dupeString(bytes) }) catch @panic("OOM");
519}
520
521/// Equvialent to the following C code, applied to all C source files owned by
522/// this `Module`:
523/// ```c
524/// #define name value
525/// ```
526/// `name` and `value` need not live longer than the function call.
527pub fn addCMacro(m: *Module, name: []const u8, value: []const u8) void {
528 const b = m.owner;
529 const graph = m.owner.graph;
530 const arena = graph.arena;
531 m.c_macros.append(arena, b.fmt("-D{s}={s}", .{ name, value })) catch @panic("OOM");
532}
533
534fn linkLibraryOrObject(m: *Module, other: *Step.Compile) void {
535 const graph = m.owner.graph;
536 const arena = graph.arena;
537
538 _ = other.getEmittedBin(); // Indicate there is a dependency on the outputted binary.
539
540 if (other.rootModuleTarget().os.tag == .windows and other.isDynamicLibrary()) {
541 _ = other.getEmittedImplib(); // Indicate dependency on the outputted implib.
542 }
543
544 m.link_objects.append(arena, .{ .other_step = other }) catch @panic("OOM");
545 m.include_dirs.append(arena, .{ .other_step = other }) catch @panic("OOM");
546}
547
548fn requireKnownTarget(m: *Module) *const std.Target {
549 const resolved_target = &(m.resolved_target orelse
550 @panic("this API requires the Module to be created with a known 'target' field"));
551 return &resolved_target.result;
552}
553
554/// Elements of `modules` and `names` are matched one-to-one.
555pub const Graph = struct {
556 modules: []const *Module,
557 names: []const []const u8,
558};
559
560/// Given that `root` is the root `Module` of a compilation, return all
561/// `Module` in the module graph, including `root` itself. `root` is guaranteed
562/// to be the first module in the returned slice.
563pub fn getGraph(root: *Module) Graph {
564 if (root.cached_graph.modules.len != 0) {
565 return root.cached_graph;
566 }
567
568 const arena = root.owner.graph.arena;
569
570 var modules: std.array_hash_map.Auto(*std.Build.Module, []const u8) = .empty;
571 var next_idx: usize = 0;
572
573 modules.putNoClobber(arena, root, "root") catch @panic("OOM");
574
575 while (next_idx < modules.count()) {
576 const mod = modules.keys()[next_idx];
577 next_idx += 1;
578 modules.ensureUnusedCapacity(arena, mod.import_table.count()) catch @panic("OOM");
579 for (mod.import_table.keys(), mod.import_table.values()) |import_name, other_mod| {
580 modules.putAssumeCapacity(other_mod, import_name);
581 }
582 }
583
584 const result: Graph = .{
585 .modules = modules.keys(),
586 .names = modules.values(),
587 };
588 root.cached_graph = result;
589 return result;
590}