From 7a81e2a129b5eb285e00910c49e7e5c9f8b57356 Mon Sep 17 00:00:00 2001 From: laurynasl Date: Wed, 13 Apr 2022 12:58:11 +0000 Subject: [PATCH 1/6] Restructure tests to all run in bazel - add rules to run tests for a specific platform - use downloaded buildifier - move lint to a script - rename ci tasks - stop running under qemu-aarch64-static as it doesn't do anything --- .bazelrc | 3 --- .build.yml | 13 ++++----- .envrc | 44 +++++++++++++++++++++--------- .gitignore | 1 + BUILD | 3 --- README.md | 48 ++++++++------------------------- WORKSPACE | 12 --------- bin/mod-tidy | 5 ++-- ci/lint | 9 +++++++ ci/test | 32 +--------------------- go.mod | 2 +- rules/BUILD | 1 + rules/platform.bzl | 67 ++++++++++++++++++++++++++++++++++++++++++++++ test/go/BUILD | 33 ++++++++++++++++++++++- 14 files changed, 163 insertions(+), 110 deletions(-) create mode 100755 ci/lint create mode 100644 rules/platform.bzl diff --git a/.bazelrc b/.bazelrc index fed6f0b..c564a31 100644 --- a/.bazelrc +++ b/.bazelrc @@ -10,6 +10,3 @@ build --extra_toolchains @zig_sdk//toolchain:linux_amd64_gnu.2.19 build --extra_toolchains @zig_sdk//toolchain:linux_arm64_gnu.2.28 build --extra_toolchains @zig_sdk//toolchain:darwin_amd64 build --extra_toolchains @zig_sdk//toolchain:darwin_arm64 - -test:qemu-aarch64 --test_env=QEMU_LD_PREFIX=/usr/aarch64-linux-gnu/ -test:qemu-aarch64 --run_under=qemu-aarch64-static diff --git a/.build.yml b/.build.yml index 233566c..7eb773f 100644 --- a/.build.yml +++ b/.build.yml @@ -3,7 +3,6 @@ packages: - direnv - shellcheck - qemu-user-static - - libc6-arm64-cross - binfmt-support sources: - https://git.sr.ht/~motiejus/bazel-zig-cc @@ -16,17 +15,19 @@ triggers: tasks: - setup: | sudo apt-get purge gcc -y && sudo apt-get autoremove -y - - test_list_toolchains_platforms: | + sudo dpkg --add-architecture arm64 + sudo apt-get update + sudo apt-get install libc6:arm64 -y + - list_toolchains_platforms: | cd bazel-zig-cc; . .envrc echo "Available toolchains:" bazel query @zig_sdk//toolchain:* echo "Available platforms:" bazel query @zig_sdk//platform:* - - test_hello_on_toolchains: | - cd bazel-zig-cc + - test: | + cd bazel-zig-cc; . .envrc ./ci/test --color=yes --curses=yes - lint: | cd bazel-zig-cc; . .envrc - shellcheck -x $(awk '/#!\/bin\/(ba)?sh/&&FNR==1{print FILENAME}' $(git ls-files)) - bazel run //:buildifier + ./ci/lint git diff --exit-code diff --git a/.envrc b/.envrc index 2b1d835..9ebc50c 100644 --- a/.envrc +++ b/.envrc @@ -1,27 +1,45 @@ set -eu -export PATH="$(git rev-parse --show-toplevel)/bin:$PATH" -_u=https://github.com/bazelbuild/bazelisk/releases/download/v1.10.1/bazelisk- +BIN_DIR="$(git rev-parse --show-toplevel)/bin" +export PATH="$BIN_DIR:$PATH" -#for os in linux darwin; do -# for arch in amd64 arm64; do -# hash=$(direnv fetchurl "${_u}$os-$arch") -# echo -e "$os-$arch\t$hash" -# done -#done +_u_bzl=https://github.com/bazelbuild/bazelisk/releases/download/v1.10.1/bazelisk- +_u_bldf=https://github.com/bazelbuild/buildtools/releases/download/5.0.1/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}linux-amd64" sha256-TLU0xSzdR6YiPUWW1TDnyceFQ4qzsKSf80fpkcIQss0=);; + bzl=$(direnv fetchurl "${_u_bzl}linux-amd64" sha256-TLU0xSzdR6YiPUWW1TDnyceFQ4qzsKSf80fpkcIQss0=) + bldf=$(direnv fetchurl "${_u_bldf}linux-amd64" sha256-Ptc1jHxqHKIW3FZukFT9C5ehSCywt+YQkr6IfUJhXF0=) + ;; linux-aarch64) - bzl=$(direnv fetchurl "${_u}linux-arm64" sha256-wd5oYN1PjV4uwnAJe9RtaiEblxoLizhVl4S9BR6pUKE=);; + bzl=$(direnv fetchurl "${_u_bzl}linux-arm64" sha256-wd5oYN1PjV4uwnAJe9RtaiEblxoLizhVl4S9BR6pUKE=) + bldf=$(direnv fetchurl "${_u_bldf}linux-arm64" sha256-xlfGKPynK34ERvGlQiMXIqELpDIVl71vYkml2mBgtv8==) + ;; darwin-x86_64) - bzl=$(direnv fetchurl "${_u}darwin-amd64" sha256-5IW7+EUy0CpgsOsjxwJhC1QI3zoZkIek8rXgmVu/LVo=);; + bzl=$(direnv fetchurl "${_u_bzl}darwin-amd64" sha256-5IW7+EUy0CpgsOsjxwJhC1QI3zoZkIek8rXgmVu/LVo=) + bldf=$(direnv fetchurl "${_u_bldf}darwin-amd64" sha256-LLClRoNjPvbeTgSRBy4i5mrJxjiQUUMrdiAN7u6vk/s=) + ;; darwin-arm64) - bzl=$(direnv fetchurl "${_u}darwin-arm64" sha256-wi1IYBRm2dOwQ8zXQFHy9CMPm59FCfCXAXyXMDqojRM=);; + bzl=$(direnv fetchurl "${_u_bzl}darwin-arm64" sha256-wi1IYBRm2dOwQ8zXQFHy9CMPm59FCfCXAXyXMDqojRM=) + bldf=$(direnv fetchurl "${_u_bldf}darwin-arm64" sha256-TaIzFfDcyr+HjIIn/dvM81VFsjs8tiJb/PMQdonMQ2Q=) + ;; *) >&2 echo "unsupported architecture tuple $(uname | tr A-Z a-z)-$(uname -m)" exit 1;; esac -ln -sf "${bzl}" bin/bazel +ln -sf "${bzl}" "$BIN_DIR/bazel" +ln -sf "${bldf}" "$BIN_DIR/buildifier" diff --git a/.gitignore b/.gitignore index 976e119..e5cfc07 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.sw[op] /bin/bazel +/bin/buildifier /bin/bazelisk-* /bazel-bazel-zig-cc diff --git a/BUILD b/BUILD index 9f1adc5..452c016 100644 --- a/BUILD +++ b/BUILD @@ -1,10 +1,7 @@ load("@bazel_gazelle//:def.bzl", "gazelle") -load("@com_github_bazelbuild_buildtools//buildifier:def.bzl", "buildifier") # 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") - -buildifier(name = "buildifier") diff --git a/README.md b/README.md index 085f9f8..f6a7538 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ For example, the toolchain `linux_amd64_gnu.2.28` is aliased to used, run: ``` -$ bazel query @zig_sdk//... | grep _toolchain$ +$ bazel query @zig_sdk//toolchain/... ``` ## Specifying non-default toolchains @@ -151,48 +151,22 @@ may apply to aarch64, but the author didn't find a need to test it (yet). - [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) # Testing - -## build & run linux cgo + glibc - -``` -$ bazel build --platforms @zig_sdk//platform:linux_amd64 //test/go:go -$ file bazel-out/k8-opt-ST-d17813c235ce/bin/test/go/go_/go -bazel-out/k8-opt-ST-d17813c235ce/bin/test/go/go_/go: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.0.0, Go BuildID=redacted, with debug_info, not stripped -$ bazel-out/k8-opt-ST-d17813c235ce/bin/test/go/go_/go -hello, world -``` - -## test linux cgo + musl on arm64 (under qemu-aarch64) - -``` -$ bazel test \ - --config=qemu-aarch64 \ - --platforms @zig_sdk//platform:linux_arm64 \ - --extra_toolchains @zig_sdk//toolchain:linux_arm64_musl //test/... -... -INFO: Build completed successfully, 10 total actions -//test/go:go_test PASSED in 0.2s -``` - -## macos cgo - -``` -$ bazel build --platforms @zig_sdk//platform:darwin_amd64 //test/go:go -... -$ file bazel-out/k8-opt-ST-d17813c235ce/bin/test/go/go_/go -bazel-out/k8-opt-ST-d17813c235ce/bin/test/go/go_/go: Mach-O 64-bit x86_64 executable, flags: -``` - ## Transient docker environment +First of all, make sure that your kernel is configured to run arm64 binaries. +You can either `apt install qemu-user-static binfmt-support`; this should setup +`binfmt_misc` to handle arm64 binaries. Or you can use this handy dockerized +script `docker run --rm --privileged multiarch/qemu-user-static --reset -p yes`. + ``` -$ docker run -e CC=/usr/bin/false -ti --rm -v $(pwd):/x -w /x debian:bullseye-slim -# apt update && apt install -y direnv git +$ docker run -e CC=/usr/bin/false -ti --rm -v $(git rev-parse --show-toplevel):/x -w /x debian:bullseye-slim +# dpkg --add-architecture arm64 && apt update && apt install -y direnv git shellcheck libc6:arm64 # . .envrc +# ./ci/test +# ./ci/lint ``` -And run the `bazel build` commands above. Take a look at `.build.yml` and see -how CI does it. +See `ci/test` for how tests are run. # Questions & Contributions diff --git a/WORKSPACE b/WORKSPACE index 189ebae..54d84a0 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -42,18 +42,6 @@ go_repositories() gazelle_dependencies(go_repository_default_config = "@//:WORKSPACE") -# protobuf is required for //:buildifier -http_archive( - name = "com_google_protobuf", - sha256 = "25f1292d4ea6666f460a2a30038eef121e6c3937ae0f61d610611dfb14b0bd32", - strip_prefix = "protobuf-3.19.1", - urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.19.1.zip"], -) - -load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") - -protobuf_deps() - load( "//toolchain:defs.bzl", zig_register_toolchains = "register_toolchains", diff --git a/bin/mod-tidy b/bin/mod-tidy index 26091ca..0c66f25 100755 --- a/bin/mod-tidy +++ b/bin/mod-tidy @@ -2,9 +2,8 @@ set -xeuo pipefail -cd "$(git rev-parse --show-toplevel)" -bazel build @go_sdk//:go_sdk -bazel-bazel-zig-cc/external/go_sdk/bin/go mod tidy +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 \ diff --git a/ci/lint b/ci/lint new file mode 100755 index 0000000..8c52243 --- /dev/null +++ b/ci/lint @@ -0,0 +1,9 @@ +#!/bin/bash +set -euo pipefail + +REPO_ROOT=$(git rev-parse --show-toplevel) +cd "$REPO_ROOT" + +# shellcheck disable=SC2046 +shellcheck -x $(awk '/#!\/bin\/(ba)?sh/&&FNR==1{print FILENAME}' $(git ls-files)) +find . \( -name 'WORKSPACE' -o -name 'BUILD' -o -name '*.bzl' \) -exec buildifier {} + diff --git a/ci/test b/ci/test index 35d0c07..5e27913 100755 --- a/ci/test +++ b/ci/test @@ -1,34 +1,4 @@ #!/bin/bash set -euo pipefail -cd "$(dirname "$0")/.." -. .envrc - -_run() { - >&2 echo - >&2 echo " $*" - >&2 echo - "$@" -} - -while read -r action platform toolchain config; do - args=("$@") - if [[ $config != : ]]; then - args+=(--config="$config") - fi - - args+=(\ - --platforms "@zig_sdk//platform:${platform}" \ - --extra_toolchains "@zig_sdk//toolchain:${toolchain}" \ - //test/... \ - ) - - _run bazel "$action" "${args[@]}" - -done < Date: Wed, 13 Apr 2022 14:52:25 +0000 Subject: [PATCH 2/6] Add libc constraint and libc aware toolchains - Also get rid of @bazel_skylib dependency --- .bazelrc | 5 +- .build.yml | 5 +- README.md | 182 +++++++++++++++++++++------ WORKSPACE | 11 +- ci/list_toolchains_platforms | 15 +++ rules/platform.bzl | 4 +- test/c/BUILD | 46 +++++++ test/c/main.c | 11 ++ test/c/test.sh | 16 +++ test/go/BUILD | 8 +- toolchain/BUILD.sdk.bazel | 2 + toolchain/defs.bzl | 65 +++++----- toolchain/libc/BUILD | 17 +++ toolchain/libc/defs.bzl | 8 ++ toolchain/libc_aware/platform/BUILD | 7 ++ toolchain/libc_aware/toolchain/BUILD | 7 ++ toolchain/platform/BUILD | 4 + toolchain/platform/defs.bzl | 49 ++++++-- toolchain/private/defs.bzl | 4 + toolchain/toolchain/defs.bzl | 57 ++++++--- 20 files changed, 400 insertions(+), 123 deletions(-) create mode 100755 ci/list_toolchains_platforms create mode 100644 test/c/BUILD create mode 100644 test/c/main.c create mode 100755 test/c/test.sh create mode 100644 toolchain/libc/BUILD create mode 100644 toolchain/libc/defs.bzl create mode 100644 toolchain/libc_aware/platform/BUILD create mode 100644 toolchain/libc_aware/toolchain/BUILD diff --git a/.bazelrc b/.bazelrc index c564a31..3cf6b94 100644 --- a/.bazelrc +++ b/.bazelrc @@ -6,7 +6,4 @@ build --worker_sandboxing build --compilation_mode=opt build --incompatible_enable_cc_toolchain_resolution -build --extra_toolchains @zig_sdk//toolchain:linux_amd64_gnu.2.19 -build --extra_toolchains @zig_sdk//toolchain:linux_arm64_gnu.2.28 -build --extra_toolchains @zig_sdk//toolchain:darwin_amd64 -build --extra_toolchains @zig_sdk//toolchain:darwin_arm64 +build --action_env BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 diff --git a/.build.yml b/.build.yml index 7eb773f..785b50f 100644 --- a/.build.yml +++ b/.build.yml @@ -20,10 +20,7 @@ tasks: sudo apt-get install libc6:arm64 -y - list_toolchains_platforms: | cd bazel-zig-cc; . .envrc - echo "Available toolchains:" - bazel query @zig_sdk//toolchain:* - echo "Available platforms:" - bazel query @zig_sdk//platform:* + ./ci/list_toolchains_platforms - test: | cd bazel-zig-cc; . .envrc ./ci/test --color=yes --curses=yes diff --git a/README.md b/README.md index f6a7538..f3464db 100644 --- a/README.md +++ b/README.md @@ -23,67 +23,168 @@ http_archive( 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_register_toolchains = "register_toolchains") +load("@bazel-zig-cc//toolchain:defs.bzl", zig_toolchains = "toolchains") -zig_register_toolchains() - -# Or, if you are using this in production, you probably want more control: -zig_register_toolchains( - version = "<...>", - url_formats = [ - "https://example.internal/zig/zig-{host_platform}-{version}.tar.xz", - ], - host_platform_sha256 = { ... }, -) +zig_toolchains() ``` +> ### zig sdk download control +> +> If you are using this in production, you probably want more control over +> where the zig sdk is downloaded from: +> ``` +> zig_register_toolchains( +> version = "<...>", +> url_formats = [ +> "https://example.internal/zig/zig-{host_platform}-{version}.tar.xz", +> ], +> host_platform_sha256 = { ... }, +> ) +> ``` + And this to `.bazelrc`: ``` build --incompatible_enable_cc_toolchain_resolution -build --extra_toolchains @zig_sdk//toolchain:linux_amd64_gnu.2.19 -build --extra_toolchains @zig_sdk//toolchain:linux_arm64_gnu.2.28 -build --extra_toolchains @zig_sdk//toolchain:darwin_amd64 -build --extra_toolchains @zig_sdk//toolchain:darwin_arm64 +``` + +The snippets above will download the zig toolchain and make the bazel +toolchains available for registration and usage. + +The next steps depend on your use case. + +## I want to manually build a single target with a specific zig cc toolchain + +You may explicitly request Bazel to use a specific toolchain (compatible with +the specified platform). For example, if you wish to compile a specific binary +(or run tests) on linux/amd64/musl, you may specify: + +``` +bazel build \ + --platforms @zig_sdk//platform:linux_arm64 \ + --extra_toolchains @zig_sdk//toolchain:linux_arm64_musl \ + //test/go:go +``` + +This registers the toolchain `@zig_sdk//toolchain:linux_arm64_musl` for linux +arm64 targets. This toolchains links code statically with musl. We also specify +that we want to build //test/go:go for linux arm64. + +## I want to use zig cc as the default compiler + +Replace the call to `zig_register_toolchains` with +``` +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", +) ``` The snippets above will download the zig toolchain and register it for the -following platforms: +following configurations: -- `x86_64-linux-gnu.2.19` for `["@platforms//os:linux", "@platforms//cpu:x86_64"]`. -- `x86_64-linux-gnu.2.28` for `["@platforms//os:linux", "@platforms//cpu:aarch64"]`. -- `x86_64-macos-gnu` for `["@platforms//os:macos", "@platforms//cpu:x86_64"]`. -- `aarch64-macos-gnu` for `["@platforms//os:macos", "@platforms//cpu:aarch64"]`. +- `toolchain:linux_amd64_gnu.2.19` for `["@platforms//os:linux", "@platforms//cpu:x86_64", "@zig_sdk//libc:unconstrained"]`. +- `toolchain:linux_arm64_gnu.2.28` for `["@platforms//os:linux", "@platforms//cpu:aarch64", "@zig_sdk//libc:unconstrained"]`. +- `toolchain:darwin_arm64` for `["@platforms//os:macos", "@platforms//cpu:x86_64"]`. +- `toolchain:darwin_arm64` for `["@platforms//os:macos", "@platforms//cpu:aarch64"]`. -Note that both Go and Bazel naming schemes are accepted. For convenience with -Go, the following Go-style toolchain aliases are created: +> ### 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/... +> ``` -|Bazel (zig) name | Go name | -|---------------- | -------- | -|`x86_64` | `amd64` | -|`aarch64` | `arm64` | -|`macos` | `darwin` | +> ### Disabling the default bazel cc toolchain +> +> It may be useful to disable the default toolchain that bazel configures for +> you, so that configuration issues can be caught early on: +> +> .bazelrc +> ``` +> build:zig_cc --action_env BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 +> ``` +> +> This is not documented in bazel, so use at your own peril. -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: +## I want to start using the zig cc toolchain gradually +You can register your zig cc toolchains under a config in your .bazelrc ``` -$ bazel query @zig_sdk//toolchain/... +build:zig_cc --extra_toolchains @zig_sdk//toolchain:linux_amd64_gnu.2.19 +build:zig_cc --extra_toolchains @zig_sdk//toolchain:linux_arm64_gnu.2.28 +build:zig_cc --extra_toolchains @zig_sdk//toolchain:darwin_amd64 +build:zig_cc --extra_toolchains @zig_sdk//toolchain:darwin_arm64 ``` -## Specifying non-default toolchains - -You may explicitly request Bazel to use a specific toolchain, even though a -different one is registered using `--extra_toolchains ` in -`.bazelrc`. For example, if you wish to compile a specific binary (or run -tests) on linux/amd64/musl, you may specify: - +Then for your builds/tests you need to specify that the `zig_cc` config +should be used: ``` ---extra_toolchains @zig_sdk//toolchain:linux_amd64_musl +bazel build --config zig_cc //test/go:go ``` -## UBSAN and "SIGILL: Illegal Instruction" +You can build a target for a different platform like so: +``` +bazel build --config zig_cc \ + --platforms @zig_sdk//platform:linux_arm64 \ + //test/go:go +``` + +## I want to use zig to build targets for multiple libc variants + +If you have targets that need to be build with different glibc versions or with +musl, you can register a linux toolchain declared under `libc_aware/toolchains`. +It will only be selected when building for a specific libc version. For example + +- `libc_aware/toolchain:linux_amd64_gnu.2.19` for `["@platforms//os:linux", "@platforms//cpu:x86_64", "@zig_sdk//libc:gnu.2.19"]`. +- `libc_aware/toolchain:linux_amd64_gnu.2.28` for `["@platforms//os:linux", "@platforms//cpu:x86_64", "@zig_sdk//libc:gnu.2.28"]`. +- `libc_aware/toolchain:x86_64-linux-musl` for `["@platforms//os:linux", "@platforms//cpu:x86_64", "@zig_sdk//libc:musl"]`. + +With these toolchains registered, you can build a project for a specific libc +aware platform: +``` +$ bazel build --platforms @zig_sdk//libc_aware/platform:linux_amd64_gnu.2.19 //test/go:go +$ bazel build --platforms @zig_sdk//libc_aware/platform:linux_amd64_gnu.2.28 //test/go:go +$ bazel build --platforms @zig_sdk//libc_aware/platform:linux_amd64_musl //test/go:go +``` + +You can see the list of libc aware toolchains and platforms by running: +``` +$ bazel query @zig_sdk//libc_aware/toolchain/... +$ bazel query @zig_sdk//libc_aware/platform/... + ``` + +This is especially useful if you are relying on [transitions][transitions], as +transitioning `extra_platforms` will cause your host tools to be rebuilt with +the specific libc version, which takes time, and your host may not be able to +run them. + +The `@zig_sdk//libc:variant` constraint is used to select a matching toolchain. +If you are using your own 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. + +# 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 @@ -200,3 +301,4 @@ the issues promptly. [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 diff --git a/WORKSPACE b/WORKSPACE index 54d84a0..759d198 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -44,7 +44,14 @@ gazelle_dependencies(go_repository_default_config = "@//:WORKSPACE") load( "//toolchain:defs.bzl", - zig_register_toolchains = "register_toolchains", + zig_toolchains = "toolchains", ) -zig_register_toolchains() +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", +) diff --git a/ci/list_toolchains_platforms b/ci/list_toolchains_platforms new file mode 100755 index 0000000..a67735f --- /dev/null +++ b/ci/list_toolchains_platforms @@ -0,0 +1,15 @@ +#!/bin/bash +set -euo pipefail + +indent() { sed 's/^/ /'; } + +echo "Available toolchains:" +bazel query '@zig_sdk//toolchain:*' | indent +echo "Available platforms:" +bazel query '@zig_sdk//platform:*' | indent +echo "Available libc aware toolchains:" +bazel query '@zig_sdk//libc_aware/toolchain:*' | indent +echo "Available libc aware platforms:" +bazel query '@zig_sdk//libc_aware/platform:*' | indent +echo "Available libc variants:" +bazel query "attr(constraint_setting, @zig_sdk//libc:variant, @zig_sdk//...)" | indent diff --git a/rules/platform.bzl b/rules/platform.bzl index 73ea7ca..268dfd6 100644 --- a/rules/platform.bzl +++ b/rules/platform.bzl @@ -1,8 +1,8 @@ def _platform_transition_impl(settings, attr): _ignore = settings return { - "//command_line_option:platforms": "@zig_sdk//platform:{}".format(attr.platform), - "//command_line_option:extra_toolchains": ["@zig_sdk//toolchain:{}".format(tc) for tc in attr.extra_toolchains], + "//command_line_option:platforms": "@zig_sdk{}".format(attr.platform), + "//command_line_option:extra_toolchains": ["@zig_sdk{}".format(tc) for tc in attr.extra_toolchains], } _platform_transition = transition( diff --git a/test/c/BUILD b/test/c/BUILD new file mode 100644 index 0000000..69d259e --- /dev/null +++ b/test/c/BUILD @@ -0,0 +1,46 @@ +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", + extra_toolchains = [ + # toolchains for specific libc versions + "//libc_aware/toolchain:linux_amd64_gnu.2.19", + "//libc_aware/toolchain:linux_amd64_gnu.2.28", + "//libc_aware/toolchain:linux_amd64_gnu.2.31", + "//libc_aware/toolchain:linux_amd64_musl", + # fallback toolchains + "//toolchain:linux_amd64_gnu.2.19", + "//toolchain:linux_arm64_gnu.2.28", + ], + platform = platform, + ), + 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_amd64_gnu.2.31", "//libc_aware/platform:linux_amd64_gnu.2.31", "glibc_2.31"), + ("linux_amd64", "//platform:linux_amd64", "glibc_2.19"), + ("linux_arm64", "//platform:linux_arm64", "glibc_2.28"), + ] +] diff --git a/test/c/main.c b/test/c/main.c new file mode 100644 index 0000000..9ab0dc1 --- /dev/null +++ b/test/c/main.c @@ -0,0 +1,11 @@ +#include +#include + +int main() { + #ifdef __GLIBC__ + printf("glibc_%d.%d", __GLIBC__, __GLIBC_MINOR__); + #else + puts("non-glibc"); + #endif + return 0; +} diff --git a/test/c/test.sh b/test/c/test.sh new file mode 100755 index 0000000..d5331ff --- /dev/null +++ b/test/c/test.sh @@ -0,0 +1,16 @@ +#/bin/bash + +set -euo pipefail + +want=$WANT +binary=$BINARY + +got=$($binary) + +if [[ "$got" != "$want" ]]; then + echo wanted: + echo \ \ "$want" + echo got: + echo \ \ "$got" + exit 1 +fi diff --git a/test/go/BUILD b/test/go/BUILD index 7dba4ab..e5206fd 100644 --- a/test/go/BUILD +++ b/test/go/BUILD @@ -26,8 +26,8 @@ go_test( platform_binary( name = "go_{}".format(toolchain), src = "go", - extra_toolchains = [toolchain], - platform = platform, + extra_toolchains = ["//toolchain:{}".format(toolchain)], + platform = "//platform:{}".format(platform), ) for platform, toolchain in [ ("linux_amd64", "linux_amd64_musl"), @@ -42,8 +42,8 @@ go_test( platform_test( name = "go_test_{}".format(toolchain), src = "go_test", - extra_toolchains = [toolchain], - platform = platform, + extra_toolchains = ["//toolchain:{}".format(toolchain)], + platform = "//platform:{}".format(platform), ) for platform, toolchain in [ ("linux_amd64", "linux_amd64_musl"), diff --git a/toolchain/BUILD.sdk.bazel b/toolchain/BUILD.sdk.bazel index cf5dc4a..ada3e5d 100644 --- a/toolchain/BUILD.sdk.bazel +++ b/toolchain/BUILD.sdk.bazel @@ -4,6 +4,8 @@ package( default_visibility = ["//visibility:public"], ) + + declare_files( zig_include_root = {zig_include_root}, ) diff --git a/toolchain/defs.bzl b/toolchain/defs.bzl index 9e34e6b..696931a 100644 --- a/toolchain/defs.bzl +++ b/toolchain/defs.bzl @@ -1,4 +1,3 @@ -load("@bazel_skylib//lib:shell.bzl", "shell") 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", "DEFAULT_INCLUDE_DIRECTORIES", "ZIG_TOOL_PATH", "target_structs") @@ -42,15 +41,15 @@ _HOST_PLATFORM_SHA256 = { "macos-x86_64": "78220a4460a7c0f563d7365313fcd3ea028ed38166ebac55ba22f17ab6404851", } -def register_toolchains( - register = [], +def toolchains( version = _VERSION, url_formats = [URL_FORMAT_JAKSTYS], host_platform_sha256 = _HOST_PLATFORM_SHA256): """ - Download zig toolchain and register some. - @param register registers the given toolchains to the system using - native.register_toolchains(). See README for possible choices. + 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", @@ -65,9 +64,6 @@ def register_toolchains( }, ) - toolchains = ["@zig_sdk//toolchain:%s" % t for t in register] - native.register_toolchains(*toolchains) - ZIG_TOOL_WRAPPER = """#!/bin/bash set -e @@ -95,6 +91,9 @@ _ZIG_TOOLS = [ "wasm-ld", # WebAssembly ] +def _quote(s): + return "'" + s.replace("'", "'\\''") + "'" + def _zig_repository_impl(repository_ctx): arch = repository_ctx.os.arch if arch == "amd64": @@ -139,34 +138,28 @@ def _zig_repository_impl(repository_ctx): content = _fcntl_h, ) - repository_ctx.symlink( - Label("//toolchain/platform:BUILD"), - "platform/BUILD", - ) + 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) - repository_ctx.template( - "BUILD", - Label("//toolchain:BUILD.sdk.bazel"), - executable = False, - substitutions = { - "{zig_include_root}": shell.quote(zig_include_root), - }, - ) - - repository_ctx.symlink( - Label("//toolchain/toolchain:BUILD"), - "toolchain/BUILD", - ) - - repository_ctx.template( - "private/BUILD", - Label("//toolchain/private:BUILD.sdk.bazel"), - executable = False, - substitutions = { - "{absolute_path}": shell.quote(str(repository_ctx.path(""))), - "{zig_include_root}": shell.quote(zig_include_root), - }, - ) + 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 = { + "{absolute_path}": _quote(str(repository_ctx.path(""))), + "{zig_include_root}": _quote(zig_include_root), + }, + ) zig_repository = repository_rule( attrs = { diff --git a/toolchain/libc/BUILD b/toolchain/libc/BUILD new file mode 100644 index 0000000..e2713f8 --- /dev/null +++ b/toolchain/libc/BUILD @@ -0,0 +1,17 @@ +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() diff --git a/toolchain/libc/defs.bzl b/toolchain/libc/defs.bzl new file mode 100644 index 0000000..58532b0 --- /dev/null +++ b/toolchain/libc/defs.bzl @@ -0,0 +1,8 @@ +load("@bazel-zig-cc//toolchain/private:defs.bzl", "LIBCS") + +def declare_libcs(): + for libc in LIBCS: + native.constraint_value( + name = libc, + constraint_setting = "variant", + ) diff --git a/toolchain/libc_aware/platform/BUILD b/toolchain/libc_aware/platform/BUILD new file mode 100644 index 0000000..b34acc6 --- /dev/null +++ b/toolchain/libc_aware/platform/BUILD @@ -0,0 +1,7 @@ +load("@bazel-zig-cc//toolchain/platform:defs.bzl", "declare_libc_aware_platforms") + +package( + default_visibility = ["//visibility:public"], +) + +declare_libc_aware_platforms() diff --git a/toolchain/libc_aware/toolchain/BUILD b/toolchain/libc_aware/toolchain/BUILD new file mode 100644 index 0000000..98065fd --- /dev/null +++ b/toolchain/libc_aware/toolchain/BUILD @@ -0,0 +1,7 @@ +load("@bazel-zig-cc//toolchain/toolchain:defs.bzl", "declare_libc_aware_toolchains") + +package( + default_visibility = ["//visibility:public"], +) + +declare_libc_aware_toolchains() diff --git a/toolchain/platform/BUILD b/toolchain/platform/BUILD index ce0ffc7..f43b852 100644 --- a/toolchain/platform/BUILD +++ b/toolchain/platform/BUILD @@ -1,3 +1,7 @@ load("@bazel-zig-cc//toolchain/platform:defs.bzl", "declare_platforms") +package( + default_visibility = ["//visibility:public"], +) + declare_platforms() diff --git a/toolchain/platform/defs.bzl b/toolchain/platform/defs.bzl index e094029..cf42191 100644 --- a/toolchain/platform/defs.bzl +++ b/toolchain/platform/defs.bzl @@ -1,17 +1,40 @@ +load("@bazel-zig-cc//toolchain/private:defs.bzl", "LIBCS") + +_CPUS = (("x86_64", "amd64"), ("aarch64", "arm64")) + def declare_platforms(): # create @zig_sdk//{os}_{arch}_platform entries with zig and go conventions - for zigcpu, gocpu in (("x86_64", "amd64"), ("aarch64", "arm64")): + for zigcpu, gocpu in _CPUS: for bzlos, oss in {"linux": ["linux"], "macos": ["macos", "darwin"]}.items(): for os in oss: - constraint_values = [ - "@platforms//os:{}".format(bzlos), - "@platforms//cpu:{}".format(zigcpu), - ] - native.platform( - name = "{os}_{zigcpu}".format(os = os, zigcpu = zigcpu), - constraint_values = constraint_values, - ) - native.platform( - name = "{os}_{gocpu}".format(os = os, gocpu = gocpu), - constraint_values = constraint_values, - ) + 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, + ) diff --git a/toolchain/private/defs.bzl b/toolchain/private/defs.bzl index 8380071..671a81a 100644 --- a/toolchain/private/defs.bzl +++ b/toolchain/private/defs.bzl @@ -28,6 +28,8 @@ _GLIBCS = [ "2.34", ] +LIBCS = ["musl", "gnu"] + ["gnu.{}".format(glibc) for glibc in _GLIBCS] + def target_structs(): ret = [] for zigcpu, gocpu in (("x86_64", "amd64"), ("aarch64", "arm64")): @@ -100,6 +102,7 @@ def _target_linux_gnu(gocpu, zigcpu, glibc_version = ""): "@platforms//os:linux", "@platforms//cpu:{}".format(zigcpu), ], + libc_constraint = "@zig_sdk//libc:{}".format(glibc_suffix), tool_paths = {"ld": "ld.lld"}, ) @@ -120,5 +123,6 @@ def _target_linux_musl(gocpu, zigcpu): "@platforms//os:linux", "@platforms//cpu:{}".format(zigcpu), ], + libc_constraint = "@zig_sdk//libc:musl", tool_paths = {"ld": "ld.lld"}, ) diff --git a/toolchain/toolchain/defs.bzl b/toolchain/toolchain/defs.bzl index 2c9bf96..1d99a61 100644 --- a/toolchain/toolchain/defs.bzl +++ b/toolchain/toolchain/defs.bzl @@ -1,25 +1,46 @@ -load("@bazel-zig-cc//toolchain/private:defs.bzl", "DEFAULT_INCLUDE_DIRECTORIES", "ZIG_TOOL_PATH", "target_structs") +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 - # 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_config.constraint_values, - toolchain = "@zig_sdk//private:%s_cc" % zigtarget, - toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", - ) + # 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"] - # Zig convention: x86_64/aarch64, linux/macos - native.toolchain( - name = zigtarget, - exec_compatible_with = None, - target_compatible_with = target_config.constraint_values, - toolchain = "@zig_sdk//private:%s_cc" % zigtarget, - toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", - ) + _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//private:%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//private:%s_cc" % zigtarget, + toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", + ) From 0c02a827ae55486cee2185bf26305fe3eb5cba3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Fri, 15 Apr 2022 15:20:42 +0300 Subject: [PATCH 3/6] README prose, the way I understand it --- README.md | 298 ++++++++++++++++++++++++------------- WORKSPACE | 9 ++ test/c/main.c | 4 +- toolchain/private/defs.bzl | 2 +- 4 files changed, 207 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index f3464db..2534f59 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,21 @@ # Bazel zig cc toolchain This is a C/C++ toolchain that can (cross-)compile C/C++ programs. It contains -clang-13, musl, glibc (versions 2-2.34, selectable), all in a ~40MB package. -Read +clang-13, musl, glibc 2-2.35, 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`: @@ -25,23 +34,18 @@ http_archive( load("@bazel-zig-cc//toolchain:defs.bzl", zig_toolchains = "toolchains") -zig_toolchains() +# version, url_formats and host_platform_sha256 are optional, but highly +# recommended. Zig SDK is by default downloaded from dl.jakstys.lt, which is a +# tiny server in the closet of Yours Truly. +zig_toolchains( + version = "<...>", + url_formats = [ + "https://example.org/zig/zig-{host_platform}-{version}.tar.xz", + ], + host_platform_sha256 = { ... }, +) ``` -> ### zig sdk download control -> -> If you are using this in production, you probably want more control over -> where the zig sdk is downloaded from: -> ``` -> zig_register_toolchains( -> version = "<...>", -> url_formats = [ -> "https://example.internal/zig/zig-{host_platform}-{version}.tar.xz", -> ], -> host_platform_sha256 = { ... }, -> ) -> ``` - And this to `.bazelrc`: ``` @@ -49,15 +53,20 @@ build --incompatible_enable_cc_toolchain_resolution ``` The snippets above will download the zig toolchain and make the bazel -toolchains available for registration and usage. +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. -The next steps depend on your use case. +## Use case: manually build a single target with a specific zig cc toolchain -## I want to 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. -You may explicitly request Bazel to use a specific toolchain (compatible with -the specified platform). For example, if you wish to compile a specific binary -(or run tests) on linux/amd64/musl, you may specify: +To request Bazel to use a specific toolchain (compatible with the specified +platform) for build/tests/whatever on linux-amd64-musl, do: ``` bazel build \ @@ -66,13 +75,82 @@ bazel build \ //test/go:go ``` -This registers the toolchain `@zig_sdk//toolchain:linux_arm64_musl` for linux -arm64 targets. This toolchains links code statically with musl. We also specify -that we want to build //test/go:go for linux arm64. +There are a few things going on here, let's try to dissect them. -## I want to use zig cc as the default compiler +### 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: + +``` +$ 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//private:aarch64-linux-musl_cc", +) +``` + +The above means toolchain is compatible with platforms that include +`@platforms//os:linux`, `@platforms//cpu:aarch64` (an alias to +`@platforms//cpu:arm64`) and `@zig_sdk//libc:unconstrained`. For a platform to +pick up the right toolchain, the toolchain's `target_compatible_with` must be +equivalent or a superset to the platforms `constraint_values`. Since the +toolchain is a superset (therefore, `libc:unconstrained` does not matter here), +the platform is compatible with this toolchain. 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(...)`: -Replace the call to `zig_register_toolchains` with ``` register_toolchains( "@zig_sdk//toolchain:linux_amd64_gnu.2.19", @@ -82,100 +160,93 @@ register_toolchains( ) ``` -The snippets above will download the zig toolchain and register it for the -following configurations: +Append this to `.bazelrc`: -- `toolchain:linux_amd64_gnu.2.19` for `["@platforms//os:linux", "@platforms//cpu:x86_64", "@zig_sdk//libc:unconstrained"]`. -- `toolchain:linux_arm64_gnu.2.28` for `["@platforms//os:linux", "@platforms//cpu:aarch64", "@zig_sdk//libc:unconstrained"]`. -- `toolchain:darwin_arm64` for `["@platforms//os:macos", "@platforms//cpu:x86_64"]`. -- `toolchain:darwin_arm64` for `["@platforms//os:macos", "@platforms//cpu:aarch64"]`. - -> ### 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/... -> ``` - -> ### Disabling the default bazel cc toolchain -> -> It may be useful to disable the default toolchain that bazel configures for -> you, so that configuration issues can be caught early on: -> -> .bazelrc -> ``` -> build:zig_cc --action_env BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 -> ``` -> -> This is not documented in bazel, so use at your own peril. - -## I want to start using the zig cc toolchain gradually - -You can register your zig cc toolchains under a config in your .bazelrc ``` -build:zig_cc --extra_toolchains @zig_sdk//toolchain:linux_amd64_gnu.2.19 -build:zig_cc --extra_toolchains @zig_sdk//toolchain:linux_arm64_gnu.2.28 -build:zig_cc --extra_toolchains @zig_sdk//toolchain:darwin_amd64 -build:zig_cc --extra_toolchains @zig_sdk//toolchain:darwin_arm64 +build --action_env BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 ``` -Then for your builds/tests you need to specify that the `zig_cc` config -should be used: +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`: + ``` -bazel build --config zig_cc //test/go:go +register_toolchains( + "@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:x86_64-linux-musl", +) ``` -You can build a target for a different platform like so: +What does `@zig_sdk//libc_aware/toolchain:linux_amd64_gnu.2.19` mean? + ``` -bazel build --config zig_cc \ - --platforms @zig_sdk//platform:linux_arm64 \ - //test/go:go +$ 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"], ``` -## I want to use zig to build targets for multiple libc variants +To see how this relates to the platform: -If you have targets that need to be build with different glibc versions or with -musl, you can register a linux toolchain declared under `libc_aware/toolchains`. -It will only be selected when building for a specific libc version. For example - -- `libc_aware/toolchain:linux_amd64_gnu.2.19` for `["@platforms//os:linux", "@platforms//cpu:x86_64", "@zig_sdk//libc:gnu.2.19"]`. -- `libc_aware/toolchain:linux_amd64_gnu.2.28` for `["@platforms//os:linux", "@platforms//cpu:x86_64", "@zig_sdk//libc:gnu.2.28"]`. -- `libc_aware/toolchain:x86_64-linux-musl` for `["@platforms//os:linux", "@platforms//cpu:x86_64", "@zig_sdk//libc:musl"]`. - -With these toolchains registered, you can build a project for a specific libc -aware platform: ``` -$ bazel build --platforms @zig_sdk//libc_aware/platform:linux_amd64_gnu.2.19 //test/go:go -$ bazel build --platforms @zig_sdk//libc_aware/platform:linux_amd64_gnu.2.28 //test/go:go -$ bazel build --platforms @zig_sdk//libc_aware/platform:linux_amd64_musl //test/go:go +$ 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"], ``` -You can see the list of libc aware toolchains and platforms by running: +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/... ``` -This is especially useful if you are relying on [transitions][transitions], as -transitioning `extra_platforms` will cause your host tools to be rebuilt with -the specific libc version, which takes time, and your host may not be able to -run them. +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: -The `@zig_sdk//libc:variant` constraint is used to select a matching toolchain. -If you are using your own 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//...)" ``` @@ -183,8 +254,28 @@ $ 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! -# UBSAN and "SIGILL: Illegal Instruction" +# 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/... +``` + +# Note: 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 @@ -197,6 +288,7 @@ SIGILL: illegal instruction This is by design: it 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 diff --git a/WORKSPACE b/WORKSPACE index 759d198..3ac9b8b 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -50,8 +50,17 @@ load( zig_toolchains() register_toolchains( + # if no `--platform` is selected, these toolchains will be used. "@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", + + # when a libc-aware platform is selected, these will be used. arm64: + "@zig_sdk//libc_aware/toolchain:linux_arm64_gnu.2.28", + "@zig_sdk//libc_aware/toolchain:linux_arm64_musl", + # ditto, amd64: + "@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_musl", ) diff --git a/test/c/main.c b/test/c/main.c index 9ab0dc1..9570a6c 100644 --- a/test/c/main.c +++ b/test/c/main.c @@ -3,9 +3,9 @@ int main() { #ifdef __GLIBC__ - printf("glibc_%d.%d", __GLIBC__, __GLIBC_MINOR__); + printf("glibc_%d.%d\n", __GLIBC__, __GLIBC_MINOR__); #else - puts("non-glibc"); + printf("non-glibc\n"); #endif return 0; } diff --git a/toolchain/private/defs.bzl b/toolchain/private/defs.bzl index 671a81a..25c3f5c 100644 --- a/toolchain/private/defs.bzl +++ b/toolchain/private/defs.bzl @@ -28,7 +28,7 @@ _GLIBCS = [ "2.34", ] -LIBCS = ["musl", "gnu"] + ["gnu.{}".format(glibc) for glibc in _GLIBCS] +LIBCS = ["musl"] + ["gnu.{}".format(glibc) for glibc in _GLIBCS] def target_structs(): ret = [] From efca6e6428a895d55ada4f4f1852139619dffa2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Mon, 18 Apr 2022 11:44:51 +0300 Subject: [PATCH 4/6] remove support for glibc autodetection; this is a hermetic toolchain --- toolchain/private/defs.bzl | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/toolchain/private/defs.bzl b/toolchain/private/defs.bzl index 25c3f5c..6059065 100644 --- a/toolchain/private/defs.bzl +++ b/toolchain/private/defs.bzl @@ -35,7 +35,7 @@ def target_structs(): for zigcpu, gocpu in (("x86_64", "amd64"), ("aarch64", "arm64")): ret.append(_target_darwin(gocpu, zigcpu)) ret.append(_target_linux_musl(gocpu, zigcpu)) - for glibc in [""] + _GLIBCS: + for glibc in _GLIBCS: ret.append(_target_linux_gnu(gocpu, zigcpu, glibc)) return ret @@ -63,24 +63,12 @@ def _target_darwin(gocpu, zigcpu): tool_paths = {"ld": "ld64.lld"}, ) -def _target_linux_gnu(gocpu, zigcpu, glibc_version = ""): - glibc_suffix = "gnu" - if glibc_version != "": - glibc_suffix = "gnu.{}".format(glibc_version) +def _target_linux_gnu(gocpu, zigcpu, glibc_version): + glibc_suffix = "gnu.{}".format(glibc_version) # https://github.com/ziglang/zig/issues/5882#issuecomment-888250676 - # fcntl_hack is only required for glibc 2.27 or less. We assume that - # glibc_version == "" (autodetect) is running a recent glibc version, thus - # adding this hack only when glibc is explicitly 2.27 or lower. - fcntl_hack = False - if glibc_version == "": - # zig doesn't reliably detect the glibc version, so - # often falls back to 2.17; the hack should be included. - # https://github.com/ziglang/zig/issues/6469 - fcntl_hack = True - else: - # hack is required for 2.27 or less. - fcntl_hack = glibc_version < "2.28" + # fcntl_hack is only required for glibc 2.27 or less. + fcntl_hack = glibc_version < "2.28" return struct( gotarget = "linux_{}_{}".format(gocpu, glibc_suffix), From a53170cf82c0d24681526efebc04ec11db8bfbf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Mon, 18 Apr 2022 11:58:43 +0300 Subject: [PATCH 5/6] ci/list* and ci/test: simple style improvements --- ci/list_toolchains_platforms | 10 +++++----- ci/test | 5 ++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/ci/list_toolchains_platforms b/ci/list_toolchains_platforms index a67735f..a7d0342 100755 --- a/ci/list_toolchains_platforms +++ b/ci/list_toolchains_platforms @@ -4,12 +4,12 @@ set -euo pipefail indent() { sed 's/^/ /'; } echo "Available toolchains:" -bazel query '@zig_sdk//toolchain:*' | indent +bazel query --noshow_progress '@zig_sdk//toolchain:*' | indent echo "Available platforms:" -bazel query '@zig_sdk//platform:*' | indent +bazel query --noshow_progress '@zig_sdk//platform:*' | indent echo "Available libc aware toolchains:" -bazel query '@zig_sdk//libc_aware/toolchain:*' | indent +bazel query --noshow_progress '@zig_sdk//libc_aware/toolchain:*' | indent echo "Available libc aware platforms:" -bazel query '@zig_sdk//libc_aware/platform:*' | indent +bazel query --noshow_progress '@zig_sdk//libc_aware/platform:*' | indent echo "Available libc variants:" -bazel query "attr(constraint_setting, @zig_sdk//libc:variant, @zig_sdk//...)" | indent +bazel query --noshow_progress "attr(constraint_setting, @zig_sdk//libc:variant, @zig_sdk//...)" | indent diff --git a/ci/test b/ci/test index 5e27913..debdc0b 100755 --- a/ci/test +++ b/ci/test @@ -1,4 +1,3 @@ -#!/bin/bash -set -euo pipefail +#!/bin/sh -bazel test ... +exec bazel test ... From 418b589ed23a3789b2fc3bb17b717a4889f00442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Mon, 18 Apr 2022 12:17:03 +0300 Subject: [PATCH 6/6] example transitions only change platforms, leave toolchains for WORKSPACE --- WORKSPACE | 12 +++++++----- rules/platform.bzl | 11 ++--------- test/c/BUILD | 10 ---------- test/go/BUILD | 32 +++++++++++++++----------------- 4 files changed, 24 insertions(+), 41 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 3ac9b8b..afa9b8a 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -50,17 +50,19 @@ load( zig_toolchains() register_toolchains( - # if no `--platform` is selected, these toolchains will be used. + # if no `--platform` is specified, these toolchains will be used for + # (linux,darwin)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", - # when a libc-aware platform is selected, these will be used. arm64: - "@zig_sdk//libc_aware/toolchain:linux_arm64_gnu.2.28", - "@zig_sdk//libc_aware/toolchain:linux_arm64_musl", - # ditto, amd64: + # 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", ) diff --git a/rules/platform.bzl b/rules/platform.bzl index 268dfd6..aabb328 100644 --- a/rules/platform.bzl +++ b/rules/platform.bzl @@ -2,7 +2,6 @@ def _platform_transition_impl(settings, attr): _ignore = settings return { "//command_line_option:platforms": "@zig_sdk{}".format(attr.platform), - "//command_line_option:extra_toolchains": ["@zig_sdk{}".format(tc) for tc in attr.extra_toolchains], } _platform_transition = transition( @@ -10,7 +9,6 @@ _platform_transition = transition( inputs = [], outputs = [ "//command_line_option:platforms", - "//command_line_option:extra_toolchains", ], ) @@ -40,16 +38,12 @@ _attrs = { "platform": attr.string( doc = "The platform to build the target for.", ), - "extra_toolchains": attr.string_list( - doc = "The toolchains to provide as extra_toolchains.", - ), "_allowlist_function_transition": attr.label( default = "@bazel_tools//tools/allowlists/function_transition_allowlist", ), } -# wrap a single exectable and build it for the specified platform passing in -# the extra_toolchains. +# wrap a single exectable and build it for the specified platform. platform_binary = rule( implementation = _platform_binary_impl, cfg = _platform_transition, @@ -57,8 +51,7 @@ platform_binary = rule( executable = True, ) -# wrap a single test target and build it for the specified platform passing in -# the extra_toolchains. +# wrap a single test target and build it for the specified platform. platform_test = rule( implementation = _platform_binary_impl, cfg = _platform_transition, diff --git a/test/c/BUILD b/test/c/BUILD index 69d259e..99e36c4 100644 --- a/test/c/BUILD +++ b/test/c/BUILD @@ -13,16 +13,6 @@ cc_binary( platform_binary( name = "which_libc_{}".format(name), src = "which_libc", - extra_toolchains = [ - # toolchains for specific libc versions - "//libc_aware/toolchain:linux_amd64_gnu.2.19", - "//libc_aware/toolchain:linux_amd64_gnu.2.28", - "//libc_aware/toolchain:linux_amd64_gnu.2.31", - "//libc_aware/toolchain:linux_amd64_musl", - # fallback toolchains - "//toolchain:linux_amd64_gnu.2.19", - "//toolchain:linux_arm64_gnu.2.28", - ], platform = platform, ), sh_test( diff --git a/test/go/BUILD b/test/go/BUILD index e5206fd..6c40c2e 100644 --- a/test/go/BUILD +++ b/test/go/BUILD @@ -24,31 +24,29 @@ go_test( [ platform_binary( - name = "go_{}".format(toolchain), + name = "go_{}".format(name), src = "go", - extra_toolchains = ["//toolchain:{}".format(toolchain)], - platform = "//platform:{}".format(platform), + platform = platform, ) - for platform, toolchain in [ - ("linux_amd64", "linux_amd64_musl"), - ("linux_amd64", "linux_amd64_gnu.2.19"), - ("linux_arm64", "linux_arm64_musl"), - ("linux_arm64", "linux_arm64_gnu.2.28"), - ("darwin_amd64", "darwin_amd64"), + 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 = "go_test_{}".format(toolchain), + name = "go_test_{}".format(name), src = "go_test", - extra_toolchains = ["//toolchain:{}".format(toolchain)], - platform = "//platform:{}".format(platform), + platform = platform, ) - for platform, toolchain in [ - ("linux_amd64", "linux_amd64_musl"), - ("linux_amd64", "linux_amd64_gnu.2.19"), - ("linux_arm64", "linux_arm64_musl"), - ("linux_arm64", "linux_arm64_gnu.2.28"), + 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"), ] ]