Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions bazel/compile/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
load("@rules_foreign_cc//foreign_cc:defs.bzl", "cmake")
load("@rules_pkg//pkg:mappings.bzl", "pkg_files")
load("@rules_pkg//pkg:pkg.bzl", "pkg_tar")
load("//:versions.bzl", "VERSIONS")
load("//:versions.bzl", "SUPPORTED_ARCHES", "VERSIONS")

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

exports_files(["sanitizer_libs.bzl", "extensions.bzl"])
exports_files(["sanitizer_libs.bzl", "libcxx_libs.bzl", "extensions.bzl"])

# Build setting to control which toolchain to use
# This can be set with --//compile:use_gcc_toolchain=true
Expand Down Expand Up @@ -164,3 +164,43 @@ pkg_tar(
extension = "tar.xz",
package_file_name = "tsan-llvm%s-x86_64.tar.xz" % VERSIONS["llvm"],
)

[
(
pkg_files(
name = "libcxx_%s_lib_files" % arch,
srcs = [
"@llvm_libcxx_%s//:libcxx" % arch,
],
prefix = "lib",
strip_prefix = ".",
),
pkg_files(
name = "libcxx_%s_compiler_rt_files" % arch,
srcs = [
"@llvm_libcxx_%s//:compiler_rt" % arch,
],
prefix = "lib",
strip_prefix = ".",
),
pkg_files(
name = "libcxx_%s_include_files" % arch,
srcs = [
"@llvm_libcxx_%s//:config_site" % arch,
],
prefix = "include",
strip_prefix = ".",
),
pkg_tar(
name = "libcxx_%s" % arch,
srcs = [
":libcxx_%s_lib_files" % arch,
":libcxx_%s_compiler_rt_files" % arch,
":libcxx_%s_include_files" % arch,
],
extension = "tar.xz",
package_file_name = "libcxx-llvm%s-%s.tar.xz" % (VERSIONS["llvm"], arch),
),
)
for arch in SUPPORTED_ARCHES
]
170 changes: 164 additions & 6 deletions bazel/compile/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# Sanitizer libraries
# Sanitizer and libcxx libraries

This directory contains build rules for creating hermetic LLVM sanitizer libraries (MSAN, TSAN) that can be used with Envoy.
This directory contains build rules for creating and downloading prebuilt LLVM libraries for use with Envoy, including:

## Building
- **Sanitizer libraries** (MSAN, TSAN) for use with sanitizer builds
- **libcxx bundles** for cross-compilation with `toolchains_llvm`

## Sanitizer libraries (MSAN, TSAN)

### Building

To build the libraries locally:

Expand All @@ -16,7 +21,7 @@ This will produce:
- `bazel-bin/compile/msan-libs-x86_64.tar.gz`
- `bazel-bin/compile/tsan-libs-x86_64.tar.gz`

## Updating prebuilt versions
### Updating prebuilt versions

The sanitizer libraries are automatically built and published to GitHub releases. To update:

Expand All @@ -39,7 +44,7 @@ The sanitizer libraries are automatically built and published to GitHub releases
"tsan_libs_sha256": "...", # Add actual SHA256
```

## Using with WORKSPACE
### Using with WORKSPACE

In your WORKSPACE file:

Expand All @@ -51,7 +56,7 @@ setup_sanitizer_libs()

This will create `@msan_libs` and `@tsan_libs` repositories you can use in your builds.

## Using with bzlmod (MODULE.bazel)
### Using with bzlmod (MODULE.bazel)

In your MODULE.bazel file:

Expand All @@ -76,3 +81,156 @@ sanitizer_ext.setup(
)
use_repo(sanitizer_ext, "msan_libs", "tsan_libs")
```

## libcxx bundles (for cross-compilation)

Prebuilt libcxx bundles provide the `libc++.a`, `libc++abi.a`, and `libunwind.a` static
libraries for each target architecture. These are intended for use with `toolchains_llvm`
when cross-compiling (e.g., building aarch64 binaries on an x86_64 host).

The bundles are downloaded from GitHub releases and expose the following Bazel targets:

- `@libcxx_libs_aarch64//:libs` — filegroup of all `.a` files for aarch64
- `@libcxx_libs_aarch64//:libcxx_libs` — `cc_library` wrapping all libs (alwayslink)
- `@libcxx_libs_aarch64//:headers` — filegroup of the `__config_site` header
- Same targets available in `@libcxx_libs_x86_64`

### Updating prebuilt versions

1. **Build the bundles** by triggering the CI workflow (see `compile/BUILD` for `libcxx_{arch}` targets)

2. **Create a release** with the naming format `bins-v{version}`

3. **Wait for CI** to publish the binaries to the release

4. **Get SHA256 hashes** for the published artifacts:
```bash
curl -L https://github.com/envoyproxy/toolshed/releases/download/bins-v{version}/libcxx-llvm{llvm_version}-aarch64.tar.xz | sha256sum
curl -L https://github.com/envoyproxy/toolshed/releases/download/bins-v{version}/libcxx-llvm{llvm_version}-x86_64.tar.xz | sha256sum
```

5. **Update versions.bzl** with the SHA256 values:
```python
"libcxx_libs_sha256": {
"aarch64": "...", # Add actual SHA256
"x86_64": "...", # Add actual SHA256
},
```

### Using with WORKSPACE

In your WORKSPACE file:

```starlark
load("@envoy_toolshed//compile:libcxx_libs.bzl", "setup_libcxx_libs")

setup_libcxx_libs()
```

This will create `@libcxx_libs_aarch64` and `@libcxx_libs_x86_64` repositories.

### Using with bzlmod (MODULE.bazel)

In your MODULE.bazel file:

```starlark
bazel_dep(name = "envoy_toolshed", version = "0.3.29")

# Setup prebuilt libcxx for cross-compilation
libcxx_libs_ext = use_extension("@envoy_toolshed//compile:extensions.bzl", "libcxx_libs_extension")
libcxx_libs_ext.setup()
use_repo(libcxx_libs_ext, "libcxx_libs_aarch64", "libcxx_libs_x86_64")
```

Or with custom versions:

```starlark
libcxx_libs_ext = use_extension("@envoy_toolshed//compile:extensions.bzl", "libcxx_libs_extension")
libcxx_libs_ext.setup(
aarch64_sha256 = "...",
x86_64_sha256 = "...",
)
use_repo(libcxx_libs_ext, "libcxx_libs_aarch64", "libcxx_libs_x86_64")
```

### Cross-compilation test

Cross-compilation tests are provided in `compile/test/`. They build simple C++ programs and verify the compiled binary is of the correct ELF architecture.

To run the architecture tests (requires the cross-compilation toolchain to be configured):

```bash
# Test aarch64 cross-compilation (hello world)
bazel test //compile/test:cross_compile_aarch64_test \
--platforms=@toolchains_llvm//platforms:linux-aarch64

# Test x86_64 cross-compilation (hello world)
bazel test //compile/test:cross_compile_x86_64_test \
--platforms=@toolchains_llvm//platforms:linux-x86_64
```

### Unwind tests

The unwind tests verify that `libunwind.a` is **statically linked** into the `test_unwind` binary,
rather than just checking the ELF architecture. They use `llvm-nm` to check for a defined
`_Unwind_RaiseException` symbol (indicating `libunwind.a` was linked in) and `llvm-readelf`
to confirm there is no `libgcc_s` dynamic dependency (which would indicate a fallback to the
system unwinder).

```bash
# Test aarch64 cross-compilation with libunwind statically linked
bazel test //compile/test:cross_compile_aarch64_unwind_test \
--platforms=@toolchains_llvm//platforms:linux-aarch64

# Test x86_64 cross-compilation with libunwind statically linked
bazel test //compile/test:cross_compile_x86_64_unwind_test \
--platforms=@toolchains_llvm//platforms:linux-x86_64
```

**Negative unwind tests** verify that libunwind is *not* statically linked when
`--@toolchains_llvm//toolchain/config:libunwind=False` is passed:

```bash
# Verify libunwind is NOT linked for aarch64 when disabled
bazel test //compile/test:cross_compile_aarch64_no_unwind_test \
--platforms=@toolchains_llvm//platforms:linux-aarch64 \
--@toolchains_llvm//toolchain/config:libunwind=False

# Verify libunwind is NOT linked for x86_64 when disabled
bazel test //compile/test:cross_compile_x86_64_no_unwind_test \
--platforms=@toolchains_llvm//platforms:linux-x86_64 \
--@toolchains_llvm//toolchain/config:libunwind=False
```

### Compiler-rt tests

The compiler-rt tests verify that `libclang_rt.builtins.a` (compiler-rt) is **statically
linked** into the `test_compiler_rt` binary. They use `llvm-nm` to check for a defined
`__divti3` symbol (a 128-bit integer division function that is provided by compiler-rt
builtins and referenced by `__int128` division) and `llvm-readelf` to confirm there is no
`libgcc_s` dynamic dependency.

```bash
# Test aarch64 cross-compilation with compiler-rt statically linked
bazel test //compile/test:cross_compile_aarch64_compiler_rt_test \
--platforms=@toolchains_llvm//platforms:linux-aarch64

# Test x86_64 cross-compilation with compiler-rt statically linked
bazel test //compile/test:cross_compile_x86_64_compiler_rt_test \
--platforms=@toolchains_llvm//platforms:linux-x86_64
```

**Negative compiler-rt tests** verify that compiler-rt builtins are *not* statically
linked when `--@toolchains_llvm//toolchain/config:compiler-rt=False` is passed:

```bash
# Verify compiler-rt is NOT linked for aarch64 when disabled
bazel test //compile/test:cross_compile_aarch64_no_compiler_rt_test \
--platforms=@toolchains_llvm//platforms:linux-aarch64 \
--@toolchains_llvm//toolchain/config:compiler-rt=False

# Verify compiler-rt is NOT linked for x86_64 when disabled
bazel test //compile/test:cross_compile_x86_64_no_compiler_rt_test \
--platforms=@toolchains_llvm//platforms:linux-x86_64 \
--@toolchains_llvm//toolchain/config:compiler-rt=False
```
76 changes: 75 additions & 1 deletion bazel/compile/extensions.bzl
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"""Module extension for sanitizer libraries configuration in bzlmod."""
"""Module extension for libcxx and sanitizer libraries configuration in bzlmod."""

load("//:versions.bzl", "LLVM_CXX_BUILD", "SUPPORTED_ARCHES", "VERSIONS")
load(":libcxx_libs.bzl", "setup_libcxx_libs")
load(":llvm_prebuilt.bzl", "llvm_prebuilt")
load(":sanitizer_libs.bzl", "setup_sanitizer_libs")

def _sanitizer_libs_impl(module_ctx):
Expand Down Expand Up @@ -55,3 +58,74 @@ sanitizer_extension = module_extension(
"setup": _setup,
},
)

def _libcxx_libs_ext_impl(module_ctx):
"""Implementation of the libcxx_libs module extension.

This extension allows configuring prebuilt libcxx libraries for cross-compilation
in MODULE.bazel using the same setup_libcxx_libs() function used in WORKSPACE.
"""

# Collect all setup tags from all modules
# Only use the first tag found (libcxx_libs repos have fixed names)
setup_tag = None
for mod in module_ctx.modules:
for tag in mod.tags.setup:
if setup_tag == None:
setup_tag = tag
else:
fail("Multiple setup() calls found for libcxx_libs_extension. Only one configuration is allowed since repository names are fixed to @libcxx_libs_aarch64 and @libcxx_libs_x86_64.")

# Call setup_libcxx_libs once with the configuration
if setup_tag:
setup_libcxx_libs(
aarch64_version = setup_tag.aarch64_version,
aarch64_sha256 = setup_tag.aarch64_sha256,
x86_64_version = setup_tag.x86_64_version,
x86_64_sha256 = setup_tag.x86_64_sha256,
)
else:
# Use default configuration if no tags specified
setup_libcxx_libs()

_libcxx_libs_setup = tag_class(
attrs = {
"aarch64_version": attr.string(
doc = "Version of aarch64 libcxx release to use (default: VERSIONS['bins_release'] from //:versions.bzl)",
),
"aarch64_sha256": attr.string(
doc = "SHA256 hash of the aarch64 libcxx libs archive (default: VERSIONS['libcxx_libs_sha256']['aarch64'] from //:versions.bzl)",
),
"x86_64_version": attr.string(
doc = "Version of x86_64 libcxx release to use (default: VERSIONS['bins_release'] from //:versions.bzl)",
),
"x86_64_sha256": attr.string(
doc = "SHA256 hash of the x86_64 libcxx libs archive (default: VERSIONS['libcxx_libs_sha256']['x86_64'] from //:versions.bzl)",
),
},
)

libcxx_libs_extension = module_extension(
implementation = _libcxx_libs_ext_impl,
tag_classes = {
"setup": _libcxx_libs_setup,
},
)

def _libcxx_ext_impl(module_ctx):
for arch in SUPPORTED_ARCHES:
config = VERSIONS["llvm_libcxx_%s" % arch]
build_file_content = LLVM_CXX_BUILD.format(**config)
url = config["url"].format(**config)
strip_prefix = config["strip_prefix"].format(**config)
llvm_prebuilt(
name = "llvm_libcxx_%s" % arch,
build_file_content = build_file_content,
sha256 = config["sha256"],
strip_prefix = strip_prefix,
url = url,
)

libcxx_extension = module_extension(
implementation = _libcxx_ext_impl,
)
Loading