diff --git a/bazel/MODULE.bazel b/bazel/MODULE.bazel index f9548fe902..b07b0515dd 100644 --- a/bazel/MODULE.bazel +++ b/bazel/MODULE.bazel @@ -86,9 +86,9 @@ use_repo(reachability_test_ext, "apparent_excluded_repo", "apparent_excluded_tra http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( - dev_dependency = True, name = "llvm_source", build_file_content = """filegroup(name = "all", srcs = glob(["**"]), visibility = ["//visibility:public"])""", + dev_dependency = True, sha256 = "ad18b70e287954c3d62bc7e0b86e7b7af2adf87bcfce21c15fe717f101d7aace", strip_prefix = "llvm-project-llvmorg-22.1.8", url = "https://github.com/llvm/llvm-project/archive/llvmorg-22.1.8.tar.gz", @@ -107,6 +107,14 @@ sysroot_ext = use_extension("//sysroot:extensions.bzl", "sysroot_extension", dev sysroot_ext.setup() use_repo(sysroot_ext, "sysroot_linux_amd64", "sysroot_linux_arm64") +macos_sysroot_test_repo = use_repo_rule("//sysroot:sysroot.bzl", "macos_sysroot") + +macos_sysroot_test_repo( + name = "sysroot_macos_empty", + dev_dependency = True, + version = "test", +) + macos_sysroot_build_ext = use_extension("//sysroot:extensions.bzl", "macos_sysroot_build_extension", dev_dependency = True) use_repo(macos_sysroot_build_ext, "macos_sdk_pkg", "pkgutil_linux_amd64") @@ -192,12 +200,12 @@ llvm.toolchain_root( ) llvm.sysroot( name = "llvm_toolchain", - label = "@sysroot_linux_amd64//:sysroot", + label = "@sysroot_linux_amd64//sysroot:sysroot", targets = ["linux-x86_64"], ) llvm.sysroot( name = "llvm_toolchain", - label = "@sysroot_linux_arm64//:sysroot", + label = "@sysroot_linux_arm64//sysroot:sysroot", targets = ["linux-aarch64"], ) llvm.extra_exec_compatible_with( diff --git a/bazel/sysroot/README.md b/bazel/sysroot/README.md index de681a843d..c59489f494 100644 --- a/bazel/sysroot/README.md +++ b/bazel/sysroot/README.md @@ -39,6 +39,33 @@ This ensures that even older glibc sysroots can compile code using modern kernel ## Usage in Bazel +### Repository layout + +Archives are extracted into the repository's `sysroot/` directory. The public +`:sysroot` target is an alias to `//sysroot:sysroot`, a single source-directory +artifact rather than a filegroup expanding every file. This gives compile actions +one sandbox symlink and one merkle-tree node, avoiding Bazel 9's multi-file sysroot +warning. Files named with `:` and `*.pl` files are removed after extraction because +a directory artifact cannot exclude individual files. + +The Linux `:headers`, `:libs`, and `:toolchain_sysroot` targets remain public: +headers and libraries still provide individual files, while `:toolchain_sysroot` +wraps the directory artifact. Their filegroups live inside the `sysroot` package +because Bazel globs cannot cross package boundaries. + +Raw file labels now use the `sysroot` package, for example +`@sysroot_linux_amd64//sysroot:usr/include/stdio.h` instead of +`@sysroot_linux_amd64//:usr/include/stdio.h`. Downstream Envoy's +`source/extensions/dynamic_modules/sdk/rust/BUILD` must update its raw `stdio.h` +label accordingly; `@sysroot_linux_amd64//:headers` is unchanged. + +Configure LLVM's `llvm.sysroot` with +`@sysroot_linux_amd64//sysroot:sysroot` (and the arm64 equivalent). +LLVM derives its sysroot path from the label's package, not the alias's resolved +artifact, so the direct label is required to get +`external//sysroot`. macOS repositories use the same +directory-and-alias layout, including when the SDK is empty. + ### Direct `.bzl` load To use these sysroots via a direct `.bzl` load: diff --git a/bazel/sysroot/sysroot.bzl b/bazel/sysroot/sysroot.bzl index b35a259b3a..3508b74870 100644 --- a/bazel/sysroot/sysroot.bzl +++ b/bazel/sysroot/sysroot.bzl @@ -1,7 +1,22 @@ -"""Repository rules for sysroots.""" +"""Repository rules exposing sysroots as single source-directory artifacts.""" load("//:versions.bzl", "VERSIONS") +_SYSROOT_ALIAS_BUILD = """\ +package(default_visibility = ["//visibility:public"]) + +alias( + name = "sysroot", + actual = "//sysroot", +) +""" + +def _prune_sysroot(ctx): + # Source directories cannot filter inputs, unlike the previous glob. + result = ctx.execute(["find", "sysroot", "-depth", "(", "-name", "*:*", "-o", "-name", "*.pl", ")", "-delete"]) + if result.return_code: + fail("Failed to prune sysroot: {}".format(result.stderr)) + def _get_platform_arch(ctx): """Get the platform architecture for sysroot selection.""" arch = ctx.os.arch @@ -13,7 +28,7 @@ def _get_platform_arch(ctx): fail("Unsupported architecture: {}".format(arch)) def _sysroot_impl(ctx): - """Implementation for sysroot repository rule.""" + """Download a sysroot with one sandbox symlink and one merkle-tree node.""" arch = ctx.attr.arch or _get_platform_arch(ctx) glibc_version = ctx.attr.glibc_version stdcc_version = ctx.attr.stdcc_version @@ -37,19 +52,16 @@ def _sysroot_impl(ctx): url = url, sha256 = ctx.attr.sha256, stripPrefix = "", + output = "sysroot", ) - ctx.file("BUILD.bazel", """ + _prune_sysroot(ctx) + ctx.file("sysroot/BUILD.bazel", """ package(default_visibility = ["//visibility:public"]) +# A single source directory avoids thousands of individual action inputs. filegroup( name = "sysroot", - srcs = glob( - ["**"], - exclude = [ - "**/*:*", - "**/*.pl", - ], - ), + srcs = ["."], ) filegroup( @@ -72,6 +84,19 @@ filegroup( exclude = ["**/*:*"], ), ) +""") + + # Globs cannot cross the sysroot subpackage boundary. + ctx.file("BUILD.bazel", _SYSROOT_ALIAS_BUILD + """ +filegroup( + name = "headers", + srcs = ["//sysroot:headers"], +) + +filegroup( + name = "libs", + srcs = ["//sysroot:libs"], +) filegroup( name = "toolchain_sysroot", @@ -161,22 +186,14 @@ package(default_visibility = ["//visibility:public"]) filegroup( name = "sysroot", - srcs = glob( - ["**"], - exclude = ["**/*:*"], - ), + srcs = ["."], ) """ def _macos_sysroot_impl(ctx): - """Implementation for macOS SDK sysroot repository rule.""" - if ctx.os.name.startswith("mac"): - # On macOS, no cross-compilation sysroot needed. - ctx.file("BUILD.bazel", _MACOS_SYSROOT_BUILD) - return - + """Expose the macOS SDK as one source directory, including empty SDKs.""" sha256 = ctx.attr.sha256 - if sha256: + if sha256 and not ctx.os.name.startswith("mac"): url = "https://github.com/envoyproxy/toolshed/releases/download/bins-v{version}/sysroot-macos-{arch}.tar.xz".format( version = ctx.attr.version, arch = ctx.attr.arch, @@ -184,12 +201,18 @@ def _macos_sysroot_impl(ctx): ctx.download_and_extract( url, sha256 = sha256, + output = "sysroot", ) + _prune_sysroot(ctx) else: - # No hash available yet — create an empty sysroot. - ctx.execute(["mkdir", "-p", "usr/include"]) - - ctx.file("BUILD.bazel", _MACOS_SYSROOT_BUILD) + # Native macOS or no hash available yet — create an empty sysroot. + result = ctx.execute(["mkdir", "-p", "sysroot/usr/include"]) + if result.return_code: + fail("Failed to create empty sysroot: {}".format(result.stderr)) + + # One sandbox symlink and one merkle-tree node, just like the Linux SDK. + ctx.file("sysroot/BUILD.bazel", _MACOS_SYSROOT_BUILD) + ctx.file("BUILD.bazel", _SYSROOT_ALIAS_BUILD) macos_sysroot = repository_rule( implementation = _macos_sysroot_impl, diff --git a/bazel/sysroot/test/BUILD b/bazel/sysroot/test/BUILD new file mode 100644 index 0000000000..c29809ff1b --- /dev/null +++ b/bazel/sysroot/test/BUILD @@ -0,0 +1,21 @@ +load(":sysroot_test.bzl", "sysroot_test") + +[ + sysroot_test( + name = "linux_%s_test" % arch, + headers = "@sysroot_linux_%s//:headers" % arch, + libs = "@sysroot_linux_%s//:libs" % arch, + stdio = "@sysroot_linux_%s//sysroot:usr/include/stdio.h" % arch, + sysroot = "@sysroot_linux_%s//:sysroot" % arch, + toolchain_sysroot = "@sysroot_linux_%s//:toolchain_sysroot" % arch, + ) + for arch in [ + "amd64", + "arm64", + ] +] + +sysroot_test( + name = "macos_empty_test", + sysroot = "@sysroot_macos_empty//:sysroot", +) diff --git a/bazel/sysroot/test/sysroot_test.bzl b/bazel/sysroot/test/sysroot_test.bzl new file mode 100644 index 0000000000..32525bc873 --- /dev/null +++ b/bazel/sysroot/test/sysroot_test.bzl @@ -0,0 +1,34 @@ +"""Regression tests for directory sysroots and their file-level targets.""" + +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") + +def _sysroot_test_impl(ctx): + env = unittest.begin(ctx) + files = ctx.attr.sysroot[DefaultInfo].files.to_list() + asserts.equals(env, 1, len(files)) + if len(files) == 1: + root = files[0] + asserts.equals(env, "sysroot", root.basename) + if ctx.attr.toolchain_sysroot: + asserts.equals(env, files, ctx.attr.toolchain_sysroot[DefaultInfo].files.to_list()) + if ctx.attr.headers: + headers = ctx.attr.headers[DefaultInfo].files.to_list() + libs = ctx.attr.libs[DefaultInfo].files.to_list() + asserts.true(env, len(headers) > 0) + asserts.true(env, len(libs) > 0) + asserts.true(env, ctx.file.stdio in headers) + for file in headers + libs: + asserts.true(env, file.path.startswith(root.path + "/")) + asserts.false(env, ":" in file.basename or file.basename.endswith(".pl")) + return unittest.end(env) + +sysroot_test = unittest.make( + _sysroot_test_impl, + attrs = { + "sysroot": attr.label(mandatory = True), + "toolchain_sysroot": attr.label(), + "headers": attr.label(), + "libs": attr.label(), + "stdio": attr.label(allow_single_file = True), + }, +)