const std = @import("std.zig"); const builtin = @import("builtin"); const io = std.io; const fs = std.fs; const mem = std.mem; const debug = std.debug; const panic = std.debug.panic; const assert = debug.assert; const warn = std.debug.print; // TODO use the log system instead of this const ArrayList = std.ArrayList; const StringHashMap = std.StringHashMap; const Allocator = mem.Allocator; const process = std.process; const BufSet = std.BufSet; const BufMap = std.BufMap; const fmt_lib = std.fmt; const File = std.fs.File; const CrossTarget = std.zig.CrossTarget; const NativeTargetInfo = std.zig.system.NativeTargetInfo; const Sha256 = std.crypto.hash.sha2.Sha256; pub const FmtStep = @import("build/FmtStep.zig"); pub const TranslateCStep = @import("build/TranslateCStep.zig"); pub const WriteFileStep = @import("build/WriteFileStep.zig"); pub const RunStep = @import("build/RunStep.zig"); pub const CheckFileStep = @import("build/CheckFileStep.zig"); pub const InstallRawStep = @import("build/InstallRawStep.zig"); pub const OptionsStep = @import("build/OptionsStep.zig"); pub const Builder = struct { install_tls: TopLevelStep, uninstall_tls: TopLevelStep, allocator: Allocator, user_input_options: UserInputOptionsMap, available_options_map: AvailableOptionsMap, available_options_list: ArrayList(AvailableOption), verbose: bool, verbose_link: bool, verbose_cc: bool, verbose_air: bool, verbose_llvm_ir: bool, verbose_cimport: bool, verbose_llvm_cpu_features: bool, /// The purpose of executing the command is for a human to read compile errors from the terminal prominent_compile_errors: bool, color: enum { auto, on, off } = .auto, invalid_user_input: bool, zig_exe: []const u8, default_step: *Step, env_map: *BufMap, top_level_steps: ArrayList(*TopLevelStep), install_prefix: []const u8, dest_dir: ?[]const u8, lib_dir: []const u8, exe_dir: []const u8, h_dir: []const u8, install_path: []const u8, sysroot: ?[]const u8 = null, search_prefixes: ArrayList([]const u8), libc_file: ?[]const u8 = null, installed_files: ArrayList(InstalledFile), build_root: []const u8, cache_root: []const u8, global_cache_root: []const u8, release_mode: ?std.builtin.Mode, is_release: bool, override_lib_dir: ?[]const u8, vcpkg_root: VcpkgRoot, pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null, args: ?[][]const u8 = null, debug_log_scopes: []const []const u8 = &.{}, /// Experimental. Use system Darling installation to run cross compiled macOS build artifacts. enable_darling: bool = false, /// Use system QEMU installation to run cross compiled foreign architecture build artifacts. enable_qemu: bool = false, /// Darwin. Use Rosetta to run x86_64 macOS build artifacts on arm64 macOS. enable_rosetta: bool = false, /// Use system Wasmtime installation to run cross compiled wasm/wasi build artifacts. enable_wasmtime: bool = false, /// Use system Wine installation to run cross compiled Windows build artifacts. enable_wine: bool = false, /// After following the steps in https://github.com/ziglang/zig/wiki/Updating-libc#glibc, /// this will be the directory $glibc-build-dir/install/glibcs /// Given the example of the aarch64 target, this is the directory /// that contains the path `aarch64-linux-gnu/lib/ld-linux-aarch64.so.1`. glibc_runtimes_dir: ?[]const u8 = null, /// Information about the native target. Computed before build() is invoked. host: NativeTargetInfo, pub const ExecError = error{ ReadFailure, ExitCodeFailure, ProcessTerminated, ExecNotSupported, } || std.ChildProcess.SpawnError; pub const PkgConfigError = error{ PkgConfigCrashed, PkgConfigFailed, PkgConfigNotInstalled, PkgConfigInvalidOutput, }; pub const PkgConfigPkg = struct { name: []const u8, desc: []const u8, }; pub const CStd = enum { C89, C99, C11, }; const UserInputOptionsMap = StringHashMap(UserInputOption); const AvailableOptionsMap = StringHashMap(AvailableOption); const AvailableOption = struct { name: []const u8, type_id: TypeId, description: []const u8, /// If the `type_id` is `enum` this provides the list of enum options enum_options: ?[]const []const u8, }; const UserInputOption = struct { name: []const u8, value: UserValue, used: bool, }; const UserValue = union(enum) { flag: void, scalar: []const u8, list: ArrayList([]const u8), }; const TypeId = enum { bool, int, float, @"enum", string, list, }; const TopLevelStep = struct { pub const base_id = .top_level; step: Step, description: []const u8, }; pub const DirList = struct { lib_dir: ?[]const u8 = null, exe_dir: ?[]const u8 = null, include_dir: ?[]const u8 = null, }; pub fn create( allocator: Allocator, zig_exe: []const u8, build_root: []const u8, cache_root: []const u8, global_cache_root: []const u8, ) !*Builder { const env_map = try allocator.create(BufMap); env_map.* = try process.getEnvMap(allocator); const host = try NativeTargetInfo.detect(allocator, .{}); const self = try allocator.create(Builder); self.* = Builder{ .zig_exe = zig_exe, .build_root = build_root, .cache_root = try fs.path.relative(allocator, build_root, cache_root), .global_cache_root = global_cache_root, .verbose = false, .verbose_link = false, .verbose_cc = false, .verbose_air = false, .verbose_llvm_ir = false, .verbose_cimport = false, .verbose_llvm_cpu_features = false, .prominent_compile_errors = false, .invalid_user_input = false, .allocator = allocator, .user_input_options = UserInputOptionsMap.init(allocator), .available_options_map = AvailableOptionsMap.init(allocator), .available_options_list = ArrayList(AvailableOption).init(allocator), .top_level_steps = ArrayList(*TopLevelStep).init(allocator), .default_step = undefined, .env_map = env_map, .search_prefixes = ArrayList([]const u8).init(allocator), .install_prefix = undefined, .lib_dir = undefined, .exe_dir = undefined, .h_dir = undefined, .dest_dir = env_map.get("DESTDIR"), .installed_files = ArrayList(InstalledFile).init(allocator), .install_tls = TopLevelStep{ .step = Step.initNoOp(.top_level, "install", allocator), .description = "Copy build artifacts to prefix path", }, .uninstall_tls = TopLevelStep{ .step = Step.init(.top_level, "uninstall", allocator, makeUninstall), .description = "Remove build artifacts from prefix path", }, .release_mode = null, .is_release = false, .override_lib_dir = null, .install_path = undefined, .vcpkg_root = VcpkgRoot{ .unattempted = {} }, .args = null, .host = host, }; try self.top_level_steps.append(&self.install_tls); try self.top_level_steps.append(&self.uninstall_tls); self.default_step = &self.install_tls.step; return self; } pub fn destroy(self: *Builder) void { self.env_map.deinit(); self.top_level_steps.deinit(); self.allocator.destroy(self); } /// This function is intended to be called by std/special/build_runner.zig, not a build.zig file. pub fn resolveInstallPrefix(self: *Builder, install_prefix: ?[]const u8, dir_list: DirList) void { if (self.dest_dir) |dest_dir| { self.install_prefix = install_prefix orelse "/usr"; self.install_path = self.pathJoin(&.{ dest_dir, self.install_prefix }); } else { self.install_prefix = install_prefix orelse (self.pathJoin(&.{ self.build_root, "zig-out" })); self.install_path = self.install_prefix; } var lib_list = [_][]const u8{ self.install_path, "lib" }; var exe_list = [_][]const u8{ self.install_path, "bin" }; var h_list = [_][]const u8{ self.install_path, "include" }; if (dir_list.lib_dir) |dir| { if (std.fs.path.isAbsolute(dir)) lib_list[0] = self.dest_dir orelse ""; lib_list[1] = dir; } if (dir_list.exe_dir) |dir| { if (std.fs.path.isAbsolute(dir)) exe_list[0] = self.dest_dir orelse ""; exe_list[1] = dir; } if (dir_list.include_dir) |dir| { if (std.fs.path.isAbsolute(dir)) h_list[0] = self.dest_dir orelse ""; h_list[1] = dir; } self.lib_dir = self.pathJoin(&lib_list); self.exe_dir = self.pathJoin(&exe_list); self.h_dir = self.pathJoin(&h_list); } fn convertOptionalPathToFileSource(path: ?[]const u8) ?FileSource { return if (path) |p| FileSource{ .path = p } else null; } pub fn addExecutable(self: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep { return addExecutableSource(self, name, convertOptionalPathToFileSource(root_src)); } pub fn addExecutableSource(builder: *Builder, name: []const u8, root_src: ?FileSource) *LibExeObjStep { return LibExeObjStep.createExecutable(builder, name, root_src); } pub fn addOptions(self: *Builder) *OptionsStep { return OptionsStep.create(self); } pub fn addObject(self: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep { return addObjectSource(self, name, convertOptionalPathToFileSource(root_src)); } pub fn addObjectSource(builder: *Builder, name: []const u8, root_src: ?FileSource) *LibExeObjStep { return LibExeObjStep.createObject(builder, name, root_src); } pub fn addSharedLibrary( self: *Builder, name: []const u8, root_src: ?[]const u8, kind: LibExeObjStep.SharedLibKind, ) *LibExeObjStep { return addSharedLibrarySource(self, name, convertOptionalPathToFileSource(root_src), kind); } pub fn addSharedLibrarySource( self: *Builder, name: []const u8, root_src: ?FileSource, kind: LibExeObjStep.SharedLibKind, ) *LibExeObjStep { return LibExeObjStep.createSharedLibrary(self, name, root_src, kind); } pub fn addStaticLibrary(self: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep { return addStaticLibrarySource(self, name, convertOptionalPathToFileSource(root_src)); } pub fn addStaticLibrarySource(self: *Builder, name: []const u8, root_src: ?FileSource) *LibExeObjStep { return LibExeObjStep.createStaticLibrary(self, name, root_src); } pub fn addTest(self: *Builder, root_src: []const u8) *LibExeObjStep { return LibExeObjStep.createTest(self, "test", .{ .path = root_src }); } pub fn addTestSource(self: *Builder, root_src: FileSource) *LibExeObjStep { return LibExeObjStep.createTest(self, "test", root_src.dupe(self)); } pub fn addTestExe(self: *Builder, name: []const u8, root_src: []const u8) *LibExeObjStep { return LibExeObjStep.createTestExe(self, name, .{ .path = root_src }); } pub fn addTestExeSource(self: *Builder, name: []const u8, root_src: FileSource) *LibExeObjStep { return LibExeObjStep.createTestExe(self, name, root_src.dupe(self)); } pub fn addAssemble(self: *Builder, name: []const u8, src: []const u8) *LibExeObjStep { return addAssembleSource(self, name, .{ .path = src }); } pub fn addAssembleSource(self: *Builder, name: []const u8, src: FileSource) *LibExeObjStep { const obj_step = LibExeObjStep.createObject(self, name, null); obj_step.addAssemblyFileSource(src.dupe(self)); return obj_step; } /// Initializes a RunStep with argv, which must at least have the path to the /// executable. More command line arguments can be added with `addArg`, /// `addArgs`, and `addArtifactArg`. /// Be careful using this function, as it introduces a system dependency. /// To run an executable built with zig build, see `LibExeObjStep.run`. pub fn addSystemCommand(self: *Builder, argv: []const []const u8) *RunStep { assert(argv.len >= 1); const run_step = RunStep.create(self, self.fmt("run {s}", .{argv[0]})); run_step.addArgs(argv); return run_step; } /// Allocator.dupe without the need to handle out of memory. pub fn dupe(self: *Builder, bytes: []const u8) []u8 { return self.allocator.dupe(u8, bytes) catch unreachable; } /// Duplicates an array of strings without the need to handle out of memory. pub fn dupeStrings(self: *Builder, strings: []const []const u8) [][]u8 { const array = self.allocator.alloc([]u8, strings.len) catch unreachable; for (strings) |s, i| { array[i] = self.dupe(s); } return array; } /// Duplicates a path and converts all slashes to the OS's canonical path separator. pub fn dupePath(self: *Builder, bytes: []const u8) []u8 { const the_copy = self.dupe(bytes); for (the_copy) |*byte| { switch (byte.*) { '/', '\\' => byte.* = fs.path.sep, else => {}, } } return the_copy; } /// Duplicates a package recursively. pub fn dupePkg(self: *Builder, package: Pkg) Pkg { var the_copy = Pkg{ .name = self.dupe(package.name), .path = package.path.dupe(self), }; if (package.dependencies) |dependencies| { const new_dependencies = self.allocator.alloc(Pkg, dependencies.len) catch unreachable; the_copy.dependencies = new_dependencies; for (dependencies) |dep_package, i| { new_dependencies[i] = self.dupePkg(dep_package); } } return the_copy; } pub fn addWriteFile(self: *Builder, file_path: []const u8, data: []const u8) *WriteFileStep { const write_file_step = self.addWriteFiles(); write_file_step.add(file_path, data); return write_file_step; } pub fn addWriteFiles(self: *Builder) *WriteFileStep { const write_file_step = self.allocator.create(WriteFileStep) catch unreachable; write_file_step.* = WriteFileStep.init(self); return write_file_step; } pub fn addLog(self: *Builder, comptime format: []const u8, args: anytype) *LogStep { const data = self.fmt(format, args); const log_step = self.allocator.create(LogStep) catch unreachable; log_step.* = LogStep.init(self, data); return log_step; } pub fn addRemoveDirTree(self: *Builder, dir_path: []const u8) *RemoveDirStep { const remove_dir_step = self.allocator.create(RemoveDirStep) catch unreachable; remove_dir_step.* = RemoveDirStep.init(self, dir_path); return remove_dir_step; } pub fn addFmt(self: *Builder, paths: []const []const u8) *FmtStep { return FmtStep.create(self, paths); } pub fn addTranslateC(self: *Builder, source: FileSource) *TranslateCStep { return TranslateCStep.create(self, source.dupe(self)); } pub fn version(self: *const Builder, major: u32, minor: u32, patch: u32) LibExeObjStep.SharedLibKind { _ = self; return .{ .versioned = .{ .major = major, .minor = minor, .patch = patch, }, }; } pub fn make(self: *Builder, step_names: []const []const u8) !void { try self.makePath(self.cache_root); var wanted_steps = ArrayList(*Step).init(self.allocator); defer wanted_steps.deinit(); if (step_names.len == 0) { try wanted_steps.append(self.default_step); } else { for (step_names) |step_name| { const s = try self.getTopLevelStepByName(step_name); try wanted_steps.append(s); } } for (wanted_steps.items) |s| { try self.makeOneStep(s); } } pub fn getInstallStep(self: *Builder) *Step { return &self.install_tls.step; } pub fn getUninstallStep(self: *Builder) *Step { return &self.uninstall_tls.step; } fn makeUninstall(uninstall_step: *Step) anyerror!void { const uninstall_tls = @fieldParentPtr(TopLevelStep, "step", uninstall_step); const self = @fieldParentPtr(Builder, "uninstall_tls", uninstall_tls); for (self.installed_files.items) |installed_file| { const full_path = self.getInstallPath(installed_file.dir, installed_file.path); if (self.verbose) { warn("rm {s}\n", .{full_path}); } fs.cwd().deleteTree(full_path) catch {}; } // TODO remove empty directories } fn makeOneStep(self: *Builder, s: *Step) anyerror!void { if (s.loop_flag) { warn("Dependency loop detected:\n {s}\n", .{s.name}); return error.DependencyLoopDetected; } s.loop_flag = true; for (s.dependencies.items) |dep| { self.makeOneStep(dep) catch |err| { if (err == error.DependencyLoopDetected) { warn(" {s}\n", .{s.name}); } return err; }; } s.loop_flag = false; try s.make(); } fn getTopLevelStepByName(self: *Builder, name: []const u8) !*Step { for (self.top_level_steps.items) |top_level_step| { if (mem.eql(u8, top_level_step.step.name, name)) { return &top_level_step.step; } } warn("Cannot run step '{s}' because it does not exist\n", .{name}); return error.InvalidStepName; } pub fn option(self: *Builder, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T { const name = self.dupe(name_raw); const description = self.dupe(description_raw); const type_id = comptime typeToEnum(T); const enum_options = if (type_id == .@"enum") blk: { const fields = comptime std.meta.fields(T); var options = ArrayList([]const u8).initCapacity(self.allocator, fields.len) catch unreachable; inline for (fields) |field| { options.appendAssumeCapacity(field.name); } break :blk options.toOwnedSlice(); } else null; const available_option = AvailableOption{ .name = name, .type_id = type_id, .description = description, .enum_options = enum_options, }; if ((self.available_options_map.fetchPut(name, available_option) catch unreachable) != null) { panic("Option '{s}' declared twice", .{name}); } self.available_options_list.append(available_option) catch unreachable; const option_ptr = self.user_input_options.getPtr(name) orelse return null; option_ptr.used = true; switch (type_id) { .bool => switch (option_ptr.value) { .flag => return true, .scalar => |s| { if (mem.eql(u8, s, "true")) { return true; } else if (mem.eql(u8, s, "false")) { return false; } else { warn("Expected -D{s} to be a boolean, but received '{s}'\n\n", .{ name, s }); self.markInvalidUserInput(); return null; } }, .list => { warn("Expected -D{s} to be a boolean, but received a list.\n\n", .{name}); self.markInvalidUserInput(); return null; }, }, .int => switch (option_ptr.value) { .flag => { warn("Expected -D{s} to be an integer, but received a boolean.\n\n", .{name}); self.markInvalidUserInput(); return null; }, .scalar => |s| { const n = std.fmt.parseInt(T, s, 10) catch |err| switch (err) { error.Overflow => { warn("-D{s} value {s} cannot fit into type {s}.\n\n", .{ name, s, @typeName(T) }); self.markInvalidUserInput(); return null; }, else => { warn("Expected -D{s} to be an integer of type {s}.\n\n", .{ name, @typeName(T) }); self.markInvalidUserInput(); return null; }, }; return n; }, .list => { warn("Expected -D{s} to be an integer, but received a list.\n\n", .{name}); self.markInvalidUserInput(); return null; }, }, .float => switch (option_ptr.value) { .flag => { warn("Expected -D{s} to be a float, but received a boolean.\n\n", .{name}); self.markInvalidUserInput(); return null; }, .scalar => |s| { const n = std.fmt.parseFloat(T, s) catch { warn("Expected -D{s} to be a float of type {s}.\n\n", .{ name, @typeName(T) }); self.markInvalidUserInput(); return null; }; return n; }, .list => { warn("Expected -D{s} to be a float, but received a list.\n\n", .{name}); self.markInvalidUserInput(); return null; }, }, .@"enum" => switch (option_ptr.value) { .flag => { warn("Expected -D{s} to be a string, but received a boolean.\n\n", .{name}); self.markInvalidUserInput(); return null; }, .scalar => |s| { if (std.meta.stringToEnum(T, s)) |enum_lit| { return enum_lit; } else { warn("Expected -D{s} to be of type {s}.\n\n", .{ name, @typeName(T) }); self.markInvalidUserInput(); return null; } }, .list => { warn("Expected -D{s} to be a string, but received a list.\n\n", .{name}); self.markInvalidUserInput(); return null; }, }, .string => switch (option_ptr.value) { .flag => { warn("Expected -D{s} to be a string, but received a boolean.\n\n", .{name}); self.markInvalidUserInput(); return null; }, .list => { warn("Expected -D{s} to be a string, but received a list.\n\n", .{name}); self.markInvalidUserInput(); return null; }, .scalar => |s| return s, }, .list => switch (option_ptr.value) { .flag => { warn("Expected -D{s} to be a list, but received a boolean.\n\n", .{name}); self.markInvalidUserInput(); return null; }, .scalar => |s| { return self.allocator.dupe([]const u8, &[_][]const u8{s}) catch unreachable; }, .list => |lst| return lst.items, }, } } pub fn step(self: *Builder, name: []const u8, description: []const u8) *Step { const step_info = self.allocator.create(TopLevelStep) catch unreachable; step_info.* = TopLevelStep{ .step = Step.initNoOp(.top_level, name, self.allocator), .description = self.dupe(description), }; self.top_level_steps.append(step_info) catch unreachable; return &step_info.step; } /// This provides the -Drelease option to the build user and does not give them the choice. pub fn setPreferredReleaseMode(self: *Builder, mode: std.builtin.Mode) void { if (self.release_mode != null) { @panic("setPreferredReleaseMode must be called before standardReleaseOptions and may not be called twice"); } const description = self.fmt("Create a release build ({s})", .{@tagName(mode)}); self.is_release = self.option(bool, "release", description) orelse false; self.release_mode = if (self.is_release) mode else std.builtin.Mode.Debug; } /// If you call this without first calling `setPreferredReleaseMode` then it gives the build user /// the choice of what kind of release. pub fn standardReleaseOptions(self: *Builder) std.builtin.Mode { if (self.release_mode) |mode| return mode; const release_safe = self.option(bool, "release-safe", "Optimizations on and safety on") orelse false; const release_fast = self.option(bool, "release-fast", "Optimizations on and safety off") orelse false; const release_small = self.option(bool, "release-small", "Size optimizations on and safety off") orelse false; const mode = if (release_safe and !release_fast and !release_small) std.builtin.Mode.ReleaseSafe else if (release_fast and !release_safe and !release_small) std.builtin.Mode.ReleaseFast else if (release_small and !release_fast and !release_safe) std.builtin.Mode.ReleaseSmall else if (!release_fast and !release_safe and !release_small) std.builtin.Mode.Debug else x: { warn("Multiple release modes (of -Drelease-safe, -Drelease-fast and -Drelease-small)\n\n", .{}); self.markInvalidUserInput(); break :x std.builtin.Mode.Debug; }; self.is_release = mode != .Debug; self.release_mode = mode; return mode; } pub const StandardTargetOptionsArgs = struct { whitelist: ?[]const CrossTarget = null, default_target: CrossTarget = CrossTarget{}, }; /// Exposes standard `zig build` options for choosing a target. pub fn standardTargetOptions(self: *Builder, args: StandardTargetOptionsArgs) CrossTarget { const maybe_triple = self.option( []const u8, "target", "The CPU architecture, OS, and ABI to build for", ); const mcpu = self.option([]const u8, "cpu", "Target CPU features to add or subtract"); if (maybe_triple == null and mcpu == null) { return args.default_target; } const triple = maybe_triple orelse "native"; var diags: CrossTarget.ParseOptions.Diagnostics = .{}; const selected_target = CrossTarget.parse(.{ .arch_os_abi = triple, .cpu_features = mcpu, .diagnostics = &diags, }) catch |err| switch (err) { error.UnknownCpuModel => { warn("Unknown CPU: '{s}'\nAvailable CPUs for architecture '{s}':\n", .{ diags.cpu_name.?, @tagName(diags.arch.?), }); for (diags.arch.?.allCpuModels()) |cpu| { warn(" {s}\n", .{cpu.name}); } warn("\n", .{}); self.markInvalidUserInput(); return args.default_target; }, error.UnknownCpuFeature => { warn( \\Unknown CPU feature: '{s}' \\Available CPU features for architecture '{s}': \\ , .{ diags.unknown_feature_name, @tagName(diags.arch.?), }); for (diags.arch.?.allFeaturesList()) |feature| { warn(" {s}: {s}\n", .{ feature.name, feature.description }); } warn("\n", .{}); self.markInvalidUserInput(); return args.default_target; }, error.UnknownOperatingSystem => { warn( \\Unknown OS: '{s}' \\Available operating systems: \\ , .{diags.os_name}); inline for (std.meta.fields(std.Target.Os.Tag)) |field| { warn(" {s}\n", .{field.name}); } warn("\n", .{}); self.markInvalidUserInput(); return args.default_target; }, else => |e| { warn("Unable to parse target '{s}': {s}\n\n", .{ triple, @errorName(e) }); self.markInvalidUserInput(); return args.default_target; }, }; const selected_canonicalized_triple = selected_target.zigTriple(self.allocator) catch unreachable; if (args.whitelist) |list| whitelist_check: { // Make sure it's a match of one of the list. var mismatch_triple = true; var mismatch_cpu_features = true; var whitelist_item = CrossTarget{}; for (list) |t| { mismatch_cpu_features = true; mismatch_triple = true; const t_triple = t.zigTriple(self.allocator) catch unreachable; if (mem.eql(u8, t_triple, selected_canonicalized_triple)) { mismatch_triple = false; whitelist_item = t; if (t.getCpuFeatures().isSuperSetOf(selected_target.getCpuFeatures())) { mismatch_cpu_features = false; break :whitelist_check; } else { break; } } } if (mismatch_triple) { warn("Chosen target '{s}' does not match one of the supported targets:\n", .{ selected_canonicalized_triple, }); for (list) |t| { const t_triple = t.zigTriple(self.allocator) catch unreachable; warn(" {s}\n", .{t_triple}); } warn("\n", .{}); } else { assert(mismatch_cpu_features); const whitelist_cpu = whitelist_item.getCpu(); const selected_cpu = selected_target.getCpu(); warn("Chosen CPU model '{s}' does not match one of the supported targets:\n", .{ selected_cpu.model.name, }); warn(" Supported feature Set: ", .{}); const all_features = whitelist_cpu.arch.allFeaturesList(); var populated_cpu_features = whitelist_cpu.model.features; populated_cpu_features.populateDependencies(all_features); for (all_features) |feature, i_usize| { const i = @intCast(std.Target.Cpu.Feature.Set.Index, i_usize); const in_cpu_set = populated_cpu_features.isEnabled(i); if (in_cpu_set) { warn("{s} ", .{feature.name}); } } warn("\n", .{}); warn(" Remove: ", .{}); for (all_features) |feature, i_usize| { const i = @intCast(std.Target.Cpu.Feature.Set.Index, i_usize); const in_cpu_set = populated_cpu_features.isEnabled(i); const in_actual_set = selected_cpu.features.isEnabled(i); if (in_actual_set and !in_cpu_set) { warn("{s} ", .{feature.name}); } } warn("\n", .{}); } self.markInvalidUserInput(); return args.default_target; } return selected_target; } pub fn addUserInputOption(self: *Builder, name_raw: []const u8, value_raw: []const u8) !bool { const name = self.dupe(name_raw); const value = self.dupe(value_raw); const gop = try self.user_input_options.getOrPut(name); if (!gop.found_existing) { gop.value_ptr.* = UserInputOption{ .name = name, .value = .{ .scalar = value }, .used = false, }; return false; } // option already exists switch (gop.value_ptr.value) { .scalar => |s| { // turn it into a list var list = ArrayList([]const u8).init(self.allocator); list.append(s) catch unreachable; list.append(value) catch unreachable; self.user_input_options.put(name, .{ .name = name, .value = .{ .list = list }, .used = false, }) catch unreachable; }, .list => |*list| { // append to the list list.append(value) catch unreachable; self.user_input_options.put(name, .{ .name = name, .value = .{ .list = list.* }, .used = false, }) catch unreachable; }, .flag => { warn("Option '-D{s}={s}' conflicts with flag '-D{s}'.\n", .{ name, value, name }); return true; }, } return false; } pub fn addUserInputFlag(self: *Builder, name_raw: []const u8) !bool { const name = self.dupe(name_raw); const gop = try self.user_input_options.getOrPut(name); if (!gop.found_existing) { gop.value_ptr.* = .{ .name = name, .value = .{ .flag = {} }, .used = false, }; return false; } // option already exists switch (gop.value_ptr.value) { .scalar => |s| { warn("Flag '-D{s}' conflicts with option '-D{s}={s}'.\n", .{ name, name, s }); return true; }, .list => { warn("Flag '-D{s}' conflicts with multiple options of the same name.\n", .{name}); return true; }, .flag => {}, } return false; } fn typeToEnum(comptime T: type) TypeId { return switch (@typeInfo(T)) { .Int => .int, .Float => .float, .Bool => .bool, .Enum => .@"enum", else => switch (T) { []const u8 => .string, []const []const u8 => .list, else => @compileError("Unsupported type: " ++ @typeName(T)), }, }; } fn markInvalidUserInput(self: *Builder) void { self.invalid_user_input = true; } pub fn validateUserInputDidItFail(self: *Builder) bool { // make sure all args are used var it = self.user_input_options.iterator(); while (it.next()) |entry| { if (!entry.value_ptr.used) { warn("Invalid option: -D{s}\n\n", .{entry.key_ptr.*}); self.markInvalidUserInput(); } } return self.invalid_user_input; } pub fn spawnChild(self: *Builder, argv: []const []const u8) !void { return self.spawnChildEnvMap(null, self.env_map, argv); } fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void { if (cwd) |yes_cwd| warn("cd {s} && ", .{yes_cwd}); for (argv) |arg| { warn("{s} ", .{arg}); } warn("\n", .{}); } pub fn spawnChildEnvMap(self: *Builder, cwd: ?[]const u8, env_map: *const BufMap, argv: []const []const u8) !void { if (self.verbose) { printCmd(cwd, argv); } if (!std.process.can_spawn) return error.ExecNotSupported; const child = std.ChildProcess.init(argv, self.allocator) catch unreachable; defer child.deinit(); child.cwd = cwd; child.env_map = env_map; const term = child.spawnAndWait() catch |err| { warn("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) }); return err; }; switch (term) { .Exited => |code| { if (code != 0) { warn("The following command exited with error code {}:\n", .{code}); printCmd(cwd, argv); return error.UncleanExit; } }, else => { warn("The following command terminated unexpectedly:\n", .{}); printCmd(cwd, argv); return error.UncleanExit; }, } } pub fn makePath(self: *Builder, path: []const u8) !void { fs.cwd().makePath(self.pathFromRoot(path)) catch |err| { warn("Unable to create path {s}: {s}\n", .{ path, @errorName(err) }); return err; }; } pub fn installArtifact(self: *Builder, artifact: *LibExeObjStep) void { self.getInstallStep().dependOn(&self.addInstallArtifact(artifact).step); } pub fn addInstallArtifact(self: *Builder, artifact: *LibExeObjStep) *InstallArtifactStep { return InstallArtifactStep.create(self, artifact); } ///`dest_rel_path` is relative to prefix path pub fn installFile(self: *Builder, src_path: []const u8, dest_rel_path: []const u8) void { self.getInstallStep().dependOn(&self.addInstallFileWithDir(.{ .path = src_path }, .prefix, dest_rel_path).step); } pub fn installDirectory(self: *Builder, options: InstallDirectoryOptions) void { self.getInstallStep().dependOn(&self.addInstallDirectory(options).step); } ///`dest_rel_path` is relative to bin path pub fn installBinFile(self: *Builder, src_path: []const u8, dest_rel_path: []const u8) void { self.getInstallStep().dependOn(&self.addInstallFileWithDir(.{ .path = src_path }, .bin, dest_rel_path).step); } ///`dest_rel_path` is relative to lib path pub fn installLibFile(self: *Builder, src_path: []const u8, dest_rel_path: []const u8) void { self.getInstallStep().dependOn(&self.addInstallFileWithDir(.{ .path = src_path }, .lib, dest_rel_path).step); } /// Output format (BIN vs Intel HEX) determined by filename pub fn installRaw(self: *Builder, artifact: *LibExeObjStep, dest_filename: []const u8, options: InstallRawStep.CreateOptions) *InstallRawStep { const raw = self.addInstallRaw(artifact, dest_filename, options); self.getInstallStep().dependOn(&raw.step); return raw; } ///`dest_rel_path` is relative to install prefix path pub fn addInstallFile(self: *Builder, source: FileSource, dest_rel_path: []const u8) *InstallFileStep { return self.addInstallFileWithDir(source.dupe(self), .prefix, dest_rel_path); } ///`dest_rel_path` is relative to bin path pub fn addInstallBinFile(self: *Builder, source: FileSource, dest_rel_path: []const u8) *InstallFileStep { return self.addInstallFileWithDir(source.dupe(self), .bin, dest_rel_path); } ///`dest_rel_path` is relative to lib path pub fn addInstallLibFile(self: *Builder, source: FileSource, dest_rel_path: []const u8) *InstallFileStep { return self.addInstallFileWithDir(source.dupe(self), .lib, dest_rel_path); } pub fn addInstallRaw(self: *Builder, artifact: *LibExeObjStep, dest_filename: []const u8, options: InstallRawStep.CreateOptions) *InstallRawStep { return InstallRawStep.create(self, artifact, dest_filename, options); } pub fn addInstallFileWithDir( self: *Builder, source: FileSource, install_dir: InstallDir, dest_rel_path: []const u8, ) *InstallFileStep { if (dest_rel_path.len == 0) { panic("dest_rel_path must be non-empty", .{}); } const install_step = self.allocator.create(InstallFileStep) catch unreachable; install_step.* = InstallFileStep.init(self, source.dupe(self), install_dir, dest_rel_path); return install_step; } pub fn addInstallDirectory(self: *Builder, options: InstallDirectoryOptions) *InstallDirStep { const install_step = self.allocator.create(InstallDirStep) catch unreachable; install_step.* = InstallDirStep.init(self, options); return install_step; } pub fn pushInstalledFile(self: *Builder, dir: InstallDir, dest_rel_path: []const u8) void { const file = InstalledFile{ .dir = dir, .path = dest_rel_path, }; self.installed_files.append(file.dupe(self)) catch unreachable; } pub fn updateFile(self: *Builder, source_path: []const u8, dest_path: []const u8) !void { if (self.verbose) { warn("cp {s} {s} ", .{ source_path, dest_path }); } const cwd = fs.cwd(); const prev_status = try fs.Dir.updateFile(cwd, source_path, cwd, dest_path, .{}); if (self.verbose) switch (prev_status) { .stale => warn("# installed\n", .{}), .fresh => warn("# up-to-date\n", .{}), }; } pub fn truncateFile(self: *Builder, dest_path: []const u8) !void { if (self.verbose) { warn("truncate {s}\n", .{dest_path}); } const cwd = fs.cwd(); var src_file = cwd.createFile(dest_path, .{}) catch |err| switch (err) { error.FileNotFound => blk: { if (fs.path.dirname(dest_path)) |dirname| { try cwd.makePath(dirname); } break :blk try cwd.createFile(dest_path, .{}); }, else => |e| return e, }; src_file.close(); } pub fn pathFromRoot(self: *Builder, rel_path: []const u8) []u8 { return fs.path.resolve(self.allocator, &[_][]const u8{ self.build_root, rel_path }) catch unreachable; } /// Shorthand for `std.fs.path.join(builder.allocator, paths) catch unreachable` pub fn pathJoin(self: *Builder, paths: []const []const u8) []u8 { return fs.path.join(self.allocator, paths) catch unreachable; } pub fn fmt(self: *Builder, comptime format: []const u8, args: anytype) []u8 { return fmt_lib.allocPrint(self.allocator, format, args) catch unreachable; } pub fn findProgram(self: *Builder, names: []const []const u8, paths: []const []const u8) ![]const u8 { // TODO report error for ambiguous situations const exe_extension = @as(CrossTarget, .{}).exeFileExt(); for (self.search_prefixes.items) |search_prefix| { for (names) |name| { if (fs.path.isAbsolute(name)) { return name; } const full_path = self.pathJoin(&.{ search_prefix, "bin", self.fmt("{s}{s}", .{ name, exe_extension }), }); return fs.realpathAlloc(self.allocator, full_path) catch continue; } } if (self.env_map.get("PATH")) |PATH| { for (names) |name| { if (fs.path.isAbsolute(name)) { return name; } var it = mem.tokenize(u8, PATH, &[_]u8{fs.path.delimiter}); while (it.next()) |path| { const full_path = self.pathJoin(&.{ path, self.fmt("{s}{s}", .{ name, exe_extension }), }); return fs.realpathAlloc(self.allocator, full_path) catch continue; } } } for (names) |name| { if (fs.path.isAbsolute(name)) { return name; } for (paths) |path| { const full_path = self.pathJoin(&.{ path, self.fmt("{s}{s}", .{ name, exe_extension }), }); return fs.realpathAlloc(self.allocator, full_path) catch continue; } } return error.FileNotFound; } pub fn execAllowFail( self: *Builder, argv: []const []const u8, out_code: *u8, stderr_behavior: std.ChildProcess.StdIo, ) ExecError![]u8 { assert(argv.len != 0); if (!std.process.can_spawn) return error.ExecNotSupported; const max_output_size = 400 * 1024; const child = try std.ChildProcess.init(argv, self.allocator); defer child.deinit(); child.stdin_behavior = .Ignore; child.stdout_behavior = .Pipe; child.stderr_behavior = stderr_behavior; child.env_map = self.env_map; try child.spawn(); const stdout = child.stdout.?.reader().readAllAlloc(self.allocator, max_output_size) catch { return error.ReadFailure; }; errdefer self.allocator.free(stdout); const term = try child.wait(); switch (term) { .Exited => |code| { if (code != 0) { out_code.* = @truncate(u8, code); return error.ExitCodeFailure; } return stdout; }, .Signal, .Stopped, .Unknown => |code| { out_code.* = @truncate(u8, code); return error.ProcessTerminated; }, } } pub fn execFromStep(self: *Builder, argv: []const []const u8, src_step: ?*Step) ![]u8 { assert(argv.len != 0); if (self.verbose) { printCmd(null, argv); } if (!std.process.can_spawn) { if (src_step) |s| warn("{s}...", .{s.name}); warn("Unable to spawn the following command: cannot spawn child process\n", .{}); printCmd(null, argv); std.os.abort(); } var code: u8 = undefined; return self.execAllowFail(argv, &code, .Inherit) catch |err| switch (err) { error.ExecNotSupported => { if (src_step) |s| warn("{s}...", .{s.name}); warn("Unable to spawn the following command: cannot spawn child process\n", .{}); printCmd(null, argv); std.os.abort(); }, error.FileNotFound => { if (src_step) |s| warn("{s}...", .{s.name}); warn("Unable to spawn the following command: file not found\n", .{}); printCmd(null, argv); std.os.exit(@truncate(u8, code)); }, error.ExitCodeFailure => { if (src_step) |s| warn("{s}...", .{s.name}); if (self.prominent_compile_errors) { warn("The step exited with error code {d}\n", .{code}); } else { warn("The following command exited with error code {d}:\n", .{code}); printCmd(null, argv); } std.os.exit(@truncate(u8, code)); }, error.ProcessTerminated => { if (src_step) |s| warn("{s}...", .{s.name}); warn("The following command terminated unexpectedly:\n", .{}); printCmd(null, argv); std.os.exit(@truncate(u8, code)); }, else => |e| return e, }; } pub fn exec(self: *Builder, argv: []const []const u8) ![]u8 { return self.execFromStep(argv, null); } pub fn addSearchPrefix(self: *Builder, search_prefix: []const u8) void { self.search_prefixes.append(self.dupePath(search_prefix)) catch unreachable; } pub fn getInstallPath(self: *Builder, dir: InstallDir, dest_rel_path: []const u8) []const u8 { assert(!fs.path.isAbsolute(dest_rel_path)); // Install paths must be relative to the prefix const base_dir = switch (dir) { .prefix => self.install_path, .bin => self.exe_dir, .lib => self.lib_dir, .header => self.h_dir, .custom => |path| self.pathJoin(&.{ self.install_path, path }), }; return fs.path.resolve( self.allocator, &[_][]const u8{ base_dir, dest_rel_path }, ) catch unreachable; } fn execPkgConfigList(self: *Builder, out_code: *u8) (PkgConfigError || ExecError)![]const PkgConfigPkg { const stdout = try self.execAllowFail(&[_][]const u8{ "pkg-config", "--list-all" }, out_code, .Ignore); var list = ArrayList(PkgConfigPkg).init(self.allocator); errdefer list.deinit(); var line_it = mem.tokenize(u8, stdout, "\r\n"); while (line_it.next()) |line| { if (mem.trim(u8, line, " \t").len == 0) continue; var tok_it = mem.tokenize(u8, line, " \t"); try list.append(PkgConfigPkg{ .name = tok_it.next() orelse return error.PkgConfigInvalidOutput, .desc = tok_it.rest(), }); } return list.toOwnedSlice(); } fn getPkgConfigList(self: *Builder) ![]const PkgConfigPkg { if (self.pkg_config_pkg_list) |res| { return res; } var code: u8 = undefined; if (self.execPkgConfigList(&code)) |list| { self.pkg_config_pkg_list = list; return list; } else |err| { const result = switch (err) { error.ProcessTerminated => error.PkgConfigCrashed, error.ExecNotSupported => error.PkgConfigFailed, error.ExitCodeFailure => error.PkgConfigFailed, error.FileNotFound => error.PkgConfigNotInstalled, error.InvalidName => error.PkgConfigNotInstalled, error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput, error.ChildExecFailed => error.PkgConfigFailed, else => return err, }; self.pkg_config_pkg_list = result; return result; } } }; test "builder.findProgram compiles" { if (builtin.os.tag == .wasi) return error.SkipZigTest; var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const builder = try Builder.create( arena.allocator(), "zig", "zig-cache", "zig-cache", "zig-cache", ); defer builder.destroy(); _ = builder.findProgram(&[_][]const u8{}, &[_][]const u8{}) catch null; } /// TODO: propose some kind of `@deprecate` builtin so that we can deprecate /// this while still having somewhat non-lazy decls. In this file we wanted to do /// refAllDecls for example which makes it trigger `@compileError` if you try /// to use that strategy. pub const Version = @compileError("deprecated; Use `std.builtin.Version`"); pub const Target = @compileError("deprecated; Use `std.zig.CrossTarget`"); pub const Pkg = struct { name: []const u8, path: FileSource, dependencies: ?[]const Pkg = null, }; pub const CSourceFile = struct { source: FileSource, args: []const []const u8, fn dupe(self: CSourceFile, b: *Builder) CSourceFile { return .{ .source = self.source.dupe(b), .args = b.dupeStrings(self.args), }; } }; const CSourceFiles = struct { files: []const []const u8, flags: []const []const u8, }; fn isLibCLibrary(name: []const u8) bool { const libc_libraries = [_][]const u8{ "c", "m", "dl", "rt", "pthread" }; for (libc_libraries) |libc_lib_name| { if (mem.eql(u8, name, libc_lib_name)) return true; } return false; } fn isLibCppLibrary(name: []const u8) bool { const libcpp_libraries = [_][]const u8{ "c++", "stdc++" }; for (libcpp_libraries) |libcpp_lib_name| { if (mem.eql(u8, name, libcpp_lib_name)) return true; } return false; } /// A file that is generated by a build step. /// This struct is an interface that is meant to be used with `@fieldParentPtr` to implement the actual path logic. pub const GeneratedFile = struct { /// The step that generates the file step: *Step, /// The path to the generated file. Must be either absolute or relative to the build root. /// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards. path: ?[]const u8 = null, pub fn getPath(self: GeneratedFile) []const u8 { return self.path orelse std.debug.panic( "getPath() was called on a GeneratedFile that wasn't build yet. Is there a missing Step dependency on step '{s}'?", .{self.step.name}, ); } }; /// A file source is a reference to an existing or future file. /// pub const FileSource = union(enum) { /// A plain file path, relative to build root or absolute. path: []const u8, /// A file that is generated by an interface. Those files usually are /// not available until built by a build step. generated: *const GeneratedFile, /// Returns a new file source that will have a relative path to the build root guaranteed. /// This should be preferred over setting `.path` directly as it documents that the files are in the project directory. pub fn relative(path: []const u8) FileSource { std.debug.assert(!std.fs.path.isAbsolute(path)); return FileSource{ .path = path }; } /// Returns a string that can be shown to represent the file source. /// Either returns the path or `"generated"`. pub fn getDisplayName(self: FileSource) []const u8 { return switch (self) { .path => self.path, .generated => "generated", }; } /// Adds dependencies this file source implies to the given step. pub fn addStepDependencies(self: FileSource, step: *Step) void { switch (self) { .path => {}, .generated => |gen| step.dependOn(gen.step), } } /// Should only be called during make(), returns a path relative to the build root or absolute. pub fn getPath(self: FileSource, builder: *Builder) []const u8 { const path = switch (self) { .path => |p| builder.pathFromRoot(p), .generated => |gen| gen.getPath(), }; return path; } /// Duplicates the file source for a given builder. pub fn dupe(self: FileSource, b: *Builder) FileSource { return switch (self) { .path => |p| .{ .path = b.dupePath(p) }, .generated => |gen| .{ .generated = gen }, }; } }; pub const LibExeObjStep = struct { pub const base_id = .lib_exe_obj; step: Step, builder: *Builder, name: []const u8, target: CrossTarget = CrossTarget{}, target_info: NativeTargetInfo, linker_script: ?FileSource = null, version_script: ?[]const u8 = null, out_filename: []const u8, linkage: ?Linkage = null, version: ?std.builtin.Version, build_mode: std.builtin.Mode, kind: Kind, major_only_filename: ?[]const u8, name_only_filename: ?[]const u8, strip: bool, lib_paths: ArrayList([]const u8), rpaths: ArrayList([]const u8), framework_dirs: ArrayList([]const u8), frameworks: BufSet, verbose_link: bool, verbose_cc: bool, emit_analysis: EmitOption = .default, emit_asm: EmitOption = .default, emit_bin: EmitOption = .default, emit_docs: EmitOption = .default, emit_implib: EmitOption = .default, emit_llvm_bc: EmitOption = .default, emit_llvm_ir: EmitOption = .default, // Lots of things depend on emit_h having a consistent path, // so it is not an EmitOption for now. emit_h: bool = false, bundle_compiler_rt: ?bool = null, single_threaded: ?bool = null, disable_stack_probing: bool, disable_sanitize_c: bool, sanitize_thread: bool, rdynamic: bool, import_memory: bool = false, import_table: bool = false, export_table: bool = false, initial_memory: ?u64 = null, max_memory: ?u64 = null, shared_memory: bool = false, global_base: ?u64 = null, c_std: Builder.CStd, override_lib_dir: ?[]const u8, main_pkg_path: ?[]const u8, exec_cmd_args: ?[]const ?[]const u8, name_prefix: []const u8, filter: ?[]const u8, test_evented_io: bool = false, code_model: std.builtin.CodeModel = .default, wasi_exec_model: ?std.builtin.WasiExecModel = null, /// Symbols to be exported when compiling to wasm export_symbol_names: []const []const u8 = &.{}, root_src: ?FileSource, out_h_filename: []const u8, out_lib_filename: []const u8, out_pdb_filename: []const u8, packages: ArrayList(Pkg), object_src: []const u8, link_objects: ArrayList(LinkObject), include_dirs: ArrayList(IncludeDir), c_macros: ArrayList([]const u8), output_dir: ?[]const u8, is_linking_libc: bool = false, is_linking_libcpp: bool = false, vcpkg_bin_path: ?[]const u8 = null, /// This may be set in order to override the default install directory override_dest_dir: ?InstallDir, installed_path: ?[]const u8, install_step: ?*InstallArtifactStep, /// Base address for an executable image. image_base: ?u64 = null, libc_file: ?FileSource = null, valgrind_support: ?bool = null, each_lib_rpath: ?bool = null, /// Create a .eh_frame_hdr section and a PT_GNU_EH_FRAME segment in the ELF /// file. link_eh_frame_hdr: bool = false, link_emit_relocs: bool = false, /// Place every function in its own section so that unused ones may be /// safely garbage-collected during the linking phase. link_function_sections: bool = false, linker_allow_shlib_undefined: ?bool = null, /// Permit read-only relocations in read-only segments. Disallowed by default. link_z_notext: bool = false, /// (Darwin) Install name for the dylib install_name: ?[]const u8 = null, /// (Darwin) Path to entitlements file entitlements: ?[]const u8 = null, /// Position Independent Code force_pic: ?bool = null, /// Position Independent Executable pie: ?bool = null, red_zone: ?bool = null, omit_frame_pointer: ?bool = null, subsystem: ?std.Target.SubSystem = null, entry_symbol_name: ?[]const u8 = null, /// Overrides the default stack size stack_size: ?u64 = null, want_lto: ?bool = null, output_path_source: GeneratedFile, output_lib_path_source: GeneratedFile, output_h_path_source: GeneratedFile, output_pdb_path_source: GeneratedFile, pub const LinkObject = union(enum) { static_path: FileSource, other_step: *LibExeObjStep, system_lib: []const u8, assembly_file: FileSource, c_source_file: *CSourceFile, c_source_files: *CSourceFiles, }; pub const IncludeDir = union(enum) { raw_path: []const u8, raw_path_system: []const u8, other_step: *LibExeObjStep, }; pub const Kind = enum { exe, lib, obj, @"test", test_exe, }; pub const SharedLibKind = union(enum) { versioned: std.builtin.Version, unversioned: void, }; pub const Linkage = enum { dynamic, static }; pub const EmitOption = union(enum) { default: void, no_emit: void, emit: void, emit_to: []const u8, fn getArg(self: @This(), b: *Builder, arg_name: []const u8) ?[]const u8 { return switch (self) { .no_emit => b.fmt("-fno-{s}", .{arg_name}), .default => null, .emit => b.fmt("-f{s}", .{arg_name}), .emit_to => |path| b.fmt("-f{s}={s}", .{ arg_name, path }), }; } }; pub fn createSharedLibrary(builder: *Builder, name: []const u8, root_src: ?FileSource, kind: SharedLibKind) *LibExeObjStep { return initExtraArgs(builder, name, root_src, .lib, .dynamic, switch (kind) { .versioned => |ver| ver, .unversioned => null, }); } pub fn createStaticLibrary(builder: *Builder, name: []const u8, root_src: ?FileSource) *LibExeObjStep { return initExtraArgs(builder, name, root_src, .lib, .static, null); } pub fn createObject(builder: *Builder, name: []const u8, root_src: ?FileSource) *LibExeObjStep { return initExtraArgs(builder, name, root_src, .obj, null, null); } pub fn createExecutable(builder: *Builder, name: []const u8, root_src: ?FileSource) *LibExeObjStep { return initExtraArgs(builder, name, root_src, .exe, null, null); } pub fn createTest(builder: *Builder, name: []const u8, root_src: FileSource) *LibExeObjStep { return initExtraArgs(builder, name, root_src, .@"test", null, null); } pub fn createTestExe(builder: *Builder, name: []const u8, root_src: FileSource) *LibExeObjStep { return initExtraArgs(builder, name, root_src, .test_exe, null, null); } fn initExtraArgs( builder: *Builder, name_raw: []const u8, root_src_raw: ?FileSource, kind: Kind, linkage: ?Linkage, ver: ?std.builtin.Version, ) *LibExeObjStep { const name = builder.dupe(name_raw); const root_src: ?FileSource = if (root_src_raw) |rsrc| rsrc.dupe(builder) else null; if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) { panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name}); } const self = builder.allocator.create(LibExeObjStep) catch unreachable; self.* = LibExeObjStep{ .strip = false, .builder = builder, .verbose_link = false, .verbose_cc = false, .build_mode = std.builtin.Mode.Debug, .linkage = linkage, .kind = kind, .root_src = root_src, .name = name, .frameworks = BufSet.init(builder.allocator), .step = Step.init(base_id, name, builder.allocator, make), .version = ver, .out_filename = undefined, .out_h_filename = builder.fmt("{s}.h", .{name}), .out_lib_filename = undefined, .out_pdb_filename = builder.fmt("{s}.pdb", .{name}), .major_only_filename = null, .name_only_filename = null, .packages = ArrayList(Pkg).init(builder.allocator), .include_dirs = ArrayList(IncludeDir).init(builder.allocator), .link_objects = ArrayList(LinkObject).init(builder.allocator), .c_macros = ArrayList([]const u8).init(builder.allocator), .lib_paths = ArrayList([]const u8).init(builder.allocator), .rpaths = ArrayList([]const u8).init(builder.allocator), .framework_dirs = ArrayList([]const u8).init(builder.allocator), .object_src = undefined, .c_std = Builder.CStd.C99, .override_lib_dir = null, .main_pkg_path = null, .exec_cmd_args = null, .name_prefix = "", .filter = null, .disable_stack_probing = false, .disable_sanitize_c = false, .sanitize_thread = false, .rdynamic = false, .output_dir = null, .override_dest_dir = null, .installed_path = null, .install_step = null, .output_path_source = GeneratedFile{ .step = &self.step }, .output_lib_path_source = GeneratedFile{ .step = &self.step }, .output_h_path_source = GeneratedFile{ .step = &self.step }, .output_pdb_path_source = GeneratedFile{ .step = &self.step }, .target_info = undefined, // populated in computeOutFileNames }; self.computeOutFileNames(); if (root_src) |rs| rs.addStepDependencies(&self.step); return self; } fn computeOutFileNames(self: *LibExeObjStep) void { self.target_info = NativeTargetInfo.detect(self.builder.allocator, self.target) catch unreachable; const target = self.target_info.target; self.out_filename = std.zig.binNameAlloc(self.builder.allocator, .{ .root_name = self.name, .target = target, .output_mode = switch (self.kind) { .lib => .Lib, .obj => .Obj, .exe, .@"test", .test_exe => .Exe, }, .link_mode = if (self.linkage) |some| @as(std.builtin.LinkMode, switch (some) { .dynamic => .Dynamic, .static => .Static, }) else null, .version = self.version, }) catch unreachable; if (self.kind == .lib) { if (self.linkage != null and self.linkage.? == .static) { self.out_lib_filename = self.out_filename; } else if (self.version) |version| { if (target.isDarwin()) { self.major_only_filename = self.builder.fmt("lib{s}.{d}.dylib", .{ self.name, version.major, }); self.name_only_filename = self.builder.fmt("lib{s}.dylib", .{self.name}); self.out_lib_filename = self.out_filename; } else if (target.os.tag == .windows) { self.out_lib_filename = self.builder.fmt("{s}.lib", .{self.name}); } else { self.major_only_filename = self.builder.fmt("lib{s}.so.{d}", .{ self.name, version.major }); self.name_only_filename = self.builder.fmt("lib{s}.so", .{self.name}); self.out_lib_filename = self.out_filename; } } else { if (target.isDarwin()) { self.out_lib_filename = self.out_filename; } else if (target.os.tag == .windows) { self.out_lib_filename = self.builder.fmt("{s}.lib", .{self.name}); } else { self.out_lib_filename = self.out_filename; } } if (self.output_dir != null) { self.output_lib_path_source.path = self.builder.pathJoin( &.{ self.output_dir.?, self.out_lib_filename }, ); } } } pub fn setTarget(self: *LibExeObjStep, target: CrossTarget) void { self.target = target; self.computeOutFileNames(); } pub fn setOutputDir(self: *LibExeObjStep, dir: []const u8) void { self.output_dir = self.builder.dupePath(dir); } pub fn install(self: *LibExeObjStep) void { self.builder.installArtifact(self); } pub fn installRaw(self: *LibExeObjStep, dest_filename: []const u8, options: InstallRawStep.CreateOptions) *InstallRawStep { return self.builder.installRaw(self, dest_filename, options); } /// Creates a `RunStep` with an executable built with `addExecutable`. /// Add command line arguments with `addArg`. pub fn run(exe: *LibExeObjStep) *RunStep { assert(exe.kind == .exe or exe.kind == .test_exe); // It doesn't have to be native. We catch that if you actually try to run it. // Consider that this is declarative; the run step may not be run unless a user // option is supplied. const run_step = RunStep.create(exe.builder, exe.builder.fmt("run {s}", .{exe.step.name})); run_step.addArtifactArg(exe); if (exe.kind == .test_exe) { run_step.addArg(exe.builder.zig_exe); } if (exe.vcpkg_bin_path) |path| { run_step.addPathDir(path); } return run_step; } pub fn setLinkerScriptPath(self: *LibExeObjStep, source: FileSource) void { self.linker_script = source.dupe(self.builder); source.addStepDependencies(&self.step); } pub fn linkFramework(self: *LibExeObjStep, framework_name: []const u8) void { // Note: No need to dupe because frameworks dupes internally. self.frameworks.insert(framework_name) catch unreachable; } /// Returns whether the library, executable, or object depends on a particular system library. pub fn dependsOnSystemLibrary(self: LibExeObjStep, name: []const u8) bool { if (isLibCLibrary(name)) { return self.is_linking_libc; } if (isLibCppLibrary(name)) { return self.is_linking_libcpp; } for (self.link_objects.items) |link_object| { switch (link_object) { .system_lib => |n| if (mem.eql(u8, n, name)) return true, else => continue, } } return false; } pub fn linkLibrary(self: *LibExeObjStep, lib: *LibExeObjStep) void { assert(lib.kind == .lib); self.linkLibraryOrObject(lib); } pub fn isDynamicLibrary(self: *LibExeObjStep) bool { return self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic; } pub fn producesPdbFile(self: *LibExeObjStep) bool { if (!self.target.isWindows() and !self.target.isUefi()) return false; if (self.strip) return false; return self.isDynamicLibrary() or self.kind == .exe or self.kind == .test_exe; } pub fn linkLibC(self: *LibExeObjStep) void { if (!self.is_linking_libc) { self.is_linking_libc = true; self.link_objects.append(.{ .system_lib = "c" }) catch unreachable; } } pub fn linkLibCpp(self: *LibExeObjStep) void { if (!self.is_linking_libcpp) { self.is_linking_libcpp = true; self.link_objects.append(.{ .system_lib = "c++" }) catch unreachable; } } /// If the value is omitted, it is set to 1. /// `name` and `value` need not live longer than the function call. pub fn defineCMacro(self: *LibExeObjStep, name: []const u8, value: ?[]const u8) void { const macro = constructCMacro(self.builder.allocator, name, value); self.c_macros.append(macro) catch unreachable; } /// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1. pub fn defineCMacroRaw(self: *LibExeObjStep, name_and_value: []const u8) void { self.c_macros.append(self.builder.dupe(name_and_value)) catch unreachable; } /// This one has no integration with anything, it just puts -lname on the command line. /// Prefer to use `linkSystemLibrary` instead. pub fn linkSystemLibraryName(self: *LibExeObjStep, name: []const u8) void { self.link_objects.append(.{ .system_lib = self.builder.dupe(name) }) catch unreachable; } /// This links against a system library, exclusively using pkg-config to find the library. /// Prefer to use `linkSystemLibrary` instead. pub fn linkSystemLibraryPkgConfigOnly(self: *LibExeObjStep, lib_name: []const u8) !void { const pkg_name = match: { // First we have to map the library name to pkg config name. Unfortunately, // there are several examples where this is not straightforward: // -lSDL2 -> pkg-config sdl2 // -lgdk-3 -> pkg-config gdk-3.0 // -latk-1.0 -> pkg-config atk const pkgs = try self.builder.getPkgConfigList(); // Exact match means instant winner. for (pkgs) |pkg| { if (mem.eql(u8, pkg.name, lib_name)) { break :match pkg.name; } } // Next we'll try ignoring case. for (pkgs) |pkg| { if (std.ascii.eqlIgnoreCase(pkg.name, lib_name)) { break :match pkg.name; } } // Now try appending ".0". for (pkgs) |pkg| { if (std.ascii.indexOfIgnoreCase(pkg.name, lib_name)) |pos| { if (pos != 0) continue; if (mem.eql(u8, pkg.name[lib_name.len..], ".0")) { break :match pkg.name; } } } // Trimming "-1.0". if (mem.endsWith(u8, lib_name, "-1.0")) { const trimmed_lib_name = lib_name[0 .. lib_name.len - "-1.0".len]; for (pkgs) |pkg| { if (std.ascii.eqlIgnoreCase(pkg.name, trimmed_lib_name)) { break :match pkg.name; } } } return error.PackageNotFound; }; var code: u8 = undefined; const stdout = if (self.builder.execAllowFail(&[_][]const u8{ "pkg-config", pkg_name, "--cflags", "--libs", }, &code, .Ignore)) |stdout| stdout else |err| switch (err) { error.ProcessTerminated => return error.PkgConfigCrashed, error.ExecNotSupported => return error.PkgConfigFailed, error.ExitCodeFailure => return error.PkgConfigFailed, error.FileNotFound => return error.PkgConfigNotInstalled, error.ChildExecFailed => return error.PkgConfigFailed, else => return err, }; var it = mem.tokenize(u8, stdout, " \r\n\t"); while (it.next()) |tok| { if (mem.eql(u8, tok, "-I")) { const dir = it.next() orelse return error.PkgConfigInvalidOutput; self.addIncludePath(dir); } else if (mem.startsWith(u8, tok, "-I")) { self.addIncludePath(tok["-I".len..]); } else if (mem.eql(u8, tok, "-L")) { const dir = it.next() orelse return error.PkgConfigInvalidOutput; self.addLibraryPath(dir); } else if (mem.startsWith(u8, tok, "-L")) { self.addLibraryPath(tok["-L".len..]); } else if (mem.eql(u8, tok, "-l")) { const lib = it.next() orelse return error.PkgConfigInvalidOutput; self.linkSystemLibraryName(lib); } else if (mem.startsWith(u8, tok, "-l")) { self.linkSystemLibraryName(tok["-l".len..]); } else if (mem.eql(u8, tok, "-D")) { const macro = it.next() orelse return error.PkgConfigInvalidOutput; self.defineCMacroRaw(macro); } else if (mem.startsWith(u8, tok, "-D")) { self.defineCMacroRaw(tok["-D".len..]); } else if (mem.eql(u8, tok, "-pthread")) { self.linkLibC(); } else if (self.builder.verbose) { warn("Ignoring pkg-config flag '{s}'\n", .{tok}); } } } pub fn linkSystemLibrary(self: *LibExeObjStep, name: []const u8) void { if (isLibCLibrary(name)) { self.linkLibC(); return; } if (isLibCppLibrary(name)) { self.linkLibCpp(); return; } if (self.linkSystemLibraryPkgConfigOnly(name)) |_| { // pkg-config worked, so nothing further needed to do. return; } else |err| switch (err) { error.PkgConfigInvalidOutput, error.PkgConfigCrashed, error.PkgConfigFailed, error.PkgConfigNotInstalled, error.PackageNotFound, => {}, else => unreachable, } self.linkSystemLibraryName(name); } pub fn setNamePrefix(self: *LibExeObjStep, text: []const u8) void { assert(self.kind == .@"test" or self.kind == .test_exe); self.name_prefix = self.builder.dupe(text); } pub fn setFilter(self: *LibExeObjStep, text: ?[]const u8) void { assert(self.kind == .@"test" or self.kind == .test_exe); self.filter = if (text) |t| self.builder.dupe(t) else null; } /// Handy when you have many C/C++ source files and want them all to have the same flags. pub fn addCSourceFiles(self: *LibExeObjStep, files: []const []const u8, flags: []const []const u8) void { const c_source_files = self.builder.allocator.create(CSourceFiles) catch unreachable; const files_copy = self.builder.dupeStrings(files); const flags_copy = self.builder.dupeStrings(flags); c_source_files.* = .{ .files = files_copy, .flags = flags_copy, }; self.link_objects.append(.{ .c_source_files = c_source_files }) catch unreachable; } pub fn addCSourceFile(self: *LibExeObjStep, file: []const u8, flags: []const []const u8) void { self.addCSourceFileSource(.{ .args = flags, .source = .{ .path = file }, }); } pub fn addCSourceFileSource(self: *LibExeObjStep, source: CSourceFile) void { const c_source_file = self.builder.allocator.create(CSourceFile) catch unreachable; c_source_file.* = source.dupe(self.builder); self.link_objects.append(.{ .c_source_file = c_source_file }) catch unreachable; source.source.addStepDependencies(&self.step); } pub fn setVerboseLink(self: *LibExeObjStep, value: bool) void { self.verbose_link = value; } pub fn setVerboseCC(self: *LibExeObjStep, value: bool) void { self.verbose_cc = value; } pub fn setBuildMode(self: *LibExeObjStep, mode: std.builtin.Mode) void { self.build_mode = mode; } pub fn overrideZigLibDir(self: *LibExeObjStep, dir_path: []const u8) void { self.override_lib_dir = self.builder.dupePath(dir_path); } pub fn setMainPkgPath(self: *LibExeObjStep, dir_path: []const u8) void { self.main_pkg_path = self.builder.dupePath(dir_path); } pub fn setLibCFile(self: *LibExeObjStep, libc_file: ?FileSource) void { self.libc_file = if (libc_file) |f| f.dupe(self.builder) else null; } /// Returns the generated executable, library or object file. /// To run an executable built with zig build, use `run`, or create an install step and invoke it. pub fn getOutputSource(self: *LibExeObjStep) FileSource { return FileSource{ .generated = &self.output_path_source }; } /// Returns the generated import library. This function can only be called for libraries. pub fn getOutputLibSource(self: *LibExeObjStep) FileSource { assert(self.kind == .lib); return FileSource{ .generated = &self.output_lib_path_source }; } /// Returns the generated header file. /// This function can only be called for libraries or object files which have `emit_h` set. pub fn getOutputHSource(self: *LibExeObjStep) FileSource { assert(self.kind != .exe and self.kind != .test_exe and self.kind != .@"test"); assert(self.emit_h); return FileSource{ .generated = &self.output_h_path_source }; } /// Returns the generated PDB file. This function can only be called for Windows and UEFI. pub fn getOutputPdbSource(self: *LibExeObjStep) FileSource { // TODO: Is this right? Isn't PDB for *any* PE/COFF file? assert(self.target.isWindows() or self.target.isUefi()); return FileSource{ .generated = &self.output_pdb_path_source }; } pub fn addAssemblyFile(self: *LibExeObjStep, path: []const u8) void { self.link_objects.append(.{ .assembly_file = .{ .path = self.builder.dupe(path) }, }) catch unreachable; } pub fn addAssemblyFileSource(self: *LibExeObjStep, source: FileSource) void { const source_duped = source.dupe(self.builder); self.link_objects.append(.{ .assembly_file = source_duped }) catch unreachable; source_duped.addStepDependencies(&self.step); } pub fn addObjectFile(self: *LibExeObjStep, source_file: []const u8) void { self.addObjectFileSource(.{ .path = source_file }); } pub fn addObjectFileSource(self: *LibExeObjStep, source: FileSource) void { self.link_objects.append(.{ .static_path = source.dupe(self.builder) }) catch unreachable; source.addStepDependencies(&self.step); } pub fn addObject(self: *LibExeObjStep, obj: *LibExeObjStep) void { assert(obj.kind == .obj); self.linkLibraryOrObject(obj); } /// TODO deprecated, use `addSystemIncludePath`. pub fn addSystemIncludeDir(self: *LibExeObjStep, path: []const u8) void { self.addSystemIncludePath(path); } pub fn addSystemIncludePath(self: *LibExeObjStep, path: []const u8) void { self.include_dirs.append(IncludeDir{ .raw_path_system = self.builder.dupe(path) }) catch unreachable; } /// TODO deprecated, use `addIncludePath`. pub fn addIncludeDir(self: *LibExeObjStep, path: []const u8) void { self.addIncludePath(path); } pub fn addIncludePath(self: *LibExeObjStep, path: []const u8) void { self.include_dirs.append(IncludeDir{ .raw_path = self.builder.dupe(path) }) catch unreachable; } /// TODO deprecated, use `addLibraryPath`. pub fn addLibPath(self: *LibExeObjStep, path: []const u8) void { self.addLibraryPath(path); } pub fn addLibraryPath(self: *LibExeObjStep, path: []const u8) void { self.lib_paths.append(self.builder.dupe(path)) catch unreachable; } pub fn addRPath(self: *LibExeObjStep, path: []const u8) void { self.rpaths.append(self.builder.dupe(path)) catch unreachable; } /// TODO deprecated, use `addFrameworkPath`. pub fn addFrameworkDir(self: *LibExeObjStep, dir_path: []const u8) void { self.addFrameworkPath(dir_path); } pub fn addFrameworkPath(self: *LibExeObjStep, dir_path: []const u8) void { self.framework_dirs.append(self.builder.dupe(dir_path)) catch unreachable; } pub fn addPackage(self: *LibExeObjStep, package: Pkg) void { self.packages.append(self.builder.dupePkg(package)) catch unreachable; self.addRecursiveBuildDeps(package); } pub fn addOptions(self: *LibExeObjStep, package_name: []const u8, options: *OptionsStep) void { self.addPackage(options.getPackage(package_name)); } fn addRecursiveBuildDeps(self: *LibExeObjStep, package: Pkg) void { package.path.addStepDependencies(&self.step); if (package.dependencies) |deps| { for (deps) |dep| { self.addRecursiveBuildDeps(dep); } } } pub fn addPackagePath(self: *LibExeObjStep, name: []const u8, pkg_index_path: []const u8) void { self.addPackage(Pkg{ .name = self.builder.dupe(name), .path = .{ .path = self.builder.dupe(pkg_index_path) }, }); } /// If Vcpkg was found on the system, it will be added to include and lib /// paths for the specified target. pub fn addVcpkgPaths(self: *LibExeObjStep, linkage: LibExeObjStep.Linkage) !void { // Ideally in the Unattempted case we would call the function recursively // after findVcpkgRoot and have only one switch statement, but the compiler // cannot resolve the error set. switch (self.builder.vcpkg_root) { .unattempted => { self.builder.vcpkg_root = if (try findVcpkgRoot(self.builder.allocator)) |root| VcpkgRoot{ .found = root } else .not_found; }, .not_found => return error.VcpkgNotFound, .found => {}, } switch (self.builder.vcpkg_root) { .unattempted => unreachable, .not_found => return error.VcpkgNotFound, .found => |root| { const allocator = self.builder.allocator; const triplet = try self.target.vcpkgTriplet(allocator, if (linkage == .static) .Static else .Dynamic); defer self.builder.allocator.free(triplet); const include_path = self.builder.pathJoin(&.{ root, "installed", triplet, "include" }); errdefer allocator.free(include_path); try self.include_dirs.append(IncludeDir{ .raw_path = include_path }); const lib_path = self.builder.pathJoin(&.{ root, "installed", triplet, "lib" }); try self.lib_paths.append(lib_path); self.vcpkg_bin_path = self.builder.pathJoin(&.{ root, "installed", triplet, "bin" }); }, } } pub fn setExecCmd(self: *LibExeObjStep, args: []const ?[]const u8) void { assert(self.kind == .@"test"); const duped_args = self.builder.allocator.alloc(?[]u8, args.len) catch unreachable; for (args) |arg, i| { duped_args[i] = if (arg) |a| self.builder.dupe(a) else null; } self.exec_cmd_args = duped_args; } fn linkLibraryOrObject(self: *LibExeObjStep, other: *LibExeObjStep) void { self.step.dependOn(&other.step); self.link_objects.append(.{ .other_step = other }) catch unreachable; self.include_dirs.append(.{ .other_step = other }) catch unreachable; } fn makePackageCmd(self: *LibExeObjStep, pkg: Pkg, zig_args: *ArrayList([]const u8)) error{OutOfMemory}!void { const builder = self.builder; try zig_args.append("--pkg-begin"); try zig_args.append(pkg.name); try zig_args.append(builder.pathFromRoot(pkg.path.getPath(self.builder))); if (pkg.dependencies) |dependencies| { for (dependencies) |sub_pkg| { try self.makePackageCmd(sub_pkg, zig_args); } } try zig_args.append("--pkg-end"); } fn make(step: *Step) !void { const self = @fieldParentPtr(LibExeObjStep, "step", step); const builder = self.builder; if (self.root_src == null and self.link_objects.items.len == 0) { warn("{s}: linker needs 1 or more objects to link\n", .{self.step.name}); return error.NeedAnObject; } var zig_args = ArrayList([]const u8).init(builder.allocator); defer zig_args.deinit(); zig_args.append(builder.zig_exe) catch unreachable; const cmd = switch (self.kind) { .lib => "build-lib", .exe => "build-exe", .obj => "build-obj", .@"test" => "test", .test_exe => "test", }; zig_args.append(cmd) catch unreachable; if (builder.color != .auto) { try zig_args.append("--color"); try zig_args.append(@tagName(builder.color)); } if (self.entry_symbol_name) |entry| { try zig_args.append("--entry"); try zig_args.append(entry); } if (self.stack_size) |stack_size| { try zig_args.append("--stack"); try zig_args.append(try std.fmt.allocPrint(builder.allocator, "{}", .{stack_size})); } if (self.root_src) |root_src| try zig_args.append(root_src.getPath(builder)); var prev_has_extra_flags = false; // Resolve transitive dependencies for (self.link_objects.items) |link_object| { switch (link_object) { .other_step => |other| { // Inherit dependency on system libraries for (other.link_objects.items) |other_link_object| { switch (other_link_object) { .system_lib => |name| self.linkSystemLibrary(name), else => continue, } } // Inherit dependencies on darwin frameworks if (!other.isDynamicLibrary()) { var it = other.frameworks.iterator(); while (it.next()) |framework| { self.frameworks.insert(framework.*) catch unreachable; } } }, else => continue, } } for (self.link_objects.items) |link_object| { switch (link_object) { .static_path => |static_path| try zig_args.append(static_path.getPath(builder)), .other_step => |other| switch (other.kind) { .exe => @panic("Cannot link with an executable build artifact"), .test_exe => @panic("Cannot link with an executable build artifact"), .@"test" => @panic("Cannot link with a test"), .obj => { try zig_args.append(other.getOutputSource().getPath(builder)); }, .lib => { const full_path_lib = other.getOutputLibSource().getPath(builder); try zig_args.append(full_path_lib); if (other.linkage != null and other.linkage.? == .dynamic and !self.target.isWindows()) { if (fs.path.dirname(full_path_lib)) |dirname| { try zig_args.append("-rpath"); try zig_args.append(dirname); } } }, }, .system_lib => |name| { try zig_args.append(builder.fmt("-l{s}", .{name})); }, .assembly_file => |asm_file| { if (prev_has_extra_flags) { try zig_args.append("-extra-cflags"); try zig_args.append("--"); prev_has_extra_flags = false; } try zig_args.append(asm_file.getPath(builder)); }, .c_source_file => |c_source_file| { if (c_source_file.args.len == 0) { if (prev_has_extra_flags) { try zig_args.append("-cflags"); try zig_args.append("--"); prev_has_extra_flags = false; } } else { try zig_args.append("-cflags"); for (c_source_file.args) |arg| { try zig_args.append(arg); } try zig_args.append("--"); } try zig_args.append(c_source_file.source.getPath(builder)); }, .c_source_files => |c_source_files| { if (c_source_files.flags.len == 0) { if (prev_has_extra_flags) { try zig_args.append("-cflags"); try zig_args.append("--"); prev_has_extra_flags = false; } } else { try zig_args.append("-cflags"); for (c_source_files.flags) |flag| { try zig_args.append(flag); } try zig_args.append("--"); } for (c_source_files.files) |file| { try zig_args.append(builder.pathFromRoot(file)); } }, } } if (self.image_base) |image_base| { try zig_args.append("--image-base"); try zig_args.append(builder.fmt("0x{x}", .{image_base})); } if (self.filter) |filter| { try zig_args.append("--test-filter"); try zig_args.append(filter); } if (self.test_evented_io) { try zig_args.append("--test-evented-io"); } if (self.name_prefix.len != 0) { try zig_args.append("--test-name-prefix"); try zig_args.append(self.name_prefix); } for (builder.debug_log_scopes) |log_scope| { try zig_args.append("--debug-log"); try zig_args.append(log_scope); } if (builder.verbose_cimport) zig_args.append("--verbose-cimport") catch unreachable; if (builder.verbose_air) zig_args.append("--verbose-air") catch unreachable; if (builder.verbose_llvm_ir) zig_args.append("--verbose-llvm-ir") catch unreachable; if (builder.verbose_link or self.verbose_link) zig_args.append("--verbose-link") catch unreachable; if (builder.verbose_cc or self.verbose_cc) zig_args.append("--verbose-cc") catch unreachable; if (builder.verbose_llvm_cpu_features) zig_args.append("--verbose-llvm-cpu-features") catch unreachable; if (self.emit_analysis.getArg(builder, "emit-analysis")) |arg| try zig_args.append(arg); if (self.emit_asm.getArg(builder, "emit-asm")) |arg| try zig_args.append(arg); if (self.emit_bin.getArg(builder, "emit-bin")) |arg| try zig_args.append(arg); if (self.emit_docs.getArg(builder, "emit-docs")) |arg| try zig_args.append(arg); if (self.emit_implib.getArg(builder, "emit-implib")) |arg| try zig_args.append(arg); if (self.emit_llvm_bc.getArg(builder, "emit-llvm-bc")) |arg| try zig_args.append(arg); if (self.emit_llvm_ir.getArg(builder, "emit-llvm-ir")) |arg| try zig_args.append(arg); if (self.emit_h) try zig_args.append("-femit-h"); if (self.strip) { try zig_args.append("--strip"); } if (self.link_eh_frame_hdr) { try zig_args.append("--eh-frame-hdr"); } if (self.link_emit_relocs) { try zig_args.append("--emit-relocs"); } if (self.link_function_sections) { try zig_args.append("-ffunction-sections"); } if (self.linker_allow_shlib_undefined) |x| { try zig_args.append(if (x) "-fallow-shlib-undefined" else "-fno-allow-shlib-undefined"); } if (self.link_z_notext) { try zig_args.append("-z"); try zig_args.append("notext"); } if (self.libc_file) |libc_file| { try zig_args.append("--libc"); try zig_args.append(libc_file.getPath(self.builder)); } else if (builder.libc_file) |libc_file| { try zig_args.append("--libc"); try zig_args.append(libc_file); } switch (self.build_mode) { .Debug => {}, // Skip since it's the default. else => zig_args.append(builder.fmt("-O{s}", .{@tagName(self.build_mode)})) catch unreachable, } try zig_args.append("--cache-dir"); try zig_args.append(builder.pathFromRoot(builder.cache_root)); try zig_args.append("--global-cache-dir"); try zig_args.append(builder.pathFromRoot(builder.global_cache_root)); zig_args.append("--name") catch unreachable; zig_args.append(self.name) catch unreachable; if (self.linkage) |some| switch (some) { .dynamic => try zig_args.append("-dynamic"), .static => try zig_args.append("-static"), }; if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic) { if (self.version) |version| { zig_args.append("--version") catch unreachable; zig_args.append(builder.fmt("{}", .{version})) catch unreachable; } if (self.target.isDarwin()) { const install_name = self.install_name orelse builder.fmt("@rpath/{s}{s}{s}", .{ self.target.libPrefix(), self.name, self.target.dynamicLibSuffix(), }); try zig_args.append("-install_name"); try zig_args.append(install_name); } } if (self.entitlements) |entitlements| { try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements }); } if (self.bundle_compiler_rt) |x| { if (x) { try zig_args.append("-fcompiler-rt"); } else { try zig_args.append("-fno-compiler-rt"); } } if (self.single_threaded) |single_threaded| { if (single_threaded) { try zig_args.append("-fsingle-threaded"); } else { try zig_args.append("-fno-single-threaded"); } } if (self.disable_stack_probing) { try zig_args.append("-fno-stack-check"); } if (self.red_zone) |red_zone| { if (red_zone) { try zig_args.append("-mred-zone"); } else { try zig_args.append("-mno-red-zone"); } } if (self.omit_frame_pointer) |omit_frame_pointer| { if (omit_frame_pointer) { try zig_args.append("-fomit-frame-pointer"); } else { try zig_args.append("-fno-omit-frame-pointer"); } } if (self.disable_sanitize_c) { try zig_args.append("-fno-sanitize-c"); } if (self.sanitize_thread) { try zig_args.append("-fsanitize-thread"); } if (self.rdynamic) { try zig_args.append("-rdynamic"); } if (self.import_memory) { try zig_args.append("--import-memory"); } if (self.import_table) { try zig_args.append("--import-table"); } if (self.export_table) { try zig_args.append("--export-table"); } if (self.initial_memory) |initial_memory| { try zig_args.append(builder.fmt("--initial-memory={d}", .{initial_memory})); } if (self.max_memory) |max_memory| { try zig_args.append(builder.fmt("--max-memory={d}", .{max_memory})); } if (self.shared_memory) { try zig_args.append("--shared-memory"); } if (self.global_base) |global_base| { try zig_args.append(builder.fmt("--global-base={d}", .{global_base})); } if (self.code_model != .default) { try zig_args.append("-mcmodel"); try zig_args.append(@tagName(self.code_model)); } if (self.wasi_exec_model) |model| { try zig_args.append(builder.fmt("-mexec-model={s}", .{@tagName(model)})); } for (self.export_symbol_names) |symbol_name| { try zig_args.append(builder.fmt("--export={s}", .{symbol_name})); } if (!self.target.isNative()) { try zig_args.append("-target"); try zig_args.append(try self.target.zigTriple(builder.allocator)); // TODO this logic can disappear if cpu model + features becomes part of the target triple const cross = self.target.toTarget(); const all_features = cross.cpu.arch.allFeaturesList(); var populated_cpu_features = cross.cpu.model.features; populated_cpu_features.populateDependencies(all_features); if (populated_cpu_features.eql(cross.cpu.features)) { // The CPU name alone is sufficient. try zig_args.append("-mcpu"); try zig_args.append(cross.cpu.model.name); } else { var mcpu_buffer = std.ArrayList(u8).init(builder.allocator); try mcpu_buffer.writer().print("-mcpu={s}", .{cross.cpu.model.name}); for (all_features) |feature, i_usize| { const i = @intCast(std.Target.Cpu.Feature.Set.Index, i_usize); const in_cpu_set = populated_cpu_features.isEnabled(i); const in_actual_set = cross.cpu.features.isEnabled(i); if (in_cpu_set and !in_actual_set) { try mcpu_buffer.writer().print("-{s}", .{feature.name}); } else if (!in_cpu_set and in_actual_set) { try mcpu_buffer.writer().print("+{s}", .{feature.name}); } } try zig_args.append(mcpu_buffer.toOwnedSlice()); } if (self.target.dynamic_linker.get()) |dynamic_linker| { try zig_args.append("--dynamic-linker"); try zig_args.append(dynamic_linker); } } if (self.linker_script) |linker_script| { try zig_args.append("--script"); try zig_args.append(linker_script.getPath(builder)); } if (self.version_script) |version_script| { try zig_args.append("--version-script"); try zig_args.append(builder.pathFromRoot(version_script)); } if (self.kind == .@"test") { if (self.exec_cmd_args) |exec_cmd_args| { for (exec_cmd_args) |cmd_arg| { if (cmd_arg) |arg| { try zig_args.append("--test-cmd"); try zig_args.append(arg); } else { try zig_args.append("--test-cmd-bin"); } } } else { const need_cross_glibc = self.target.isGnuLibC() and self.is_linking_libc; switch (self.builder.host.getExternalExecutor(self.target_info, .{ .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null, .link_libc = self.is_linking_libc, })) { .native => {}, .bad_dl, .bad_os_or_cpu => { try zig_args.append("--test-no-exec"); }, .rosetta => if (builder.enable_rosetta) { try zig_args.append("--test-cmd-bin"); } else { try zig_args.append("--test-no-exec"); }, .qemu => |bin_name| ok: { if (builder.enable_qemu) qemu: { const glibc_dir_arg = if (need_cross_glibc) builder.glibc_runtimes_dir orelse break :qemu else null; try zig_args.append("--test-cmd"); try zig_args.append(bin_name); if (glibc_dir_arg) |dir| { // TODO look into making this a call to `linuxTriple`. This // needs the directory to be called "i686" rather than // "i386" which is why we do it manually here. const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}"; const cpu_arch = self.target.getCpuArch(); const os_tag = self.target.getOsTag(); const abi = self.target.getAbi(); const cpu_arch_name: []const u8 = if (cpu_arch == .i386) "i686" else @tagName(cpu_arch); const full_dir = try std.fmt.allocPrint(builder.allocator, fmt_str, .{ dir, cpu_arch_name, @tagName(os_tag), @tagName(abi), }); try zig_args.append("--test-cmd"); try zig_args.append("-L"); try zig_args.append("--test-cmd"); try zig_args.append(full_dir); } try zig_args.append("--test-cmd-bin"); break :ok; } try zig_args.append("--test-no-exec"); }, .wine => |bin_name| if (builder.enable_wine) { try zig_args.append("--test-cmd"); try zig_args.append(bin_name); try zig_args.append("--test-cmd-bin"); } else { try zig_args.append("--test-no-exec"); }, .wasmtime => |bin_name| if (builder.enable_wasmtime) { try zig_args.append("--test-cmd"); try zig_args.append(bin_name); try zig_args.append("--test-cmd"); try zig_args.append("--dir=."); try zig_args.append("--test-cmd"); try zig_args.append("--allow-unknown-exports"); // TODO: Remove when stage2 is default compiler try zig_args.append("--test-cmd-bin"); } else { try zig_args.append("--test-no-exec"); }, .darling => |bin_name| if (builder.enable_darling) { try zig_args.append("--test-cmd"); try zig_args.append(bin_name); try zig_args.append("--test-cmd-bin"); } else { try zig_args.append("--test-no-exec"); }, } } } else if (self.kind == .test_exe) { try zig_args.append("--test-no-exec"); } for (self.packages.items) |pkg| { try self.makePackageCmd(pkg, &zig_args); } for (self.include_dirs.items) |include_dir| { switch (include_dir) { .raw_path => |include_path| { try zig_args.append("-I"); try zig_args.append(self.builder.pathFromRoot(include_path)); }, .raw_path_system => |include_path| { if (builder.sysroot != null) { try zig_args.append("-iwithsysroot"); } else { try zig_args.append("-isystem"); } const resolved_include_path = self.builder.pathFromRoot(include_path); const common_include_path = if (builtin.os.tag == .windows and builder.sysroot != null and fs.path.isAbsolute(resolved_include_path)) blk: { // We need to check for disk designator and strip it out from dir path so // that zig/clang can concat resolved_include_path with sysroot. const disk_designator = fs.path.diskDesignatorWindows(resolved_include_path); if (mem.indexOf(u8, resolved_include_path, disk_designator)) |where| { break :blk resolved_include_path[where + disk_designator.len ..]; } break :blk resolved_include_path; } else resolved_include_path; try zig_args.append(common_include_path); }, .other_step => |other| if (other.emit_h) { const h_path = other.getOutputHSource().getPath(self.builder); try zig_args.append("-isystem"); try zig_args.append(fs.path.dirname(h_path).?); }, } } for (self.lib_paths.items) |lib_path| { try zig_args.append("-L"); try zig_args.append(lib_path); } for (self.rpaths.items) |rpath| { try zig_args.append("-rpath"); try zig_args.append(rpath); } for (self.c_macros.items) |c_macro| { try zig_args.append("-D"); try zig_args.append(c_macro); } if (self.target.isDarwin()) { for (self.framework_dirs.items) |dir| { if (builder.sysroot != null) { try zig_args.append("-iframeworkwithsysroot"); } else { try zig_args.append("-iframework"); } try zig_args.append(dir); try zig_args.append("-F"); try zig_args.append(dir); } var it = self.frameworks.iterator(); while (it.next()) |framework| { zig_args.append("-framework") catch unreachable; zig_args.append(framework.*) catch unreachable; } } else { if (self.framework_dirs.items.len > 0) { warn("Framework directories have been added for a non-darwin target, this will have no affect on the build\n", .{}); } if (self.frameworks.count() > 0) { warn("Frameworks have been added for a non-darwin target, this will have no affect on the build\n", .{}); } } if (builder.sysroot) |sysroot| { try zig_args.appendSlice(&[_][]const u8{ "--sysroot", sysroot }); } for (builder.search_prefixes.items) |search_prefix| { try zig_args.append("-L"); try zig_args.append(builder.pathJoin(&.{ search_prefix, "lib", })); try zig_args.append("-isystem"); try zig_args.append(builder.pathJoin(&.{ search_prefix, "include", })); } if (self.valgrind_support) |valgrind_support| { if (valgrind_support) { try zig_args.append("-fvalgrind"); } else { try zig_args.append("-fno-valgrind"); } } if (self.each_lib_rpath) |each_lib_rpath| { if (each_lib_rpath) { try zig_args.append("-feach-lib-rpath"); } else { try zig_args.append("-fno-each-lib-rpath"); } } if (self.override_lib_dir) |dir| { try zig_args.append("--zig-lib-dir"); try zig_args.append(builder.pathFromRoot(dir)); } else if (self.builder.override_lib_dir) |dir| { try zig_args.append("--zig-lib-dir"); try zig_args.append(builder.pathFromRoot(dir)); } if (self.main_pkg_path) |dir| { try zig_args.append("--main-pkg-path"); try zig_args.append(builder.pathFromRoot(dir)); } if (self.force_pic) |pic| { if (pic) { try zig_args.append("-fPIC"); } else { try zig_args.append("-fno-PIC"); } } if (self.pie) |pie| { if (pie) { try zig_args.append("-fPIE"); } else { try zig_args.append("-fno-PIE"); } } if (self.want_lto) |lto| { if (lto) { try zig_args.append("-flto"); } else { try zig_args.append("-fno-lto"); } } if (self.subsystem) |subsystem| { try zig_args.append("--subsystem"); try zig_args.append(switch (subsystem) { .Console => "console", .Windows => "windows", .Posix => "posix", .Native => "native", .EfiApplication => "efi_application", .EfiBootServiceDriver => "efi_boot_service_driver", .EfiRom => "efi_rom", .EfiRuntimeDriver => "efi_runtime_driver", }); } try zig_args.append("--enable-cache"); // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux // 2,097,152. If our args exceed 30 KiB, we instead write them to a "response file" and // pass that to zig, e.g. via 'zig build-lib @args.rsp' var args_length: usize = 0; for (zig_args.items) |arg| { args_length += arg.len + 1; // +1 to account for null terminator } if (args_length >= 30 * 1024) { const args_dir = try fs.path.join( builder.allocator, &[_][]const u8{ builder.pathFromRoot("zig-cache"), "args" }, ); try std.fs.cwd().makePath(args_dir); // Write the args to zig-cache/args/ to avoid conflicts with // other zig build commands running in parallel. const partially_quoted = try std.mem.join(builder.allocator, "\" \"", zig_args.items[2..]); const args = try std.mem.concat(builder.allocator, u8, &[_][]const u8{ "\"", partially_quoted, "\"" }); var args_hash: [Sha256.digest_length]u8 = undefined; Sha256.hash(args, &args_hash, .{}); var args_hex_hash: [Sha256.digest_length * 2]u8 = undefined; _ = try std.fmt.bufPrint( &args_hex_hash, "{s}", .{std.fmt.fmtSliceHexLower(&args_hash)}, ); const args_file = try fs.path.join(builder.allocator, &[_][]const u8{ args_dir, args_hex_hash[0..] }); try std.fs.cwd().writeFile(args_file, args); zig_args.shrinkRetainingCapacity(2); try zig_args.append(try std.mem.concat(builder.allocator, u8, &[_][]const u8{ "@", args_file })); } const output_dir_nl = try builder.execFromStep(zig_args.items, &self.step); const build_output_dir = mem.trimRight(u8, output_dir_nl, "\r\n"); if (self.output_dir) |output_dir| { var src_dir = try std.fs.cwd().openDir(build_output_dir, .{ .iterate = true }); defer src_dir.close(); // Create the output directory if it doesn't exist. try std.fs.cwd().makePath(output_dir); var dest_dir = try std.fs.cwd().openDir(output_dir, .{}); defer dest_dir.close(); var it = src_dir.iterate(); while (try it.next()) |entry| { // The compiler can put these files into the same directory, but we don't // want to copy them over. if (mem.eql(u8, entry.name, "stage1.id") or mem.eql(u8, entry.name, "llvm-ar.id") or mem.eql(u8, entry.name, "libs.txt") or mem.eql(u8, entry.name, "builtin.zig") or mem.eql(u8, entry.name, "zld.id") or mem.eql(u8, entry.name, "lld.id")) continue; _ = try src_dir.updateFile(entry.name, dest_dir, entry.name, .{}); } } else { self.output_dir = build_output_dir; } // This will ensure all output filenames will now have the output_dir available! self.computeOutFileNames(); // Update generated files if (self.output_dir != null) { self.output_path_source.path = builder.pathJoin( &.{ self.output_dir.?, self.out_filename }, ); if (self.emit_h) { self.output_h_path_source.path = builder.pathJoin( &.{ self.output_dir.?, self.out_h_filename }, ); } if (self.target.isWindows() or self.target.isUefi()) { self.output_pdb_path_source.path = builder.pathJoin( &.{ self.output_dir.?, self.out_pdb_filename }, ); } } if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and self.version != null and self.target.wantSharedLibSymLinks()) { try doAtomicSymLinks(builder.allocator, self.getOutputSource().getPath(builder), self.major_only_filename.?, self.name_only_filename.?); } } }; /// Allocates a new string for assigning a value to a named macro. /// If the value is omitted, it is set to 1. /// `name` and `value` need not live longer than the function call. pub fn constructCMacro(allocator: Allocator, name: []const u8, value: ?[]const u8) []const u8 { var macro = allocator.alloc( u8, name.len + if (value) |value_slice| value_slice.len + 1 else 0, ) catch |err| if (err == error.OutOfMemory) @panic("Out of memory") else unreachable; mem.copy(u8, macro, name); if (value) |value_slice| { macro[name.len] = '='; mem.copy(u8, macro[name.len + 1 ..], value_slice); } return macro; } pub const InstallArtifactStep = struct { pub const base_id = .install_artifact; step: Step, builder: *Builder, artifact: *LibExeObjStep, dest_dir: InstallDir, pdb_dir: ?InstallDir, h_dir: ?InstallDir, const Self = @This(); pub fn create(builder: *Builder, artifact: *LibExeObjStep) *Self { if (artifact.install_step) |s| return s; const self = builder.allocator.create(Self) catch unreachable; self.* = Self{ .builder = builder, .step = Step.init(.install_artifact, builder.fmt("install {s}", .{artifact.step.name}), builder.allocator, make), .artifact = artifact, .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) { .obj => @panic("Cannot install a .obj build artifact."), .@"test" => @panic("Cannot install a test build artifact, use addTestExe instead."), .exe, .test_exe => InstallDir{ .bin = {} }, .lib => InstallDir{ .lib = {} }, }, .pdb_dir = if (artifact.producesPdbFile()) blk: { if (artifact.kind == .exe or artifact.kind == .test_exe) { break :blk InstallDir{ .bin = {} }; } else { break :blk InstallDir{ .lib = {} }; } } else null, .h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null, }; self.step.dependOn(&artifact.step); artifact.install_step = self; builder.pushInstalledFile(self.dest_dir, artifact.out_filename); if (self.artifact.isDynamicLibrary()) { if (artifact.major_only_filename) |name| { builder.pushInstalledFile(.lib, name); } if (artifact.name_only_filename) |name| { builder.pushInstalledFile(.lib, name); } if (self.artifact.target.isWindows()) { builder.pushInstalledFile(.lib, artifact.out_lib_filename); } } if (self.pdb_dir) |pdb_dir| { builder.pushInstalledFile(pdb_dir, artifact.out_pdb_filename); } if (self.h_dir) |h_dir| { builder.pushInstalledFile(h_dir, artifact.out_h_filename); } return self; } fn make(step: *Step) !void { const self = @fieldParentPtr(Self, "step", step); const builder = self.builder; const full_dest_path = builder.getInstallPath(self.dest_dir, self.artifact.out_filename); try builder.updateFile(self.artifact.getOutputSource().getPath(builder), full_dest_path); if (self.artifact.isDynamicLibrary() and self.artifact.version != null and self.artifact.target.wantSharedLibSymLinks()) { try doAtomicSymLinks(builder.allocator, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?); } if (self.artifact.isDynamicLibrary() and self.artifact.target.isWindows() and self.artifact.emit_implib != .no_emit) { const full_implib_path = builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename); try builder.updateFile(self.artifact.getOutputLibSource().getPath(builder), full_implib_path); } if (self.pdb_dir) |pdb_dir| { const full_pdb_path = builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename); try builder.updateFile(self.artifact.getOutputPdbSource().getPath(builder), full_pdb_path); } if (self.h_dir) |h_dir| { const full_pdb_path = builder.getInstallPath(h_dir, self.artifact.out_h_filename); try builder.updateFile(self.artifact.getOutputHSource().getPath(builder), full_pdb_path); } self.artifact.installed_path = full_dest_path; } }; pub const InstallFileStep = struct { pub const base_id = .install_file; step: Step, builder: *Builder, source: FileSource, dir: InstallDir, dest_rel_path: []const u8, pub fn init( builder: *Builder, source: FileSource, dir: InstallDir, dest_rel_path: []const u8, ) InstallFileStep { builder.pushInstalledFile(dir, dest_rel_path); return InstallFileStep{ .builder = builder, .step = Step.init(.install_file, builder.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), builder.allocator, make), .source = source.dupe(builder), .dir = dir.dupe(builder), .dest_rel_path = builder.dupePath(dest_rel_path), }; } fn make(step: *Step) !void { const self = @fieldParentPtr(InstallFileStep, "step", step); const full_dest_path = self.builder.getInstallPath(self.dir, self.dest_rel_path); const full_src_path = self.source.getPath(self.builder); try self.builder.updateFile(full_src_path, full_dest_path); } }; pub const InstallDirectoryOptions = struct { source_dir: []const u8, install_dir: InstallDir, install_subdir: []const u8, /// File paths which end in any of these suffixes will be excluded /// from being installed. exclude_extensions: []const []const u8 = &.{}, /// File paths which end in any of these suffixes will result in /// empty files being installed. This is mainly intended for large /// test.zig files in order to prevent needless installation bloat. /// However if the files were not present at all, then /// `@import("test.zig")` would be a compile error. blank_extensions: []const []const u8 = &.{}, fn dupe(self: InstallDirectoryOptions, b: *Builder) InstallDirectoryOptions { return .{ .source_dir = b.dupe(self.source_dir), .install_dir = self.install_dir.dupe(b), .install_subdir = b.dupe(self.install_subdir), .exclude_extensions = b.dupeStrings(self.exclude_extensions), .blank_extensions = b.dupeStrings(self.blank_extensions), }; } }; pub const InstallDirStep = struct { pub const base_id = .install_dir; step: Step, builder: *Builder, options: InstallDirectoryOptions, pub fn init( builder: *Builder, options: InstallDirectoryOptions, ) InstallDirStep { builder.pushInstalledFile(options.install_dir, options.install_subdir); return InstallDirStep{ .builder = builder, .step = Step.init(.install_dir, builder.fmt("install {s}/", .{options.source_dir}), builder.allocator, make), .options = options.dupe(builder), }; } fn make(step: *Step) !void { const self = @fieldParentPtr(InstallDirStep, "step", step); const dest_prefix = self.builder.getInstallPath(self.options.install_dir, self.options.install_subdir); const full_src_dir = self.builder.pathFromRoot(self.options.source_dir); var src_dir = try std.fs.cwd().openDir(full_src_dir, .{ .iterate = true }); defer src_dir.close(); var it = try src_dir.walk(self.builder.allocator); next_entry: while (try it.next()) |entry| { for (self.options.exclude_extensions) |ext| { if (mem.endsWith(u8, entry.path, ext)) { continue :next_entry; } } const full_path = self.builder.pathJoin(&.{ full_src_dir, entry.path, }); const dest_path = self.builder.pathJoin(&.{ dest_prefix, entry.path, }); switch (entry.kind) { .Directory => try fs.cwd().makePath(dest_path), .File => { for (self.options.blank_extensions) |ext| { if (mem.endsWith(u8, entry.path, ext)) { try self.builder.truncateFile(dest_path); continue :next_entry; } } try self.builder.updateFile(full_path, dest_path); }, else => continue, } } } }; pub const LogStep = struct { pub const base_id = .log; step: Step, builder: *Builder, data: []const u8, pub fn init(builder: *Builder, data: []const u8) LogStep { return LogStep{ .builder = builder, .step = Step.init(.log, builder.fmt("log {s}", .{data}), builder.allocator, make), .data = builder.dupe(data), }; } fn make(step: *Step) anyerror!void { const self = @fieldParentPtr(LogStep, "step", step); warn("{s}", .{self.data}); } }; pub const RemoveDirStep = struct { pub const base_id = .remove_dir; step: Step, builder: *Builder, dir_path: []const u8, pub fn init(builder: *Builder, dir_path: []const u8) RemoveDirStep { return RemoveDirStep{ .builder = builder, .step = Step.init(.remove_dir, builder.fmt("RemoveDir {s}", .{dir_path}), builder.allocator, make), .dir_path = builder.dupePath(dir_path), }; } fn make(step: *Step) !void { const self = @fieldParentPtr(RemoveDirStep, "step", step); const full_path = self.builder.pathFromRoot(self.dir_path); fs.cwd().deleteTree(full_path) catch |err| { warn("Unable to remove {s}: {s}\n", .{ full_path, @errorName(err) }); return err; }; } }; const ThisModule = @This(); pub const Step = struct { id: Id, name: []const u8, makeFn: MakeFn, dependencies: ArrayList(*Step), loop_flag: bool, done_flag: bool, const MakeFn = switch (builtin.zig_backend) { .stage1 => fn (self: *Step) anyerror!void, else => *const fn (self: *Step) anyerror!void, }; pub const Id = enum { top_level, lib_exe_obj, install_artifact, install_file, install_dir, log, remove_dir, fmt, translate_c, write_file, run, check_file, install_raw, options, custom, }; pub fn init(id: Id, name: []const u8, allocator: Allocator, makeFn: MakeFn) Step { return Step{ .id = id, .name = allocator.dupe(u8, name) catch unreachable, .makeFn = makeFn, .dependencies = ArrayList(*Step).init(allocator), .loop_flag = false, .done_flag = false, }; } pub fn initNoOp(id: Id, name: []const u8, allocator: Allocator) Step { return init(id, name, allocator, makeNoOp); } pub fn make(self: *Step) !void { if (self.done_flag) return; try self.makeFn(self); self.done_flag = true; } pub fn dependOn(self: *Step, other: *Step) void { self.dependencies.append(other) catch unreachable; } fn makeNoOp(self: *Step) anyerror!void { _ = self; } pub fn cast(step: *Step, comptime T: type) ?*T { if (step.id == T.base_id) { return @fieldParentPtr(T, "step", step); } return null; } }; fn doAtomicSymLinks(allocator: Allocator, output_path: []const u8, filename_major_only: []const u8, filename_name_only: []const u8) !void { const out_dir = fs.path.dirname(output_path) orelse "."; const out_basename = fs.path.basename(output_path); // sym link for libfoo.so.1 to libfoo.so.1.2.3 const major_only_path = fs.path.join( allocator, &[_][]const u8{ out_dir, filename_major_only }, ) catch unreachable; fs.atomicSymLink(allocator, out_basename, major_only_path) catch |err| { warn("Unable to symlink {s} -> {s}\n", .{ major_only_path, out_basename }); return err; }; // sym link for libfoo.so to libfoo.so.1 const name_only_path = fs.path.join( allocator, &[_][]const u8{ out_dir, filename_name_only }, ) catch unreachable; fs.atomicSymLink(allocator, filename_major_only, name_only_path) catch |err| { warn("Unable to symlink {s} -> {s}\n", .{ name_only_path, filename_major_only }); return err; }; } /// Returned slice must be freed by the caller. fn findVcpkgRoot(allocator: Allocator) !?[]const u8 { const appdata_path = try fs.getAppDataDir(allocator, "vcpkg"); defer allocator.free(appdata_path); const path_file = try fs.path.join(allocator, &[_][]const u8{ appdata_path, "vcpkg.path.txt" }); defer allocator.free(path_file); const file = fs.cwd().openFile(path_file, .{}) catch return null; defer file.close(); const size = @intCast(usize, try file.getEndPos()); const vcpkg_path = try allocator.alloc(u8, size); const size_read = try file.read(vcpkg_path); std.debug.assert(size == size_read); return vcpkg_path; } const VcpkgRoot = union(VcpkgRootStatus) { unattempted: void, not_found: void, found: []const u8, }; const VcpkgRootStatus = enum { unattempted, not_found, found, }; pub const InstallDir = union(enum) { prefix: void, lib: void, bin: void, header: void, /// A path relative to the prefix custom: []const u8, /// Duplicates the install directory including the path if set to custom. pub fn dupe(self: InstallDir, builder: *Builder) InstallDir { if (self == .custom) { // Written with this temporary to avoid RLS problems const duped_path = builder.dupe(self.custom); return .{ .custom = duped_path }; } else { return self; } } }; pub const InstalledFile = struct { dir: InstallDir, path: []const u8, /// Duplicates the installed file path and directory. pub fn dupe(self: InstalledFile, builder: *Builder) InstalledFile { return .{ .dir = self.dir.dupe(builder), .path = builder.dupe(self.path), }; } }; test "Builder.dupePkg()" { if (builtin.os.tag == .wasi) return error.SkipZigTest; var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); var builder = try Builder.create( arena.allocator(), "test", "test", "test", "test", ); defer builder.destroy(); var pkg_dep = Pkg{ .name = "pkg_dep", .path = .{ .path = "/not/a/pkg_dep.zig" }, }; var pkg_top = Pkg{ .name = "pkg_top", .path = .{ .path = "/not/a/pkg_top.zig" }, .dependencies = &[_]Pkg{pkg_dep}, }; const dupe = builder.dupePkg(pkg_top); const original_deps = pkg_top.dependencies.?; const dupe_deps = dupe.dependencies.?; // probably the same top level package details try std.testing.expectEqualStrings(pkg_top.name, dupe.name); // probably the same dependencies try std.testing.expectEqual(original_deps.len, dupe_deps.len); try std.testing.expectEqual(original_deps[0].name, pkg_dep.name); // could segfault otherwise if pointers in duplicated package's fields are // the same as those in stack allocated package's fields try std.testing.expect(dupe_deps.ptr != original_deps.ptr); try std.testing.expect(dupe.name.ptr != pkg_top.name.ptr); try std.testing.expect(dupe.path.path.ptr != pkg_top.path.path.ptr); try std.testing.expect(dupe_deps[0].name.ptr != pkg_dep.name.ptr); try std.testing.expect(dupe_deps[0].path.path.ptr != pkg_dep.path.path.ptr); } test "LibExeObjStep.addPackage" { if (builtin.os.tag == .wasi) return error.SkipZigTest; var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); var builder = try Builder.create( arena.allocator(), "test", "test", "test", "test", ); defer builder.destroy(); const pkg_dep = Pkg{ .name = "pkg_dep", .path = .{ .path = "/not/a/pkg_dep.zig" }, }; const pkg_top = Pkg{ .name = "pkg_dep", .path = .{ .path = "/not/a/pkg_top.zig" }, .dependencies = &[_]Pkg{pkg_dep}, }; var exe = builder.addExecutable("not_an_executable", "/not/an/executable.zig"); exe.addPackage(pkg_top); try std.testing.expectEqual(@as(usize, 1), exe.packages.items.len); const dupe = exe.packages.items[0]; try std.testing.expectEqualStrings(pkg_top.name, dupe.name); }