From 2b68ed3977735c1783496e39163d2eb838eee32c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Oct 2026 20:10:41 +0000 Subject: [PATCH 1/2] Initial plan From 0299fb8971eff4343505919e16797039eccc3b64 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Oct 2026 20:23:51 +0000 Subject: [PATCH 2/2] Follow up #5490: preserve legacy sysroots and validate hermetic GCC runtimes Co-authored-by: phlax <454682+phlax@users.noreply.github.com> --- .github/workflows/_release.yml | 23 +++-- .github/workflows/bazel.yml | 6 ++ bazel/.bazelrc | 4 +- bazel/MODULE.bazel | 5 + bazel/compile/test/BUILD.bazel | 40 ++++++++ bazel/compile/test/wee8_link_probe.cc | 9 ++ bazel/platforms/rbe/BUILD | 20 +--- bazel/sysroot/README.md | 72 +++++++++++--- bazel/sysroot/extensions.bzl | 7 +- bazel/sysroot/sysroot.bzl | 49 ++++++--- bazel/sysroot/test/BUILD | 22 ++++- .../test/build_sysroot_options_test.sh | 18 ++++ bazel/sysroot/test/sysroot_test.bzl | 29 ++++++ bazel/toolchains/gcc/BUILD.bazel | 42 +++++++- bazel/toolchains/gcc/extensions.bzl | 47 ++++++--- bazel/toolchains/gcc/runtime_abi_test.sh | 99 +++++++++++++++++++ bazel/toolchains/gcc/toolchain.bzl | 14 ++- bazel/toolchains/gcc/toolchain_test.bzl | 46 +++++++++ bazel/versions.bzl | 36 +++++-- 19 files changed, 519 insertions(+), 69 deletions(-) create mode 100644 bazel/compile/test/wee8_link_probe.cc create mode 100755 bazel/toolchains/gcc/runtime_abi_test.sh diff --git a/.github/workflows/_release.yml b/.github/workflows/_release.yml index 92c3cebcf5..02401b27cc 100644 --- a/.github/workflows/_release.yml +++ b/.github/workflows/_release.yml @@ -63,6 +63,11 @@ on: fi done OUTPUT=$(printf '%s\n' "${_OUTPUT[@]}") + stdcc-versions: + description: Sysroot hash variants (deprecated variants are pinned and never updated) + type: string + default: | + ["base", "13"] template-version-update: type: string default: | @@ -193,6 +198,7 @@ jobs: current: ${{ steps.bins-deps.outputs.output }} shas: ${{ steps.bins-shas.outputs.shas }} glibc_versions: ${{ inputs.glibc-versions }} + stdcc_versions: ${{ inputs.stdcc-versions }} arches: ${{ inputs.arches }} glint_version: ${{ inputs.glint-version }} versions_file: ${{ inputs.bazel-versions-file }} @@ -206,6 +212,7 @@ jobs: | .shas as $shas | .current as $current | .glibc_versions as $glibc_versions + | .stdcc_versions as $stdccs | .arches as $arches | .san as $san | .glint_version as $glint_version @@ -244,13 +251,17 @@ jobs: else "" end] as $glint_updates | [$glibc_versions[] | . as $glibc + | $stdccs[] + | . as $stdcc + | select($stdcc == "base") | "sysroot-glibc\($glibc)-\($arch).tar.xz" as $sysroot - | ($shas[$sysroot] - | split(":") - | .[1]) as $new_sha - | $current.sysroot_hashes[$glibc][$arch] as $current_sha - | if $current_sha != $new_sha then - "sed -i s/\($current_sha)/\($new_sha)/g \($versions_file)" + | ($shas[$sysroot] // null) as $sha_entry + | if $sha_entry then + ($sha_entry | split(":") | .[1]) as $new_sha + | $current.sysroot_hashes[$glibc][$stdcc][$arch] as $current_sha + | if $current_sha != $new_sha then + "sed -i s/\($current_sha)/\($new_sha)/g \($versions_file)" + else "" end else "" end] as $sysroot_updates | $glint_updates + $sysroot_updates] as $arch_updates | [ diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index 6ff5ab9645..25448ac8d7 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -214,6 +214,7 @@ jobs: bazel_args: >- --config=ci --config=gcc + --skip_incompatible_explicit_targets privileged: false rbe: true runner: ubuntu-24.04 @@ -221,8 +222,13 @@ jobs: //compile/test:check_glibc_test //compile/test:stdlib_libstdcxx_test //compile/test:gcc_runtime_test + //compile/test:wee8_prebuilt_link_test //toolchains/gcc:linux_x64_paths_test //toolchains/gcc:linux_arm64_paths_test + //toolchains/gcc:linux_x86_64_runtime_test + //toolchains/gcc:linux_aarch64_runtime_test + //toolchains/gcc:linux_x86_64_runtime_abi_test + //toolchains/gcc:linux_aarch64_runtime_abi_test - name: x86-to-arm action: test bazel_args: >- diff --git a/bazel/.bazelrc b/bazel/.bazelrc index 070ddaab50..8fc0b3f865 100644 --- a/bazel/.bazelrc +++ b/bazel/.bazelrc @@ -93,8 +93,10 @@ common:remote-exec --define=engflow_rbe=true # This is safe for ordinary (libcxx) targets: gcc requires //compile:libstdcxx # on the target platform, so it is not a candidate at all, and the gcc platforms # yield no toolchain and are skipped. +# Both GCC 13.4.0 archives are x86_64-hosted: aarch64 GCC builds cross-compile +# on linux_x64_gcc workers, not the native arm64 pool. common:remote-exec --host_platform=//platforms/rbe:linux_x64 -common:remote-exec --extra_execution_platforms=//platforms/rbe:linux_x64_gcc,//platforms/rbe:linux_arm64_gcc,//platforms/rbe:linux_x64,//platforms/rbe:linux_arm64 +common:remote-exec --extra_execution_platforms=//platforms/rbe:linux_x64_gcc,//platforms/rbe:linux_x64,//platforms/rbe:linux_arm64 ############################################################################# diff --git a/bazel/MODULE.bazel b/bazel/MODULE.bazel index 97befdbe2c..12cbe0081f 100644 --- a/bazel/MODULE.bazel +++ b/bazel/MODULE.bazel @@ -116,6 +116,10 @@ sysroot_ext = use_extension("//sysroot:extensions.bzl", "sysroot_extension", dev sysroot_ext.setup() use_repo(sysroot_ext, "sysroot_linux_amd64", "sysroot_linux_arm64") +# Exercise deprecated downloads without changing the toolchains' base sysroots. +sysroot_ext.setup(stdcc_version = "13", name_prefix = "legacy") +use_repo(sysroot_ext, "legacy_sysroot_linux_amd64", "legacy_sysroot_linux_arm64") + macos_sysroot_test_repo = use_repo_rule("//sysroot:sysroot.bzl", "macos_sysroot") macos_sysroot_test_repo( @@ -185,6 +189,7 @@ use_repo(gcc, "gcc_linux_aarch64", "gcc_linux_x86_64") register_toolchains( "//toolchains/gcc:linux_x64_toolchain", "//toolchains/gcc:linux_arm64_toolchain", + dev_dependency = True, ) # Configure LLVM toolchain diff --git a/bazel/compile/test/BUILD.bazel b/bazel/compile/test/BUILD.bazel index fe5ffecf4f..fdf62c2dc2 100644 --- a/bazel/compile/test/BUILD.bazel +++ b/bazel/compile/test/BUILD.bazel @@ -1,10 +1,21 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") load("@rules_cc//cc:defs.bzl", "cc_binary") load("@rules_shell//shell:sh_test.bzl", "sh_test") +load("//:versions.bzl", "VERSIONS") load(":clang_version_test.bzl", "clang_version_test") package(default_visibility = ["//visibility:public"]) +# TODO: remove the hash gate after the next bins release rebuilds libstdcxx wee8. +# This published prebuilt used host glibc 2.39; changing its hash activates CI. +_WEE8_LINK_COMPATIBLE = select({ + "//compile:gcc_build": [], + "//conditions:default": ["@platforms//:incompatible"], +}) if VERSIONS["wee8_sha256"]["x86_64"]["libstdcxx"] not in [ + "", + "ad9895c550bdf8f4de931c0143c99cc28ae38aa0b475bc4602e494eedac46e90", +] else ["@platforms//:incompatible"] + exports_files([ "assert_llvm_minimal_bins_layout.sh", "assert_llvm_minimal_manifest.sh", @@ -325,3 +336,32 @@ sh_test( "//conditions:default": ["@platforms//:incompatible"], }), ) + +# Unlike archive symbol-name filtering, this link resolves wee8's libc references +# against the base sysroot and produces an ELF with actual GLIBC version tags. +cc_binary( + name = "wee8_link_probe", + srcs = ["wee8_link_probe.cc"], + tags = ["manual"], + target_compatible_with = _WEE8_LINK_COMPATIBLE, + deps = ["//v8:wee8"], +) + +sh_test( + name = "wee8_prebuilt_link_test", + size = "small", + srcs = ["check_glibc.sh"], + data = [ + ":wee8_link_probe", + "@llvm_toolchain_llvm//:nm", + "@llvm_toolchain_llvm//:readelf", + ], + env = { + "BINARY": "$(rootpath :wee8_link_probe)", + "CHECK_STATIC_RUNTIME": "true", + "NM": "$(rootpath @llvm_toolchain_llvm//:nm)", + "READELF": "$(rootpath @llvm_toolchain_llvm//:readelf)", + }, + tags = ["manual"], + target_compatible_with = _WEE8_LINK_COMPATIBLE, +) diff --git a/bazel/compile/test/wee8_link_probe.cc b/bazel/compile/test/wee8_link_probe.cc new file mode 100644 index 0000000000..dc19435669 --- /dev/null +++ b/bazel/compile/test/wee8_link_probe.cc @@ -0,0 +1,9 @@ +#include "wasm-api/wasm.h" + +int main() { + wasm_engine_t *engine = wasm_engine_new(); + wasm_store_t *store = wasm_store_new(engine); + wasm_store_delete(store); + wasm_engine_delete(engine); + return 0; +} diff --git a/bazel/platforms/rbe/BUILD b/bazel/platforms/rbe/BUILD index b0606cf650..2a3b07f5b8 100644 --- a/bazel/platforms/rbe/BUILD +++ b/bazel/platforms/rbe/BUILD @@ -13,6 +13,10 @@ WORKER_IMAGE = "docker://gcr.io/envoy-ci/envoy-build:worker-v0.1.8@sha256:934b50 # GCC worker container image used by EngFlow RBE for gcc builds. # # Image produced by this repo at release docker-v0.1.3. +# GCC is now hermetic: GCC_WORKER_IMAGE and //compile:gcc_worker are no longer +# required for correctness and can be collapsed into the general worker later. +# Both GCC 13.4.0 archives are x86_64-hosted; aarch64 targets cross-compile on +# linux_x64_gcc rather than the arm64 RBE pool. Native sibling releases omit GCC 13. GCC_WORKER_IMAGE = "docker://gcr.io/envoy-ci/envoy-build:gcc-v0.1.8@sha256:342d7eaccb753e8efb2aa171dccfd3bd7e531a22730ecb72372ab64c5967681b" platform( @@ -47,24 +51,10 @@ platform( constraint_values = [ "@platforms//os:linux", "@platforms//cpu:x86_64", - "//compile:gcc_worker" + "//compile:gcc_worker", ], exec_properties = { "container-image": GCC_WORKER_IMAGE, "OSFamily": "Linux", }, ) - -platform( - name = "linux_arm64_gcc", - constraint_values = [ - "@platforms//os:linux", - "@platforms//cpu:aarch64", - "//compile:gcc_worker" - ], - exec_properties = { - "container-image": GCC_WORKER_IMAGE, - "OSFamily": "Linux", - "Pool": "arm", - }, -) diff --git a/bazel/sysroot/README.md b/bazel/sysroot/README.md index 2bea8ce41e..584225363b 100644 --- a/bazel/sysroot/README.md +++ b/bazel/sysroot/README.md @@ -32,13 +32,25 @@ Linux x86_64 (including aarch64 cross-compilation). GCC supplies its own libstdc++, libgcc, libgcc_eh, and crtbegin/crtend objects; glibc supplies crt1/crti/crtn from this sysroot. GCC links its C++ runtime statically. LLVM continues to supply compiler-rt and libc++ from its own toolchain. +The GCC 13.4.0 release has no aarch64-hosted archive (native sibling releases +start at newer GCC versions), so there is no `linux_arm64_gcc` execution +platform: aarch64 GCC actions run on x86_64 workers. The pinned archives come from [f0rmiga/gcc-builds release 18082025](https://github.com/f0rmiga/gcc-builds/releases/tag/18082025), -the same source used by envoyproxy/envoy#39632. Its -[build recipe](https://github.com/f0rmiga/gcc-builds/blob/3fcca92fd993788a88af150cd4c885e4860e0055/Dockerfile) -builds the target runtime against glibc 2.26. The repository rule removes the -archive's bundled libc sysroot and explicitly uses toolshed's glibc 2.31 sysroot. +the same source used by envoyproxy/envoy#39632. Empirical inspection of the +SHA-pinned archives measured maximum GLIBC requirements of **2.26 (x86_64)** +and **2.25 (aarch64)** in the shared libstdc++ siblings. The static archives' +`nm -u` output contains no GLIBC version tags, `__isoc23_*`, or +`__libc_single_threaded`; absence of version tags alone is not proof of +compatibility. `//toolchains/gcc:linux_{x86_64,aarch64}_runtime_abi_test` also +resolves every strong undefined name in libstdc++.a/libgcc.a/libgcc_eh.a against +the other runtimes, GCC CRT, and base-sysroot exports at or below GLIBC_2.31 +(1,383 x86_64 and 1,365 aarch64 names in the pinned archives). The existing +stdlib fixture linked with these inputs on both architectures with a measured +maximum GLIBC_2.25 and no dynamic libstdc++/libgcc_s dependencies. +The repository rule removes the archive's bundled libc sysroot and explicitly +uses toolshed's glibc 2.31 sysroot. Replacement GCC archives **must build their target runtimes against glibc <= 2.31**; changing `--sysroot` cannot lower the glibc requirement of an already-built libstdc++. @@ -46,10 +58,36 @@ already-built libstdc++. Downstreams can instantiate the downloads with `gcc_extension` from `//toolchains/gcc:extensions.bzl`, using a `toolchain(name = ..., arch = "x86_64" | "aarch64", sysroot = "@sysroot_linux_...//sysroot")` tag per target. -Each generated repository exports `:cc_toolchain`, `:config`, and `:files`. +Downstreams must instantiate this extension and register their own toolchains; +toolshed's registrations are dev-only. Register GCC before LLVM, as in +toolshed's `MODULE.bazel`. +Each generated repository exports `:cc_toolchain`, `:config`, `:files`, +`:libstdcxx` (headers and static library), and `:libgcc` (static runtime archives). The sysroot label must point directly into the `sysroot` package, not its root-package alias. +Consumers compiling with a non-GCC toolchain but needing the libstdc++ ABI +(for example, Envoy's `compat/openssl/prefixer` linking against prebuilt +`libclang-cpp.so`) should use the GCC repository rather than a second sysroot: + +```starlark +cc_binary( + name = "prefixer", + srcs = ["prefixer.cpp"], + copts = ["-nostdinc++", "-stdlib=libstdc++"], + deps = [ + "@gcc_linux_x86_64//:libstdcxx", + # ... clang-tooling dependencies ... + ], +) +``` + +Use the appropriate `@//:libstdcxx` for the compilation architecture. +Its `CcInfo` supplies headers as compile inputs, include directories, and the +static archive as a link input, including on RBE. Keep libc/CRT from the base +sysroot: adding `-L` for a legacy sysroot's entire `usr/lib/` can mix +libc/libpthread versions and produce `GLIBC_PRIVATE` and `__libc_csu_*` errors. + **Release TODO:** the next `bins-v*` release must regenerate both wee8 libstdcxx archives and refresh `VERSIONS["wee8_sha256"][arch]["libstdcxx"]` before downstream consumption (including envoyproxy/envoy#39632). Existing @@ -57,6 +95,10 @@ x86_64 libstdcxx artifacts were built against host glibc 2.39 and are not compatible with this floor; aarch64 libstdcxx remains an incompatible prebuilt stub until its first archive/hash is published. `//v8:wee8_packages` builds all four arch/stdlib combinations without consuming these prebuilts. +`//compile/test:wee8_prebuilt_link_test` links wasm C-API entry points against +`//v8:wee8`, then checks the executable's GLIBC versions and static C++ runtime. +The GCC CI leg includes it, but the known-incompatible x86_64 hash gates it off +until replaced; the archive symbol-name scan remains an early pre-filter. ## Architecture Support @@ -175,16 +217,22 @@ The setup will automatically validate your configuration: - **Missing hashes** (e.g., for unreleased configurations) will fail with a helpful message All SHA256 hashes are centrally managed in `versions.bzl` as -`sysroot_hashes[glibc_version][arch]`. +`sysroot_hashes[glibc_version][variant][arch]`, where variant is `"base"` or `"13"`. ### Migrating existing configurations -Sysroots no longer bundle libstdc++. Remove `stdcc_version` from repository-rule, -`setup_sysroots`, and module-extension calls, and remove `variant`, `ppa_toolchain`, -and `stdcc_version` arguments from `sysrootfs` calls. The script no longer accepts -`--variant`, `--ppa-toolchain`, or `--stdcc`. Use the unsuffixed sysroot build -targets below and supply any C++ standard library through the toolchain. -Existing unsuffixed archives and their SHA256 hashes are unchanged. +New sysroots no longer bundle libstdc++. Downloads with `stdcc_version = "13"` +on repository-rule, `setup_sysroots`, and module-extension calls remain supported +for already-published `sysroot-glibc{2.28,2.31}-libstdc++13-{amd64,arm64}.tar.xz` +archives. **This variant is deprecated and will not be rebuilt in future +`bins-v*` releases.** Its default download remains pinned to the legacy release; +an explicit `version` overrides that pin. Consumers must migrate to +`@//:libstdcxx` and remove `stdcc_version`. + +For sysroot *builds*, remove `variant`, `ppa_toolchain`, and `stdcc_version` +arguments from `sysrootfs` calls. The script no longer accepts `--variant`, +`--ppa-toolchain`, or `--stdcc`. Use the unsuffixed build targets below. +Existing unsuffixed archive names and hashes are unchanged. ## Building Sysroots with Bazel diff --git a/bazel/sysroot/extensions.bzl b/bazel/sysroot/extensions.bzl index 5524ddc224..e18242ea70 100644 --- a/bazel/sysroot/extensions.bzl +++ b/bazel/sysroot/extensions.bzl @@ -27,13 +27,14 @@ def _sysroot_impl(module_ctx): setup_sysroots( version = setup_tag.version if setup_tag.version else None, glibc_version = setup_tag.glibc_version, + stdcc_version = setup_tag.stdcc_version if setup_tag.stdcc_version else None, name_prefix = setup_tag.name_prefix, ) _setup = tag_class( attrs = { "version": attr.string( - doc = "Version of sysroot release to use (default: uses VERSIONS['bins_release'])", + doc = "Release to use (default: VERSIONS['bins_release'] for base sysroots, VERSIONS['sysroot_stdcc_release'] for deprecated variants)", ), "glibc_version": attr.string( default = "2.31", @@ -45,6 +46,10 @@ _setup = tag_class( Allows multiple sysroot setups, e.g., name_prefix='old' creates @old_sysroot_linux_amd64 and @old_sysroot_linux_arm64""", ), + "stdcc_version": attr.string( + default = "", + doc = "Deprecated libstdc++ sysroot variant (e.g., '13'). Use the base sysroot and hermetic GCC toolchain instead.", + ), }, ) diff --git a/bazel/sysroot/sysroot.bzl b/bazel/sysroot/sysroot.bzl index e6e898b5c6..e4de2a3086 100644 --- a/bazel/sysroot/sysroot.bzl +++ b/bazel/sysroot/sysroot.bzl @@ -31,10 +31,12 @@ def _sysroot_impl(ctx): """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 - url = "https://github.com/envoyproxy/toolshed/releases/download/bins-v{version}/sysroot-glibc{glibc_version}-{arch}.tar.xz".format( + stdcc_version = ctx.attr.stdcc_version + url = "https://github.com/envoyproxy/toolshed/releases/download/bins-v{version}/sysroot-glibc{glibc_version}{stdlib_suffix}-{arch}.tar.xz".format( version = ctx.attr.version, arch = arch, glibc_version = glibc_version, + stdlib_suffix = "-libstdc++{}".format(stdcc_version) if stdcc_version else "", ) ctx.download_and_extract( @@ -111,15 +113,19 @@ sysroot = repository_rule( mandatory = True, doc = "glibc version (e.g., '2.31' or '2.28')", ), + "stdcc_version": attr.string( + doc = "Deprecated libstdc++ sysroot variant (e.g., '13'). Only previously published archives are available.", + ), }, doc = "Downloads sysroot for the specified or host architecture", ) -def _get_sysroot_hash(glibc_version, arch): +def _get_sysroot_hash(glibc_version, stdcc_version, arch): """Get the SHA256 hash for a specific sysroot configuration. Args: glibc_version: glibc version (e.g., "2.31" or "2.28") + stdcc_version: Deprecated libstdc++ variant, or None for the base sysroot arch: Architecture (e.g., "amd64" or "arm64") Returns: @@ -133,21 +139,31 @@ def _get_sysroot_hash(glibc_version, arch): ", ".join(VERSIONS["sysroot_hashes"].keys()), )) + stdlib_variant = stdcc_version if stdcc_version else "base" + if stdlib_variant not in VERSIONS["sysroot_hashes"][glibc_version]: + fail("Unsupported libstdc++ version '{}' for glibc {}. Supported variants: {}".format( + stdlib_variant, + glibc_version, + ", ".join(VERSIONS["sysroot_hashes"][glibc_version].keys()), + )) + # Validate architecture - if arch not in VERSIONS["sysroot_hashes"][glibc_version]: - fail("Unsupported architecture '{}' for glibc {}. Supported architectures: {}".format( + if arch not in VERSIONS["sysroot_hashes"][glibc_version][stdlib_variant]: + fail("Unsupported architecture '{}' for glibc {} with libstdc++ {}. Supported architectures: {}".format( arch, glibc_version, - ", ".join(VERSIONS["sysroot_hashes"][glibc_version].keys()), + stdlib_variant, + ", ".join(VERSIONS["sysroot_hashes"][glibc_version][stdlib_variant].keys()), )) # Get the hash - sha256 = VERSIONS["sysroot_hashes"][glibc_version][arch] + sha256 = VERSIONS["sysroot_hashes"][glibc_version][stdlib_variant][arch] # Validate hash is not empty if not sha256: - fail("SHA256 hash not yet available for glibc {} on {}. This configuration may not be released yet.".format( + fail("SHA256 hash not yet available for glibc {} with libstdc++ {} on {}. This configuration may not be released yet.".format( glibc_version, + stdlib_variant, arch, )) @@ -222,20 +238,27 @@ def setup_macos_sysroot(version = None): def setup_sysroots( version = None, glibc_version = "2.31", + stdcc_version = None, name_prefix = ""): """Setup function to configure sysroots, shared by module extensions and direct callers. Args: - version: Version of sysroot release to use (default: uses VERSIONS["bins_release"]) + version: Release to use (defaults to VERSIONS["bins_release"] for base sysroots, + or the pinned VERSIONS["sysroot_stdcc_release"] for deprecated variants). glibc_version: glibc version to use (default: "2.31", also available: "2.28") + stdcc_version: Deprecated libstdc++ variant (e.g., "13"), retained for published archives only. name_prefix: Optional prefix for sysroot repository names (default: "") Allows multiple sysroot setups, e.g., name_prefix="old" creates @old_sysroot_linux_amd64 and @old_sysroot_linux_arm64 """ + if stdcc_version: + print("WARNING: stdcc_version is deprecated; libstdc++ sysroot variants are no longer built. Use the base sysroot and hermetic GCC toolchain instead.") + # Get hashes from versions.bzl based on configuration - amd64_sha256 = _get_sysroot_hash(glibc_version, "amd64") - arm64_sha256 = _get_sysroot_hash(glibc_version, "arm64") + amd64_sha256 = _get_sysroot_hash(glibc_version, stdcc_version, "amd64") + arm64_sha256 = _get_sysroot_hash(glibc_version, stdcc_version, "arm64") + release = version or (VERSIONS["sysroot_stdcc_release"] if stdcc_version else VERSIONS["bins_release"]) # Construct repository names with optional prefix if name_prefix: @@ -248,17 +271,19 @@ def setup_sysroots( # AMD64 sysroot sysroot( name = amd64_name, - version = version or VERSIONS["bins_release"], + version = release, sha256 = amd64_sha256, arch = "amd64", glibc_version = glibc_version, + stdcc_version = stdcc_version, ) # ARM64 sysroot sysroot( name = arm64_name, - version = version or VERSIONS["bins_release"], + version = release, sha256 = arm64_sha256, arch = "arm64", glibc_version = glibc_version, + stdcc_version = stdcc_version, ) diff --git a/bazel/sysroot/test/BUILD b/bazel/sysroot/test/BUILD index a43a52eea9..c4cabd757c 100644 --- a/bazel/sysroot/test/BUILD +++ b/bazel/sysroot/test/BUILD @@ -1,11 +1,15 @@ load("@rules_shell//shell:sh_test.bzl", "sh_test") -load(":sysroot_test.bzl", "sysroot_build_test", "sysroot_test") +load(":sysroot_test.bzl", "sysroot_build_test", "sysroot_hashes_test", "sysroot_test") sysroot_build_test( name = "sysroot_build_test", target_under_test = "//sysroot:sysroots", ) +sysroot_hashes_test( + name = "sysroot_hashes_test", +) + sh_test( name = "build_sysroot_options_test", srcs = ["build_sysroot_options_test.sh"], @@ -28,6 +32,22 @@ sh_test( ] ] +[ + sysroot_test( + name = "legacy_linux_%s_test" % arch, + headers = "@legacy_sysroot_linux_%s//:headers" % arch, + libs = "@legacy_sysroot_linux_%s//:libs" % arch, + stdcc_version = "13", + stdio = "@legacy_sysroot_linux_%s//sysroot:usr/include/stdio.h" % arch, + sysroot = "@legacy_sysroot_linux_%s//:sysroot" % arch, + toolchain_sysroot = "@legacy_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/build_sysroot_options_test.sh b/bazel/sysroot/test/build_sysroot_options_test.sh index 4bc527fb32..c7ff67bb44 100755 --- a/bazel/sysroot/test/build_sysroot_options_test.sh +++ b/bazel/sysroot/test/build_sysroot_options_test.sh @@ -13,3 +13,21 @@ for option in --variant --ppa-toolchain --stdcc; do exit 1 fi done + +# Removed options must not be advertised, and base builds must remain available. +output=$(bash "$script" 2>&1) && { + echo "Expected missing required arguments to fail" >&2 + exit 1 +} +for option in --variant --ppa-toolchain --stdcc; do + if [[ "$output" == *"$option"* ]]; then + echo "Removed option $option is still advertised" >&2 + exit 1 + fi +done +for option in --arch --glibc --debian; do + if [[ "$output" != *"$option"* ]]; then + echo "Missing base sysroot option $option" >&2 + exit 1 + fi +done diff --git a/bazel/sysroot/test/sysroot_test.bzl b/bazel/sysroot/test/sysroot_test.bzl index e43b80ce33..c8d64b9292 100644 --- a/bazel/sysroot/test/sysroot_test.bzl +++ b/bazel/sysroot/test/sysroot_test.bzl @@ -1,6 +1,7 @@ """Regression tests for directory sysroots and their file-level targets.""" load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts", "unittest") +load("//:versions.bzl", "VERSIONS") def _sysroot_build_test_impl(ctx): env = analysistest.begin(ctx) @@ -19,6 +20,29 @@ def _sysroot_build_test_impl(ctx): sysroot_build_test = analysistest.make(_sysroot_build_test_impl) +def _sysroot_hashes_test_impl(ctx): + env = unittest.begin(ctx) + asserts.equals(env, "0.2.27", VERSIONS["sysroot_stdcc_release"]) + hashes = VERSIONS["sysroot_hashes"] + published = { + "2.28": { + "amd64": "b0a230b3875f36dc50739fb4711e8745a0b65fd1b1e4d0c4961c6832980c0510", + "arm64": "8fd6257c1a6ea3048d03d46e3a452873f885fd17ced5640deb3b3994c8dda4bf", + }, + "2.31": { + "amd64": "0562bde13b68aa1c49ab286c5bb765158a9f17fb69f3f1d190672ef171502dbf", + "arm64": "db9c5b96cd4ecc9b5ec4ac865d5e91eacf2020ef583aa262c6a906741f46ca11", + }, + } + for glibc_version, variants in hashes.items(): + asserts.equals(env, ["13", "base"], sorted(variants.keys())) + asserts.equals(env, published[glibc_version], variants["13"]) + for arch in ["amd64", "arm64"]: + asserts.equals(env, 64, len(variants["base"][arch])) + return unittest.end(env) + +sysroot_hashes_test = unittest.make(_sysroot_hashes_test_impl) + def _sysroot_test_impl(ctx): env = unittest.begin(ctx) files = ctx.attr.sysroot[DefaultInfo].files.to_list() @@ -34,6 +58,10 @@ def _sysroot_test_impl(ctx): asserts.true(env, len(headers) > 0) asserts.true(env, len(libs) > 0) asserts.true(env, ctx.file.stdio in headers) + if ctx.attr.stdcc_version: + include_prefix = root.path + "/usr/include/c++/" + ctx.attr.stdcc_version + "/" + asserts.true(env, any([file.path.startswith(include_prefix) for file in headers])) + asserts.true(env, any([file.basename.startswith("libstdc++.") for file in libs])) for file in headers + libs: asserts.true(env, file.path.startswith(root.path + "/")) asserts.false(env, ":" in file.basename or file.basename.endswith(".pl")) @@ -47,5 +75,6 @@ sysroot_test = unittest.make( "headers": attr.label(), "libs": attr.label(), "stdio": attr.label(allow_single_file = True), + "stdcc_version": attr.string(), }, ) diff --git a/bazel/toolchains/gcc/BUILD.bazel b/bazel/toolchains/gcc/BUILD.bazel index c59b800d5e..78624a88b1 100644 --- a/bazel/toolchains/gcc/BUILD.bazel +++ b/bazel/toolchains/gcc/BUILD.bazel @@ -1,4 +1,6 @@ -load(":toolchain_test.bzl", "gcc_paths_test") +load("@rules_shell//shell:sh_test.bzl", "sh_test") +load("//:versions.bzl", "VERSIONS") +load(":toolchain_test.bzl", "gcc_paths_test", "gcc_runtime_test") package(default_visibility = ["//visibility:public"]) @@ -64,6 +66,44 @@ gcc_paths_test( target_under_test = ":linux_arm64", ) +[ + gcc_runtime_test( + name = "linux_{}_runtime_test".format(arch), + arch = arch, + compiler = "@gcc_linux_{}//:xbin/gcc".format(arch), + libgcc = "@gcc_linux_{}//:libgcc".format(arch), + target_under_test = "@gcc_linux_{}//:libstdcxx".format(arch), + version = VERSIONS["gcc_toolchain"]["version"], + ) + for arch in ["x86_64", "aarch64"] +] + +[ + sh_test( + name = "linux_{}_runtime_abi_test".format(arch), + srcs = ["runtime_abi_test.sh"], + args = [ + "$(rootpath @gcc_linux_{}//:xbin/gcc)".format(arch), + "$(rootpath @sysroot_linux_{}//sysroot)".format("amd64" if arch == "x86_64" else "arm64"), + arch, + VERSIONS["gcc_toolchain"]["version"], + "$(rootpath //compile/test:check_glibc.sh)", + ], + data = [ + "//compile/test:check_glibc.sh", + "@gcc_linux_{}//:files".format(arch), + "@gcc_linux_{}//:xbin/gcc".format(arch), + "@sysroot_linux_{}//sysroot".format("amd64" if arch == "x86_64" else "arm64"), + ], + exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + target_compatible_with = ["@platforms//os:linux"], + ) + for arch in ["x86_64", "aarch64"] +] + toolchain( name = "linux_arm64_toolchain", exec_compatible_with = [ diff --git a/bazel/toolchains/gcc/extensions.bzl b/bazel/toolchains/gcc/extensions.bzl index 4ab73f666f..11aad0464b 100644 --- a/bazel/toolchains/gcc/extensions.bzl +++ b/bazel/toolchains/gcc/extensions.bzl @@ -1,18 +1,15 @@ """Pinned x86_64-hosted GCC cross compilers using the shared base sysroot.""" -_VERSION = "13.4.0" -_RELEASE = "18082025" -_SHA256 = { - "x86_64": "d96071c1b98499afd7b7b56ebd69ad414020edf66e982004acffe7df8aaf7e02", - "aarch64": "770cf6bf62bdf78763de526d3a9f5cae4c19f1a3aca0ef8f18b05f1a46d1ffaf", -} +load("//:versions.bzl", "VERSIONS") + +_GCC = VERSIONS["gcc_toolchain"] def _gcc_repository_impl(ctx): arch = ctx.attr.arch triple = arch + "-linux" ctx.download_and_extract( - url = "https://github.com/f0rmiga/gcc-builds/releases/download/{}/gcc-toolchain-{}-{}.tar.xz".format(_RELEASE, _VERSION, arch), - sha256 = _SHA256[arch], + url = "https://github.com/f0rmiga/gcc-builds/releases/download/{}/gcc-toolchain-{}-{}.tar.xz".format(_GCC["release"], _GCC["version"], arch), + sha256 = _GCC["sha256"][arch], ) # Never expose the archive's libc to compilation or linking. @@ -38,16 +35,19 @@ exec "${ROOT}/%s" "$@" ) cxx_prefix = "aarch64-linux/" if arch == "aarch64" else "" for path in [ - "lib/gcc/" + triple + "/" + _VERSION + "/" + filename + "lib/gcc/" + triple + "/" + _GCC["version"] + "/" + filename for filename in ["crtbegin.o", "crtend.o", "libgcc.a", "libgcc_eh.a", "include", "include-fixed"] ] + [ - cxx_prefix + "include/c++/" + _VERSION, + cxx_prefix + "include/c++/" + _GCC["version"], cxx_prefix + "lib64/libstdc++.a", cxx_prefix + "lib64/libstdc++.so", ]: if not ctx.path(path).exists: fail("GCC archive is missing compiler-owned runtime input " + path) + cxx_include = cxx_prefix + "include/c++/" + _GCC["version"] + gcc_lib = "lib/gcc/" + triple + "/" + _GCC["version"] ctx.file("BUILD.bazel", """ +load("@rules_cc//cc:cc_library.bzl", "cc_library") load("@rules_cc//cc/toolchains:cc_toolchain.bzl", "cc_toolchain") load(%r, "gcc_linux_cc_toolchain_config") @@ -66,6 +66,19 @@ filegroup( srcs = glob(["bin/**", "xbin/**", "lib/**", "lib64/**", "libexec/**", "include/**", %r]) + [%r], ) +cc_library( + name = "libstdcxx", + srcs = [%r], + hdrs = glob([%r]), + includes = %r, + linkstatic = True, +) + +filegroup( + name = "libgcc", + srcs = %r, +) + cc_toolchain( name = "cc_toolchain", all_files = ":files", @@ -81,7 +94,19 @@ cc_toolchain( ) exports_files(glob(["xbin/*"])) -""" % (str(ctx.attr._config), arch, triple, _VERSION, str(ctx.attr.sysroot), triple + "/**", str(ctx.attr.sysroot))) +""" % ( + str(ctx.attr._config), + arch, + triple, + _GCC["version"], + str(ctx.attr.sysroot), + triple + "/**", + str(ctx.attr.sysroot), + cxx_prefix + "lib64/libstdc++.a", + cxx_include + "/**", + [cxx_include, cxx_include + "/" + triple, cxx_include + "/backward"], + [gcc_lib + "/libgcc.a", gcc_lib + "/libgcc_eh.a"], + )) _gcc_repository = repository_rule( implementation = _gcc_repository_impl, diff --git a/bazel/toolchains/gcc/runtime_abi_test.sh b/bazel/toolchains/gcc/runtime_abi_test.sh new file mode 100755 index 0000000000..f86545ea54 --- /dev/null +++ b/bazel/toolchains/gcc/runtime_abi_test.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +set -euo pipefail + +root="${1%/xbin/gcc}" +sysroot="$2" +arch="$3" +version="$4" +check_glibc="$5" +readelf="$root/bin/readelf" +nm="$root/bin/nm" +cxx="$root/lib64" +if [[ "$arch" == aarch64 ]]; then + readelf="$root/bin/aarch64-linux-readelf" + nm="$root/bin/aarch64-linux-nm" + cxx="$root/aarch64-linux/lib64" +fi +gcc="$root/lib/gcc/$arch-linux/$version" + +declare -A defined=() undefined=() +max_major=0 +max_minor=0 +checked_exports=0 + +check_version() { + local symbol="$1" major minor + if [[ "$symbol" =~ @GLIBC_([0-9]+)\.([0-9]+) ]]; then + major="${BASH_REMATCH[1]}" + minor="${BASH_REMATCH[2]}" + if (( major > 2 || (major == 2 && minor > 31) )); then + echo "Runtime requires newer than glibc 2.31: $symbol" >&2 + exit 1 + fi + if (( major > max_major || (major == max_major && minor > max_minor) )); then + max_major="$major" + max_minor="$minor" + fi + fi +} + +# A shared sibling exposes symbol versions, but is NOT evidence that the static +# archive is safe: static objects usually have unversioned undefined names. +symbols="$("$readelf" --dyn-syms --wide "$cxx/libstdc++.so")" +while read -r index value size type bind visibility section symbol rest; do + if [[ "$section" == UND && -n "${symbol:-}" ]]; then + check_version "$symbol" + fi +done <<< "$symbols" + +# Resolve strong undefined archive symbols against the other static runtimes, +# GCC's crtbegin/crtend, and glibc's nonshared helpers (e.g. atexit). +for input in "$cxx/libstdc++.a" "$gcc/libgcc.a" "$gcc/libgcc_eh.a" "$gcc/crtbegin.o" "$gcc/crtend.o"; do + # The shared checker runs nm -u over every member, rejecting versioned + # requirements and newer unversioned __isoc23_*/__libc_single_threaded names. + BINARY="$input" NM="$nm" READELF="$readelf" bash "$check_glibc" + symbols="$("$readelf" --symbols --wide "$input")" + while read -r index value size type bind visibility section symbol rest; do + [[ -n "${symbol:-}" && ( "$bind" == GLOBAL || "$bind" == WEAK || "$bind" == UNIQUE ) ]] || continue + if [[ "$section" == UND ]]; then + check_version "$symbol" + [[ "$bind" != GLOBAL ]] || undefined["${symbol%%@*}"]=1 + else + defined["${symbol%%@*}"]=1 + fi + done <<< "$symbols" +done + +while IFS= read -r input; do + if [[ "$input" == */libc_nonshared.a ]]; then + symbols="$("$readelf" --symbols --wide "$input")" + else + # Ignore linker scripts named *.so; inspect actual ELF DSOs. + symbols="$("$readelf" --dyn-syms --wide "$input" 2>/dev/null)" || continue + fi + while read -r index value size type bind visibility section symbol rest; do + [[ -n "${symbol:-}" && "$section" != UND && ( "$bind" == GLOBAL || "$bind" == WEAK ) ]] || continue + # Private or post-floor exports cannot satisfy a glibc 2.31 contract. + [[ "$symbol" != *@GLIBC_PRIVATE ]] || continue + if [[ "$symbol" =~ @+GLIBC_([0-9]+)\.([0-9]+) ]]; then + major="${BASH_REMATCH[1]}" + minor="${BASH_REMATCH[2]}" + (( major < 2 || (major == 2 && minor <= 31) )) || continue + fi + defined["${symbol%%@*}"]=1 + checked_exports=$((checked_exports + 1)) + done <<< "$symbols" +done < <(find "$sysroot/lib" "$sysroot/usr/lib" -type f \( -name '*.so*' -o -name libc_nonshared.a \)) + +(( checked_exports > 0 )) || { echo "No base-sysroot ELF exports found" >&2; exit 1; } +# This one symbol is synthesized by the linker, not libc or a startup object. +defined[_GLOBAL_OFFSET_TABLE_]=1 +failed=0 +for symbol in "${!undefined[@]}"; do + if [[ ! -v "defined[$symbol]" ]]; then + echo "Static runtime symbol unavailable in base glibc <=2.31: $symbol" >&2 + failed=1 + fi +done +(( failed == 0 )) || exit 1 +echo "$arch: libstdc++.so maximum GLIBC_$max_major.$max_minor; ${#undefined[@]} strong static undefined names resolved against runtimes/CRT/base glibc <=2.31" diff --git a/bazel/toolchains/gcc/toolchain.bzl b/bazel/toolchains/gcc/toolchain.bzl index 3010941322..b462de1c83 100644 --- a/bazel/toolchains/gcc/toolchain.bzl +++ b/bazel/toolchains/gcc/toolchain.bzl @@ -21,6 +21,9 @@ def gcc_linux_cc_toolchain_config(name, arch, triple, version, sysroot): sysroot_path = sysroot.workspace_root + "/" + sysroot.package gcc_lib = root + "/lib/gcc/" + triple + "/" + version cxx_include = root + ("/aarch64-linux" if arch == "aarch64" else "") + "/include/c++/" + version + # -nostdinc removes GCC's configured archive/host include search. -idirafter + # restores only these compiler headers and base-sysroot libc headers, after + # project -I/-isystem paths (including the explicit C++ headers below). c_includes = [ gcc_lib + "/include", gcc_lib + "/include-fixed", @@ -41,6 +44,12 @@ def gcc_linux_cc_toolchain_config(name, arch, triple, version, sysroot): sysroot_path + "/usr/lib/" + arch + "-linux-gnu", sysroot_path + "/lib/" + arch + "-linux-gnu", ] + # -B selects bundled assembler/linker executables and startup objects. + # crtbegin/crtend come from gcc_lib; crt1/Scrt1, crti and crtn must instead + # come from the base sysroot's multiarch lib directories. -L repeats those + # paths for ld's libraries; unlike -B it does not control startup objects. + # --sysroot alone does not add Debian's usr/lib/-linux-gnu layout to + # these archives' configured GCC search paths, hence the explicit -B/-L. search_flags = ["--sysroot=" + sysroot_path, "-B" + root + "/bin/"] search_flags += ["-B" + directory + "/" for directory in lib_dirs] gcc_cc_toolchain_config( @@ -56,14 +65,13 @@ def gcc_linux_cc_toolchain_config(name, arch, triple, version, sysroot): dbg_compile_flags = ["-g"], host_system_name = "x86_64-linux", link_flags = search_flags + ["-L" + directory for directory in lib_dirs] + [ - "-static-libstdc++", "-static-libgcc", "-Wl,--build-id=md5", "-Wl,--hash-style=gnu", "-Wl,-z,relro,-z,now", ], - # The C driver does not interpret -static-libstdc++ for an explicit - # -lstdc++; select the archive explicitly as well. + # xbin/gcc is the C driver: -static-libstdc++ has no effect on explicit + # -lstdc++, so select the archive by name instead. link_libs = ["-l:libstdc++.a", "-lm"], opt_compile_flags = ["-O2", "-DNDEBUG"], # The bundled GNU BFD linker does not support --start-lib/--end-lib. diff --git a/bazel/toolchains/gcc/toolchain_test.bzl b/bazel/toolchains/gcc/toolchain_test.bzl index 93532a8b88..38d25de140 100644 --- a/bazel/toolchains/gcc/toolchain_test.bzl +++ b/bazel/toolchains/gcc/toolchain_test.bzl @@ -2,6 +2,7 @@ load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") load("@rules_cc//cc/common:cc_common.bzl", "cc_common") +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") def _gcc_paths_test_impl(ctx): env = analysistest.begin(ctx) @@ -27,3 +28,48 @@ gcc_paths_test = analysistest.make( "sysroot": attr.label(allow_single_file = True), }, ) + +def _gcc_runtime_test_impl(ctx): + env = analysistest.begin(ctx) + info = analysistest.target_under_test(env)[CcInfo] + root = ctx.file.compiler.path[:-len("/xbin/gcc")] + prefix = root + ("/aarch64-linux" if ctx.attr.arch == "aarch64" else "") + include = prefix + "/include/c++/" + ctx.attr.version + includes = info.compilation_context.system_includes.to_list() + for directory in [include, include + "/" + ctx.attr.arch + "-linux", include + "/backward"]: + asserts.true(env, directory in includes, "Missing repository-rooted C++ include: " + directory) + all_includes = includes + info.compilation_context.includes.to_list() + info.compilation_context.quote_includes.to_list() + for directory in all_includes: + # cc_library also supplies bin/genfiles variants of repository paths. + repository_path = directory == root or directory.startswith(root + "/") + generated_path = directory.startswith("bazel-out/") and ( + directory.endswith("/" + root) or ("/" + root + "/") in directory + ) + asserts.true(env, repository_path or generated_path, "Include directory escapes GCC repository: " + directory) + asserts.false(env, directory.count("external/") > 1, "Repository prefix duplicated: " + directory) + libraries = [] + for linker_input in info.linking_context.linker_inputs.to_list(): + inputs = linker_input.libraries + libraries.extend(inputs.to_list() if type(inputs) == "depset" else inputs) + asserts.equals(env, 1, len(libraries)) + for library in libraries: + asserts.equals(env, prefix + "/lib64/libstdc++.a", library.static_library.path) + asserts.equals(env, None, library.dynamic_library) + asserts.equals(env, None, library.interface_library) + gcc_lib = root + "/lib/gcc/" + ctx.attr.arch + "-linux/" + ctx.attr.version + asserts.equals( + env, + [gcc_lib + "/libgcc.a", gcc_lib + "/libgcc_eh.a"], + sorted([file.path for file in ctx.attr.libgcc[DefaultInfo].files.to_list()]), + ) + return analysistest.end(env) + +gcc_runtime_test = analysistest.make( + _gcc_runtime_test_impl, + attrs = { + "arch": attr.string(mandatory = True), + "compiler": attr.label(allow_single_file = True), + "libgcc": attr.label(mandatory = True), + "version": attr.string(mandatory = True), + }, +) diff --git a/bazel/versions.bzl b/bazel/versions.bzl index 87015cdaf2..2040b7da44 100644 --- a/bazel/versions.bzl +++ b/bazel/versions.bzl @@ -15,6 +15,14 @@ LLVM_DISTRIBUTIONS = { } VERSIONS = { + "gcc_toolchain": { + "version": "13.4.0", + "release": "18082025", + "sha256": { + "x86_64": "d96071c1b98499afd7b7b56ebd69ad414020edf66e982004acffe7df8aaf7e02", + "aarch64": "770cf6bf62bdf78763de526d3a9f5cae4c19f1a3aca0ef8f18b05f1a46d1ffaf", + }, + }, "cmake": "3.23.2", "git": "2.55.0", "llvm": LLVM_VERSION, @@ -96,16 +104,32 @@ VERSIONS = { }, }, - # Sysroot hashes organized by glibc version and architecture. - # Format: sysroot_hashes[glibc_version][arch] + # Last release containing deprecated libstdc++ sysroot variants. Do not bump with bins_release. + "sysroot_stdcc_release": "0.2.27", + + # Sysroot hashes organized by glibc version, stdlib variant, and architecture. + # Deprecated libstdc++ variants retain their published hashes but are no longer built. + # Format: sysroot_hashes[glibc_version][stdlib_variant][arch] "sysroot_hashes": { "2.31": { - "amd64": "c027659e4ed93c2f53ca29cc611962790cde21289cbedcfd3419354d6a50767e", - "arm64": "33fb571b3b9b5edb9854fa4444719fae01c0c08825b3051357fcd4ab3721749a", + "base": { + "amd64": "c027659e4ed93c2f53ca29cc611962790cde21289cbedcfd3419354d6a50767e", + "arm64": "33fb571b3b9b5edb9854fa4444719fae01c0c08825b3051357fcd4ab3721749a", + }, + "13": { + "amd64": "0562bde13b68aa1c49ab286c5bb765158a9f17fb69f3f1d190672ef171502dbf", + "arm64": "db9c5b96cd4ecc9b5ec4ac865d5e91eacf2020ef583aa262c6a906741f46ca11", + }, }, "2.28": { - "amd64": "d7ea7bc466377b2294127875216a701e400a57445b1c8daca223c71445681fc1", - "arm64": "ae47a5b829fa354d1936e97c3c01aca3b3fa49546717809f1618a2c20d9ff5f2", + "base": { + "amd64": "d7ea7bc466377b2294127875216a701e400a57445b1c8daca223c71445681fc1", + "arm64": "ae47a5b829fa354d1936e97c3c01aca3b3fa49546717809f1618a2c20d9ff5f2", + }, + "13": { + "amd64": "b0a230b3875f36dc50739fb4711e8745a0b65fd1b1e4d0c4961c6832980c0510", + "arm64": "8fd6257c1a6ea3048d03d46e3a452873f885fd17ced5640deb3b3994c8dda4bf", + }, }, },