From 73cf7b64291ed8b5dcb4cb52df103be08f15a347 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 30 Jan 2023 21:39:43 -0700 Subject: update build.zig API usage --- test/link/macho/objcpp/build.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'test/link/macho/objcpp/build.zig') diff --git a/test/link/macho/objcpp/build.zig b/test/link/macho/objcpp/build.zig index 767578e225..f4c88b2862 100644 --- a/test/link/macho/objcpp/build.zig +++ b/test/link/macho/objcpp/build.zig @@ -2,16 +2,18 @@ const std = @import("std"); const Builder = std.build.Builder; pub fn build(b: *Builder) void { - const mode = b.standardReleaseOptions(); + const optimize = b.standardOptimizeOption(.{}); const test_step = b.step("test", "Test the program"); - const exe = b.addExecutable("test", null); + const exe = b.addExecutable(.{ + .name = "test", + .optimize = optimize, + }); b.default_step.dependOn(&exe.step); exe.addIncludePath("."); exe.addCSourceFile("Foo.mm", &[0][]const u8{}); exe.addCSourceFile("test.mm", &[0][]const u8{}); - exe.setBuildMode(mode); exe.linkLibCpp(); // TODO when we figure out how to ship framework stubs for cross-compilation, // populate paths to the sysroot here. -- cgit v1.2.3 From 36e2d992dd8c45ca89a51d508c6c413cff5ad2cd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 31 Jan 2023 00:19:51 -0700 Subject: combine std.build and std.build.Builder into std.Build I've been wanting to do this for along time. --- build.zig | 31 +- doc/langref.html.in | 20 +- lib/build_runner.zig | 9 +- lib/init-exe/build.zig | 2 +- lib/init-lib/build.zig | 2 +- lib/std/Build.zig | 1771 +++++++++++++++++++ lib/std/Build/CheckFileStep.zig | 51 + lib/std/Build/CheckObjectStep.zig | 1024 +++++++++++ lib/std/Build/ConfigHeaderStep.zig | 287 +++ lib/std/Build/EmulatableRunStep.zig | 213 +++ lib/std/Build/FmtStep.zig | 32 + lib/std/Build/InstallArtifactStep.zig | 85 + lib/std/Build/InstallDirStep.zig | 93 + lib/std/Build/InstallFileStep.zig | 40 + lib/std/Build/InstallRawStep.zig | 110 ++ lib/std/Build/LibExeObjStep.zig | 2045 +++++++++++++++++++++ lib/std/Build/LogStep.zig | 23 + lib/std/Build/OptionsStep.zig | 363 ++++ lib/std/Build/RemoveDirStep.zig | 29 + lib/std/Build/RunStep.zig | 376 ++++ lib/std/Build/Step.zig | 97 + lib/std/Build/TranslateCStep.zig | 136 ++ lib/std/Build/WriteFileStep.zig | 115 ++ lib/std/build.zig | 1863 -------------------- lib/std/build/CheckFileStep.zig | 53 - lib/std/build/CheckObjectStep.zig | 1026 ----------- lib/std/build/ConfigHeaderStep.zig | 288 --- lib/std/build/EmulatableRunStep.zig | 215 --- lib/std/build/FmtStep.zig | 37 - lib/std/build/InstallArtifactStep.zig | 88 - lib/std/build/InstallDirStep.zig | 95 - lib/std/build/InstallFileStep.zig | 42 - lib/std/build/InstallRawStep.zig | 106 -- lib/std/build/LibExeObjStep.zig | 2047 ---------------------- lib/std/build/LogStep.zig | 25 - lib/std/build/OptionsStep.zig | 365 ---- lib/std/build/RemoveDirStep.zig | 31 - lib/std/build/RunStep.zig | 378 ---- lib/std/build/TranslateCStep.zig | 138 -- lib/std/build/WriteFileStep.zig | 117 -- lib/std/std.zig | 8 +- test/link/bss/build.zig | 4 +- test/link/common_symbols/build.zig | 4 +- test/link/common_symbols_alignment/build.zig | 4 +- test/link/interdependent_static_c_libs/build.zig | 4 +- test/link/macho/bugs/13056/build.zig | 3 +- test/link/macho/bugs/13457/build.zig | 5 +- test/link/macho/dead_strip/build.zig | 7 +- test/link/macho/dead_strip_dylibs/build.zig | 7 +- test/link/macho/dylib/build.zig | 3 +- test/link/macho/empty/build.zig | 5 +- test/link/macho/entry/build.zig | 3 +- test/link/macho/headerpad/build.zig | 7 +- test/link/macho/linksection/build.zig | 2 +- test/link/macho/needed_framework/build.zig | 5 +- test/link/macho/needed_library/build.zig | 5 +- test/link/macho/objc/build.zig | 5 +- test/link/macho/objcpp/build.zig | 3 +- test/link/macho/pagezero/build.zig | 3 +- test/link/macho/search_strategy/build.zig | 13 +- test/link/macho/stack_size/build.zig | 3 +- test/link/macho/strict_validation/build.zig | 5 +- test/link/macho/tls/build.zig | 3 +- test/link/macho/unwind_info/build.zig | 11 +- test/link/macho/uuid/build.zig | 11 +- test/link/macho/weak_framework/build.zig | 5 +- test/link/macho/weak_library/build.zig | 5 +- test/link/static_lib_as_system_lib/build.zig | 3 +- test/link/wasm/archive/build.zig | 3 +- test/link/wasm/basic-features/build.zig | 2 +- test/link/wasm/bss/build.zig | 3 +- test/link/wasm/export-data/build.zig | 3 +- test/link/wasm/export/build.zig | 2 +- test/link/wasm/extern-mangle/build.zig | 3 +- test/link/wasm/extern/build.zig | 2 +- test/link/wasm/function-table/build.zig | 3 +- test/link/wasm/infer-features/build.zig | 2 +- test/link/wasm/producers/build.zig | 3 +- test/link/wasm/segments/build.zig | 3 +- test/link/wasm/stack_pointer/build.zig | 3 +- test/link/wasm/type/build.zig | 3 +- test/src/compare_output.zig | 5 +- test/src/run_translated_c.zig | 5 +- test/src/translate_c.zig | 5 +- test/standalone/brace_expansion/build.zig | 4 +- test/standalone/c_compiler/build.zig | 5 +- test/standalone/emit_asm_and_bin/build.zig | 4 +- test/standalone/empty_env/build.zig | 4 +- test/standalone/global_linkage/build.zig | 4 +- test/standalone/install_raw_hex/build.zig | 4 +- test/standalone/issue_11595/build.zig | 5 +- test/standalone/issue_12588/build.zig | 3 +- test/standalone/issue_12706/build.zig | 5 +- test/standalone/issue_13030/build.zig | 3 +- test/standalone/issue_339/build.zig | 4 +- test/standalone/issue_5825/build.zig | 4 +- test/standalone/issue_7030/build.zig | 4 +- test/standalone/issue_794/build.zig | 4 +- test/standalone/issue_8550/build.zig | 2 +- test/standalone/issue_9812/build.zig | 2 +- test/standalone/load_dynamic_library/build.zig | 4 +- test/standalone/main_pkg_path/build.zig | 4 +- test/standalone/mix_c_files/build.zig | 5 +- test/standalone/mix_o_files/build.zig | 4 +- test/standalone/options/build.zig | 2 +- test/standalone/pie/build.zig | 4 +- test/standalone/pkg_import/build.zig | 4 +- test/standalone/shared_library/build.zig | 4 +- test/standalone/static_c_lib/build.zig | 4 +- test/standalone/test_runner_path/build.zig | 4 +- test/standalone/use_alias/build.zig | 4 +- test/standalone/windows_spawn/build.zig | 4 +- test/tests.zig | 60 +- 113 files changed, 7086 insertions(+), 7146 deletions(-) create mode 100644 lib/std/Build.zig create mode 100644 lib/std/Build/CheckFileStep.zig create mode 100644 lib/std/Build/CheckObjectStep.zig create mode 100644 lib/std/Build/ConfigHeaderStep.zig create mode 100644 lib/std/Build/EmulatableRunStep.zig create mode 100644 lib/std/Build/FmtStep.zig create mode 100644 lib/std/Build/InstallArtifactStep.zig create mode 100644 lib/std/Build/InstallDirStep.zig create mode 100644 lib/std/Build/InstallFileStep.zig create mode 100644 lib/std/Build/InstallRawStep.zig create mode 100644 lib/std/Build/LibExeObjStep.zig create mode 100644 lib/std/Build/LogStep.zig create mode 100644 lib/std/Build/OptionsStep.zig create mode 100644 lib/std/Build/RemoveDirStep.zig create mode 100644 lib/std/Build/RunStep.zig create mode 100644 lib/std/Build/Step.zig create mode 100644 lib/std/Build/TranslateCStep.zig create mode 100644 lib/std/Build/WriteFileStep.zig delete mode 100644 lib/std/build.zig delete mode 100644 lib/std/build/CheckFileStep.zig delete mode 100644 lib/std/build/CheckObjectStep.zig delete mode 100644 lib/std/build/ConfigHeaderStep.zig delete mode 100644 lib/std/build/EmulatableRunStep.zig delete mode 100644 lib/std/build/FmtStep.zig delete mode 100644 lib/std/build/InstallArtifactStep.zig delete mode 100644 lib/std/build/InstallDirStep.zig delete mode 100644 lib/std/build/InstallFileStep.zig delete mode 100644 lib/std/build/InstallRawStep.zig delete mode 100644 lib/std/build/LibExeObjStep.zig delete mode 100644 lib/std/build/LogStep.zig delete mode 100644 lib/std/build/OptionsStep.zig delete mode 100644 lib/std/build/RemoveDirStep.zig delete mode 100644 lib/std/build/RunStep.zig delete mode 100644 lib/std/build/TranslateCStep.zig delete mode 100644 lib/std/build/WriteFileStep.zig (limited to 'test/link/macho/objcpp/build.zig') diff --git a/build.zig b/build.zig index 98da9f31ee..6b6c384fc4 100644 --- a/build.zig +++ b/build.zig @@ -1,19 +1,18 @@ const std = @import("std"); const builtin = std.builtin; -const Builder = std.build.Builder; const tests = @import("test/tests.zig"); const BufMap = std.BufMap; const mem = std.mem; const ArrayList = std.ArrayList; const io = std.io; const fs = std.fs; -const InstallDirectoryOptions = std.build.InstallDirectoryOptions; +const InstallDirectoryOptions = std.Build.InstallDirectoryOptions; const assert = std.debug.assert; const zig_version = std.builtin.Version{ .major = 0, .minor = 11, .patch = 0 }; const stack_size = 32 * 1024 * 1024; -pub fn build(b: *Builder) !void { +pub fn build(b: *std.Build) !void { const release = b.option(bool, "release", "Build in release mode") orelse false; const only_c = b.option(bool, "only-c", "Translate the Zig compiler to C code, with only the C backend enabled") orelse false; const target = t: { @@ -477,7 +476,7 @@ pub fn build(b: *Builder) !void { try addWasiUpdateStep(b, version); } -fn addWasiUpdateStep(b: *Builder, version: [:0]const u8) !void { +fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { const semver = try std.SemanticVersion.parse(version); var target: std.zig.CrossTarget = .{ @@ -514,10 +513,10 @@ fn addWasiUpdateStep(b: *Builder, version: [:0]const u8) !void { } fn addCompilerStep( - b: *Builder, + b: *std.Build, optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget, -) *std.build.LibExeObjStep { +) *std.Build.LibExeObjStep { const exe = b.addExecutable(.{ .name = "zig", .root_source_file = .{ .path = "src/main.zig" }, @@ -543,9 +542,9 @@ const exe_cflags = [_][]const u8{ }; fn addCmakeCfgOptionsToExe( - b: *Builder, + b: *std.Build, cfg: CMakeConfig, - exe: *std.build.LibExeObjStep, + exe: *std.Build.LibExeObjStep, use_zig_libcxx: bool, ) !void { if (exe.target.isDarwin()) { @@ -624,7 +623,7 @@ fn addCmakeCfgOptionsToExe( } } -fn addStaticLlvmOptionsToExe(exe: *std.build.LibExeObjStep) !void { +fn addStaticLlvmOptionsToExe(exe: *std.Build.LibExeObjStep) !void { // Adds the Zig C++ sources which both stage1 and stage2 need. // // We need this because otherwise zig_clang_cc1_main.cpp ends up pulling @@ -661,9 +660,9 @@ fn addStaticLlvmOptionsToExe(exe: *std.build.LibExeObjStep) !void { } fn addCxxKnownPath( - b: *Builder, + b: *std.Build, ctx: CMakeConfig, - exe: *std.build.LibExeObjStep, + exe: *std.Build.LibExeObjStep, objname: []const u8, errtxt: ?[]const u8, need_cpp_includes: bool, @@ -696,7 +695,7 @@ fn addCxxKnownPath( } } -fn addCMakeLibraryList(exe: *std.build.LibExeObjStep, list: []const u8) void { +fn addCMakeLibraryList(exe: *std.Build.LibExeObjStep, list: []const u8) void { var it = mem.tokenize(u8, list, ";"); while (it.next()) |lib| { if (mem.startsWith(u8, lib, "-l")) { @@ -710,7 +709,7 @@ fn addCMakeLibraryList(exe: *std.build.LibExeObjStep, list: []const u8) void { } const CMakeConfig = struct { - llvm_linkage: std.build.LibExeObjStep.Linkage, + llvm_linkage: std.Build.LibExeObjStep.Linkage, cmake_binary_dir: []const u8, cmake_prefix_path: []const u8, cmake_static_library_prefix: []const u8, @@ -727,7 +726,7 @@ const CMakeConfig = struct { const max_config_h_bytes = 1 * 1024 * 1024; -fn findConfigH(b: *Builder, config_h_path_option: ?[]const u8) ?[]const u8 { +fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 { if (config_h_path_option) |path| { var config_h_or_err = fs.cwd().openFile(path, .{}); if (config_h_or_err) |*file| { @@ -773,7 +772,7 @@ fn findConfigH(b: *Builder, config_h_path_option: ?[]const u8) ?[]const u8 { } else unreachable; // TODO should not need `else unreachable`. } -fn parseConfigH(b: *Builder, config_h_text: []const u8) ?CMakeConfig { +fn parseConfigH(b: *std.Build, config_h_text: []const u8) ?CMakeConfig { var ctx: CMakeConfig = .{ .llvm_linkage = undefined, .cmake_binary_dir = undefined, @@ -862,7 +861,7 @@ fn parseConfigH(b: *Builder, config_h_text: []const u8) ?CMakeConfig { return ctx; } -fn toNativePathSep(b: *Builder, s: []const u8) []u8 { +fn toNativePathSep(b: *std.Build, s: []const u8) []u8 { const duplicated = b.allocator.dupe(u8, s) catch unreachable; for (duplicated) |*byte| switch (byte.*) { '/' => byte.* = fs.path.sep, diff --git a/doc/langref.html.in b/doc/langref.html.in index c008149f41..1163ad0200 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -9528,9 +9528,9 @@ fn foo(comptime T: type, ptr: *T) T { To add standard build options to a build.zig file:

{#code_begin|syntax|build#} -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const exe = b.addExecutable(.{ .name = "example", @@ -10551,9 +10551,9 @@ const separator = if (builtin.os.tag == .windows) '\\' else '/';

This build.zig file is automatically generated by zig init-exe.

{#code_begin|syntax|build_executable#} -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options @@ -10588,9 +10588,9 @@ pub fn build(b: *Builder) void {

This build.zig file is automatically generated by zig init-lib.

{#code_begin|syntax|build_library#} -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const lib = b.addStaticLibrary(.{ .name = "example", @@ -10961,9 +10961,9 @@ int main(int argc, char **argv) { } {#end_syntax_block#} {#code_begin|syntax|build_c#} -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const lib = b.addSharedLibrary("mathtest", "mathtest.zig", b.version(1, 0, 0)); const exe = b.addExecutable(.{ @@ -11025,9 +11025,9 @@ int main(int argc, char **argv) { } {#end_syntax_block#} {#code_begin|syntax|build_object#} -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const obj = b.addObject("base64", "base64.zig"); const exe = b.addExecutable(.{ diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 4df2eb1d62..189b118787 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -3,7 +3,6 @@ const std = @import("std"); const builtin = @import("builtin"); const io = std.io; const fmt = std.fmt; -const Builder = std.build.Builder; const mem = std.mem; const process = std.process; const ArrayList = std.ArrayList; @@ -42,7 +41,7 @@ pub fn main() !void { return error.InvalidArgs; }; - const builder = try Builder.create( + const builder = try std.Build.create( allocator, zig_exe, build_root, @@ -58,7 +57,7 @@ pub fn main() !void { const stdout_stream = io.getStdOut().writer(); var install_prefix: ?[]const u8 = null; - var dir_list = Builder.DirList{}; + var dir_list = std.Build.DirList{}; // before arg parsing, check for the NO_COLOR environment variable // if it exists, default the color setting to .off @@ -230,7 +229,7 @@ pub fn main() !void { }; } -fn usage(builder: *Builder, already_ran_build: bool, out_stream: anytype) !void { +fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !void { // run the build script to collect the options if (!already_ran_build) { builder.resolveInstallPrefix(null, .{}); @@ -330,7 +329,7 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: anytype) !void ); } -fn usageAndErr(builder: *Builder, already_ran_build: bool, out_stream: anytype) void { +fn usageAndErr(builder: *std.Build, already_ran_build: bool, out_stream: anytype) void { usage(builder, already_ran_build, out_stream) catch {}; process.exit(1); } diff --git a/lib/init-exe/build.zig b/lib/init-exe/build.zig index 36e5feddec..b88515b403 100644 --- a/lib/init-exe/build.zig +++ b/lib/init-exe/build.zig @@ -3,7 +3,7 @@ const std = @import("std"); // Although this function looks imperative, note that its job is to // declaratively construct a build graph that will be executed by an external // runner. -pub fn build(b: *std.build.Builder) void { +pub fn build(b: *std.Build) void { // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options diff --git a/lib/init-lib/build.zig b/lib/init-lib/build.zig index 4a7b700dc2..5ebb55373f 100644 --- a/lib/init-lib/build.zig +++ b/lib/init-lib/build.zig @@ -3,7 +3,7 @@ const std = @import("std"); // Although this function looks imperative, note that its job is to // declaratively construct a build graph that will be executed by an external // runner. -pub fn build(b: *std.build.Builder) void { +pub fn build(b: *std.Build) void { // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options diff --git a/lib/std/Build.zig b/lib/std/Build.zig new file mode 100644 index 0000000000..a3c579c743 --- /dev/null +++ b/lib/std/Build.zig @@ -0,0 +1,1771 @@ +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 log = std.log; +const ArrayList = std.ArrayList; +const StringHashMap = std.StringHashMap; +const Allocator = mem.Allocator; +const process = std.process; +const EnvMap = std.process.EnvMap; +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; +const Build = @This(); + +pub const Step = @import("Build/Step.zig"); +pub const CheckFileStep = @import("Build/CheckFileStep.zig"); +pub const CheckObjectStep = @import("Build/CheckObjectStep.zig"); +pub const ConfigHeaderStep = @import("Build/ConfigHeaderStep.zig"); +pub const EmulatableRunStep = @import("Build/EmulatableRunStep.zig"); +pub const FmtStep = @import("Build/FmtStep.zig"); +pub const InstallArtifactStep = @import("Build/InstallArtifactStep.zig"); +pub const InstallDirStep = @import("Build/InstallDirStep.zig"); +pub const InstallFileStep = @import("Build/InstallFileStep.zig"); +pub const InstallRawStep = @import("Build/InstallRawStep.zig"); +pub const LibExeObjStep = @import("Build/LibExeObjStep.zig"); +pub const LogStep = @import("Build/LogStep.zig"); +pub const OptionsStep = @import("Build/OptionsStep.zig"); +pub const RemoveDirStep = @import("Build/RemoveDirStep.zig"); +pub const RunStep = @import("Build/RunStep.zig"); +pub const TranslateCStep = @import("Build/TranslateCStep.zig"); +pub const WriteFileStep = @import("Build/WriteFileStep.zig"); + +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, +reference_trace: ?u32 = null, +invalid_user_input: bool, +zig_exe: []const u8, +default_step: *Step, +env_map: *EnvMap, +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), +/// Path to the directory containing build.zig. +build_root: []const u8, +cache_root: []const u8, +global_cache_root: []const u8, +/// zig lib dir +override_lib_dir: ?[]const u8, +vcpkg_root: VcpkgRoot = .unattempted, +pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null, +args: ?[][]const u8 = null, +debug_log_scopes: []const []const u8 = &.{}, +debug_compile_errors: bool = false, + +/// 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, + +dep_prefix: []const u8 = "", + +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), + map: StringHashMap(*const UserValue), +}; + +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, +) !*Build { + const env_map = try allocator.create(EnvMap); + env_map.* = try process.getEnvMap(allocator); + + const host = try NativeTargetInfo.detect(.{}); + + const self = try allocator.create(Build); + self.* = Build{ + .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", + }, + .override_lib_dir = null, + .install_path = undefined, + .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; +} + +fn createChild( + parent: *Build, + dep_name: []const u8, + build_root: []const u8, + args: anytype, +) !*Build { + const child = try createChildOnly(parent, dep_name, build_root); + try applyArgs(child, args); + return child; +} + +fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: []const u8) !*Build { + const allocator = parent.allocator; + const child = try allocator.create(Build); + child.* = .{ + .allocator = allocator, + .install_tls = .{ + .step = Step.initNoOp(.top_level, "install", allocator), + .description = "Copy build artifacts to prefix path", + }, + .uninstall_tls = .{ + .step = Step.init(.top_level, "uninstall", allocator, makeUninstall), + .description = "Remove build artifacts from prefix path", + }, + .user_input_options = UserInputOptionsMap.init(allocator), + .available_options_map = AvailableOptionsMap.init(allocator), + .available_options_list = ArrayList(AvailableOption).init(allocator), + .verbose = parent.verbose, + .verbose_link = parent.verbose_link, + .verbose_cc = parent.verbose_cc, + .verbose_air = parent.verbose_air, + .verbose_llvm_ir = parent.verbose_llvm_ir, + .verbose_cimport = parent.verbose_cimport, + .verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features, + .prominent_compile_errors = parent.prominent_compile_errors, + .color = parent.color, + .reference_trace = parent.reference_trace, + .invalid_user_input = false, + .zig_exe = parent.zig_exe, + .default_step = undefined, + .env_map = parent.env_map, + .top_level_steps = ArrayList(*TopLevelStep).init(allocator), + .install_prefix = undefined, + .dest_dir = parent.dest_dir, + .lib_dir = parent.lib_dir, + .exe_dir = parent.exe_dir, + .h_dir = parent.h_dir, + .install_path = parent.install_path, + .sysroot = parent.sysroot, + .search_prefixes = ArrayList([]const u8).init(allocator), + .libc_file = parent.libc_file, + .installed_files = ArrayList(InstalledFile).init(allocator), + .build_root = build_root, + .cache_root = parent.cache_root, + .global_cache_root = parent.global_cache_root, + .override_lib_dir = parent.override_lib_dir, + .debug_log_scopes = parent.debug_log_scopes, + .debug_compile_errors = parent.debug_compile_errors, + .enable_darling = parent.enable_darling, + .enable_qemu = parent.enable_qemu, + .enable_rosetta = parent.enable_rosetta, + .enable_wasmtime = parent.enable_wasmtime, + .enable_wine = parent.enable_wine, + .glibc_runtimes_dir = parent.glibc_runtimes_dir, + .host = parent.host, + .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }), + }; + try child.top_level_steps.append(&child.install_tls); + try child.top_level_steps.append(&child.uninstall_tls); + child.default_step = &child.install_tls.step; + return child; +} + +fn applyArgs(b: *Build, args: anytype) !void { + inline for (@typeInfo(@TypeOf(args)).Struct.fields) |field| { + const v = @field(args, field.name); + const T = @TypeOf(v); + switch (T) { + CrossTarget => { + try b.user_input_options.put(field.name, .{ + .name = field.name, + .value = .{ .scalar = try v.zigTriple(b.allocator) }, + .used = false, + }); + try b.user_input_options.put("cpu", .{ + .name = "cpu", + .value = .{ .scalar = try serializeCpu(b.allocator, v.getCpu()) }, + .used = false, + }); + }, + []const u8 => { + try b.user_input_options.put(field.name, .{ + .name = field.name, + .value = .{ .scalar = v }, + .used = false, + }); + }, + else => switch (@typeInfo(T)) { + .Bool => { + try b.user_input_options.put(field.name, .{ + .name = field.name, + .value = .{ .scalar = if (v) "true" else "false" }, + .used = false, + }); + }, + .Enum => { + try b.user_input_options.put(field.name, .{ + .name = field.name, + .value = .{ .scalar = @tagName(v) }, + .used = false, + }); + }, + .Int => { + try b.user_input_options.put(field.name, .{ + .name = field.name, + .value = .{ .scalar = try std.fmt.allocPrint(b.allocator, "{d}", .{v}) }, + .used = false, + }); + }, + else => @compileError("option '" ++ field.name ++ "' has unsupported type: " ++ @typeName(T)), + }, + } + } + const Hasher = std.crypto.auth.siphash.SipHash128(1, 3); + // Random bytes to make unique. Refresh this with new random bytes when + // implementation is modified in a non-backwards-compatible way. + var hash = Hasher.init("ZaEsvQ5ClaA2IdH9"); + hash.update(b.dep_prefix); + // TODO additionally update the hash with `args`. + + var digest: [16]u8 = undefined; + hash.final(&digest); + var hash_basename: [digest.len * 2]u8 = undefined; + _ = std.fmt.bufPrint(&hash_basename, "{s}", .{std.fmt.fmtSliceHexLower(&digest)}) catch + unreachable; + + const install_prefix = b.pathJoin(&.{ b.cache_root, "i", &hash_basename }); + b.resolveInstallPrefix(install_prefix, .{}); +} + +pub fn destroy(self: *Build) void { + self.env_map.deinit(); + self.top_level_steps.deinit(); + self.allocator.destroy(self); +} + +/// This function is intended to be called by lib/build_runner.zig, not a build.zig file. +pub fn resolveInstallPrefix(self: *Build, 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); +} + +pub fn addOptions(self: *Build) *OptionsStep { + return OptionsStep.create(self); +} + +pub const ExecutableOptions = struct { + name: []const u8, + root_source_file: ?FileSource = null, + version: ?std.builtin.Version = null, + target: CrossTarget = .{}, + optimize: std.builtin.Mode = .Debug, + linkage: ?LibExeObjStep.Linkage = null, +}; + +pub fn addExecutable(b: *Build, options: ExecutableOptions) *LibExeObjStep { + return LibExeObjStep.create(b, .{ + .name = options.name, + .root_source_file = options.root_source_file, + .version = options.version, + .target = options.target, + .optimize = options.optimize, + .kind = .exe, + .linkage = options.linkage, + }); +} + +pub const ObjectOptions = struct { + name: []const u8, + root_source_file: ?FileSource = null, + target: CrossTarget, + optimize: std.builtin.Mode, +}; + +pub fn addObject(b: *Build, options: ObjectOptions) *LibExeObjStep { + return LibExeObjStep.create(b, .{ + .name = options.name, + .root_source_file = options.root_source_file, + .target = options.target, + .optimize = options.optimize, + .kind = .obj, + }); +} + +pub const SharedLibraryOptions = struct { + name: []const u8, + root_source_file: ?FileSource = null, + version: ?std.builtin.Version = null, + target: CrossTarget, + optimize: std.builtin.Mode, +}; + +pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *LibExeObjStep { + return LibExeObjStep.create(b, .{ + .name = options.name, + .root_source_file = options.root_source_file, + .kind = .lib, + .linkage = .dynamic, + .version = options.version, + .target = options.target, + .optimize = options.optimize, + }); +} + +pub const StaticLibraryOptions = struct { + name: []const u8, + root_source_file: ?FileSource = null, + target: CrossTarget, + optimize: std.builtin.Mode, + version: ?std.builtin.Version = null, +}; + +pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *LibExeObjStep { + return LibExeObjStep.create(b, .{ + .name = options.name, + .root_source_file = options.root_source_file, + .kind = .lib, + .linkage = .static, + .version = options.version, + .target = options.target, + .optimize = options.optimize, + }); +} + +pub const TestOptions = struct { + name: []const u8 = "test", + kind: LibExeObjStep.Kind = .@"test", + root_source_file: FileSource, + target: CrossTarget = .{}, + optimize: std.builtin.Mode = .Debug, + version: ?std.builtin.Version = null, +}; + +pub fn addTest(b: *Build, options: TestOptions) *LibExeObjStep { + return LibExeObjStep.create(b, .{ + .name = options.name, + .kind = options.kind, + .root_source_file = options.root_source_file, + .target = options.target, + .optimize = options.optimize, + }); +} + +pub const AssemblyOptions = struct { + name: []const u8, + source_file: FileSource, + target: CrossTarget, + optimize: std.builtin.Mode, +}; + +pub fn addAssembly(b: *Build, options: AssemblyOptions) *LibExeObjStep { + const obj_step = LibExeObjStep.create(b, .{ + .name = options.name, + .root_source_file = null, + .target = options.target, + .optimize = options.optimize, + }); + obj_step.addAssemblyFileSource(options.source_file.dupe(b)); + 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: *Build, 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; +} + +pub fn addConfigHeader( + b: *Build, + source: FileSource, + style: ConfigHeaderStep.Style, + values: anytype, +) *ConfigHeaderStep { + const config_header_step = ConfigHeaderStep.create(b, source, style); + config_header_step.addValues(values); + return config_header_step; +} + +/// Allocator.dupe without the need to handle out of memory. +pub fn dupe(self: *Build, 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: *Build, 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: *Build, 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: *Build, package: Pkg) Pkg { + var the_copy = Pkg{ + .name = self.dupe(package.name), + .source = package.source.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: *Build, 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: *Build) *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: *Build, 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: *Build, 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: *Build, paths: []const []const u8) *FmtStep { + return FmtStep.create(self, paths); +} + +pub fn addTranslateC(self: *Build, options: TranslateCStep.Options) *TranslateCStep { + return TranslateCStep.create(self, options); +} + +pub fn make(self: *Build, 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: *Build) *Step { + return &self.install_tls.step; +} + +pub fn getUninstallStep(self: *Build) *Step { + return &self.uninstall_tls.step; +} + +fn makeUninstall(uninstall_step: *Step) anyerror!void { + const uninstall_tls = @fieldParentPtr(TopLevelStep, "step", uninstall_step); + const self = @fieldParentPtr(Build, "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) { + log.info("rm {s}", .{full_path}); + } + fs.cwd().deleteTree(full_path) catch {}; + } + + // TODO remove empty directories +} + +fn makeOneStep(self: *Build, s: *Step) anyerror!void { + if (s.loop_flag) { + log.err("Dependency loop detected:\n {s}", .{s.name}); + return error.DependencyLoopDetected; + } + s.loop_flag = true; + + for (s.dependencies.items) |dep| { + self.makeOneStep(dep) catch |err| { + if (err == error.DependencyLoopDetected) { + log.err(" {s}", .{s.name}); + } + return err; + }; + } + + s.loop_flag = false; + + try s.make(); +} + +fn getTopLevelStepByName(self: *Build, 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; + } + } + log.err("Cannot run step '{s}' because it does not exist", .{name}); + return error.InvalidStepName; +} + +pub fn option(self: *Build, 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() catch unreachable; + } 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 { + log.err("Expected -D{s} to be a boolean, but received '{s}'\n", .{ name, s }); + self.markInvalidUserInput(); + return null; + } + }, + .list, .map => { + log.err("Expected -D{s} to be a boolean, but received a {s}.\n", .{ + name, @tagName(option_ptr.value), + }); + self.markInvalidUserInput(); + return null; + }, + }, + .int => switch (option_ptr.value) { + .flag, .list, .map => { + log.err("Expected -D{s} to be an integer, but received a {s}.\n", .{ + name, @tagName(option_ptr.value), + }); + self.markInvalidUserInput(); + return null; + }, + .scalar => |s| { + const n = std.fmt.parseInt(T, s, 10) catch |err| switch (err) { + error.Overflow => { + log.err("-D{s} value {s} cannot fit into type {s}.\n", .{ name, s, @typeName(T) }); + self.markInvalidUserInput(); + return null; + }, + else => { + log.err("Expected -D{s} to be an integer of type {s}.\n", .{ name, @typeName(T) }); + self.markInvalidUserInput(); + return null; + }, + }; + return n; + }, + }, + .float => switch (option_ptr.value) { + .flag, .map, .list => { + log.err("Expected -D{s} to be a float, but received a {s}.\n", .{ + name, @tagName(option_ptr.value), + }); + self.markInvalidUserInput(); + return null; + }, + .scalar => |s| { + const n = std.fmt.parseFloat(T, s) catch { + log.err("Expected -D{s} to be a float of type {s}.\n", .{ name, @typeName(T) }); + self.markInvalidUserInput(); + return null; + }; + return n; + }, + }, + .@"enum" => switch (option_ptr.value) { + .flag, .map, .list => { + log.err("Expected -D{s} to be an enum, but received a {s}.\n", .{ + name, @tagName(option_ptr.value), + }); + self.markInvalidUserInput(); + return null; + }, + .scalar => |s| { + if (std.meta.stringToEnum(T, s)) |enum_lit| { + return enum_lit; + } else { + log.err("Expected -D{s} to be of type {s}.\n", .{ name, @typeName(T) }); + self.markInvalidUserInput(); + return null; + } + }, + }, + .string => switch (option_ptr.value) { + .flag, .list, .map => { + log.err("Expected -D{s} to be a string, but received a {s}.\n", .{ + name, @tagName(option_ptr.value), + }); + self.markInvalidUserInput(); + return null; + }, + .scalar => |s| return s, + }, + .list => switch (option_ptr.value) { + .flag, .map => { + log.err("Expected -D{s} to be a list, but received a {s}.\n", .{ + name, @tagName(option_ptr.value), + }); + 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: *Build, 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; +} + +pub const StandardOptimizeOptionOptions = struct { + preferred_optimize_mode: ?std.builtin.Mode = null, +}; + +pub fn standardOptimizeOption(self: *Build, options: StandardOptimizeOptionOptions) std.builtin.Mode { + if (options.preferred_optimize_mode) |mode| { + if (self.option(bool, "release", "optimize for end users") orelse false) { + return mode; + } else { + return .Debug; + } + } else { + return self.option( + std.builtin.Mode, + "optimize", + "prioritize performance, safety, or binary size (-O flag)", + ) orelse .Debug; + } +} + +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: *Build, 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 => { + log.err("Unknown CPU: '{s}'\nAvailable CPUs for architecture '{s}':", .{ + diags.cpu_name.?, + @tagName(diags.arch.?), + }); + for (diags.arch.?.allCpuModels()) |cpu| { + log.err(" {s}", .{cpu.name}); + } + self.markInvalidUserInput(); + return args.default_target; + }, + error.UnknownCpuFeature => { + log.err( + \\Unknown CPU feature: '{s}' + \\Available CPU features for architecture '{s}': + \\ + , .{ + diags.unknown_feature_name.?, + @tagName(diags.arch.?), + }); + for (diags.arch.?.allFeaturesList()) |feature| { + log.err(" {s}: {s}", .{ feature.name, feature.description }); + } + self.markInvalidUserInput(); + return args.default_target; + }, + error.UnknownOperatingSystem => { + log.err( + \\Unknown OS: '{s}' + \\Available operating systems: + \\ + , .{diags.os_name.?}); + inline for (std.meta.fields(std.Target.Os.Tag)) |field| { + log.err(" {s}", .{field.name}); + } + self.markInvalidUserInput(); + return args.default_target; + }, + else => |e| { + log.err("Unable to parse target '{s}': {s}\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) { + log.err("Chosen target '{s}' does not match one of the supported targets:", .{ + selected_canonicalized_triple, + }); + for (list) |t| { + const t_triple = t.zigTriple(self.allocator) catch unreachable; + log.err(" {s}", .{t_triple}); + } + } else { + assert(mismatch_cpu_features); + const whitelist_cpu = whitelist_item.getCpu(); + const selected_cpu = selected_target.getCpu(); + log.err("Chosen CPU model '{s}' does not match one of the supported targets:", .{ + selected_cpu.model.name, + }); + log.err(" 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) { + log.err("{s} ", .{feature.name}); + } + } + log.err(" 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) { + log.err("{s} ", .{feature.name}); + } + } + } + self.markInvalidUserInput(); + return args.default_target; + } + + return selected_target; +} + +pub fn addUserInputOption(self: *Build, 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 => { + log.warn("Option '-D{s}={s}' conflicts with flag '-D{s}'.", .{ name, value, name }); + return true; + }, + .map => |*map| { + _ = map; + log.warn("TODO maps as command line arguments is not implemented yet.", .{}); + return true; + }, + } + return false; +} + +pub fn addUserInputFlag(self: *Build, 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| { + log.err("Flag '-D{s}' conflicts with option '-D{s}={s}'.", .{ name, name, s }); + return true; + }, + .list, .map => { + log.err("Flag '-D{s}' conflicts with multiple options of the same name.", .{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: *Build) void { + self.invalid_user_input = true; +} + +pub fn validateUserInputDidItFail(self: *Build) bool { + // make sure all args are used + var it = self.user_input_options.iterator(); + while (it.next()) |entry| { + if (!entry.value_ptr.used) { + log.err("Invalid option: -D{s}", .{entry.key_ptr.*}); + self.markInvalidUserInput(); + } + } + + return self.invalid_user_input; +} + +pub fn spawnChild(self: *Build, 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| std.debug.print("cd {s} && ", .{yes_cwd}); + for (argv) |arg| { + std.debug.print("{s} ", .{arg}); + } + std.debug.print("\n", .{}); +} + +pub fn spawnChildEnvMap(self: *Build, cwd: ?[]const u8, env_map: *const EnvMap, argv: []const []const u8) !void { + if (self.verbose) { + printCmd(cwd, argv); + } + + if (!std.process.can_spawn) + return error.ExecNotSupported; + + var child = std.ChildProcess.init(argv, self.allocator); + child.cwd = cwd; + child.env_map = env_map; + + const term = child.spawnAndWait() catch |err| { + log.err("Unable to spawn {s}: {s}", .{ argv[0], @errorName(err) }); + return err; + }; + + switch (term) { + .Exited => |code| { + if (code != 0) { + log.err("The following command exited with error code {}:", .{code}); + printCmd(cwd, argv); + return error.UncleanExit; + } + }, + else => { + log.err("The following command terminated unexpectedly:", .{}); + printCmd(cwd, argv); + + return error.UncleanExit; + }, + } +} + +pub fn makePath(self: *Build, path: []const u8) !void { + fs.cwd().makePath(self.pathFromRoot(path)) catch |err| { + log.err("Unable to create path {s}: {s}", .{ path, @errorName(err) }); + return err; + }; +} + +pub fn installArtifact(self: *Build, artifact: *LibExeObjStep) void { + self.getInstallStep().dependOn(&self.addInstallArtifact(artifact).step); +} + +pub fn addInstallArtifact(self: *Build, artifact: *LibExeObjStep) *InstallArtifactStep { + return InstallArtifactStep.create(self, artifact); +} + +///`dest_rel_path` is relative to prefix path +pub fn installFile(self: *Build, 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: *Build, options: InstallDirectoryOptions) void { + self.getInstallStep().dependOn(&self.addInstallDirectory(options).step); +} + +///`dest_rel_path` is relative to bin path +pub fn installBinFile(self: *Build, 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: *Build, 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: *Build, 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: *Build, 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: *Build, 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: *Build, source: FileSource, dest_rel_path: []const u8) *InstallFileStep { + return self.addInstallFileWithDir(source.dupe(self), .lib, dest_rel_path); +} + +pub fn addInstallHeaderFile(b: *Build, src_path: []const u8, dest_rel_path: []const u8) *InstallFileStep { + return b.addInstallFileWithDir(.{ .path = src_path }, .header, dest_rel_path); +} + +pub fn addInstallRaw(self: *Build, artifact: *LibExeObjStep, dest_filename: []const u8, options: InstallRawStep.CreateOptions) *InstallRawStep { + return InstallRawStep.create(self, artifact, dest_filename, options); +} + +pub fn addInstallFileWithDir( + self: *Build, + 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: *Build, 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: *Build, 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: *Build, source_path: []const u8, dest_path: []const u8) !void { + if (self.verbose) { + log.info("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 => log.info("# installed", .{}), + .fresh => log.info("# up-to-date", .{}), + }; +} + +pub fn truncateFile(self: *Build, dest_path: []const u8) !void { + if (self.verbose) { + log.info("truncate {s}", .{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: *Build, 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(Build.allocator, paths) catch unreachable` +pub fn pathJoin(self: *Build, paths: []const []const u8) []u8 { + return fs.path.join(self.allocator, paths) catch unreachable; +} + +pub fn fmt(self: *Build, comptime format: []const u8, args: anytype) []u8 { + return fmt_lib.allocPrint(self.allocator, format, args) catch unreachable; +} + +pub fn findProgram(self: *Build, 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: *Build, + 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; + var child = std.ChildProcess.init(argv, self.allocator); + 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: *Build, 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| log.err("{s}...", .{s.name}); + log.err("Unable to spawn the following command: cannot spawn child process", .{}); + 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| log.err("{s}...", .{s.name}); + log.err("Unable to spawn the following command: cannot spawn child process", .{}); + printCmd(null, argv); + std.os.abort(); + }, + error.FileNotFound => { + if (src_step) |s| log.err("{s}...", .{s.name}); + log.err("Unable to spawn the following command: file not found", .{}); + printCmd(null, argv); + std.os.exit(@truncate(u8, code)); + }, + error.ExitCodeFailure => { + if (src_step) |s| log.err("{s}...", .{s.name}); + if (self.prominent_compile_errors) { + log.err("The step exited with error code {d}", .{code}); + } else { + log.err("The following command exited with error code {d}:", .{code}); + printCmd(null, argv); + } + + std.os.exit(@truncate(u8, code)); + }, + error.ProcessTerminated => { + if (src_step) |s| log.err("{s}...", .{s.name}); + log.err("The following command terminated unexpectedly:", .{}); + printCmd(null, argv); + std.os.exit(@truncate(u8, code)); + }, + else => |e| return e, + }; +} + +pub fn exec(self: *Build, argv: []const []const u8) ![]u8 { + return self.execFromStep(argv, null); +} + +pub fn addSearchPrefix(self: *Build, search_prefix: []const u8) void { + self.search_prefixes.append(self.dupePath(search_prefix)) catch unreachable; +} + +pub fn getInstallPath(self: *Build, 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; +} + +pub const Dependency = struct { + builder: *Build, + + pub fn artifact(d: *Dependency, name: []const u8) *LibExeObjStep { + var found: ?*LibExeObjStep = null; + for (d.builder.install_tls.step.dependencies.items) |dep_step| { + const inst = dep_step.cast(InstallArtifactStep) orelse continue; + if (mem.eql(u8, inst.artifact.name, name)) { + if (found != null) panic("artifact name '{s}' is ambiguous", .{name}); + found = inst.artifact; + } + } + return found orelse { + for (d.builder.install_tls.step.dependencies.items) |dep_step| { + const inst = dep_step.cast(InstallArtifactStep) orelse continue; + log.info("available artifact: '{s}'", .{inst.artifact.name}); + } + panic("unable to find artifact '{s}'", .{name}); + }; + } +}; + +pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency { + const build_runner = @import("root"); + const deps = build_runner.dependencies; + + inline for (@typeInfo(deps.imports).Struct.decls) |decl| { + if (mem.startsWith(u8, decl.name, b.dep_prefix) and + mem.endsWith(u8, decl.name, name) and + decl.name.len == b.dep_prefix.len + name.len) + { + const build_zig = @field(deps.imports, decl.name); + const build_root = @field(deps.build_root, decl.name); + return dependencyInner(b, name, build_root, build_zig, args); + } + } + + const full_path = b.pathFromRoot("build.zig.ini"); + std.debug.print("no dependency named '{s}' in '{s}'\n", .{ name, full_path }); + std.process.exit(1); +} + +fn dependencyInner( + b: *Build, + name: []const u8, + build_root: []const u8, + comptime build_zig: type, + args: anytype, +) *Dependency { + const sub_builder = b.createChild(name, build_root, args) catch unreachable; + sub_builder.runBuild(build_zig) catch unreachable; + + if (sub_builder.validateUserInputDidItFail()) { + std.debug.dumpCurrentStackTrace(@returnAddress()); + } + + const dep = b.allocator.create(Dependency) catch unreachable; + dep.* = .{ .builder = sub_builder }; + return dep; +} + +pub fn runBuild(b: *Build, build_zig: anytype) anyerror!void { + switch (@typeInfo(@typeInfo(@TypeOf(build_zig.build)).Fn.return_type.?)) { + .Void => build_zig.build(b), + .ErrorUnion => try build_zig.build(b), + else => @compileError("expected return type of build to be 'void' or '!void'"), + } +} + +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 Build.create( + arena.allocator(), + "zig", + "zig-cache", + "zig-cache", + "zig-cache", + ); + defer builder.destroy(); + _ = builder.findProgram(&[_][]const u8{}, &[_][]const u8{}) catch null; +} + +pub const Pkg = struct { + name: []const u8, + source: FileSource, + dependencies: ?[]const Pkg = null, +}; + +/// 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, other_step: *Step) void { + switch (self) { + .path => {}, + .generated => |gen| other_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: *Build) []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: *Build) FileSource { + return switch (self) { + .path => |p| .{ .path = b.dupePath(p) }, + .generated => |gen| .{ .generated = gen }, + }; + } +}; + +/// 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; +} + +/// deprecated: use `InstallDirStep.Options` +pub const InstallDirectoryOptions = InstallDirStep.Options; + +pub const VcpkgRoot = union(VcpkgRootStatus) { + unattempted: void, + not_found: void, + found: []const u8, +}; + +pub 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: *Build) 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: *Build) InstalledFile { + return .{ + .dir = self.dir.dupe(builder), + .path = builder.dupe(self.path), + }; + } +}; + +pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 { + // TODO this logic can disappear if cpu model + features becomes part of the target triple + const all_features = cpu.arch.allFeaturesList(); + var populated_cpu_features = cpu.model.features; + populated_cpu_features.populateDependencies(all_features); + + if (populated_cpu_features.eql(cpu.features)) { + // The CPU name alone is sufficient. + return cpu.model.name; + } else { + var mcpu_buffer = ArrayList(u8).init(allocator); + try mcpu_buffer.appendSlice(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 = 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}); + } + } + + return try mcpu_buffer.toOwnedSlice(); + } +} + +test "dupePkg()" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var builder = try Build.create( + arena.allocator(), + "test", + "test", + "test", + "test", + ); + defer builder.destroy(); + + var pkg_dep = Pkg{ + .name = "pkg_dep", + .source = .{ .path = "/not/a/pkg_dep.zig" }, + }; + var pkg_top = Pkg{ + .name = "pkg_top", + .source = .{ .path = "/not/a/pkg_top.zig" }, + .dependencies = &[_]Pkg{pkg_dep}, + }; + const duped = builder.dupePkg(pkg_top); + + const original_deps = pkg_top.dependencies.?; + const dupe_deps = duped.dependencies.?; + + // probably the same top level package details + try std.testing.expectEqualStrings(pkg_top.name, duped.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(duped.name.ptr != pkg_top.name.ptr); + try std.testing.expect(duped.source.path.ptr != pkg_top.source.path.ptr); + try std.testing.expect(dupe_deps[0].name.ptr != pkg_dep.name.ptr); + try std.testing.expect(dupe_deps[0].source.path.ptr != pkg_dep.source.path.ptr); +} + +test { + _ = CheckFileStep; + _ = CheckObjectStep; + _ = EmulatableRunStep; + _ = FmtStep; + _ = InstallArtifactStep; + _ = InstallDirStep; + _ = InstallFileStep; + _ = InstallRawStep; + _ = LibExeObjStep; + _ = LogStep; + _ = OptionsStep; + _ = RemoveDirStep; + _ = RunStep; + _ = TranslateCStep; + _ = WriteFileStep; +} diff --git a/lib/std/Build/CheckFileStep.zig b/lib/std/Build/CheckFileStep.zig new file mode 100644 index 0000000000..3b8cfe5263 --- /dev/null +++ b/lib/std/Build/CheckFileStep.zig @@ -0,0 +1,51 @@ +const std = @import("../std.zig"); +const Step = std.Build.Step; +const fs = std.fs; +const mem = std.mem; + +const CheckFileStep = @This(); + +pub const base_id = .check_file; + +step: Step, +builder: *std.Build, +expected_matches: []const []const u8, +source: std.Build.FileSource, +max_bytes: usize = 20 * 1024 * 1024, + +pub fn create( + builder: *std.Build, + source: std.Build.FileSource, + expected_matches: []const []const u8, +) *CheckFileStep { + const self = builder.allocator.create(CheckFileStep) catch unreachable; + self.* = CheckFileStep{ + .builder = builder, + .step = Step.init(.check_file, "CheckFile", builder.allocator, make), + .source = source.dupe(builder), + .expected_matches = builder.dupeStrings(expected_matches), + }; + self.source.addStepDependencies(&self.step); + return self; +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(CheckFileStep, "step", step); + + const src_path = self.source.getPath(self.builder); + const contents = try fs.cwd().readFileAlloc(self.builder.allocator, src_path, self.max_bytes); + + for (self.expected_matches) |expected_match| { + if (mem.indexOf(u8, contents, expected_match) == null) { + std.debug.print( + \\ + \\========= Expected to find: =================== + \\{s} + \\========= But file does not contain it: ======= + \\{s} + \\ + , .{ expected_match, contents }); + return error.TestFailed; + } + } +} diff --git a/lib/std/Build/CheckObjectStep.zig b/lib/std/Build/CheckObjectStep.zig new file mode 100644 index 0000000000..7907be1787 --- /dev/null +++ b/lib/std/Build/CheckObjectStep.zig @@ -0,0 +1,1024 @@ +const std = @import("../std.zig"); +const assert = std.debug.assert; +const fs = std.fs; +const macho = std.macho; +const math = std.math; +const mem = std.mem; +const testing = std.testing; + +const CheckObjectStep = @This(); + +const Allocator = mem.Allocator; +const Step = std.Build.Step; +const EmulatableRunStep = std.Build.EmulatableRunStep; + +pub const base_id = .check_object; + +step: Step, +builder: *std.Build, +source: std.Build.FileSource, +max_bytes: usize = 20 * 1024 * 1024, +checks: std.ArrayList(Check), +dump_symtab: bool = false, +obj_format: std.Target.ObjectFormat, + +pub fn create(builder: *std.Build, source: std.Build.FileSource, obj_format: std.Target.ObjectFormat) *CheckObjectStep { + const gpa = builder.allocator; + const self = gpa.create(CheckObjectStep) catch unreachable; + self.* = .{ + .builder = builder, + .step = Step.init(.check_file, "CheckObject", gpa, make), + .source = source.dupe(builder), + .checks = std.ArrayList(Check).init(gpa), + .obj_format = obj_format, + }; + self.source.addStepDependencies(&self.step); + return self; +} + +/// Runs and (optionally) compares the output of a binary. +/// Asserts `self` was generated from an executable step. +pub fn runAndCompare(self: *CheckObjectStep) *EmulatableRunStep { + const dependencies_len = self.step.dependencies.items.len; + assert(dependencies_len > 0); + const exe_step = self.step.dependencies.items[dependencies_len - 1]; + const exe = exe_step.cast(std.Build.LibExeObjStep).?; + const emulatable_step = EmulatableRunStep.create(self.builder, "EmulatableRun", exe); + emulatable_step.step.dependOn(&self.step); + return emulatable_step; +} + +/// There two types of actions currently suported: +/// * `.match` - is the main building block of standard matchers with optional eat-all token `{*}` +/// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature +/// i.e., it won't really handle edge cases/nontrivial examples. But given that we do want to use +/// it mainly to test the output of our object format parser-dumpers when testing the linkers, etc. +/// it should be plenty useful in its current form. +/// * `.compute_cmp` - can be used to perform an operation on the extracted global variables +/// using the MatchAction. It currently only supports an addition. The operation is required +/// to be specified in Reverse Polish Notation to ease in operator-precedence parsing (well, +/// to avoid any parsing really). +/// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively +/// they could then be added with this simple program `vmaddr entryoff +`. +const Action = struct { + tag: enum { match, not_present, compute_cmp }, + phrase: []const u8, + expected: ?ComputeCompareExpected = null, + + /// Will return true if the `phrase` was found in the `haystack`. + /// Some examples include: + /// + /// LC 0 => will match in its entirety + /// vmaddr {vmaddr} => will match `vmaddr` and then extract the following value as u64 + /// and save under `vmaddr` global name (see `global_vars` param) + /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib` + /// in that order with other letters in between + fn match(act: Action, haystack: []const u8, global_vars: anytype) !bool { + assert(act.tag == .match or act.tag == .not_present); + + var candidate_var: ?struct { name: []const u8, value: u64 } = null; + var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " "); + var needle_it = mem.tokenize(u8, mem.trim(u8, act.phrase, " "), " "); + + while (needle_it.next()) |needle_tok| { + const hay_tok = hay_it.next() orelse return false; + + if (mem.indexOf(u8, needle_tok, "{*}")) |index| { + // We have fuzzy matchers within the search pattern, so we match substrings. + var start = index; + var n_tok = needle_tok; + var h_tok = hay_tok; + while (true) { + n_tok = n_tok[start + 3 ..]; + const inner = if (mem.indexOf(u8, n_tok, "{*}")) |sub_end| + n_tok[0..sub_end] + else + n_tok; + if (mem.indexOf(u8, h_tok, inner) == null) return false; + start = mem.indexOf(u8, n_tok, "{*}") orelse break; + } + } else if (mem.startsWith(u8, needle_tok, "{")) { + const closing_brace = mem.indexOf(u8, needle_tok, "}") orelse return error.MissingClosingBrace; + if (closing_brace != needle_tok.len - 1) return error.ClosingBraceNotLast; + + const name = needle_tok[1..closing_brace]; + if (name.len == 0) return error.MissingBraceValue; + const value = try std.fmt.parseInt(u64, hay_tok, 16); + candidate_var = .{ + .name = name, + .value = value, + }; + } else { + if (!mem.eql(u8, hay_tok, needle_tok)) return false; + } + } + + if (candidate_var) |v| { + try global_vars.putNoClobber(v.name, v.value); + } + + return true; + } + + /// Will return true if the `phrase` is correctly parsed into an RPN program and + /// its reduced, computed value compares using `op` with the expected value, either + /// a literal or another extracted variable. + fn computeCmp(act: Action, gpa: Allocator, global_vars: anytype) !bool { + var op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa); + var values = std.ArrayList(u64).init(gpa); + + var it = mem.tokenize(u8, act.phrase, " "); + while (it.next()) |next| { + if (mem.eql(u8, next, "+")) { + try op_stack.append(.add); + } else if (mem.eql(u8, next, "-")) { + try op_stack.append(.sub); + } else if (mem.eql(u8, next, "%")) { + try op_stack.append(.mod); + } else if (mem.eql(u8, next, "*")) { + try op_stack.append(.mul); + } else { + const val = std.fmt.parseInt(u64, next, 0) catch blk: { + break :blk global_vars.get(next) orelse { + std.debug.print( + \\ + \\========= Variable was not extracted: =========== + \\{s} + \\ + , .{next}); + return error.UnknownVariable; + }; + }; + try values.append(val); + } + } + + var op_i: usize = 1; + var reduced: u64 = values.items[0]; + for (op_stack.items) |op| { + const other = values.items[op_i]; + switch (op) { + .add => { + reduced += other; + }, + .sub => { + reduced -= other; + }, + .mod => { + reduced %= other; + }, + .mul => { + reduced *= other; + }, + } + op_i += 1; + } + + const exp_value = switch (act.expected.?.value) { + .variable => |name| global_vars.get(name) orelse { + std.debug.print( + \\ + \\========= Variable was not extracted: =========== + \\{s} + \\ + , .{name}); + return error.UnknownVariable; + }, + .literal => |x| x, + }; + return math.compare(reduced, act.expected.?.op, exp_value); + } +}; + +const ComputeCompareExpected = struct { + op: math.CompareOperator, + value: union(enum) { + variable: []const u8, + literal: u64, + }, + + pub fn format( + value: @This(), + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + if (fmt.len != 0) std.fmt.invalidFmtError(fmt, value); + _ = options; + try writer.print("{s} ", .{@tagName(value.op)}); + switch (value.value) { + .variable => |name| try writer.writeAll(name), + .literal => |x| try writer.print("{x}", .{x}), + } + } +}; + +const Check = struct { + builder: *std.Build, + actions: std.ArrayList(Action), + + fn create(b: *std.Build) Check { + return .{ + .builder = b, + .actions = std.ArrayList(Action).init(b.allocator), + }; + } + + fn match(self: *Check, phrase: []const u8) void { + self.actions.append(.{ + .tag = .match, + .phrase = self.builder.dupe(phrase), + }) catch unreachable; + } + + fn notPresent(self: *Check, phrase: []const u8) void { + self.actions.append(.{ + .tag = .not_present, + .phrase = self.builder.dupe(phrase), + }) catch unreachable; + } + + fn computeCmp(self: *Check, phrase: []const u8, expected: ComputeCompareExpected) void { + self.actions.append(.{ + .tag = .compute_cmp, + .phrase = self.builder.dupe(phrase), + .expected = expected, + }) catch unreachable; + } +}; + +/// Creates a new sequence of actions with `phrase` as the first anchor searched phrase. +pub fn checkStart(self: *CheckObjectStep, phrase: []const u8) void { + var new_check = Check.create(self.builder); + new_check.match(phrase); + self.checks.append(new_check) catch unreachable; +} + +/// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)`. +/// Asserts at least one check already exists. +pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void { + assert(self.checks.items.len > 0); + const last = &self.checks.items[self.checks.items.len - 1]; + last.match(phrase); +} + +/// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)` +/// however ensures there is no matching phrase in the output. +/// Asserts at least one check already exists. +pub fn checkNotPresent(self: *CheckObjectStep, phrase: []const u8) void { + assert(self.checks.items.len > 0); + const last = &self.checks.items[self.checks.items.len - 1]; + last.notPresent(phrase); +} + +/// Creates a new check checking specifically symbol table parsed and dumped from the object +/// file. +/// Issuing this check will force parsing and dumping of the symbol table. +pub fn checkInSymtab(self: *CheckObjectStep) void { + self.dump_symtab = true; + const symtab_label = switch (self.obj_format) { + .macho => MachODumper.symtab_label, + else => @panic("TODO other parsers"), + }; + self.checkStart(symtab_label); +} + +/// Creates a new standalone, singular check which allows running simple binary operations +/// on the extracted variables. It will then compare the reduced program with the value of +/// the expected variable. +pub fn checkComputeCompare( + self: *CheckObjectStep, + program: []const u8, + expected: ComputeCompareExpected, +) void { + var new_check = Check.create(self.builder); + new_check.computeCmp(program, expected); + self.checks.append(new_check) catch unreachable; +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(CheckObjectStep, "step", step); + + const gpa = self.builder.allocator; + const src_path = self.source.getPath(self.builder); + const contents = try fs.cwd().readFileAllocOptions( + gpa, + src_path, + self.max_bytes, + null, + @alignOf(u64), + null, + ); + + const output = switch (self.obj_format) { + .macho => try MachODumper.parseAndDump(contents, .{ + .gpa = gpa, + .dump_symtab = self.dump_symtab, + }), + .elf => @panic("TODO elf parser"), + .coff => @panic("TODO coff parser"), + .wasm => try WasmDumper.parseAndDump(contents, .{ + .gpa = gpa, + .dump_symtab = self.dump_symtab, + }), + else => unreachable, + }; + + var vars = std.StringHashMap(u64).init(gpa); + + for (self.checks.items) |chk| { + var it = mem.tokenize(u8, output, "\r\n"); + for (chk.actions.items) |act| { + switch (act.tag) { + .match => { + while (it.next()) |line| { + if (try act.match(line, &vars)) break; + } else { + std.debug.print( + \\ + \\========= Expected to find: ========================== + \\{s} + \\========= But parsed file does not contain it: ======= + \\{s} + \\ + , .{ act.phrase, output }); + return error.TestFailed; + } + }, + .not_present => { + while (it.next()) |line| { + if (try act.match(line, &vars)) { + std.debug.print( + \\ + \\========= Expected not to find: =================== + \\{s} + \\========= But parsed file does contain it: ======== + \\{s} + \\ + , .{ act.phrase, output }); + return error.TestFailed; + } + } + }, + .compute_cmp => { + const res = act.computeCmp(gpa, vars) catch |err| switch (err) { + error.UnknownVariable => { + std.debug.print( + \\========= From parsed file: ===================== + \\{s} + \\ + , .{output}); + return error.TestFailed; + }, + else => |e| return e, + }; + if (!res) { + std.debug.print( + \\ + \\========= Comparison failed for action: =========== + \\{s} {} + \\========= From parsed file: ======================= + \\{s} + \\ + , .{ act.phrase, act.expected.?, output }); + return error.TestFailed; + } + }, + } + } + } +} + +const Opts = struct { + gpa: ?Allocator = null, + dump_symtab: bool = false, +}; + +const MachODumper = struct { + const LoadCommandIterator = macho.LoadCommandIterator; + const symtab_label = "symtab"; + + fn parseAndDump(bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 { + const gpa = opts.gpa orelse unreachable; // MachO dumper requires an allocator + var stream = std.io.fixedBufferStream(bytes); + const reader = stream.reader(); + + const hdr = try reader.readStruct(macho.mach_header_64); + if (hdr.magic != macho.MH_MAGIC_64) { + return error.InvalidMagicNumber; + } + + var output = std.ArrayList(u8).init(gpa); + const writer = output.writer(); + + var symtab: []const macho.nlist_64 = undefined; + var strtab: []const u8 = undefined; + var sections = std.ArrayList(macho.section_64).init(gpa); + var imports = std.ArrayList([]const u8).init(gpa); + + var it = LoadCommandIterator{ + .ncmds = hdr.ncmds, + .buffer = bytes[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], + }; + var i: usize = 0; + while (it.next()) |cmd| { + switch (cmd.cmd()) { + .SEGMENT_64 => { + const seg = cmd.cast(macho.segment_command_64).?; + try sections.ensureUnusedCapacity(seg.nsects); + for (cmd.getSections()) |sect| { + sections.appendAssumeCapacity(sect); + } + }, + .SYMTAB => if (opts.dump_symtab) { + const lc = cmd.cast(macho.symtab_command).?; + symtab = @ptrCast( + [*]const macho.nlist_64, + @alignCast(@alignOf(macho.nlist_64), &bytes[lc.symoff]), + )[0..lc.nsyms]; + strtab = bytes[lc.stroff..][0..lc.strsize]; + }, + .LOAD_DYLIB, + .LOAD_WEAK_DYLIB, + .REEXPORT_DYLIB, + => { + try imports.append(cmd.getDylibPathName()); + }, + else => {}, + } + + try dumpLoadCommand(cmd, i, writer); + try writer.writeByte('\n'); + + i += 1; + } + + if (opts.dump_symtab) { + try writer.print("{s}\n", .{symtab_label}); + for (symtab) |sym| { + if (sym.stab()) continue; + const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); + if (sym.sect()) { + const sect = sections.items[sym.n_sect - 1]; + try writer.print("{x} ({s},{s})", .{ + sym.n_value, + sect.segName(), + sect.sectName(), + }); + if (sym.ext()) { + try writer.writeAll(" external"); + } + try writer.print(" {s}\n", .{sym_name}); + } else if (sym.undf()) { + const ordinal = @divTrunc(@bitCast(i16, sym.n_desc), macho.N_SYMBOL_RESOLVER); + const import_name = blk: { + if (ordinal <= 0) { + if (ordinal == macho.BIND_SPECIAL_DYLIB_SELF) + break :blk "self import"; + if (ordinal == macho.BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE) + break :blk "main executable"; + if (ordinal == macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP) + break :blk "flat lookup"; + unreachable; + } + const full_path = imports.items[@bitCast(u16, ordinal) - 1]; + const basename = fs.path.basename(full_path); + assert(basename.len > 0); + const ext = mem.lastIndexOfScalar(u8, basename, '.') orelse basename.len; + break :blk basename[0..ext]; + }; + try writer.writeAll("(undefined)"); + if (sym.weakRef()) { + try writer.writeAll(" weak"); + } + if (sym.ext()) { + try writer.writeAll(" external"); + } + try writer.print(" {s} (from {s})\n", .{ + sym_name, + import_name, + }); + } else unreachable; + } + } + + return output.toOwnedSlice(); + } + + fn dumpLoadCommand(lc: macho.LoadCommandIterator.LoadCommand, index: usize, writer: anytype) !void { + // print header first + try writer.print( + \\LC {d} + \\cmd {s} + \\cmdsize {d} + , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); + + switch (lc.cmd()) { + .SEGMENT_64 => { + const seg = lc.cast(macho.segment_command_64).?; + try writer.writeByte('\n'); + try writer.print( + \\segname {s} + \\vmaddr {x} + \\vmsize {x} + \\fileoff {x} + \\filesz {x} + , .{ + seg.segName(), + seg.vmaddr, + seg.vmsize, + seg.fileoff, + seg.filesize, + }); + + for (lc.getSections()) |sect| { + try writer.writeByte('\n'); + try writer.print( + \\sectname {s} + \\addr {x} + \\size {x} + \\offset {x} + \\align {x} + , .{ + sect.sectName(), + sect.addr, + sect.size, + sect.offset, + sect.@"align", + }); + } + }, + + .ID_DYLIB, + .LOAD_DYLIB, + .LOAD_WEAK_DYLIB, + .REEXPORT_DYLIB, + => { + const dylib = lc.cast(macho.dylib_command).?; + try writer.writeByte('\n'); + try writer.print( + \\name {s} + \\timestamp {d} + \\current version {x} + \\compatibility version {x} + , .{ + lc.getDylibPathName(), + dylib.dylib.timestamp, + dylib.dylib.current_version, + dylib.dylib.compatibility_version, + }); + }, + + .MAIN => { + const main = lc.cast(macho.entry_point_command).?; + try writer.writeByte('\n'); + try writer.print( + \\entryoff {x} + \\stacksize {x} + , .{ main.entryoff, main.stacksize }); + }, + + .RPATH => { + try writer.writeByte('\n'); + try writer.print( + \\path {s} + , .{ + lc.getRpathPathName(), + }); + }, + + .UUID => { + const uuid = lc.cast(macho.uuid_command).?; + try writer.writeByte('\n'); + try writer.print("uuid {x}", .{std.fmt.fmtSliceHexLower(&uuid.uuid)}); + }, + + .DATA_IN_CODE, + .FUNCTION_STARTS, + .CODE_SIGNATURE, + => { + const llc = lc.cast(macho.linkedit_data_command).?; + try writer.writeByte('\n'); + try writer.print( + \\dataoff {x} + \\datasize {x} + , .{ llc.dataoff, llc.datasize }); + }, + + .DYLD_INFO_ONLY => { + const dlc = lc.cast(macho.dyld_info_command).?; + try writer.writeByte('\n'); + try writer.print( + \\rebaseoff {x} + \\rebasesize {x} + \\bindoff {x} + \\bindsize {x} + \\weakbindoff {x} + \\weakbindsize {x} + \\lazybindoff {x} + \\lazybindsize {x} + \\exportoff {x} + \\exportsize {x} + , .{ + dlc.rebase_off, + dlc.rebase_size, + dlc.bind_off, + dlc.bind_size, + dlc.weak_bind_off, + dlc.weak_bind_size, + dlc.lazy_bind_off, + dlc.lazy_bind_size, + dlc.export_off, + dlc.export_size, + }); + }, + + .SYMTAB => { + const slc = lc.cast(macho.symtab_command).?; + try writer.writeByte('\n'); + try writer.print( + \\symoff {x} + \\nsyms {x} + \\stroff {x} + \\strsize {x} + , .{ + slc.symoff, + slc.nsyms, + slc.stroff, + slc.strsize, + }); + }, + + .DYSYMTAB => { + const dlc = lc.cast(macho.dysymtab_command).?; + try writer.writeByte('\n'); + try writer.print( + \\ilocalsym {x} + \\nlocalsym {x} + \\iextdefsym {x} + \\nextdefsym {x} + \\iundefsym {x} + \\nundefsym {x} + \\indirectsymoff {x} + \\nindirectsyms {x} + , .{ + dlc.ilocalsym, + dlc.nlocalsym, + dlc.iextdefsym, + dlc.nextdefsym, + dlc.iundefsym, + dlc.nundefsym, + dlc.indirectsymoff, + dlc.nindirectsyms, + }); + }, + + else => {}, + } + } +}; + +const WasmDumper = struct { + const symtab_label = "symbols"; + + fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 { + const gpa = opts.gpa orelse unreachable; // Wasm dumper requires an allocator + if (opts.dump_symtab) { + @panic("TODO: Implement symbol table parsing and dumping"); + } + + var fbs = std.io.fixedBufferStream(bytes); + const reader = fbs.reader(); + + const buf = try reader.readBytesNoEof(8); + if (!mem.eql(u8, buf[0..4], &std.wasm.magic)) { + return error.InvalidMagicByte; + } + if (!mem.eql(u8, buf[4..], &std.wasm.version)) { + return error.UnsupportedWasmVersion; + } + + var output = std.ArrayList(u8).init(gpa); + errdefer output.deinit(); + const writer = output.writer(); + + while (reader.readByte()) |current_byte| { + const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch |err| { + std.debug.print("Found invalid section id '{d}'\n", .{current_byte}); + return err; + }; + + const section_length = try std.leb.readULEB128(u32, reader); + try parseAndDumpSection(section, bytes[fbs.pos..][0..section_length], writer); + fbs.pos += section_length; + } else |_| {} // reached end of stream + + return output.toOwnedSlice(); + } + + fn parseAndDumpSection(section: std.wasm.Section, data: []const u8, writer: anytype) !void { + var fbs = std.io.fixedBufferStream(data); + const reader = fbs.reader(); + + try writer.print( + \\Section {s} + \\size {d} + , .{ @tagName(section), data.len }); + + switch (section) { + .type, + .import, + .function, + .table, + .memory, + .global, + .@"export", + .element, + .code, + .data, + => { + const entries = try std.leb.readULEB128(u32, reader); + try writer.print("\nentries {d}\n", .{entries}); + try dumpSection(section, data[fbs.pos..], entries, writer); + }, + .custom => { + const name_length = try std.leb.readULEB128(u32, reader); + const name = data[fbs.pos..][0..name_length]; + fbs.pos += name_length; + try writer.print("\nname {s}\n", .{name}); + + if (mem.eql(u8, name, "name")) { + try parseDumpNames(reader, writer, data); + } else if (mem.eql(u8, name, "producers")) { + try parseDumpProducers(reader, writer, data); + } else if (mem.eql(u8, name, "target_features")) { + try parseDumpFeatures(reader, writer, data); + } + // TODO: Implement parsing and dumping other custom sections (such as relocations) + }, + .start => { + const start = try std.leb.readULEB128(u32, reader); + try writer.print("\nstart {d}\n", .{start}); + }, + else => {}, // skip unknown sections + } + } + + fn dumpSection(section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void { + var fbs = std.io.fixedBufferStream(data); + const reader = fbs.reader(); + + switch (section) { + .type => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + const func_type = try reader.readByte(); + if (func_type != std.wasm.function_type) { + std.debug.print("Expected function type, found byte '{d}'\n", .{func_type}); + return error.UnexpectedByte; + } + const params = try std.leb.readULEB128(u32, reader); + try writer.print("params {d}\n", .{params}); + var index: u32 = 0; + while (index < params) : (index += 1) { + try parseDumpType(std.wasm.Valtype, reader, writer); + } else index = 0; + const returns = try std.leb.readULEB128(u32, reader); + try writer.print("returns {d}\n", .{returns}); + while (index < returns) : (index += 1) { + try parseDumpType(std.wasm.Valtype, reader, writer); + } + } + }, + .import => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + const module_name_len = try std.leb.readULEB128(u32, reader); + const module_name = data[fbs.pos..][0..module_name_len]; + fbs.pos += module_name_len; + const name_len = try std.leb.readULEB128(u32, reader); + const name = data[fbs.pos..][0..name_len]; + fbs.pos += name_len; + + const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch |err| { + std.debug.print("Invalid import kind\n", .{}); + return err; + }; + + try writer.print( + \\module {s} + \\name {s} + \\kind {s} + , .{ module_name, name, @tagName(kind) }); + try writer.writeByte('\n'); + switch (kind) { + .function => { + try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); + }, + .memory => { + try parseDumpLimits(reader, writer); + }, + .global => { + try parseDumpType(std.wasm.Valtype, reader, writer); + try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)}); + }, + .table => { + try parseDumpType(std.wasm.RefType, reader, writer); + try parseDumpLimits(reader, writer); + }, + } + } + }, + .function => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); + } + }, + .table => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + try parseDumpType(std.wasm.RefType, reader, writer); + try parseDumpLimits(reader, writer); + } + }, + .memory => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + try parseDumpLimits(reader, writer); + } + }, + .global => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + try parseDumpType(std.wasm.Valtype, reader, writer); + try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)}); + try parseDumpInit(reader, writer); + } + }, + .@"export" => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + const name_len = try std.leb.readULEB128(u32, reader); + const name = data[fbs.pos..][0..name_len]; + fbs.pos += name_len; + const kind_byte = try std.leb.readULEB128(u8, reader); + const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch |err| { + std.debug.print("invalid export kind value '{d}'\n", .{kind_byte}); + return err; + }; + const index = try std.leb.readULEB128(u32, reader); + try writer.print( + \\name {s} + \\kind {s} + \\index {d} + , .{ name, @tagName(kind), index }); + try writer.writeByte('\n'); + } + }, + .element => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)}); + try parseDumpInit(reader, writer); + + const function_indexes = try std.leb.readULEB128(u32, reader); + var function_index: u32 = 0; + try writer.print("indexes {d}\n", .{function_indexes}); + while (function_index < function_indexes) : (function_index += 1) { + try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); + } + } + }, + .code => {}, // code section is considered opaque to linker + .data => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + const index = try std.leb.readULEB128(u32, reader); + try writer.print("memory index 0x{x}\n", .{index}); + try parseDumpInit(reader, writer); + const size = try std.leb.readULEB128(u32, reader); + try writer.print("size {d}\n", .{size}); + try reader.skipBytes(size, .{}); // we do not care about the content of the segments + } + }, + else => unreachable, + } + } + + fn parseDumpType(comptime WasmType: type, reader: anytype, writer: anytype) !void { + const type_byte = try reader.readByte(); + const valtype = std.meta.intToEnum(WasmType, type_byte) catch |err| { + std.debug.print("Invalid wasm type value '{d}'\n", .{type_byte}); + return err; + }; + try writer.print("type {s}\n", .{@tagName(valtype)}); + } + + fn parseDumpLimits(reader: anytype, writer: anytype) !void { + const flags = try std.leb.readULEB128(u8, reader); + const min = try std.leb.readULEB128(u32, reader); + + try writer.print("min {x}\n", .{min}); + if (flags != 0) { + try writer.print("max {x}\n", .{try std.leb.readULEB128(u32, reader)}); + } + } + + fn parseDumpInit(reader: anytype, writer: anytype) !void { + const byte = try std.leb.readULEB128(u8, reader); + const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch |err| { + std.debug.print("invalid wasm opcode '{d}'\n", .{byte}); + return err; + }; + switch (opcode) { + .i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}), + .i64_const => try writer.print("i64.const {x}\n", .{try std.leb.readILEB128(i64, reader)}), + .f32_const => try writer.print("f32.const {x}\n", .{@bitCast(f32, try reader.readIntLittle(u32))}), + .f64_const => try writer.print("f64.const {x}\n", .{@bitCast(f64, try reader.readIntLittle(u64))}), + .global_get => try writer.print("global.get {x}\n", .{try std.leb.readULEB128(u32, reader)}), + else => unreachable, + } + const end_opcode = try std.leb.readULEB128(u8, reader); + if (end_opcode != std.wasm.opcode(.end)) { + std.debug.print("expected 'end' opcode in init expression\n", .{}); + return error.MissingEndOpcode; + } + } + + fn parseDumpNames(reader: anytype, writer: anytype, data: []const u8) !void { + while (reader.context.pos < data.len) { + try parseDumpType(std.wasm.NameSubsection, reader, writer); + const size = try std.leb.readULEB128(u32, reader); + const entries = try std.leb.readULEB128(u32, reader); + try writer.print( + \\size {d} + \\names {d} + , .{ size, entries }); + try writer.writeByte('\n'); + var i: u32 = 0; + while (i < entries) : (i += 1) { + const index = try std.leb.readULEB128(u32, reader); + const name_len = try std.leb.readULEB128(u32, reader); + const pos = reader.context.pos; + const name = data[pos..][0..name_len]; + reader.context.pos += name_len; + + try writer.print( + \\index {d} + \\name {s} + , .{ index, name }); + try writer.writeByte('\n'); + } + } + } + + fn parseDumpProducers(reader: anytype, writer: anytype, data: []const u8) !void { + const field_count = try std.leb.readULEB128(u32, reader); + try writer.print("fields {d}\n", .{field_count}); + var current_field: u32 = 0; + while (current_field < field_count) : (current_field += 1) { + const field_name_length = try std.leb.readULEB128(u32, reader); + const field_name = data[reader.context.pos..][0..field_name_length]; + reader.context.pos += field_name_length; + + const value_count = try std.leb.readULEB128(u32, reader); + try writer.print( + \\field_name {s} + \\values {d} + , .{ field_name, value_count }); + try writer.writeByte('\n'); + var current_value: u32 = 0; + while (current_value < value_count) : (current_value += 1) { + const value_length = try std.leb.readULEB128(u32, reader); + const value = data[reader.context.pos..][0..value_length]; + reader.context.pos += value_length; + + const version_length = try std.leb.readULEB128(u32, reader); + const version = data[reader.context.pos..][0..version_length]; + reader.context.pos += version_length; + + try writer.print( + \\value_name {s} + \\version {s} + , .{ value, version }); + try writer.writeByte('\n'); + } + } + } + + fn parseDumpFeatures(reader: anytype, writer: anytype, data: []const u8) !void { + const feature_count = try std.leb.readULEB128(u32, reader); + try writer.print("features {d}\n", .{feature_count}); + + var index: u32 = 0; + while (index < feature_count) : (index += 1) { + const prefix_byte = try std.leb.readULEB128(u8, reader); + const name_length = try std.leb.readULEB128(u32, reader); + const feature_name = data[reader.context.pos..][0..name_length]; + reader.context.pos += name_length; + + try writer.print("{c} {s}\n", .{ prefix_byte, feature_name }); + } + } +}; diff --git a/lib/std/Build/ConfigHeaderStep.zig b/lib/std/Build/ConfigHeaderStep.zig new file mode 100644 index 0000000000..b961227c9c --- /dev/null +++ b/lib/std/Build/ConfigHeaderStep.zig @@ -0,0 +1,287 @@ +const std = @import("../std.zig"); +const ConfigHeaderStep = @This(); +const Step = std.Build.Step; + +pub const base_id: Step.Id = .config_header; + +pub const Style = enum { + /// The configure format supported by autotools. It uses `#undef foo` to + /// mark lines that can be substituted with different values. + autoconf, + /// The configure format supported by CMake. It uses `@@FOO@@` and + /// `#cmakedefine` for template substitution. + cmake, +}; + +pub const Value = union(enum) { + undef, + defined, + boolean: bool, + int: i64, + ident: []const u8, + string: []const u8, +}; + +step: Step, +builder: *std.Build, +source: std.Build.FileSource, +style: Style, +values: std.StringHashMap(Value), +max_bytes: usize = 2 * 1024 * 1024, +output_dir: []const u8, +output_basename: []const u8, + +pub fn create(builder: *std.Build, source: std.Build.FileSource, style: Style) *ConfigHeaderStep { + const self = builder.allocator.create(ConfigHeaderStep) catch @panic("OOM"); + const name = builder.fmt("configure header {s}", .{source.getDisplayName()}); + self.* = .{ + .builder = builder, + .step = Step.init(base_id, name, builder.allocator, make), + .source = source, + .style = style, + .values = std.StringHashMap(Value).init(builder.allocator), + .output_dir = undefined, + .output_basename = "config.h", + }; + switch (source) { + .path => |p| { + const basename = std.fs.path.basename(p); + if (std.mem.endsWith(u8, basename, ".h.in")) { + self.output_basename = basename[0 .. basename.len - 3]; + } + }, + else => {}, + } + return self; +} + +pub fn addValues(self: *ConfigHeaderStep, values: anytype) void { + return addValuesInner(self, values) catch @panic("OOM"); +} + +fn addValuesInner(self: *ConfigHeaderStep, values: anytype) !void { + inline for (@typeInfo(@TypeOf(values)).Struct.fields) |field| { + switch (@typeInfo(field.type)) { + .Null => { + try self.values.put(field.name, .undef); + }, + .Void => { + try self.values.put(field.name, .defined); + }, + .Bool => { + try self.values.put(field.name, .{ .boolean = @field(values, field.name) }); + }, + .ComptimeInt => { + try self.values.put(field.name, .{ .int = @field(values, field.name) }); + }, + .EnumLiteral => { + try self.values.put(field.name, .{ .ident = @tagName(@field(values, field.name)) }); + }, + .Pointer => |ptr| { + switch (@typeInfo(ptr.child)) { + .Array => |array| { + if (ptr.size == .One and array.child == u8) { + try self.values.put(field.name, .{ .string = @field(values, field.name) }); + continue; + } + }, + else => {}, + } + + @compileError("unsupported ConfigHeaderStep value type: " ++ + @typeName(field.type)); + }, + else => @compileError("unsupported ConfigHeaderStep value type: " ++ + @typeName(field.type)), + } + } +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(ConfigHeaderStep, "step", step); + const gpa = self.builder.allocator; + const src_path = self.source.getPath(self.builder); + const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); + + // The cache is used here not really as a way to speed things up - because writing + // the data to a file would probably be very fast - but as a way to find a canonical + // location to put build artifacts. + + // If, for example, a hard-coded path was used as the location to put ConfigHeaderStep + // files, then two ConfigHeaderStep executing in parallel might clobber each other. + + // TODO port the cache system from the compiler to zig std lib. Until then + // we construct the path directly, and no "cache hit" detection happens; + // the files are always written. + // Note there is very similar code over in WriteFileStep + const Hasher = std.crypto.auth.siphash.SipHash128(1, 3); + // Random bytes to make ConfigHeaderStep unique. Refresh this with new + // random bytes when ConfigHeaderStep implementation is modified in a + // non-backwards-compatible way. + var hash = Hasher.init("X1pQzdDt91Zlh7Eh"); + hash.update(self.source.getDisplayName()); + hash.update(contents); + + var digest: [16]u8 = undefined; + hash.final(&digest); + var hash_basename: [digest.len * 2]u8 = undefined; + _ = std.fmt.bufPrint( + &hash_basename, + "{s}", + .{std.fmt.fmtSliceHexLower(&digest)}, + ) catch unreachable; + + self.output_dir = try std.fs.path.join(gpa, &[_][]const u8{ + self.builder.cache_root, "o", &hash_basename, + }); + var dir = std.fs.cwd().makeOpenPath(self.output_dir, .{}) catch |err| { + std.debug.print("unable to make path {s}: {s}\n", .{ self.output_dir, @errorName(err) }); + return err; + }; + defer dir.close(); + + var values_copy = try self.values.clone(); + defer values_copy.deinit(); + + var output = std.ArrayList(u8).init(gpa); + defer output.deinit(); + try output.ensureTotalCapacity(contents.len); + + try output.appendSlice("/* This file was generated by ConfigHeaderStep using the Zig Build System. */\n"); + + switch (self.style) { + .autoconf => try render_autoconf(contents, &output, &values_copy, src_path), + .cmake => try render_cmake(contents, &output, &values_copy, src_path), + } + + try dir.writeFile(self.output_basename, output.items); +} + +fn render_autoconf( + contents: []const u8, + output: *std.ArrayList(u8), + values_copy: *std.StringHashMap(Value), + src_path: []const u8, +) !void { + var any_errors = false; + var line_index: u32 = 0; + var line_it = std.mem.split(u8, contents, "\n"); + while (line_it.next()) |line| : (line_index += 1) { + if (!std.mem.startsWith(u8, line, "#")) { + try output.appendSlice(line); + try output.appendSlice("\n"); + continue; + } + var it = std.mem.tokenize(u8, line[1..], " \t\r"); + const undef = it.next().?; + if (!std.mem.eql(u8, undef, "undef")) { + try output.appendSlice(line); + try output.appendSlice("\n"); + continue; + } + const name = it.rest(); + const kv = values_copy.fetchRemove(name) orelse { + std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{ + src_path, line_index + 1, name, + }); + any_errors = true; + continue; + }; + try renderValue(output, name, kv.value); + } + + { + var it = values_copy.iterator(); + while (it.next()) |entry| { + const name = entry.key_ptr.*; + std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name }); + } + } + + if (any_errors) { + return error.HeaderConfigFailed; + } +} + +fn render_cmake( + contents: []const u8, + output: *std.ArrayList(u8), + values_copy: *std.StringHashMap(Value), + src_path: []const u8, +) !void { + var any_errors = false; + var line_index: u32 = 0; + var line_it = std.mem.split(u8, contents, "\n"); + while (line_it.next()) |line| : (line_index += 1) { + if (!std.mem.startsWith(u8, line, "#")) { + try output.appendSlice(line); + try output.appendSlice("\n"); + continue; + } + var it = std.mem.tokenize(u8, line[1..], " \t\r"); + const cmakedefine = it.next().?; + if (!std.mem.eql(u8, cmakedefine, "cmakedefine")) { + try output.appendSlice(line); + try output.appendSlice("\n"); + continue; + } + const name = it.next() orelse { + std.debug.print("{s}:{d}: error: missing define name\n", .{ + src_path, line_index + 1, + }); + any_errors = true; + continue; + }; + const kv = values_copy.fetchRemove(name) orelse { + std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{ + src_path, line_index + 1, name, + }); + any_errors = true; + continue; + }; + try renderValue(output, name, kv.value); + } + + { + var it = values_copy.iterator(); + while (it.next()) |entry| { + const name = entry.key_ptr.*; + std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name }); + } + } + + if (any_errors) { + return error.HeaderConfigFailed; + } +} + +fn renderValue(output: *std.ArrayList(u8), name: []const u8, value: Value) !void { + switch (value) { + .undef => { + try output.appendSlice("/* #undef "); + try output.appendSlice(name); + try output.appendSlice(" */\n"); + }, + .defined => { + try output.appendSlice("#define "); + try output.appendSlice(name); + try output.appendSlice("\n"); + }, + .boolean => |b| { + try output.appendSlice("#define "); + try output.appendSlice(name); + try output.appendSlice(" "); + try output.appendSlice(if (b) "true\n" else "false\n"); + }, + .int => |i| { + try output.writer().print("#define {s} {d}\n", .{ name, i }); + }, + .ident => |ident| { + try output.writer().print("#define {s} {s}\n", .{ name, ident }); + }, + .string => |string| { + // TODO: use C-specific escaping instead of zig string literals + try output.writer().print("#define {s} \"{}\"\n", .{ name, std.zig.fmtEscapes(string) }); + }, + } +} diff --git a/lib/std/Build/EmulatableRunStep.zig b/lib/std/Build/EmulatableRunStep.zig new file mode 100644 index 0000000000..b7b12d791f --- /dev/null +++ b/lib/std/Build/EmulatableRunStep.zig @@ -0,0 +1,213 @@ +//! Unlike `RunStep` this step will provide emulation, when enabled, to run foreign binaries. +//! When a binary is foreign, but emulation for the target is disabled, the specified binary +//! will not be run and therefore also not validated against its output. +//! This step can be useful when wishing to run a built binary on multiple platforms, +//! without having to verify if it's possible to be ran against. + +const std = @import("../std.zig"); +const Step = std.Build.Step; +const LibExeObjStep = std.Build.LibExeObjStep; +const RunStep = std.Build.RunStep; + +const fs = std.fs; +const process = std.process; +const EnvMap = process.EnvMap; + +const EmulatableRunStep = @This(); + +pub const base_id = .emulatable_run; + +const max_stdout_size = 1 * 1024 * 1024; // 1 MiB + +step: Step, +builder: *std.Build, + +/// The artifact (executable) to be run by this step +exe: *LibExeObjStep, + +/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution +expected_exit_code: ?u8 = 0, + +/// Override this field to modify the environment +env_map: ?*EnvMap, + +/// Set this to modify the current working directory +cwd: ?[]const u8, + +stdout_action: RunStep.StdIoAction = .inherit, +stderr_action: RunStep.StdIoAction = .inherit, + +/// When set to true, hides the warning of skipping a foreign binary which cannot be run on the host +/// or through emulation. +hide_foreign_binaries_warning: bool, + +/// Creates a step that will execute the given artifact. This step will allow running the +/// binary through emulation when any of the emulation options such as `enable_rosetta` are set to true. +/// When set to false, and the binary is foreign, running the executable is skipped. +/// Asserts given artifact is an executable. +pub fn create(builder: *std.Build, name: []const u8, artifact: *LibExeObjStep) *EmulatableRunStep { + std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe); + const self = builder.allocator.create(EmulatableRunStep) catch unreachable; + + const option_name = "hide-foreign-warnings"; + const hide_warnings = if (builder.available_options_map.get(option_name) == null) warn: { + break :warn builder.option(bool, option_name, "Hide the warning when a foreign binary which is incompatible is skipped") orelse false; + } else false; + + self.* = .{ + .builder = builder, + .step = Step.init(.emulatable_run, name, builder.allocator, make), + .exe = artifact, + .env_map = null, + .cwd = null, + .hide_foreign_binaries_warning = hide_warnings, + }; + self.step.dependOn(&artifact.step); + + return self; +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(EmulatableRunStep, "step", step); + const host_info = self.builder.host; + + var argv_list = std.ArrayList([]const u8).init(self.builder.allocator); + defer argv_list.deinit(); + + const need_cross_glibc = self.exe.target.isGnuLibC() and self.exe.is_linking_libc; + switch (host_info.getExternalExecutor(self.exe.target_info, .{ + .qemu_fixes_dl = need_cross_glibc and self.builder.glibc_runtimes_dir != null, + .link_libc = self.exe.is_linking_libc, + })) { + .native => {}, + .rosetta => if (!self.builder.enable_rosetta) return warnAboutForeignBinaries(self), + .wine => |bin_name| if (self.builder.enable_wine) { + try argv_list.append(bin_name); + } else return, + .qemu => |bin_name| if (self.builder.enable_qemu) { + const glibc_dir_arg = if (need_cross_glibc) + self.builder.glibc_runtimes_dir orelse return + else + null; + try argv_list.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 + // "x86" which is why we do it manually here. + const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}"; + const cpu_arch = self.exe.target.getCpuArch(); + const os_tag = self.exe.target.getOsTag(); + const abi = self.exe.target.getAbi(); + const cpu_arch_name: []const u8 = if (cpu_arch == .x86) + "i686" + else + @tagName(cpu_arch); + const full_dir = try std.fmt.allocPrint(self.builder.allocator, fmt_str, .{ + dir, cpu_arch_name, @tagName(os_tag), @tagName(abi), + }); + + try argv_list.append("-L"); + try argv_list.append(full_dir); + } + } else return warnAboutForeignBinaries(self), + .darling => |bin_name| if (self.builder.enable_darling) { + try argv_list.append(bin_name); + } else return warnAboutForeignBinaries(self), + .wasmtime => |bin_name| if (self.builder.enable_wasmtime) { + try argv_list.append(bin_name); + try argv_list.append("--dir=."); + } else return warnAboutForeignBinaries(self), + else => return warnAboutForeignBinaries(self), + } + + if (self.exe.target.isWindows()) { + // On Windows we don't have rpaths so we have to add .dll search paths to PATH + RunStep.addPathForDynLibsInternal(&self.step, self.builder, self.exe); + } + + const executable_path = self.exe.installed_path orelse self.exe.getOutputSource().getPath(self.builder); + try argv_list.append(executable_path); + + try RunStep.runCommand( + argv_list.items, + self.builder, + self.expected_exit_code, + self.stdout_action, + self.stderr_action, + .Inherit, + self.env_map, + self.cwd, + false, + ); +} + +pub fn expectStdErrEqual(self: *EmulatableRunStep, bytes: []const u8) void { + self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) }; +} + +pub fn expectStdOutEqual(self: *EmulatableRunStep, bytes: []const u8) void { + self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) }; +} + +fn warnAboutForeignBinaries(step: *EmulatableRunStep) void { + if (step.hide_foreign_binaries_warning) return; + const builder = step.builder; + const artifact = step.exe; + + const host_name = builder.host.target.zigTriple(builder.allocator) catch unreachable; + const foreign_name = artifact.target.zigTriple(builder.allocator) catch unreachable; + const target_info = std.zig.system.NativeTargetInfo.detect(artifact.target) catch unreachable; + const need_cross_glibc = artifact.target.isGnuLibC() and artifact.is_linking_libc; + switch (builder.host.getExternalExecutor(target_info, .{ + .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null, + .link_libc = artifact.is_linking_libc, + })) { + .native => unreachable, + .bad_dl => |foreign_dl| { + const host_dl = builder.host.dynamic_linker.get() orelse "(none)"; + std.debug.print("the host system does not appear to be capable of executing binaries from the target because the host dynamic linker is '{s}', while the target dynamic linker is '{s}'. Consider setting the dynamic linker as '{s}'.\n", .{ + host_dl, foreign_dl, host_dl, + }); + }, + .bad_os_or_cpu => { + std.debug.print("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}).\n", .{ + host_name, foreign_name, + }); + }, + .darling => if (!builder.enable_darling) { + std.debug.print( + "the host system ({s}) does not appear to be capable of executing binaries " ++ + "from the target ({s}). Consider enabling darling.\n", + .{ host_name, foreign_name }, + ); + }, + .rosetta => if (!builder.enable_rosetta) { + std.debug.print( + "the host system ({s}) does not appear to be capable of executing binaries " ++ + "from the target ({s}). Consider enabling rosetta.\n", + .{ host_name, foreign_name }, + ); + }, + .wine => if (!builder.enable_wine) { + std.debug.print( + "the host system ({s}) does not appear to be capable of executing binaries " ++ + "from the target ({s}). Consider enabling wine.\n", + .{ host_name, foreign_name }, + ); + }, + .qemu => if (!builder.enable_qemu) { + std.debug.print( + "the host system ({s}) does not appear to be capable of executing binaries " ++ + "from the target ({s}). Consider enabling qemu.\n", + .{ host_name, foreign_name }, + ); + }, + .wasmtime => { + std.debug.print( + "the host system ({s}) does not appear to be capable of executing binaries " ++ + "from the target ({s}). Consider enabling wasmtime.\n", + .{ host_name, foreign_name }, + ); + }, + } +} diff --git a/lib/std/Build/FmtStep.zig b/lib/std/Build/FmtStep.zig new file mode 100644 index 0000000000..44a93dee66 --- /dev/null +++ b/lib/std/Build/FmtStep.zig @@ -0,0 +1,32 @@ +const std = @import("../std.zig"); +const Step = std.Build.Step; +const FmtStep = @This(); + +pub const base_id = .fmt; + +step: Step, +builder: *std.Build, +argv: [][]const u8, + +pub fn create(builder: *std.Build, paths: []const []const u8) *FmtStep { + const self = builder.allocator.create(FmtStep) catch unreachable; + const name = "zig fmt"; + self.* = FmtStep{ + .step = Step.init(.fmt, name, builder.allocator, make), + .builder = builder, + .argv = builder.allocator.alloc([]u8, paths.len + 2) catch unreachable, + }; + + self.argv[0] = builder.zig_exe; + self.argv[1] = "fmt"; + for (paths) |path, i| { + self.argv[2 + i] = builder.pathFromRoot(path); + } + return self; +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(FmtStep, "step", step); + + return self.builder.spawnChild(self.argv); +} diff --git a/lib/std/Build/InstallArtifactStep.zig b/lib/std/Build/InstallArtifactStep.zig new file mode 100644 index 0000000000..929b30e935 --- /dev/null +++ b/lib/std/Build/InstallArtifactStep.zig @@ -0,0 +1,85 @@ +const std = @import("../std.zig"); +const Step = std.Build.Step; +const LibExeObjStep = std.Build.LibExeObjStep; +const InstallDir = std.Build.InstallDir; +const InstallArtifactStep = @This(); + +pub const base_id = .install_artifact; + +step: Step, +builder: *std.Build, +artifact: *LibExeObjStep, +dest_dir: InstallDir, +pdb_dir: ?InstallDir, +h_dir: ?InstallDir, + +pub fn create(builder: *std.Build, artifact: *LibExeObjStep) *InstallArtifactStep { + if (artifact.install_step) |s| return s; + + const self = builder.allocator.create(InstallArtifactStep) catch unreachable; + self.* = InstallArtifactStep{ + .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(InstallArtifactStep, "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 LibExeObjStep.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_h_path = builder.getInstallPath(h_dir, self.artifact.out_h_filename); + try builder.updateFile(self.artifact.getOutputHSource().getPath(builder), full_h_path); + } + self.artifact.installed_path = full_dest_path; +} diff --git a/lib/std/Build/InstallDirStep.zig b/lib/std/Build/InstallDirStep.zig new file mode 100644 index 0000000000..41dbb3e35a --- /dev/null +++ b/lib/std/Build/InstallDirStep.zig @@ -0,0 +1,93 @@ +const std = @import("../std.zig"); +const mem = std.mem; +const fs = std.fs; +const Step = std.Build.Step; +const InstallDir = std.Build.InstallDir; +const InstallDirStep = @This(); +const log = std.log; + +step: Step, +builder: *std.Build, +options: Options, +/// This is used by the build system when a file being installed comes from one +/// package but is being installed by another. +override_source_builder: ?*std.Build = null, + +pub const base_id = .install_dir; + +pub const Options = 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: Options, b: *std.Build) Options { + 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 fn init( + builder: *std.Build, + options: Options, +) 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 src_builder = self.override_source_builder orelse self.builder; + const full_src_dir = src_builder.pathFromRoot(self.options.source_dir); + var src_dir = std.fs.cwd().openIterableDir(full_src_dir, .{}) catch |err| { + log.err("InstallDirStep: unable to open source directory '{s}': {s}", .{ + full_src_dir, @errorName(err), + }); + return error.StepFailed; + }; + 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, + } + } +} diff --git a/lib/std/Build/InstallFileStep.zig b/lib/std/Build/InstallFileStep.zig new file mode 100644 index 0000000000..8c8d8ad2d4 --- /dev/null +++ b/lib/std/Build/InstallFileStep.zig @@ -0,0 +1,40 @@ +const std = @import("../std.zig"); +const Step = std.Build.Step; +const FileSource = std.Build.FileSource; +const InstallDir = std.Build.InstallDir; +const InstallFileStep = @This(); + +pub const base_id = .install_file; + +step: Step, +builder: *std.Build, +source: FileSource, +dir: InstallDir, +dest_rel_path: []const u8, +/// This is used by the build system when a file being installed comes from one +/// package but is being installed by another. +override_source_builder: ?*std.Build = null, + +pub fn init( + builder: *std.Build, + 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 src_builder = self.override_source_builder orelse self.builder; + const full_src_path = self.source.getPath(src_builder); + const full_dest_path = self.builder.getInstallPath(self.dir, self.dest_rel_path); + try self.builder.updateFile(full_src_path, full_dest_path); +} diff --git a/lib/std/Build/InstallRawStep.zig b/lib/std/Build/InstallRawStep.zig new file mode 100644 index 0000000000..08d646ff88 --- /dev/null +++ b/lib/std/Build/InstallRawStep.zig @@ -0,0 +1,110 @@ +//! TODO: Rename this to ObjCopyStep now that it invokes the `zig objcopy` +//! subcommand rather than containing an implementation directly. + +const std = @import("std"); +const InstallRawStep = @This(); + +const Allocator = std.mem.Allocator; +const ArenaAllocator = std.heap.ArenaAllocator; +const ArrayListUnmanaged = std.ArrayListUnmanaged; +const File = std.fs.File; +const InstallDir = std.Build.InstallDir; +const LibExeObjStep = std.Build.LibExeObjStep; +const Step = std.Build.Step; +const elf = std.elf; +const fs = std.fs; +const io = std.io; +const sort = std.sort; + +pub const base_id = .install_raw; + +pub const RawFormat = enum { + bin, + hex, +}; + +step: Step, +builder: *std.Build, +artifact: *LibExeObjStep, +dest_dir: InstallDir, +dest_filename: []const u8, +options: CreateOptions, +output_file: std.Build.GeneratedFile, + +pub const CreateOptions = struct { + format: ?RawFormat = null, + dest_dir: ?InstallDir = null, + only_section: ?[]const u8 = null, + pad_to: ?u64 = null, +}; + +pub fn create( + builder: *std.Build, + artifact: *LibExeObjStep, + dest_filename: []const u8, + options: CreateOptions, +) *InstallRawStep { + const self = builder.allocator.create(InstallRawStep) catch unreachable; + self.* = InstallRawStep{ + .step = Step.init(.install_raw, builder.fmt("install raw binary {s}", .{artifact.step.name}), builder.allocator, make), + .builder = builder, + .artifact = artifact, + .dest_dir = if (options.dest_dir) |d| d else switch (artifact.kind) { + .obj => unreachable, + .@"test" => unreachable, + .exe, .test_exe => .bin, + .lib => unreachable, + }, + .dest_filename = dest_filename, + .options = options, + .output_file = std.Build.GeneratedFile{ .step = &self.step }, + }; + self.step.dependOn(&artifact.step); + + builder.pushInstalledFile(self.dest_dir, dest_filename); + return self; +} + +pub fn getOutputSource(self: *const InstallRawStep) std.Build.FileSource { + return std.Build.FileSource{ .generated = &self.output_file }; +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(InstallRawStep, "step", step); + const b = self.builder; + + if (self.artifact.target.getObjectFormat() != .elf) { + std.debug.print("InstallRawStep only works with ELF format.\n", .{}); + return error.InvalidObjectFormat; + } + + const full_src_path = self.artifact.getOutputSource().getPath(b); + const full_dest_path = b.getInstallPath(self.dest_dir, self.dest_filename); + self.output_file.path = full_dest_path; + + fs.cwd().makePath(b.getInstallPath(self.dest_dir, "")) catch unreachable; + + var argv_list = std.ArrayList([]const u8).init(b.allocator); + try argv_list.appendSlice(&.{ b.zig_exe, "objcopy" }); + + if (self.options.only_section) |only_section| { + try argv_list.appendSlice(&.{ "-j", only_section }); + } + if (self.options.pad_to) |pad_to| { + try argv_list.appendSlice(&.{ + "--pad-to", + b.fmt("{d}", .{pad_to}), + }); + } + if (self.options.format) |format| switch (format) { + .bin => try argv_list.appendSlice(&.{ "-O", "binary" }), + .hex => try argv_list.appendSlice(&.{ "-O", "hex" }), + }; + + try argv_list.appendSlice(&.{ full_src_path, full_dest_path }); + _ = try self.builder.execFromStep(argv_list.items, &self.step); +} + +test { + std.testing.refAllDecls(InstallRawStep); +} diff --git a/lib/std/Build/LibExeObjStep.zig b/lib/std/Build/LibExeObjStep.zig new file mode 100644 index 0000000000..af9d34440d --- /dev/null +++ b/lib/std/Build/LibExeObjStep.zig @@ -0,0 +1,2045 @@ +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const mem = std.mem; +const log = std.log; +const fs = std.fs; +const assert = std.debug.assert; +const panic = std.debug.panic; +const ArrayList = std.ArrayList; +const StringHashMap = std.StringHashMap; +const Sha256 = std.crypto.hash.sha2.Sha256; +const Allocator = mem.Allocator; +const Step = std.Build.Step; +const CrossTarget = std.zig.CrossTarget; +const NativeTargetInfo = std.zig.system.NativeTargetInfo; +const FileSource = std.Build.FileSource; +const PkgConfigPkg = std.Build.PkgConfigPkg; +const PkgConfigError = std.Build.PkgConfigError; +const ExecError = std.Build.ExecError; +const Pkg = std.Build.Pkg; +const VcpkgRoot = std.Build.VcpkgRoot; +const InstallDir = std.Build.InstallDir; +const InstallArtifactStep = std.Build.InstallArtifactStep; +const GeneratedFile = std.Build.GeneratedFile; +const InstallRawStep = std.Build.InstallRawStep; +const EmulatableRunStep = std.Build.EmulatableRunStep; +const CheckObjectStep = std.Build.CheckObjectStep; +const RunStep = std.Build.RunStep; +const OptionsStep = std.Build.OptionsStep; +const ConfigHeaderStep = std.Build.ConfigHeaderStep; +const LibExeObjStep = @This(); + +pub const base_id = .lib_exe_obj; + +step: Step, +builder: *std.Build, +name: []const u8, +target: CrossTarget, +target_info: NativeTargetInfo, +optimize: std.builtin.Mode, +linker_script: ?FileSource = null, +version_script: ?[]const u8 = null, +out_filename: []const u8, +linkage: ?Linkage = null, +version: ?std.builtin.Version, +kind: Kind, +major_only_filename: ?[]const u8, +name_only_filename: ?[]const u8, +strip: ?bool, +unwind_tables: ?bool, +// keep in sync with src/link.zig:CompressDebugSections +compress_debug_sections: enum { none, zlib } = .none, +lib_paths: ArrayList([]const u8), +rpaths: ArrayList([]const u8), +framework_dirs: ArrayList([]const u8), +frameworks: StringHashMap(FrameworkLinkInfo), +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, +stack_protector: ?bool = null, +disable_stack_probing: bool, +disable_sanitize_c: bool, +sanitize_thread: bool, +rdynamic: bool, +import_memory: bool = false, +/// For WebAssembly targets, this will allow for undefined symbols to +/// be imported from the host environment. +import_symbols: 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: std.Build.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, +test_runner: ?[]const u8, +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), +installed_headers: ArrayList(*Step), +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, +/// On ELF targets, this will emit a link section called ".note.gnu.build-id" +/// which can be used to coordinate a stripped binary with its debug symbols. +/// As an example, the bloaty project refuses to work unless its inputs have +/// build ids, in order to prevent accidental mismatches. +/// The default is to not include this section because it slows down linking. +build_id: ?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, + +/// Remove functions and data that are unreachable by the entry point or +/// exported symbols. +link_gc_sections: ?bool = null, + +linker_allow_shlib_undefined: ?bool = null, + +/// Permit read-only relocations in read-only segments. Disallowed by default. +link_z_notext: bool = false, + +/// Force all relocations to be read-only after processing. +link_z_relro: bool = true, + +/// Allow relocations to be lazily processed after load. +link_z_lazy: bool = false, + +/// Common page size +link_z_common_page_size: ?u64 = null, + +/// Maximum page size +link_z_max_page_size: ?u64 = null, + +/// (Darwin) Install name for the dylib +install_name: ?[]const u8 = null, + +/// (Darwin) Path to entitlements file +entitlements: ?[]const u8 = null, + +/// (Darwin) Size of the pagezero segment. +pagezero_size: ?u64 = null, + +/// (Darwin) Search strategy for searching system libraries. Either `paths_first` or `dylibs_first`. +/// The former lowers to `-search_paths_first` linker option, while the latter to `-search_dylibs_first` +/// option. +/// By default, if no option is specified, the linker assumes `paths_first` as the default +/// search strategy. +search_strategy: ?enum { paths_first, dylibs_first } = null, + +/// (Darwin) Set size of the padding between the end of load commands +/// and start of `__TEXT,__text` section. +headerpad_size: ?u32 = null, + +/// (Darwin) Automatically Set size of the padding between the end of load commands +/// and start of `__TEXT,__text` section to a value fitting all paths expanded to MAXPATHLEN. +headerpad_max_install_names: bool = false, + +/// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols. +dead_strip_dylibs: bool = false, + +/// Position Independent Code +force_pic: ?bool = null, + +/// Position Independent Executable +pie: ?bool = null, + +red_zone: ?bool = null, + +omit_frame_pointer: ?bool = null, +dll_export_fns: ?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, +use_llvm: ?bool = null, +use_lld: ?bool = null, + +output_path_source: GeneratedFile, +output_lib_path_source: GeneratedFile, +output_h_path_source: GeneratedFile, +output_pdb_path_source: GeneratedFile, + +pub const CSourceFiles = struct { + files: []const []const u8, + flags: []const []const u8, +}; + +pub const CSourceFile = struct { + source: FileSource, + args: []const []const u8, + + pub fn dupe(self: CSourceFile, b: *std.Build) CSourceFile { + return .{ + .source = self.source.dupe(b), + .args = b.dupeStrings(self.args), + }; + } +}; + +pub const LinkObject = union(enum) { + static_path: FileSource, + other_step: *LibExeObjStep, + system_lib: SystemLib, + assembly_file: FileSource, + c_source_file: *CSourceFile, + c_source_files: *CSourceFiles, +}; + +pub const SystemLib = struct { + name: []const u8, + needed: bool, + weak: bool, + use_pkg_config: enum { + /// Don't use pkg-config, just pass -lfoo where foo is name. + no, + /// Try to get information on how to link the library from pkg-config. + /// If that fails, fall back to passing -lfoo where foo is name. + yes, + /// Try to get information on how to link the library from pkg-config. + /// If that fails, error out. + force, + }, +}; + +const FrameworkLinkInfo = struct { + needed: bool = false, + weak: bool = false, +}; + +pub const IncludeDir = union(enum) { + raw_path: []const u8, + raw_path_system: []const u8, + other_step: *LibExeObjStep, + config_header_step: *ConfigHeaderStep, +}; + +pub const Options = struct { + name: []const u8, + root_source_file: ?FileSource = null, + target: CrossTarget, + optimize: std.builtin.Mode, + kind: Kind, + linkage: ?Linkage = null, + version: ?std.builtin.Version = null, +}; + +pub const Kind = enum { + exe, + lib, + obj, + @"test", + test_exe, +}; + +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: *std.Build, 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 create(builder: *std.Build, options: Options) *LibExeObjStep { + const name = builder.dupe(options.name); + const root_src: ?FileSource = if (options.root_source_file) |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 = null, + .unwind_tables = null, + .builder = builder, + .verbose_link = false, + .verbose_cc = false, + .optimize = options.optimize, + .target = options.target, + .linkage = options.linkage, + .kind = options.kind, + .root_src = root_src, + .name = name, + .frameworks = StringHashMap(FrameworkLinkInfo).init(builder.allocator), + .step = Step.init(base_id, name, builder.allocator, make), + .version = options.version, + .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), + .installed_headers = ArrayList(*Step).init(builder.allocator), + .object_src = undefined, + .c_std = std.Build.CStd.C99, + .override_lib_dir = null, + .main_pkg_path = null, + .exec_cmd_args = null, + .name_prefix = "", + .filter = null, + .test_runner = 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.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 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); +} + +pub fn installHeader(a: *LibExeObjStep, src_path: []const u8, dest_rel_path: []const u8) void { + const install_file = a.builder.addInstallHeaderFile(src_path, dest_rel_path); + a.builder.getInstallStep().dependOn(&install_file.step); + a.installed_headers.append(&install_file.step) catch unreachable; +} + +pub fn installHeadersDirectory( + a: *LibExeObjStep, + src_dir_path: []const u8, + dest_rel_path: []const u8, +) void { + return installHeadersDirectoryOptions(a, .{ + .source_dir = src_dir_path, + .install_dir = .header, + .install_subdir = dest_rel_path, + }); +} + +pub fn installHeadersDirectoryOptions( + a: *LibExeObjStep, + options: std.Build.InstallDirStep.Options, +) void { + const install_dir = a.builder.addInstallDirectory(options); + a.builder.getInstallStep().dependOn(&install_dir.step); + a.installed_headers.append(&install_dir.step) catch unreachable; +} + +pub fn installLibraryHeaders(a: *LibExeObjStep, l: *LibExeObjStep) void { + assert(l.kind == .lib); + const install_step = a.builder.getInstallStep(); + // Copy each element from installed_headers, modifying the builder + // to be the new parent's builder. + for (l.installed_headers.items) |step| { + const step_copy = switch (step.id) { + inline .install_file, .install_dir => |id| blk: { + const T = id.Type(); + const ptr = a.builder.allocator.create(T) catch unreachable; + ptr.* = step.cast(T).?.*; + ptr.override_source_builder = ptr.builder; + ptr.builder = a.builder; + break :blk &ptr.step; + }, + else => unreachable, + }; + a.installed_headers.append(step_copy) catch unreachable; + install_step.dependOn(step_copy); + } + a.installed_headers.appendSlice(l.installed_headers.items) catch unreachable; +} + +/// 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; +} + +/// Creates an `EmulatableRunStep` with an executable built with `addExecutable`. +/// Allows running foreign binaries through emulation platforms such as Qemu or Rosetta. +/// When a binary cannot be ran through emulation or the option is disabled, a warning +/// will be printed and the binary will *NOT* be ran. +pub fn runEmulatable(exe: *LibExeObjStep) *EmulatableRunStep { + assert(exe.kind == .exe or exe.kind == .test_exe); + + const run_step = EmulatableRunStep.create(exe.builder, exe.builder.fmt("run {s}", .{exe.step.name}), exe); + if (exe.vcpkg_bin_path) |path| { + RunStep.addPathDirInternal(&run_step.step, exe.builder, path); + } + return run_step; +} + +pub fn checkObject(self: *LibExeObjStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep { + return CheckObjectStep.create(self.builder, self.getOutputSource(), obj_format); +} + +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 { + self.frameworks.put(self.builder.dupe(framework_name), .{}) catch unreachable; +} + +pub fn linkFrameworkNeeded(self: *LibExeObjStep, framework_name: []const u8) void { + self.frameworks.put(self.builder.dupe(framework_name), .{ + .needed = true, + }) catch unreachable; +} + +pub fn linkFrameworkWeak(self: *LibExeObjStep, framework_name: []const u8) void { + self.frameworks.put(self.builder.dupe(framework_name), .{ + .weak = true, + }) 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 => |lib| if (mem.eql(u8, lib.name, 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 == Linkage.dynamic; +} + +pub fn isStaticLibrary(self: *LibExeObjStep) bool { + return self.kind == .lib and self.linkage != Linkage.dynamic; +} + +pub fn producesPdbFile(self: *LibExeObjStep) bool { + if (!self.target.isWindows() and !self.target.isUefi()) return false; + if (self.target.getObjectFormat() == .c) return false; + if (self.strip == true) return false; + return self.isDynamicLibrary() or self.kind == .exe or self.kind == .test_exe; +} + +pub fn linkLibC(self: *LibExeObjStep) void { + self.is_linking_libc = true; +} + +pub fn linkLibCpp(self: *LibExeObjStep) void { + self.is_linking_libcpp = true; +} + +/// 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 = std.Build.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 = .{ + .name = self.builder.dupe(name), + .needed = false, + .weak = false, + .use_pkg_config = .no, + }, + }) catch unreachable; +} + +/// This one has no integration with anything, it just puts -needed-lname on the command line. +/// Prefer to use `linkSystemLibraryNeeded` instead. +pub fn linkSystemLibraryNeededName(self: *LibExeObjStep, name: []const u8) void { + self.link_objects.append(.{ + .system_lib = .{ + .name = self.builder.dupe(name), + .needed = true, + .weak = false, + .use_pkg_config = .no, + }, + }) catch unreachable; +} + +/// Darwin-only. This one has no integration with anything, it just puts -weak-lname on the +/// command line. Prefer to use `linkSystemLibraryWeak` instead. +pub fn linkSystemLibraryWeakName(self: *LibExeObjStep, name: []const u8) void { + self.link_objects.append(.{ + .system_lib = .{ + .name = self.builder.dupe(name), + .needed = false, + .weak = true, + .use_pkg_config = .no, + }, + }) 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 { + self.link_objects.append(.{ + .system_lib = .{ + .name = self.builder.dupe(lib_name), + .needed = false, + .weak = false, + .use_pkg_config = .force, + }, + }) catch unreachable; +} + +/// This links against a system library, exclusively using pkg-config to find the library. +/// Prefer to use `linkSystemLibraryNeeded` instead. +pub fn linkSystemLibraryNeededPkgConfigOnly(self: *LibExeObjStep, lib_name: []const u8) void { + self.link_objects.append(.{ + .system_lib = .{ + .name = self.builder.dupe(lib_name), + .needed = true, + .weak = false, + .use_pkg_config = .force, + }, + }) catch unreachable; +} + +/// Run pkg-config for the given library name and parse the output, returning the arguments +/// that should be passed to zig to link the given library. +pub fn runPkgConfig(self: *LibExeObjStep, lib_name: []const u8) ![]const []const u8 { + 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 getPkgConfigList(self.builder); + + // 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 zig_args = ArrayList([]const u8).init(self.builder.allocator); + defer zig_args.deinit(); + + 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; + try zig_args.appendSlice(&[_][]const u8{ "-I", dir }); + } else if (mem.startsWith(u8, tok, "-I")) { + try zig_args.append(tok); + } else if (mem.eql(u8, tok, "-L")) { + const dir = it.next() orelse return error.PkgConfigInvalidOutput; + try zig_args.appendSlice(&[_][]const u8{ "-L", dir }); + } else if (mem.startsWith(u8, tok, "-L")) { + try zig_args.append(tok); + } else if (mem.eql(u8, tok, "-l")) { + const lib = it.next() orelse return error.PkgConfigInvalidOutput; + try zig_args.appendSlice(&[_][]const u8{ "-l", lib }); + } else if (mem.startsWith(u8, tok, "-l")) { + try zig_args.append(tok); + } else if (mem.eql(u8, tok, "-D")) { + const macro = it.next() orelse return error.PkgConfigInvalidOutput; + try zig_args.appendSlice(&[_][]const u8{ "-D", macro }); + } else if (mem.startsWith(u8, tok, "-D")) { + try zig_args.append(tok); + } else if (self.builder.verbose) { + log.warn("Ignoring pkg-config flag '{s}'", .{tok}); + } + } + + return zig_args.toOwnedSlice(); +} + +pub fn linkSystemLibrary(self: *LibExeObjStep, name: []const u8) void { + self.linkSystemLibraryInner(name, .{}); +} + +pub fn linkSystemLibraryNeeded(self: *LibExeObjStep, name: []const u8) void { + self.linkSystemLibraryInner(name, .{ .needed = true }); +} + +pub fn linkSystemLibraryWeak(self: *LibExeObjStep, name: []const u8) void { + self.linkSystemLibraryInner(name, .{ .weak = true }); +} + +fn linkSystemLibraryInner(self: *LibExeObjStep, name: []const u8, opts: struct { + needed: bool = false, + weak: bool = false, +}) void { + if (isLibCLibrary(name)) { + self.linkLibC(); + return; + } + if (isLibCppLibrary(name)) { + self.linkLibCpp(); + return; + } + + self.link_objects.append(.{ + .system_lib = .{ + .name = self.builder.dupe(name), + .needed = opts.needed, + .weak = opts.weak, + .use_pkg_config = .yes, + }, + }) catch unreachable; +} + +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; +} + +pub fn setTestRunner(self: *LibExeObjStep, path: ?[]const u8) void { + assert(self.kind == .@"test" or self.kind == .test_exe); + self.test_runner = if (path) |p| self.builder.dupePath(p) 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 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); +} + +pub const addSystemIncludeDir = @compileError("deprecated; use addSystemIncludePath"); +pub const addIncludeDir = @compileError("deprecated; use addIncludePath"); +pub const addLibPath = @compileError("deprecated, use addLibraryPath"); +pub const addFrameworkDir = @compileError("deprecated, use addFrameworkPath"); + +pub fn addSystemIncludePath(self: *LibExeObjStep, path: []const u8) void { + self.include_dirs.append(IncludeDir{ .raw_path_system = self.builder.dupe(path) }) catch unreachable; +} + +pub fn addIncludePath(self: *LibExeObjStep, path: []const u8) void { + self.include_dirs.append(IncludeDir{ .raw_path = self.builder.dupe(path) }) catch unreachable; +} + +pub fn addConfigHeader(self: *LibExeObjStep, config_header: *ConfigHeaderStep) void { + self.step.dependOn(&config_header.step); + self.include_dirs.append(.{ .config_header_step = config_header }) catch @panic("OOM"); +} + +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; +} + +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.source.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), + .source = .{ .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.source.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) { + log.err("{s}: linker needs 1 or more objects to link", .{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 (builder.reference_trace) |some| { + try zig_args.append(try std.fmt.allocPrint(builder.allocator, "-freference-trace={d}", .{some})); + } + + try addFlag(&zig_args, "LLVM", self.use_llvm); + try addFlag(&zig_args, "LLD", self.use_lld); + + if (self.target.ofmt) |ofmt| { + try zig_args.append(try std.fmt.allocPrint(builder.allocator, "-ofmt={s}", .{@tagName(ofmt)})); + } + + 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)); + + // We will add link objects from transitive dependencies, but we want to keep + // all link objects in the same order provided. + // This array is used to keep self.link_objects immutable. + var transitive_deps: TransitiveDeps = .{ + .link_objects = ArrayList(LinkObject).init(builder.allocator), + .seen_system_libs = StringHashMap(void).init(builder.allocator), + .seen_steps = std.AutoHashMap(*const Step, void).init(builder.allocator), + .is_linking_libcpp = self.is_linking_libcpp, + .is_linking_libc = self.is_linking_libc, + .frameworks = &self.frameworks, + }; + + try transitive_deps.seen_steps.put(&self.step, {}); + try transitive_deps.add(self.link_objects.items); + + var prev_has_extra_flags = false; + + for (transitive_deps.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 => l: { + if (self.isStaticLibrary() and other.isStaticLibrary()) { + // Avoid putting a static library inside a static library. + break :l; + } + + const full_path_lib = other.getOutputLibSource().getPath(builder); + try zig_args.append(full_path_lib); + + if (other.linkage == 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 => |system_lib| { + const prefix: []const u8 = prefix: { + if (system_lib.needed) break :prefix "-needed-l"; + if (system_lib.weak) { + if (self.target.isDarwin()) break :prefix "-weak-l"; + log.warn("Weak library import used for a non-darwin target, this will be converted to normally library import `-lname`", .{}); + } + break :prefix "-l"; + }; + switch (system_lib.use_pkg_config) { + .no => try zig_args.append(builder.fmt("{s}{s}", .{ prefix, system_lib.name })), + .yes, .force => { + if (self.runPkgConfig(system_lib.name)) |args| { + try zig_args.appendSlice(args); + } else |err| switch (err) { + error.PkgConfigInvalidOutput, + error.PkgConfigCrashed, + error.PkgConfigFailed, + error.PkgConfigNotInstalled, + error.PackageNotFound, + => switch (system_lib.use_pkg_config) { + .yes => { + // pkg-config failed, so fall back to linking the library + // by name directly. + try zig_args.append(builder.fmt("{s}{s}", .{ + prefix, + system_lib.name, + })); + }, + .force => { + panic("pkg-config failed for library {s}", .{system_lib.name}); + }, + .no => unreachable, + }, + + else => |e| return e, + } + }, + } + }, + + .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 (transitive_deps.is_linking_libcpp) { + try zig_args.append("-lc++"); + } + + if (transitive_deps.is_linking_libc) { + try zig_args.append("-lc"); + } + + 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); + } + + if (self.test_runner) |test_runner| { + try zig_args.append("--test-runner"); + try zig_args.append(builder.pathFromRoot(test_runner)); + } + + for (builder.debug_log_scopes) |log_scope| { + try zig_args.append("--debug-log"); + try zig_args.append(log_scope); + } + + if (builder.debug_compile_errors) { + try zig_args.append("--debug-compile-errors"); + } + + 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"); + + try addFlag(&zig_args, "strip", self.strip); + try addFlag(&zig_args, "unwind-tables", self.unwind_tables); + + switch (self.compress_debug_sections) { + .none => {}, + .zlib => try zig_args.append("--compress-debug-sections=zlib"), + } + + 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.link_gc_sections) |x| { + try zig_args.append(if (x) "--gc-sections" else "--no-gc-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.link_z_relro) { + try zig_args.append("-z"); + try zig_args.append("norelro"); + } + if (self.link_z_lazy) { + try zig_args.append("-z"); + try zig_args.append("lazy"); + } + if (self.link_z_common_page_size) |size| { + try zig_args.append("-z"); + try zig_args.append(builder.fmt("common-page-size={d}", .{size})); + } + if (self.link_z_max_page_size) |size| { + try zig_args.append("-z"); + try zig_args.append(builder.fmt("max-page-size={d}", .{size})); + } + + if (self.libc_file) |libc_file| { + try zig_args.append("--libc"); + try zig_args.append(libc_file.getPath(builder)); + } else if (builder.libc_file) |libc_file| { + try zig_args.append("--libc"); + try zig_args.append(libc_file); + } + + switch (self.optimize) { + .Debug => {}, // Skip since it's the default. + else => zig_args.append(builder.fmt("-O{s}", .{@tagName(self.optimize)})) 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.pagezero_size) |pagezero_size| { + const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{pagezero_size}); + try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size }); + } + if (self.search_strategy) |strat| switch (strat) { + .paths_first => try zig_args.append("-search_paths_first"), + .dylibs_first => try zig_args.append("-search_dylibs_first"), + }; + if (self.headerpad_size) |headerpad_size| { + const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{headerpad_size}); + try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size }); + } + if (self.headerpad_max_install_names) { + try zig_args.append("-headerpad_max_install_names"); + } + if (self.dead_strip_dylibs) { + try zig_args.append("-dead_strip_dylibs"); + } + + try addFlag(&zig_args, "compiler-rt", self.bundle_compiler_rt); + try addFlag(&zig_args, "single-threaded", self.single_threaded); + if (self.disable_stack_probing) { + try zig_args.append("-fno-stack-check"); + } + try addFlag(&zig_args, "stack-protector", self.stack_protector); + if (self.red_zone) |red_zone| { + if (red_zone) { + try zig_args.append("-mred-zone"); + } else { + try zig_args.append("-mno-red-zone"); + } + } + try addFlag(&zig_args, "omit-frame-pointer", self.omit_frame_pointer); + try addFlag(&zig_args, "dll-export-fns", self.dll_export_fns); + + 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_symbols) { + try zig_args.append("--import-symbols"); + } + 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.appendSlice(&.{ + "-target", try self.target.zigTriple(builder.allocator), + "-mcpu", try std.Build.serializeCpu(builder.allocator, self.target.getCpu()), + }); + + 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 transitive_deps.is_linking_libc; + + switch (builder.host.getExternalExecutor(self.target_info, .{ + .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null, + .link_libc = transitive_deps.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 + // "x86" 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 == .x86) + "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-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(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 = 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(builder); + try zig_args.append("-isystem"); + try zig_args.append(fs.path.dirname(h_path).?); + } + if (other.installed_headers.items.len > 0) { + for (other.installed_headers.items) |install_step| { + try install_step.make(); + } + try zig_args.append("-I"); + try zig_args.append(builder.pathJoin(&.{ + other.builder.install_prefix, "include", + })); + } + }, + .config_header_step => |config_header| { + try zig_args.append("-I"); + try zig_args.append(config_header.output_dir); + }, + } + } + + 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()) |entry| { + const name = entry.key_ptr.*; + const info = entry.value_ptr.*; + if (info.needed) { + zig_args.append("-needed_framework") catch unreachable; + } else if (info.weak) { + zig_args.append("-weak_framework") catch unreachable; + } else { + zig_args.append("-framework") catch unreachable; + } + zig_args.append(name) catch unreachable; + } + } else { + if (self.framework_dirs.items.len > 0) { + log.info("Framework directories have been added for a non-darwin target, this will have no affect on the build", .{}); + } + + if (self.frameworks.count() > 0) { + log.info("Frameworks have been added for a non-darwin target, this will have no affect on the build", .{}); + } + } + + 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("-I"); + try zig_args.append(builder.pathJoin(&.{ + search_prefix, "include", + })); + } + + try addFlag(&zig_args, "valgrind", self.valgrind_support); + try addFlag(&zig_args, "each-lib-rpath", self.each_lib_rpath); + try addFlag(&zig_args, "build-id", self.build_id); + + if (self.override_lib_dir) |dir| { + try zig_args.append("--zig-lib-dir"); + try zig_args.append(builder.pathFromRoot(dir)); + } else if (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)); + } + + try addFlag(&zig_args, "PIC", self.force_pic); + try addFlag(&zig_args, "PIE", self.pie); + try addFlag(&zig_args, "lto", self.want_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' + // See @file syntax here: https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html + 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); + + var args_arena = std.heap.ArenaAllocator.init(builder.allocator); + defer args_arena.deinit(); + + const args_to_escape = zig_args.items[2..]; + var escaped_args = try ArrayList([]const u8).initCapacity(args_arena.allocator(), args_to_escape.len); + + arg_blk: for (args_to_escape) |arg| { + for (arg) |c, arg_idx| { + if (c == '\\' or c == '"') { + // Slow path for arguments that need to be escaped. We'll need to allocate and copy + var escaped = try ArrayList(u8).initCapacity(args_arena.allocator(), arg.len + 1); + const writer = escaped.writer(); + writer.writeAll(arg[0..arg_idx]) catch unreachable; + for (arg[arg_idx..]) |to_escape| { + if (to_escape == '\\' or to_escape == '"') try writer.writeByte('\\'); + try writer.writeByte(to_escape); + } + escaped_args.appendAssumeCapacity(escaped.items); + continue :arg_blk; + } + } + escaped_args.appendAssumeCapacity(arg); // no escaping needed so just use original argument + } + + // 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, "\" \"", escaped_args.items); + 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().openIterableDir(build_output_dir, .{}); + 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, "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.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.?); + } +} + +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; +} + +/// 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; +} + +pub 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| { + log.err("Unable to symlink {s} -> {s}", .{ 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| { + log.err("Unable to symlink {s} -> {s}", .{ name_only_path, filename_major_only }); + return err; + }; +} + +fn execPkgConfigList(self: *std.Build, 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: *std.Build) ![]const PkgConfigPkg { + if (self.pkg_config_pkg_list) |res| { + return res; + } + var code: u8 = undefined; + if (execPkgConfigList(self, &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 "addPackage" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + + var builder = try std.Build.create( + arena.allocator(), + "test", + "test", + "test", + "test", + ); + defer builder.destroy(); + + const pkg_dep = Pkg{ + .name = "pkg_dep", + .source = .{ .path = "/not/a/pkg_dep.zig" }, + }; + const pkg_top = Pkg{ + .name = "pkg_dep", + .source = .{ .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); +} + +fn addFlag(args: *ArrayList([]const u8), comptime name: []const u8, opt: ?bool) !void { + const cond = opt orelse return; + try args.ensureUnusedCapacity(1); + if (cond) { + args.appendAssumeCapacity("-f" ++ name); + } else { + args.appendAssumeCapacity("-fno-" ++ name); + } +} + +const TransitiveDeps = struct { + link_objects: ArrayList(LinkObject), + seen_system_libs: StringHashMap(void), + seen_steps: std.AutoHashMap(*const Step, void), + is_linking_libcpp: bool, + is_linking_libc: bool, + frameworks: *StringHashMap(FrameworkLinkInfo), + + fn add(td: *TransitiveDeps, link_objects: []const LinkObject) !void { + try td.link_objects.ensureUnusedCapacity(link_objects.len); + + for (link_objects) |link_object| { + try td.link_objects.append(link_object); + switch (link_object) { + .other_step => |other| try addInner(td, other, other.isDynamicLibrary()), + else => {}, + } + } + } + + fn addInner(td: *TransitiveDeps, other: *LibExeObjStep, dyn: bool) !void { + // Inherit dependency on libc and libc++ + td.is_linking_libcpp = td.is_linking_libcpp or other.is_linking_libcpp; + td.is_linking_libc = td.is_linking_libc or other.is_linking_libc; + + // Inherit dependencies on darwin frameworks + if (!dyn) { + var it = other.frameworks.iterator(); + while (it.next()) |framework| { + try td.frameworks.put(framework.key_ptr.*, framework.value_ptr.*); + } + } + + // Inherit dependencies on system libraries and static libraries. + for (other.link_objects.items) |other_link_object| { + switch (other_link_object) { + .system_lib => |system_lib| { + if ((try td.seen_system_libs.fetchPut(system_lib.name, {})) != null) + continue; + + if (dyn) + continue; + + try td.link_objects.append(other_link_object); + }, + .other_step => |inner_other| { + if ((try td.seen_steps.fetchPut(&inner_other.step, {})) != null) + continue; + + if (!dyn) + try td.link_objects.append(other_link_object); + + try addInner(td, inner_other, dyn or inner_other.isDynamicLibrary()); + }, + else => continue, + } + } + } +}; diff --git a/lib/std/Build/LogStep.zig b/lib/std/Build/LogStep.zig new file mode 100644 index 0000000000..6d51df8cbd --- /dev/null +++ b/lib/std/Build/LogStep.zig @@ -0,0 +1,23 @@ +const std = @import("../std.zig"); +const log = std.log; +const Step = std.Build.Step; +const LogStep = @This(); + +pub const base_id = .log; + +step: Step, +builder: *std.Build, +data: []const u8, + +pub fn init(builder: *std.Build, 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); + log.info("{s}", .{self.data}); +} diff --git a/lib/std/Build/OptionsStep.zig b/lib/std/Build/OptionsStep.zig new file mode 100644 index 0000000000..3d26807411 --- /dev/null +++ b/lib/std/Build/OptionsStep.zig @@ -0,0 +1,363 @@ +const std = @import("../std.zig"); +const builtin = @import("builtin"); +const fs = std.fs; +const Step = std.Build.Step; +const GeneratedFile = std.Build.GeneratedFile; +const LibExeObjStep = std.Build.LibExeObjStep; +const FileSource = std.Build.FileSource; + +const OptionsStep = @This(); + +pub const base_id = .options; + +step: Step, +generated_file: GeneratedFile, +builder: *std.Build, + +contents: std.ArrayList(u8), +artifact_args: std.ArrayList(OptionArtifactArg), +file_source_args: std.ArrayList(OptionFileSourceArg), + +pub fn create(builder: *std.Build) *OptionsStep { + const self = builder.allocator.create(OptionsStep) catch unreachable; + self.* = .{ + .builder = builder, + .step = Step.init(.options, "options", builder.allocator, make), + .generated_file = undefined, + .contents = std.ArrayList(u8).init(builder.allocator), + .artifact_args = std.ArrayList(OptionArtifactArg).init(builder.allocator), + .file_source_args = std.ArrayList(OptionFileSourceArg).init(builder.allocator), + }; + self.generated_file = .{ .step = &self.step }; + + return self; +} + +pub fn addOption(self: *OptionsStep, comptime T: type, name: []const u8, value: T) void { + const out = self.contents.writer(); + switch (T) { + []const []const u8 => { + out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{std.zig.fmtId(name)}) catch unreachable; + for (value) |slice| { + out.print(" \"{}\",\n", .{std.zig.fmtEscapes(slice)}) catch unreachable; + } + out.writeAll("};\n") catch unreachable; + return; + }, + [:0]const u8 => { + out.print("pub const {}: [:0]const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable; + return; + }, + []const u8 => { + out.print("pub const {}: []const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable; + return; + }, + ?[:0]const u8 => { + out.print("pub const {}: ?[:0]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable; + if (value) |payload| { + out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable; + } else { + out.writeAll("null;\n") catch unreachable; + } + return; + }, + ?[]const u8 => { + out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable; + if (value) |payload| { + out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable; + } else { + out.writeAll("null;\n") catch unreachable; + } + return; + }, + std.builtin.Version => { + out.print( + \\pub const {}: @import("std").builtin.Version = .{{ + \\ .major = {d}, + \\ .minor = {d}, + \\ .patch = {d}, + \\}}; + \\ + , .{ + std.zig.fmtId(name), + + value.major, + value.minor, + value.patch, + }) catch unreachable; + return; + }, + std.SemanticVersion => { + out.print( + \\pub const {}: @import("std").SemanticVersion = .{{ + \\ .major = {d}, + \\ .minor = {d}, + \\ .patch = {d}, + \\ + , .{ + std.zig.fmtId(name), + + value.major, + value.minor, + value.patch, + }) catch unreachable; + if (value.pre) |some| { + out.print(" .pre = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable; + } + if (value.build) |some| { + out.print(" .build = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable; + } + out.writeAll("};\n") catch unreachable; + return; + }, + else => {}, + } + switch (@typeInfo(T)) { + .Enum => |enum_info| { + out.print("pub const {} = enum {{\n", .{std.zig.fmtId(@typeName(T))}) catch unreachable; + inline for (enum_info.fields) |field| { + out.print(" {},\n", .{std.zig.fmtId(field.name)}) catch unreachable; + } + out.writeAll("};\n") catch unreachable; + out.print("pub const {}: {s} = {s}.{s};\n", .{ + std.zig.fmtId(name), + std.zig.fmtId(@typeName(T)), + std.zig.fmtId(@typeName(T)), + std.zig.fmtId(@tagName(value)), + }) catch unreachable; + return; + }, + else => {}, + } + out.print("pub const {}: {s} = ", .{ std.zig.fmtId(name), @typeName(T) }) catch unreachable; + printLiteral(out, value, 0) catch unreachable; + out.writeAll(";\n") catch unreachable; +} + +// TODO: non-recursive? +fn printLiteral(out: anytype, val: anytype, indent: u8) !void { + const T = @TypeOf(val); + switch (@typeInfo(T)) { + .Array => { + try out.print("{s} {{\n", .{@typeName(T)}); + for (val) |item| { + try out.writeByteNTimes(' ', indent + 4); + try printLiteral(out, item, indent + 4); + try out.writeAll(",\n"); + } + try out.writeByteNTimes(' ', indent); + try out.writeAll("}"); + }, + .Pointer => |p| { + if (p.size != .Slice) { + @compileError("Non-slice pointers are not yet supported in build options"); + } + try out.print("&[_]{s} {{\n", .{@typeName(p.child)}); + for (val) |item| { + try out.writeByteNTimes(' ', indent + 4); + try printLiteral(out, item, indent + 4); + try out.writeAll(",\n"); + } + try out.writeByteNTimes(' ', indent); + try out.writeAll("}"); + }, + .Optional => { + if (val) |inner| { + return printLiteral(out, inner, indent); + } else { + return out.writeAll("null"); + } + }, + .Void, + .Bool, + .Int, + .ComptimeInt, + .Float, + .Null, + => try out.print("{any}", .{val}), + else => @compileError(std.fmt.comptimePrint("`{s}` are not yet supported as build options", .{@tagName(@typeInfo(T))})), + } +} + +/// The value is the path in the cache dir. +/// Adds a dependency automatically. +pub fn addOptionFileSource( + self: *OptionsStep, + name: []const u8, + source: FileSource, +) void { + self.file_source_args.append(.{ + .name = name, + .source = source.dupe(self.builder), + }) catch unreachable; + source.addStepDependencies(&self.step); +} + +/// The value is the path in the cache dir. +/// Adds a dependency automatically. +pub fn addOptionArtifact(self: *OptionsStep, name: []const u8, artifact: *LibExeObjStep) void { + self.artifact_args.append(.{ .name = self.builder.dupe(name), .artifact = artifact }) catch unreachable; + self.step.dependOn(&artifact.step); +} + +pub fn getPackage(self: *OptionsStep, package_name: []const u8) std.Build.Pkg { + return .{ .name = package_name, .source = self.getSource() }; +} + +pub fn getSource(self: *OptionsStep) FileSource { + return .{ .generated = &self.generated_file }; +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(OptionsStep, "step", step); + + for (self.artifact_args.items) |item| { + self.addOption( + []const u8, + item.name, + self.builder.pathFromRoot(item.artifact.getOutputSource().getPath(self.builder)), + ); + } + + for (self.file_source_args.items) |item| { + self.addOption( + []const u8, + item.name, + item.source.getPath(self.builder), + ); + } + + const options_directory = self.builder.pathFromRoot( + try fs.path.join( + self.builder.allocator, + &[_][]const u8{ self.builder.cache_root, "options" }, + ), + ); + + try fs.cwd().makePath(options_directory); + + const options_file = try fs.path.join( + self.builder.allocator, + &[_][]const u8{ options_directory, &self.hashContentsToFileName() }, + ); + + try fs.cwd().writeFile(options_file, self.contents.items); + + self.generated_file.path = options_file; +} + +fn hashContentsToFileName(self: *OptionsStep) [64]u8 { + // This implementation is copied from `WriteFileStep.make` + + var hash = std.crypto.hash.blake2.Blake2b384.init(.{}); + + // Random bytes to make OptionsStep unique. Refresh this with + // new random bytes when OptionsStep implementation is modified + // in a non-backwards-compatible way. + hash.update("yL0Ya4KkmcCjBlP8"); + hash.update(self.contents.items); + + var digest: [48]u8 = undefined; + hash.final(&digest); + var hash_basename: [64]u8 = undefined; + _ = fs.base64_encoder.encode(&hash_basename, &digest); + return hash_basename; +} + +const OptionArtifactArg = struct { + name: []const u8, + artifact: *LibExeObjStep, +}; + +const OptionFileSourceArg = struct { + name: []const u8, + source: FileSource, +}; + +test "OptionsStep" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var builder = try std.Build.create( + arena.allocator(), + "test", + "test", + "test", + "test", + ); + defer builder.destroy(); + + const options = builder.addOptions(); + + // TODO this regressed at some point + //const KeywordEnum = enum { + // @"0.8.1", + //}; + + const nested_array = [2][2]u16{ + [2]u16{ 300, 200 }, + [2]u16{ 300, 200 }, + }; + const nested_slice: []const []const u16 = &[_][]const u16{ &nested_array[0], &nested_array[1] }; + + options.addOption(usize, "option1", 1); + options.addOption(?usize, "option2", null); + options.addOption(?usize, "option3", 3); + options.addOption(comptime_int, "option4", 4); + options.addOption([]const u8, "string", "zigisthebest"); + options.addOption(?[]const u8, "optional_string", null); + options.addOption([2][2]u16, "nested_array", nested_array); + options.addOption([]const []const u16, "nested_slice", nested_slice); + //options.addOption(KeywordEnum, "keyword_enum", .@"0.8.1"); + options.addOption(std.builtin.Version, "version", try std.builtin.Version.parse("0.1.2")); + options.addOption(std.SemanticVersion, "semantic_version", try std.SemanticVersion.parse("0.1.2-foo+bar")); + + try std.testing.expectEqualStrings( + \\pub const option1: usize = 1; + \\pub const option2: ?usize = null; + \\pub const option3: ?usize = 3; + \\pub const option4: comptime_int = 4; + \\pub const string: []const u8 = "zigisthebest"; + \\pub const optional_string: ?[]const u8 = null; + \\pub const nested_array: [2][2]u16 = [2][2]u16 { + \\ [2]u16 { + \\ 300, + \\ 200, + \\ }, + \\ [2]u16 { + \\ 300, + \\ 200, + \\ }, + \\}; + \\pub const nested_slice: []const []const u16 = &[_][]const u16 { + \\ &[_]u16 { + \\ 300, + \\ 200, + \\ }, + \\ &[_]u16 { + \\ 300, + \\ 200, + \\ }, + \\}; + //\\pub const KeywordEnum = enum { + //\\ @"0.8.1", + //\\}; + //\\pub const keyword_enum: KeywordEnum = KeywordEnum.@"0.8.1"; + \\pub const version: @import("std").builtin.Version = .{ + \\ .major = 0, + \\ .minor = 1, + \\ .patch = 2, + \\}; + \\pub const semantic_version: @import("std").SemanticVersion = .{ + \\ .major = 0, + \\ .minor = 1, + \\ .patch = 2, + \\ .pre = "foo", + \\ .build = "bar", + \\}; + \\ + , options.contents.items); + + _ = try std.zig.parse(arena.allocator(), try options.contents.toOwnedSliceSentinel(0)); +} diff --git a/lib/std/Build/RemoveDirStep.zig b/lib/std/Build/RemoveDirStep.zig new file mode 100644 index 0000000000..f3b71dcec1 --- /dev/null +++ b/lib/std/Build/RemoveDirStep.zig @@ -0,0 +1,29 @@ +const std = @import("../std.zig"); +const log = std.log; +const fs = std.fs; +const Step = std.Build.Step; +const RemoveDirStep = @This(); + +pub const base_id = .remove_dir; + +step: Step, +builder: *std.Build, +dir_path: []const u8, + +pub fn init(builder: *std.Build, 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| { + log.err("Unable to remove {s}: {s}", .{ full_path, @errorName(err) }); + return err; + }; +} diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig new file mode 100644 index 0000000000..83e4f7f66c --- /dev/null +++ b/lib/std/Build/RunStep.zig @@ -0,0 +1,376 @@ +const std = @import("../std.zig"); +const builtin = @import("builtin"); +const Step = std.Build.Step; +const LibExeObjStep = std.Build.LibExeObjStep; +const WriteFileStep = std.Build.WriteFileStep; +const fs = std.fs; +const mem = std.mem; +const process = std.process; +const ArrayList = std.ArrayList; +const EnvMap = process.EnvMap; +const Allocator = mem.Allocator; +const ExecError = std.Build.ExecError; + +const max_stdout_size = 1 * 1024 * 1024; // 1 MiB + +const RunStep = @This(); + +pub const base_id: Step.Id = .run; + +step: Step, +builder: *std.Build, + +/// See also addArg and addArgs to modifying this directly +argv: ArrayList(Arg), + +/// Set this to modify the current working directory +cwd: ?[]const u8, + +/// Override this field to modify the environment, or use setEnvironmentVariable +env_map: ?*EnvMap, + +stdout_action: StdIoAction = .inherit, +stderr_action: StdIoAction = .inherit, + +stdin_behavior: std.ChildProcess.StdIo = .Inherit, + +/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution +expected_exit_code: ?u8 = 0, + +/// Print the command before running it +print: bool, + +pub const StdIoAction = union(enum) { + inherit, + ignore, + expect_exact: []const u8, + expect_matches: []const []const u8, +}; + +pub const Arg = union(enum) { + artifact: *LibExeObjStep, + file_source: std.Build.FileSource, + bytes: []u8, +}; + +pub fn create(builder: *std.Build, name: []const u8) *RunStep { + const self = builder.allocator.create(RunStep) catch unreachable; + self.* = RunStep{ + .builder = builder, + .step = Step.init(base_id, name, builder.allocator, make), + .argv = ArrayList(Arg).init(builder.allocator), + .cwd = null, + .env_map = null, + .print = builder.verbose, + }; + return self; +} + +pub fn addArtifactArg(self: *RunStep, artifact: *LibExeObjStep) void { + self.argv.append(Arg{ .artifact = artifact }) catch unreachable; + self.step.dependOn(&artifact.step); +} + +pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void { + self.argv.append(Arg{ + .file_source = file_source.dupe(self.builder), + }) catch unreachable; + file_source.addStepDependencies(&self.step); +} + +pub fn addArg(self: *RunStep, arg: []const u8) void { + self.argv.append(Arg{ .bytes = self.builder.dupe(arg) }) catch unreachable; +} + +pub fn addArgs(self: *RunStep, args: []const []const u8) void { + for (args) |arg| { + self.addArg(arg); + } +} + +pub fn clearEnvironment(self: *RunStep) void { + const new_env_map = self.builder.allocator.create(EnvMap) catch unreachable; + new_env_map.* = EnvMap.init(self.builder.allocator); + self.env_map = new_env_map; +} + +pub fn addPathDir(self: *RunStep, search_path: []const u8) void { + addPathDirInternal(&self.step, self.builder, search_path); +} + +/// For internal use only, users of `RunStep` should use `addPathDir` directly. +pub fn addPathDirInternal(step: *Step, builder: *std.Build, search_path: []const u8) void { + const env_map = getEnvMapInternal(step, builder.allocator); + + const key = "PATH"; + var prev_path = env_map.get(key); + + if (prev_path) |pp| { + const new_path = builder.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path }); + env_map.put(key, new_path) catch unreachable; + } else { + env_map.put(key, builder.dupePath(search_path)) catch unreachable; + } +} + +pub fn getEnvMap(self: *RunStep) *EnvMap { + return getEnvMapInternal(&self.step, self.builder.allocator); +} + +fn getEnvMapInternal(step: *Step, allocator: Allocator) *EnvMap { + const maybe_env_map = switch (step.id) { + .run => step.cast(RunStep).?.env_map, + .emulatable_run => step.cast(std.Build.EmulatableRunStep).?.env_map, + else => unreachable, + }; + return maybe_env_map orelse { + const env_map = allocator.create(EnvMap) catch unreachable; + env_map.* = process.getEnvMap(allocator) catch unreachable; + switch (step.id) { + .run => step.cast(RunStep).?.env_map = env_map, + .emulatable_run => step.cast(RunStep).?.env_map = env_map, + else => unreachable, + } + return env_map; + }; +} + +pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void { + const env_map = self.getEnvMap(); + env_map.put( + self.builder.dupe(key), + self.builder.dupe(value), + ) catch unreachable; +} + +pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void { + self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) }; +} + +pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void { + self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) }; +} + +fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo { + return switch (action) { + .ignore => .Ignore, + .inherit => .Inherit, + .expect_exact, .expect_matches => .Pipe, + }; +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(RunStep, "step", step); + + var argv_list = ArrayList([]const u8).init(self.builder.allocator); + for (self.argv.items) |arg| { + switch (arg) { + .bytes => |bytes| try argv_list.append(bytes), + .file_source => |file| try argv_list.append(file.getPath(self.builder)), + .artifact => |artifact| { + if (artifact.target.isWindows()) { + // On Windows we don't have rpaths so we have to add .dll search paths to PATH + self.addPathForDynLibs(artifact); + } + const executable_path = artifact.installed_path orelse artifact.getOutputSource().getPath(self.builder); + try argv_list.append(executable_path); + }, + } + } + + try runCommand( + argv_list.items, + self.builder, + self.expected_exit_code, + self.stdout_action, + self.stderr_action, + self.stdin_behavior, + self.env_map, + self.cwd, + self.print, + ); +} + +pub fn runCommand( + argv: []const []const u8, + builder: *std.Build, + expected_exit_code: ?u8, + stdout_action: StdIoAction, + stderr_action: StdIoAction, + stdin_behavior: std.ChildProcess.StdIo, + env_map: ?*EnvMap, + maybe_cwd: ?[]const u8, + print: bool, +) !void { + const cwd = if (maybe_cwd) |cwd| builder.pathFromRoot(cwd) else builder.build_root; + + if (!std.process.can_spawn) { + const cmd = try std.mem.join(builder.allocator, " ", argv); + std.debug.print("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(builtin.os.tag), cmd }); + builder.allocator.free(cmd); + return ExecError.ExecNotSupported; + } + + var child = std.ChildProcess.init(argv, builder.allocator); + child.cwd = cwd; + child.env_map = env_map orelse builder.env_map; + + child.stdin_behavior = stdin_behavior; + child.stdout_behavior = stdIoActionToBehavior(stdout_action); + child.stderr_behavior = stdIoActionToBehavior(stderr_action); + + if (print) + printCmd(cwd, argv); + + child.spawn() catch |err| { + std.debug.print("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) }); + return err; + }; + + // TODO need to poll to read these streams to prevent a deadlock (or rely on evented I/O). + + var stdout: ?[]const u8 = null; + defer if (stdout) |s| builder.allocator.free(s); + + switch (stdout_action) { + .expect_exact, .expect_matches => { + stdout = child.stdout.?.reader().readAllAlloc(builder.allocator, max_stdout_size) catch unreachable; + }, + .inherit, .ignore => {}, + } + + var stderr: ?[]const u8 = null; + defer if (stderr) |s| builder.allocator.free(s); + + switch (stderr_action) { + .expect_exact, .expect_matches => { + stderr = child.stderr.?.reader().readAllAlloc(builder.allocator, max_stdout_size) catch unreachable; + }, + .inherit, .ignore => {}, + } + + const term = child.wait() catch |err| { + std.debug.print("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) }); + return err; + }; + + switch (term) { + .Exited => |code| blk: { + const expected_code = expected_exit_code orelse break :blk; + + if (code != expected_code) { + if (builder.prominent_compile_errors) { + std.debug.print("Run step exited with error code {} (expected {})\n", .{ + code, + expected_code, + }); + } else { + std.debug.print("The following command exited with error code {} (expected {}):\n", .{ + code, + expected_code, + }); + printCmd(cwd, argv); + } + + return error.UnexpectedExitCode; + } + }, + else => { + std.debug.print("The following command terminated unexpectedly:\n", .{}); + printCmd(cwd, argv); + return error.UncleanExit; + }, + } + + switch (stderr_action) { + .inherit, .ignore => {}, + .expect_exact => |expected_bytes| { + if (!mem.eql(u8, expected_bytes, stderr.?)) { + std.debug.print( + \\ + \\========= Expected this stderr: ========= + \\{s} + \\========= But found: ==================== + \\{s} + \\ + , .{ expected_bytes, stderr.? }); + printCmd(cwd, argv); + return error.TestFailed; + } + }, + .expect_matches => |matches| for (matches) |match| { + if (mem.indexOf(u8, stderr.?, match) == null) { + std.debug.print( + \\ + \\========= Expected to find in stderr: ========= + \\{s} + \\========= But stderr does not contain it: ===== + \\{s} + \\ + , .{ match, stderr.? }); + printCmd(cwd, argv); + return error.TestFailed; + } + }, + } + + switch (stdout_action) { + .inherit, .ignore => {}, + .expect_exact => |expected_bytes| { + if (!mem.eql(u8, expected_bytes, stdout.?)) { + std.debug.print( + \\ + \\========= Expected this stdout: ========= + \\{s} + \\========= But found: ==================== + \\{s} + \\ + , .{ expected_bytes, stdout.? }); + printCmd(cwd, argv); + return error.TestFailed; + } + }, + .expect_matches => |matches| for (matches) |match| { + if (mem.indexOf(u8, stdout.?, match) == null) { + std.debug.print( + \\ + \\========= Expected to find in stdout: ========= + \\{s} + \\========= But stdout does not contain it: ===== + \\{s} + \\ + , .{ match, stdout.? }); + printCmd(cwd, argv); + return error.TestFailed; + } + }, + } +} + +fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void { + if (cwd) |yes_cwd| std.debug.print("cd {s} && ", .{yes_cwd}); + for (argv) |arg| { + std.debug.print("{s} ", .{arg}); + } + std.debug.print("\n", .{}); +} + +fn addPathForDynLibs(self: *RunStep, artifact: *LibExeObjStep) void { + addPathForDynLibsInternal(&self.step, self.builder, artifact); +} + +/// This should only be used for internal usage, this is called automatically +/// for the user. +pub fn addPathForDynLibsInternal(step: *Step, builder: *std.Build, artifact: *LibExeObjStep) void { + for (artifact.link_objects.items) |link_object| { + switch (link_object) { + .other_step => |other| { + if (other.target.isWindows() and other.isDynamicLibrary()) { + addPathDirInternal(step, builder, fs.path.dirname(other.getOutputSource().getPath(builder)).?); + addPathForDynLibsInternal(step, builder, other); + } + }, + else => {}, + } + } +} diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig new file mode 100644 index 0000000000..86d6645c29 --- /dev/null +++ b/lib/std/Build/Step.zig @@ -0,0 +1,97 @@ +id: Id, +name: []const u8, +makeFn: *const fn (self: *Step) anyerror!void, +dependencies: std.ArrayList(*Step), +loop_flag: bool, +done_flag: bool, + +pub const Id = enum { + top_level, + lib_exe_obj, + install_artifact, + install_file, + install_dir, + log, + remove_dir, + fmt, + translate_c, + write_file, + run, + emulatable_run, + check_file, + check_object, + config_header, + install_raw, + options, + custom, + + pub fn Type(comptime id: Id) type { + return switch (id) { + .top_level => Build.TopLevelStep, + .lib_exe_obj => Build.LibExeObjStep, + .install_artifact => Build.InstallArtifactStep, + .install_file => Build.InstallFileStep, + .install_dir => Build.InstallDirStep, + .log => Build.LogStep, + .remove_dir => Build.RemoveDirStep, + .fmt => Build.FmtStep, + .translate_c => Build.TranslateCStep, + .write_file => Build.WriteFileStep, + .run => Build.RunStep, + .emulatable_run => Build.EmulatableRunStep, + .check_file => Build.CheckFileStep, + .check_object => Build.CheckObjectStep, + .config_header => Build.ConfigHeaderStep, + .install_raw => Build.InstallRawStep, + .options => Build.OptionsStep, + .custom => @compileError("no type available for custom step"), + }; + } +}; + +pub fn init( + id: Id, + name: []const u8, + allocator: Allocator, + makeFn: *const fn (self: *Step) anyerror!void, +) Step { + return Step{ + .id = id, + .name = allocator.dupe(u8, name) catch unreachable, + .makeFn = makeFn, + .dependencies = std.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; +} + +const Step = @This(); +const std = @import("../std.zig"); +const Build = std.Build; +const Allocator = std.mem.Allocator; diff --git a/lib/std/Build/TranslateCStep.zig b/lib/std/Build/TranslateCStep.zig new file mode 100644 index 0000000000..5404747846 --- /dev/null +++ b/lib/std/Build/TranslateCStep.zig @@ -0,0 +1,136 @@ +const std = @import("../std.zig"); +const Step = std.Build.Step; +const LibExeObjStep = std.Build.LibExeObjStep; +const CheckFileStep = std.Build.CheckFileStep; +const fs = std.fs; +const mem = std.mem; +const CrossTarget = std.zig.CrossTarget; + +const TranslateCStep = @This(); + +pub const base_id = .translate_c; + +step: Step, +builder: *std.Build, +source: std.Build.FileSource, +include_dirs: std.ArrayList([]const u8), +c_macros: std.ArrayList([]const u8), +output_dir: ?[]const u8, +out_basename: []const u8, +target: CrossTarget, +optimize: std.builtin.OptimizeMode, +output_file: std.Build.GeneratedFile, + +pub const Options = struct { + source_file: std.Build.FileSource, + target: CrossTarget, + optimize: std.builtin.OptimizeMode, +}; + +pub fn create(builder: *std.Build, options: Options) *TranslateCStep { + const self = builder.allocator.create(TranslateCStep) catch unreachable; + const source = options.source_file.dupe(builder); + self.* = TranslateCStep{ + .step = Step.init(.translate_c, "translate-c", builder.allocator, make), + .builder = builder, + .source = source, + .include_dirs = std.ArrayList([]const u8).init(builder.allocator), + .c_macros = std.ArrayList([]const u8).init(builder.allocator), + .output_dir = null, + .out_basename = undefined, + .target = options.target, + .optimize = options.optimize, + .output_file = std.Build.GeneratedFile{ .step = &self.step }, + }; + source.addStepDependencies(&self.step); + return self; +} + +pub const AddExecutableOptions = struct { + name: ?[]const u8 = null, + version: ?std.builtin.Version = null, + target: ?CrossTarget = null, + optimize: ?std.builtin.Mode = null, + linkage: ?LibExeObjStep.Linkage = null, +}; + +/// Creates a step to build an executable from the translated source. +pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *LibExeObjStep { + return self.builder.addExecutable(.{ + .root_source_file = .{ .generated = &self.output_file }, + .name = options.name orelse "translated_c", + .version = options.version, + .target = options.target orelse self.target, + .optimize = options.optimize orelse self.optimize, + .linkage = options.linkage, + }); +} + +pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void { + self.include_dirs.append(self.builder.dupePath(include_dir)) catch unreachable; +} + +pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep { + return CheckFileStep.create(self.builder, .{ .generated = &self.output_file }, self.builder.dupeStrings(expected_matches)); +} + +/// 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: *TranslateCStep, name: []const u8, value: ?[]const u8) void { + const macro = std.Build.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: *TranslateCStep, name_and_value: []const u8) void { + self.c_macros.append(self.builder.dupe(name_and_value)) catch unreachable; +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(TranslateCStep, "step", step); + + var argv_list = std.ArrayList([]const u8).init(self.builder.allocator); + try argv_list.append(self.builder.zig_exe); + try argv_list.append("translate-c"); + try argv_list.append("-lc"); + + try argv_list.append("--enable-cache"); + + if (!self.target.isNative()) { + try argv_list.append("-target"); + try argv_list.append(try self.target.zigTriple(self.builder.allocator)); + } + + switch (self.optimize) { + .Debug => {}, // Skip since it's the default. + else => try argv_list.append(self.builder.fmt("-O{s}", .{@tagName(self.optimize)})), + } + + for (self.include_dirs.items) |include_dir| { + try argv_list.append("-I"); + try argv_list.append(include_dir); + } + + for (self.c_macros.items) |c_macro| { + try argv_list.append("-D"); + try argv_list.append(c_macro); + } + + try argv_list.append(self.source.getPath(self.builder)); + + const output_path_nl = try self.builder.execFromStep(argv_list.items, &self.step); + const output_path = mem.trimRight(u8, output_path_nl, "\r\n"); + + self.out_basename = fs.path.basename(output_path); + if (self.output_dir) |output_dir| { + const full_dest = try fs.path.join(self.builder.allocator, &[_][]const u8{ output_dir, self.out_basename }); + try self.builder.updateFile(output_path, full_dest); + } else { + self.output_dir = fs.path.dirname(output_path).?; + } + + self.output_file.path = fs.path.join( + self.builder.allocator, + &[_][]const u8{ self.output_dir.?, self.out_basename }, + ) catch unreachable; +} diff --git a/lib/std/Build/WriteFileStep.zig b/lib/std/Build/WriteFileStep.zig new file mode 100644 index 0000000000..59ac568221 --- /dev/null +++ b/lib/std/Build/WriteFileStep.zig @@ -0,0 +1,115 @@ +const std = @import("../std.zig"); +const Step = std.Build.Step; +const fs = std.fs; +const ArrayList = std.ArrayList; + +const WriteFileStep = @This(); + +pub const base_id = .write_file; + +step: Step, +builder: *std.Build, +output_dir: []const u8, +files: std.TailQueue(File), + +pub const File = struct { + source: std.Build.GeneratedFile, + basename: []const u8, + bytes: []const u8, +}; + +pub fn init(builder: *std.Build) WriteFileStep { + return WriteFileStep{ + .builder = builder, + .step = Step.init(.write_file, "writefile", builder.allocator, make), + .files = .{}, + .output_dir = undefined, + }; +} + +pub fn add(self: *WriteFileStep, basename: []const u8, bytes: []const u8) void { + const node = self.builder.allocator.create(std.TailQueue(File).Node) catch unreachable; + node.* = .{ + .data = .{ + .source = std.Build.GeneratedFile{ .step = &self.step }, + .basename = self.builder.dupePath(basename), + .bytes = self.builder.dupe(bytes), + }, + }; + + self.files.append(node); +} + +/// Gets a file source for the given basename. If the file does not exist, returns `null`. +pub fn getFileSource(step: *WriteFileStep, basename: []const u8) ?std.Build.FileSource { + var it = step.files.first; + while (it) |node| : (it = node.next) { + if (std.mem.eql(u8, node.data.basename, basename)) + return std.Build.FileSource{ .generated = &node.data.source }; + } + return null; +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(WriteFileStep, "step", step); + + // The cache is used here not really as a way to speed things up - because writing + // the data to a file would probably be very fast - but as a way to find a canonical + // location to put build artifacts. + + // If, for example, a hard-coded path was used as the location to put WriteFileStep + // files, then two WriteFileSteps executing in parallel might clobber each other. + + // TODO port the cache system from the compiler to zig std lib. Until then + // we directly construct the path, and no "cache hit" detection happens; + // the files are always written. + // Note there is similar code over in ConfigHeaderStep. + const Hasher = std.crypto.auth.siphash.SipHash128(1, 3); + // Random bytes to make WriteFileStep unique. Refresh this with + // new random bytes when WriteFileStep implementation is modified + // in a non-backwards-compatible way. + var hash = Hasher.init("eagVR1dYXoE7ARDP"); + + { + var it = self.files.first; + while (it) |node| : (it = node.next) { + hash.update(node.data.basename); + hash.update(node.data.bytes); + hash.update("|"); + } + } + var digest: [16]u8 = undefined; + hash.final(&digest); + var hash_basename: [digest.len * 2]u8 = undefined; + _ = std.fmt.bufPrint( + &hash_basename, + "{s}", + .{std.fmt.fmtSliceHexLower(&digest)}, + ) catch unreachable; + + self.output_dir = try fs.path.join(self.builder.allocator, &[_][]const u8{ + self.builder.cache_root, "o", &hash_basename, + }); + var dir = fs.cwd().makeOpenPath(self.output_dir, .{}) catch |err| { + std.debug.print("unable to make path {s}: {s}\n", .{ self.output_dir, @errorName(err) }); + return err; + }; + defer dir.close(); + { + var it = self.files.first; + while (it) |node| : (it = node.next) { + dir.writeFile(node.data.basename, node.data.bytes) catch |err| { + std.debug.print("unable to write {s} into {s}: {s}\n", .{ + node.data.basename, + self.output_dir, + @errorName(err), + }); + return err; + }; + node.data.source.path = fs.path.join( + self.builder.allocator, + &[_][]const u8{ self.output_dir, node.data.basename }, + ) catch unreachable; + } + } +} diff --git a/lib/std/build.zig b/lib/std/build.zig deleted file mode 100644 index 4ee00a4710..0000000000 --- a/lib/std/build.zig +++ /dev/null @@ -1,1863 +0,0 @@ -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 log = std.log; -const ArrayList = std.ArrayList; -const StringHashMap = std.StringHashMap; -const Allocator = mem.Allocator; -const process = std.process; -const EnvMap = std.process.EnvMap; -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; -const ThisModule = @This(); - -pub const CheckFileStep = @import("build/CheckFileStep.zig"); -pub const CheckObjectStep = @import("build/CheckObjectStep.zig"); -pub const ConfigHeaderStep = @import("build/ConfigHeaderStep.zig"); -pub const EmulatableRunStep = @import("build/EmulatableRunStep.zig"); -pub const FmtStep = @import("build/FmtStep.zig"); -pub const InstallArtifactStep = @import("build/InstallArtifactStep.zig"); -pub const InstallDirStep = @import("build/InstallDirStep.zig"); -pub const InstallFileStep = @import("build/InstallFileStep.zig"); -pub const InstallRawStep = @import("build/InstallRawStep.zig"); -pub const LibExeObjStep = @import("build/LibExeObjStep.zig"); -pub const LogStep = @import("build/LogStep.zig"); -pub const OptionsStep = @import("build/OptionsStep.zig"); -pub const RemoveDirStep = @import("build/RemoveDirStep.zig"); -pub const RunStep = @import("build/RunStep.zig"); -pub const TranslateCStep = @import("build/TranslateCStep.zig"); -pub const WriteFileStep = @import("build/WriteFileStep.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, - reference_trace: ?u32 = null, - invalid_user_input: bool, - zig_exe: []const u8, - default_step: *Step, - env_map: *EnvMap, - 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), - /// Path to the directory containing build.zig. - build_root: []const u8, - cache_root: []const u8, - global_cache_root: []const u8, - /// zig lib dir - override_lib_dir: ?[]const u8, - vcpkg_root: VcpkgRoot = .unattempted, - pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null, - args: ?[][]const u8 = null, - debug_log_scopes: []const []const u8 = &.{}, - debug_compile_errors: bool = false, - - /// 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, - - dep_prefix: []const u8 = "", - - 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), - map: StringHashMap(*const UserValue), - }; - - 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(EnvMap); - env_map.* = try process.getEnvMap(allocator); - - const host = try NativeTargetInfo.detect(.{}); - - 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", - }, - .override_lib_dir = null, - .install_path = undefined, - .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; - } - - fn createChild( - parent: *Builder, - dep_name: []const u8, - build_root: []const u8, - args: anytype, - ) !*Builder { - const child = try createChildOnly(parent, dep_name, build_root); - try applyArgs(child, args); - return child; - } - - fn createChildOnly(parent: *Builder, dep_name: []const u8, build_root: []const u8) !*Builder { - const allocator = parent.allocator; - const child = try allocator.create(Builder); - child.* = .{ - .allocator = allocator, - .install_tls = .{ - .step = Step.initNoOp(.top_level, "install", allocator), - .description = "Copy build artifacts to prefix path", - }, - .uninstall_tls = .{ - .step = Step.init(.top_level, "uninstall", allocator, makeUninstall), - .description = "Remove build artifacts from prefix path", - }, - .user_input_options = UserInputOptionsMap.init(allocator), - .available_options_map = AvailableOptionsMap.init(allocator), - .available_options_list = ArrayList(AvailableOption).init(allocator), - .verbose = parent.verbose, - .verbose_link = parent.verbose_link, - .verbose_cc = parent.verbose_cc, - .verbose_air = parent.verbose_air, - .verbose_llvm_ir = parent.verbose_llvm_ir, - .verbose_cimport = parent.verbose_cimport, - .verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features, - .prominent_compile_errors = parent.prominent_compile_errors, - .color = parent.color, - .reference_trace = parent.reference_trace, - .invalid_user_input = false, - .zig_exe = parent.zig_exe, - .default_step = undefined, - .env_map = parent.env_map, - .top_level_steps = ArrayList(*TopLevelStep).init(allocator), - .install_prefix = undefined, - .dest_dir = parent.dest_dir, - .lib_dir = parent.lib_dir, - .exe_dir = parent.exe_dir, - .h_dir = parent.h_dir, - .install_path = parent.install_path, - .sysroot = parent.sysroot, - .search_prefixes = ArrayList([]const u8).init(allocator), - .libc_file = parent.libc_file, - .installed_files = ArrayList(InstalledFile).init(allocator), - .build_root = build_root, - .cache_root = parent.cache_root, - .global_cache_root = parent.global_cache_root, - .override_lib_dir = parent.override_lib_dir, - .debug_log_scopes = parent.debug_log_scopes, - .debug_compile_errors = parent.debug_compile_errors, - .enable_darling = parent.enable_darling, - .enable_qemu = parent.enable_qemu, - .enable_rosetta = parent.enable_rosetta, - .enable_wasmtime = parent.enable_wasmtime, - .enable_wine = parent.enable_wine, - .glibc_runtimes_dir = parent.glibc_runtimes_dir, - .host = parent.host, - .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }), - }; - try child.top_level_steps.append(&child.install_tls); - try child.top_level_steps.append(&child.uninstall_tls); - child.default_step = &child.install_tls.step; - return child; - } - - fn applyArgs(b: *Builder, args: anytype) !void { - inline for (@typeInfo(@TypeOf(args)).Struct.fields) |field| { - const v = @field(args, field.name); - const T = @TypeOf(v); - switch (T) { - CrossTarget => { - try b.user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .scalar = try v.zigTriple(b.allocator) }, - .used = false, - }); - try b.user_input_options.put("cpu", .{ - .name = "cpu", - .value = .{ .scalar = try serializeCpu(b.allocator, v.getCpu()) }, - .used = false, - }); - }, - []const u8 => { - try b.user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .scalar = v }, - .used = false, - }); - }, - else => switch (@typeInfo(T)) { - .Bool => { - try b.user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .scalar = if (v) "true" else "false" }, - .used = false, - }); - }, - .Enum => { - try b.user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .scalar = @tagName(v) }, - .used = false, - }); - }, - .Int => { - try b.user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .scalar = try std.fmt.allocPrint(b.allocator, "{d}", .{v}) }, - .used = false, - }); - }, - else => @compileError("option '" ++ field.name ++ "' has unsupported type: " ++ @typeName(T)), - }, - } - } - const Hasher = std.crypto.auth.siphash.SipHash128(1, 3); - // Random bytes to make unique. Refresh this with new random bytes when - // implementation is modified in a non-backwards-compatible way. - var hash = Hasher.init("ZaEsvQ5ClaA2IdH9"); - hash.update(b.dep_prefix); - // TODO additionally update the hash with `args`. - - var digest: [16]u8 = undefined; - hash.final(&digest); - var hash_basename: [digest.len * 2]u8 = undefined; - _ = std.fmt.bufPrint(&hash_basename, "{s}", .{std.fmt.fmtSliceHexLower(&digest)}) catch - unreachable; - - const install_prefix = b.pathJoin(&.{ b.cache_root, "i", &hash_basename }); - b.resolveInstallPrefix(install_prefix, .{}); - } - - 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 lib/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); - } - - pub fn addOptions(self: *Builder) *OptionsStep { - return OptionsStep.create(self); - } - - pub const ExecutableOptions = struct { - name: []const u8, - root_source_file: ?FileSource = null, - version: ?std.builtin.Version = null, - target: CrossTarget = .{}, - optimize: std.builtin.Mode = .Debug, - linkage: ?LibExeObjStep.Linkage = null, - }; - - pub fn addExecutable(b: *Builder, options: ExecutableOptions) *LibExeObjStep { - return LibExeObjStep.create(b, .{ - .name = options.name, - .root_source_file = options.root_source_file, - .version = options.version, - .target = options.target, - .optimize = options.optimize, - .kind = .exe, - .linkage = options.linkage, - }); - } - - pub const ObjectOptions = struct { - name: []const u8, - root_source_file: ?FileSource = null, - target: CrossTarget, - optimize: std.builtin.Mode, - }; - - pub fn addObject(b: *Builder, options: ObjectOptions) *LibExeObjStep { - return LibExeObjStep.create(b, .{ - .name = options.name, - .root_source_file = options.root_source_file, - .target = options.target, - .optimize = options.optimize, - .kind = .obj, - }); - } - - pub const SharedLibraryOptions = struct { - name: []const u8, - root_source_file: ?FileSource = null, - version: ?std.builtin.Version = null, - target: CrossTarget, - optimize: std.builtin.Mode, - }; - - pub fn addSharedLibrary(b: *Builder, options: SharedLibraryOptions) *LibExeObjStep { - return LibExeObjStep.create(b, .{ - .name = options.name, - .root_source_file = options.root_source_file, - .kind = .lib, - .linkage = .dynamic, - .version = options.version, - .target = options.target, - .optimize = options.optimize, - }); - } - - pub const StaticLibraryOptions = struct { - name: []const u8, - root_source_file: ?FileSource = null, - target: CrossTarget, - optimize: std.builtin.Mode, - version: ?std.builtin.Version = null, - }; - - pub fn addStaticLibrary(b: *Builder, options: StaticLibraryOptions) *LibExeObjStep { - return LibExeObjStep.create(b, .{ - .name = options.name, - .root_source_file = options.root_source_file, - .kind = .lib, - .linkage = .static, - .version = options.version, - .target = options.target, - .optimize = options.optimize, - }); - } - - pub const TestOptions = struct { - name: []const u8 = "test", - kind: LibExeObjStep.Kind = .@"test", - root_source_file: FileSource, - target: CrossTarget = .{}, - optimize: std.builtin.Mode = .Debug, - version: ?std.builtin.Version = null, - }; - - pub fn addTest(b: *Builder, options: TestOptions) *LibExeObjStep { - return LibExeObjStep.create(b, .{ - .name = options.name, - .kind = options.kind, - .root_source_file = options.root_source_file, - .target = options.target, - .optimize = options.optimize, - }); - } - - pub const AssemblyOptions = struct { - name: []const u8, - source_file: FileSource, - target: CrossTarget, - optimize: std.builtin.Mode, - }; - - pub fn addAssembly(b: *Builder, options: AssemblyOptions) *LibExeObjStep { - const obj_step = LibExeObjStep.create(b, .{ - .name = options.name, - .root_source_file = null, - .target = options.target, - .optimize = options.optimize, - }); - obj_step.addAssemblyFileSource(options.source_file.dupe(b)); - 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; - } - - pub fn addConfigHeader( - b: *Builder, - source: FileSource, - style: ConfigHeaderStep.Style, - values: anytype, - ) *ConfigHeaderStep { - const config_header_step = ConfigHeaderStep.create(b, source, style); - config_header_step.addValues(values); - return config_header_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), - .source = package.source.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, options: TranslateCStep.Options) *TranslateCStep { - return TranslateCStep.create(self, options); - } - - 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) { - log.info("rm {s}", .{full_path}); - } - fs.cwd().deleteTree(full_path) catch {}; - } - - // TODO remove empty directories - } - - fn makeOneStep(self: *Builder, s: *Step) anyerror!void { - if (s.loop_flag) { - log.err("Dependency loop detected:\n {s}", .{s.name}); - return error.DependencyLoopDetected; - } - s.loop_flag = true; - - for (s.dependencies.items) |dep| { - self.makeOneStep(dep) catch |err| { - if (err == error.DependencyLoopDetected) { - log.err(" {s}", .{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; - } - } - log.err("Cannot run step '{s}' because it does not exist", .{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() catch unreachable; - } 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 { - log.err("Expected -D{s} to be a boolean, but received '{s}'\n", .{ name, s }); - self.markInvalidUserInput(); - return null; - } - }, - .list, .map => { - log.err("Expected -D{s} to be a boolean, but received a {s}.\n", .{ - name, @tagName(option_ptr.value), - }); - self.markInvalidUserInput(); - return null; - }, - }, - .int => switch (option_ptr.value) { - .flag, .list, .map => { - log.err("Expected -D{s} to be an integer, but received a {s}.\n", .{ - name, @tagName(option_ptr.value), - }); - self.markInvalidUserInput(); - return null; - }, - .scalar => |s| { - const n = std.fmt.parseInt(T, s, 10) catch |err| switch (err) { - error.Overflow => { - log.err("-D{s} value {s} cannot fit into type {s}.\n", .{ name, s, @typeName(T) }); - self.markInvalidUserInput(); - return null; - }, - else => { - log.err("Expected -D{s} to be an integer of type {s}.\n", .{ name, @typeName(T) }); - self.markInvalidUserInput(); - return null; - }, - }; - return n; - }, - }, - .float => switch (option_ptr.value) { - .flag, .map, .list => { - log.err("Expected -D{s} to be a float, but received a {s}.\n", .{ - name, @tagName(option_ptr.value), - }); - self.markInvalidUserInput(); - return null; - }, - .scalar => |s| { - const n = std.fmt.parseFloat(T, s) catch { - log.err("Expected -D{s} to be a float of type {s}.\n", .{ name, @typeName(T) }); - self.markInvalidUserInput(); - return null; - }; - return n; - }, - }, - .@"enum" => switch (option_ptr.value) { - .flag, .map, .list => { - log.err("Expected -D{s} to be an enum, but received a {s}.\n", .{ - name, @tagName(option_ptr.value), - }); - self.markInvalidUserInput(); - return null; - }, - .scalar => |s| { - if (std.meta.stringToEnum(T, s)) |enum_lit| { - return enum_lit; - } else { - log.err("Expected -D{s} to be of type {s}.\n", .{ name, @typeName(T) }); - self.markInvalidUserInput(); - return null; - } - }, - }, - .string => switch (option_ptr.value) { - .flag, .list, .map => { - log.err("Expected -D{s} to be a string, but received a {s}.\n", .{ - name, @tagName(option_ptr.value), - }); - self.markInvalidUserInput(); - return null; - }, - .scalar => |s| return s, - }, - .list => switch (option_ptr.value) { - .flag, .map => { - log.err("Expected -D{s} to be a list, but received a {s}.\n", .{ - name, @tagName(option_ptr.value), - }); - 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; - } - - pub const StandardOptimizeOptionOptions = struct { - preferred_optimize_mode: ?std.builtin.Mode = null, - }; - - pub fn standardOptimizeOption(self: *Builder, options: StandardOptimizeOptionOptions) std.builtin.Mode { - if (options.preferred_optimize_mode) |mode| { - if (self.option(bool, "release", "optimize for end users") orelse false) { - return mode; - } else { - return .Debug; - } - } else { - return self.option( - std.builtin.Mode, - "optimize", - "prioritize performance, safety, or binary size (-O flag)", - ) orelse .Debug; - } - } - - 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 => { - log.err("Unknown CPU: '{s}'\nAvailable CPUs for architecture '{s}':", .{ - diags.cpu_name.?, - @tagName(diags.arch.?), - }); - for (diags.arch.?.allCpuModels()) |cpu| { - log.err(" {s}", .{cpu.name}); - } - self.markInvalidUserInput(); - return args.default_target; - }, - error.UnknownCpuFeature => { - log.err( - \\Unknown CPU feature: '{s}' - \\Available CPU features for architecture '{s}': - \\ - , .{ - diags.unknown_feature_name.?, - @tagName(diags.arch.?), - }); - for (diags.arch.?.allFeaturesList()) |feature| { - log.err(" {s}: {s}", .{ feature.name, feature.description }); - } - self.markInvalidUserInput(); - return args.default_target; - }, - error.UnknownOperatingSystem => { - log.err( - \\Unknown OS: '{s}' - \\Available operating systems: - \\ - , .{diags.os_name.?}); - inline for (std.meta.fields(std.Target.Os.Tag)) |field| { - log.err(" {s}", .{field.name}); - } - self.markInvalidUserInput(); - return args.default_target; - }, - else => |e| { - log.err("Unable to parse target '{s}': {s}\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) { - log.err("Chosen target '{s}' does not match one of the supported targets:", .{ - selected_canonicalized_triple, - }); - for (list) |t| { - const t_triple = t.zigTriple(self.allocator) catch unreachable; - log.err(" {s}", .{t_triple}); - } - } else { - assert(mismatch_cpu_features); - const whitelist_cpu = whitelist_item.getCpu(); - const selected_cpu = selected_target.getCpu(); - log.err("Chosen CPU model '{s}' does not match one of the supported targets:", .{ - selected_cpu.model.name, - }); - log.err(" 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) { - log.err("{s} ", .{feature.name}); - } - } - log.err(" 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) { - log.err("{s} ", .{feature.name}); - } - } - } - 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 => { - log.warn("Option '-D{s}={s}' conflicts with flag '-D{s}'.", .{ name, value, name }); - return true; - }, - .map => |*map| { - _ = map; - log.warn("TODO maps as command line arguments is not implemented yet.", .{}); - 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| { - log.err("Flag '-D{s}' conflicts with option '-D{s}={s}'.", .{ name, name, s }); - return true; - }, - .list, .map => { - log.err("Flag '-D{s}' conflicts with multiple options of the same name.", .{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) { - log.err("Invalid option: -D{s}", .{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| std.debug.print("cd {s} && ", .{yes_cwd}); - for (argv) |arg| { - std.debug.print("{s} ", .{arg}); - } - std.debug.print("\n", .{}); - } - - pub fn spawnChildEnvMap(self: *Builder, cwd: ?[]const u8, env_map: *const EnvMap, argv: []const []const u8) !void { - if (self.verbose) { - printCmd(cwd, argv); - } - - if (!std.process.can_spawn) - return error.ExecNotSupported; - - var child = std.ChildProcess.init(argv, self.allocator); - child.cwd = cwd; - child.env_map = env_map; - - const term = child.spawnAndWait() catch |err| { - log.err("Unable to spawn {s}: {s}", .{ argv[0], @errorName(err) }); - return err; - }; - - switch (term) { - .Exited => |code| { - if (code != 0) { - log.err("The following command exited with error code {}:", .{code}); - printCmd(cwd, argv); - return error.UncleanExit; - } - }, - else => { - log.err("The following command terminated unexpectedly:", .{}); - printCmd(cwd, argv); - - return error.UncleanExit; - }, - } - } - - pub fn makePath(self: *Builder, path: []const u8) !void { - fs.cwd().makePath(self.pathFromRoot(path)) catch |err| { - log.err("Unable to create path {s}: {s}", .{ 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 addInstallHeaderFile(b: *Builder, src_path: []const u8, dest_rel_path: []const u8) *InstallFileStep { - return b.addInstallFileWithDir(.{ .path = src_path }, .header, 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) { - log.info("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 => log.info("# installed", .{}), - .fresh => log.info("# up-to-date", .{}), - }; - } - - pub fn truncateFile(self: *Builder, dest_path: []const u8) !void { - if (self.verbose) { - log.info("truncate {s}", .{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; - var child = std.ChildProcess.init(argv, self.allocator); - 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| log.err("{s}...", .{s.name}); - log.err("Unable to spawn the following command: cannot spawn child process", .{}); - 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| log.err("{s}...", .{s.name}); - log.err("Unable to spawn the following command: cannot spawn child process", .{}); - printCmd(null, argv); - std.os.abort(); - }, - error.FileNotFound => { - if (src_step) |s| log.err("{s}...", .{s.name}); - log.err("Unable to spawn the following command: file not found", .{}); - printCmd(null, argv); - std.os.exit(@truncate(u8, code)); - }, - error.ExitCodeFailure => { - if (src_step) |s| log.err("{s}...", .{s.name}); - if (self.prominent_compile_errors) { - log.err("The step exited with error code {d}", .{code}); - } else { - log.err("The following command exited with error code {d}:", .{code}); - printCmd(null, argv); - } - - std.os.exit(@truncate(u8, code)); - }, - error.ProcessTerminated => { - if (src_step) |s| log.err("{s}...", .{s.name}); - log.err("The following command terminated unexpectedly:", .{}); - 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; - } - - pub const Dependency = struct { - builder: *Builder, - - pub fn artifact(d: *Dependency, name: []const u8) *LibExeObjStep { - var found: ?*LibExeObjStep = null; - for (d.builder.install_tls.step.dependencies.items) |dep_step| { - const inst = dep_step.cast(InstallArtifactStep) orelse continue; - if (mem.eql(u8, inst.artifact.name, name)) { - if (found != null) panic("artifact name '{s}' is ambiguous", .{name}); - found = inst.artifact; - } - } - return found orelse { - for (d.builder.install_tls.step.dependencies.items) |dep_step| { - const inst = dep_step.cast(InstallArtifactStep) orelse continue; - log.info("available artifact: '{s}'", .{inst.artifact.name}); - } - panic("unable to find artifact '{s}'", .{name}); - }; - } - }; - - pub fn dependency(b: *Builder, name: []const u8, args: anytype) *Dependency { - const build_runner = @import("root"); - const deps = build_runner.dependencies; - - inline for (@typeInfo(deps.imports).Struct.decls) |decl| { - if (mem.startsWith(u8, decl.name, b.dep_prefix) and - mem.endsWith(u8, decl.name, name) and - decl.name.len == b.dep_prefix.len + name.len) - { - const build_zig = @field(deps.imports, decl.name); - const build_root = @field(deps.build_root, decl.name); - return dependencyInner(b, name, build_root, build_zig, args); - } - } - - const full_path = b.pathFromRoot("build.zig.ini"); - std.debug.print("no dependency named '{s}' in '{s}'\n", .{ name, full_path }); - std.process.exit(1); - } - - fn dependencyInner( - b: *Builder, - name: []const u8, - build_root: []const u8, - comptime build_zig: type, - args: anytype, - ) *Dependency { - const sub_builder = b.createChild(name, build_root, args) catch unreachable; - sub_builder.runBuild(build_zig) catch unreachable; - - if (sub_builder.validateUserInputDidItFail()) { - std.debug.dumpCurrentStackTrace(@returnAddress()); - } - - const dep = b.allocator.create(Dependency) catch unreachable; - dep.* = .{ .builder = sub_builder }; - return dep; - } - - pub fn runBuild(b: *Builder, build_zig: anytype) anyerror!void { - switch (@typeInfo(@typeInfo(@TypeOf(build_zig.build)).Fn.return_type.?)) { - .Void => build_zig.build(b), - .ErrorUnion => try build_zig.build(b), - else => @compileError("expected return type of build to be 'void' or '!void'"), - } - } -}; - -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; -} - -pub const Pkg = struct { - name: []const u8, - source: FileSource, - dependencies: ?[]const Pkg = null, -}; - -/// 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 }, - }; - } -}; - -/// 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; -} - -/// deprecated: use `InstallDirStep.Options` -pub const InstallDirectoryOptions = InstallDirStep.Options; - -pub const Step = struct { - id: Id, - name: []const u8, - makeFn: MakeFn, - dependencies: ArrayList(*Step), - loop_flag: bool, - done_flag: bool, - - const MakeFn = *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, - emulatable_run, - check_file, - check_object, - config_header, - install_raw, - options, - custom, - - pub fn Type(comptime id: Id) type { - return switch (id) { - .top_level => Builder.TopLevelStep, - .lib_exe_obj => LibExeObjStep, - .install_artifact => InstallArtifactStep, - .install_file => InstallFileStep, - .install_dir => InstallDirStep, - .log => LogStep, - .remove_dir => RemoveDirStep, - .fmt => FmtStep, - .translate_c => TranslateCStep, - .write_file => WriteFileStep, - .run => RunStep, - .emulatable_run => EmulatableRunStep, - .check_file => CheckFileStep, - .check_object => CheckObjectStep, - .config_header => ConfigHeaderStep, - .install_raw => InstallRawStep, - .options => OptionsStep, - .custom => @compileError("no type available for custom step"), - }; - } - }; - - 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; - } -}; - -pub const VcpkgRoot = union(VcpkgRootStatus) { - unattempted: void, - not_found: void, - found: []const u8, -}; - -pub 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), - }; - } -}; - -pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 { - // TODO this logic can disappear if cpu model + features becomes part of the target triple - const all_features = cpu.arch.allFeaturesList(); - var populated_cpu_features = cpu.model.features; - populated_cpu_features.populateDependencies(all_features); - - if (populated_cpu_features.eql(cpu.features)) { - // The CPU name alone is sufficient. - return cpu.model.name; - } else { - var mcpu_buffer = ArrayList(u8).init(allocator); - try mcpu_buffer.appendSlice(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 = 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}); - } - } - - return try mcpu_buffer.toOwnedSlice(); - } -} - -test "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", - .source = .{ .path = "/not/a/pkg_dep.zig" }, - }; - var pkg_top = Pkg{ - .name = "pkg_top", - .source = .{ .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.source.path.ptr != pkg_top.source.path.ptr); - try std.testing.expect(dupe_deps[0].name.ptr != pkg_dep.name.ptr); - try std.testing.expect(dupe_deps[0].source.path.ptr != pkg_dep.source.path.ptr); -} - -test { - _ = CheckFileStep; - _ = CheckObjectStep; - _ = EmulatableRunStep; - _ = FmtStep; - _ = InstallArtifactStep; - _ = InstallDirStep; - _ = InstallFileStep; - _ = InstallRawStep; - _ = LibExeObjStep; - _ = LogStep; - _ = OptionsStep; - _ = RemoveDirStep; - _ = RunStep; - _ = TranslateCStep; - _ = WriteFileStep; -} diff --git a/lib/std/build/CheckFileStep.zig b/lib/std/build/CheckFileStep.zig deleted file mode 100644 index 2c06ab9279..0000000000 --- a/lib/std/build/CheckFileStep.zig +++ /dev/null @@ -1,53 +0,0 @@ -const std = @import("../std.zig"); -const build = std.build; -const Step = build.Step; -const Builder = build.Builder; -const fs = std.fs; -const mem = std.mem; - -const CheckFileStep = @This(); - -pub const base_id = .check_file; - -step: Step, -builder: *Builder, -expected_matches: []const []const u8, -source: build.FileSource, -max_bytes: usize = 20 * 1024 * 1024, - -pub fn create( - builder: *Builder, - source: build.FileSource, - expected_matches: []const []const u8, -) *CheckFileStep { - const self = builder.allocator.create(CheckFileStep) catch unreachable; - self.* = CheckFileStep{ - .builder = builder, - .step = Step.init(.check_file, "CheckFile", builder.allocator, make), - .source = source.dupe(builder), - .expected_matches = builder.dupeStrings(expected_matches), - }; - self.source.addStepDependencies(&self.step); - return self; -} - -fn make(step: *Step) !void { - const self = @fieldParentPtr(CheckFileStep, "step", step); - - const src_path = self.source.getPath(self.builder); - const contents = try fs.cwd().readFileAlloc(self.builder.allocator, src_path, self.max_bytes); - - for (self.expected_matches) |expected_match| { - if (mem.indexOf(u8, contents, expected_match) == null) { - std.debug.print( - \\ - \\========= Expected to find: =================== - \\{s} - \\========= But file does not contain it: ======= - \\{s} - \\ - , .{ expected_match, contents }); - return error.TestFailed; - } - } -} diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig deleted file mode 100644 index 4ef350b418..0000000000 --- a/lib/std/build/CheckObjectStep.zig +++ /dev/null @@ -1,1026 +0,0 @@ -const std = @import("../std.zig"); -const assert = std.debug.assert; -const build = std.build; -const fs = std.fs; -const macho = std.macho; -const math = std.math; -const mem = std.mem; -const testing = std.testing; - -const CheckObjectStep = @This(); - -const Allocator = mem.Allocator; -const Builder = build.Builder; -const Step = build.Step; -const EmulatableRunStep = build.EmulatableRunStep; - -pub const base_id = .check_object; - -step: Step, -builder: *Builder, -source: build.FileSource, -max_bytes: usize = 20 * 1024 * 1024, -checks: std.ArrayList(Check), -dump_symtab: bool = false, -obj_format: std.Target.ObjectFormat, - -pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Target.ObjectFormat) *CheckObjectStep { - const gpa = builder.allocator; - const self = gpa.create(CheckObjectStep) catch unreachable; - self.* = .{ - .builder = builder, - .step = Step.init(.check_file, "CheckObject", gpa, make), - .source = source.dupe(builder), - .checks = std.ArrayList(Check).init(gpa), - .obj_format = obj_format, - }; - self.source.addStepDependencies(&self.step); - return self; -} - -/// Runs and (optionally) compares the output of a binary. -/// Asserts `self` was generated from an executable step. -pub fn runAndCompare(self: *CheckObjectStep) *EmulatableRunStep { - const dependencies_len = self.step.dependencies.items.len; - assert(dependencies_len > 0); - const exe_step = self.step.dependencies.items[dependencies_len - 1]; - const exe = exe_step.cast(std.build.LibExeObjStep).?; - const emulatable_step = EmulatableRunStep.create(self.builder, "EmulatableRun", exe); - emulatable_step.step.dependOn(&self.step); - return emulatable_step; -} - -/// There two types of actions currently suported: -/// * `.match` - is the main building block of standard matchers with optional eat-all token `{*}` -/// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature -/// i.e., it won't really handle edge cases/nontrivial examples. But given that we do want to use -/// it mainly to test the output of our object format parser-dumpers when testing the linkers, etc. -/// it should be plenty useful in its current form. -/// * `.compute_cmp` - can be used to perform an operation on the extracted global variables -/// using the MatchAction. It currently only supports an addition. The operation is required -/// to be specified in Reverse Polish Notation to ease in operator-precedence parsing (well, -/// to avoid any parsing really). -/// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively -/// they could then be added with this simple program `vmaddr entryoff +`. -const Action = struct { - tag: enum { match, not_present, compute_cmp }, - phrase: []const u8, - expected: ?ComputeCompareExpected = null, - - /// Will return true if the `phrase` was found in the `haystack`. - /// Some examples include: - /// - /// LC 0 => will match in its entirety - /// vmaddr {vmaddr} => will match `vmaddr` and then extract the following value as u64 - /// and save under `vmaddr` global name (see `global_vars` param) - /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib` - /// in that order with other letters in between - fn match(act: Action, haystack: []const u8, global_vars: anytype) !bool { - assert(act.tag == .match or act.tag == .not_present); - - var candidate_var: ?struct { name: []const u8, value: u64 } = null; - var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " "); - var needle_it = mem.tokenize(u8, mem.trim(u8, act.phrase, " "), " "); - - while (needle_it.next()) |needle_tok| { - const hay_tok = hay_it.next() orelse return false; - - if (mem.indexOf(u8, needle_tok, "{*}")) |index| { - // We have fuzzy matchers within the search pattern, so we match substrings. - var start = index; - var n_tok = needle_tok; - var h_tok = hay_tok; - while (true) { - n_tok = n_tok[start + 3 ..]; - const inner = if (mem.indexOf(u8, n_tok, "{*}")) |sub_end| - n_tok[0..sub_end] - else - n_tok; - if (mem.indexOf(u8, h_tok, inner) == null) return false; - start = mem.indexOf(u8, n_tok, "{*}") orelse break; - } - } else if (mem.startsWith(u8, needle_tok, "{")) { - const closing_brace = mem.indexOf(u8, needle_tok, "}") orelse return error.MissingClosingBrace; - if (closing_brace != needle_tok.len - 1) return error.ClosingBraceNotLast; - - const name = needle_tok[1..closing_brace]; - if (name.len == 0) return error.MissingBraceValue; - const value = try std.fmt.parseInt(u64, hay_tok, 16); - candidate_var = .{ - .name = name, - .value = value, - }; - } else { - if (!mem.eql(u8, hay_tok, needle_tok)) return false; - } - } - - if (candidate_var) |v| { - try global_vars.putNoClobber(v.name, v.value); - } - - return true; - } - - /// Will return true if the `phrase` is correctly parsed into an RPN program and - /// its reduced, computed value compares using `op` with the expected value, either - /// a literal or another extracted variable. - fn computeCmp(act: Action, gpa: Allocator, global_vars: anytype) !bool { - var op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa); - var values = std.ArrayList(u64).init(gpa); - - var it = mem.tokenize(u8, act.phrase, " "); - while (it.next()) |next| { - if (mem.eql(u8, next, "+")) { - try op_stack.append(.add); - } else if (mem.eql(u8, next, "-")) { - try op_stack.append(.sub); - } else if (mem.eql(u8, next, "%")) { - try op_stack.append(.mod); - } else if (mem.eql(u8, next, "*")) { - try op_stack.append(.mul); - } else { - const val = std.fmt.parseInt(u64, next, 0) catch blk: { - break :blk global_vars.get(next) orelse { - std.debug.print( - \\ - \\========= Variable was not extracted: =========== - \\{s} - \\ - , .{next}); - return error.UnknownVariable; - }; - }; - try values.append(val); - } - } - - var op_i: usize = 1; - var reduced: u64 = values.items[0]; - for (op_stack.items) |op| { - const other = values.items[op_i]; - switch (op) { - .add => { - reduced += other; - }, - .sub => { - reduced -= other; - }, - .mod => { - reduced %= other; - }, - .mul => { - reduced *= other; - }, - } - op_i += 1; - } - - const exp_value = switch (act.expected.?.value) { - .variable => |name| global_vars.get(name) orelse { - std.debug.print( - \\ - \\========= Variable was not extracted: =========== - \\{s} - \\ - , .{name}); - return error.UnknownVariable; - }, - .literal => |x| x, - }; - return math.compare(reduced, act.expected.?.op, exp_value); - } -}; - -const ComputeCompareExpected = struct { - op: math.CompareOperator, - value: union(enum) { - variable: []const u8, - literal: u64, - }, - - pub fn format( - value: @This(), - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - if (fmt.len != 0) std.fmt.invalidFmtError(fmt, value); - _ = options; - try writer.print("{s} ", .{@tagName(value.op)}); - switch (value.value) { - .variable => |name| try writer.writeAll(name), - .literal => |x| try writer.print("{x}", .{x}), - } - } -}; - -const Check = struct { - builder: *Builder, - actions: std.ArrayList(Action), - - fn create(b: *Builder) Check { - return .{ - .builder = b, - .actions = std.ArrayList(Action).init(b.allocator), - }; - } - - fn match(self: *Check, phrase: []const u8) void { - self.actions.append(.{ - .tag = .match, - .phrase = self.builder.dupe(phrase), - }) catch unreachable; - } - - fn notPresent(self: *Check, phrase: []const u8) void { - self.actions.append(.{ - .tag = .not_present, - .phrase = self.builder.dupe(phrase), - }) catch unreachable; - } - - fn computeCmp(self: *Check, phrase: []const u8, expected: ComputeCompareExpected) void { - self.actions.append(.{ - .tag = .compute_cmp, - .phrase = self.builder.dupe(phrase), - .expected = expected, - }) catch unreachable; - } -}; - -/// Creates a new sequence of actions with `phrase` as the first anchor searched phrase. -pub fn checkStart(self: *CheckObjectStep, phrase: []const u8) void { - var new_check = Check.create(self.builder); - new_check.match(phrase); - self.checks.append(new_check) catch unreachable; -} - -/// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)`. -/// Asserts at least one check already exists. -pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void { - assert(self.checks.items.len > 0); - const last = &self.checks.items[self.checks.items.len - 1]; - last.match(phrase); -} - -/// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)` -/// however ensures there is no matching phrase in the output. -/// Asserts at least one check already exists. -pub fn checkNotPresent(self: *CheckObjectStep, phrase: []const u8) void { - assert(self.checks.items.len > 0); - const last = &self.checks.items[self.checks.items.len - 1]; - last.notPresent(phrase); -} - -/// Creates a new check checking specifically symbol table parsed and dumped from the object -/// file. -/// Issuing this check will force parsing and dumping of the symbol table. -pub fn checkInSymtab(self: *CheckObjectStep) void { - self.dump_symtab = true; - const symtab_label = switch (self.obj_format) { - .macho => MachODumper.symtab_label, - else => @panic("TODO other parsers"), - }; - self.checkStart(symtab_label); -} - -/// Creates a new standalone, singular check which allows running simple binary operations -/// on the extracted variables. It will then compare the reduced program with the value of -/// the expected variable. -pub fn checkComputeCompare( - self: *CheckObjectStep, - program: []const u8, - expected: ComputeCompareExpected, -) void { - var new_check = Check.create(self.builder); - new_check.computeCmp(program, expected); - self.checks.append(new_check) catch unreachable; -} - -fn make(step: *Step) !void { - const self = @fieldParentPtr(CheckObjectStep, "step", step); - - const gpa = self.builder.allocator; - const src_path = self.source.getPath(self.builder); - const contents = try fs.cwd().readFileAllocOptions( - gpa, - src_path, - self.max_bytes, - null, - @alignOf(u64), - null, - ); - - const output = switch (self.obj_format) { - .macho => try MachODumper.parseAndDump(contents, .{ - .gpa = gpa, - .dump_symtab = self.dump_symtab, - }), - .elf => @panic("TODO elf parser"), - .coff => @panic("TODO coff parser"), - .wasm => try WasmDumper.parseAndDump(contents, .{ - .gpa = gpa, - .dump_symtab = self.dump_symtab, - }), - else => unreachable, - }; - - var vars = std.StringHashMap(u64).init(gpa); - - for (self.checks.items) |chk| { - var it = mem.tokenize(u8, output, "\r\n"); - for (chk.actions.items) |act| { - switch (act.tag) { - .match => { - while (it.next()) |line| { - if (try act.match(line, &vars)) break; - } else { - std.debug.print( - \\ - \\========= Expected to find: ========================== - \\{s} - \\========= But parsed file does not contain it: ======= - \\{s} - \\ - , .{ act.phrase, output }); - return error.TestFailed; - } - }, - .not_present => { - while (it.next()) |line| { - if (try act.match(line, &vars)) { - std.debug.print( - \\ - \\========= Expected not to find: =================== - \\{s} - \\========= But parsed file does contain it: ======== - \\{s} - \\ - , .{ act.phrase, output }); - return error.TestFailed; - } - } - }, - .compute_cmp => { - const res = act.computeCmp(gpa, vars) catch |err| switch (err) { - error.UnknownVariable => { - std.debug.print( - \\========= From parsed file: ===================== - \\{s} - \\ - , .{output}); - return error.TestFailed; - }, - else => |e| return e, - }; - if (!res) { - std.debug.print( - \\ - \\========= Comparison failed for action: =========== - \\{s} {} - \\========= From parsed file: ======================= - \\{s} - \\ - , .{ act.phrase, act.expected.?, output }); - return error.TestFailed; - } - }, - } - } - } -} - -const Opts = struct { - gpa: ?Allocator = null, - dump_symtab: bool = false, -}; - -const MachODumper = struct { - const LoadCommandIterator = macho.LoadCommandIterator; - const symtab_label = "symtab"; - - fn parseAndDump(bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 { - const gpa = opts.gpa orelse unreachable; // MachO dumper requires an allocator - var stream = std.io.fixedBufferStream(bytes); - const reader = stream.reader(); - - const hdr = try reader.readStruct(macho.mach_header_64); - if (hdr.magic != macho.MH_MAGIC_64) { - return error.InvalidMagicNumber; - } - - var output = std.ArrayList(u8).init(gpa); - const writer = output.writer(); - - var symtab: []const macho.nlist_64 = undefined; - var strtab: []const u8 = undefined; - var sections = std.ArrayList(macho.section_64).init(gpa); - var imports = std.ArrayList([]const u8).init(gpa); - - var it = LoadCommandIterator{ - .ncmds = hdr.ncmds, - .buffer = bytes[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], - }; - var i: usize = 0; - while (it.next()) |cmd| { - switch (cmd.cmd()) { - .SEGMENT_64 => { - const seg = cmd.cast(macho.segment_command_64).?; - try sections.ensureUnusedCapacity(seg.nsects); - for (cmd.getSections()) |sect| { - sections.appendAssumeCapacity(sect); - } - }, - .SYMTAB => if (opts.dump_symtab) { - const lc = cmd.cast(macho.symtab_command).?; - symtab = @ptrCast( - [*]const macho.nlist_64, - @alignCast(@alignOf(macho.nlist_64), &bytes[lc.symoff]), - )[0..lc.nsyms]; - strtab = bytes[lc.stroff..][0..lc.strsize]; - }, - .LOAD_DYLIB, - .LOAD_WEAK_DYLIB, - .REEXPORT_DYLIB, - => { - try imports.append(cmd.getDylibPathName()); - }, - else => {}, - } - - try dumpLoadCommand(cmd, i, writer); - try writer.writeByte('\n'); - - i += 1; - } - - if (opts.dump_symtab) { - try writer.print("{s}\n", .{symtab_label}); - for (symtab) |sym| { - if (sym.stab()) continue; - const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); - if (sym.sect()) { - const sect = sections.items[sym.n_sect - 1]; - try writer.print("{x} ({s},{s})", .{ - sym.n_value, - sect.segName(), - sect.sectName(), - }); - if (sym.ext()) { - try writer.writeAll(" external"); - } - try writer.print(" {s}\n", .{sym_name}); - } else if (sym.undf()) { - const ordinal = @divTrunc(@bitCast(i16, sym.n_desc), macho.N_SYMBOL_RESOLVER); - const import_name = blk: { - if (ordinal <= 0) { - if (ordinal == macho.BIND_SPECIAL_DYLIB_SELF) - break :blk "self import"; - if (ordinal == macho.BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE) - break :blk "main executable"; - if (ordinal == macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP) - break :blk "flat lookup"; - unreachable; - } - const full_path = imports.items[@bitCast(u16, ordinal) - 1]; - const basename = fs.path.basename(full_path); - assert(basename.len > 0); - const ext = mem.lastIndexOfScalar(u8, basename, '.') orelse basename.len; - break :blk basename[0..ext]; - }; - try writer.writeAll("(undefined)"); - if (sym.weakRef()) { - try writer.writeAll(" weak"); - } - if (sym.ext()) { - try writer.writeAll(" external"); - } - try writer.print(" {s} (from {s})\n", .{ - sym_name, - import_name, - }); - } else unreachable; - } - } - - return output.toOwnedSlice(); - } - - fn dumpLoadCommand(lc: macho.LoadCommandIterator.LoadCommand, index: usize, writer: anytype) !void { - // print header first - try writer.print( - \\LC {d} - \\cmd {s} - \\cmdsize {d} - , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); - - switch (lc.cmd()) { - .SEGMENT_64 => { - const seg = lc.cast(macho.segment_command_64).?; - try writer.writeByte('\n'); - try writer.print( - \\segname {s} - \\vmaddr {x} - \\vmsize {x} - \\fileoff {x} - \\filesz {x} - , .{ - seg.segName(), - seg.vmaddr, - seg.vmsize, - seg.fileoff, - seg.filesize, - }); - - for (lc.getSections()) |sect| { - try writer.writeByte('\n'); - try writer.print( - \\sectname {s} - \\addr {x} - \\size {x} - \\offset {x} - \\align {x} - , .{ - sect.sectName(), - sect.addr, - sect.size, - sect.offset, - sect.@"align", - }); - } - }, - - .ID_DYLIB, - .LOAD_DYLIB, - .LOAD_WEAK_DYLIB, - .REEXPORT_DYLIB, - => { - const dylib = lc.cast(macho.dylib_command).?; - try writer.writeByte('\n'); - try writer.print( - \\name {s} - \\timestamp {d} - \\current version {x} - \\compatibility version {x} - , .{ - lc.getDylibPathName(), - dylib.dylib.timestamp, - dylib.dylib.current_version, - dylib.dylib.compatibility_version, - }); - }, - - .MAIN => { - const main = lc.cast(macho.entry_point_command).?; - try writer.writeByte('\n'); - try writer.print( - \\entryoff {x} - \\stacksize {x} - , .{ main.entryoff, main.stacksize }); - }, - - .RPATH => { - try writer.writeByte('\n'); - try writer.print( - \\path {s} - , .{ - lc.getRpathPathName(), - }); - }, - - .UUID => { - const uuid = lc.cast(macho.uuid_command).?; - try writer.writeByte('\n'); - try writer.print("uuid {x}", .{std.fmt.fmtSliceHexLower(&uuid.uuid)}); - }, - - .DATA_IN_CODE, - .FUNCTION_STARTS, - .CODE_SIGNATURE, - => { - const llc = lc.cast(macho.linkedit_data_command).?; - try writer.writeByte('\n'); - try writer.print( - \\dataoff {x} - \\datasize {x} - , .{ llc.dataoff, llc.datasize }); - }, - - .DYLD_INFO_ONLY => { - const dlc = lc.cast(macho.dyld_info_command).?; - try writer.writeByte('\n'); - try writer.print( - \\rebaseoff {x} - \\rebasesize {x} - \\bindoff {x} - \\bindsize {x} - \\weakbindoff {x} - \\weakbindsize {x} - \\lazybindoff {x} - \\lazybindsize {x} - \\exportoff {x} - \\exportsize {x} - , .{ - dlc.rebase_off, - dlc.rebase_size, - dlc.bind_off, - dlc.bind_size, - dlc.weak_bind_off, - dlc.weak_bind_size, - dlc.lazy_bind_off, - dlc.lazy_bind_size, - dlc.export_off, - dlc.export_size, - }); - }, - - .SYMTAB => { - const slc = lc.cast(macho.symtab_command).?; - try writer.writeByte('\n'); - try writer.print( - \\symoff {x} - \\nsyms {x} - \\stroff {x} - \\strsize {x} - , .{ - slc.symoff, - slc.nsyms, - slc.stroff, - slc.strsize, - }); - }, - - .DYSYMTAB => { - const dlc = lc.cast(macho.dysymtab_command).?; - try writer.writeByte('\n'); - try writer.print( - \\ilocalsym {x} - \\nlocalsym {x} - \\iextdefsym {x} - \\nextdefsym {x} - \\iundefsym {x} - \\nundefsym {x} - \\indirectsymoff {x} - \\nindirectsyms {x} - , .{ - dlc.ilocalsym, - dlc.nlocalsym, - dlc.iextdefsym, - dlc.nextdefsym, - dlc.iundefsym, - dlc.nundefsym, - dlc.indirectsymoff, - dlc.nindirectsyms, - }); - }, - - else => {}, - } - } -}; - -const WasmDumper = struct { - const symtab_label = "symbols"; - - fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 { - const gpa = opts.gpa orelse unreachable; // Wasm dumper requires an allocator - if (opts.dump_symtab) { - @panic("TODO: Implement symbol table parsing and dumping"); - } - - var fbs = std.io.fixedBufferStream(bytes); - const reader = fbs.reader(); - - const buf = try reader.readBytesNoEof(8); - if (!mem.eql(u8, buf[0..4], &std.wasm.magic)) { - return error.InvalidMagicByte; - } - if (!mem.eql(u8, buf[4..], &std.wasm.version)) { - return error.UnsupportedWasmVersion; - } - - var output = std.ArrayList(u8).init(gpa); - errdefer output.deinit(); - const writer = output.writer(); - - while (reader.readByte()) |current_byte| { - const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch |err| { - std.debug.print("Found invalid section id '{d}'\n", .{current_byte}); - return err; - }; - - const section_length = try std.leb.readULEB128(u32, reader); - try parseAndDumpSection(section, bytes[fbs.pos..][0..section_length], writer); - fbs.pos += section_length; - } else |_| {} // reached end of stream - - return output.toOwnedSlice(); - } - - fn parseAndDumpSection(section: std.wasm.Section, data: []const u8, writer: anytype) !void { - var fbs = std.io.fixedBufferStream(data); - const reader = fbs.reader(); - - try writer.print( - \\Section {s} - \\size {d} - , .{ @tagName(section), data.len }); - - switch (section) { - .type, - .import, - .function, - .table, - .memory, - .global, - .@"export", - .element, - .code, - .data, - => { - const entries = try std.leb.readULEB128(u32, reader); - try writer.print("\nentries {d}\n", .{entries}); - try dumpSection(section, data[fbs.pos..], entries, writer); - }, - .custom => { - const name_length = try std.leb.readULEB128(u32, reader); - const name = data[fbs.pos..][0..name_length]; - fbs.pos += name_length; - try writer.print("\nname {s}\n", .{name}); - - if (mem.eql(u8, name, "name")) { - try parseDumpNames(reader, writer, data); - } else if (mem.eql(u8, name, "producers")) { - try parseDumpProducers(reader, writer, data); - } else if (mem.eql(u8, name, "target_features")) { - try parseDumpFeatures(reader, writer, data); - } - // TODO: Implement parsing and dumping other custom sections (such as relocations) - }, - .start => { - const start = try std.leb.readULEB128(u32, reader); - try writer.print("\nstart {d}\n", .{start}); - }, - else => {}, // skip unknown sections - } - } - - fn dumpSection(section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void { - var fbs = std.io.fixedBufferStream(data); - const reader = fbs.reader(); - - switch (section) { - .type => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - const func_type = try reader.readByte(); - if (func_type != std.wasm.function_type) { - std.debug.print("Expected function type, found byte '{d}'\n", .{func_type}); - return error.UnexpectedByte; - } - const params = try std.leb.readULEB128(u32, reader); - try writer.print("params {d}\n", .{params}); - var index: u32 = 0; - while (index < params) : (index += 1) { - try parseDumpType(std.wasm.Valtype, reader, writer); - } else index = 0; - const returns = try std.leb.readULEB128(u32, reader); - try writer.print("returns {d}\n", .{returns}); - while (index < returns) : (index += 1) { - try parseDumpType(std.wasm.Valtype, reader, writer); - } - } - }, - .import => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - const module_name_len = try std.leb.readULEB128(u32, reader); - const module_name = data[fbs.pos..][0..module_name_len]; - fbs.pos += module_name_len; - const name_len = try std.leb.readULEB128(u32, reader); - const name = data[fbs.pos..][0..name_len]; - fbs.pos += name_len; - - const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch |err| { - std.debug.print("Invalid import kind\n", .{}); - return err; - }; - - try writer.print( - \\module {s} - \\name {s} - \\kind {s} - , .{ module_name, name, @tagName(kind) }); - try writer.writeByte('\n'); - switch (kind) { - .function => { - try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); - }, - .memory => { - try parseDumpLimits(reader, writer); - }, - .global => { - try parseDumpType(std.wasm.Valtype, reader, writer); - try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)}); - }, - .table => { - try parseDumpType(std.wasm.RefType, reader, writer); - try parseDumpLimits(reader, writer); - }, - } - } - }, - .function => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); - } - }, - .table => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - try parseDumpType(std.wasm.RefType, reader, writer); - try parseDumpLimits(reader, writer); - } - }, - .memory => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - try parseDumpLimits(reader, writer); - } - }, - .global => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - try parseDumpType(std.wasm.Valtype, reader, writer); - try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)}); - try parseDumpInit(reader, writer); - } - }, - .@"export" => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - const name_len = try std.leb.readULEB128(u32, reader); - const name = data[fbs.pos..][0..name_len]; - fbs.pos += name_len; - const kind_byte = try std.leb.readULEB128(u8, reader); - const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch |err| { - std.debug.print("invalid export kind value '{d}'\n", .{kind_byte}); - return err; - }; - const index = try std.leb.readULEB128(u32, reader); - try writer.print( - \\name {s} - \\kind {s} - \\index {d} - , .{ name, @tagName(kind), index }); - try writer.writeByte('\n'); - } - }, - .element => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)}); - try parseDumpInit(reader, writer); - - const function_indexes = try std.leb.readULEB128(u32, reader); - var function_index: u32 = 0; - try writer.print("indexes {d}\n", .{function_indexes}); - while (function_index < function_indexes) : (function_index += 1) { - try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); - } - } - }, - .code => {}, // code section is considered opaque to linker - .data => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - const index = try std.leb.readULEB128(u32, reader); - try writer.print("memory index 0x{x}\n", .{index}); - try parseDumpInit(reader, writer); - const size = try std.leb.readULEB128(u32, reader); - try writer.print("size {d}\n", .{size}); - try reader.skipBytes(size, .{}); // we do not care about the content of the segments - } - }, - else => unreachable, - } - } - - fn parseDumpType(comptime WasmType: type, reader: anytype, writer: anytype) !void { - const type_byte = try reader.readByte(); - const valtype = std.meta.intToEnum(WasmType, type_byte) catch |err| { - std.debug.print("Invalid wasm type value '{d}'\n", .{type_byte}); - return err; - }; - try writer.print("type {s}\n", .{@tagName(valtype)}); - } - - fn parseDumpLimits(reader: anytype, writer: anytype) !void { - const flags = try std.leb.readULEB128(u8, reader); - const min = try std.leb.readULEB128(u32, reader); - - try writer.print("min {x}\n", .{min}); - if (flags != 0) { - try writer.print("max {x}\n", .{try std.leb.readULEB128(u32, reader)}); - } - } - - fn parseDumpInit(reader: anytype, writer: anytype) !void { - const byte = try std.leb.readULEB128(u8, reader); - const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch |err| { - std.debug.print("invalid wasm opcode '{d}'\n", .{byte}); - return err; - }; - switch (opcode) { - .i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}), - .i64_const => try writer.print("i64.const {x}\n", .{try std.leb.readILEB128(i64, reader)}), - .f32_const => try writer.print("f32.const {x}\n", .{@bitCast(f32, try reader.readIntLittle(u32))}), - .f64_const => try writer.print("f64.const {x}\n", .{@bitCast(f64, try reader.readIntLittle(u64))}), - .global_get => try writer.print("global.get {x}\n", .{try std.leb.readULEB128(u32, reader)}), - else => unreachable, - } - const end_opcode = try std.leb.readULEB128(u8, reader); - if (end_opcode != std.wasm.opcode(.end)) { - std.debug.print("expected 'end' opcode in init expression\n", .{}); - return error.MissingEndOpcode; - } - } - - fn parseDumpNames(reader: anytype, writer: anytype, data: []const u8) !void { - while (reader.context.pos < data.len) { - try parseDumpType(std.wasm.NameSubsection, reader, writer); - const size = try std.leb.readULEB128(u32, reader); - const entries = try std.leb.readULEB128(u32, reader); - try writer.print( - \\size {d} - \\names {d} - , .{ size, entries }); - try writer.writeByte('\n'); - var i: u32 = 0; - while (i < entries) : (i += 1) { - const index = try std.leb.readULEB128(u32, reader); - const name_len = try std.leb.readULEB128(u32, reader); - const pos = reader.context.pos; - const name = data[pos..][0..name_len]; - reader.context.pos += name_len; - - try writer.print( - \\index {d} - \\name {s} - , .{ index, name }); - try writer.writeByte('\n'); - } - } - } - - fn parseDumpProducers(reader: anytype, writer: anytype, data: []const u8) !void { - const field_count = try std.leb.readULEB128(u32, reader); - try writer.print("fields {d}\n", .{field_count}); - var current_field: u32 = 0; - while (current_field < field_count) : (current_field += 1) { - const field_name_length = try std.leb.readULEB128(u32, reader); - const field_name = data[reader.context.pos..][0..field_name_length]; - reader.context.pos += field_name_length; - - const value_count = try std.leb.readULEB128(u32, reader); - try writer.print( - \\field_name {s} - \\values {d} - , .{ field_name, value_count }); - try writer.writeByte('\n'); - var current_value: u32 = 0; - while (current_value < value_count) : (current_value += 1) { - const value_length = try std.leb.readULEB128(u32, reader); - const value = data[reader.context.pos..][0..value_length]; - reader.context.pos += value_length; - - const version_length = try std.leb.readULEB128(u32, reader); - const version = data[reader.context.pos..][0..version_length]; - reader.context.pos += version_length; - - try writer.print( - \\value_name {s} - \\version {s} - , .{ value, version }); - try writer.writeByte('\n'); - } - } - } - - fn parseDumpFeatures(reader: anytype, writer: anytype, data: []const u8) !void { - const feature_count = try std.leb.readULEB128(u32, reader); - try writer.print("features {d}\n", .{feature_count}); - - var index: u32 = 0; - while (index < feature_count) : (index += 1) { - const prefix_byte = try std.leb.readULEB128(u8, reader); - const name_length = try std.leb.readULEB128(u32, reader); - const feature_name = data[reader.context.pos..][0..name_length]; - reader.context.pos += name_length; - - try writer.print("{c} {s}\n", .{ prefix_byte, feature_name }); - } - } -}; diff --git a/lib/std/build/ConfigHeaderStep.zig b/lib/std/build/ConfigHeaderStep.zig deleted file mode 100644 index 400c06525e..0000000000 --- a/lib/std/build/ConfigHeaderStep.zig +++ /dev/null @@ -1,288 +0,0 @@ -const std = @import("../std.zig"); -const ConfigHeaderStep = @This(); -const Step = std.build.Step; -const Builder = std.build.Builder; - -pub const base_id: Step.Id = .config_header; - -pub const Style = enum { - /// The configure format supported by autotools. It uses `#undef foo` to - /// mark lines that can be substituted with different values. - autoconf, - /// The configure format supported by CMake. It uses `@@FOO@@` and - /// `#cmakedefine` for template substitution. - cmake, -}; - -pub const Value = union(enum) { - undef, - defined, - boolean: bool, - int: i64, - ident: []const u8, - string: []const u8, -}; - -step: Step, -builder: *Builder, -source: std.build.FileSource, -style: Style, -values: std.StringHashMap(Value), -max_bytes: usize = 2 * 1024 * 1024, -output_dir: []const u8, -output_basename: []const u8, - -pub fn create(builder: *Builder, source: std.build.FileSource, style: Style) *ConfigHeaderStep { - const self = builder.allocator.create(ConfigHeaderStep) catch @panic("OOM"); - const name = builder.fmt("configure header {s}", .{source.getDisplayName()}); - self.* = .{ - .builder = builder, - .step = Step.init(base_id, name, builder.allocator, make), - .source = source, - .style = style, - .values = std.StringHashMap(Value).init(builder.allocator), - .output_dir = undefined, - .output_basename = "config.h", - }; - switch (source) { - .path => |p| { - const basename = std.fs.path.basename(p); - if (std.mem.endsWith(u8, basename, ".h.in")) { - self.output_basename = basename[0 .. basename.len - 3]; - } - }, - else => {}, - } - return self; -} - -pub fn addValues(self: *ConfigHeaderStep, values: anytype) void { - return addValuesInner(self, values) catch @panic("OOM"); -} - -fn addValuesInner(self: *ConfigHeaderStep, values: anytype) !void { - inline for (@typeInfo(@TypeOf(values)).Struct.fields) |field| { - switch (@typeInfo(field.type)) { - .Null => { - try self.values.put(field.name, .undef); - }, - .Void => { - try self.values.put(field.name, .defined); - }, - .Bool => { - try self.values.put(field.name, .{ .boolean = @field(values, field.name) }); - }, - .ComptimeInt => { - try self.values.put(field.name, .{ .int = @field(values, field.name) }); - }, - .EnumLiteral => { - try self.values.put(field.name, .{ .ident = @tagName(@field(values, field.name)) }); - }, - .Pointer => |ptr| { - switch (@typeInfo(ptr.child)) { - .Array => |array| { - if (ptr.size == .One and array.child == u8) { - try self.values.put(field.name, .{ .string = @field(values, field.name) }); - continue; - } - }, - else => {}, - } - - @compileError("unsupported ConfigHeaderStep value type: " ++ - @typeName(field.type)); - }, - else => @compileError("unsupported ConfigHeaderStep value type: " ++ - @typeName(field.type)), - } - } -} - -fn make(step: *Step) !void { - const self = @fieldParentPtr(ConfigHeaderStep, "step", step); - const gpa = self.builder.allocator; - const src_path = self.source.getPath(self.builder); - const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); - - // The cache is used here not really as a way to speed things up - because writing - // the data to a file would probably be very fast - but as a way to find a canonical - // location to put build artifacts. - - // If, for example, a hard-coded path was used as the location to put ConfigHeaderStep - // files, then two ConfigHeaderStep executing in parallel might clobber each other. - - // TODO port the cache system from the compiler to zig std lib. Until then - // we construct the path directly, and no "cache hit" detection happens; - // the files are always written. - // Note there is very similar code over in WriteFileStep - const Hasher = std.crypto.auth.siphash.SipHash128(1, 3); - // Random bytes to make ConfigHeaderStep unique. Refresh this with new - // random bytes when ConfigHeaderStep implementation is modified in a - // non-backwards-compatible way. - var hash = Hasher.init("X1pQzdDt91Zlh7Eh"); - hash.update(self.source.getDisplayName()); - hash.update(contents); - - var digest: [16]u8 = undefined; - hash.final(&digest); - var hash_basename: [digest.len * 2]u8 = undefined; - _ = std.fmt.bufPrint( - &hash_basename, - "{s}", - .{std.fmt.fmtSliceHexLower(&digest)}, - ) catch unreachable; - - self.output_dir = try std.fs.path.join(gpa, &[_][]const u8{ - self.builder.cache_root, "o", &hash_basename, - }); - var dir = std.fs.cwd().makeOpenPath(self.output_dir, .{}) catch |err| { - std.debug.print("unable to make path {s}: {s}\n", .{ self.output_dir, @errorName(err) }); - return err; - }; - defer dir.close(); - - var values_copy = try self.values.clone(); - defer values_copy.deinit(); - - var output = std.ArrayList(u8).init(gpa); - defer output.deinit(); - try output.ensureTotalCapacity(contents.len); - - try output.appendSlice("/* This file was generated by ConfigHeaderStep using the Zig Build System. */\n"); - - switch (self.style) { - .autoconf => try render_autoconf(contents, &output, &values_copy, src_path), - .cmake => try render_cmake(contents, &output, &values_copy, src_path), - } - - try dir.writeFile(self.output_basename, output.items); -} - -fn render_autoconf( - contents: []const u8, - output: *std.ArrayList(u8), - values_copy: *std.StringHashMap(Value), - src_path: []const u8, -) !void { - var any_errors = false; - var line_index: u32 = 0; - var line_it = std.mem.split(u8, contents, "\n"); - while (line_it.next()) |line| : (line_index += 1) { - if (!std.mem.startsWith(u8, line, "#")) { - try output.appendSlice(line); - try output.appendSlice("\n"); - continue; - } - var it = std.mem.tokenize(u8, line[1..], " \t\r"); - const undef = it.next().?; - if (!std.mem.eql(u8, undef, "undef")) { - try output.appendSlice(line); - try output.appendSlice("\n"); - continue; - } - const name = it.rest(); - const kv = values_copy.fetchRemove(name) orelse { - std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{ - src_path, line_index + 1, name, - }); - any_errors = true; - continue; - }; - try renderValue(output, name, kv.value); - } - - { - var it = values_copy.iterator(); - while (it.next()) |entry| { - const name = entry.key_ptr.*; - std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name }); - } - } - - if (any_errors) { - return error.HeaderConfigFailed; - } -} - -fn render_cmake( - contents: []const u8, - output: *std.ArrayList(u8), - values_copy: *std.StringHashMap(Value), - src_path: []const u8, -) !void { - var any_errors = false; - var line_index: u32 = 0; - var line_it = std.mem.split(u8, contents, "\n"); - while (line_it.next()) |line| : (line_index += 1) { - if (!std.mem.startsWith(u8, line, "#")) { - try output.appendSlice(line); - try output.appendSlice("\n"); - continue; - } - var it = std.mem.tokenize(u8, line[1..], " \t\r"); - const cmakedefine = it.next().?; - if (!std.mem.eql(u8, cmakedefine, "cmakedefine")) { - try output.appendSlice(line); - try output.appendSlice("\n"); - continue; - } - const name = it.next() orelse { - std.debug.print("{s}:{d}: error: missing define name\n", .{ - src_path, line_index + 1, - }); - any_errors = true; - continue; - }; - const kv = values_copy.fetchRemove(name) orelse { - std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{ - src_path, line_index + 1, name, - }); - any_errors = true; - continue; - }; - try renderValue(output, name, kv.value); - } - - { - var it = values_copy.iterator(); - while (it.next()) |entry| { - const name = entry.key_ptr.*; - std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name }); - } - } - - if (any_errors) { - return error.HeaderConfigFailed; - } -} - -fn renderValue(output: *std.ArrayList(u8), name: []const u8, value: Value) !void { - switch (value) { - .undef => { - try output.appendSlice("/* #undef "); - try output.appendSlice(name); - try output.appendSlice(" */\n"); - }, - .defined => { - try output.appendSlice("#define "); - try output.appendSlice(name); - try output.appendSlice("\n"); - }, - .boolean => |b| { - try output.appendSlice("#define "); - try output.appendSlice(name); - try output.appendSlice(" "); - try output.appendSlice(if (b) "true\n" else "false\n"); - }, - .int => |i| { - try output.writer().print("#define {s} {d}\n", .{ name, i }); - }, - .ident => |ident| { - try output.writer().print("#define {s} {s}\n", .{ name, ident }); - }, - .string => |string| { - // TODO: use C-specific escaping instead of zig string literals - try output.writer().print("#define {s} \"{}\"\n", .{ name, std.zig.fmtEscapes(string) }); - }, - } -} diff --git a/lib/std/build/EmulatableRunStep.zig b/lib/std/build/EmulatableRunStep.zig deleted file mode 100644 index 52ce8edfac..0000000000 --- a/lib/std/build/EmulatableRunStep.zig +++ /dev/null @@ -1,215 +0,0 @@ -//! Unlike `RunStep` this step will provide emulation, when enabled, to run foreign binaries. -//! When a binary is foreign, but emulation for the target is disabled, the specified binary -//! will not be run and therefore also not validated against its output. -//! This step can be useful when wishing to run a built binary on multiple platforms, -//! without having to verify if it's possible to be ran against. - -const std = @import("../std.zig"); -const build = std.build; -const Step = std.build.Step; -const Builder = std.build.Builder; -const LibExeObjStep = std.build.LibExeObjStep; -const RunStep = std.build.RunStep; - -const fs = std.fs; -const process = std.process; -const EnvMap = process.EnvMap; - -const EmulatableRunStep = @This(); - -pub const base_id = .emulatable_run; - -const max_stdout_size = 1 * 1024 * 1024; // 1 MiB - -step: Step, -builder: *Builder, - -/// The artifact (executable) to be run by this step -exe: *LibExeObjStep, - -/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution -expected_exit_code: ?u8 = 0, - -/// Override this field to modify the environment -env_map: ?*EnvMap, - -/// Set this to modify the current working directory -cwd: ?[]const u8, - -stdout_action: RunStep.StdIoAction = .inherit, -stderr_action: RunStep.StdIoAction = .inherit, - -/// When set to true, hides the warning of skipping a foreign binary which cannot be run on the host -/// or through emulation. -hide_foreign_binaries_warning: bool, - -/// Creates a step that will execute the given artifact. This step will allow running the -/// binary through emulation when any of the emulation options such as `enable_rosetta` are set to true. -/// When set to false, and the binary is foreign, running the executable is skipped. -/// Asserts given artifact is an executable. -pub fn create(builder: *Builder, name: []const u8, artifact: *LibExeObjStep) *EmulatableRunStep { - std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe); - const self = builder.allocator.create(EmulatableRunStep) catch unreachable; - - const option_name = "hide-foreign-warnings"; - const hide_warnings = if (builder.available_options_map.get(option_name) == null) warn: { - break :warn builder.option(bool, option_name, "Hide the warning when a foreign binary which is incompatible is skipped") orelse false; - } else false; - - self.* = .{ - .builder = builder, - .step = Step.init(.emulatable_run, name, builder.allocator, make), - .exe = artifact, - .env_map = null, - .cwd = null, - .hide_foreign_binaries_warning = hide_warnings, - }; - self.step.dependOn(&artifact.step); - - return self; -} - -fn make(step: *Step) !void { - const self = @fieldParentPtr(EmulatableRunStep, "step", step); - const host_info = self.builder.host; - - var argv_list = std.ArrayList([]const u8).init(self.builder.allocator); - defer argv_list.deinit(); - - const need_cross_glibc = self.exe.target.isGnuLibC() and self.exe.is_linking_libc; - switch (host_info.getExternalExecutor(self.exe.target_info, .{ - .qemu_fixes_dl = need_cross_glibc and self.builder.glibc_runtimes_dir != null, - .link_libc = self.exe.is_linking_libc, - })) { - .native => {}, - .rosetta => if (!self.builder.enable_rosetta) return warnAboutForeignBinaries(self), - .wine => |bin_name| if (self.builder.enable_wine) { - try argv_list.append(bin_name); - } else return, - .qemu => |bin_name| if (self.builder.enable_qemu) { - const glibc_dir_arg = if (need_cross_glibc) - self.builder.glibc_runtimes_dir orelse return - else - null; - try argv_list.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 - // "x86" which is why we do it manually here. - const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}"; - const cpu_arch = self.exe.target.getCpuArch(); - const os_tag = self.exe.target.getOsTag(); - const abi = self.exe.target.getAbi(); - const cpu_arch_name: []const u8 = if (cpu_arch == .x86) - "i686" - else - @tagName(cpu_arch); - const full_dir = try std.fmt.allocPrint(self.builder.allocator, fmt_str, .{ - dir, cpu_arch_name, @tagName(os_tag), @tagName(abi), - }); - - try argv_list.append("-L"); - try argv_list.append(full_dir); - } - } else return warnAboutForeignBinaries(self), - .darling => |bin_name| if (self.builder.enable_darling) { - try argv_list.append(bin_name); - } else return warnAboutForeignBinaries(self), - .wasmtime => |bin_name| if (self.builder.enable_wasmtime) { - try argv_list.append(bin_name); - try argv_list.append("--dir=."); - } else return warnAboutForeignBinaries(self), - else => return warnAboutForeignBinaries(self), - } - - if (self.exe.target.isWindows()) { - // On Windows we don't have rpaths so we have to add .dll search paths to PATH - RunStep.addPathForDynLibsInternal(&self.step, self.builder, self.exe); - } - - const executable_path = self.exe.installed_path orelse self.exe.getOutputSource().getPath(self.builder); - try argv_list.append(executable_path); - - try RunStep.runCommand( - argv_list.items, - self.builder, - self.expected_exit_code, - self.stdout_action, - self.stderr_action, - .Inherit, - self.env_map, - self.cwd, - false, - ); -} - -pub fn expectStdErrEqual(self: *EmulatableRunStep, bytes: []const u8) void { - self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) }; -} - -pub fn expectStdOutEqual(self: *EmulatableRunStep, bytes: []const u8) void { - self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) }; -} - -fn warnAboutForeignBinaries(step: *EmulatableRunStep) void { - if (step.hide_foreign_binaries_warning) return; - const builder = step.builder; - const artifact = step.exe; - - const host_name = builder.host.target.zigTriple(builder.allocator) catch unreachable; - const foreign_name = artifact.target.zigTriple(builder.allocator) catch unreachable; - const target_info = std.zig.system.NativeTargetInfo.detect(artifact.target) catch unreachable; - const need_cross_glibc = artifact.target.isGnuLibC() and artifact.is_linking_libc; - switch (builder.host.getExternalExecutor(target_info, .{ - .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null, - .link_libc = artifact.is_linking_libc, - })) { - .native => unreachable, - .bad_dl => |foreign_dl| { - const host_dl = builder.host.dynamic_linker.get() orelse "(none)"; - std.debug.print("the host system does not appear to be capable of executing binaries from the target because the host dynamic linker is '{s}', while the target dynamic linker is '{s}'. Consider setting the dynamic linker as '{s}'.\n", .{ - host_dl, foreign_dl, host_dl, - }); - }, - .bad_os_or_cpu => { - std.debug.print("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}).\n", .{ - host_name, foreign_name, - }); - }, - .darling => if (!builder.enable_darling) { - std.debug.print( - "the host system ({s}) does not appear to be capable of executing binaries " ++ - "from the target ({s}). Consider enabling darling.\n", - .{ host_name, foreign_name }, - ); - }, - .rosetta => if (!builder.enable_rosetta) { - std.debug.print( - "the host system ({s}) does not appear to be capable of executing binaries " ++ - "from the target ({s}). Consider enabling rosetta.\n", - .{ host_name, foreign_name }, - ); - }, - .wine => if (!builder.enable_wine) { - std.debug.print( - "the host system ({s}) does not appear to be capable of executing binaries " ++ - "from the target ({s}). Consider enabling wine.\n", - .{ host_name, foreign_name }, - ); - }, - .qemu => if (!builder.enable_qemu) { - std.debug.print( - "the host system ({s}) does not appear to be capable of executing binaries " ++ - "from the target ({s}). Consider enabling qemu.\n", - .{ host_name, foreign_name }, - ); - }, - .wasmtime => { - std.debug.print( - "the host system ({s}) does not appear to be capable of executing binaries " ++ - "from the target ({s}). Consider enabling wasmtime.\n", - .{ host_name, foreign_name }, - ); - }, - } -} diff --git a/lib/std/build/FmtStep.zig b/lib/std/build/FmtStep.zig deleted file mode 100644 index 62923623f2..0000000000 --- a/lib/std/build/FmtStep.zig +++ /dev/null @@ -1,37 +0,0 @@ -const std = @import("../std.zig"); -const build = @import("../build.zig"); -const Step = build.Step; -const Builder = build.Builder; -const BufMap = std.BufMap; -const mem = std.mem; - -const FmtStep = @This(); - -pub const base_id = .fmt; - -step: Step, -builder: *Builder, -argv: [][]const u8, - -pub fn create(builder: *Builder, paths: []const []const u8) *FmtStep { - const self = builder.allocator.create(FmtStep) catch unreachable; - const name = "zig fmt"; - self.* = FmtStep{ - .step = Step.init(.fmt, name, builder.allocator, make), - .builder = builder, - .argv = builder.allocator.alloc([]u8, paths.len + 2) catch unreachable, - }; - - self.argv[0] = builder.zig_exe; - self.argv[1] = "fmt"; - for (paths) |path, i| { - self.argv[2 + i] = builder.pathFromRoot(path); - } - return self; -} - -fn make(step: *Step) !void { - const self = @fieldParentPtr(FmtStep, "step", step); - - return self.builder.spawnChild(self.argv); -} diff --git a/lib/std/build/InstallArtifactStep.zig b/lib/std/build/InstallArtifactStep.zig deleted file mode 100644 index 537b8c8fd9..0000000000 --- a/lib/std/build/InstallArtifactStep.zig +++ /dev/null @@ -1,88 +0,0 @@ -const std = @import("../std.zig"); -const build = @import("../build.zig"); -const Step = build.Step; -const Builder = build.Builder; -const LibExeObjStep = std.build.LibExeObjStep; -const InstallDir = std.build.InstallDir; - -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 LibExeObjStep.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_h_path = builder.getInstallPath(h_dir, self.artifact.out_h_filename); - try builder.updateFile(self.artifact.getOutputHSource().getPath(builder), full_h_path); - } - self.artifact.installed_path = full_dest_path; -} diff --git a/lib/std/build/InstallDirStep.zig b/lib/std/build/InstallDirStep.zig deleted file mode 100644 index 0a41e1aaef..0000000000 --- a/lib/std/build/InstallDirStep.zig +++ /dev/null @@ -1,95 +0,0 @@ -const std = @import("../std.zig"); -const mem = std.mem; -const fs = std.fs; -const build = @import("../build.zig"); -const Step = build.Step; -const Builder = build.Builder; -const InstallDir = std.build.InstallDir; -const InstallDirStep = @This(); -const log = std.log; - -step: Step, -builder: *Builder, -options: Options, -/// This is used by the build system when a file being installed comes from one -/// package but is being installed by another. -override_source_builder: ?*Builder = null, - -pub const base_id = .install_dir; - -pub const Options = 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: Options, b: *Builder) Options { - 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 fn init( - builder: *Builder, - options: Options, -) 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 src_builder = self.override_source_builder orelse self.builder; - const full_src_dir = src_builder.pathFromRoot(self.options.source_dir); - var src_dir = std.fs.cwd().openIterableDir(full_src_dir, .{}) catch |err| { - log.err("InstallDirStep: unable to open source directory '{s}': {s}", .{ - full_src_dir, @errorName(err), - }); - return error.StepFailed; - }; - 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, - } - } -} diff --git a/lib/std/build/InstallFileStep.zig b/lib/std/build/InstallFileStep.zig deleted file mode 100644 index 37203e64c5..0000000000 --- a/lib/std/build/InstallFileStep.zig +++ /dev/null @@ -1,42 +0,0 @@ -const std = @import("../std.zig"); -const build = @import("../build.zig"); -const Step = build.Step; -const Builder = build.Builder; -const FileSource = std.build.FileSource; -const InstallDir = std.build.InstallDir; -const InstallFileStep = @This(); - -pub const base_id = .install_file; - -step: Step, -builder: *Builder, -source: FileSource, -dir: InstallDir, -dest_rel_path: []const u8, -/// This is used by the build system when a file being installed comes from one -/// package but is being installed by another. -override_source_builder: ?*Builder = null, - -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 src_builder = self.override_source_builder orelse self.builder; - const full_src_path = self.source.getPath(src_builder); - const full_dest_path = self.builder.getInstallPath(self.dir, self.dest_rel_path); - try self.builder.updateFile(full_src_path, full_dest_path); -} diff --git a/lib/std/build/InstallRawStep.zig b/lib/std/build/InstallRawStep.zig deleted file mode 100644 index e8266dff5a..0000000000 --- a/lib/std/build/InstallRawStep.zig +++ /dev/null @@ -1,106 +0,0 @@ -//! TODO: Rename this to ObjCopyStep now that it invokes the `zig objcopy` -//! subcommand rather than containing an implementation directly. - -const std = @import("std"); -const InstallRawStep = @This(); - -const Allocator = std.mem.Allocator; -const ArenaAllocator = std.heap.ArenaAllocator; -const ArrayListUnmanaged = std.ArrayListUnmanaged; -const Builder = std.build.Builder; -const File = std.fs.File; -const InstallDir = std.build.InstallDir; -const LibExeObjStep = std.build.LibExeObjStep; -const Step = std.build.Step; -const elf = std.elf; -const fs = std.fs; -const io = std.io; -const sort = std.sort; - -pub const base_id = .install_raw; - -pub const RawFormat = enum { - bin, - hex, -}; - -step: Step, -builder: *Builder, -artifact: *LibExeObjStep, -dest_dir: InstallDir, -dest_filename: []const u8, -options: CreateOptions, -output_file: std.build.GeneratedFile, - -pub const CreateOptions = struct { - format: ?RawFormat = null, - dest_dir: ?InstallDir = null, - only_section: ?[]const u8 = null, - pad_to: ?u64 = null, -}; - -pub fn create(builder: *Builder, artifact: *LibExeObjStep, dest_filename: []const u8, options: CreateOptions) *InstallRawStep { - const self = builder.allocator.create(InstallRawStep) catch unreachable; - self.* = InstallRawStep{ - .step = Step.init(.install_raw, builder.fmt("install raw binary {s}", .{artifact.step.name}), builder.allocator, make), - .builder = builder, - .artifact = artifact, - .dest_dir = if (options.dest_dir) |d| d else switch (artifact.kind) { - .obj => unreachable, - .@"test" => unreachable, - .exe, .test_exe => .bin, - .lib => unreachable, - }, - .dest_filename = dest_filename, - .options = options, - .output_file = std.build.GeneratedFile{ .step = &self.step }, - }; - self.step.dependOn(&artifact.step); - - builder.pushInstalledFile(self.dest_dir, dest_filename); - return self; -} - -pub fn getOutputSource(self: *const InstallRawStep) std.build.FileSource { - return std.build.FileSource{ .generated = &self.output_file }; -} - -fn make(step: *Step) !void { - const self = @fieldParentPtr(InstallRawStep, "step", step); - const b = self.builder; - - if (self.artifact.target.getObjectFormat() != .elf) { - std.debug.print("InstallRawStep only works with ELF format.\n", .{}); - return error.InvalidObjectFormat; - } - - const full_src_path = self.artifact.getOutputSource().getPath(b); - const full_dest_path = b.getInstallPath(self.dest_dir, self.dest_filename); - self.output_file.path = full_dest_path; - - fs.cwd().makePath(b.getInstallPath(self.dest_dir, "")) catch unreachable; - - var argv_list = std.ArrayList([]const u8).init(b.allocator); - try argv_list.appendSlice(&.{ b.zig_exe, "objcopy" }); - - if (self.options.only_section) |only_section| { - try argv_list.appendSlice(&.{ "-j", only_section }); - } - if (self.options.pad_to) |pad_to| { - try argv_list.appendSlice(&.{ - "--pad-to", - b.fmt("{d}", .{pad_to}), - }); - } - if (self.options.format) |format| switch (format) { - .bin => try argv_list.appendSlice(&.{ "-O", "binary" }), - .hex => try argv_list.appendSlice(&.{ "-O", "hex" }), - }; - - try argv_list.appendSlice(&.{ full_src_path, full_dest_path }); - _ = try self.builder.execFromStep(argv_list.items, &self.step); -} - -test { - std.testing.refAllDecls(InstallRawStep); -} diff --git a/lib/std/build/LibExeObjStep.zig b/lib/std/build/LibExeObjStep.zig deleted file mode 100644 index 6ee5205b50..0000000000 --- a/lib/std/build/LibExeObjStep.zig +++ /dev/null @@ -1,2047 +0,0 @@ -const builtin = @import("builtin"); -const std = @import("../std.zig"); -const mem = std.mem; -const log = std.log; -const fs = std.fs; -const assert = std.debug.assert; -const panic = std.debug.panic; -const ArrayList = std.ArrayList; -const StringHashMap = std.StringHashMap; -const Sha256 = std.crypto.hash.sha2.Sha256; -const Allocator = mem.Allocator; -const build = @import("../build.zig"); -const Step = build.Step; -const Builder = build.Builder; -const CrossTarget = std.zig.CrossTarget; -const NativeTargetInfo = std.zig.system.NativeTargetInfo; -const FileSource = std.build.FileSource; -const PkgConfigPkg = Builder.PkgConfigPkg; -const PkgConfigError = Builder.PkgConfigError; -const ExecError = Builder.ExecError; -const Pkg = std.build.Pkg; -const VcpkgRoot = std.build.VcpkgRoot; -const InstallDir = std.build.InstallDir; -const InstallArtifactStep = std.build.InstallArtifactStep; -const GeneratedFile = std.build.GeneratedFile; -const InstallRawStep = std.build.InstallRawStep; -const EmulatableRunStep = std.build.EmulatableRunStep; -const CheckObjectStep = std.build.CheckObjectStep; -const RunStep = std.build.RunStep; -const OptionsStep = std.build.OptionsStep; -const ConfigHeaderStep = std.build.ConfigHeaderStep; -const LibExeObjStep = @This(); - -pub const base_id = .lib_exe_obj; - -step: Step, -builder: *Builder, -name: []const u8, -target: CrossTarget, -target_info: NativeTargetInfo, -optimize: std.builtin.Mode, -linker_script: ?FileSource = null, -version_script: ?[]const u8 = null, -out_filename: []const u8, -linkage: ?Linkage = null, -version: ?std.builtin.Version, -kind: Kind, -major_only_filename: ?[]const u8, -name_only_filename: ?[]const u8, -strip: ?bool, -unwind_tables: ?bool, -// keep in sync with src/link.zig:CompressDebugSections -compress_debug_sections: enum { none, zlib } = .none, -lib_paths: ArrayList([]const u8), -rpaths: ArrayList([]const u8), -framework_dirs: ArrayList([]const u8), -frameworks: StringHashMap(FrameworkLinkInfo), -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, -stack_protector: ?bool = null, -disable_stack_probing: bool, -disable_sanitize_c: bool, -sanitize_thread: bool, -rdynamic: bool, -import_memory: bool = false, -/// For WebAssembly targets, this will allow for undefined symbols to -/// be imported from the host environment. -import_symbols: 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, -test_runner: ?[]const u8, -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), -installed_headers: ArrayList(*std.build.Step), -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, -/// On ELF targets, this will emit a link section called ".note.gnu.build-id" -/// which can be used to coordinate a stripped binary with its debug symbols. -/// As an example, the bloaty project refuses to work unless its inputs have -/// build ids, in order to prevent accidental mismatches. -/// The default is to not include this section because it slows down linking. -build_id: ?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, - -/// Remove functions and data that are unreachable by the entry point or -/// exported symbols. -link_gc_sections: ?bool = null, - -linker_allow_shlib_undefined: ?bool = null, - -/// Permit read-only relocations in read-only segments. Disallowed by default. -link_z_notext: bool = false, - -/// Force all relocations to be read-only after processing. -link_z_relro: bool = true, - -/// Allow relocations to be lazily processed after load. -link_z_lazy: bool = false, - -/// Common page size -link_z_common_page_size: ?u64 = null, - -/// Maximum page size -link_z_max_page_size: ?u64 = null, - -/// (Darwin) Install name for the dylib -install_name: ?[]const u8 = null, - -/// (Darwin) Path to entitlements file -entitlements: ?[]const u8 = null, - -/// (Darwin) Size of the pagezero segment. -pagezero_size: ?u64 = null, - -/// (Darwin) Search strategy for searching system libraries. Either `paths_first` or `dylibs_first`. -/// The former lowers to `-search_paths_first` linker option, while the latter to `-search_dylibs_first` -/// option. -/// By default, if no option is specified, the linker assumes `paths_first` as the default -/// search strategy. -search_strategy: ?enum { paths_first, dylibs_first } = null, - -/// (Darwin) Set size of the padding between the end of load commands -/// and start of `__TEXT,__text` section. -headerpad_size: ?u32 = null, - -/// (Darwin) Automatically Set size of the padding between the end of load commands -/// and start of `__TEXT,__text` section to a value fitting all paths expanded to MAXPATHLEN. -headerpad_max_install_names: bool = false, - -/// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols. -dead_strip_dylibs: bool = false, - -/// Position Independent Code -force_pic: ?bool = null, - -/// Position Independent Executable -pie: ?bool = null, - -red_zone: ?bool = null, - -omit_frame_pointer: ?bool = null, -dll_export_fns: ?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, -use_llvm: ?bool = null, -use_lld: ?bool = null, - -output_path_source: GeneratedFile, -output_lib_path_source: GeneratedFile, -output_h_path_source: GeneratedFile, -output_pdb_path_source: GeneratedFile, - -pub const CSourceFiles = struct { - files: []const []const u8, - flags: []const []const u8, -}; - -pub const CSourceFile = struct { - source: FileSource, - args: []const []const u8, - - pub fn dupe(self: CSourceFile, b: *Builder) CSourceFile { - return .{ - .source = self.source.dupe(b), - .args = b.dupeStrings(self.args), - }; - } -}; - -pub const LinkObject = union(enum) { - static_path: FileSource, - other_step: *LibExeObjStep, - system_lib: SystemLib, - assembly_file: FileSource, - c_source_file: *CSourceFile, - c_source_files: *CSourceFiles, -}; - -pub const SystemLib = struct { - name: []const u8, - needed: bool, - weak: bool, - use_pkg_config: enum { - /// Don't use pkg-config, just pass -lfoo where foo is name. - no, - /// Try to get information on how to link the library from pkg-config. - /// If that fails, fall back to passing -lfoo where foo is name. - yes, - /// Try to get information on how to link the library from pkg-config. - /// If that fails, error out. - force, - }, -}; - -const FrameworkLinkInfo = struct { - needed: bool = false, - weak: bool = false, -}; - -pub const IncludeDir = union(enum) { - raw_path: []const u8, - raw_path_system: []const u8, - other_step: *LibExeObjStep, - config_header_step: *ConfigHeaderStep, -}; - -pub const Options = struct { - name: []const u8, - root_source_file: ?FileSource = null, - target: CrossTarget, - optimize: std.builtin.Mode, - kind: Kind, - linkage: ?Linkage = null, - version: ?std.builtin.Version = null, -}; - -pub const Kind = enum { - exe, - lib, - obj, - @"test", - test_exe, -}; - -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 create(builder: *Builder, options: Options) *LibExeObjStep { - const name = builder.dupe(options.name); - const root_src: ?FileSource = if (options.root_source_file) |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 = null, - .unwind_tables = null, - .builder = builder, - .verbose_link = false, - .verbose_cc = false, - .optimize = options.optimize, - .target = options.target, - .linkage = options.linkage, - .kind = options.kind, - .root_src = root_src, - .name = name, - .frameworks = StringHashMap(FrameworkLinkInfo).init(builder.allocator), - .step = Step.init(base_id, name, builder.allocator, make), - .version = options.version, - .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), - .installed_headers = ArrayList(*std.build.Step).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, - .test_runner = 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.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 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); -} - -pub fn installHeader(a: *LibExeObjStep, src_path: []const u8, dest_rel_path: []const u8) void { - const install_file = a.builder.addInstallHeaderFile(src_path, dest_rel_path); - a.builder.getInstallStep().dependOn(&install_file.step); - a.installed_headers.append(&install_file.step) catch unreachable; -} - -pub fn installHeadersDirectory( - a: *LibExeObjStep, - src_dir_path: []const u8, - dest_rel_path: []const u8, -) void { - return installHeadersDirectoryOptions(a, .{ - .source_dir = src_dir_path, - .install_dir = .header, - .install_subdir = dest_rel_path, - }); -} - -pub fn installHeadersDirectoryOptions( - a: *LibExeObjStep, - options: std.build.InstallDirStep.Options, -) void { - const install_dir = a.builder.addInstallDirectory(options); - a.builder.getInstallStep().dependOn(&install_dir.step); - a.installed_headers.append(&install_dir.step) catch unreachable; -} - -pub fn installLibraryHeaders(a: *LibExeObjStep, l: *LibExeObjStep) void { - assert(l.kind == .lib); - const install_step = a.builder.getInstallStep(); - // Copy each element from installed_headers, modifying the builder - // to be the new parent's builder. - for (l.installed_headers.items) |step| { - const step_copy = switch (step.id) { - inline .install_file, .install_dir => |id| blk: { - const T = id.Type(); - const ptr = a.builder.allocator.create(T) catch unreachable; - ptr.* = step.cast(T).?.*; - ptr.override_source_builder = ptr.builder; - ptr.builder = a.builder; - break :blk &ptr.step; - }, - else => unreachable, - }; - a.installed_headers.append(step_copy) catch unreachable; - install_step.dependOn(step_copy); - } - a.installed_headers.appendSlice(l.installed_headers.items) catch unreachable; -} - -/// 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; -} - -/// Creates an `EmulatableRunStep` with an executable built with `addExecutable`. -/// Allows running foreign binaries through emulation platforms such as Qemu or Rosetta. -/// When a binary cannot be ran through emulation or the option is disabled, a warning -/// will be printed and the binary will *NOT* be ran. -pub fn runEmulatable(exe: *LibExeObjStep) *EmulatableRunStep { - assert(exe.kind == .exe or exe.kind == .test_exe); - - const run_step = EmulatableRunStep.create(exe.builder, exe.builder.fmt("run {s}", .{exe.step.name}), exe); - if (exe.vcpkg_bin_path) |path| { - RunStep.addPathDirInternal(&run_step.step, exe.builder, path); - } - return run_step; -} - -pub fn checkObject(self: *LibExeObjStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep { - return CheckObjectStep.create(self.builder, self.getOutputSource(), obj_format); -} - -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 { - self.frameworks.put(self.builder.dupe(framework_name), .{}) catch unreachable; -} - -pub fn linkFrameworkNeeded(self: *LibExeObjStep, framework_name: []const u8) void { - self.frameworks.put(self.builder.dupe(framework_name), .{ - .needed = true, - }) catch unreachable; -} - -pub fn linkFrameworkWeak(self: *LibExeObjStep, framework_name: []const u8) void { - self.frameworks.put(self.builder.dupe(framework_name), .{ - .weak = true, - }) 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 => |lib| if (mem.eql(u8, lib.name, 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 == Linkage.dynamic; -} - -pub fn isStaticLibrary(self: *LibExeObjStep) bool { - return self.kind == .lib and self.linkage != Linkage.dynamic; -} - -pub fn producesPdbFile(self: *LibExeObjStep) bool { - if (!self.target.isWindows() and !self.target.isUefi()) return false; - if (self.target.getObjectFormat() == .c) return false; - if (self.strip == true) return false; - return self.isDynamicLibrary() or self.kind == .exe or self.kind == .test_exe; -} - -pub fn linkLibC(self: *LibExeObjStep) void { - self.is_linking_libc = true; -} - -pub fn linkLibCpp(self: *LibExeObjStep) void { - self.is_linking_libcpp = true; -} - -/// 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 = std.build.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 = .{ - .name = self.builder.dupe(name), - .needed = false, - .weak = false, - .use_pkg_config = .no, - }, - }) catch unreachable; -} - -/// This one has no integration with anything, it just puts -needed-lname on the command line. -/// Prefer to use `linkSystemLibraryNeeded` instead. -pub fn linkSystemLibraryNeededName(self: *LibExeObjStep, name: []const u8) void { - self.link_objects.append(.{ - .system_lib = .{ - .name = self.builder.dupe(name), - .needed = true, - .weak = false, - .use_pkg_config = .no, - }, - }) catch unreachable; -} - -/// Darwin-only. This one has no integration with anything, it just puts -weak-lname on the -/// command line. Prefer to use `linkSystemLibraryWeak` instead. -pub fn linkSystemLibraryWeakName(self: *LibExeObjStep, name: []const u8) void { - self.link_objects.append(.{ - .system_lib = .{ - .name = self.builder.dupe(name), - .needed = false, - .weak = true, - .use_pkg_config = .no, - }, - }) 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 { - self.link_objects.append(.{ - .system_lib = .{ - .name = self.builder.dupe(lib_name), - .needed = false, - .weak = false, - .use_pkg_config = .force, - }, - }) catch unreachable; -} - -/// This links against a system library, exclusively using pkg-config to find the library. -/// Prefer to use `linkSystemLibraryNeeded` instead. -pub fn linkSystemLibraryNeededPkgConfigOnly(self: *LibExeObjStep, lib_name: []const u8) void { - self.link_objects.append(.{ - .system_lib = .{ - .name = self.builder.dupe(lib_name), - .needed = true, - .weak = false, - .use_pkg_config = .force, - }, - }) catch unreachable; -} - -/// Run pkg-config for the given library name and parse the output, returning the arguments -/// that should be passed to zig to link the given library. -pub fn runPkgConfig(self: *LibExeObjStep, lib_name: []const u8) ![]const []const u8 { - 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 getPkgConfigList(self.builder); - - // 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 zig_args = ArrayList([]const u8).init(self.builder.allocator); - defer zig_args.deinit(); - - 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; - try zig_args.appendSlice(&[_][]const u8{ "-I", dir }); - } else if (mem.startsWith(u8, tok, "-I")) { - try zig_args.append(tok); - } else if (mem.eql(u8, tok, "-L")) { - const dir = it.next() orelse return error.PkgConfigInvalidOutput; - try zig_args.appendSlice(&[_][]const u8{ "-L", dir }); - } else if (mem.startsWith(u8, tok, "-L")) { - try zig_args.append(tok); - } else if (mem.eql(u8, tok, "-l")) { - const lib = it.next() orelse return error.PkgConfigInvalidOutput; - try zig_args.appendSlice(&[_][]const u8{ "-l", lib }); - } else if (mem.startsWith(u8, tok, "-l")) { - try zig_args.append(tok); - } else if (mem.eql(u8, tok, "-D")) { - const macro = it.next() orelse return error.PkgConfigInvalidOutput; - try zig_args.appendSlice(&[_][]const u8{ "-D", macro }); - } else if (mem.startsWith(u8, tok, "-D")) { - try zig_args.append(tok); - } else if (self.builder.verbose) { - log.warn("Ignoring pkg-config flag '{s}'", .{tok}); - } - } - - return zig_args.toOwnedSlice(); -} - -pub fn linkSystemLibrary(self: *LibExeObjStep, name: []const u8) void { - self.linkSystemLibraryInner(name, .{}); -} - -pub fn linkSystemLibraryNeeded(self: *LibExeObjStep, name: []const u8) void { - self.linkSystemLibraryInner(name, .{ .needed = true }); -} - -pub fn linkSystemLibraryWeak(self: *LibExeObjStep, name: []const u8) void { - self.linkSystemLibraryInner(name, .{ .weak = true }); -} - -fn linkSystemLibraryInner(self: *LibExeObjStep, name: []const u8, opts: struct { - needed: bool = false, - weak: bool = false, -}) void { - if (isLibCLibrary(name)) { - self.linkLibC(); - return; - } - if (isLibCppLibrary(name)) { - self.linkLibCpp(); - return; - } - - self.link_objects.append(.{ - .system_lib = .{ - .name = self.builder.dupe(name), - .needed = opts.needed, - .weak = opts.weak, - .use_pkg_config = .yes, - }, - }) catch unreachable; -} - -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; -} - -pub fn setTestRunner(self: *LibExeObjStep, path: ?[]const u8) void { - assert(self.kind == .@"test" or self.kind == .test_exe); - self.test_runner = if (path) |p| self.builder.dupePath(p) 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 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); -} - -pub const addSystemIncludeDir = @compileError("deprecated; use addSystemIncludePath"); -pub const addIncludeDir = @compileError("deprecated; use addIncludePath"); -pub const addLibPath = @compileError("deprecated, use addLibraryPath"); -pub const addFrameworkDir = @compileError("deprecated, use addFrameworkPath"); - -pub fn addSystemIncludePath(self: *LibExeObjStep, path: []const u8) void { - self.include_dirs.append(IncludeDir{ .raw_path_system = self.builder.dupe(path) }) catch unreachable; -} - -pub fn addIncludePath(self: *LibExeObjStep, path: []const u8) void { - self.include_dirs.append(IncludeDir{ .raw_path = self.builder.dupe(path) }) catch unreachable; -} - -pub fn addConfigHeader(self: *LibExeObjStep, config_header: *ConfigHeaderStep) void { - self.step.dependOn(&config_header.step); - self.include_dirs.append(.{ .config_header_step = config_header }) catch @panic("OOM"); -} - -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; -} - -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.source.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), - .source = .{ .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.source.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) { - log.err("{s}: linker needs 1 or more objects to link", .{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 (builder.reference_trace) |some| { - try zig_args.append(try std.fmt.allocPrint(builder.allocator, "-freference-trace={d}", .{some})); - } - - try addFlag(&zig_args, "LLVM", self.use_llvm); - try addFlag(&zig_args, "LLD", self.use_lld); - - if (self.target.ofmt) |ofmt| { - try zig_args.append(try std.fmt.allocPrint(builder.allocator, "-ofmt={s}", .{@tagName(ofmt)})); - } - - 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)); - - // We will add link objects from transitive dependencies, but we want to keep - // all link objects in the same order provided. - // This array is used to keep self.link_objects immutable. - var transitive_deps: TransitiveDeps = .{ - .link_objects = ArrayList(LinkObject).init(builder.allocator), - .seen_system_libs = StringHashMap(void).init(builder.allocator), - .seen_steps = std.AutoHashMap(*const Step, void).init(builder.allocator), - .is_linking_libcpp = self.is_linking_libcpp, - .is_linking_libc = self.is_linking_libc, - .frameworks = &self.frameworks, - }; - - try transitive_deps.seen_steps.put(&self.step, {}); - try transitive_deps.add(self.link_objects.items); - - var prev_has_extra_flags = false; - - for (transitive_deps.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 => l: { - if (self.isStaticLibrary() and other.isStaticLibrary()) { - // Avoid putting a static library inside a static library. - break :l; - } - - const full_path_lib = other.getOutputLibSource().getPath(builder); - try zig_args.append(full_path_lib); - - if (other.linkage == 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 => |system_lib| { - const prefix: []const u8 = prefix: { - if (system_lib.needed) break :prefix "-needed-l"; - if (system_lib.weak) { - if (self.target.isDarwin()) break :prefix "-weak-l"; - log.warn("Weak library import used for a non-darwin target, this will be converted to normally library import `-lname`", .{}); - } - break :prefix "-l"; - }; - switch (system_lib.use_pkg_config) { - .no => try zig_args.append(builder.fmt("{s}{s}", .{ prefix, system_lib.name })), - .yes, .force => { - if (self.runPkgConfig(system_lib.name)) |args| { - try zig_args.appendSlice(args); - } else |err| switch (err) { - error.PkgConfigInvalidOutput, - error.PkgConfigCrashed, - error.PkgConfigFailed, - error.PkgConfigNotInstalled, - error.PackageNotFound, - => switch (system_lib.use_pkg_config) { - .yes => { - // pkg-config failed, so fall back to linking the library - // by name directly. - try zig_args.append(builder.fmt("{s}{s}", .{ - prefix, - system_lib.name, - })); - }, - .force => { - panic("pkg-config failed for library {s}", .{system_lib.name}); - }, - .no => unreachable, - }, - - else => |e| return e, - } - }, - } - }, - - .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 (transitive_deps.is_linking_libcpp) { - try zig_args.append("-lc++"); - } - - if (transitive_deps.is_linking_libc) { - try zig_args.append("-lc"); - } - - 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); - } - - if (self.test_runner) |test_runner| { - try zig_args.append("--test-runner"); - try zig_args.append(builder.pathFromRoot(test_runner)); - } - - for (builder.debug_log_scopes) |log_scope| { - try zig_args.append("--debug-log"); - try zig_args.append(log_scope); - } - - if (builder.debug_compile_errors) { - try zig_args.append("--debug-compile-errors"); - } - - 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"); - - try addFlag(&zig_args, "strip", self.strip); - try addFlag(&zig_args, "unwind-tables", self.unwind_tables); - - switch (self.compress_debug_sections) { - .none => {}, - .zlib => try zig_args.append("--compress-debug-sections=zlib"), - } - - 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.link_gc_sections) |x| { - try zig_args.append(if (x) "--gc-sections" else "--no-gc-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.link_z_relro) { - try zig_args.append("-z"); - try zig_args.append("norelro"); - } - if (self.link_z_lazy) { - try zig_args.append("-z"); - try zig_args.append("lazy"); - } - if (self.link_z_common_page_size) |size| { - try zig_args.append("-z"); - try zig_args.append(builder.fmt("common-page-size={d}", .{size})); - } - if (self.link_z_max_page_size) |size| { - try zig_args.append("-z"); - try zig_args.append(builder.fmt("max-page-size={d}", .{size})); - } - - if (self.libc_file) |libc_file| { - try zig_args.append("--libc"); - try zig_args.append(libc_file.getPath(builder)); - } else if (builder.libc_file) |libc_file| { - try zig_args.append("--libc"); - try zig_args.append(libc_file); - } - - switch (self.optimize) { - .Debug => {}, // Skip since it's the default. - else => zig_args.append(builder.fmt("-O{s}", .{@tagName(self.optimize)})) 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.pagezero_size) |pagezero_size| { - const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{pagezero_size}); - try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size }); - } - if (self.search_strategy) |strat| switch (strat) { - .paths_first => try zig_args.append("-search_paths_first"), - .dylibs_first => try zig_args.append("-search_dylibs_first"), - }; - if (self.headerpad_size) |headerpad_size| { - const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{headerpad_size}); - try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size }); - } - if (self.headerpad_max_install_names) { - try zig_args.append("-headerpad_max_install_names"); - } - if (self.dead_strip_dylibs) { - try zig_args.append("-dead_strip_dylibs"); - } - - try addFlag(&zig_args, "compiler-rt", self.bundle_compiler_rt); - try addFlag(&zig_args, "single-threaded", self.single_threaded); - if (self.disable_stack_probing) { - try zig_args.append("-fno-stack-check"); - } - try addFlag(&zig_args, "stack-protector", self.stack_protector); - if (self.red_zone) |red_zone| { - if (red_zone) { - try zig_args.append("-mred-zone"); - } else { - try zig_args.append("-mno-red-zone"); - } - } - try addFlag(&zig_args, "omit-frame-pointer", self.omit_frame_pointer); - try addFlag(&zig_args, "dll-export-fns", self.dll_export_fns); - - 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_symbols) { - try zig_args.append("--import-symbols"); - } - 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.appendSlice(&.{ - "-target", try self.target.zigTriple(builder.allocator), - "-mcpu", try build.serializeCpu(builder.allocator, self.target.getCpu()), - }); - - 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 transitive_deps.is_linking_libc; - - switch (builder.host.getExternalExecutor(self.target_info, .{ - .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null, - .link_libc = transitive_deps.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 - // "x86" 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 == .x86) - "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-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(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 = 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(builder); - try zig_args.append("-isystem"); - try zig_args.append(fs.path.dirname(h_path).?); - } - if (other.installed_headers.items.len > 0) { - for (other.installed_headers.items) |install_step| { - try install_step.make(); - } - try zig_args.append("-I"); - try zig_args.append(builder.pathJoin(&.{ - other.builder.install_prefix, "include", - })); - } - }, - .config_header_step => |config_header| { - try zig_args.append("-I"); - try zig_args.append(config_header.output_dir); - }, - } - } - - 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()) |entry| { - const name = entry.key_ptr.*; - const info = entry.value_ptr.*; - if (info.needed) { - zig_args.append("-needed_framework") catch unreachable; - } else if (info.weak) { - zig_args.append("-weak_framework") catch unreachable; - } else { - zig_args.append("-framework") catch unreachable; - } - zig_args.append(name) catch unreachable; - } - } else { - if (self.framework_dirs.items.len > 0) { - log.info("Framework directories have been added for a non-darwin target, this will have no affect on the build", .{}); - } - - if (self.frameworks.count() > 0) { - log.info("Frameworks have been added for a non-darwin target, this will have no affect on the build", .{}); - } - } - - 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("-I"); - try zig_args.append(builder.pathJoin(&.{ - search_prefix, "include", - })); - } - - try addFlag(&zig_args, "valgrind", self.valgrind_support); - try addFlag(&zig_args, "each-lib-rpath", self.each_lib_rpath); - try addFlag(&zig_args, "build-id", self.build_id); - - if (self.override_lib_dir) |dir| { - try zig_args.append("--zig-lib-dir"); - try zig_args.append(builder.pathFromRoot(dir)); - } else if (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)); - } - - try addFlag(&zig_args, "PIC", self.force_pic); - try addFlag(&zig_args, "PIE", self.pie); - try addFlag(&zig_args, "lto", self.want_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' - // See @file syntax here: https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html - 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); - - var args_arena = std.heap.ArenaAllocator.init(builder.allocator); - defer args_arena.deinit(); - - const args_to_escape = zig_args.items[2..]; - var escaped_args = try ArrayList([]const u8).initCapacity(args_arena.allocator(), args_to_escape.len); - - arg_blk: for (args_to_escape) |arg| { - for (arg) |c, arg_idx| { - if (c == '\\' or c == '"') { - // Slow path for arguments that need to be escaped. We'll need to allocate and copy - var escaped = try ArrayList(u8).initCapacity(args_arena.allocator(), arg.len + 1); - const writer = escaped.writer(); - writer.writeAll(arg[0..arg_idx]) catch unreachable; - for (arg[arg_idx..]) |to_escape| { - if (to_escape == '\\' or to_escape == '"') try writer.writeByte('\\'); - try writer.writeByte(to_escape); - } - escaped_args.appendAssumeCapacity(escaped.items); - continue :arg_blk; - } - } - escaped_args.appendAssumeCapacity(arg); // no escaping needed so just use original argument - } - - // 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, "\" \"", escaped_args.items); - 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().openIterableDir(build_output_dir, .{}); - 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, "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.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.?); - } -} - -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; -} - -/// 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; -} - -pub 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| { - log.err("Unable to symlink {s} -> {s}", .{ 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| { - log.err("Unable to symlink {s} -> {s}", .{ name_only_path, filename_major_only }); - return err; - }; -} - -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 (execPkgConfigList(self, &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 "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", - .source = .{ .path = "/not/a/pkg_dep.zig" }, - }; - const pkg_top = Pkg{ - .name = "pkg_dep", - .source = .{ .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); -} - -fn addFlag(args: *ArrayList([]const u8), comptime name: []const u8, opt: ?bool) !void { - const cond = opt orelse return; - try args.ensureUnusedCapacity(1); - if (cond) { - args.appendAssumeCapacity("-f" ++ name); - } else { - args.appendAssumeCapacity("-fno-" ++ name); - } -} - -const TransitiveDeps = struct { - link_objects: ArrayList(LinkObject), - seen_system_libs: StringHashMap(void), - seen_steps: std.AutoHashMap(*const Step, void), - is_linking_libcpp: bool, - is_linking_libc: bool, - frameworks: *StringHashMap(FrameworkLinkInfo), - - fn add(td: *TransitiveDeps, link_objects: []const LinkObject) !void { - try td.link_objects.ensureUnusedCapacity(link_objects.len); - - for (link_objects) |link_object| { - try td.link_objects.append(link_object); - switch (link_object) { - .other_step => |other| try addInner(td, other, other.isDynamicLibrary()), - else => {}, - } - } - } - - fn addInner(td: *TransitiveDeps, other: *LibExeObjStep, dyn: bool) !void { - // Inherit dependency on libc and libc++ - td.is_linking_libcpp = td.is_linking_libcpp or other.is_linking_libcpp; - td.is_linking_libc = td.is_linking_libc or other.is_linking_libc; - - // Inherit dependencies on darwin frameworks - if (!dyn) { - var it = other.frameworks.iterator(); - while (it.next()) |framework| { - try td.frameworks.put(framework.key_ptr.*, framework.value_ptr.*); - } - } - - // Inherit dependencies on system libraries and static libraries. - for (other.link_objects.items) |other_link_object| { - switch (other_link_object) { - .system_lib => |system_lib| { - if ((try td.seen_system_libs.fetchPut(system_lib.name, {})) != null) - continue; - - if (dyn) - continue; - - try td.link_objects.append(other_link_object); - }, - .other_step => |inner_other| { - if ((try td.seen_steps.fetchPut(&inner_other.step, {})) != null) - continue; - - if (!dyn) - try td.link_objects.append(other_link_object); - - try addInner(td, inner_other, dyn or inner_other.isDynamicLibrary()); - }, - else => continue, - } - } - } -}; diff --git a/lib/std/build/LogStep.zig b/lib/std/build/LogStep.zig deleted file mode 100644 index fd937b00f9..0000000000 --- a/lib/std/build/LogStep.zig +++ /dev/null @@ -1,25 +0,0 @@ -const std = @import("../std.zig"); -const log = std.log; -const build = @import("../build.zig"); -const Step = build.Step; -const Builder = build.Builder; -const LogStep = @This(); - -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); - log.info("{s}", .{self.data}); -} diff --git a/lib/std/build/OptionsStep.zig b/lib/std/build/OptionsStep.zig deleted file mode 100644 index fb06cc2179..0000000000 --- a/lib/std/build/OptionsStep.zig +++ /dev/null @@ -1,365 +0,0 @@ -const std = @import("../std.zig"); -const builtin = @import("builtin"); -const build = std.build; -const fs = std.fs; -const Step = build.Step; -const Builder = build.Builder; -const GeneratedFile = build.GeneratedFile; -const LibExeObjStep = build.LibExeObjStep; -const FileSource = build.FileSource; - -const OptionsStep = @This(); - -pub const base_id = .options; - -step: Step, -generated_file: GeneratedFile, -builder: *Builder, - -contents: std.ArrayList(u8), -artifact_args: std.ArrayList(OptionArtifactArg), -file_source_args: std.ArrayList(OptionFileSourceArg), - -pub fn create(builder: *Builder) *OptionsStep { - const self = builder.allocator.create(OptionsStep) catch unreachable; - self.* = .{ - .builder = builder, - .step = Step.init(.options, "options", builder.allocator, make), - .generated_file = undefined, - .contents = std.ArrayList(u8).init(builder.allocator), - .artifact_args = std.ArrayList(OptionArtifactArg).init(builder.allocator), - .file_source_args = std.ArrayList(OptionFileSourceArg).init(builder.allocator), - }; - self.generated_file = .{ .step = &self.step }; - - return self; -} - -pub fn addOption(self: *OptionsStep, comptime T: type, name: []const u8, value: T) void { - const out = self.contents.writer(); - switch (T) { - []const []const u8 => { - out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{std.zig.fmtId(name)}) catch unreachable; - for (value) |slice| { - out.print(" \"{}\",\n", .{std.zig.fmtEscapes(slice)}) catch unreachable; - } - out.writeAll("};\n") catch unreachable; - return; - }, - [:0]const u8 => { - out.print("pub const {}: [:0]const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable; - return; - }, - []const u8 => { - out.print("pub const {}: []const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable; - return; - }, - ?[:0]const u8 => { - out.print("pub const {}: ?[:0]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable; - if (value) |payload| { - out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable; - } else { - out.writeAll("null;\n") catch unreachable; - } - return; - }, - ?[]const u8 => { - out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable; - if (value) |payload| { - out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable; - } else { - out.writeAll("null;\n") catch unreachable; - } - return; - }, - std.builtin.Version => { - out.print( - \\pub const {}: @import("std").builtin.Version = .{{ - \\ .major = {d}, - \\ .minor = {d}, - \\ .patch = {d}, - \\}}; - \\ - , .{ - std.zig.fmtId(name), - - value.major, - value.minor, - value.patch, - }) catch unreachable; - return; - }, - std.SemanticVersion => { - out.print( - \\pub const {}: @import("std").SemanticVersion = .{{ - \\ .major = {d}, - \\ .minor = {d}, - \\ .patch = {d}, - \\ - , .{ - std.zig.fmtId(name), - - value.major, - value.minor, - value.patch, - }) catch unreachable; - if (value.pre) |some| { - out.print(" .pre = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable; - } - if (value.build) |some| { - out.print(" .build = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable; - } - out.writeAll("};\n") catch unreachable; - return; - }, - else => {}, - } - switch (@typeInfo(T)) { - .Enum => |enum_info| { - out.print("pub const {} = enum {{\n", .{std.zig.fmtId(@typeName(T))}) catch unreachable; - inline for (enum_info.fields) |field| { - out.print(" {},\n", .{std.zig.fmtId(field.name)}) catch unreachable; - } - out.writeAll("};\n") catch unreachable; - out.print("pub const {}: {s} = {s}.{s};\n", .{ - std.zig.fmtId(name), - std.zig.fmtId(@typeName(T)), - std.zig.fmtId(@typeName(T)), - std.zig.fmtId(@tagName(value)), - }) catch unreachable; - return; - }, - else => {}, - } - out.print("pub const {}: {s} = ", .{ std.zig.fmtId(name), @typeName(T) }) catch unreachable; - printLiteral(out, value, 0) catch unreachable; - out.writeAll(";\n") catch unreachable; -} - -// TODO: non-recursive? -fn printLiteral(out: anytype, val: anytype, indent: u8) !void { - const T = @TypeOf(val); - switch (@typeInfo(T)) { - .Array => { - try out.print("{s} {{\n", .{@typeName(T)}); - for (val) |item| { - try out.writeByteNTimes(' ', indent + 4); - try printLiteral(out, item, indent + 4); - try out.writeAll(",\n"); - } - try out.writeByteNTimes(' ', indent); - try out.writeAll("}"); - }, - .Pointer => |p| { - if (p.size != .Slice) { - @compileError("Non-slice pointers are not yet supported in build options"); - } - try out.print("&[_]{s} {{\n", .{@typeName(p.child)}); - for (val) |item| { - try out.writeByteNTimes(' ', indent + 4); - try printLiteral(out, item, indent + 4); - try out.writeAll(",\n"); - } - try out.writeByteNTimes(' ', indent); - try out.writeAll("}"); - }, - .Optional => { - if (val) |inner| { - return printLiteral(out, inner, indent); - } else { - return out.writeAll("null"); - } - }, - .Void, - .Bool, - .Int, - .ComptimeInt, - .Float, - .Null, - => try out.print("{any}", .{val}), - else => @compileError(std.fmt.comptimePrint("`{s}` are not yet supported as build options", .{@tagName(@typeInfo(T))})), - } -} - -/// The value is the path in the cache dir. -/// Adds a dependency automatically. -pub fn addOptionFileSource( - self: *OptionsStep, - name: []const u8, - source: FileSource, -) void { - self.file_source_args.append(.{ - .name = name, - .source = source.dupe(self.builder), - }) catch unreachable; - source.addStepDependencies(&self.step); -} - -/// The value is the path in the cache dir. -/// Adds a dependency automatically. -pub fn addOptionArtifact(self: *OptionsStep, name: []const u8, artifact: *LibExeObjStep) void { - self.artifact_args.append(.{ .name = self.builder.dupe(name), .artifact = artifact }) catch unreachable; - self.step.dependOn(&artifact.step); -} - -pub fn getPackage(self: *OptionsStep, package_name: []const u8) build.Pkg { - return .{ .name = package_name, .source = self.getSource() }; -} - -pub fn getSource(self: *OptionsStep) FileSource { - return .{ .generated = &self.generated_file }; -} - -fn make(step: *Step) !void { - const self = @fieldParentPtr(OptionsStep, "step", step); - - for (self.artifact_args.items) |item| { - self.addOption( - []const u8, - item.name, - self.builder.pathFromRoot(item.artifact.getOutputSource().getPath(self.builder)), - ); - } - - for (self.file_source_args.items) |item| { - self.addOption( - []const u8, - item.name, - item.source.getPath(self.builder), - ); - } - - const options_directory = self.builder.pathFromRoot( - try fs.path.join( - self.builder.allocator, - &[_][]const u8{ self.builder.cache_root, "options" }, - ), - ); - - try fs.cwd().makePath(options_directory); - - const options_file = try fs.path.join( - self.builder.allocator, - &[_][]const u8{ options_directory, &self.hashContentsToFileName() }, - ); - - try fs.cwd().writeFile(options_file, self.contents.items); - - self.generated_file.path = options_file; -} - -fn hashContentsToFileName(self: *OptionsStep) [64]u8 { - // This implementation is copied from `WriteFileStep.make` - - var hash = std.crypto.hash.blake2.Blake2b384.init(.{}); - - // Random bytes to make OptionsStep unique. Refresh this with - // new random bytes when OptionsStep implementation is modified - // in a non-backwards-compatible way. - hash.update("yL0Ya4KkmcCjBlP8"); - hash.update(self.contents.items); - - var digest: [48]u8 = undefined; - hash.final(&digest); - var hash_basename: [64]u8 = undefined; - _ = fs.base64_encoder.encode(&hash_basename, &digest); - return hash_basename; -} - -const OptionArtifactArg = struct { - name: []const u8, - artifact: *LibExeObjStep, -}; - -const OptionFileSourceArg = struct { - name: []const u8, - source: FileSource, -}; - -test "OptionsStep" { - 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 options = builder.addOptions(); - - // TODO this regressed at some point - //const KeywordEnum = enum { - // @"0.8.1", - //}; - - const nested_array = [2][2]u16{ - [2]u16{ 300, 200 }, - [2]u16{ 300, 200 }, - }; - const nested_slice: []const []const u16 = &[_][]const u16{ &nested_array[0], &nested_array[1] }; - - options.addOption(usize, "option1", 1); - options.addOption(?usize, "option2", null); - options.addOption(?usize, "option3", 3); - options.addOption(comptime_int, "option4", 4); - options.addOption([]const u8, "string", "zigisthebest"); - options.addOption(?[]const u8, "optional_string", null); - options.addOption([2][2]u16, "nested_array", nested_array); - options.addOption([]const []const u16, "nested_slice", nested_slice); - //options.addOption(KeywordEnum, "keyword_enum", .@"0.8.1"); - options.addOption(std.builtin.Version, "version", try std.builtin.Version.parse("0.1.2")); - options.addOption(std.SemanticVersion, "semantic_version", try std.SemanticVersion.parse("0.1.2-foo+bar")); - - try std.testing.expectEqualStrings( - \\pub const option1: usize = 1; - \\pub const option2: ?usize = null; - \\pub const option3: ?usize = 3; - \\pub const option4: comptime_int = 4; - \\pub const string: []const u8 = "zigisthebest"; - \\pub const optional_string: ?[]const u8 = null; - \\pub const nested_array: [2][2]u16 = [2][2]u16 { - \\ [2]u16 { - \\ 300, - \\ 200, - \\ }, - \\ [2]u16 { - \\ 300, - \\ 200, - \\ }, - \\}; - \\pub const nested_slice: []const []const u16 = &[_][]const u16 { - \\ &[_]u16 { - \\ 300, - \\ 200, - \\ }, - \\ &[_]u16 { - \\ 300, - \\ 200, - \\ }, - \\}; - //\\pub const KeywordEnum = enum { - //\\ @"0.8.1", - //\\}; - //\\pub const keyword_enum: KeywordEnum = KeywordEnum.@"0.8.1"; - \\pub const version: @import("std").builtin.Version = .{ - \\ .major = 0, - \\ .minor = 1, - \\ .patch = 2, - \\}; - \\pub const semantic_version: @import("std").SemanticVersion = .{ - \\ .major = 0, - \\ .minor = 1, - \\ .patch = 2, - \\ .pre = "foo", - \\ .build = "bar", - \\}; - \\ - , options.contents.items); - - _ = try std.zig.parse(arena.allocator(), try options.contents.toOwnedSliceSentinel(0)); -} diff --git a/lib/std/build/RemoveDirStep.zig b/lib/std/build/RemoveDirStep.zig deleted file mode 100644 index 959414e54f..0000000000 --- a/lib/std/build/RemoveDirStep.zig +++ /dev/null @@ -1,31 +0,0 @@ -const std = @import("../std.zig"); -const log = std.log; -const fs = std.fs; -const build = @import("../build.zig"); -const Step = build.Step; -const Builder = build.Builder; -const RemoveDirStep = @This(); - -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| { - log.err("Unable to remove {s}: {s}", .{ full_path, @errorName(err) }); - return err; - }; -} diff --git a/lib/std/build/RunStep.zig b/lib/std/build/RunStep.zig deleted file mode 100644 index 5183a328cd..0000000000 --- a/lib/std/build/RunStep.zig +++ /dev/null @@ -1,378 +0,0 @@ -const std = @import("../std.zig"); -const builtin = @import("builtin"); -const build = std.build; -const Step = build.Step; -const Builder = build.Builder; -const LibExeObjStep = build.LibExeObjStep; -const WriteFileStep = build.WriteFileStep; -const fs = std.fs; -const mem = std.mem; -const process = std.process; -const ArrayList = std.ArrayList; -const EnvMap = process.EnvMap; -const Allocator = mem.Allocator; -const ExecError = build.Builder.ExecError; - -const max_stdout_size = 1 * 1024 * 1024; // 1 MiB - -const RunStep = @This(); - -pub const base_id: Step.Id = .run; - -step: Step, -builder: *Builder, - -/// See also addArg and addArgs to modifying this directly -argv: ArrayList(Arg), - -/// Set this to modify the current working directory -cwd: ?[]const u8, - -/// Override this field to modify the environment, or use setEnvironmentVariable -env_map: ?*EnvMap, - -stdout_action: StdIoAction = .inherit, -stderr_action: StdIoAction = .inherit, - -stdin_behavior: std.ChildProcess.StdIo = .Inherit, - -/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution -expected_exit_code: ?u8 = 0, - -/// Print the command before running it -print: bool, - -pub const StdIoAction = union(enum) { - inherit, - ignore, - expect_exact: []const u8, - expect_matches: []const []const u8, -}; - -pub const Arg = union(enum) { - artifact: *LibExeObjStep, - file_source: build.FileSource, - bytes: []u8, -}; - -pub fn create(builder: *Builder, name: []const u8) *RunStep { - const self = builder.allocator.create(RunStep) catch unreachable; - self.* = RunStep{ - .builder = builder, - .step = Step.init(base_id, name, builder.allocator, make), - .argv = ArrayList(Arg).init(builder.allocator), - .cwd = null, - .env_map = null, - .print = builder.verbose, - }; - return self; -} - -pub fn addArtifactArg(self: *RunStep, artifact: *LibExeObjStep) void { - self.argv.append(Arg{ .artifact = artifact }) catch unreachable; - self.step.dependOn(&artifact.step); -} - -pub fn addFileSourceArg(self: *RunStep, file_source: build.FileSource) void { - self.argv.append(Arg{ - .file_source = file_source.dupe(self.builder), - }) catch unreachable; - file_source.addStepDependencies(&self.step); -} - -pub fn addArg(self: *RunStep, arg: []const u8) void { - self.argv.append(Arg{ .bytes = self.builder.dupe(arg) }) catch unreachable; -} - -pub fn addArgs(self: *RunStep, args: []const []const u8) void { - for (args) |arg| { - self.addArg(arg); - } -} - -pub fn clearEnvironment(self: *RunStep) void { - const new_env_map = self.builder.allocator.create(EnvMap) catch unreachable; - new_env_map.* = EnvMap.init(self.builder.allocator); - self.env_map = new_env_map; -} - -pub fn addPathDir(self: *RunStep, search_path: []const u8) void { - addPathDirInternal(&self.step, self.builder, search_path); -} - -/// For internal use only, users of `RunStep` should use `addPathDir` directly. -pub fn addPathDirInternal(step: *Step, builder: *Builder, search_path: []const u8) void { - const env_map = getEnvMapInternal(step, builder.allocator); - - const key = "PATH"; - var prev_path = env_map.get(key); - - if (prev_path) |pp| { - const new_path = builder.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path }); - env_map.put(key, new_path) catch unreachable; - } else { - env_map.put(key, builder.dupePath(search_path)) catch unreachable; - } -} - -pub fn getEnvMap(self: *RunStep) *EnvMap { - return getEnvMapInternal(&self.step, self.builder.allocator); -} - -fn getEnvMapInternal(step: *Step, allocator: Allocator) *EnvMap { - const maybe_env_map = switch (step.id) { - .run => step.cast(RunStep).?.env_map, - .emulatable_run => step.cast(build.EmulatableRunStep).?.env_map, - else => unreachable, - }; - return maybe_env_map orelse { - const env_map = allocator.create(EnvMap) catch unreachable; - env_map.* = process.getEnvMap(allocator) catch unreachable; - switch (step.id) { - .run => step.cast(RunStep).?.env_map = env_map, - .emulatable_run => step.cast(RunStep).?.env_map = env_map, - else => unreachable, - } - return env_map; - }; -} - -pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void { - const env_map = self.getEnvMap(); - env_map.put( - self.builder.dupe(key), - self.builder.dupe(value), - ) catch unreachable; -} - -pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void { - self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) }; -} - -pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void { - self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) }; -} - -fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo { - return switch (action) { - .ignore => .Ignore, - .inherit => .Inherit, - .expect_exact, .expect_matches => .Pipe, - }; -} - -fn make(step: *Step) !void { - const self = @fieldParentPtr(RunStep, "step", step); - - var argv_list = ArrayList([]const u8).init(self.builder.allocator); - for (self.argv.items) |arg| { - switch (arg) { - .bytes => |bytes| try argv_list.append(bytes), - .file_source => |file| try argv_list.append(file.getPath(self.builder)), - .artifact => |artifact| { - if (artifact.target.isWindows()) { - // On Windows we don't have rpaths so we have to add .dll search paths to PATH - self.addPathForDynLibs(artifact); - } - const executable_path = artifact.installed_path orelse artifact.getOutputSource().getPath(self.builder); - try argv_list.append(executable_path); - }, - } - } - - try runCommand( - argv_list.items, - self.builder, - self.expected_exit_code, - self.stdout_action, - self.stderr_action, - self.stdin_behavior, - self.env_map, - self.cwd, - self.print, - ); -} - -pub fn runCommand( - argv: []const []const u8, - builder: *Builder, - expected_exit_code: ?u8, - stdout_action: StdIoAction, - stderr_action: StdIoAction, - stdin_behavior: std.ChildProcess.StdIo, - env_map: ?*EnvMap, - maybe_cwd: ?[]const u8, - print: bool, -) !void { - const cwd = if (maybe_cwd) |cwd| builder.pathFromRoot(cwd) else builder.build_root; - - if (!std.process.can_spawn) { - const cmd = try std.mem.join(builder.allocator, " ", argv); - std.debug.print("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(builtin.os.tag), cmd }); - builder.allocator.free(cmd); - return ExecError.ExecNotSupported; - } - - var child = std.ChildProcess.init(argv, builder.allocator); - child.cwd = cwd; - child.env_map = env_map orelse builder.env_map; - - child.stdin_behavior = stdin_behavior; - child.stdout_behavior = stdIoActionToBehavior(stdout_action); - child.stderr_behavior = stdIoActionToBehavior(stderr_action); - - if (print) - printCmd(cwd, argv); - - child.spawn() catch |err| { - std.debug.print("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) }); - return err; - }; - - // TODO need to poll to read these streams to prevent a deadlock (or rely on evented I/O). - - var stdout: ?[]const u8 = null; - defer if (stdout) |s| builder.allocator.free(s); - - switch (stdout_action) { - .expect_exact, .expect_matches => { - stdout = child.stdout.?.reader().readAllAlloc(builder.allocator, max_stdout_size) catch unreachable; - }, - .inherit, .ignore => {}, - } - - var stderr: ?[]const u8 = null; - defer if (stderr) |s| builder.allocator.free(s); - - switch (stderr_action) { - .expect_exact, .expect_matches => { - stderr = child.stderr.?.reader().readAllAlloc(builder.allocator, max_stdout_size) catch unreachable; - }, - .inherit, .ignore => {}, - } - - const term = child.wait() catch |err| { - std.debug.print("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) }); - return err; - }; - - switch (term) { - .Exited => |code| blk: { - const expected_code = expected_exit_code orelse break :blk; - - if (code != expected_code) { - if (builder.prominent_compile_errors) { - std.debug.print("Run step exited with error code {} (expected {})\n", .{ - code, - expected_code, - }); - } else { - std.debug.print("The following command exited with error code {} (expected {}):\n", .{ - code, - expected_code, - }); - printCmd(cwd, argv); - } - - return error.UnexpectedExitCode; - } - }, - else => { - std.debug.print("The following command terminated unexpectedly:\n", .{}); - printCmd(cwd, argv); - return error.UncleanExit; - }, - } - - switch (stderr_action) { - .inherit, .ignore => {}, - .expect_exact => |expected_bytes| { - if (!mem.eql(u8, expected_bytes, stderr.?)) { - std.debug.print( - \\ - \\========= Expected this stderr: ========= - \\{s} - \\========= But found: ==================== - \\{s} - \\ - , .{ expected_bytes, stderr.? }); - printCmd(cwd, argv); - return error.TestFailed; - } - }, - .expect_matches => |matches| for (matches) |match| { - if (mem.indexOf(u8, stderr.?, match) == null) { - std.debug.print( - \\ - \\========= Expected to find in stderr: ========= - \\{s} - \\========= But stderr does not contain it: ===== - \\{s} - \\ - , .{ match, stderr.? }); - printCmd(cwd, argv); - return error.TestFailed; - } - }, - } - - switch (stdout_action) { - .inherit, .ignore => {}, - .expect_exact => |expected_bytes| { - if (!mem.eql(u8, expected_bytes, stdout.?)) { - std.debug.print( - \\ - \\========= Expected this stdout: ========= - \\{s} - \\========= But found: ==================== - \\{s} - \\ - , .{ expected_bytes, stdout.? }); - printCmd(cwd, argv); - return error.TestFailed; - } - }, - .expect_matches => |matches| for (matches) |match| { - if (mem.indexOf(u8, stdout.?, match) == null) { - std.debug.print( - \\ - \\========= Expected to find in stdout: ========= - \\{s} - \\========= But stdout does not contain it: ===== - \\{s} - \\ - , .{ match, stdout.? }); - printCmd(cwd, argv); - return error.TestFailed; - } - }, - } -} - -fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void { - if (cwd) |yes_cwd| std.debug.print("cd {s} && ", .{yes_cwd}); - for (argv) |arg| { - std.debug.print("{s} ", .{arg}); - } - std.debug.print("\n", .{}); -} - -fn addPathForDynLibs(self: *RunStep, artifact: *LibExeObjStep) void { - addPathForDynLibsInternal(&self.step, self.builder, artifact); -} - -/// This should only be used for internal usage, this is called automatically -/// for the user. -pub fn addPathForDynLibsInternal(step: *Step, builder: *Builder, artifact: *LibExeObjStep) void { - for (artifact.link_objects.items) |link_object| { - switch (link_object) { - .other_step => |other| { - if (other.target.isWindows() and other.isDynamicLibrary()) { - addPathDirInternal(step, builder, fs.path.dirname(other.getOutputSource().getPath(builder)).?); - addPathForDynLibsInternal(step, builder, other); - } - }, - else => {}, - } - } -} diff --git a/lib/std/build/TranslateCStep.zig b/lib/std/build/TranslateCStep.zig deleted file mode 100644 index 9f45d606a1..0000000000 --- a/lib/std/build/TranslateCStep.zig +++ /dev/null @@ -1,138 +0,0 @@ -const std = @import("../std.zig"); -const build = std.build; -const Step = build.Step; -const Builder = build.Builder; -const LibExeObjStep = build.LibExeObjStep; -const CheckFileStep = build.CheckFileStep; -const fs = std.fs; -const mem = std.mem; -const CrossTarget = std.zig.CrossTarget; - -const TranslateCStep = @This(); - -pub const base_id = .translate_c; - -step: Step, -builder: *Builder, -source: build.FileSource, -include_dirs: std.ArrayList([]const u8), -c_macros: std.ArrayList([]const u8), -output_dir: ?[]const u8, -out_basename: []const u8, -target: CrossTarget, -optimize: std.builtin.OptimizeMode, -output_file: build.GeneratedFile, - -pub const Options = struct { - source_file: build.FileSource, - target: CrossTarget, - optimize: std.builtin.OptimizeMode, -}; - -pub fn create(builder: *Builder, options: Options) *TranslateCStep { - const self = builder.allocator.create(TranslateCStep) catch unreachable; - const source = options.source_file.dupe(builder); - self.* = TranslateCStep{ - .step = Step.init(.translate_c, "translate-c", builder.allocator, make), - .builder = builder, - .source = source, - .include_dirs = std.ArrayList([]const u8).init(builder.allocator), - .c_macros = std.ArrayList([]const u8).init(builder.allocator), - .output_dir = null, - .out_basename = undefined, - .target = options.target, - .optimize = options.optimize, - .output_file = build.GeneratedFile{ .step = &self.step }, - }; - source.addStepDependencies(&self.step); - return self; -} - -pub const AddExecutableOptions = struct { - name: ?[]const u8 = null, - version: ?std.builtin.Version = null, - target: ?CrossTarget = null, - optimize: ?std.builtin.Mode = null, - linkage: ?LibExeObjStep.Linkage = null, -}; - -/// Creates a step to build an executable from the translated source. -pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *LibExeObjStep { - return self.builder.addExecutable(.{ - .root_source_file = .{ .generated = &self.output_file }, - .name = options.name orelse "translated_c", - .version = options.version, - .target = options.target orelse self.target, - .optimize = options.optimize orelse self.optimize, - .linkage = options.linkage, - }); -} - -pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void { - self.include_dirs.append(self.builder.dupePath(include_dir)) catch unreachable; -} - -pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep { - return CheckFileStep.create(self.builder, .{ .generated = &self.output_file }, self.builder.dupeStrings(expected_matches)); -} - -/// 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: *TranslateCStep, name: []const u8, value: ?[]const u8) void { - const macro = build.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: *TranslateCStep, name_and_value: []const u8) void { - self.c_macros.append(self.builder.dupe(name_and_value)) catch unreachable; -} - -fn make(step: *Step) !void { - const self = @fieldParentPtr(TranslateCStep, "step", step); - - var argv_list = std.ArrayList([]const u8).init(self.builder.allocator); - try argv_list.append(self.builder.zig_exe); - try argv_list.append("translate-c"); - try argv_list.append("-lc"); - - try argv_list.append("--enable-cache"); - - if (!self.target.isNative()) { - try argv_list.append("-target"); - try argv_list.append(try self.target.zigTriple(self.builder.allocator)); - } - - switch (self.optimize) { - .Debug => {}, // Skip since it's the default. - else => try argv_list.append(self.builder.fmt("-O{s}", .{@tagName(self.optimize)})), - } - - for (self.include_dirs.items) |include_dir| { - try argv_list.append("-I"); - try argv_list.append(include_dir); - } - - for (self.c_macros.items) |c_macro| { - try argv_list.append("-D"); - try argv_list.append(c_macro); - } - - try argv_list.append(self.source.getPath(self.builder)); - - const output_path_nl = try self.builder.execFromStep(argv_list.items, &self.step); - const output_path = mem.trimRight(u8, output_path_nl, "\r\n"); - - self.out_basename = fs.path.basename(output_path); - if (self.output_dir) |output_dir| { - const full_dest = try fs.path.join(self.builder.allocator, &[_][]const u8{ output_dir, self.out_basename }); - try self.builder.updateFile(output_path, full_dest); - } else { - self.output_dir = fs.path.dirname(output_path).?; - } - - self.output_file.path = fs.path.join( - self.builder.allocator, - &[_][]const u8{ self.output_dir.?, self.out_basename }, - ) catch unreachable; -} diff --git a/lib/std/build/WriteFileStep.zig b/lib/std/build/WriteFileStep.zig deleted file mode 100644 index 4faae8f74e..0000000000 --- a/lib/std/build/WriteFileStep.zig +++ /dev/null @@ -1,117 +0,0 @@ -const std = @import("../std.zig"); -const build = @import("../build.zig"); -const Step = build.Step; -const Builder = build.Builder; -const fs = std.fs; -const ArrayList = std.ArrayList; - -const WriteFileStep = @This(); - -pub const base_id = .write_file; - -step: Step, -builder: *Builder, -output_dir: []const u8, -files: std.TailQueue(File), - -pub const File = struct { - source: build.GeneratedFile, - basename: []const u8, - bytes: []const u8, -}; - -pub fn init(builder: *Builder) WriteFileStep { - return WriteFileStep{ - .builder = builder, - .step = Step.init(.write_file, "writefile", builder.allocator, make), - .files = .{}, - .output_dir = undefined, - }; -} - -pub fn add(self: *WriteFileStep, basename: []const u8, bytes: []const u8) void { - const node = self.builder.allocator.create(std.TailQueue(File).Node) catch unreachable; - node.* = .{ - .data = .{ - .source = build.GeneratedFile{ .step = &self.step }, - .basename = self.builder.dupePath(basename), - .bytes = self.builder.dupe(bytes), - }, - }; - - self.files.append(node); -} - -/// Gets a file source for the given basename. If the file does not exist, returns `null`. -pub fn getFileSource(step: *WriteFileStep, basename: []const u8) ?build.FileSource { - var it = step.files.first; - while (it) |node| : (it = node.next) { - if (std.mem.eql(u8, node.data.basename, basename)) - return build.FileSource{ .generated = &node.data.source }; - } - return null; -} - -fn make(step: *Step) !void { - const self = @fieldParentPtr(WriteFileStep, "step", step); - - // The cache is used here not really as a way to speed things up - because writing - // the data to a file would probably be very fast - but as a way to find a canonical - // location to put build artifacts. - - // If, for example, a hard-coded path was used as the location to put WriteFileStep - // files, then two WriteFileSteps executing in parallel might clobber each other. - - // TODO port the cache system from the compiler to zig std lib. Until then - // we directly construct the path, and no "cache hit" detection happens; - // the files are always written. - // Note there is similar code over in ConfigHeaderStep. - const Hasher = std.crypto.auth.siphash.SipHash128(1, 3); - // Random bytes to make WriteFileStep unique. Refresh this with - // new random bytes when WriteFileStep implementation is modified - // in a non-backwards-compatible way. - var hash = Hasher.init("eagVR1dYXoE7ARDP"); - - { - var it = self.files.first; - while (it) |node| : (it = node.next) { - hash.update(node.data.basename); - hash.update(node.data.bytes); - hash.update("|"); - } - } - var digest: [16]u8 = undefined; - hash.final(&digest); - var hash_basename: [digest.len * 2]u8 = undefined; - _ = std.fmt.bufPrint( - &hash_basename, - "{s}", - .{std.fmt.fmtSliceHexLower(&digest)}, - ) catch unreachable; - - self.output_dir = try fs.path.join(self.builder.allocator, &[_][]const u8{ - self.builder.cache_root, "o", &hash_basename, - }); - var dir = fs.cwd().makeOpenPath(self.output_dir, .{}) catch |err| { - std.debug.print("unable to make path {s}: {s}\n", .{ self.output_dir, @errorName(err) }); - return err; - }; - defer dir.close(); - { - var it = self.files.first; - while (it) |node| : (it = node.next) { - dir.writeFile(node.data.basename, node.data.bytes) catch |err| { - std.debug.print("unable to write {s} into {s}: {s}\n", .{ - node.data.basename, - self.output_dir, - @errorName(err), - }); - return err; - }; - node.data.source.path = fs.path.join( - self.builder.allocator, - &[_][]const u8{ self.output_dir, node.data.basename }, - ) catch unreachable; - } - } -} diff --git a/lib/std/std.zig b/lib/std/std.zig index ba52784b45..e0318ceb43 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -9,6 +9,7 @@ pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged; pub const AutoHashMap = hash_map.AutoHashMap; pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; pub const BoundedArray = @import("bounded_array.zig").BoundedArray; +pub const Build = @import("Build.zig"); pub const BufMap = @import("buf_map.zig").BufMap; pub const BufSet = @import("buf_set.zig").BufSet; pub const ChildProcess = @import("child_process.zig").ChildProcess; @@ -49,7 +50,6 @@ pub const array_hash_map = @import("array_hash_map.zig"); pub const atomic = @import("atomic.zig"); pub const base64 = @import("base64.zig"); pub const bit_set = @import("bit_set.zig"); -pub const build = @import("build.zig"); pub const builtin = @import("builtin.zig"); pub const c = @import("c.zig"); pub const coff = @import("coff.zig"); @@ -96,6 +96,12 @@ pub const wasm = @import("wasm.zig"); pub const zig = @import("zig.zig"); pub const start = @import("start.zig"); +///// Deprecated. Use `std.Build` instead. +//pub const build = struct { +// /// Deprecated. Use `std.Build` instead. +// pub const Builder = Build; +//}; + const root = @import("root"); const options_override = if (@hasDecl(root, "std_options")) root.std_options else struct {}; diff --git a/test/link/bss/build.zig b/test/link/bss/build.zig index c31fa7faf5..0df9c1d323 100644 --- a/test/link/bss/build.zig +++ b/test/link/bss/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const test_step = b.step("test", "Test"); diff --git a/test/link/common_symbols/build.zig b/test/link/common_symbols/build.zig index 068c3f9c57..ee9dd94ebd 100644 --- a/test/link/common_symbols/build.zig +++ b/test/link/common_symbols/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const lib_a = b.addStaticLibrary(.{ diff --git a/test/link/common_symbols_alignment/build.zig b/test/link/common_symbols_alignment/build.zig index b6dd39801c..f6efdc784b 100644 --- a/test/link/common_symbols_alignment/build.zig +++ b/test/link/common_symbols_alignment/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target = b.standardTargetOptions(.{}); diff --git a/test/link/interdependent_static_c_libs/build.zig b/test/link/interdependent_static_c_libs/build.zig index 50a214490d..d8962a8e08 100644 --- a/test/link/interdependent_static_c_libs/build.zig +++ b/test/link/interdependent_static_c_libs/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target = b.standardTargetOptions(.{}); diff --git a/test/link/macho/bugs/13056/build.zig b/test/link/macho/bugs/13056/build.zig index a65cd60766..662fd25c92 100644 --- a/test/link/macho/bugs/13056/build.zig +++ b/test/link/macho/bugs/13056/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target: std.zig.CrossTarget = .{ .os_tag = .macos }; diff --git a/test/link/macho/bugs/13457/build.zig b/test/link/macho/bugs/13457/build.zig index 4c1ce89261..6ca1f31b86 100644 --- a/test/link/macho/bugs/13457/build.zig +++ b/test/link/macho/bugs/13457/build.zig @@ -1,8 +1,7 @@ const std = @import("std"); -const Builder = std.build.Builder; -const LibExeObjectStep = std.build.LibExeObjStep; +const LibExeObjectStep = std.Build.LibExeObjStep; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target: std.zig.CrossTarget = .{ .os_tag = .macos }; diff --git a/test/link/macho/dead_strip/build.zig b/test/link/macho/dead_strip/build.zig index a4c3575e45..b6c3002492 100644 --- a/test/link/macho/dead_strip/build.zig +++ b/test/link/macho/dead_strip/build.zig @@ -1,8 +1,7 @@ const std = @import("std"); -const Builder = std.build.Builder; -const LibExeObjectStep = std.build.LibExeObjStep; +const LibExeObjectStep = std.Build.LibExeObjStep; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target: std.zig.CrossTarget = .{ .os_tag = .macos }; @@ -37,7 +36,7 @@ pub fn build(b: *Builder) void { } } -fn createScenario(b: *Builder, optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget) *LibExeObjectStep { +fn createScenario(b: *std.Build, optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget) *LibExeObjectStep { const exe = b.addExecutable(.{ .name = "test", .optimize = optimize, diff --git a/test/link/macho/dead_strip_dylibs/build.zig b/test/link/macho/dead_strip_dylibs/build.zig index 0127b575fc..f61b30ca4a 100644 --- a/test/link/macho/dead_strip_dylibs/build.zig +++ b/test/link/macho/dead_strip_dylibs/build.zig @@ -1,8 +1,7 @@ const std = @import("std"); -const Builder = std.build.Builder; -const LibExeObjectStep = std.build.LibExeObjStep; +const LibExeObjectStep = std.Build.LibExeObjStep; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const test_step = b.step("test", "Test the program"); @@ -36,7 +35,7 @@ pub fn build(b: *Builder) void { } } -fn createScenario(b: *Builder, optimize: std.builtin.OptimizeMode) *LibExeObjectStep { +fn createScenario(b: *std.Build, optimize: std.builtin.OptimizeMode) *LibExeObjectStep { const exe = b.addExecutable(.{ .name = "test", .optimize = optimize, diff --git a/test/link/macho/dylib/build.zig b/test/link/macho/dylib/build.zig index acd27a507f..7a1e2d862c 100644 --- a/test/link/macho/dylib/build.zig +++ b/test/link/macho/dylib/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target: std.zig.CrossTarget = .{ .os_tag = .macos }; diff --git a/test/link/macho/empty/build.zig b/test/link/macho/empty/build.zig index 8b2d047371..586da1511b 100644 --- a/test/link/macho/empty/build.zig +++ b/test/link/macho/empty/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target: std.zig.CrossTarget = .{ .os_tag = .macos }; @@ -17,7 +16,7 @@ pub fn build(b: *Builder) void { exe.addCSourceFile("empty.c", &[0][]const u8{}); exe.linkLibC(); - const run_cmd = std.build.EmulatableRunStep.create(b, "run", exe); + const run_cmd = std.Build.EmulatableRunStep.create(b, "run", exe); run_cmd.expectStdOutEqual("Hello!\n"); test_step.dependOn(&run_cmd.step); } diff --git a/test/link/macho/entry/build.zig b/test/link/macho/entry/build.zig index 87e4d1b5da..4504da9c6c 100644 --- a/test/link/macho/entry/build.zig +++ b/test/link/macho/entry/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const test_step = b.step("test", "Test"); diff --git a/test/link/macho/headerpad/build.zig b/test/link/macho/headerpad/build.zig index 74efb5d580..2b3c6abb8a 100644 --- a/test/link/macho/headerpad/build.zig +++ b/test/link/macho/headerpad/build.zig @@ -1,9 +1,8 @@ const std = @import("std"); const builtin = @import("builtin"); -const Builder = std.build.Builder; -const LibExeObjectStep = std.build.LibExeObjStep; +const LibExeObjectStep = std.Build.LibExeObjStep; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const test_step = b.step("test", "Test"); @@ -94,7 +93,7 @@ pub fn build(b: *Builder) void { } } -fn simpleExe(b: *Builder, optimize: std.builtin.OptimizeMode) *LibExeObjectStep { +fn simpleExe(b: *std.Build, optimize: std.builtin.OptimizeMode) *LibExeObjectStep { const exe = b.addExecutable(.{ .name = "main", .optimize = optimize, diff --git a/test/link/macho/linksection/build.zig b/test/link/macho/linksection/build.zig index eebb31a21e..227d4eeb63 100644 --- a/test/link/macho/linksection/build.zig +++ b/test/link/macho/linksection/build.zig @@ -1,6 +1,6 @@ const std = @import("std"); -pub fn build(b: *std.build.Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target = std.zig.CrossTarget{ .os_tag = .macos }; diff --git a/test/link/macho/needed_framework/build.zig b/test/link/macho/needed_framework/build.zig index 33965a9272..62b70b21f1 100644 --- a/test/link/macho/needed_framework/build.zig +++ b/test/link/macho/needed_framework/build.zig @@ -1,8 +1,7 @@ const std = @import("std"); -const Builder = std.build.Builder; -const LibExeObjectStep = std.build.LibExeObjStep; +const LibExeObjectStep = std.Build.LibExeObjStep; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const test_step = b.step("test", "Test the program"); diff --git a/test/link/macho/needed_library/build.zig b/test/link/macho/needed_library/build.zig index 137239d292..cdad94357b 100644 --- a/test/link/macho/needed_library/build.zig +++ b/test/link/macho/needed_library/build.zig @@ -1,8 +1,7 @@ const std = @import("std"); -const Builder = std.build.Builder; -const LibExeObjectStep = std.build.LibExeObjStep; +const LibExeObjectStep = std.Build.LibExeObjStep; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target: std.zig.CrossTarget = .{ .os_tag = .macos }; diff --git a/test/link/macho/objc/build.zig b/test/link/macho/objc/build.zig index 9c38739a5c..10d293baab 100644 --- a/test/link/macho/objc/build.zig +++ b/test/link/macho/objc/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const test_step = b.step("test", "Test the program"); @@ -18,6 +17,6 @@ pub fn build(b: *Builder) void { // populate paths to the sysroot here. exe.linkFramework("Foundation"); - const run_cmd = std.build.EmulatableRunStep.create(b, "run", exe); + const run_cmd = std.Build.EmulatableRunStep.create(b, "run", exe); test_step.dependOn(&run_cmd.step); } diff --git a/test/link/macho/objcpp/build.zig b/test/link/macho/objcpp/build.zig index f4c88b2862..2a3459be50 100644 --- a/test/link/macho/objcpp/build.zig +++ b/test/link/macho/objcpp/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const test_step = b.step("test", "Test the program"); diff --git a/test/link/macho/pagezero/build.zig b/test/link/macho/pagezero/build.zig index f61aa34a93..0a8471b919 100644 --- a/test/link/macho/pagezero/build.zig +++ b/test/link/macho/pagezero/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target: std.zig.CrossTarget = .{ .os_tag = .macos }; diff --git a/test/link/macho/search_strategy/build.zig b/test/link/macho/search_strategy/build.zig index db894b6ae3..eeda89446b 100644 --- a/test/link/macho/search_strategy/build.zig +++ b/test/link/macho/search_strategy/build.zig @@ -1,8 +1,7 @@ const std = @import("std"); -const Builder = std.build.Builder; -const LibExeObjectStep = std.build.LibExeObjStep; +const LibExeObjectStep = std.Build.LibExeObjStep; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target: std.zig.CrossTarget = .{ .os_tag = .macos }; @@ -29,14 +28,14 @@ pub fn build(b: *Builder) void { const exe = createScenario(b, optimize, target); exe.search_strategy = .paths_first; - const run = std.build.EmulatableRunStep.create(b, "run", exe); + const run = std.Build.EmulatableRunStep.create(b, "run", exe); run.cwd = b.pathFromRoot("."); run.expectStdOutEqual("Hello world"); test_step.dependOn(&run.step); } } -fn createScenario(b: *Builder, optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget) *LibExeObjectStep { +fn createScenario(b: *std.Build, optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget) *LibExeObjectStep { const static = b.addStaticLibrary(.{ .name = "a", .optimize = optimize, @@ -44,7 +43,7 @@ fn createScenario(b: *Builder, optimize: std.builtin.OptimizeMode, target: std.z }); static.addCSourceFile("a.c", &.{}); static.linkLibC(); - static.override_dest_dir = std.build.InstallDir{ + static.override_dest_dir = std.Build.InstallDir{ .custom = "static", }; static.install(); @@ -57,7 +56,7 @@ fn createScenario(b: *Builder, optimize: std.builtin.OptimizeMode, target: std.z }); dylib.addCSourceFile("a.c", &.{}); dylib.linkLibC(); - dylib.override_dest_dir = std.build.InstallDir{ + dylib.override_dest_dir = std.Build.InstallDir{ .custom = "dynamic", }; dylib.install(); diff --git a/test/link/macho/stack_size/build.zig b/test/link/macho/stack_size/build.zig index 74e9a86e94..3529a134eb 100644 --- a/test/link/macho/stack_size/build.zig +++ b/test/link/macho/stack_size/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target: std.zig.CrossTarget = .{ .os_tag = .macos }; diff --git a/test/link/macho/strict_validation/build.zig b/test/link/macho/strict_validation/build.zig index b6baf63c11..6eabc02b5f 100644 --- a/test/link/macho/strict_validation/build.zig +++ b/test/link/macho/strict_validation/build.zig @@ -1,9 +1,8 @@ const std = @import("std"); const builtin = @import("builtin"); -const Builder = std.build.Builder; -const LibExeObjectStep = std.build.LibExeObjStep; +const LibExeObjectStep = std.Build.LibExeObjStep; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target: std.zig.CrossTarget = .{ .os_tag = .macos }; diff --git a/test/link/macho/tls/build.zig b/test/link/macho/tls/build.zig index 9b2fe952bf..c77588cb5d 100644 --- a/test/link/macho/tls/build.zig +++ b/test/link/macho/tls/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target: std.zig.CrossTarget = .{ .os_tag = .macos }; diff --git a/test/link/macho/unwind_info/build.zig b/test/link/macho/unwind_info/build.zig index dbbdbb3e51..e43c002e2e 100644 --- a/test/link/macho/unwind_info/build.zig +++ b/test/link/macho/unwind_info/build.zig @@ -1,9 +1,8 @@ const std = @import("std"); const builtin = @import("builtin"); -const Builder = std.build.Builder; -const LibExeObjectStep = std.build.LibExeObjStep; +const LibExeObjectStep = std.Build.LibExeObjStep; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target: std.zig.CrossTarget = .{ .os_tag = .macos }; @@ -14,8 +13,8 @@ pub fn build(b: *Builder) void { } fn testUnwindInfo( - b: *Builder, - test_step: *std.build.Step, + b: *std.Build, + test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget, dead_strip: bool, @@ -52,7 +51,7 @@ fn testUnwindInfo( test_step.dependOn(&run_cmd.step); } -fn createScenario(b: *Builder, optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget) *LibExeObjectStep { +fn createScenario(b: *std.Build, optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget) *LibExeObjectStep { const exe = b.addExecutable(.{ .name = "test", .optimize = optimize, diff --git a/test/link/macho/uuid/build.zig b/test/link/macho/uuid/build.zig index 86ff99e8b1..62c288f1a0 100644 --- a/test/link/macho/uuid/build.zig +++ b/test/link/macho/uuid/build.zig @@ -1,8 +1,7 @@ const std = @import("std"); -const Builder = std.build.Builder; -const LibExeObjectStep = std.build.LibExeObjStep; +const LibExeObjectStep = std.Build.LibExeObjStep; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const test_step = b.step("test", "Test"); test_step.dependOn(b.getInstallStep()); @@ -27,8 +26,8 @@ pub fn build(b: *Builder) void { } fn testUuid( - b: *Builder, - test_step: *std.build.Step, + b: *std.Build, + test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget, comptime exp: []const u8, @@ -52,7 +51,7 @@ fn testUuid( } } -fn simpleDylib(b: *Builder, optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget) *LibExeObjectStep { +fn simpleDylib(b: *std.Build, optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget) *LibExeObjectStep { const dylib = b.addSharedLibrary(.{ .name = "test", .version = .{ .major = 1, .minor = 0 }, diff --git a/test/link/macho/weak_framework/build.zig b/test/link/macho/weak_framework/build.zig index f8460c4e82..5be66991dd 100644 --- a/test/link/macho/weak_framework/build.zig +++ b/test/link/macho/weak_framework/build.zig @@ -1,8 +1,7 @@ const std = @import("std"); -const Builder = std.build.Builder; -const LibExeObjectStep = std.build.LibExeObjStep; +const LibExeObjectStep = std.Build.LibExeObjStep; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const test_step = b.step("test", "Test the program"); diff --git a/test/link/macho/weak_library/build.zig b/test/link/macho/weak_library/build.zig index 229d965e48..505ab5ae96 100644 --- a/test/link/macho/weak_library/build.zig +++ b/test/link/macho/weak_library/build.zig @@ -1,8 +1,7 @@ const std = @import("std"); -const Builder = std.build.Builder; -const LibExeObjectStep = std.build.LibExeObjStep; +const LibExeObjectStep = std.Build.LibExeObjStep; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target: std.zig.CrossTarget = .{ .os_tag = .macos }; diff --git a/test/link/static_lib_as_system_lib/build.zig b/test/link/static_lib_as_system_lib/build.zig index 895cdcf316..b6cf32d711 100644 --- a/test/link/static_lib_as_system_lib/build.zig +++ b/test/link/static_lib_as_system_lib/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target = b.standardTargetOptions(.{}); diff --git a/test/link/wasm/archive/build.zig b/test/link/wasm/archive/build.zig index 7401ba22dc..342c4c08d1 100644 --- a/test/link/wasm/archive/build.zig +++ b/test/link/wasm/archive/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const test_step = b.step("test", "Test"); test_step.dependOn(b.getInstallStep()); diff --git a/test/link/wasm/basic-features/build.zig b/test/link/wasm/basic-features/build.zig index 69e88aefae..9f57066518 100644 --- a/test/link/wasm/basic-features/build.zig +++ b/test/link/wasm/basic-features/build.zig @@ -1,6 +1,6 @@ const std = @import("std"); -pub fn build(b: *std.build.Builder) void { +pub fn build(b: *std.Build) void { // Library with explicitly set cpu features const lib = b.addSharedLibrary(.{ .name = "lib", diff --git a/test/link/wasm/bss/build.zig b/test/link/wasm/bss/build.zig index 6b29fd0dc3..1017e70a71 100644 --- a/test/link/wasm/bss/build.zig +++ b/test/link/wasm/bss/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const test_step = b.step("test", "Test"); test_step.dependOn(b.getInstallStep()); diff --git a/test/link/wasm/export-data/build.zig b/test/link/wasm/export-data/build.zig index 8eab283ec2..95caf42dd0 100644 --- a/test/link/wasm/export-data/build.zig +++ b/test/link/wasm/export-data/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const test_step = b.step("test", "Test"); test_step.dependOn(b.getInstallStep()); diff --git a/test/link/wasm/export/build.zig b/test/link/wasm/export/build.zig index 2b9a91d728..69c34a320e 100644 --- a/test/link/wasm/export/build.zig +++ b/test/link/wasm/export/build.zig @@ -1,6 +1,6 @@ const std = @import("std"); -pub fn build(b: *std.build.Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const no_export = b.addSharedLibrary(.{ diff --git a/test/link/wasm/extern-mangle/build.zig b/test/link/wasm/extern-mangle/build.zig index 71bb986dff..19913e6eca 100644 --- a/test/link/wasm/extern-mangle/build.zig +++ b/test/link/wasm/extern-mangle/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const test_step = b.step("test", "Test"); test_step.dependOn(b.getInstallStep()); diff --git a/test/link/wasm/extern/build.zig b/test/link/wasm/extern/build.zig index 800c76a31c..569d94091a 100644 --- a/test/link/wasm/extern/build.zig +++ b/test/link/wasm/extern/build.zig @@ -1,6 +1,6 @@ const std = @import("std"); -pub fn build(b: *std.build.Builder) void { +pub fn build(b: *std.Build) void { const exe = b.addExecutable(.{ .name = "extern", .root_source_file = .{ .path = "main.zig" }, diff --git a/test/link/wasm/function-table/build.zig b/test/link/wasm/function-table/build.zig index 804aaf0b09..4c25d0d860 100644 --- a/test/link/wasm/function-table/build.zig +++ b/test/link/wasm/function-table/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const test_step = b.step("test", "Test"); diff --git a/test/link/wasm/infer-features/build.zig b/test/link/wasm/infer-features/build.zig index 147fb55fda..d6d706a33d 100644 --- a/test/link/wasm/infer-features/build.zig +++ b/test/link/wasm/infer-features/build.zig @@ -1,6 +1,6 @@ const std = @import("std"); -pub fn build(b: *std.build.Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); // Wasm Object file which we will use to infer the features from diff --git a/test/link/wasm/producers/build.zig b/test/link/wasm/producers/build.zig index 57ee6acd18..2589b0dfcf 100644 --- a/test/link/wasm/producers/build.zig +++ b/test/link/wasm/producers/build.zig @@ -1,8 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const test_step = b.step("test", "Test"); test_step.dependOn(b.getInstallStep()); diff --git a/test/link/wasm/segments/build.zig b/test/link/wasm/segments/build.zig index 8f7d9e0583..76160e905f 100644 --- a/test/link/wasm/segments/build.zig +++ b/test/link/wasm/segments/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const test_step = b.step("test", "Test"); test_step.dependOn(b.getInstallStep()); diff --git a/test/link/wasm/stack_pointer/build.zig b/test/link/wasm/stack_pointer/build.zig index 42971c607d..95c7643880 100644 --- a/test/link/wasm/stack_pointer/build.zig +++ b/test/link/wasm/stack_pointer/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const test_step = b.step("test", "Test"); test_step.dependOn(b.getInstallStep()); diff --git a/test/link/wasm/type/build.zig b/test/link/wasm/type/build.zig index 7fa3849083..816b57ccab 100644 --- a/test/link/wasm/type/build.zig +++ b/test/link/wasm/type/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const test_step = b.step("test", "Test"); test_step.dependOn(b.getInstallStep()); diff --git a/test/src/compare_output.zig b/test/src/compare_output.zig index a885faaadf..edd48321c9 100644 --- a/test/src/compare_output.zig +++ b/test/src/compare_output.zig @@ -1,7 +1,6 @@ // This is the implementation of the test harness. // For the actual test cases, see test/compare_output.zig. const std = @import("std"); -const build = std.build; const ArrayList = std.ArrayList; const fmt = std.fmt; const mem = std.mem; @@ -9,8 +8,8 @@ const fs = std.fs; const OptimizeMode = std.builtin.OptimizeMode; pub const CompareOutputContext = struct { - b: *build.Builder, - step: *build.Step, + b: *std.Build, + step: *std.Build.Step, test_index: usize, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode, diff --git a/test/src/run_translated_c.zig b/test/src/run_translated_c.zig index 0c54655b32..2103172ed6 100644 --- a/test/src/run_translated_c.zig +++ b/test/src/run_translated_c.zig @@ -1,15 +1,14 @@ // This is the implementation of the test harness for running translated // C code. For the actual test cases, see test/run_translated_c.zig. const std = @import("std"); -const build = std.build; const ArrayList = std.ArrayList; const fmt = std.fmt; const mem = std.mem; const fs = std.fs; pub const RunTranslatedCContext = struct { - b: *build.Builder, - step: *build.Step, + b: *std.Build, + step: *std.Build.Step, test_index: usize, test_filter: ?[]const u8, target: std.zig.CrossTarget, diff --git a/test/src/translate_c.zig b/test/src/translate_c.zig index ad5fbb7091..e275ee57ee 100644 --- a/test/src/translate_c.zig +++ b/test/src/translate_c.zig @@ -1,7 +1,6 @@ // This is the implementation of the test harness. // For the actual test cases, see test/translate_c.zig. const std = @import("std"); -const build = std.build; const ArrayList = std.ArrayList; const fmt = std.fmt; const mem = std.mem; @@ -9,8 +8,8 @@ const fs = std.fs; const CrossTarget = std.zig.CrossTarget; pub const TranslateCContext = struct { - b: *build.Builder, - step: *build.Step, + b: *std.Build, + step: *std.Build.Step, test_index: usize, test_filter: ?[]const u8, diff --git a/test/standalone/brace_expansion/build.zig b/test/standalone/brace_expansion/build.zig index 89250ff96f..7c32a09bef 100644 --- a/test/standalone/brace_expansion/build.zig +++ b/test/standalone/brace_expansion/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const main = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, .optimize = b.standardOptimizeOption(.{}), diff --git a/test/standalone/c_compiler/build.zig b/test/standalone/c_compiler/build.zig index 6959f810d6..dce999d4a2 100644 --- a/test/standalone/c_compiler/build.zig +++ b/test/standalone/c_compiler/build.zig @@ -1,9 +1,8 @@ const std = @import("std"); const builtin = @import("builtin"); -const Builder = std.build.Builder; const CrossTarget = std.zig.CrossTarget; -// TODO integrate this with the std.build executor API +// TODO integrate this with the std.Build executor API fn isRunnableTarget(t: CrossTarget) bool { if (t.isNative()) return true; @@ -11,7 +10,7 @@ fn isRunnableTarget(t: CrossTarget) bool { t.getCpuArch() == builtin.cpu.arch); } -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target = b.standardTargetOptions(.{}); diff --git a/test/standalone/emit_asm_and_bin/build.zig b/test/standalone/emit_asm_and_bin/build.zig index b8cbd5fc17..5345f0f538 100644 --- a/test/standalone/emit_asm_and_bin/build.zig +++ b/test/standalone/emit_asm_and_bin/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const main = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, .optimize = b.standardOptimizeOption(.{}), diff --git a/test/standalone/empty_env/build.zig b/test/standalone/empty_env/build.zig index ecdd74aa90..c4b4846141 100644 --- a/test/standalone/empty_env/build.zig +++ b/test/standalone/empty_env/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const main = b.addExecutable(.{ .name = "main", .root_source_file = .{ .path = "main.zig" }, diff --git a/test/standalone/global_linkage/build.zig b/test/standalone/global_linkage/build.zig index 3064c6cc08..9f79c80fcf 100644 --- a/test/standalone/global_linkage/build.zig +++ b/test/standalone/global_linkage/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const obj1 = b.addStaticLibrary(.{ diff --git a/test/standalone/install_raw_hex/build.zig b/test/standalone/install_raw_hex/build.zig index 94016b1d74..b0f938a344 100644 --- a/test/standalone/install_raw_hex/build.zig +++ b/test/standalone/install_raw_hex/build.zig @@ -1,8 +1,8 @@ const builtin = @import("builtin"); const std = @import("std"); -const CheckFileStep = std.build.CheckFileStep; +const CheckFileStep = std.Build.CheckFileStep; -pub fn build(b: *std.build.Builder) void { +pub fn build(b: *std.Build) void { const target = .{ .cpu_arch = .thumb, .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m4 }, diff --git a/test/standalone/issue_11595/build.zig b/test/standalone/issue_11595/build.zig index b0310947f6..c335fb73da 100644 --- a/test/standalone/issue_11595/build.zig +++ b/test/standalone/issue_11595/build.zig @@ -1,9 +1,8 @@ const std = @import("std"); const builtin = @import("builtin"); -const Builder = std.build.Builder; const CrossTarget = std.zig.CrossTarget; -// TODO integrate this with the std.build executor API +// TODO integrate this with the std.Build executor API fn isRunnableTarget(t: CrossTarget) bool { if (t.isNative()) return true; @@ -11,7 +10,7 @@ fn isRunnableTarget(t: CrossTarget) bool { t.getCpuArch() == builtin.cpu.arch); } -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target = b.standardTargetOptions(.{}); diff --git a/test/standalone/issue_12588/build.zig b/test/standalone/issue_12588/build.zig index 27a23d5a76..9f14c53e38 100644 --- a/test/standalone/issue_12588/build.zig +++ b/test/standalone/issue_12588/build.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const Builder = std.build.Builder; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target = b.standardTargetOptions(.{}); diff --git a/test/standalone/issue_12706/build.zig b/test/standalone/issue_12706/build.zig index e3c40d34c6..9d616477a2 100644 --- a/test/standalone/issue_12706/build.zig +++ b/test/standalone/issue_12706/build.zig @@ -1,9 +1,8 @@ const std = @import("std"); const builtin = @import("builtin"); -const Builder = std.build.Builder; const CrossTarget = std.zig.CrossTarget; -// TODO integrate this with the std.build executor API +// TODO integrate this with the std.Build executor API fn isRunnableTarget(t: CrossTarget) bool { if (t.isNative()) return true; @@ -11,7 +10,7 @@ fn isRunnableTarget(t: CrossTarget) bool { t.getCpuArch() == builtin.cpu.arch); } -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target = b.standardTargetOptions(.{}); diff --git a/test/standalone/issue_13030/build.zig b/test/standalone/issue_13030/build.zig index 510c7610d9..258d9b7db8 100644 --- a/test/standalone/issue_13030/build.zig +++ b/test/standalone/issue_13030/build.zig @@ -1,9 +1,8 @@ const std = @import("std"); const builtin = @import("builtin"); -const Builder = std.build.Builder; const CrossTarget = std.zig.CrossTarget; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target = b.standardTargetOptions(.{}); diff --git a/test/standalone/issue_339/build.zig b/test/standalone/issue_339/build.zig index 34c555cfdb..62ac128aab 100644 --- a/test/standalone/issue_339/build.zig +++ b/test/standalone/issue_339/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const obj = b.addObject(.{ .name = "test", .root_source_file = .{ .path = "test.zig" }, diff --git a/test/standalone/issue_5825/build.zig b/test/standalone/issue_5825/build.zig index 8d7acc3e9a..89272280d4 100644 --- a/test/standalone/issue_5825/build.zig +++ b/test/standalone/issue_5825/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const target = .{ .cpu_arch = .x86_64, .os_tag = .windows, diff --git a/test/standalone/issue_7030/build.zig b/test/standalone/issue_7030/build.zig index 41a646abe8..dc535318cc 100644 --- a/test/standalone/issue_7030/build.zig +++ b/test/standalone/issue_7030/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const exe = b.addExecutable(.{ .name = "issue_7030", .root_source_file = .{ .path = "main.zig" }, diff --git a/test/standalone/issue_794/build.zig b/test/standalone/issue_794/build.zig index 59ff7ea9ab..3089a28fd0 100644 --- a/test/standalone/issue_794/build.zig +++ b/test/standalone/issue_794/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const test_artifact = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, }); diff --git a/test/standalone/issue_8550/build.zig b/test/standalone/issue_8550/build.zig index 233f701661..c3303d55db 100644 --- a/test/standalone/issue_8550/build.zig +++ b/test/standalone/issue_8550/build.zig @@ -1,6 +1,6 @@ const std = @import("std"); -pub fn build(b: *std.build.Builder) !void { +pub fn build(b: *std.Build) !void { const target = std.zig.CrossTarget{ .os_tag = .freestanding, .cpu_arch = .arm, diff --git a/test/standalone/issue_9812/build.zig b/test/standalone/issue_9812/build.zig index 50eefe846c..4ca55ce999 100644 --- a/test/standalone/issue_9812/build.zig +++ b/test/standalone/issue_9812/build.zig @@ -1,6 +1,6 @@ const std = @import("std"); -pub fn build(b: *std.build.Builder) !void { +pub fn build(b: *std.Build) !void { const optimize = b.standardOptimizeOption(.{}); const zip_add = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, diff --git a/test/standalone/load_dynamic_library/build.zig b/test/standalone/load_dynamic_library/build.zig index 1aca02bc71..44fc37893c 100644 --- a/test/standalone/load_dynamic_library/build.zig +++ b/test/standalone/load_dynamic_library/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); diff --git a/test/standalone/main_pkg_path/build.zig b/test/standalone/main_pkg_path/build.zig index baee74052e..f9919d5ab5 100644 --- a/test/standalone/main_pkg_path/build.zig +++ b/test/standalone/main_pkg_path/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const test_exe = b.addTest(.{ .root_source_file = .{ .path = "a/test.zig" }, }); diff --git a/test/standalone/mix_c_files/build.zig b/test/standalone/mix_c_files/build.zig index ad69f05ff6..f2dfb2093f 100644 --- a/test/standalone/mix_c_files/build.zig +++ b/test/standalone/mix_c_files/build.zig @@ -1,9 +1,8 @@ const std = @import("std"); const builtin = @import("builtin"); -const Builder = std.build.Builder; const CrossTarget = std.zig.CrossTarget; -// TODO integrate this with the std.build executor API +// TODO integrate this with the std.Build executor API fn isRunnableTarget(t: CrossTarget) bool { if (t.isNative()) return true; @@ -11,7 +10,7 @@ fn isRunnableTarget(t: CrossTarget) bool { t.getCpuArch() == builtin.cpu.arch); } -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target = b.standardTargetOptions(.{}); diff --git a/test/standalone/mix_o_files/build.zig b/test/standalone/mix_o_files/build.zig index de37265388..2708343aa5 100644 --- a/test/standalone/mix_o_files/build.zig +++ b/test/standalone/mix_o_files/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const obj = b.addObject(.{ diff --git a/test/standalone/options/build.zig b/test/standalone/options/build.zig index 87a584a887..3f1e823359 100644 --- a/test/standalone/options/build.zig +++ b/test/standalone/options/build.zig @@ -1,6 +1,6 @@ const std = @import("std"); -pub fn build(b: *std.build.Builder) void { +pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); diff --git a/test/standalone/pie/build.zig b/test/standalone/pie/build.zig index 3f0b8b9f2f..d51ea27328 100644 --- a/test/standalone/pie/build.zig +++ b/test/standalone/pie/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const main = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, .optimize = b.standardOptimizeOption(.{}), diff --git a/test/standalone/pkg_import/build.zig b/test/standalone/pkg_import/build.zig index 8dcfaeded0..5fbc8a67ae 100644 --- a/test/standalone/pkg_import/build.zig +++ b/test/standalone/pkg_import/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const exe = b.addExecutable(.{ diff --git a/test/standalone/shared_library/build.zig b/test/standalone/shared_library/build.zig index 135be095bc..91f7c8a06a 100644 --- a/test/standalone/shared_library/build.zig +++ b/test/standalone/shared_library/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target = b.standardTargetOptions(.{}); const lib = b.addSharedLibrary(.{ diff --git a/test/standalone/static_c_lib/build.zig b/test/standalone/static_c_lib/build.zig index 81b4349e20..9937888843 100644 --- a/test/standalone/static_c_lib/build.zig +++ b/test/standalone/static_c_lib/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const foo = b.addStaticLibrary(.{ diff --git a/test/standalone/test_runner_path/build.zig b/test/standalone/test_runner_path/build.zig index 9b02da50c1..f073c55d4a 100644 --- a/test/standalone/test_runner_path/build.zig +++ b/test/standalone/test_runner_path/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const test_exe = b.addTest(.{ .root_source_file = .{ .path = "test.zig" }, .kind = .test_exe, diff --git a/test/standalone/use_alias/build.zig b/test/standalone/use_alias/build.zig index d2ca90f3ab..89e07efb22 100644 --- a/test/standalone/use_alias/build.zig +++ b/test/standalone/use_alias/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const main = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, .optimize = b.standardOptimizeOption(.{}), diff --git a/test/standalone/windows_spawn/build.zig b/test/standalone/windows_spawn/build.zig index de58a602c3..3ebde5a50c 100644 --- a/test/standalone/windows_spawn/build.zig +++ b/test/standalone/windows_spawn/build.zig @@ -1,6 +1,6 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const hello = b.addExecutable(.{ diff --git a/test/tests.zig b/test/tests.zig index 575550be02..de25528dde 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1,7 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); const debug = std.debug; -const build = std.build; const CrossTarget = std.zig.CrossTarget; const io = std.io; const fs = std.fs; @@ -9,9 +8,10 @@ const mem = std.mem; const fmt = std.fmt; const ArrayList = std.ArrayList; const OptimizeMode = std.builtin.OptimizeMode; -const LibExeObjStep = build.LibExeObjStep; +const LibExeObjStep = std.Build.LibExeObjStep; const Allocator = mem.Allocator; -const ExecError = build.Builder.ExecError; +const ExecError = std.Build.ExecError; +const Step = std.Build.Step; // Cases const compare_output = @import("compare_output.zig"); @@ -462,7 +462,7 @@ const test_targets = blk: { const max_stdout_size = 1 * 1024 * 1024; // 1 MB -pub fn addCompareOutputTests(b: *build.Builder, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *build.Step { +pub fn addCompareOutputTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step { const cases = b.allocator.create(CompareOutputContext) catch unreachable; cases.* = CompareOutputContext{ .b = b, @@ -477,7 +477,7 @@ pub fn addCompareOutputTests(b: *build.Builder, test_filter: ?[]const u8, optimi return cases.step; } -pub fn addStackTraceTests(b: *build.Builder, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *build.Step { +pub fn addStackTraceTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step { const cases = b.allocator.create(StackTracesContext) catch unreachable; cases.* = StackTracesContext{ .b = b, @@ -493,7 +493,7 @@ pub fn addStackTraceTests(b: *build.Builder, test_filter: ?[]const u8, optimize_ } pub fn addStandaloneTests( - b: *build.Builder, + b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode, skip_non_native: bool, @@ -506,7 +506,7 @@ pub fn addStandaloneTests( enable_wasmtime: bool, enable_wine: bool, enable_symlinks_windows: bool, -) *build.Step { +) *Step { const cases = b.allocator.create(StandaloneContext) catch unreachable; cases.* = StandaloneContext{ .b = b, @@ -532,13 +532,13 @@ pub fn addStandaloneTests( } pub fn addLinkTests( - b: *build.Builder, + b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode, enable_macos_sdk: bool, omit_stage2: bool, enable_symlinks_windows: bool, -) *build.Step { +) *Step { const cases = b.allocator.create(StandaloneContext) catch unreachable; cases.* = StandaloneContext{ .b = b, @@ -556,7 +556,7 @@ pub fn addLinkTests( return cases.step; } -pub fn addCliTests(b: *build.Builder, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *build.Step { +pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step { _ = test_filter; _ = optimize_modes; const step = b.step("test-cli", "Test the command line interface"); @@ -577,7 +577,7 @@ pub fn addCliTests(b: *build.Builder, test_filter: ?[]const u8, optimize_modes: return step; } -pub fn addAssembleAndLinkTests(b: *build.Builder, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *build.Step { +pub fn addAssembleAndLinkTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step { const cases = b.allocator.create(CompareOutputContext) catch unreachable; cases.* = CompareOutputContext{ .b = b, @@ -592,7 +592,7 @@ pub fn addAssembleAndLinkTests(b: *build.Builder, test_filter: ?[]const u8, opti return cases.step; } -pub fn addTranslateCTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step { +pub fn addTranslateCTests(b: *std.Build, test_filter: ?[]const u8) *Step { const cases = b.allocator.create(TranslateCContext) catch unreachable; cases.* = TranslateCContext{ .b = b, @@ -607,10 +607,10 @@ pub fn addTranslateCTests(b: *build.Builder, test_filter: ?[]const u8) *build.St } pub fn addRunTranslatedCTests( - b: *build.Builder, + b: *std.Build, test_filter: ?[]const u8, target: std.zig.CrossTarget, -) *build.Step { +) *Step { const cases = b.allocator.create(RunTranslatedCContext) catch unreachable; cases.* = .{ .b = b, @@ -625,7 +625,7 @@ pub fn addRunTranslatedCTests( return cases.step; } -pub fn addGenHTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step { +pub fn addGenHTests(b: *std.Build, test_filter: ?[]const u8) *Step { const cases = b.allocator.create(GenHContext) catch unreachable; cases.* = GenHContext{ .b = b, @@ -640,7 +640,7 @@ pub fn addGenHTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step { } pub fn addPkgTests( - b: *build.Builder, + b: *std.Build, test_filter: ?[]const u8, root_src: []const u8, name: []const u8, @@ -651,7 +651,7 @@ pub fn addPkgTests( skip_libc: bool, skip_stage1: bool, skip_stage2: bool, -) *build.Step { +) *Step { const step = b.step(b.fmt("test-{s}", .{name}), desc); for (test_targets) |test_target| { @@ -742,8 +742,8 @@ pub fn addPkgTests( } pub const StackTracesContext = struct { - b: *build.Builder, - step: *build.Step, + b: *std.Build, + step: *Step, test_index: usize, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode, @@ -840,7 +840,7 @@ pub const StackTracesContext = struct { const RunAndCompareStep = struct { pub const base_id = .custom; - step: build.Step, + step: Step, context: *StackTracesContext, exe: *LibExeObjStep, name: []const u8, @@ -858,7 +858,7 @@ pub const StackTracesContext = struct { const allocator = context.b.allocator; const ptr = allocator.create(RunAndCompareStep) catch unreachable; ptr.* = RunAndCompareStep{ - .step = build.Step.init(.custom, "StackTraceCompareOutputStep", allocator, make), + .step = Step.init(.custom, "StackTraceCompareOutputStep", allocator, make), .context = context, .exe = exe, .name = name, @@ -871,7 +871,7 @@ pub const StackTracesContext = struct { return ptr; } - fn make(step: *build.Step) !void { + fn make(step: *Step) !void { const self = @fieldParentPtr(RunAndCompareStep, "step", step); const b = self.context.b; @@ -1014,8 +1014,8 @@ pub const StackTracesContext = struct { }; pub const StandaloneContext = struct { - b: *build.Builder, - step: *build.Step, + b: *std.Build, + step: *Step, test_index: usize, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode, @@ -1150,8 +1150,8 @@ pub const StandaloneContext = struct { }; pub const GenHContext = struct { - b: *build.Builder, - step: *build.Step, + b: *std.Build, + step: *Step, test_index: usize, test_filter: ?[]const u8, @@ -1178,7 +1178,7 @@ pub const GenHContext = struct { }; const GenHCmpOutputStep = struct { - step: build.Step, + step: Step, context: *GenHContext, obj: *LibExeObjStep, name: []const u8, @@ -1194,7 +1194,7 @@ pub const GenHContext = struct { const allocator = context.b.allocator; const ptr = allocator.create(GenHCmpOutputStep) catch unreachable; ptr.* = GenHCmpOutputStep{ - .step = build.Step.init(.Custom, "ParseCCmpOutput", allocator, make), + .step = Step.init(.Custom, "ParseCCmpOutput", allocator, make), .context = context, .obj = obj, .name = name, @@ -1206,7 +1206,7 @@ pub const GenHContext = struct { return ptr; } - fn make(step: *build.Step) !void { + fn make(step: *Step) !void { const self = @fieldParentPtr(GenHCmpOutputStep, "step", step); const b = self.context.b; @@ -1348,7 +1348,7 @@ const c_abi_targets = [_]CrossTarget{ }, }; -pub fn addCAbiTests(b: *build.Builder, skip_non_native: bool, skip_release: bool) *build.Step { +pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *Step { const step = b.step("test-c-abi", "Run the C ABI tests"); const optimize_modes: [2]OptimizeMode = .{ .Debug, .ReleaseFast }; -- cgit v1.2.3