build.zig raw

   1  // THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
   2  // OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
   3  //
   4  // Permission is hereby granted to use or copy this program
   5  // for any purpose, provided the above notices are retained on all copies.
   6  // Permission to modify the code and to distribute modified code is granted,
   7  // provided the above notices are retained, and a notice that the code was
   8  // modified is included with the above copyright notice.
   9  
  10  // A script to build and test the collector using Zig build system.
  11  // This script matches `CMakeLists.txt` file as much as possible.
  12  
  13  const builtin = @import("builtin");
  14  const std = @import("std");
  15  
  16  // The Zig version here should match that in file `build.zig.zon`.
  17  const zig_min_required_version = "0.14.0";
  18  
  19  // TODO: specify `PACKAGE_VERSION` and `LIB*_VER_INFO`.
  20  
  21  // Compared to the `cmake` script, some definitions and compiler options are
  22  // hard-coded here, which is natural because `build.zig` is only built with
  23  // the Zig build system and Zig ships with an embedded clang (as of zig 0.14).
  24  // As a consequence, we do not have to support lots of different compilers
  25  // (a notable exception is msvc target which implies use of the corresponding
  26  // native compiler).
  27  // And, on the contrary, we know exactly what we get and thus we can align on
  28  // clang's capabilities rather than having to discover compiler capabilities.
  29  // Similarly, since Zig ships `libc` headers for many platforms, we can, with
  30  // the knowledge of the platform, determine what capabilities should be
  31  // enabled or not.
  32  
  33  comptime {
  34      const required_ver = std.SemanticVersion.parse(zig_min_required_version)
  35                              catch unreachable;
  36      if (builtin.zig_version.order(required_ver) == .lt) {
  37          @compileError(std.fmt.comptimePrint(
  38              "Zig version {} does not meet the build requirement of {}",
  39              .{ builtin.zig_version, required_ver },
  40          ));
  41      }
  42  }
  43  
  44  pub fn build(b: *std.Build) void {
  45      const optimize = b.standardOptimizeOption(.{});
  46      const target = b.standardTargetOptions(.{});
  47      const t = target.result;
  48  
  49      const default_enable_threads = !t.cpu.arch.isWasm(); // emscripten/wasi
  50  
  51      // Customize build by passing "-D<option_name>[=false]" in command line.
  52      const enable_cplusplus = b.option(bool, "enable_cplusplus",
  53                                        "C++ support") orelse false;
  54      const build_shared_libs = b.option(bool, "BUILD_SHARED_LIBS",
  55                  "Build shared libraries (otherwise static ones)") orelse true;
  56      const build_cord = b.option(bool, "build_cord",
  57                                  "Build cord library") orelse true;
  58      const cflags_extra = b.option([]const u8, "CFLAGS_EXTRA",
  59                                    "Extra user-defined cflags") orelse "";
  60      // TODO: support `enable_docs`
  61      const enable_threads = b.option(bool, "enable_threads",
  62                  "Support threads") orelse default_enable_threads;
  63      const enable_parallel_mark = b.option(bool, "enable_parallel_mark",
  64          "Parallelize marking and free list construction") orelse true;
  65      const enable_thread_local_alloc = b.option(bool,
  66          "enable_thread_local_alloc",
  67          "Turn on thread-local allocation optimization") orelse true;
  68      const enable_threads_discovery = b.option(bool,
  69          "enable_threads_discovery",
  70          "Enable threads discovery in GC") orelse true;
  71      const enable_rwlock = b.option(bool, "enable_rwlock",
  72          "Enable reader mode of the allocator lock") orelse false;
  73      const enable_throw_bad_alloc_library = b.option(bool,
  74          "enable_throw_bad_alloc_library",
  75          "Turn on C++ gctba library build") orelse true;
  76      const enable_gcj_support = b.option(bool, "enable_gcj_support",
  77                                          "Support for gcj") orelse true;
  78      const enable_sigrt_signals = b.option(bool, "enable_sigrt_signals",
  79          "Use SIGRTMIN-based signals for thread suspend/resume") orelse false;
  80      const enable_valgrind_tracking = b.option(bool,
  81          "enable_valgrind_tracking",
  82          "Support tracking GC_malloc and friends for heap profiling tools")
  83          orelse false;
  84      const enable_gc_debug = b.option(bool, "enable_gc_debug",
  85          "Support for pointer back-tracing") orelse false;
  86      const disable_gc_debug = b.option(bool, "disable_gc_debug",
  87          "Disable debugging like GC_dump and its callees") orelse false;
  88      const enable_java_finalization = b.option(bool,
  89          "enable_java_finalization",
  90          "Support for java finalization") orelse true;
  91      const enable_atomic_uncollectable = b.option(bool,
  92          "enable_atomic_uncollectable",
  93          "Support for atomic uncollectible allocation") orelse true;
  94      const enable_redirect_malloc = b.option(bool, "enable_redirect_malloc",
  95          "Redirect malloc and friend to GC routines") orelse false;
  96      const enable_disclaim = b.option(bool, "enable_disclaim",
  97          "Support alternative finalization interface") orelse true;
  98      const enable_dynamic_pointer_mask = b.option(bool,
  99          "enable_dynamic_pointer_mask",
 100          "Support pointer mask/shift set at runtime") orelse false;
 101      const enable_large_config = b.option(bool, "enable_large_config",
 102          "Optimize for large heap or root set") orelse false;
 103      const enable_gc_assertions = b.option(bool, "enable_gc_assertions",
 104          "Enable collector-internal assertion checking") orelse false;
 105      const enable_mmap = b.option(bool, "enable_mmap",
 106          "Use mmap instead of sbrk to expand the heap") orelse false;
 107      const enable_munmap = b.option(bool, "enable_munmap",
 108          "Return page to the OS if empty for N collections") orelse true;
 109      const enable_dynamic_loading = b.option(bool, "enable_dynamic_loading",
 110          "Enable tracing of dynamic library data roots") orelse true;
 111      const enable_register_main_static_data = b.option(bool,
 112          "enable_register_main_static_data",
 113          "Perform the initial guess of data root sets") orelse true;
 114      const enable_checksums = b.option(bool, "enable_checksums",
 115          "Report erroneously cleared dirty bits") orelse false;
 116      const enable_werror = b.option(bool, "enable_werror",
 117          "Pass -Werror to the C compiler (treat warnings as errors)")
 118          orelse false;
 119      const enable_single_obj_compilation = b.option(bool,
 120          "enable_single_obj_compilation",
 121          "Compile all libgc source files into single .o") orelse false;
 122      const disable_single_obj_compilation = b.option(bool,
 123          "disable_single_obj_compilation",
 124          "Compile each libgc source file independently") orelse false;
 125      const enable_handle_fork = b.option(bool, "enable_handle_fork",
 126          "Attempt to ensure a usable collector after fork()") orelse true;
 127      const disable_handle_fork = b.option(bool, "disable_handle_fork",
 128          "Prohibit installation of pthread_atfork() handlers") orelse false;
 129      // TODO: support `enable_emscripten_asyncify`
 130      const install_headers = b.option(bool, "install_headers",
 131          "Install header and pkg-config metadata files") orelse true;
 132      // TODO: support `with_libatomic_ops`, `without_libatomic_ops`
 133  
 134      var source_files: std.ArrayListUnmanaged([]const u8) = .empty;
 135      defer source_files.deinit(b.allocator);
 136      var flags: std.ArrayListUnmanaged([]const u8) = .empty;
 137      defer flags.deinit(b.allocator);
 138  
 139      // Always enabled.
 140      flags.append(b.allocator, "-D ALL_INTERIOR_POINTERS") catch unreachable;
 141      flags.append(b.allocator, "-D NO_EXECUTE_PERMISSION") catch unreachable;
 142  
 143      // Output all warnings.
 144      flags.appendSlice(b.allocator, &.{
 145          "-Wall",
 146          "-Wextra",
 147          "-Wpedantic",
 148      }) catch unreachable;
 149  
 150      // Disable MS `crt` security warnings reported e.g. for `getenv`, `strcpy`.
 151      if (t.abi == .msvc) {
 152          flags.append(b.allocator,
 153                       "-D _CRT_SECURE_NO_DEPRECATE") catch unreachable;
 154      }
 155  
 156      source_files.appendSlice(b.allocator, &.{
 157          "allchblk.c",
 158          "alloc.c",
 159          "blacklst.c",
 160          "dbg_mlc.c",
 161          "dyn_load.c",
 162          "finalize.c",
 163          "headers.c",
 164          "mach_dep.c",
 165          "malloc.c",
 166          "mallocx.c",
 167          "mark.c",
 168          "mark_rts.c",
 169          "misc.c",
 170          "new_hblk.c",
 171          "os_dep.c",
 172          "ptr_chck.c",
 173          "reclaim.c",
 174          "typd_mlc.c",
 175      }) catch unreachable;
 176  
 177      if (enable_threads) {
 178          flags.append(b.allocator, "-D GC_THREADS") catch unreachable;
 179          if (enable_parallel_mark) {
 180              flags.append(b.allocator, "-D PARALLEL_MARK") catch unreachable;
 181          }
 182          if (t.os.tag != .windows) { // assume `pthreads`
 183              // TODO: support cygwin when supported by zig
 184              // Zig comes with clang which supports GCC atomic intrinsics.
 185              flags.append(b.allocator,
 186                           "-D GC_BUILTIN_ATOMIC") catch unreachable;
 187              // TODO: define and use `THREADDLLIBS_LIST`
 188              source_files.appendSlice(b.allocator, &.{
 189                  "gc_dlopen.c",
 190                  "pthread_start.c",
 191                  "pthread_support.c",
 192              }) catch unreachable;
 193              if (t.os.tag.isDarwin()) {
 194                  source_files.append(b.allocator,
 195                                      "darwin_stop_world.c") catch unreachable;
 196              } else {
 197                  source_files.append(b.allocator,
 198                                      "pthread_stop_world.c") catch unreachable;
 199              }
 200              // Common defines for POSIX platforms.
 201              flags.append(b.allocator, "-D _REENTRANT") catch unreachable;
 202              // TODO: some targets might need `_PTHREADS` defined too.
 203              if (enable_thread_local_alloc) {
 204                  flags.append(b.allocator,
 205                               "-D THREAD_LOCAL_ALLOC") catch unreachable;
 206                  source_files.appendSlice(b.allocator, &.{
 207                      "specific.c",
 208                      "thread_local_alloc.c",
 209                  }) catch unreachable;
 210              }
 211              // Message for clients: Explicit `GC_INIT` call may be required.
 212              if (enable_handle_fork and !disable_handle_fork) {
 213                  flags.append(b.allocator, "-D HANDLE_FORK") catch unreachable;
 214              }
 215              if (enable_sigrt_signals) {
 216                  flags.append(b.allocator,
 217                               "-D GC_USESIGRT_SIGNALS") catch unreachable;
 218              }
 219          } else {
 220              // Assume the GCC atomic intrinsics are supported.
 221              flags.append(b.allocator,
 222                           "-D GC_BUILTIN_ATOMIC") catch unreachable;
 223              if (enable_thread_local_alloc
 224                      and (enable_parallel_mark or !build_shared_libs)) {
 225                  // Imply `THREAD_LOCAL_ALLOC` unless `GC_DLL`.
 226                  flags.append(b.allocator,
 227                               "-D THREAD_LOCAL_ALLOC") catch unreachable;
 228                  source_files.append(b.allocator,
 229                                      "thread_local_alloc.c") catch unreachable;
 230              }
 231              flags.append(b.allocator,
 232                           "-D EMPTY_GETENV_RESULTS") catch unreachable;
 233              source_files.appendSlice(b.allocator, &.{
 234                  // Add `pthread_start.c` file just in case client defines
 235                  // `GC_WIN32_PTHREADS` macro.
 236                  "pthread_start.c",
 237                  "pthread_support.c",
 238                  "win32_threads.c",
 239              }) catch unreachable;
 240          }
 241      }
 242  
 243      // TODO: define/use `NEED_LIB_RT`
 244  
 245      if (disable_handle_fork) {
 246          flags.append(b.allocator, "-D NO_HANDLE_FORK") catch unreachable;
 247      }
 248  
 249      if (enable_gcj_support) {
 250          flags.append(b.allocator, "-D GC_GCJ_SUPPORT") catch unreachable;
 251          // TODO: do not define `GC_ENABLE_SUSPEND_THREAD` on kFreeBSD
 252          // if `enable_thread_local_alloc` (a workaround for some bug).
 253          flags.append(b.allocator,
 254                       "-D GC_ENABLE_SUSPEND_THREAD") catch unreachable;
 255          source_files.append(b.allocator, "gcj_mlc.c") catch unreachable;
 256      }
 257  
 258      if (enable_disclaim) {
 259          flags.append(b.allocator, "-D ENABLE_DISCLAIM") catch unreachable;
 260          source_files.append(b.allocator, "fnlz_mlc.c") catch unreachable;
 261      }
 262  
 263      if (enable_dynamic_pointer_mask) {
 264          flags.append(b.allocator, "-D DYNAMIC_POINTER_MASK") catch unreachable;
 265      }
 266  
 267      if (enable_java_finalization) {
 268          flags.append(b.allocator, "-D JAVA_FINALIZATION") catch unreachable;
 269      }
 270  
 271      if (enable_atomic_uncollectable) {
 272          flags.append(b.allocator,
 273                       "-D GC_ATOMIC_UNCOLLECTABLE") catch unreachable;
 274      }
 275  
 276      if (enable_valgrind_tracking) {
 277          flags.append(b.allocator, "-D VALGRIND_TRACKING") catch unreachable;
 278      }
 279  
 280      if (enable_gc_debug) {
 281          flags.append(b.allocator, "-D DBG_HDRS_ALL") catch unreachable;
 282          flags.append(b.allocator, "-D KEEP_BACK_PTRS") catch unreachable;
 283          if (t.os.tag == .linux) {
 284              flags.append(b.allocator, "-D MAKE_BACK_GRAPH") catch unreachable;
 285              // TODO: do not define `SAVE_CALL_COUNT` for e2k
 286              flags.append(b.allocator,
 287                           "-D SAVE_CALL_COUNT=8") catch unreachable;
 288              source_files.append(b.allocator, "backgraph.c") catch unreachable;
 289          }
 290      }
 291  
 292      if (disable_gc_debug) {
 293          flags.append(b.allocator, "-D NO_DEBUGGING") catch unreachable;
 294      }
 295      if (optimize != .Debug) {
 296          flags.append(b.allocator, "-D NDEBUG") catch unreachable;
 297      }
 298  
 299      if (enable_redirect_malloc) {
 300          if (enable_gc_debug) {
 301              flags.append(b.allocator,
 302                           "-D REDIRECT_MALLOC=GC_debug_malloc_replacement")
 303                  catch unreachable;
 304              flags.append(b.allocator,
 305                           "-D REDIRECT_REALLOC=GC_debug_realloc_replacement")
 306                  catch unreachable;
 307              flags.append(b.allocator,
 308                           "-D REDIRECT_FREE=GC_debug_free") catch unreachable;
 309          } else {
 310              flags.append(b.allocator,
 311                           "-D REDIRECT_MALLOC=GC_malloc") catch unreachable;
 312          }
 313          if (t.os.tag == .windows) {
 314              flags.append(b.allocator,
 315                           "-D REDIRECT_MALLOC_IN_HEADER") catch unreachable;
 316          } else {
 317              flags.append(b.allocator,
 318                           "-D GC_USE_DLOPEN_WRAP") catch unreachable;
 319          }
 320      }
 321  
 322      if (enable_mmap or enable_munmap) {
 323          flags.append(b.allocator, "-D USE_MMAP") catch unreachable;
 324      }
 325  
 326      if (enable_munmap) {
 327          flags.append(b.allocator, "-D USE_MUNMAP") catch unreachable;
 328      }
 329  
 330      if (!enable_dynamic_loading) {
 331          flags.append(b.allocator,
 332                       "-D IGNORE_DDYNAMIC_LOADING") catch unreachable;
 333      }
 334  
 335      if (!enable_register_main_static_data) {
 336          flags.append(b.allocator,
 337                       "-D GC_DONT_REGISTER_MAIN_STATIC_DATA") catch unreachable;
 338      }
 339  
 340      if (enable_large_config) {
 341          flags.append(b.allocator, "-D LARGE_CONFIG") catch unreachable;
 342      }
 343  
 344      if (enable_gc_assertions) {
 345          flags.append(b.allocator, "-D GC_ASSERTIONS") catch unreachable;
 346      }
 347  
 348      if (!enable_threads_discovery) {
 349          flags.append(b.allocator,
 350                       "-D GC_NO_THREADS_DISCOVERY") catch unreachable;
 351      }
 352  
 353      if (enable_rwlock) {
 354          flags.append(b.allocator, "-D USE_RWLOCK") catch unreachable;
 355      }
 356  
 357      if (enable_checksums) {
 358          if (enable_munmap or enable_threads) {
 359              @panic("CHECKSUMS not compatible with USE_MUNMAP or threads");
 360          }
 361          flags.append(b.allocator, "-D CHECKSUMS") catch unreachable;
 362          source_files.append(b.allocator, "checksums.c") catch unreachable;
 363      }
 364  
 365      if (enable_werror) {
 366          flags.append(b.allocator, "-Werror") catch unreachable;
 367      }
 368  
 369      if (enable_single_obj_compilation
 370              or (build_shared_libs and !disable_single_obj_compilation)) {
 371          source_files.clearAndFree(b.allocator);
 372          source_files.append(b.allocator, "extra/gc.c") catch unreachable;
 373          if (enable_threads and !t.os.tag.isDarwin() and t.os.tag != .windows) {
 374              flags.append(b.allocator,
 375                           "-D GC_PTHREAD_START_STANDALONE") catch unreachable;
 376              source_files.append(b.allocator,
 377                                  "pthread_start.c") catch unreachable;
 378          }
 379      }
 380  
 381      // Add implementation of `backtrace` and `backtrace_symbols`.
 382      if (t.abi == .msvc) {
 383          source_files.append(b.allocator, "extra/msvc_dbg.c") catch unreachable;
 384      }
 385  
 386      // TODO: declare that the libraries do not refer to external symbols
 387      // of `build_shared_libs`.
 388  
 389      // `zig cc` supports this flag.
 390      flags.appendSlice(b.allocator, &.{
 391          // TODO: `-Wno-unused-command-line-argument`
 392          // Prevent "__builtin_return_address with nonzero argument is unsafe".
 393          "-Wno-frame-address",
 394      }) catch unreachable;
 395  
 396      if (build_shared_libs) {
 397          flags.append(b.allocator, "-D GC_DLL") catch unreachable;
 398          if (t.abi == .msvc) {
 399              // TODO: depend on `user32.lib` file instead
 400              flags.append(b.allocator,
 401                           "-D DONT_USE_USER32_DLL") catch unreachable;
 402          } else {
 403              // `zig cc` supports these flags.
 404              flags.append(b.allocator,
 405                           "-D GC_VISIBILITY_HIDDEN_SET") catch unreachable;
 406              flags.append(b.allocator,
 407                           "-fvisibility=hidden") catch unreachable;
 408          }
 409      } else {
 410          flags.append(b.allocator, "-D GC_NOT_DLL") catch unreachable;
 411          if (t.os.tag == .windows) {
 412              // Do not require the clients to link with `user32` system library.
 413              flags.append(b.allocator,
 414                           "-D DONT_USE_USER32_DLL") catch unreachable;
 415          }
 416      }
 417  
 418      // Note: Zig uses clang which ships with these so, unless another
 419      // sysroot/libc, etc. headers location is pointed out, it is fine to
 420      // hard-code enable this.
 421      // `-U GC_MISSING_EXECINFO_H`
 422      // `-U GC_NO_SIGSETJMP`
 423      flags.append(b.allocator, "-D HAVE_SYS_TYPES_H") catch unreachable;
 424  
 425      if (t.abi == .msvc) {
 426          // To workaround "extension used" error reported
 427          // for `__try`/`__finally`.
 428          flags.append(b.allocator, "-D NO_SEH_AVAILABLE") catch unreachable;
 429      } else {
 430          flags.append(b.allocator, "-D HAVE_UNISTD_H") catch unreachable;
 431      }
 432  
 433      const have_getcontext = !t.abi.isMusl() and t.os.tag != .windows;
 434      if (!have_getcontext) {
 435          flags.append(b.allocator, "-D NO_GETCONTEXT") catch unreachable;
 436      }
 437  
 438      if (!t.os.tag.isDarwin() and t.os.tag != .windows) {
 439          // `dl_iterate_phdr` exists (as a strong symbol).
 440          flags.append(b.allocator, "-D HAVE_DL_ITERATE_PHDR") catch unreachable;
 441          if (enable_threads) {
 442              // `pthread_sigmask` and `sigset_t` are available and needed.
 443              flags.append(b.allocator,
 444                           "-D HAVE_PTHREAD_SIGMASK") catch unreachable;
 445          }
 446      }
 447  
 448      // Build with `GC_wcsdup` support (`wcslen` is available).
 449      flags.append(b.allocator, "-D GC_REQUIRE_WCSDUP") catch unreachable;
 450  
 451      // `pthread_setname_np`, if available, may have 1, 2 or 3 arguments.
 452      if (t.os.tag.isDarwin()) {
 453          flags.append(b.allocator, "-D HAVE_PTHREAD_SETNAME_NP_WITHOUT_TID")
 454                  catch unreachable;
 455      } else if (t.os.tag == .linux) {
 456          flags.append(b.allocator,
 457                       "-D HAVE_PTHREAD_SETNAME_NP_WITH_TID") catch unreachable;
 458      } else {
 459          // TODO: support `HAVE_PTHREAD_SETNAME_NP_WITH_TID_AND_ARG`
 460          // and `HAVE_PTHREAD_SET_NAME_NP` targets.
 461      }
 462  
 463      if (t.os.tag != .windows) {
 464          // Define to use `dladdr` function (used for debugging).
 465          flags.append(b.allocator, "-D HAVE_DLADDR") catch unreachable;
 466      }
 467  
 468      // TODO: as of zig 0.14, exception.h and getsect.h are not provided
 469      // by zig itself for Darwin target.
 470      if (t.os.tag.isDarwin() and !target.query.isNative()) {
 471          flags.append(b.allocator,
 472                       "-D MISSING_MACH_O_GETSECT_H") catch unreachable;
 473          flags.append(b.allocator,
 474                       "-D NO_MPROTECT_VDB") catch unreachable;
 475      }
 476  
 477      if (enable_cplusplus and enable_werror) {
 478          if (build_shared_libs and t.os.tag == .windows or t.abi == .msvc) {
 479              // Avoid "replacement operator new[] cannot be declared inline"
 480              // warnings.
 481              flags.append(b.allocator,
 482                           "-Wno-inline-new-delete") catch unreachable;
 483          }
 484          if (t.abi == .msvc) {
 485              // TODO: as of zig 0.14,
 486              // "argument unused during compilation: -nostdinc++" warning is
 487              // reported if using MS compiler.
 488              flags.append(b.allocator, "-Wno-unused-command-line-argument")
 489                  catch unreachable;
 490          }
 491      }
 492  
 493      // Extra user-defined flags (if any) to pass to the compiler.
 494      if (cflags_extra.len > 0) {
 495          // Split it up on a space and append each part to flags separately.
 496          var tokenizer = std.mem.tokenizeScalar(u8, cflags_extra, ' ');
 497          while (tokenizer.next()) |token| {
 498              flags.append(b.allocator, token) catch unreachable;
 499          }
 500      }
 501  
 502      // TODO: convert `VER_INFO` values to `VERSION`, `SOVERSION` ones
 503      const gc = b.addLibrary(.{
 504          .linkage = if (build_shared_libs) .dynamic else .static,
 505          .name = "gc",
 506          .root_module = b.createModule(.{
 507              .target = target,
 508              .optimize = optimize,
 509          }),
 510      });
 511      gc.addCSourceFiles(.{
 512          .files = source_files.items,
 513          .flags = flags.items,
 514      });
 515      gc.addIncludePath(b.path("include"));
 516      gc.linkLibC();
 517  
 518      var gccpp: *std.Build.Step.Compile = undefined;
 519      var gctba: *std.Build.Step.Compile = undefined;
 520      if (enable_cplusplus) {
 521          gccpp = b.addLibrary(.{
 522              .linkage = if (build_shared_libs) .dynamic else .static,
 523              .name = "gccpp",
 524              .root_module = b.createModule(.{
 525                  .target = target,
 526                  .optimize = optimize,
 527              }),
 528          });
 529          gccpp.addCSourceFiles(.{
 530              .files = &.{
 531                  "gc_badalc.cc",
 532                  "gc_cpp.cc",
 533              },
 534              .flags = flags.items,
 535          });
 536          gccpp.addIncludePath(b.path("include"));
 537          gccpp.linkLibrary(gc);
 538          linkLibCpp(gccpp);
 539          if (enable_throw_bad_alloc_library) {
 540              // The same as `gccpp` but contains only `gc_badalc`.
 541              gctba = b.addLibrary(.{
 542                  .linkage = if (build_shared_libs) .dynamic else .static,
 543                  .name = "gctba",
 544                  .root_module = b.createModule(.{
 545                      .target = target,
 546                      .optimize = optimize,
 547                  }),
 548              });
 549              gctba.addCSourceFiles(.{
 550                  .files = &.{
 551                      "gc_badalc.cc",
 552                  },
 553                  .flags = flags.items,
 554              });
 555              gctba.addIncludePath(b.path("include"));
 556              gctba.linkLibrary(gc);
 557              linkLibCpp(gctba);
 558          }
 559      }
 560  
 561      var cord: *std.Build.Step.Compile = undefined;
 562      if (build_cord) {
 563          cord = b.addLibrary(.{
 564              .linkage = if (build_shared_libs) .dynamic else .static,
 565              .name = "cord",
 566              .root_module = b.createModule(.{
 567                  .target = target,
 568                  .optimize = optimize,
 569              })
 570          });
 571          cord.addCSourceFiles(.{
 572              .files = &.{
 573                  "cord/cordbscs.c",
 574                  "cord/cordprnt.c",
 575                  "cord/cordxtra.c",
 576              },
 577              .flags = flags.items,
 578          });
 579          cord.addIncludePath(b.path("include"));
 580          cord.linkLibrary(gc);
 581          cord.linkLibC();
 582      }
 583  
 584      if (install_headers) {
 585          installHeader(b, gc, "gc.h");
 586          installHeader(b, gc, "gc/gc.h");
 587          installHeader(b, gc, "gc/gc_backptr.h");
 588          installHeader(b, gc, "gc/gc_config_macros.h");
 589          installHeader(b, gc, "gc/gc_inline.h");
 590          installHeader(b, gc, "gc/gc_mark.h");
 591          installHeader(b, gc, "gc/gc_tiny_fl.h");
 592          installHeader(b, gc, "gc/gc_typed.h");
 593          installHeader(b, gc, "gc/gc_version.h");
 594          installHeader(b, gc, "gc/javaxfc.h");
 595          installHeader(b, gc, "gc/leak_detector.h");
 596          if (enable_cplusplus) {
 597              installHeader(b, gccpp, "gc_cpp.h");
 598              installHeader(b, gccpp, "gc/gc_allocator.h");
 599              installHeader(b, gccpp, "gc/gc_cpp.h");
 600              if (enable_throw_bad_alloc_library) {
 601                  // The same headers as `gccpp` library has.
 602                  installHeader(b, gctba, "gc_cpp.h");
 603                  installHeader(b, gctba, "gc/gc_allocator.h");
 604                  installHeader(b, gctba, "gc/gc_cpp.h");
 605              }
 606          }
 607          if (enable_disclaim) {
 608              installHeader(b, gc, "gc/gc_disclaim.h");
 609          }
 610          if (enable_gcj_support) {
 611              installHeader(b, gc, "gc/gc_gcj.h");
 612          }
 613          if (enable_threads) {
 614              installHeader(b, gc, "gc/gc_pthread_redirects.h");
 615          }
 616          if (build_cord) {
 617              installHeader(b, cord, "gc/cord.h");
 618              installHeader(b, cord, "gc/cord_pos.h");
 619              installHeader(b, cord, "gc/ec.h");
 620          }
 621          // TODO: compose and install `bdw-gc.pc` and `pkgconfig`.
 622      }
 623  
 624      b.installArtifact(gc);
 625      if (enable_cplusplus) {
 626          b.installArtifact(gccpp);
 627          if (enable_throw_bad_alloc_library) {
 628              b.installArtifact(gctba);
 629          }
 630      }
 631      if (build_cord) {
 632          b.installArtifact(cord);
 633      }
 634  
 635      // Note: there is no `build_tests` option, as the tests are built
 636      // only if `test` step is requested.
 637      const test_step = b.step("test", "Run tests");
 638      addTest(b, gc, test_step, flags, "gctest", "tests/gctest.c");
 639      if (build_cord) {
 640          addTestExt(b, gc, cord, test_step, flags,
 641                     "cordtest", "cord/tests/cordtest.c");
 642          // TODO: add `de` test (Windows only)
 643      }
 644      addTest(b, gc, test_step, flags, "hugetest", "tests/huge.c");
 645      addTest(b, gc, test_step, flags, "leaktest", "tests/leak.c");
 646      addTest(b, gc, test_step, flags, "middletest", "tests/middle.c");
 647      addTest(b, gc, test_step, flags, "realloctest", "tests/realloc.c");
 648      addTest(b, gc, test_step, flags, "smashtest", "tests/smash.c");
 649      // TODO: add `staticroots` test
 650      if (enable_gc_debug) {
 651          addTest(b, gc, test_step, flags, "tracetest", "tests/trace.c");
 652      }
 653      if (enable_threads) {
 654          addTest(b, gc, test_step, flags, "atomicopstest", "tests/atomicops.c");
 655          addTest(b, gc, test_step, flags,
 656                  "initfromthreadtest", "tests/initfromthread.c");
 657          addTest(b, gc, test_step, flags,
 658                  "subthreadcreatetest", "tests/subthreadcreate.c");
 659          addTest(b, gc, test_step, flags,
 660                  "threadleaktest", "tests/threadleak.c");
 661          if (t.os.tag != .windows) {
 662              addTest(b, gc, test_step, flags,
 663                      "threadkeytest", "tests/threadkey.c");
 664          }
 665      }
 666      if (enable_cplusplus) {
 667          addTestExt(b, gc, gccpp, test_step, flags, "cpptest", "tests/cpp.cc");
 668          if (enable_throw_bad_alloc_library) {
 669              addTestExt(b, gc, gctba, test_step, flags,
 670                         "treetest", "tests/tree.cc");
 671          }
 672      }
 673      if (enable_disclaim) {
 674          addTest(b, gc, test_step, flags,
 675                  "disclaim_bench", "tests/disclaim_bench.c");
 676          addTest(b, gc, test_step, flags, "disclaimtest", "tests/disclaim.c");
 677          addTest(b, gc, test_step, flags, "weakmaptest", "tests/weakmap.c");
 678      }
 679  }
 680  
 681  fn linkLibCpp(lib: *std.Build.Step.Compile) void {
 682      const t = lib.rootModuleTarget();
 683      if (t.abi == .msvc) {
 684          // TODO: as of zig 0.14, "unable to build libcxxabi" warning is
 685          // reported if linking C++ code using MS compiler.
 686          lib.linkLibC();
 687      } else {
 688          lib.linkLibCpp();
 689      }
 690  }
 691  
 692  fn addTest(b: *std.Build, gc: *std.Build.Step.Compile,
 693             test_step: *std.Build.Step,
 694             flags: std.ArrayListUnmanaged([]const u8), testname: []const u8,
 695             filename: []const u8) void {
 696      addTestExt(b, gc, null, test_step, flags, testname, filename);
 697  }
 698  
 699  fn addTestExt(b: *std.Build, gc: *std.Build.Step.Compile,
 700                lib2: ?*std.Build.Step.Compile, test_step: *std.Build.Step,
 701                flags: std.ArrayListUnmanaged([]const u8), testname: []const u8,
 702                filename: []const u8) void {
 703      const test_exe = b.addExecutable(.{
 704          .name = testname,
 705          .root_module = b.createModule(.{
 706              .optimize = gc.root_module.optimize.?,
 707              .target = gc.root_module.resolved_target.?
 708          })
 709      });
 710      test_exe.addCSourceFile(.{
 711          .file = b.path(filename),
 712          .flags = flags.items
 713      });
 714      test_exe.addIncludePath(b.path("include"));
 715      test_exe.linkLibrary(gc);
 716      if (lib2 != null) {
 717          test_exe.linkLibrary(lib2.?);
 718      }
 719      test_exe.linkLibC();
 720      const run_test_exe = b.addRunArtifact(test_exe);
 721      test_step.dependOn(&run_test_exe.step);
 722  }
 723  
 724  fn installHeader(b: *std.Build, lib: *std.Build.Step.Compile,
 725                   hfile: []const u8) void {
 726     const src_path = b.pathJoin(&.{ "include", hfile });
 727     lib.installHeader(b.path(src_path), hfile);
 728  }
 729