1

Merge branch 'laurynasl_libc'

This commit is contained in:
Motiejus Jakštys 2022-04-20 12:24:05 +03:00
commit c2c6edd11c
28 changed files with 638 additions and 237 deletions

View File

@ -6,10 +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
test:qemu-aarch64 --test_env=QEMU_LD_PREFIX=/usr/aarch64-linux-gnu/
test:qemu-aarch64 --run_under=qemu-aarch64-static
build --action_env BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1

View File

@ -3,7 +3,6 @@ packages:
- direnv
- shellcheck
- qemu-user-static
- libc6-arm64-cross
- binfmt-support
- moreutils
sources:
@ -17,19 +16,18 @@ 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
./ci/list_toolchains_platforms
- test: |
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
./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
- test_release: |
cd bazel-zig-cc; . .envrc

44
.envrc
View File

@ -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"

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
*.sw[op]
/bin/bazel
/bin/buildifier
/bin/bazelisk-*
/bazel-bazel-zig-cc

3
BUILD
View File

@ -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")

304
README.md
View File

@ -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`:
@ -23,15 +32,15 @@ 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 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.internal/zig/zig-{host_platform}-{version}.tar.xz",
"https://example.org/zig/zig-{host_platform}-{version}.tar.xz",
],
host_platform_sha256 = { ... },
)
@ -41,21 +50,215 @@ 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 register it for the
following platforms:
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.
- `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"]`.
## Use case: manually build a single target with a specific zig cc toolchain
Note that both Go and Bazel naming schemes are accepted. For convenience with
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:
```
$ 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(...)`:
```
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",
)
```
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_amd64_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 |
@ -69,21 +272,10 @@ 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
You may explicitly request Bazel to use a specific toolchain, even though a
different one is registered using `--extra_toolchains <toolchain>` in
`.bazelrc`. For example, if you wish to compile a specific binary (or run
tests) on linux/amd64/musl, you may specify:
```
--extra_toolchains @zig_sdk//toolchain:linux_amd64_musl
```
## UBSAN and "SIGILL: Illegal Instruction"
# 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
@ -96,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
@ -151,48 +344,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:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE|HAS_TLV_DESCRIPTORS>
```
## 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
@ -226,3 +393,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

View File

@ -42,21 +42,27 @@ 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",
zig_toolchains = "toolchains",
)
zig_register_toolchains()
zig_toolchains()
register_toolchains(
# 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",
# 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",
)

View File

@ -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 \

9
ci/lint Executable file
View File

@ -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 {} +

15
ci/list_toolchains_platforms Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
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

35
ci/test
View File

@ -1,34 +1,3 @@
#!/bin/bash
set -euo pipefail
#!/bin/sh
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 <<EOF
test linux_amd64 linux_amd64_musl :
test linux_amd64 linux_amd64_gnu.2.19 :
test linux_arm64 linux_arm64_musl qemu-aarch64
test linux_arm64 linux_arm64_gnu.2.28 qemu-aarch64
build darwin_amd64 darwin_amd64 :
EOF
exec bazel test ...

2
go.mod
View File

@ -1,3 +1,3 @@
module git.sr.ht/~motiejus/bazel-zig-cc
go 1.16
go 1.18

View File

@ -0,0 +1 @@
package(default_visibility = ["//test:__pkg__"])

60
rules/platform.bzl Normal file
View File

@ -0,0 +1,60 @@
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:
executable = ctx.actions.declare_file("{}_{}".format(ctx.file.src.basename, ctx.attr.platform))
ctx.actions.run_shell(
command = "cp {} {}".format(source_info.files_to_run.executable.path, executable.path),
inputs = [source_info.files_to_run.executable],
outputs = [executable],
)
return [DefaultInfo(
files = depset(ctx.files.src),
executable = executable,
)]
_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.",
),
"_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,
)

36
test/c/BUILD Normal file
View File

@ -0,0 +1,36 @@
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",
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"),
]
]

11
test/c/main.c Normal file
View File

@ -0,0 +1,11 @@
#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;
}

16
test/c/test.sh Executable file
View File

@ -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

View File

@ -1,5 +1,6 @@
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 = "go_lib",
@ -12,7 +13,6 @@ go_library(
go_binary(
name = "go",
embed = [":go_lib"],
static = "on",
visibility = ["//visibility:public"],
)
@ -21,3 +21,32 @@ go_test(
srcs = ["hello_test.go"],
embed = [":go_lib"],
)
[
platform_binary(
name = "go_{}".format(name),
src = "go",
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 = "go_test_{}".format(name),
src = "go_test",
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"),
]
]

View File

@ -4,6 +4,8 @@ package(
default_visibility = ["//visibility:public"],
)
declare_files(
zig_include_root = {zig_include_root},
)

View File

@ -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,32 +138,26 @@ 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)
for dest, src in {
"BUILD": "//toolchain:BUILD.sdk.bazel",
"private/BUILD": "//toolchain/private:BUILD.sdk.bazel",
}.items():
repository_ctx.template(
"BUILD",
Label("//toolchain:BUILD.sdk.bazel"),
dest,
Label(src),
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),
"{absolute_path}": _quote(str(repository_ctx.path(""))),
"{zig_include_root}": _quote(zig_include_root),
},
)

17
toolchain/libc/BUILD Normal file
View File

@ -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()

8
toolchain/libc/defs.bzl Normal file
View File

@ -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",
)

View File

@ -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()

View File

@ -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()

View File

@ -1,3 +1,7 @@
load("@bazel-zig-cc//toolchain/platform:defs.bzl", "declare_platforms")
package(
default_visibility = ["//visibility:public"],
)
declare_platforms()

View File

@ -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:
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}".format(os = os, zigcpu = zigcpu),
name = "{os}_{zigcpu}{suffix}".format(os = os, zigcpu = zigcpu, suffix = suffix),
constraint_values = constraint_values,
)
native.platform(
name = "{os}_{gocpu}".format(os = os, gocpu = gocpu),
name = "{os}_{gocpu}{suffix}".format(os = os, gocpu = gocpu, suffix = suffix),
constraint_values = constraint_values,
)

View File

@ -28,12 +28,14 @@ _GLIBCS = [
"2.34",
]
LIBCS = ["musl"] + ["gnu.{}".format(glibc) for glibc in _GLIBCS]
def target_structs():
ret = []
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
@ -61,23 +63,11 @@ def _target_darwin(gocpu, zigcpu):
tool_paths = {"ld": "ld64.lld"},
)
def _target_linux_gnu(gocpu, zigcpu, glibc_version = ""):
glibc_suffix = "gnu"
if 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 is only required for glibc 2.27 or less.
fcntl_hack = glibc_version < "2.28"
return struct(
@ -100,6 +90,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 +111,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"},
)

View File

@ -1,16 +1,37 @@
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
# 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_config.constraint_values,
target_compatible_with = target_compatible_with,
toolchain = "@zig_sdk//private:%s_cc" % zigtarget,
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
)
@ -19,7 +40,7 @@ def declare_toolchains():
native.toolchain(
name = zigtarget,
exec_compatible_with = None,
target_compatible_with = target_config.constraint_values,
target_compatible_with = target_compatible_with,
toolchain = "@zig_sdk//private:%s_cc" % zigtarget,
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
)