~motiejus/bazel-zig-cc

cd5c8e7f9436ba82bec99035d9a52a8a294c363e — Motiejus Jakštys 1 year, 2 months ago e08cf8e
Announce the move to github.com/uber/bazel-zig-cc
58 files changed, 48 insertions(+), 3106 deletions(-)

D .bazelrc
D .bazelversion
D .build.yml
D .envrc
D .gitignore
D BUILD
D LICENSE
D NOTICE
M README.md
D WORKSPACE
D bin/mod-tidy
D ci/launcher
D ci/lint
D ci/list_toolchains_platforms
D ci/release
D ci/test
D ci/testdata/want_cache
D contrib/README-patched.md
D contrib/build-and-archive
D contrib/makerel
D contrib/own_zig.md
D go.mod
D go.sum
D release
D relnotes.awk
D repositories.bzl
D rules/BUILD
D rules/platform.bzl
D rules/rules_go.bzl
D test/c/BUILD
D test/c/main.c
D test/c/test.sh
D test/cgo/BUILD
D test/cgo/cgo.go
D test/cgo/cgo_test.go
D test/glibc_hacks/BUILD
D test/glibc_hacks/main.c
D test/gorace/BUILD
D test/gorace/main.go
D test/gorace/main_test.go
D test/windows/BUILD
D test/windows/main.c
D toolchain/BUILD
D toolchain/BUILD.sdk.bazel
D toolchain/defs.bzl
D toolchain/launcher.zig
D toolchain/libc/BUILD
D toolchain/libc/defs.bzl
D toolchain/libc_aware/platform/BUILD
D toolchain/libc_aware/toolchain/BUILD
D toolchain/platform/BUILD
D toolchain/platform/defs.bzl
D toolchain/private/BUILD
D toolchain/private/cc_toolchains.bzl
D toolchain/private/defs.bzl
D toolchain/toolchain/BUILD
D toolchain/toolchain/defs.bzl
D toolchain/zig_toolchain.bzl
D .bazelrc => .bazelrc +0 -9
@@ 1,9 0,0 @@
test --sandbox_default_allow_network=false
test --test_output=errors

build --verbose_failures
build --worker_sandboxing

build --experimental_reuse_sandbox_directories
build --incompatible_enable_cc_toolchain_resolution
build --action_env BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1

D .bazelversion => .bazelversion +0 -1
@@ 1,1 0,0 @@
6.0.0

D .build.yml => .build.yml +0 -44
@@ 1,44 0,0 @@
image: debian/stable
packages:
  - direnv
  - shellcheck
  - qemu-user-static
  - binfmt-support
  - moreutils
  - file
  - wine64
sources:
  - https://git.sr.ht/~motiejus/bazel-zig-cc
environment:
  CC: /usr/bin/false
triggers:
  - action: email
    condition: failure
    to: motiejus+srht@jakstys.lt
tasks:
  - setup: |
      sudo apt-get purge gcc -y && sudo apt-get autoremove -y
      sudo dpkg --add-architecture arm64
      sudo apt-get update
      sudo apt-get install libc6:arm64 -y
  - lint_gazelle: |
      cd bazel-zig-cc; . .envrc
      ./ci/lint
      bazel run --color=yes --curses=yes //:gazelle
      git diff --exit-code
  - test_release: |
      cd bazel-zig-cc; . .envrc
      ./ci/release
  - list_toolchains_platforms: |
      cd bazel-zig-cc; . .envrc
      ./ci/list_toolchains_platforms
  - test_launcher: |
      cd bazel-zig-cc; . .envrc
      ./ci/launcher --color=yes --curses=yes
  - test: |
      cd bazel-zig-cc; . .envrc
      export BAZEL_ZIG_CC_CACHE_PREFIX=/tmp/bazel-zig-cc-2
      ./ci/test \
        --color=yes --curses=yes \
        --repo_env BAZEL_ZIG_CC_CACHE_PREFIX=$BAZEL_ZIG_CC_CACHE_PREFIX \
        --sandbox_writable_path "$BAZEL_ZIG_CC_CACHE_PREFIX"

D .envrc => .envrc +0 -48
@@ 1,48 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

set -eu

BIN_DIR="$(git rev-parse --show-toplevel)/bin"
export PATH="$BIN_DIR:$PATH"

_u_bzl=https://github.com/bazelbuild/bazelisk/releases/download/v1.12.0/bazelisk-
_u_bldf=https://github.com/bazelbuild/buildtools/releases/download/5.1.0/buildifier-

if [[ "${PRINT_TOOL_HASHES:-no}" = "yes" ]]; then
    for os in linux darwin; do
        for arch in amd64 arm64; do
            hash_bzl=$(direnv fetchurl "${_u_bzl}$os-$arch")
            hash_bldf=$(direnv fetchurl "${_u_bldf}$os-$arch")
            echo -e "bzl:  $os-$arch\t$hash_bzl"
            echo -e "bldf: $os-$arch\t$hash_bldf"
        done
    done
fi

# to fetch the hashes, run:
# $ PRINT_TOOL_HASHES=yes bash .envrc
case "$(uname | tr A-Z a-z)-$(uname -m)" in
    linux-x86_64)
        bzl=$(direnv fetchurl "${_u_bzl}linux-amd64" sha256-awvLLqFbyhb/+r5v2nWANEA3U1TAhUgP42HSy/MlAds=)
        bldf=$(direnv fetchurl "${_u_bldf}linux-amd64" sha256-Ur9rECy0+IRk4ZfKrAbWl5P6KwX1rVCn579vvWVmSKM=)
        ;;
    linux-aarch64)
        bzl=$(direnv fetchurl "${_u_bzl}linux-arm64" sha256-KdhhykjfJKPo3sV/sAUIumZKMZIQR7JobDjPmiDUY58=)
        bldf=$(direnv fetchurl "${_u_bldf}linux-arm64" sha256-kX1ZnbsEDmOuen4a23ENIFeBGQL9yeNczpJev9lm7rg=)
        ;;
    darwin-x86_64)
        bzl=$(direnv fetchurl "${_u_bzl}darwin-amd64" sha256-cM9/50gI0WQY03H+uMzU58RCFdsD0sT/x1t2e3ZUCfs=)
        bldf=$(direnv fetchurl "${_u_bldf}darwin-amd64" sha256-yTeNn0KT/DjsVKCPvHTnqdKJFNrmiRM0QB5Z849uZdw=)
        ;;
    darwin-arm64)
        bzl=$(direnv fetchurl "${_u_bzl}darwin-arm64" sha256-NFu4uQDWue90I06enkE67Tj7Ke8lXkrhisYb9KYQLYQ=)
        bldf=$(direnv fetchurl "${_u_bldf}darwin-arm64" sha256-dF/rXqlstv85p2soIcV1kf1wtSgyVWJIbUe10IkA4uQ=)
        ;;
    *)
        >&2 echo "unsupported architecture tuple $(uname | tr A-Z a-z)-$(uname -m)"
        exit 1;;
esac

ln -sf "${bzl}" "$BIN_DIR/bazel"
ln -sf "${bldf}" "$BIN_DIR/buildifier"

D .gitignore => .gitignore +0 -12
@@ 1,12 0,0 @@
*~
*.sw[op]

/bin/bazel
/bin/buildifier
/bin/bazelisk-*

/bazel-bazel-zig-cc
/bazel-bin
/bazel-out
/bazel-testlogs
/bazel-x

D BUILD => BUILD +0 -10
@@ 1,10 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

load("@bazel_gazelle//:def.bzl", "gazelle")

# gazelle:map_kind go_binary go_binary //rules:rules_go.bzl

# gazelle:build_file_name BUILD
# gazelle:prefix git.sr.ht/~motiejus/bazel-zig-cc
gazelle(name = "gazelle")

D LICENSE => LICENSE +0 -222
@@ 1,222 0,0 @@
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

Portions Copyright 2022-23 Uber Technologies, Inc.
Portions Copyright 2022 bazel-zig-cc contributors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

Portions Copyright 2021 Adam Bouhenguel

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to
   deal in the Software without restriction, including without limitation the
   rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
   sell copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
   IN THE SOFTWARE.

D NOTICE => NOTICE +0 -8
@@ 1,8 0,0 @@
bazel-zig-cc

Originally from development at https://github.com/ajbouh/bazel-zig-cc
Copyright 2021 Adam Bouhenguel and licensed under the MIT license

Portions of the code
Copyright 2023 Uber Technologies, Inc. and by the bazel-zig-cc authors
originally licensed under MIT. License changed to Apache 2.0 as of v1.0.1

M README.md => README.md +48 -505
@@ 1,507 1,50 @@
[![builds.sr.ht status](https://builds.sr.ht/~motiejus/bazel-zig-cc.svg)](https://builds.sr.ht/~motiejus/bazel-zig-cc)

# Bazel zig cc toolchain

This is a C/C++ toolchain that can (cross-)compile C/C++ programs. It contains
clang-15, musl, glibc 2-2.34, all in a ~40MB package. Read
[here](https://andrewkelley.me/post/zig-cc-powerful-drop-in-replacement-gcc-clang.html)
about zig-cc; the rest of the README will present how to use this toolchain
from Bazel.

Configuring toolchains in Bazel is complex, under-documented, and fraught with
peril. I, the co-author of bazel-zig-cc, am still confused on how this all
works, and often wonder why it works at all. That aside, we made the our best
effort to make bazel-zig-cc usable for your C/C++/CGo projects, with as many
guardrails as we could install.

While copy-pasting the code in your project, attempt to read and understand the
text surrounding the code snippets. This will save you hours of head
scratching, I promise.

# Usage

Add this to your `WORKSPACE`:

```
BAZEL_ZIG_CC_VERSION = "v1.0.1"

http_archive(
    name = "bazel-zig-cc",
    sha256 = "e9f82bfb74b3df5ca0e67f4d4989e7f1f7ce3386c295fd7fda881ab91f83e509",
    strip_prefix = "bazel-zig-cc-{}".format(BAZEL_ZIG_CC_VERSION),
    urls = ["https://git.sr.ht/~motiejus/bazel-zig-cc/archive/{}.tar.gz".format(BAZEL_ZIG_CC_VERSION)],
)

load("@bazel-zig-cc//toolchain:defs.bzl", zig_toolchains = "toolchains")

# version, url_formats and host_platform_sha256 are optional for those who
# want to control their Zig SDK version.
zig_toolchains(
    version = "<...>",
    url_formats = [
        "https://example.org/zig/zig-{host_platform}-{version}.{_ext}",
    ],
    host_platform_sha256 = { ... },
)
```

And this to `.bazelrc`:

```
build --incompatible_enable_cc_toolchain_resolution
```

The snippets above will download the zig toolchain and make the bazel
toolchains available for registration and usage. If you do nothing else, this
may work. The `.bazelrc` snippet instructs Bazel to use the registered "new
kinds of toolchains". All above are required regardless of how wants to use it.
The next steps depend on how one wants to use bazel-zig-cc. The descriptions
below is a gentle introduction to C++ toolchains from "user's perspective" too.

## Use case: manually build a single target with a specific zig cc toolchain

This option is least disruptive to the workflow compared to no hermetic C++
toolchain, and works best when trying out or getting started with bazel-zig-cc
for a subset of targets.

To request Bazel to use a specific toolchain (compatible with the specified
platform) for build/tests/whatever on linux-amd64-musl, do:

```
bazel build \
    --platforms @zig_sdk//platform:linux_arm64 \
    --extra_toolchains @zig_sdk//toolchain:linux_arm64_musl \
    //test/go:go
```

There are a few things going on here, let's try to dissect them.

### Option `--platforms @zig_sdk//platform:linux_arm64`

Specifies that the our target platform is `linux_arm64`, which resolves into:

```
$ bazel query --output=build @zig_sdk//platform:linux_arm64
platform(
  name = "linux_arm64",
  generator_name = "linux_arm64",
  generator_function = "declare_platforms",
  generator_location = "platform/BUILD:7:18",
  constraint_values = ["@platforms//os:linux", "@platforms//cpu:aarch64"],
)
```

`constraint_values` instructs Bazel to be looking for a **toolchain** that is
compatible with (in Bazelspeak, `target_compatible_with`) **all of the**
`["@platforms//os:linux", "@platforms//cpu:aarch64"]`.

### Option `--toolchains=@zig_sdk//toolchain:linux_arm64_musl`

Inspect first (`@platforms//cpu:aarch64` is an alias to
`@platforms//cpu:arm64`):

```
$ bazel query --output=build @zig_sdk//toolchain:linux_arm64_musl
toolchain(
  name = "linux_arm64_musl",
  generator_name = "linux_arm64_musl",
  generator_function = "declare_toolchains",
  generator_location = "toolchain/BUILD:7:19",
  toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
  target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:aarch64", "@zig_sdk//libc:unconstrained"],
  toolchain = "@zig_sdk//:aarch64-linux-musl_cc",
)
```

For a platform to pick up the right toolchain, the platform's
`constraint_values` must be a subset[^1] of the toolchain's
`target_compatible_with`. Since the platform is a subset (therefore,
toolchain's `@zig_sdk//libc:unconstrained` does not matter), this toolchain is
selected for this platform. As a result, `--platforms
@zig_sdk//platform:linux_amd64` causes Bazel to select a toolchain
`@zig_sdk//platform:linux_arm64_musl` (because it satisfies all constraints),
which will compile and link the C/C++ code with musl.

`@zig_sdk//libc:unconstrained` will become important later.

### Same as above, less typing (with `--config`)

Specifying the platform and toolchain for every target may become burdensome,
so they can be put used via `--config`. For example, append this to `.bazelrc`:

```
build:linux_arm64 --platforms @zig_sdk//platform:linux_arm64
build:linux_arm64 --extra_toolchains @zig_sdk//toolchain:linux_arm64_musl
```

And then building to linux-arm64-musl boils down to:

```
bazel build --config=linux_arm64_musl //test/go:go
```

## Use case: always compile with zig cc

Instead of adding the toolchains to `.bazelrc`, they can be added
unconditionally. Append this to `WORKSPACE` after `zig_toolchains(...)`:

```
register_toolchains(
    "@zig_sdk//toolchain:linux_amd64_gnu.2.19",
    "@zig_sdk//toolchain:linux_arm64_gnu.2.28",
    "@zig_sdk//toolchain:darwin_amd64",
    "@zig_sdk//toolchain:darwin_arm64",
    "@zig_sdk//toolchain:windows_amd64",
    "@zig_sdk//toolchain:windows_arm64",
)
```

Append this to `.bazelrc`:

```
build --action_env BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
```

From Bazel's perspective, this is almost equivalent to always specifying
`--extra_toolchains` on every `bazel <...>` command-line invocation. It also
means there is no way to disable the toolchain with the command line. This is
useful if you find bazel-zig-cc useful enough to compile for all of your
targets and tools.

With `BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1` Bazel stops detecting the default
host toolchain. Configuring toolchains is complicated enough, and the
auto-detection (read: fallback to non-hermetic toolchain) is a footgun best
avoided. This option is not documented in bazel, so may break. If you intend to
use the hermetic toolchain exclusively, it won't hurt.

## Use case: zig-cc for targets for multiple libc variants

When some targets need to be build with different libcs (either different
versions of glibc or musl), use a linux toolchain from
`@zig_sdk//libc_aware/toolchains:<...>`. The toolchain will only be selected
when building for a specific libc. For example, in `WORKSPACE`:

```
register_toolchains(
    "@zig_sdk//libc_aware/toolchain:linux_amd64_gnu.2.19",
    "@zig_sdk//libc_aware/toolchain:linux_arm64_gnu.2.28",
    "@zig_sdk//libc_aware/toolchain:x86_64-linux-musl",
)
```

What does `@zig_sdk//libc_aware/toolchain:linux_amd64_gnu.2.19` mean?

```
$ bazel query --output=build @zig_sdk//libc_aware/toolchain:linux_amd64_gnu.2.19 |& grep target
  target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:x86_64", "@zig_sdk//libc:gnu.2.19"],
```

To see how this relates to the platform:

```
$ bazel query --output=build @zig_sdk//libc_aware/platform:linux_amd64_gnu.2.19 |& grep constraint
  constraint_values = ["@platforms//os:linux", "@platforms//cpu:x86_64", "@zig_sdk//libc:gnu.2.19"],
```

In this case, the platform's `constraint_values` and toolchain's
`target_compatible_with` are identical, causing Bazel to select the right
toolchain for the requested platform. With these toolchains registered, one can
build a project for a specific libc-aware platform; it will select the
appropriate toolchain:

```
$ bazel run --platforms @zig_sdk//libc_aware/platform:linux_amd64_gnu.2.19 //test/c:which_libc
glibc_2.19
$ bazel run --platforms @zig_sdk//libc_aware/platform:linux_amd64_gnu.2.28 //test/c:which_libc
glibc_2.28
$ bazel run --platforms @zig_sdk//libc_aware/platform:linux_amd64_musl //test/c:which_libc
non_glibc
$ bazel run --run_under=file --platforms @zig_sdk//libc_aware/platform:linux_arm64_gnu.2.28 //test/c:which_libc
which_libc: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 2.0.0, stripped
```

To the list of libc aware toolchains and platforms:

```
$ bazel query @zig_sdk//libc_aware/toolchain/...
$ bazel query @zig_sdk//libc_aware/platform/...
 ```

Libc-aware toolchains are especially useful when relying on
[transitions][transitions], as transitioning `extra_platforms` will cause the
host tools to be rebuilt with the specific libc version, which takes time; also
the build host may not be able to run them if, say, target glibc version is
newer than on the host. Some tests in this repository (under `test/`) are using
transitions; you may check out how it's done.

The `@zig_sdk//libc:variant` constraint is necessary to select a matching
toolchain. Remember: the toolchain's `target_compatible_with` must be
equivalent or a superset of the platform's `constraint_values`. This is why
both libc-aware platforms and libc-aware toolchains reside in their own
namespace; if we try to mix non-libc-aware to libc-aware, confusion ensues.

To use the libc constraints in the project's platform definitions, add a
`@zig_sdk//libc:variant` constraint to them. See the list of available values:

```
$ bazel query "attr(constraint_setting, @zig_sdk//libc:variant, @zig_sdk//...)"
```

`@zig_sdk//libc:unconstrained` is a special value that indicates that no value
for the constraint is specified. The non libc aware linux toolchains are only
compatible with this value to prevent accidental silent fallthrough to them.
This is a guardrail. Thanks, future me!

# Note: Naming

Both Go and Bazel naming schemes are accepted. For convenience with
Go, the following Go-style toolchain aliases are created:

|Bazel (zig) name | Go name  |
|---------------- | -------- |
|`x86_64`         | `amd64`  |
|`aarch64`        | `arm64`  |
|`macos`          | `darwin` |

For example, the toolchain `linux_amd64_gnu.2.28` is aliased to
`x86_64-linux-gnu.2.28`. To find out which toolchains can be registered or
used, run:

```
$ bazel query @zig_sdk//toolchain/...
```

# Incompatibilities with clang and gcc

`zig cc` is *almost* a drop-in replacement for clang/gcc. This section lists
some of the discovered differences and ways to live with them.

## UBSAN and "SIGILL: Illegal Instruction"

`zig cc` differs from "mainstream" compilers by [enabling UBSAN by
default][ubsan1]. Which means your program may compile successfully and crash
with:

```
SIGILL: illegal instruction
```

This flag encourages program authors to fix the undefined behavior. There are
[many ways][ubsan2] to find the undefined behavior.

# Known Issues In bazel-zig-cc

These are the things you may stumble into when using bazel-zig-cc. I am
unlikely to implement them any time soon, but patches implementing those will
be accepted. See [Questions & Contributions](#questions-amp-contributions) on
how to contribute.

## Zig cache location

Currently zig cache is in `$HOME`, so `bazel clean --expunge` does not clear
the zig cache. Zig's cache should be stored somewhere in the project's path.

## zig cc concurrency

- Bazel spawns up to `nproc` workers.
- For each of those, Go may spawn up to `nproc` processes while compiling.
- Zig may do the same.

... causing explosion of heavy compiler processes. This causes CPU to spike.
Tracked in [ziglang/zig #12101  RFC: -j/--jobs for zig
subcommands](https://github.com/ziglang/zig/issues/12101).

## OSX: sysroot

For non-trivial programs (and for all darwin/arm64 cgo programs) MacOS SDK may
be necessary. Read [Jakub's comment][sysroot] about it. Support for OSX sysroot
is currently not implemented.

## OSX: different OS targets (Catalina -- Monterey)

[Zig 0.9.0](https://ziglang.org/download/0.9.0/release-notes.html#macOS) may
target macos.10 (Catalina), macos.11 (Big Sur) or macos.12 (Monterey). It
currently targets the lowest version, without ability to change it.

# Known Issues In Upstream

This section lists issues that I've stumbled into when using `zig cc`, and is
outside of bazel-zig-cc's control.

## using glibc 2.27 or older

**Severity: Medium**

Task: [ziglang/zig #9485 glibc 2.27 or older: fcntl64 not found, but zig's glibc headers refer it](https://github.com/ziglang/zig/issues/9485)

Background: when glibc 2.27 or older is selected, it may miss `fcntl64`. A
workaround is applied for `x86_64`, but not for aarch64. The same workaround
may apply to aarch64, but the author didn't find a need to test it (yet).

In September 2022 the severity has been bumped to Medium, because glibc header
updates cause a lot of churn when upgrading the SDK, when it shouldn't cause
any at all.

Feel free to track [Universal headers][universal-headers] project for a fix.

## Number of libc stubs with Go 1.20+

Until Go 1.19 the number of glibc stubs that needed to be compiled was strictly
controlled. Go 1.20 no longer ships with pre-compiled archive files for the
standard library, and it generates them on the fly, causing many extraneous
libc stubs. Therefore, the initial compilation will take longer until those
stubs are pre-cached.

# Closed Upstream Issues

- [ziglang/zig #12317 Possibility to disable caching for user](https://github.com/ziglang/zig/issues/12317) (CLOSED, thanks andrewrk and motiejus)
- [golang/go #52690 Go linker does not put libc onto the linker line](https://github.com/golang/go/issues/52690) (CLOSED, thanks andrewrk and motiejus)
- [ziglang/zig #10386 zig cc regression in 0.9.0](https://github.com/ziglang/zig/issues/10386) (CLOSED, thanks Xavier)
- [ziglang/zig #10312 macho: fail if requested -framework is not found](https://github.com/ziglang/zig/pull/10312) (CLOSED, thanks kubkon)
- [ziglang/zig #10299 [darwin aarch64 cgo] regression](https://github.com/ziglang/zig/issues/10299) (CLOSED, thanks kubkon)
- [ziglang/zig #10297 [darwin x86_64 cgo] regression](https://github.com/ziglang/zig/issues/10297) (CLOSED, thanks kubkon)
- [ziglang/zig #9431 FileNotFound when compiling macos](https://github.com/ziglang/zig/issues/9431) (CLOSED, thanks andrewrk)
- [ziglang/zig #9139 zig c++ hanging when compiling in parallel](https://github.com/ziglang/zig/issues/9139) (CLOSED, thanks andrewrk)
- [ziglang/zig #9050 golang linker segfault](https://github.com/ziglang/zig/issues/9050) (CLOSED, thanks kubkon)
- [ziglang/zig #7917 [meta] better c/c++ toolchain compatibility](https://github.com/ziglang/zig/issues/7917) (CLOSED, thanks andrewrk)
- [ziglang/zig #7915 ar-compatible command for zig cc](https://github.com/ziglang/zig/issues/7915) (CLOSED, thanks andrewrk)
- [ziglang/zig #7667 misplaced relocated glibc stubs (pthread_sigmask)](https://github.com/ziglang/zig/issues/7667) (CLOSED, thanks mjonaitis and andrewrk)
- [rules/go #2894 Per-arch_target linker flags](https://github.com/bazelbuild/rules_go/issues/2894) (CLOSED, thanks mjonaitis)
- [golang/go #46644 cmd/link: with CC=zig: SIGSERV when cross-compiling to darwin/amd64](https://github.com/golang/go/issues/46644) (CLOSED, thanks kubkon)

... and more.

# Host Environments

This repository is used on the following (host) platforms:

- `linux_amd64`, a.k.a. `x86_64`.
- `linux_arm64`, a.k.a. `AArch64`.
- `darwin_amd64`, the 64-bit post-PowerPC models.
- `darwin_arm64`, the M1.
- `windows_amd64`, a.k.a. `x64`.

The tests are running (CId) on linux-amd64, and are assuming the kernel is
configured to run `linux_arm64` and `windows_amd64` binaries.

There are two reasonably convenient ways to configure `linux_arm64` emulation:

1. Install and configure [`binfmt_misc`][binfmt_misc]:
   ```
   apt install qemu-user-static binfmt-support
   ```

2. Magic of the internet:
   ```
   docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
   ```

## Transient docker environment

A standalone Docker environment to play with bazel-zig-cc:

```
$ docker run -e CC=/usr/bin/false -ti --rm -v "$PWD:/x" -w /x debian:bullseye-slim
# apt update
# apt install --no-install-recommends -y direnv git shellcheck ca-certificates
# eval "$(direnv hook bash)" && direnv allow
# ./ci/lint
# ./ci/launcher
# ./ci/test
```

Some of the tests rely on `qemu-aarch64` to run arm64 binaries and wine for
Windows binaries. Therefore, with the setup above, these tests will fail.
To install *all* dependencies, so all tests can pass:

```
$ docker run -e CC=/usr/bin/false -ti --rm -v "$PWD:/x" -w /x debian:bullseye-slim
# dpkg --add-architecture arm64 && apt update
# apt install --no-install-recommends -y direnv git shellcheck ca-certificates libc6:arm64 qemu-user-static wine64
# eval "$(direnv hook bash)" && direnv allow
<... run the ci/ commands as above>
```

# Questions & Contributions

Project's mailing list is [~motiejus/bazel-zig-cc@lists.sr.ht][mailing-list].
Used for:

- announcements (I am aiming to send an email with every release).
- user discussions.
- raising issues.
- contributions.

I will generally respond to emails about issues. I may even be able to fix
them. However, no promises: you are much more likely (and welcome!) to get it
fixed by submitting a patch.

To contribute, send your patches to the mailing list, as described in
[git-send-email.io][git-send-email] or via [Sourcehut web UI][video].

Copyright is retained by the contributors.

# Maintainers

This section lists the driving forces behind bazel-zig-cc. Committers have push
access, maintainers have their areas. Should make it easier to understand our
interests when reading patches or mailing lists.

- Owner: Motiejus Jakštys. Applies others' patches, writes documentation,
  emails, and occasionally contributes. Signs releases.
- Committer: Laurynas Lubys. Bazel expert with regards to tests, transitions
  and overall structure. Rewrote bazel-zig-cc to cater for platforms when libc
  platforms were added.
- Committer: Ken Micklas. Ken was leading hermetic toolchain effort at Uber
  throughout 2022, of which bazel-zig-cc is a part of.
- Maintainer for Windows: Fabian Hahn. If you make a change that breaks
  Windows, Fabian will find you. Please don't break Windows, so Fabian doesn't
  have to look for you. Instead, send him your patches first.

You may find contact information of the individuals in the commit logs.

# Publicity

This section lists notable uses or mentions of bazel-zig-cc.

- 2023-01-24 [bazel-zig-cc v1.0.0][bazel-zig-cc-v1]: releasing bazel-zig-cc and
  admitting that bazel-zig-cc is used in production to compile all of Uber's
  [Go Monorepo][go-monorepo].
- 2022-11-18 [BazelCon 2022: Making Uber's hermetic C++
  toolchain][bazelcon2022]: Laurynas Lubys presents the story of how this
  repository came into being and how it was used (as of the conference).
- 2022-05-23 [How Zig is used at Uber (youtube)][yt-how-zig-is-used-at-uber]:
  Yours Truly (the author) talks about how bazel-zig-cc came to existence and
  how it's used at Uber in Milan Zig Meetup.
- 2022-05-23 [How Uber uses Zig][how-uber-uses-zig]: text version of the above.
- 2022-03-30 [Google Open Source Peer Bonus Program][google-award] awarded the
  author $250 for bazel-zig-cc.
- 2022-01-13 [bazel-zig-cc building Envoy][zig-cc-envoy].

# Thanks

Many thanks to Adam Bouhenguel and his [bazel-zig-cc][ajbouh], the parent of
this repository. Also, the Zig team for making this all possible and handling
the issues promptly.

[^1]: a [mathematical subset][subset]: both can be equal.

[binfmt_misc]: https://en.wikipedia.org/wiki/Binfmt_misc
[mailing-list]: https://lists.sr.ht/~motiejus/bazel-zig-cc
[ajbouh]: https://github.com/ajbouh/bazel-zig-cc/
[git-send-email]: https://git-send-email.io/
[video]: https://spacepub.space/w/no6jnhHeUrt2E5ST168tRL
[sysroot]: https://github.com/ziglang/zig/issues/10299#issuecomment-989153750
[ubsan1]: https://github.com/ziglang/zig/issues/4830#issuecomment-605491606
[ubsan2]: https://github.com/ziglang/zig/issues/5163
[transitions]: https://docs.bazel.build/versions/main/skylark/config.html#user-defined-transitions
[subset]: https://en.wikipedia.org/wiki/Subset
[yt-how-zig-is-used-at-uber]: https://www.youtube.com/watch?v=SCj2J3HcEfc
[how-uber-uses-zig]: https://jakstys.lt/2022/how-uber-uses-zig/
[zig-cc-envoy]: https://github.com/envoyproxy/envoy/issues/19535
[google-award]: https://opensource.googleblog.com/2022/03/Announcing-First-Group-of-Google-Open-Source-Peer-Bonus-Winners-in-2022.html
[go-gc-sections]: https://go-review.googlesource.com/c/go/+/407814
[universal-headers]: https://github.com/ziglang/universal-headers
[bazel-zig-cc-v1]: https://lists.sr.ht/~motiejus/bazel-zig-cc/%3CCAFVMu-rYbf_jDTT4p%3DCS2KV1asdS5Ovo5AyuCwgv2AXr8OOP0g%40mail.gmail.com%3E
[go-monorepo]: https://www.uber.com/blog/go-monorepo-bazel/
[bazelcon2022]: https://www.youtube.com/watch?v=a1jXzx3884g
Moved to [github.com/uber/bazel-zig-cc](https://github.com/uber/bazel-zig-cc).
A copy of the [announcement][1]:

---

Hi folks,

The transfer of bazel-zig-cc from my personal SourceHut account to
github is now complete: https://github.com/uber/bazel-zig-cc

Main changes:
- This will now be Uber's project, not mine. The child has grown and left the
  house. :)
- CLA will be required from now on. However, pre-move copyright owners retain
  their copyright of course; we did not ask to transfer it (albeit I
  transferred mine to Uber for personal reasons).
- Comms are now in bazel's slack (bazel.slack.com) #zig and github issues/PRs.

Short-term a.k.a. migration:
- I will put up a release in github in the next couple of days and communicate
  here; it will be my last message in the mailing list.
- This mailing list will be shut down "in a couple of weeks".
- Your links (i.e. where you download bazel-zig-cc from) will keep working at
  least until 2023-06-01. After this date everyone will need to change it to
  github or your own mirror (the exact details will be announced, like said, in
  a couple of days).

As for why? In case you haven't noticed the comms yet, bazel-zig-cc is now
powering Uber's Go Monorepo. So it no longer fits the "personal project" bill
and it made sense to be moved. Also, my days at Uber are [numbered][2], so it
makes even more sense for Uber to increase the bus factor. I still recommend
Uber as a great place to work if you like such things (I spend quite a bit of
time with Zig during my working hours, which is great) -- but it just does not
fit the bill for my personal circumstances.

Now that we have finished the move, we have additional 2 awesome maintainers
now: [@linzhp][3] and [@sywhang][4]. They are from Uber's Go Monorepo/Platform
teams, and have dealt with much more Go, Bazel and Starlark than myself. I will
be co-maintaining bazel-zig-cc at least until 2023-06-30.

Congratulations everyone and have fun!

Motiejus

[1]: https://lists.sr.ht/~motiejus/bazel-zig-cc/%3CCAFVMu-qcAQPHhPb63GWZMTL6E_9ZLTtw4tGcYMQFogXLaDhnAg%40mail.gmail.com%3E
[2]: https://jakstys.lt/2023/7-years-at-uber/
[3]: https://github.com/linzhp
[4]: https://github.com/sywhang

D WORKSPACE => WORKSPACE +0 -72
@@ 1,72 0,0 @@
workspace(
    name = "bazel-zig-cc",
)

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "io_bazel_rules_go",
    sha256 = "dd926a88a564a9246713a9c00b35315f54cbd46b31a26d5d8fb264c07045f05d",
    urls = [
        "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.38.1/rules_go-v0.38.1.zip",
        "https://github.com/bazelbuild/rules_go/releases/download/v0.38.1/rules_go-v0.38.1.zip",
    ],
)

load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")

http_archive(
    name = "bazel_gazelle",
    sha256 = "ecba0f04f96b4960a5b250c8e8eeec42281035970aa8852dda73098274d14a1d",
    urls = [
        "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.29.0/bazel-gazelle-v0.29.0.tar.gz",
        "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.29.0/bazel-gazelle-v0.29.0.tar.gz",
    ],
)

load("@io_bazel_rules_go//go:deps.bzl", "go_download_sdk", "go_register_toolchains", "go_rules_dependencies")
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")

go_rules_dependencies()

# use latest stable.
go_download_sdk(
    name = "go_sdk",
    version = "1.20",
)

go_register_toolchains()

load("//:repositories.bzl", "go_repositories")

# gazelle:repository_macro repositories.bzl%go_repositories
go_repositories()

gazelle_dependencies(go_repository_default_config = "@//:WORKSPACE")

load(
    "//toolchain:defs.bzl",
    zig_toolchains = "toolchains",
)

zig_toolchains()

register_toolchains(
    # if no `--platform` is specified, these toolchains will be used for
    # (linux,darwin,windows)x(amd64,arm64)
    "@zig_sdk//toolchain:linux_amd64_gnu.2.19",
    "@zig_sdk//toolchain:linux_arm64_gnu.2.28",
    "@zig_sdk//toolchain:darwin_amd64",
    "@zig_sdk//toolchain:darwin_arm64",
    "@zig_sdk//toolchain:windows_amd64",
    "@zig_sdk//toolchain:windows_arm64",

    # amd64 toolchains for libc-aware platforms:
    "@zig_sdk//libc_aware/toolchain:linux_amd64_gnu.2.19",
    "@zig_sdk//libc_aware/toolchain:linux_amd64_gnu.2.28",
    "@zig_sdk//libc_aware/toolchain:linux_amd64_gnu.2.31",
    "@zig_sdk//libc_aware/toolchain:linux_amd64_musl",
    # arm64 toolchains for libc-aware platforms:
    "@zig_sdk//libc_aware/toolchain:linux_arm64_gnu.2.28",
    "@zig_sdk//libc_aware/toolchain:linux_arm64_musl",
)

D bin/mod-tidy => bin/mod-tidy +0 -12
@@ 1,12 0,0 @@
#!/bin/sh

# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0
set -eu

cd "$(git rev-parse --show-toplevel)/"
bazel run @go_sdk//:bin/go -- mod tidy
exec bazel run //:gazelle -- update-repos \
    -from_file=go.mod \
    -prune \
    -to_macro=repositories.bzl%go_repositories

D ci/launcher => ci/launcher +0 -31
@@ 1,31 0,0 @@
#!/usr/bin/env bash

# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

set -xeuo pipefail

ZIG=${ZIG:-$(bazel run "$@" --run_under=echo @zig_sdk//:zig)}

for target in \
    aarch64-linux-gnu.2.19 \
    aarch64-macos-none \
    x86_64-linux-gnu.2.19 \
    x86_64-macos-none \
    x86_64-windows-gnu
do
    $ZIG build-exe -fno-emit-bin -target $target toolchain/launcher.zig
done

$ZIG fmt --check toolchain/launcher.zig

# until bazel-zig-cc gets a zig toolchain, run launcher's unit tests here.
$ZIG test toolchain/launcher.zig

# ReleaseSafe because of https://github.com/ziglang/zig/issues/14036
$ZIG test \
    -OReleaseSafe \
    -target x86_64-windows-gnu \
    --test-cmd wine64-stable \
    --test-cmd-bin \
    toolchain/launcher.zig

D ci/lint => ci/lint +0 -34
@@ 1,34 0,0 @@
#!/usr/bin/env bash

# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

set -euo pipefail

cd "$(git rev-parse --show-toplevel)"

mapfile -t files < \
    <(git ls-files)
mapfile -t scripts < \
    <(awk '/#!(\/usr\/bin\/env bash|\/bin\/sh)/&&FNR==1{print FILENAME}' "${files[@]}")
mapfile -t buildfiles < \
    <(find . \( -name 'WORKSPACE' -o -name 'BUILD' -o -name '*.bzl' \))

>&2 echo "shellcheck"
for f in "${scripts[@]}"; do >&2 echo "  $f"; done
shellcheck "${scripts[@]}"
>&2 echo -e "OK\n"

>&2 echo "buildifier -mode diff"
for f in "${buildfiles[@]}"; do >&2 echo "  $f"; done
fail=0
out=$(buildifier -mode diff -diff_command='diff -u' "${buildfiles[@]}") || fail=1
if [[ "$fail" == 1 ]]; then
    >&2 echo "ERROR: buildifier:"
    echo "$out"
    >&2 echo
    >&2 echo "You may try running:"
    >&2 echo "  buildifier ${buildfiles[*]}"
    exit 1
fi
>&2 echo -e "OK\n"

D ci/list_toolchains_platforms => ci/list_toolchains_platforms +0 -19
@@ 1,19 0,0 @@
#!/usr/bin/env bash

# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

set -euo pipefail

indent() { sed 's/^/    /'; }

echo "Available toolchains:"
bazel query --noshow_progress '@zig_sdk//toolchain:*' | indent
echo "Available platforms:"
bazel query --noshow_progress '@zig_sdk//platform:*' | indent
echo "Available libc aware toolchains:"
bazel query --noshow_progress '@zig_sdk//libc_aware/toolchain:*' | indent
echo "Available libc aware platforms:"
bazel query --noshow_progress '@zig_sdk//libc_aware/platform:*' | indent
echo "Available libc variants:"
bazel query --noshow_progress "attr(constraint_setting, @zig_sdk//libc:variant, @zig_sdk//...)" | indent

D ci/release => ci/release +0 -24
@@ 1,24 0,0 @@
#!/usr/bin/env bash

# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

set -xeuo pipefail

cd "$(git rev-parse --show-toplevel)"
prev_ref=$(git rev-parse HEAD)
git commit --allow-empty -m "this is a test commit"
./release --nosign v99.0
cleanup() { git tag -d v99.0; git reset --hard "$prev_ref"; }
trap cleanup EXIT

want=" 1 file changed, 2 insertions(+), 2 deletions(-)"
got=$(git show --shortstat HEAD | tail -1)

if [[ "$want" != "$got" ]]; then
    echo wanted:
    echo \ \ "$want"
    echo got:
    echo \ \ "$got"
    exit 1
fi

D ci/test => ci/test +0 -32
@@ 1,32 0,0 @@
#!/usr/bin/env bash

# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

set -xeuo pipefail

BAZEL_ZIG_CC_CACHE_PREFIX=${BAZEL_ZIG_CC_CACHE_PREFIX:-/tmp/bazel-zig-cc}
mkdir -p "${BAZEL_ZIG_CC_CACHE_PREFIX}"

# check a very hermetic setup with a single target. Re-building all of
# them takes a long time, so using only one. If we ever decide to build all
# targets, we will need to exclude Go, since go dynamically links to glibc on
# linux.
bazel build "$@" \
    --experimental_use_hermetic_linux_sandbox \
    --sandbox_add_mount_pair=/proc \
    //test/c:which_libc_linux_amd64_gnu.2.19

# then test everything else with the standard sandbox
bazel test "$@" ...

# Ensure that github.com/ziglang/zig/issues/13050 does not regress
find "$BAZEL_ZIG_CC_CACHE_PREFIX" -name mutex_destructor.o -execdir file '{}' \; | \
    sort | uniq -c | sort -rn > /tmp/got_cache

diff -u ci/testdata/want_cache /tmp/got_cache || {
    >&2 echo "ERROR: unexpected artifacts. This is TODO."
    # TODO: Go 1.20 regressed this. Find a way to re-enable. See README.
    #exit 1
    exit 0
}

D ci/testdata/want_cache => ci/testdata/want_cache +0 -3
@@ 1,3 0,0 @@
      5 ./mutex_destructor.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
      3 ./mutex_destructor.o: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), not stripped
      1 ./mutex_destructor.o: Mach-O 64-bit x86_64 object, flags:<|SUBSECTIONS_VIA_SYMBOLS>

D contrib/README-patched.md => contrib/README-patched.md +0 -33
@@ 1,33 0,0 @@
Patched Zig release
-------------------

This file explains how this zig version was created. As of first writing,
0.10.0-dev.4301+uber1 + https://github.com/ziglang/zig-bootstrap/pull/131

Steps to re-create a patched zig:

```
$ wget https://github.com/ziglang/zig/pull/13051.patch
$ git clone https://github.com/ziglang/zig-bootstrap; cd zig-bootstrap

# if https://github.com/ziglang/zig-bootstrap/pull/131 is not merged: patch it.

$ git am --directory=zig ../13051.patch

$ ${EDITOR:-vi} build  # replace the hash with "+uber1" and bump the last number in ZIG_VERSION

$ ./build-and-archive
```

Recent zig-bootstrap versions require cmake >= 3.19, which is available from
ubuntu 22.04 (jammy) or debian 12 (bookworm). Otherwise CMAKE will unable to
"Find the C compiler". A workaround:

    docker run --privileged -v `pwd`:/x -w /x -ti --rm buildpack-deps:bookworm \
        sh -c 'apt-get update && apt-get install -y cmake && exec ./build-and-archive'

(`--privileged` is necessary because of devpod nuances. You can skip it if you
don't know what is a devpod.)

`build-and-archive`, this file and 13051.patch should be in the archive where
you got your patched zig.

D contrib/build-and-archive => contrib/build-and-archive +0 -57
@@ 1,57 0,0 @@
#!/usr/bin/env bash
set -xeuo pipefail

zig_version=$(awk -F'"' '/^ZIG_VERSION=/{print $2}' build)

./build -j"$(nproc)" x86_64-linux-musl baseline
./build -j"$(nproc)" x86_64-macos-none baseline
./build -j"$(nproc)" aarch64-linux-musl baseline
./build -j"$(nproc)" aarch64-macos-none apple_a14
./build -j"$(nproc)" x86_64-windows-gnu baseline

rm -fr zig-linux-x86_64-"${zig_version}"
rm -fr zig-macos-x86_64-"${zig_version}"
rm -fr zig-linux-aarch64-"${zig_version}"
rm -fr zig-macos-aarch64-"${zig_version}"
rm -fr zig-windows-x86_64-"${zig_version}"

cp -r out/zig-x86_64-linux-musl-baseline zig-linux-x86_64-"${zig_version}"
cp -r out/zig-x86_64-macos-none-baseline zig-macos-x86_64-"${zig_version}"
cp -r out/zig-aarch64-linux-musl-baseline zig-linux-aarch64-"${zig_version}"
cp -r out/zig-aarch64-macos-none-apple_a14 zig-macos-aarch64-"${zig_version}"
cp -r out/zig-x86_64-windows-gnu-baseline zig-windows-x86_64-"${zig_version}"

for os_arch in linux-{x86_64,aarch64} macos-{x86_64,aarch64} windows-x86_64; do
    if [[ $os_arch == windows-x86_64 ]]; then
        zig=zig.exe
    else
        zig=zig
    fi
    mv zig-${os_arch}-"${zig_version}"/{bin/${zig},}
    rmdir zig-${os_arch}-"${zig_version}"/bin

    # copy some clarifying files for future readers
    mkdir zig-${os_arch}-"${zig_version}"/patches
    cp README-patched.md zig-${os_arch}-"${zig_version}"/patches/README.md
    cp 13051.patch "$0" zig-${os_arch}-"${zig_version}"/patches/
done

# linux-x86_64, windows-x86_64 and macos-aarch64 have libs in lib/, rather than
# in lib/zig/.
for os_arch in linux-x86_64 windows-x86_64 macos-aarch64; do
    mv zig-${os_arch}-"${zig_version}"/{lib,lib2}
    mv zig-${os_arch}-"${zig_version}"/{lib2/zig,lib}
    rmdir zig-${os_arch}-"${zig_version}"/lib2
done

tar cJf zig-linux-x86_64-"${zig_version}".tar.xz zig-linux-x86_64-"${zig_version}"/ &
tar cJf zig-macos-x86_64-"${zig_version}".tar.xz zig-macos-x86_64-"${zig_version}"/ &
tar cJf zig-linux-aarch64-"${zig_version}".tar.xz zig-linux-aarch64-"${zig_version}"/ &
tar cJf zig-macos-aarch64-"${zig_version}".tar.xz zig-macos-aarch64-"${zig_version}"/ &

# 7z is the quickest, so blocking on it first. Then wait for tars to complete.
7z a zig-windows-x86_64-"${zig_version}".zip zig-windows-x86_64-"${zig_version}"/
wait %1 %2 %3 %4

sha256sum ./*.tar.xz ./*.zip
ls -l ./*.tar.xz ./*.zip

D contrib/makerel => contrib/makerel +0 -28
@@ 1,28 0,0 @@
#!/usr/bin/env bash
set -euo pipefail

zigdir=out/zig-x86_64-linux-musl-x86_64_v3

if [[ ! "$PWD" =~ /zig-bootstrap$ ]]; then
    >&2 echo "expected to be in zig-bootstrap directory. Bailing"
    exit 1
fi


if [[ ! -f "$zigdir/bin/zig" ]]; then
    >&2 echo "$zigdir/bin/zig not found. Please run:"
    >&2 echo "    ./build -j\$(nproc) x86_64-linux-musl x86_64_v3"
    exit 1
fi

pushd "$zigdir"
    vsn=$(bin/zig version)
    outdir="zig-linux-x86_64-$vsn"
    mkdir -p "$outdir"
    cp -r "bin/zig" "$outdir"
    cp -r "lib/zig" "$outdir/lib"
    tar -cf "$outdir.tar" "$outdir"
    xz -vk -9 -T0 "$outdir.tar"
popd

echo "$zigdir/$outdir.tar.xz is ready for use"

D contrib/own_zig.md => contrib/own_zig.md +0 -39
@@ 1,39 0,0 @@
How to test a different version of zig
--------------------------------------

Assume you want to test an unreleased version of zig. Here's how:

1. Clone zig-bootstrap:

      $ git clone https://github.com/ziglang/zig-bootstrap
      $ cd zig-bootstrap

2. Copy over zig/ from ~/zig:

      $ rm -fr zig
      $ git -C ~/zig archive --format=tar --prefix=zig/ master | tar -xv

3. Build it (assuming `x86_64-linux`):

      $ vim build  # edit ZIG_VERSION
      $ ./build -j$(nproc) x86_64-linux-musl baseline

4. Pack the release tarball:

      $ ~/code/bazel-zig-cc/makerel

This gives us a usable Zig SDK. Now:

- Send the .tar.xz it to your mirror.
- Point toolchain/defs.bzl to the new version.
- Run tests.

Links
-----

- [ziglang/release-cutter][1], a script that creates binaries for [ziglang.org/download][2].
- [ziglang/zig-bootstrap][3], a set of scripts that compile a static Zig.

[1]: https://github.com/ziglang/release-cutter/blob/master/script
[2]: https://ziglang.org/download
[3]: https://github.com/ziglang/zig-bootstrap

D go.mod => go.mod +0 -3
@@ 1,3 0,0 @@
module git.sr.ht/~motiejus/bazel-zig-cc

go 1.18

D go.sum => go.sum +0 -0
D release => release +0 -38
@@ 1,38 0,0 @@
#!/usr/bin/env bash

# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

set -xeuo pipefail

sign=(-u motiejus@jakstys.lt)
[[ $1 == "--nosign" ]] && { sign=(); shift; }

_err(){ >&2 echo "ERROR: $*"; exit 1; }

git status --porcelain | grep -q "" &&
    _err "working tree is dirty, commit your changes first."

[[ "$1" =~ ^v([0-9]+)\.([0-9]+)(\.([0-9]+))?(-rc([0-9]+))?$ ]] || \
    _err "arg1 accepts the following formats: v1.0 v1.0.0 v1.0-rc1 v1.0.1-rc1"

git tag | grep -q "^$1$" &&
    _err "tag $1 already exists"

last_tag=$(git -c 'versionsort.suffix=-' tag -l --sort=v:refname | tail -1)

{
    echo bazel-zig-cc "$1"
    echo
    echo Changelog since "$last_tag":
    git log --pretty=format:"- [%an] %s" "$last_tag"..HEAD | \
        grep -v "Update release notes for $last_tag"
} | git tag "${sign[@]}" -F - "$1"

shasum=$(git archive --prefix="bazel-zig-cc-$1/" --format=tar "$1" | \
    gzip -n | sha256sum | cut -f1 -d" ")

./relnotes.awk -v tag="$1" -v sha256sum="$shasum" README.md | sponge README.md

git add README.md
git commit -m "Update release notes for $1"

D relnotes.awk => relnotes.awk +0 -37
@@ 1,37 0,0 @@
#!/usr/bin/awk -f

# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

BEGIN {stage=0};

!/```/ && stage==0 {
    print
}

/```/ && stage==0 {
    print "```"
    print "BAZEL_ZIG_CC_VERSION = \""tag"\""
    print ""
    print "http_archive("
    print "    name = \"bazel-zig-cc\","
    print "    sha256 = \""sha256sum"\","
    print "    strip_prefix = \"bazel-zig-cc-{}\".format(BAZEL_ZIG_CC_VERSION),"
    print "    urls = [\"https://git.sr.ht/~motiejus/bazel-zig-cc/archive/{}.tar.gz\".format(BAZEL_ZIG_CC_VERSION)],"
    print ")"
    stage=1
    next
}

!/^)$/ && stage==1 {
    next
};

/^)$/ && stage==1 {
    stage=2
    next
};

stage==2 {
    print;
};

D repositories.bzl => repositories.bzl +0 -5
@@ 1,5 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

def go_repositories():
    pass

D rules/BUILD => rules/BUILD +0 -4
@@ 1,4 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

package(default_visibility = ["//test:__pkg__"])

D rules/platform.bzl => rules/platform.bzl +0 -78
@@ 1,78 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

def _vars_script(env, run_under, cmd):
    ret = ["#!/bin/sh"]
    for k, v in env.items():
        ret += ['export {}="{}"'.format(k, v)]
    ret += ['exec {} {} "$@"'.format(run_under, cmd)]
    return "\n".join(ret) + "\n"  # trailing newline is easier on the eyes

def _platform_transition_impl(settings, attr):
    _ignore = settings
    return {
        "//command_line_option:platforms": "@zig_sdk{}".format(attr.platform),
    }

_platform_transition = transition(
    implementation = _platform_transition_impl,
    inputs = [],
    outputs = [
        "//command_line_option:platforms",
    ],
)

def _platform_binary_impl(ctx):
    source_info = ctx.attr.src[DefaultInfo]

    executable = None
    if source_info.files_to_run and source_info.files_to_run.executable:
        command = _vars_script(ctx.attr.env, ctx.attr.run_under, source_info.files_to_run.executable.short_path)
        executable = ctx.actions.declare_file("{}_{}".format(ctx.file.src.basename, ctx.attr.platform))
        ctx.actions.write(
            output = executable,
            content = command,
            is_executable = True,
        )

    return [DefaultInfo(
        executable = executable,
        files = depset([executable]),
        runfiles = ctx.runfiles(files = ctx.files.src),
    )]

_attrs = {
    "src": attr.label(
        allow_single_file = True,
        mandatory = True,
        doc = "Target to build.",
    ),
    "platform": attr.string(
        doc = "The platform to build the target for.",
    ),
    "run_under": attr.string(
        doc = "wrapper executable",
    ),
    "env": attr.string_dict(
        doc = "Environment variables for the test",
    ),
    "_allowlist_function_transition": attr.label(
        default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
    ),
}

# wrap a single exectable and build it for the specified platform.
platform_binary = rule(
    implementation = _platform_binary_impl,
    cfg = _platform_transition,
    attrs = _attrs,
    executable = True,
)

# wrap a single test target and build it for the specified platform.
platform_test = rule(
    implementation = _platform_binary_impl,
    cfg = _platform_transition,
    attrs = _attrs,
    test = True,
)

D rules/rules_go.bzl => rules/rules_go.bzl +0 -24
@@ 1,24 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

load("@io_bazel_rules_go//go:def.bzl", go_binary_rule = "go_binary")

"""
go_binary overrides go_binary from rules_go and provides default
gc_linkopts values that are needed to compile for macos target.
To use it, add this map_kind gazelle directive to your BUILD.bazel files
where target binary needs to be compiled with zig toolchain.

Example: if this toolchain is registered as bazel-zig-cc in your WORKSPACE, add this to
your root BUILD file
# gazelle:map_kind go_binary go_binary @bazel-zig-cc//rules:rules_go.bzl
"""

_MACOS_GC_LINKOPTS = ["-s", "-w", "-buildmode=pie"]

def go_binary(**kwargs):
    kwargs["gc_linkopts"] = select({
        "@platforms//os:macos": _MACOS_GC_LINKOPTS,
        "//conditions:default": [],
    }) + kwargs.pop("gc_linkopts", [])
    go_binary_rule(**kwargs)

D test/c/BUILD => test/c/BUILD +0 -38
@@ 1,38 0,0 @@
load("@bazel-zig-cc//rules:platform.bzl", "platform_binary")

cc_binary(
    name = "which_libc",
    srcs = ["main.c"],
    target_compatible_with = [
        "@platforms//os:linux",
    ],
)

[
    (
        platform_binary(
            name = "which_libc_{}".format(name),
            src = "which_libc",
            env = {"QEMU_LD_PREFIX": "/usr/aarch64-linux-gnu"} if "arm64" in name else {},
            platform = platform,
            run_under = "qemu-aarch64-static" if "arm64" in name else "",
        ),
        sh_test(
            name = "test_libc_{}".format(name),
            srcs = ["test.sh"],
            data = ["which_libc_{}".format(name)],
            env = {
                "WANT": want,
                "BINARY": "$(location which_libc_{})".format(name),
            },
        ),
    )
    for name, platform, want in [
        ("linux_amd64_musl", "//libc_aware/platform:linux_amd64_musl", "non-glibc"),
        ("linux_amd64_gnu.2.19", "//libc_aware/platform:linux_amd64_gnu.2.19", "glibc_2.19"),
        ("linux_amd64_gnu.2.28", "//libc_aware/platform:linux_amd64_gnu.2.28", "glibc_2.28"),
        ("linux_arm64_musl", "//libc_aware/platform:linux_arm64_musl", "non-glibc"),
        ("linux_amd64", "//platform:linux_amd64", "glibc_2.19"),
        ("linux_arm64", "//platform:linux_arm64", "glibc_2.28"),
    ]
]

D test/c/main.c => test/c/main.c +0 -14
@@ 1,14 0,0 @@
// Copyright 2023 Uber Technologies, Inc.
// Licensed under the Apache License, Version 2.0

#include <stdio.h>
#include <features.h>

int main() {
    #ifdef __GLIBC__
    printf("glibc_%d.%d\n", __GLIBC__, __GLIBC_MINOR__);
    #else
    printf("non-glibc\n");
    #endif
    return 0;
}

D test/c/test.sh => test/c/test.sh +0 -20
@@ 1,20 0,0 @@
#!/bin/sh

# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

set -eu

# shellcheck disable=SC2153
want=$WANT
# shellcheck disable=SC2153
binary=$BINARY
got=$($binary)

if [ "$got" != "$want" ]; then
    echo wanted:
    echo \ \ "$want"
    echo got:
    echo \ \ "$got"
    exit 1
fi

D test/cgo/BUILD => test/cgo/BUILD +0 -57
@@ 1,57 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
load("//rules:rules_go.bzl", "go_binary")
load("@bazel-zig-cc//rules:platform.bzl", "platform_binary", "platform_test")

go_library(
    name = "cgo_lib",
    srcs = ["cgo.go"],
    cgo = True,
    importpath = "git.sr.ht/~motiejus/bazel-zig-cc/test/cgo",
    visibility = ["//visibility:private"],
)

go_test(
    name = "cgo_test",
    srcs = ["cgo_test.go"],
    embed = [":cgo_lib"],
)

go_binary(
    name = "cgo",
    embed = [":cgo_lib"],
    visibility = ["//visibility:public"],
)

[
    platform_binary(
        name = "cgo_{}".format(name),
        src = "cgo",
        platform = platform,
    )
    for name, platform in [
        ("linux_amd64_musl", "//libc_aware/platform:linux_amd64_musl"),
        ("linux_amd64_gnu.2.19", "//libc_aware/platform:linux_amd64_gnu.2.19"),
        ("linux_arm64_musl", "//libc_aware/platform:linux_arm64_musl"),
        ("linux_arm64_gnu.2.28", "//libc_aware/platform:linux_arm64_gnu.2.28"),
        ("darwin_amd64", "//platform:darwin_amd64"),
    ]
]

[
    platform_test(
        name = "cgo_test_{}".format(name),
        src = "cgo_test",
        env = {"QEMU_LD_PREFIX": "/usr/aarch64-linux-gnu"} if is_arm64 else {},
        platform = platform,
        run_under = "qemu-aarch64-static" if is_arm64 else "",
    )
    for name, platform, is_arm64 in [
        ("linux_amd64_musl", "//libc_aware/platform:linux_amd64_musl", False),
        ("linux_amd64_gnu.2.19", "//libc_aware/platform:linux_amd64_gnu.2.19", False),
        ("linux_arm64_musl", "//libc_aware/platform:linux_arm64_musl", True),
        ("linux_arm64_gnu.2.28", "//libc_aware/platform:linux_arm64_gnu.2.28", True),
    ]
]

D test/cgo/cgo.go => test/cgo/cgo.go +0 -17
@@ 1,17 0,0 @@
// Copyright 2023 Uber Technologies, Inc.
// Licensed under the Apache License, Version 2.0

package main

// #include <stdio.h>
// char* hello() { return "hello, world"; }
// void phello() { printf("%s\n", hello()); }
import "C"

func main() {
	C.phello()
}

func Chello() string {
	return C.GoString(C.hello())
}

D test/cgo/cgo_test.go => test/cgo/cgo_test.go +0 -16
@@ 1,16 0,0 @@
// Copyright 2023 Uber Technologies, Inc.
// Licensed under the Apache License, Version 2.0

package main

import (
	"testing"
)

func TestHello(t *testing.T) {
	want := "hello, world"
	got := Chello()
	if got != want {
		t.Errorf("expected %q, got %q", want, got)
	}
}

D test/glibc_hacks/BUILD => test/glibc_hacks/BUILD +0 -25
@@ 1,25 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

load("@bazel-zig-cc//rules:platform.bzl", "platform_binary")

cc_binary(
    name = "main",
    srcs = ["main.c"],
)

[
    (
        platform_binary(
            name = "main_{}".format(name),
            src = "main",
            platform = platform,
        ),
    )
    for name, platform in [
        ("linux_amd64_musl", "//libc_aware/platform:linux_amd64_musl"),
        ("linux_amd64_gnu.2.19", "//libc_aware/platform:linux_amd64_gnu.2.19"),
        ("linux_amd64_gnu.2.28", "//libc_aware/platform:linux_amd64_gnu.2.28"),
        ("linux_arm64_musl", "//libc_aware/platform:linux_arm64_musl"),
    ]
]

D test/glibc_hacks/main.c => test/glibc_hacks/main.c +0 -16
@@ 1,16 0,0 @@
// Copyright 2023 Uber Technologies, Inc.
// Licensed under the Apache License, Version 2.0

// This file tests that problematic functions (glibc-hacks) work.
// Also see https://github.com/ziglang/zig/issues/9485

#define _FILE_OFFSET_BITS 64
#include <unistd.h>
#include <fcntl.h>
#include <resolv.h>
#include <stdio.h>

int main() {
    printf("Your lucky numbers are %p and %p\n", fcntl, res_search);
    return 0;
}

D test/gorace/BUILD => test/gorace/BUILD +0 -27
@@ 1,27 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
load("//rules:rules_go.bzl", "go_binary")

go_library(
    name = "gorace_lib",
    srcs = ["main.go"],
    # keep
    cgo = True,
    importpath = "git.sr.ht/~motiejus/bazel-zig-cc/test/gorace",
    visibility = ["//visibility:private"],
)

go_binary(
    name = "gorace",
    embed = [":gorace_lib"],
    visibility = ["//visibility:public"],
)

go_test(
    name = "gorace_test",
    srcs = ["main_test.go"],
    embed = [":gorace_lib"],
    race = "on",
)

D test/gorace/main.go => test/gorace/main.go +0 -16
@@ 1,16 0,0 @@
// Copyright 2023 Uber Technologies, Inc.
// Licensed under the Apache License, Version 2.0
//
// Package main tests that Zig can compile race-enabled tests.
//
// As of writing, this fails:
//   CGO_ENABLED=1 CC="zig cc" go test -race .
//
// More context: https://github.com/ziglang/zig/issues/11398
//
// This fails, because `zig cc` adds `--gc-sections` to the linker
// flag by default, which is incompatible with cgo. bazel-zig-cc
// adds a workaround for it.
package main

func main() {}

D test/gorace/main_test.go => test/gorace/main_test.go +0 -11
@@ 1,11 0,0 @@
// Copyright 2023 Uber Technologies, Inc.
// Licensed under the Apache License, Version 2.0

package main

import "testing"

func TestFoo(t *testing.T) {
	_ = t
	main()
}

D test/windows/BUILD => test/windows/BUILD +0 -24
@@ 1,24 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

load("@bazel-zig-cc//rules:platform.bzl", "platform_binary", "platform_test")

cc_binary(
    name = "winver",
    srcs = ["main.c"],
    tags = ["manual"],
)

platform_test(
    name = "winver_windows_amd64",
    src = "winver",
    platform = "//platform:windows_amd64",
    run_under = "wine64-stable",
    tags = ["no-sandbox"],
)

platform_binary(
    name = "winver_windows_arm64",
    src = "winver",
    platform = "//platform:windows_arm64",
)

D test/windows/main.c => test/windows/main.c +0 -19
@@ 1,19 0,0 @@
// Copyright 2023 Uber Technologies, Inc.
// Licensed under the Apache License, Version 2.0

#include <stdio.h>
#include <windows.h>

int main() {
    DWORD version = GetVersion();
    DWORD majorVersion = (DWORD)(LOBYTE(LOWORD(version)));
    DWORD minorVersion = (DWORD)(HIBYTE(LOWORD(version)));

    DWORD build = 0;
    if (version < 0x80000000) {
        build = (DWORD)(HIWORD(version));
    }

    printf("Running Windows version %d.%d (%d).\n", majorVersion, minorVersion, build);
    return 0;
}

D toolchain/BUILD => toolchain/BUILD +0 -0
D toolchain/BUILD.sdk.bazel => toolchain/BUILD.sdk.bazel +0 -25
@@ 1,25 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

load("@bazel-zig-cc//toolchain:defs.bzl", "declare_files")
load("@bazel-zig-cc//toolchain/private:cc_toolchains.bzl", "declare_cc_toolchains")

package(
    default_visibility = ["//visibility:public"],
)

declare_files(
    os = {os},
)

exports_files([
    "glibc-hacks/fcntl.map",
    "glibc-hacks/fcntl.h",
    "glibc-hacks/res_search.map",
    "glibc-hacks/res_search.h",
])

declare_cc_toolchains(
    os = {os},
    zig_sdk_path = {zig_sdk_path},
)

D toolchain/defs.bzl => toolchain/defs.bzl +0 -311
@@ 1,311 0,0 @@
load("@bazel_skylib//lib:paths.bzl", "paths")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "read_user_netrc", "use_netrc")
load("@bazel-zig-cc//toolchain/private:defs.bzl", "target_structs", "zig_tool_path")

# Directories that `zig c++` includes behind the scenes.
_DEFAULT_INCLUDE_DIRECTORIES = [
    "libcxx/include",
    "libcxxabi/include",
    "libunwind/include",
]

# Official recommended version. Should use this when we have a usable release.
URL_FORMAT_RELEASE = "https://ziglang.org/download/{version}/zig-{host_platform}-{version}.{_ext}"

# Caution: nightly releases are purged from ziglang.org after ~90 days. A real
# solution would be to allow the downstream project specify their own mirrors.
# This is explained in
# https://sr.ht/~motiejus/bazel-zig-cc/#alternative-download-urls and is
# awaiting my attention or your contribution.
URL_FORMAT_NIGHTLY = "https://ziglang.org/builds/zig-{host_platform}-{version}.{_ext}"

# Official Bazel's mirror with selected Zig SDK versions. Bazel community is
# generous enough to host the artifacts, which we use.
URL_FORMAT_BAZELMIRROR = "https://mirror.bazel.build/" + URL_FORMAT_NIGHTLY.lstrip("https://")

_VERSION = "0.11.0-dev.1796+c9e02d3e6"

_HOST_PLATFORM_SHA256 = {
    "linux-aarch64": "5902b34b463635b25c11555650d095eb5030e2a05d8a4570c091313cd1a38b12",
    "linux-x86_64": "aa9da2305fad89f648db2fd1fade9f0f9daf01d06f3b07887ad3098402794778",
    "macos-aarch64": "51b4e88123d6cbb102f2a6665dd0d61467341f36b07bb0a8d46a37ea367b60d5",
    "macos-x86_64": "dd8eeae5249aa21f9e51ff4ff536a3e7bf2c0686ee78bf6032d18e74c8416c56",
    "windows-x86_64": "260f34d0d5312d2642097bb33c14ac552cd57c59a15383364df6764d01f0bfc9",
}

_HOST_PLATFORM_EXT = {
    "linux-aarch64": "tar.xz",
    "linux-x86_64": "tar.xz",
    "macos-aarch64": "tar.xz",
    "macos-x86_64": "tar.xz",
    "windows-x86_64": "zip",
}

def toolchains(
        version = _VERSION,
        url_formats = [URL_FORMAT_BAZELMIRROR, URL_FORMAT_NIGHTLY],
        host_platform_sha256 = _HOST_PLATFORM_SHA256,
        host_platform_ext = _HOST_PLATFORM_EXT):
    """
        Download zig toolchain and declare bazel toolchains.
        The platforms are not registered automatically, that should be done by
        the user with register_toolchains() in the WORKSPACE file. See README
        for possible choices.
    """
    zig_repository(
        name = "zig_sdk",
        version = version,
        url_formats = url_formats,
        host_platform_sha256 = host_platform_sha256,
        host_platform_ext = host_platform_ext,
    )

_ZIG_TOOLS = [
    "c++",
    "ar",
]

_template_mapfile = """
%s {
    %s;
};
"""

_template_linker = """
#ifdef __ASSEMBLER__
.symver {from_function}, {to_function_abi}
#else
__asm__(".symver {from_function}, {to_function_abi}");
#endif
"""

def _glibc_hack(from_function, to_function_abi):
    # Cannot use .format(...) here, because starlark thinks
    # that the byte 3 (the opening brace on the first line)
    # is a nested { ... }, returning an error:
    # Error in format: Nested replacement fields are not supported
    to_function, to_abi = to_function_abi.split("@")
    mapfile = _template_mapfile % (to_abi, to_function)
    header = _template_linker.format(
        from_function = from_function,
        to_function_abi = to_function_abi,
    )
    return struct(
        mapfile = mapfile,
        header = header,
    )

def _quote(s):
    return "'" + s.replace("'", "'\\''") + "'"

def _zig_repository_impl(repository_ctx):
    arch = repository_ctx.os.arch
    if arch == "amd64":
        arch = "x86_64"

    os = repository_ctx.os.name.lower()
    if os.startswith("mac os"):
        os = "macos"

    if os.startswith("windows"):
        os = "windows"

    host_platform = "{}-{}".format(os, arch)

    zig_sha256 = repository_ctx.attr.host_platform_sha256[host_platform]
    zig_ext = repository_ctx.attr.host_platform_ext[host_platform]
    format_vars = {
        "_ext": zig_ext,
        "version": repository_ctx.attr.version,
        "host_platform": host_platform,
    }

    # Fetch Label dependencies before doing download/extract.
    # The Bazel docs are not very clear about this behavior but see:
    # https://bazel.build/extending/repo#when_is_the_implementation_function_executed
    # and a related rules_go PR:
    # https://github.com/bazelbuild/bazel-gazelle/pull/1206
    for dest, src in {
        "platform/BUILD": "//toolchain/platform:BUILD",
        "toolchain/BUILD": "//toolchain/toolchain:BUILD",
        "libc/BUILD": "//toolchain/libc:BUILD",
        "libc_aware/platform/BUILD": "//toolchain/libc_aware/platform:BUILD",
        "libc_aware/toolchain/BUILD": "//toolchain/libc_aware/toolchain:BUILD",
    }.items():
        repository_ctx.symlink(Label(src), dest)

    for dest, src in {
        "BUILD": "//toolchain:BUILD.sdk.bazel",
        # "private/BUILD": "//toolchain/private:BUILD.sdk.bazel",
    }.items():
        repository_ctx.template(
            dest,
            Label(src),
            executable = False,
            substitutions = {
                "{zig_sdk_path}": _quote("external/zig_sdk"),
                "{os}": _quote(os),
            },
        )

    urls = [uf.format(**format_vars) for uf in repository_ctx.attr.url_formats]
    repository_ctx.download_and_extract(
        auth = use_netrc(read_user_netrc(repository_ctx), urls, {}),
        url = urls,
        stripPrefix = "zig-{host_platform}-{version}/".format(**format_vars),
        sha256 = zig_sha256,
    )

    cache_prefix = repository_ctx.os.environ.get("BAZEL_ZIG_CC_CACHE_PREFIX", "")
    if cache_prefix == "":
        if os == "windows":
            cache_prefix = "C:\\\\Temp\\\\bazel-zig-cc"
        else:
            cache_prefix = "/tmp/bazel-zig-cc"

    repository_ctx.template(
        "tools/launcher.zig",
        Label("//toolchain:launcher.zig"),
        executable = False,
        substitutions = {
            "{BAZEL_ZIG_CC_CACHE_PREFIX}": cache_prefix,
        },
    )

    ret = repository_ctx.execute(
        [
            paths.join("..", "zig"),
            "build-exe",
            "-OReleaseSafe",
            "launcher.zig",
        ] + (["-static"] if os == "linux" else []),
        working_directory = "tools",
        environment = {
            "ZIG_LOCAL_CACHE_DIR": cache_prefix,
            "ZIG_GLOBAL_CACHE_DIR": cache_prefix,
        },
    )
    if ret.return_code != 0:
        fail("compilation failed:\nreturn_code={}\nstderr={}\nstdout={}".format(
            ret.return_code,
            ret.stdout,
            ret.stderr,
        ))

    exe = ".exe" if os == "windows" else ""
    for target_config in target_structs():
        for zig_tool in _ZIG_TOOLS + target_config.tool_paths.values():
            tool_path = zig_tool_path(os).format(
                zig_tool = zig_tool,
                zigtarget = target_config.zigtarget,
            )
            repository_ctx.symlink("tools/launcher{}".format(exe), tool_path)

    fcntl_hack = _glibc_hack("fcntl64", "fcntl@GLIBC_2.2.5")
    repository_ctx.file("glibc-hacks/fcntl.map", content = fcntl_hack.mapfile)
    repository_ctx.file("glibc-hacks/fcntl.h", content = fcntl_hack.header)
    res_search_amd64 = _glibc_hack("res_search", "__res_search@GLIBC_2.2.5")
    repository_ctx.file("glibc-hacks/res_search-amd64.map", content = res_search_amd64.mapfile)
    repository_ctx.file("glibc-hacks/res_search-amd64.h", content = res_search_amd64.header)
    res_search_arm64 = _glibc_hack("res_search", "__res_search@GLIBC_2.17")
    repository_ctx.file("glibc-hacks/res_search-arm64.map", content = res_search_arm64.mapfile)
    repository_ctx.file("glibc-hacks/res_search-arm64.h", content = res_search_arm64.header)

zig_repository = repository_rule(
    attrs = {
        "version": attr.string(),
        "host_platform_sha256": attr.string_dict(),
        "url_formats": attr.string_list(allow_empty = False),
        "host_platform_ext": attr.string_dict(),
    },
    environ = ["BAZEL_ZIG_CC_CACHE_PREFIX"],
    implementation = _zig_repository_impl,
)

def filegroup(name, **kwargs):
    native.filegroup(name = name, **kwargs)
    return ":" + name

def declare_files(os):
    filegroup(name = "all", srcs = native.glob(["**"]))
    filegroup(name = "empty")
    if os == "windows":
        native.exports_files(["zig.exe"], visibility = ["//visibility:public"])
        native.alias(name = "zig", actual = ":zig.exe")
    else:
        native.exports_files(["zig"], visibility = ["//visibility:public"])
    filegroup(name = "lib/std", srcs = native.glob(["lib/std/**"]))
    lazy_filegroups = {}

    for target_config in target_structs():
        all_includes = [native.glob(["lib/{}/**".format(i)]) for i in target_config.includes]
        all_includes.append(getattr(target_config, "compiler_extra_includes", []))

        cxx_tool_label = ":" + zig_tool_path(os).format(
            zig_tool = "c++",
            zigtarget = target_config.zigtarget,
        )

        filegroup(
            name = "{}_includes".format(target_config.zigtarget),
            srcs = _flatten(all_includes),
        )

        filegroup(
            name = "{}_compiler_files".format(target_config.zigtarget),
            srcs = [
                ":zig",
                ":{}_includes".format(target_config.zigtarget),
                cxx_tool_label,
            ],
        )

        filegroup(
            name = "{}_linker_files".format(target_config.zigtarget),
            srcs = [
                ":zig",
                ":{}_includes".format(target_config.zigtarget),
                cxx_tool_label,
            ] + native.glob([
                "lib/libc/{}/**".format(target_config.libc),
                "lib/libcxx/**",
                "lib/libcxxabi/**",
                "lib/libunwind/**",
                "lib/compiler_rt/**",
                "lib/std/**",
                "lib/*.zig",
                "lib/*.h",
            ]),
        )

        filegroup(
            name = "{}_ar_files".format(target_config.zigtarget),
            srcs = [
                ":zig",
                ":" + zig_tool_path(os).format(
                    zig_tool = "ar",
                    zigtarget = target_config.zigtarget,
                ),
            ],
        )

        filegroup(
            name = "{}_all_files".format(target_config.zigtarget),
            srcs = [
                ":{}_linker_files".format(target_config.zigtarget),
                ":{}_compiler_files".format(target_config.zigtarget),
                ":{}_ar_files".format(target_config.zigtarget),
            ],
        )

        for d in _DEFAULT_INCLUDE_DIRECTORIES + target_config.includes:
            d = "lib/" + d
            if d not in lazy_filegroups:
                lazy_filegroups[d] = filegroup(name = d, srcs = native.glob([d + "/**"]))

def _flatten(iterable):
    result = []
    for element in iterable:
        result += element
    return result

D toolchain/launcher.zig => toolchain/launcher.zig +0 -436
@@ 1,436 0,0 @@
// Copyright 2023 Uber Technologies, Inc.
// Licensed under the Apache License, Version 2.0
//
// A wrapper for `zig` subcommands.
//
// In simple cases it is usually enough to:
//
//      zig c++ -target <triple> <...>
//
// However, there are some caveats:
//
// * Sometimes toolchains (looking at you, Go, see an example in
// https://github.com/golang/go/pull/55966) skip CFLAGS to the underlying
// compiler. Doing that may carry a huge cost, because zig may need to spend
// ~30s compiling libc++ for an innocent feature test. Having an executable per
// target platform (like GCC does things, e.g. aarch64-linux-gnu-<tool>) is
// what most toolchains are designed to work with. So we need a wrapper per
// zig sub-command per target. As of writing, the layout is:
//   tools/
//   ├── x86_64-linux-gnu.2.34
//   │   ├── ar
//   │   ├── c++
//   │   └── ld.lld
//   ├── x86_64-linux-musl
//   │   ├── ar
//   │   ├── c++
//   │   └── ld.lld
//   ├── x86_64-macos-none
//   │   ├── ar
//   │   ├── c++
//   │   └── ld64.lld
//   ...
// * ZIG_LIB_DIR controls the output of `zig c++ -MF -MD <...>`. Bazel uses
// command to understand which input files were used to the compilation. If any
// of the files are not in `external/<...>/`, Bazel will understand and
// complain that the compiler is using undeclared directories on the host file
// system. We do not declare prerequisites using absolute paths, because that
// busts Bazel's remote cache.
// * BAZEL_ZIG_CC_CACHE_PREFIX is configurable per toolchain instance, and
// ZIG_GLOBAL_CACHE_DIR and ZIG_LOCAL_CACHE_DIR must be set to its value for
// all `zig` invocations.
//
// Originally this was a Bash script, then a POSIX shell script, then two
// scripts (one with pre-defined BAZEL_ZIG_CC_CACHE_PREFIX, one without). Then
// Windows came along with two PowerShell scripts (ports of the POSIX shell
// scripts), which I kept breaking. Then Bazel 6 came with
// `--experimental_use_hermetic_linux_sandbox`, which hermetizes the sandbox to
// the extreme: the sandbox has nothing that is not declared. /bin/sh and its
// dependencies (/lib/x86_64-linux-gnu/libc.so.6 on my system) are obviously
// not declared. So one can either declare those dependencies, bundle a shell
// to execute the wrapper, or port the shell logic to a cross-platform program
// that compiles to a static binary. By a chance we happen to already ship a
// toolchain of a language that could compile such program. And behold, the
// program is below.

const builtin = @import("builtin");
const std = @import("std");
const fs = std.fs;
const mem = std.mem;
const process = std.process;
const ChildProcess = std.ChildProcess;
const ArrayListUnmanaged = std.ArrayListUnmanaged;
const sep = fs.path.sep_str;

const EXE = switch (builtin.target.os.tag) {
    .windows => ".exe",
    else => "",
};

const CACHE_DIR = "{BAZEL_ZIG_CC_CACHE_PREFIX}";

const usage_cpp =
    \\
    \\Usage: <...>/tools/<target-triple>/{[zig_tool]s}{[exe]s} <args>...
    \\
    \\Wraps the "zig" multi-call binary. It determines the target platform from
    \\the directory where it was called. Then sets ZIG_LIB_DIR,
    \\ZIG_GLOBAL_CACHE_DIR, ZIG_LOCAL_CACHE_DIR and then calls:
    \\
    \\  zig c++ -target <target-triple> <args>...
;

const usage_other =
    \\Usage: <...>/tools/<target-triple>/{[zig_tool]s}{[exe]s} <args>...
    \\
    \\Wraps the "zig" multi-call binary. It sets ZIG_LIB_DIR,
    \\ZIG_GLOBAL_CACHE_DIR, ZIG_LOCAL_CACHE_DIR, and then calls:
    \\
    \\  zig {[zig_tool]s} <args>...
;

const Action = enum {
    err,
    exec,
};

const ExecParams = struct {
    args: ArrayListUnmanaged([]const u8),
    env: process.EnvMap,
};

const ParseResults = union(Action) {
    err: []const u8,
    exec: ExecParams,
};

pub fn main() u8 {
    const allocator = if (builtin.link_libc)
        std.heap.c_allocator
    else blk: {
        var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        break :blk gpa.allocator();
    };
    var arena_allocator = std.heap.ArenaAllocator.init(allocator);
    defer arena_allocator.deinit();
    const arena = arena_allocator.allocator();

    var argv_it = process.argsWithAllocator(arena) catch |err|
        return fatal("error parsing args: {s}\n", .{@errorName(err)});

    const action = parseArgs(arena, fs.cwd(), &argv_it) catch |err|
        return fatal("error: {s}\n", .{@errorName(err)});

    switch (action) {
        .err => |msg| return fatal("{s}", .{msg}),
        .exec => |params| {
            if (builtin.os.tag == .windows)
                return spawnWindows(arena, params)
            else
                return execUnix(arena, params);
        },
    }
}

fn spawnWindows(arena: mem.Allocator, params: ExecParams) u8 {
    var proc = ChildProcess.init(params.args.items, arena);
    proc.env_map = &params.env;
    const ret = proc.spawnAndWait() catch |err|
        return fatal(
        "error spawning {s}: {s}\n",
        .{ params.args.items[0], @errorName(err) },
    );

    switch (ret) {
        .Exited => |code| return code,
        else => |other| return fatal("abnormal exit: {any}\n", .{other}),
    }
}

fn execUnix(arena: mem.Allocator, params: ExecParams) u8 {
    const err = process.execve(arena, params.args.items, &params.env);
    std.debug.print(
        "error execing {s}: {s}\n",
        .{ params.args.items[0], @errorName(err) },
    );
    return 1;
}

// argv_it is an object that has such method:
//     fn next(self: *Self) ?[]const u8
// in non-testing code it is *process.ArgIterator.
// Leaks memory: the name of the first argument is arena not by chance.
fn parseArgs(
    arena: mem.Allocator,
    cwd: fs.Dir,
    argv_it: anytype,
) error{OutOfMemory}!ParseResults {
    const arg0 = argv_it.next() orelse
        return parseFatal(arena, "error: argv[0] cannot be null", .{});

    const zig_tool = blk: {
        const b = fs.path.basename(arg0);
        if (builtin.target.os.tag == .windows and
            std.ascii.eqlIgnoreCase(".exe", b[b.len - 4 ..]))
            break :blk b[0 .. b.len - 4];

        break :blk b;
    };
    const maybe_target = getTarget(arg0) catch |err| switch (err) {
        error.BadParent => {
            const fmt_args = .{ .zig_tool = zig_tool, .exe = EXE };
            if (mem.eql(u8, zig_tool, "c++"))
                return parseFatal(arena, usage_cpp, fmt_args)
            else
                return parseFatal(arena, usage_other, fmt_args);
        },
        else => |e| return e,
    };

    const root = blk: {
        var dir = cwd.openDir(
            "external" ++ sep ++ "zig_sdk" ++ sep ++ "lib",
            .{ .access_sub_paths = false, .no_follow = true },
        );

        if (dir) |*dir_exists| {
            dir_exists.close();
            break :blk "external" ++ sep ++ "zig_sdk";
        } else |_| {}

        // directory does not exist or there was an error opening it
        const here = fs.path.dirname(arg0) orelse ".";
        break :blk try fs.path.join(arena, &[_][]const u8{ here, "..", ".." });
    };

    const zig_lib_dir = try fs.path.join(arena, &[_][]const u8{ root, "lib" });
    const zig_exe = try fs.path.join(
        arena,
        &[_][]const u8{ root, "zig" ++ EXE },
    );

    var env = process.getEnvMap(arena) catch |err|
        return parseFatal(arena, "error getting env: {s}", .{@errorName(err)});

    try env.put("ZIG_LIB_DIR", zig_lib_dir);
    try env.put("ZIG_LOCAL_CACHE_DIR", CACHE_DIR);
    try env.put("ZIG_GLOBAL_CACHE_DIR", CACHE_DIR);

    // args is the path to the zig binary and args to it.
    var args = ArrayListUnmanaged([]const u8){};
    try args.appendSlice(arena, &[_][]const u8{ zig_exe, zig_tool });
    if (maybe_target) |target|
        try args.appendSlice(arena, &[_][]const u8{ "-target", target });

    while (argv_it.next()) |arg|
        try args.append(arena, arg);

    return ParseResults{ .exec = .{ .args = args, .env = env } };
}

fn parseFatal(
    arena: mem.Allocator,
    comptime fmt: []const u8,
    args: anytype,
) error{OutOfMemory}!ParseResults {
    const msg = try std.fmt.allocPrint(arena, fmt ++ "\n", args);
    return ParseResults{ .err = msg };
}

pub fn fatal(comptime fmt: []const u8, args: anytype) u8 {
    std.debug.print(fmt, args);
    return 1;
}

fn getTarget(self_exe: []const u8) error{BadParent}!?[]const u8 {
    const here = fs.path.dirname(self_exe) orelse return error.BadParent;
    const triple = fs.path.basename(here);

    // Validating the triple now will help users catch errors even if they
    // don't yet need the target. yes yes the validation will miss things
    // strings `is.it.x86_64?-stallinux,macos-`; we are trying to aid users
    // that run things from the wrong directory, not trying to punish the ones
    // having fun.
    var it = mem.split(u8, triple, "-");

    const arch = it.next() orelse return error.BadParent;
    if (mem.indexOf(u8, "aarch64,x86_64", arch) == null)
        return error.BadParent;

    const got_os = it.next() orelse return error.BadParent;
    if (mem.indexOf(u8, "linux,macos,windows", got_os) == null)
        return error.BadParent;

    // ABI triple is too much of a moving target
    if (it.next() == null) return error.BadParent;
    // but the target needs to have 3 dashes.
    if (it.next() != null) return error.BadParent;

    if (mem.eql(u8, "c++" ++ EXE, fs.path.basename(self_exe)))
        return triple
    else
        return null;
}

const testing = std.testing;

pub const TestArgIterator = struct {
    index: usize = 0,
    argv: []const [:0]const u8,

    pub fn next(self: *TestArgIterator) ?[:0]const u8 {
        if (self.index == self.argv.len) return null;

        defer self.index += 1;
        return self.argv[self.index];
    }
};

fn compareExec(
    res: ParseResults,
    want_args: []const [:0]const u8,
    want_env_zig_lib_dir: []const u8,
) !void {
    try testing.expectEqual(want_args.len, res.exec.args.items.len);

    for (want_args, res.exec.args.items) |want_arg, got_arg|
        try testing.expectEqualStrings(want_arg, got_arg);

    try testing.expectEqualStrings(
        want_env_zig_lib_dir,
        res.exec.env.get("ZIG_LIB_DIR").?,
    );
}

test "launcher:parseArgs" {
    // not using testing.allocator, because parseArgs is designed to be used
    // with an arena.
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    var allocator = gpa.allocator();

    const tests = [_]struct {
        args: []const [:0]const u8,
        precreate_dir: ?[]const u8 = null,
        want_result: union(Action) {
            err: []const u8,
            exec: struct {
                args: []const [:0]const u8,
                env_zig_lib_dir: []const u8,
            },
        },
    }{
        .{
            .args = &[_][:0]const u8{"ar" ++ EXE},
            .want_result = .{
                .err = std.fmt.comptimePrint(usage_other ++ "\n", .{
                    .zig_tool = "ar",
                    .exe = EXE,
                }),
            },
        },
        .{
            .args = &[_][:0]const u8{"c++" ++ EXE},
            .want_result = .{
                .err = std.fmt.comptimePrint(usage_cpp ++ "\n", .{
                    .zig_tool = "c++",
                    .exe = EXE,
                }),
            },
        },
        .{
            .args = &[_][:0]const u8{
                "tools" ++ sep ++ "x86_64-linux-musl" ++ sep ++ "c++" ++ EXE,
                "main.c",
                "-o",
                "/dev/null",
            },
            .want_result = .{
                .exec = .{
                    .args = &[_][:0]const u8{
                        "tools" ++ sep ++ "x86_64-linux-musl" ++ sep ++
                            ".." ++ sep ++ ".." ++ sep ++ "zig" ++ EXE,
                        "c++",
                        "-target",
                        "x86_64-linux-musl",
                        "main.c",
                        "-o",
                        "/dev/null",
                    },
                    .env_zig_lib_dir = "tools" ++ sep ++ "x86_64-linux-musl" ++
                        sep ++ ".." ++ sep ++ ".." ++ sep ++ "lib",
                },
            },
        },
        .{
            .args = &[_][:0]const u8{
                "tools" ++ sep ++ "x86_64-linux-musl" ++ sep ++ "ar" ++ EXE,
                "-rcs",
                "all.a",
                "main.o",
                "foo.o",
            },
            .want_result = .{
                .exec = .{
                    .args = &[_][:0]const u8{
                        "tools" ++ sep ++ "x86_64-linux-musl" ++ sep ++ ".." ++
                            sep ++ ".." ++ sep ++ "zig" ++ EXE,
                        "ar",
                        "-rcs",
                        "all.a",
                        "main.o",
                        "foo.o",
                    },
                    .env_zig_lib_dir = "tools" ++ sep ++ "x86_64-linux-musl" ++
                        sep ++ ".." ++ sep ++ ".." ++ sep ++ "lib",
                },
            },
        },
        .{
            .args = &[_][:0]const u8{
                "external_zig_sdk" ++ sep ++ "tools" ++ sep ++
                    "x86_64-linux-gnu.2.28" ++ sep ++ "c++" ++ EXE,
                "main.c",
                "-o",
                "/dev/null",
            },
            .precreate_dir = "external" ++ sep ++ "zig_sdk" ++ sep ++ "lib",
            .want_result = .{
                .exec = .{
                    .args = &[_][:0]const u8{
                        "external" ++ sep ++ "zig_sdk" ++ sep ++ "zig" ++ EXE,
                        "c++",
                        "-target",
                        "x86_64-linux-gnu.2.28",
                        "main.c",
                        "-o",
                        "/dev/null",
                    },
                    .env_zig_lib_dir = "external" ++ sep ++ "zig_sdk" ++
                        sep ++ "lib",
                },
            },
        },
    };

    for (tests) |tt| {
        var tmp = testing.tmpDir(.{});
        defer tmp.cleanup();

        if (tt.precreate_dir) |dir|
            try tmp.dir.makePath(dir);

        var res = try parseArgs(allocator, tmp.dir, &TestArgIterator{
            .argv = tt.args,
        });

        switch (tt.want_result) {
            .err => |want_msg| try testing.expectEqualStrings(
                want_msg,
                res.err,
            ),
            .exec => |want| {
                try compareExec(res, want.args, want.env_zig_lib_dir);
            },
        }
    }
}

D toolchain/libc/BUILD => toolchain/libc/BUILD +0 -20
@@ 1,20 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

load("@bazel-zig-cc//toolchain/libc:defs.bzl", "declare_libcs")

package(
    default_visibility = ["//visibility:public"],
)

constraint_setting(
    name = "variant",
    default_constraint_value = "unconstrained",
)

constraint_value(
    name = "unconstrained",
    constraint_setting = "variant",
)

declare_libcs()

D toolchain/libc/defs.bzl => toolchain/libc/defs.bzl +0 -11
@@ 1,11 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

load("@bazel-zig-cc//toolchain/private:defs.bzl", "LIBCS")

def declare_libcs():
    for libc in LIBCS:
        native.constraint_value(
            name = libc,
            constraint_setting = "variant",
        )

D toolchain/libc_aware/platform/BUILD => toolchain/libc_aware/platform/BUILD +0 -10
@@ 1,10 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

load("@bazel-zig-cc//toolchain/platform:defs.bzl", "declare_libc_aware_platforms")

package(
    default_visibility = ["//visibility:public"],
)

declare_libc_aware_platforms()

D toolchain/libc_aware/toolchain/BUILD => toolchain/libc_aware/toolchain/BUILD +0 -10
@@ 1,10 0,0 @@
# Copyright 2023 Uber Technologies, Inc.
# Licensed under the Apache License, Version 2.0

load("@bazel-zig-cc//toolchain/toolchain:defs.bzl", "declare_libc_aware_toolchains")

package(
    default_visibility = ["//visibility:public"],
)

declare_libc_aware_toolchains()

D toolchain/platform/BUILD => toolchain/platform/BUILD +0 -7
@@ 1,7 0,0 @@
load("@bazel-zig-cc//toolchain/platform:defs.bzl", "declare_platforms")

package(
    default_visibility = ["//visibility:public"],
)

declare_platforms()

D toolchain/platform/defs.bzl => toolchain/platform/defs.bzl +0 -45
@@ 1,45 0,0 @@
load("@bazel-zig-cc//toolchain/private:defs.bzl", "LIBCS")

_CPUS = (("x86_64", "amd64"), ("aarch64", "arm64"))
_OS = {
    "linux": ["linux"],
    "macos": ["macos", "darwin"],
    "windows": ["windows"],
}

def declare_platforms():
    # create @zig_sdk//{os}_{arch}_platform entries with zig and go conventions
    for zigcpu, gocpu in _CPUS:
        for bzlos, oss in _OS.items():
            for os in oss:
                declare_platform(gocpu, zigcpu, bzlos, os)

def declare_libc_aware_platforms():
    # create @zig_sdk//{os}_{arch}_platform entries with zig and go conventions
    # with libc specified
    for zigcpu, gocpu in _CPUS:
        for libc in LIBCS:
            declare_platform(
                gocpu,
                zigcpu,
                "linux",
                "linux",
                suffix = "_{}".format(libc),
                extra_constraints = ["@zig_sdk//libc:{}".format(libc)],
            )

def declare_platform(gocpu, zigcpu, bzlos, os, suffix = "", extra_constraints = []):
    constraint_values = [
        "@platforms//os:{}".format(bzlos),
        "@platforms//cpu:{}".format(zigcpu),
    ] + extra_constraints

    native.platform(
        name = "{os}_{zigcpu}{suffix}".format(os = os, zigcpu = zigcpu, suffix = suffix),
        constraint_values = constraint_values,
    )

    native.platform(
        name = "{os}_{gocpu}{suffix}".format(os = os, gocpu = gocpu, suffix = suffix),
        constraint_values = constraint_values,
    )

D toolchain/private/BUILD => toolchain/private/BUILD +0 -0
D toolchain/private/cc_toolchains.bzl => toolchain/private/cc_toolchains.bzl +0 -74
@@ 1,74 0,0 @@
load(":defs.bzl", "target_structs", "zig_tool_path")
load("@bazel-zig-cc//toolchain:zig_toolchain.bzl", "zig_cc_toolchain_config")

DEFAULT_TOOL_PATHS = {
    "ar": "ar",
    "gcc": "c++",  # https://github.com/bazelbuild/bazel/issues/4644
    "cpp": "/usr/bin/false",
    "gcov": "/usr/bin/false",
    "nm": "/usr/bin/false",
    "objdump": "/usr/bin/false",
    "strip": "/usr/bin/false",
}.items()

def declare_cc_toolchains(os, zig_sdk_path):
    for target_config in target_structs():
        gotarget = target_config.gotarget
        zigtarget = target_config.zigtarget

        cxx_builtin_include_directories = []
        absolute_tool_paths = {}
        for name, path in target_config.tool_paths.items() + DEFAULT_TOOL_PATHS:
            if path[0] == "/":
                absolute_tool_paths[name] = path
                continue
            tool_path = zig_tool_path(os).format(
                zig_tool = path,
                zigtarget = zigtarget,
            )
            absolute_tool_paths[name] = tool_path

        dynamic_library_linkopts = target_config.dynamic_library_linkopts
        copts = target_config.copts
        linkopts = []
        for s in getattr(target_config, "linker_version_scripts", []):
            linkopts = ["-Wl,--version-script,%s/%s" % (zig_sdk_path, s)]
        for incl in getattr(target_config, "compiler_extra_includes", []):
            copts = copts + ["-include", zig_sdk_path + "/" + incl]

        # We can't pass a list of structs to a rule, so we use json encoding.
        artifact_name_patterns = getattr(target_config, "artifact_name_patterns", [])
        artifact_name_pattern_strings = [json.encode(p) for p in artifact_name_patterns]

        zig_cc_toolchain_config(
            name = zigtarget + "_cc_config",
            target = zigtarget,
            tool_paths = absolute_tool_paths,
            cxx_builtin_include_directories = cxx_builtin_include_directories,
            copts = copts,
            linkopts = linkopts,
            dynamic_library_linkopts = dynamic_library_linkopts,
            target_cpu = target_config.bazel_target_cpu,
            target_system_name = "unknown",
            target_libc = "unknown",
            compiler = "clang",
            abi_version = "unknown",
            abi_libc_version = "unknown",
            artifact_name_patterns = artifact_name_pattern_strings,
            visibility = ["//visibility:private"],
        )

        native.cc_toolchain(
            name = zigtarget + "_cc",
            toolchain_identifier = zigtarget + "-toolchain",
            toolchain_config = ":%s_cc_config" % zigtarget,
            all_files = "@zig_sdk//:%s_all_files" % zigtarget,
            ar_files = "@zig_sdk//:%s_ar_files" % zigtarget,
            compiler_files = "@zig_sdk//:%s_compiler_files" % zigtarget,
            linker_files = "@zig_sdk//:%s_linker_files" % zigtarget,
            dwp_files = "@zig_sdk//:empty",
            objcopy_files = "@zig_sdk//:empty",
            strip_files = "@zig_sdk//:empty",
            supports_param_files = 0,
            visibility = ["//visibility:private"],
        )

D toolchain/private/defs.bzl => toolchain/private/defs.bzl +0 -185
@@ 1,185 0,0 @@
_ZIG_TOOL_PATH = "tools/{zigtarget}/{zig_tool}"

# Zig supports even older glibcs than defined below, but we have tested only
# down to 2.17.
# $ zig targets | jq -r '.glibc[]' | sort -V
_GLIBCS = [
    "2.17",
    "2.18",
    "2.19",
    "2.22",
    "2.23",
    "2.24",
    "2.25",
    "2.26",
    "2.27",
    "2.28",
    "2.29",
    "2.30",
    "2.31",
    "2.32",
    "2.33",
    "2.34",
]

_INCLUDE_TAIL = [
    "libcxx/include",
    "libcxxabi/include",
    "include",
]

LIBCS = ["musl"] + ["gnu.{}".format(glibc) for glibc in _GLIBCS]

def zig_tool_path(os):
    if os == "windows":
        return _ZIG_TOOL_PATH + ".exe"
    else:
        return _ZIG_TOOL_PATH

def target_structs():
    ret = []
    for zigcpu, gocpu in (("x86_64", "amd64"), ("aarch64", "arm64")):
        ret.append(_target_macos(gocpu, zigcpu))
        ret.append(_target_windows(gocpu, zigcpu))
        ret.append(_target_linux_musl(gocpu, zigcpu))
        for glibc in _GLIBCS:
            ret.append(_target_linux_gnu(gocpu, zigcpu, glibc))
    return ret

def _target_macos(gocpu, zigcpu):
    min_os = "11"
    copts = []

    if zigcpu == "aarch64":
        copts = ["-mcpu=apple_m1"]

    return struct(
        gotarget = "darwin_{}".format(gocpu),
        zigtarget = "{}-macos-none".format(zigcpu),
        includes = [
            "libunwind/include",
            # TODO: Define a toolchain for each minimum OS version
            "libc/include/{}-macos.{}-none".format(zigcpu, min_os),
            "libc/include/any-macos.{}-any".format(min_os),
            "libc/include/any-macos-any",
        ] + _INCLUDE_TAIL,
        dynamic_library_linkopts = ["-Wl,-undefined=dynamic_lookup"],
        copts = copts,
        libc = "darwin",
        bazel_target_cpu = "darwin",
        constraint_values = [
            "@platforms//os:macos",
            "@platforms//cpu:{}".format(zigcpu),
        ],
        tool_paths = {"ld": "ld64.lld"},
        artifact_name_patterns = [
            {
                "category_name": "dynamic_library",
                "prefix": "lib",
                "extension": ".dylib",
            },
        ],
    )

def _target_windows(gocpu, zigcpu):
    return struct(
        gotarget = "windows_{}".format(gocpu),
        zigtarget = "{}-windows-gnu".format(zigcpu),
        includes = [
            "libc/mingw",
            "libunwind/include",
            "libc/include/any-windows-any",
        ] + _INCLUDE_TAIL,
        dynamic_library_linkopts = [],
        copts = [],
        libc = "mingw",
        bazel_target_cpu = "x64_windows",
        constraint_values = [
            "@platforms//os:windows",
            "@platforms//cpu:{}".format(zigcpu),
        ],
        tool_paths = {"ld": "ld64.lld"},
        artifact_name_patterns = [
            {
                "category_name": "static_library",
                "prefix": "",
                "extension": ".lib",
            },
            {
                "category_name": "dynamic_library",
                "prefix": "",
                "extension": ".dll",
            },
            {
                "category_name": "executable",
                "prefix": "",
                "extension": ".exe",
            },
        ],
    )

def _target_linux_gnu(gocpu, zigcpu, glibc_version):
    glibc_suffix = "gnu.{}".format(glibc_version)

    compiler_extra_includes = []
    linker_version_scripts = []
    if glibc_version < "2.28":
        # https://github.com/ziglang/zig/issues/5882#issuecomment-888250676
        compiler_extra_includes.append("glibc-hacks/fcntl.h")
        linker_version_scripts.append("glibc-hacks/fcntl.map")
    if glibc_version < "2.34":
        compiler_extra_includes.append("glibc-hacks/res_search-{}.h".format(gocpu))
        linker_version_scripts.append("glibc-hacks/res_search-{}.map".format(gocpu))

    return struct(
        gotarget = "linux_{}_{}".format(gocpu, glibc_suffix),
        zigtarget = "{}-linux-{}".format(zigcpu, glibc_suffix),
        includes = [
                       "libc/include/{}-linux-gnu".format(zigcpu),
                       "libc/include/generic-glibc",
                   ] +
                   # x86_64-linux-any is x86_64-linux and x86-linux combined.
                   (["libc/include/x86-linux-any"] if zigcpu == "x86_64" else []) +
                   (["libc/include/{}-linux-any".format(zigcpu)] if zigcpu != "x86_64" else []) + [
            "libc/include/any-linux-any",
        ] + _INCLUDE_TAIL,
        compiler_extra_includes = compiler_extra_includes,
        linker_version_scripts = linker_version_scripts,
        dynamic_library_linkopts = [],
        copts = [],
        libc = "glibc",
        bazel_target_cpu = "k8",
        constraint_values = [
            "@platforms//os:linux",
            "@platforms//cpu:{}".format(zigcpu),
        ],
        libc_constraint = "@zig_sdk//libc:{}".format(glibc_suffix),
        tool_paths = {"ld": "ld.lld"},
        artifact_name_patterns = [],
    )

def _target_linux_musl(gocpu, zigcpu):
    return struct(
        gotarget = "linux_{}_musl".format(gocpu),
        zigtarget = "{}-linux-musl".format(zigcpu),
        includes = [
                       "libc/include/{}-linux-musl".format(zigcpu),
                       "libc/include/generic-musl",
                   ] +
                   # x86_64-linux-any is x86_64-linux and x86-linux combined.
                   (["libc/include/x86-linux-any"] if zigcpu == "x86_64" else []) +
                   (["libc/include/{}-linux-any".format(zigcpu)] if zigcpu != "x86_64" else []) + [
            "libc/include/any-linux-any",
        ] + _INCLUDE_TAIL,
        dynamic_library_linkopts = [],
        copts = ["-D_LIBCPP_HAS_MUSL_LIBC", "-D_LIBCPP_HAS_THREAD_API_PTHREAD"],
        libc = "musl",
        bazel_target_cpu = "k8",
        constraint_values = [
            "@platforms//os:linux",
            "@platforms//cpu:{}".format(zigcpu),
        ],
        libc_constraint = "@zig_sdk//libc:musl",
        tool_paths = {"ld": "ld.lld"},
        artifact_name_patterns = [],
    )

D toolchain/toolchain/BUILD => toolchain/toolchain/BUILD +0 -7
@@ 1,7 0,0 @@
load("@bazel-zig-cc//toolchain/toolchain:defs.bzl", "declare_toolchains")

package(
    default_visibility = ["//visibility:public"],
)

declare_toolchains()

D toolchain/toolchain/defs.bzl => toolchain/toolchain/defs.bzl +0 -46
@@ 1,46 0,0 @@
load("@bazel-zig-cc//toolchain/private:defs.bzl", "target_structs")

def declare_toolchains():
    for target_config in target_structs():
        gotarget = target_config.gotarget
        zigtarget = target_config.zigtarget

        # if the toolchain is libc aware, create two variants for it: one that
        # is only selected if libc is not expicitly set and another one that is
        # only selected if the specific libc variant is selected.
        extra_constraints = []
        if hasattr(target_config, "libc_constraint"):
            extra_constraints = ["@zig_sdk//libc:unconstrained"]

        _declare_toolchain(gotarget, zigtarget, target_config.constraint_values + extra_constraints)

def declare_libc_aware_toolchains():
    for target_config in target_structs():
        gotarget = target_config.gotarget
        zigtarget = target_config.zigtarget

        # if the toolchain is libc aware, create two variants for it: one that
        # is only selected if libc is not expicitly set and another one that is
        # only selected if the specific libc variant is selected.
        if hasattr(target_config, "libc_constraint"):
            _declare_toolchain(gotarget, zigtarget, target_config.constraint_values + [target_config.libc_constraint])

def _declare_toolchain(gotarget, zigtarget, target_compatible_with):
    # register two kinds of toolchain targets: Go and Zig conventions.
    # Go convention: amd64/arm64, linux/darwin
    native.toolchain(
        name = gotarget,
        exec_compatible_with = None,
        target_compatible_with = target_compatible_with,
        toolchain = "@zig_sdk//:%s_cc" % zigtarget,
        toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
    )

    # Zig convention: x86_64/aarch64, linux/macos
    native.toolchain(
        name = zigtarget,
        exec_compatible_with = None,
        target_compatible_with = target_compatible_with,
        toolchain = "@zig_sdk//:%s_cc" % zigtarget,
        toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
    )

D toolchain/zig_toolchain.bzl => toolchain/zig_toolchain.bzl +0 -187
@@ 1,187 0,0 @@
load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
load(
    "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
    "artifact_name_pattern",
    "feature",
    "feature_set",
    "flag_group",
    "flag_set",
    "tool",
    "tool_path",
)

all_link_actions = [
    ACTION_NAMES.cpp_link_executable,
    ACTION_NAMES.cpp_link_dynamic_library,
    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
]

dynamic_library_link_actions = [
    ACTION_NAMES.cpp_link_dynamic_library,
    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
]

compile_and_link_actions = [
    ACTION_NAMES.c_compile,
    ACTION_NAMES.cpp_compile,
]

rest_compile_actions = [
    ACTION_NAMES.assemble,
    ACTION_NAMES.cc_flags_make_variable,
    ACTION_NAMES.clif_match,
    ACTION_NAMES.cpp_header_parsing,
    ACTION_NAMES.cpp_module_codegen,
    ACTION_NAMES.cpp_module_compile,
    ACTION_NAMES.linkstamp_compile,
    ACTION_NAMES.lto_backend,
    ACTION_NAMES.preprocess_assemble,
]

def _compilation_mode_features(ctx):
    actions = all_link_actions + compile_and_link_actions + rest_compile_actions

    dbg_feature = feature(
        name = "dbg",
        flag_sets = [
            flag_set(
                actions = actions,
                flag_groups = [
                    flag_group(
                        flags = ["-g"],
                    ),
                ],
            ),
        ],
    )

    opt_feature = feature(
        name = "opt",
        flag_sets = [
            flag_set(
                actions = actions,
                flag_groups = [
                    flag_group(
                        flags = ["-O2", "-DNDEBUG"],
                    ),
                ],
            ),
        ],
    )

    fastbuild_feature = feature(
        name = "fastbuild",
        flag_sets = [
            flag_set(
                actions = actions,
                flag_groups = [
                    flag_group(
                        flags = ["-fno-lto", "-Wl,-S"],
                    ),
                ],
            ),
        ],
    )

    return [
        dbg_feature,
        opt_feature,
        fastbuild_feature,
    ]

def _zig_cc_toolchain_config_impl(ctx):
    compiler_flags = [
        "-I" + d
        for d in ctx.attr.cxx_builtin_include_directories
    ] + [
        "-no-canonical-prefixes",
        "-Wno-builtin-macro-redefined",
        "-D__DATE__=\"redacted\"",
        "-D__TIMESTAMP__=\"redacted\"",
        "-D__TIME__=\"redacted\"",
    ]

    compile_and_link_flags = feature(
        name = "compile_and_link_flags",
        enabled = True,
        flag_sets = [
            flag_set(
                actions = compile_and_link_actions,
                flag_groups = [
                    flag_group(flags = compiler_flags + ctx.attr.copts),
                ],
            ),
        ],
    )

    if ctx.attr.dynamic_library_linkopts:
        dynamic_library_flag_sets = [
            flag_set(
                actions = dynamic_library_link_actions,
                flag_groups = [flag_group(flags = ctx.attr.dynamic_library_linkopts)],
            ),
        ]
    else:
        dynamic_library_flag_sets = []

    default_linker_flags = feature(
        name = "default_linker_flags",
        enabled = True,
        flag_sets = dynamic_library_flag_sets,
    )

    supports_dynamic_linker = feature(
        name = "supports_dynamic_linker",
        enabled = True,
    )

    features = [
        compile_and_link_flags,
        default_linker_flags,
        supports_dynamic_linker,
    ] + _compilation_mode_features(ctx)

    artifact_name_patterns = [
        artifact_name_pattern(**json.decode(p))
        for p in ctx.attr.artifact_name_patterns
    ]

    return cc_common.create_cc_toolchain_config_info(
        ctx = ctx,
        features = features,
        toolchain_identifier = "%s-toolchain" % ctx.attr.target,
        host_system_name = "local",
        target_system_name = ctx.attr.target_system_name,
        target_cpu = ctx.attr.target_cpu,
        target_libc = ctx.attr.target_libc,
        compiler = ctx.attr.compiler,
        abi_version = ctx.attr.abi_version,
        abi_libc_version = ctx.attr.abi_libc_version,
        tool_paths = [
            tool_path(name = name, path = path)
            for name, path in ctx.attr.tool_paths.items()
        ],
        cxx_builtin_include_directories = ctx.attr.cxx_builtin_include_directories,
        artifact_name_patterns = artifact_name_patterns,
    )

zig_cc_toolchain_config = rule(
    implementation = _zig_cc_toolchain_config_impl,
    attrs = {
        "cxx_builtin_include_directories": attr.string_list(),
        "linkopts": attr.string_list(),
        "dynamic_library_linkopts": attr.string_list(),
        "copts": attr.string_list(),
        "tool_paths": attr.string_dict(),
        "target": attr.string(),
        "target_system_name": attr.string(),
        "target_cpu": attr.string(),
        "target_libc": attr.string(),
        "target_suffix": attr.string(),
        "compiler": attr.string(),
        "abi_version": attr.string(),
        "abi_libc_version": attr.string(),
        "artifact_name_patterns": attr.string_list(),
    },
    provides = [CcToolchainConfigInfo],
)