From 4274927ace537066287d295d0ac3acc4690bb962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 15:00:56 +0200 Subject: [PATCH 01/33] bazel: switch C++ toolchain to hermetic-llvm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace toolchains_llvm + prebuilt LLVM release binaries with hermetic-llvm (BCR module 'llvm'): statically linked LLVM 22.1.8 binaries and a zero-sysroot cc_toolchain. No host compiler, linker, headers or libraries are involved; the toolchain runs unmodified on hosts without libxml2.so.2 (Ubuntu 25.10+, Arch, Fedora 41+, where prebuilt ld.lld could not start) and on non-FHS distros such as NixOS. clang-tidy now comes from @llvm//tools:clang-tidy. tcl_lang needs a one-line patch: tclZipfs.c's '#include "crypt.h"' resolved to glibc's crypt.h instead of the vendored minizip header once glibc headers became explicit -isystem directories. Drop the override when a fixed tcl_lang lands in BCR. Tip of the hat to @dzbarsky for pointing us at hermetic-llvm: https://github.com/bazel-contrib/toolchains_llvm/pull/795#issuecomment-4881863463 Signed-off-by: Øyvind Harboe --- MODULE.bazel | 32 +++--- MODULE.bazel.lock | 106 +++--------------- ...ude-vendored-minizip-crypt.h-by-path.patch | 20 ++++ bazel/tcl-patches/BUILD.bazel | 1 + docs/agents/ci.md | 2 +- docs/user/Bazel.md | 20 ++-- etc/bazel-hermetic | 100 +++++++++++++++++ etc/run-clang-tidy.sh | 2 +- test/downstream/MODULE.bazel | 15 +-- tools/lint/BUILD.bazel | 2 +- 10 files changed, 175 insertions(+), 125 deletions(-) create mode 100644 bazel/tcl-patches/0001-tclZipfs-include-vendored-minizip-crypt.h-by-path.patch create mode 100644 bazel/tcl-patches/BUILD.bazel create mode 100755 etc/bazel-hermetic diff --git a/MODULE.bazel b/MODULE.bazel index b66488108ff..69e6eca7cc9 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -83,6 +83,18 @@ bazel_dep(name = "or-tools", version = "9.15") bazel_dep(name = "spdlog", version = "1.15.1") bazel_dep(name = "sv-lang", version = "10.0.1-20260316-f04e8156") bazel_dep(name = "tcl_lang", version = "9.0.2.bcr.1") + +# tclZipfs.c's '#include "crypt.h"' picks up glibc's crypt.h instead of the +# vendored minizip one when glibc headers are explicit -isystem directories +# (hermetic-llvm zero-sysroot toolchain). Drop when fixed in a tcl_lang +# BCR release. +single_version_override( + module_name = "tcl_lang", + patch_strip = 1, + patches = [ + "//bazel/tcl-patches:0001-tclZipfs-include-vendored-minizip-crypt.h-by-path.patch", + ], +) bazel_dep(name = "tcmalloc", version = "0.0.0-20250927-12f2552") bazel_dep(name = "yaml-cpp", version = "0.9.0") bazel_dep(name = "zlib", version = "1.3.1.bcr.8") @@ -101,7 +113,12 @@ git_override( ## Lock the compiler version and avoid any local compiler. ## Downstream consumers must configure their own C++ toolchain. -bazel_dep(name = "toolchains_llvm", version = "1.5.0", dev_dependency = True) +## +## hermetic-llvm (BCR module "llvm") provides statically linked LLVM +## binaries and a zero-sysroot cc_toolchain: no host compiler, linker, +## libxml2 or /usr/include involved, and it runs unmodified on distros +## without FHS paths (e.g. NixOS). +bazel_dep(name = "llvm", version = "0.8.11", dev_dependency = True) # --- Dev dependencies (not propagated to downstream consumers) --- @@ -162,19 +179,8 @@ bazel_dep(name = "yosys-slang", version = "0.0.0", dev_dependency = True) # --- Extensions --- -llvm = use_extension( - "@toolchains_llvm//toolchain/extensions:llvm.bzl", - "llvm", - dev_dependency = True, -) -llvm.toolchain( - llvm_version = "20.1.8", -) -use_repo(llvm, "llvm_toolchain", "llvm_toolchain_llvm") - -# FYI: Comment out @llvm_toolchain//:all on NixOS and use the local clang toolchain instead. register_toolchains( - "@llvm_toolchain//:all", + "@llvm//toolchain:all", "@rules_verilator//verilator:verilator_toolchain", dev_dependency = True, ) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 9303bdd309e..fc6d726abc0 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -86,8 +86,10 @@ "https://bcr.bazel.build/modules/bazel_features/1.39.0/MODULE.bazel": "28739425c1fc283c91931619749c832b555e60bcd1010b40d8441ce0a5cf726d", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", "https://bcr.bazel.build/modules/bazel_features/1.41.0/MODULE.bazel": "6e0f87fafed801273c371d41e22a15a6f8abf83fdd7f87d5e44ad317b94433d0", + "https://bcr.bazel.build/modules/bazel_features/1.42.0/MODULE.bazel": "e8ca15cb2639c5f12183db6dcb678735555d0cdd739b32a0418b6532b5e565f8", "https://bcr.bazel.build/modules/bazel_features/1.43.0/MODULE.bazel": "defa2226f06ba20550d6548c3a2ea2a7929634437a52973869c20c225450eb91", - "https://bcr.bazel.build/modules/bazel_features/1.43.0/source.json": "1c4207dc858d6de0eecef30026793616bbf420c74aac27b6bad212534a730437", + "https://bcr.bazel.build/modules/bazel_features/1.50.0/MODULE.bazel": "2083ef9c7a469f520890483ccf8e0189d6e71e2117e7752e15e6554433d5ae3e", + "https://bcr.bazel.build/modules/bazel_features/1.50.0/source.json": "e0ee3debde2789ff56e4452e612d126925ba9ab64d4bde79c67f099d2902df9b", "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", "https://bcr.bazel.build/modules/bazel_lib/3.0.0-rc.0/MODULE.bazel": "d6e00979a98ac14ada5e31c8794708b41434d461e7e7ca39b59b765e6d233b18", @@ -408,7 +410,8 @@ "https://bcr.bazel.build/modules/freetype/2.13.3/source.json": "a051388a7fa6b0e2ccf8e70bc30ecb00d9708fa98e5c2adac1d67514d8332cc3", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.2/MODULE.bazel": "ae318680f31d1960f1d102db3b7e04cfa6fb38ae9ba54319b6b9b104b49e7c65", - "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.2/source.json": "004aeff692d2e12debb1105c5c332a95db9dfd7fe68be60c6f9cf7e1f18613bf", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/MODULE.bazel": "f1b7bb2dd53e8f2ef984b39485ec8a44e9076dda5c4b8efd2fb4c6a6e856a31d", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/source.json": "ebe931bfe362e4b41e59ee00a528db6074157ff2ced92eb9e970acab2e1089c9", "https://bcr.bazel.build/modules/gazelle/0.27.0/MODULE.bazel": "3446abd608295de6d90b4a8a118ed64a9ce11dcb3dda2dc3290a22056bd20996", "https://bcr.bazel.build/modules/gazelle/0.30.0/MODULE.bazel": "f888a1effe338491f35f0e0e85003b47bb9d8295ccba73c37e07702d8d31c65b", "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", @@ -483,6 +486,8 @@ "https://bcr.bazel.build/modules/libxau/1.0.12.bcr.1/source.json": "4076a85407185883f1563210abf36e80c9636d076d7197169bb431595b3f1151", "https://bcr.bazel.build/modules/libxcb/1.17.0.bcr.2/MODULE.bazel": "83d6740822a296210c0c60cd429a892934bdb237f7135eaf395b370c05af32a5", "https://bcr.bazel.build/modules/libxcb/1.17.0.bcr.2/source.json": "58c8c30c5d0f6253c94d7a38ab95fd5174683d613d47b9114520da6acef52ae8", + "https://bcr.bazel.build/modules/llvm/0.8.11/MODULE.bazel": "0f8c30b74be64f0e91764e925d0f562c70e8d85b6cea912f724d6b3753d6a33f", + "https://bcr.bazel.build/modules/llvm/0.8.11/source.json": "b40edb2bb2ed271bf613b396d245cb473c42fb057a2b26a3bc7d7e8bfcf6aa71", "https://bcr.bazel.build/modules/m4/1.4.20.bcr.4/MODULE.bazel": "582008fee330b47fe8db3e786cf78f05c926d2b37fcde0178316bbc5a717e096", "https://bcr.bazel.build/modules/m4/1.4.21/MODULE.bazel": "f0228f83067f5b7c3ecb5dd5269ca4337e935c31305e26d5a60a8876f6fc2620", "https://bcr.bazel.build/modules/m4/1.4.21/source.json": "e6df963f42bf3e7c227274f583a3bccb5c4e8e2ecd5b730b6e47bdb919e02950", @@ -517,6 +522,7 @@ "https://bcr.bazel.build/modules/or-tools/9.15/MODULE.bazel": "8fbef0fbc8b0dd12feece54228b1cf8fc74bc20879715a4c6aa1dda0115ed6b1", "https://bcr.bazel.build/modules/or-tools/9.15/source.json": "02a81ec210d571aa98bf116a91a512c0dd2d8509ab64c30f113eb5674280ea31", "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", + "https://bcr.bazel.build/modules/package_metadata/0.0.3/MODULE.bazel": "77890552ecea9e284b5424c9de827a58099348763a4359e975c359a83d4faa83", "https://bcr.bazel.build/modules/package_metadata/0.0.6/MODULE.bazel": "341dab6f417197494517d54c8e557c0baee1de7aec83543a4fbefe57900acb7e", "https://bcr.bazel.build/modules/package_metadata/0.0.6/source.json": "9581d8b22db43550ac75ecc314ee4fa0a33400bfdc77d1317d8af6b18dca7756", "https://bcr.bazel.build/modules/pcre2/10.43/MODULE.bazel": "08eaa025111bd0fedc14a8187c2905fa6ee4501fbe558193e9bf6cc3e2cdf23c", @@ -531,7 +537,8 @@ "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", - "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", + "https://bcr.bazel.build/modules/platforms/1.1.0/MODULE.bazel": "1c0c09f5bdcf4b3f924720d2478a3711cb39f4977019ca5988685e5b7e18b3d2", + "https://bcr.bazel.build/modules/platforms/1.1.0/source.json": "fcf351c47596c939140ab0d333dfdd08ed1ea6ce33c2fe70c12493a301cf1344", "https://bcr.bazel.build/modules/prometheus-cpp/1.2.4/MODULE.bazel": "0fbe5dcff66311947a3f6b86ebc6a6d9328e31a28413ca864debc4a043f371e5", "https://bcr.bazel.build/modules/prometheus-cpp/1.3.0/MODULE.bazel": "ce82e086bbc0b60267e970f6a54b2ca6d0f22d3eb6633e00e2cc2899c700f3d8", "https://bcr.bazel.build/modules/prometheus-cpp/1.3.0/source.json": "8cb66b4e535afc718e9d104a3db96ccb71a42ee816a100e50fd0d5ac843c0606", @@ -624,8 +631,8 @@ "https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4", "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", "https://bcr.bazel.build/modules/rules_cc/0.2.18/MODULE.bazel": "4460ec36adc8f722a6a2a4ac9374cb91f2acebadaa93fc37966129afb3dece87", - "https://bcr.bazel.build/modules/rules_cc/0.2.18/source.json": "abad668ff2fd63ada1ac49bf386d37e27048b89a3465a6fd968bb832b00a09d3", - "https://bcr.bazel.build/modules/rules_cc/0.2.2/MODULE.bazel": "a0656c5a8ff7f76bb1319ebf301bab9d94da5b48894cac25a14ed115f9dd0884", + "https://bcr.bazel.build/modules/rules_cc/0.2.20/MODULE.bazel": "f5c07bce5ddcb99be21a0812ff5aadb439e688b7449c6542152363b2fd859c1a", + "https://bcr.bazel.build/modules/rules_cc/0.2.20/source.json": "1155433dc6b8161bc339ce94095b337ed95feb1f048b014e10b62339d4b4239c", "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", "https://bcr.bazel.build/modules/rules_cc/0.2.9/MODULE.bazel": "34263f1dca62ea664265438cef714d7db124c03e1ed55ebb4f1dc860164308d1", @@ -793,24 +800,24 @@ "https://bcr.bazel.build/modules/swig/4.3.0/MODULE.bazel": "51619e147172c5380869cc90460b1c7fecfe21d6f566e97bc7ecf61244bdc7b8", "https://bcr.bazel.build/modules/systemc/3.0.2/MODULE.bazel": "7c4283e827307eb8eedfcba74f500f114e248916d948a2cdc45971818f3fde0d", "https://bcr.bazel.build/modules/systemc/3.0.2/source.json": "47370abd3ed04ce3c1a65a5b87c973e3e80e7e10e6b1dca2f15076f612d86d4c", + "https://bcr.bazel.build/modules/tar.bzl/0.10.4/MODULE.bazel": "e8f9ff79199e8d9eaad7f1b0a77ad74b30bb82d794b87d8ca942bead5de83ae9", + "https://bcr.bazel.build/modules/tar.bzl/0.10.4/source.json": "20143442376c03426f6135292ba02d825cb75308aa47e6bf42dd4cc5a435c2ff", "https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468", "https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351", "https://bcr.bazel.build/modules/tar.bzl/0.5.5/MODULE.bazel": "4bfab9bbc7a1966c2c5f7371f5848f5e2d27c465951b4435adc9aaf00ed681da", "https://bcr.bazel.build/modules/tar.bzl/0.6.0/MODULE.bazel": "a3584b4edcfafcabd9b0ef9819808f05b372957bbdff41601429d5fd0aac2e7c", - "https://bcr.bazel.build/modules/tar.bzl/0.6.0/source.json": "4a620381df075a16cb3a7ed57bd1d05f7480222394c64a20fa51bdb636fda658", "https://bcr.bazel.build/modules/tcl_lang/8.6.16.bcr.1/MODULE.bazel": "1fc27ececc903378b88ad5a0b92d2675b54fe3add9bcc27d612195bd823c2f2d", "https://bcr.bazel.build/modules/tcl_lang/9.0.2.bcr.1/MODULE.bazel": "43ade6ad42bac483f82f02c6705a0b7afe021908d2719433bcddcb5ab98e73a1", "https://bcr.bazel.build/modules/tcl_lang/9.0.2.bcr.1/source.json": "fac478c17b901d1b168339ed49881e5883fac98bb9397586300d3ca2ed5cefa8", "https://bcr.bazel.build/modules/tcmalloc/0.0.0-20250927-12f2552/MODULE.bazel": "b702a6b6806b1041d84918c5098b765b204261647f8cb3e75e0f439106b65ddd", "https://bcr.bazel.build/modules/tcmalloc/0.0.0-20250927-12f2552/source.json": "a6f5da61dd65e3f2f7380b4f52dd4b0f771a5b6ba9db7b46be7c28c52bc7af58", - "https://bcr.bazel.build/modules/toolchains_llvm/1.5.0/MODULE.bazel": "31c7077ef64bafdf2dfb46d4bca321b4e8f143b00ac68b2c31f5ff0c91044b60", - "https://bcr.bazel.build/modules/toolchains_llvm/1.5.0/source.json": "aecbd0eea924f27bf8d33d3823f1427e1eddc826ff96425e4304b0d7ad6d7ffa", "https://bcr.bazel.build/modules/upb/0.0.0-20211020-160625a/MODULE.bazel": "6cced416be2dc5b9c05efd5b997049ba795e5e4e6fafbe1624f4587767638928", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/upb/0.0.0-20230516-61a97ef/MODULE.bazel": "c0df5e35ad55e264160417fd0875932ee3c9dda63d9fccace35ac62f45e1b6f9", "https://bcr.bazel.build/modules/upb/0.0.0-20230907-e7430e6/MODULE.bazel": "3a7dedadf70346e678dc059dbe44d05cbf3ab17f1ce43a1c7a42edc7cbf93fd9", "https://bcr.bazel.build/modules/verilator/5.036.bcr.3/MODULE.bazel": "19ff7c6a9133f404157b0d499ff8c93bfc7e98e7e8d7569b51e50f79a5a33d1b", "https://bcr.bazel.build/modules/verilator/5.036.bcr.3/source.json": "d7aa35ef34334e9bf11ffc308c1fa7f44f313b45e4e80c497cbd26deb585bcbf", + "https://bcr.bazel.build/modules/with_cfg.bzl/0.12.0/MODULE.bazel": "b573395fe63aef4299ba095173e2f62ccfee5ad9bbf7acaa95dba73af9fc2b38", "https://bcr.bazel.build/modules/with_cfg.bzl/0.14.1/MODULE.bazel": "aa0ef3f6c67dd35db7ac38c76785dc02f48b53f34e16b20487e70f51b5324d0a", "https://bcr.bazel.build/modules/with_cfg.bzl/0.14.1/source.json": "4666d3035f69063ecd136f3f0c68e424f1b1dd97ffc9cceb679522c0be38761b", "https://bcr.bazel.build/modules/xcb-proto/1.17.0/MODULE.bazel": "13062923a9e615a2f4d284a3f28e467536f7abcfd79d2c04c2acf3837e9da31b", @@ -1852,7 +1859,7 @@ }, "@@rules_multitool+//multitool:extension.bzl%multitool": { "general": { - "bzlTransitiveDigest": "wiKpOglG9yFkIjhZb/HSDMiRCk7GiA+1Jqs2oUXXAUE=", + "bzlTransitiveDigest": "6xmws4Qtb2mS1rTiYu1itT8wHrwiyWAgWXUA+euChIk=", "usagesDigest": "ZVm5YLWTHZE8lFq+UcA2T2lhKEur8bIVk9tvjn8SO2U=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -2130,7 +2137,7 @@ }, "@@rules_rust+//crate_universe/private:internal_extensions.bzl%cu_nr": { "general": { - "bzlTransitiveDigest": "XNSSyC0l44xJx00HmJ1oOQa+7MWTCktagNjZqsxhIYc=", + "bzlTransitiveDigest": "Otq2Iebci1zuLjK+lEbK5I2Lz+3gYicIhW6OGuK94aI=", "usagesDigest": "dQ7SQZ7uSSL3vVKSMBKRxKJUm9OVrZZA+S1/QQfT570=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -2439,85 +2446,6 @@ ] } }, - "@@toolchains_llvm+//toolchain/extensions:llvm.bzl%llvm": { - "general": { - "bzlTransitiveDigest": "SFT0LhY0ioB2PsbncmTCGyGh8M0OtAJ2fCq0fHtf7ps=", - "usagesDigest": "ZuaBuvEE8efZLVQ2tR8Vbv3podpYbZjO703SKYIAb4g=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "llvm_toolchain_llvm": { - "repoRuleId": "@@toolchains_llvm+//toolchain:rules.bzl%llvm", - "attributes": { - "alternative_llvm_sources": [], - "auth_patterns": {}, - "distribution": "auto", - "exec_arch": "", - "exec_os": "", - "extra_llvm_distributions": {}, - "libclang_rt": {}, - "llvm_mirror": "", - "llvm_version": "20.1.8", - "llvm_versions": {}, - "netrc": "", - "sha256": {}, - "strip_prefix": {}, - "urls": {} - } - }, - "llvm_toolchain": { - "repoRuleId": "@@toolchains_llvm+//toolchain:rules.bzl%toolchain", - "attributes": { - "absolute_paths": false, - "archive_flags": {}, - "compile_flags": {}, - "conly_flags": {}, - "coverage_compile_flags": {}, - "coverage_link_flags": {}, - "cxx_builtin_include_directories": {}, - "cxx_flags": {}, - "cxx_standard": {}, - "dbg_compile_flags": {}, - "exec_arch": "", - "exec_os": "", - "extra_exec_compatible_with": {}, - "extra_target_compatible_with": {}, - "fastbuild_compile_flags": {}, - "link_flags": {}, - "link_libs": {}, - "llvm_versions": { - "": "20.1.8" - }, - "opt_compile_flags": {}, - "opt_link_flags": {}, - "stdlib": {}, - "target_settings": {}, - "unfiltered_compile_flags": {}, - "toolchain_roots": {}, - "sysroot": {} - } - } - }, - "recordedRepoMappingEntries": [ - [ - "toolchains_llvm+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "toolchains_llvm+", - "bazel_tools", - "bazel_tools" - ], - [ - "toolchains_llvm+", - "toolchains_llvm", - "toolchains_llvm+" - ] - ] - } - }, "@@yosys-slang+//:dependency_support/slang_ext.bzl%vendored_slang_extension": { "general": { "bzlTransitiveDigest": "5QIB1e5+v5Cj2rslpyAj2M5L+l6B1BFByldLM+ewKMA=", diff --git a/bazel/tcl-patches/0001-tclZipfs-include-vendored-minizip-crypt.h-by-path.patch b/bazel/tcl-patches/0001-tclZipfs-include-vendored-minizip-crypt.h-by-path.patch new file mode 100644 index 00000000000..11bf1388c8b --- /dev/null +++ b/bazel/tcl-patches/0001-tclZipfs-include-vendored-minizip-crypt.h-by-path.patch @@ -0,0 +1,20 @@ +tclZipfs.c includes the vendored minizip crypt.h as #include "crypt.h", +relying on compat/zlib/contrib/minizip preceding any system directory +that also has a crypt.h. Hermetic toolchains that provide glibc headers +as explicit -isystem directories (e.g. the hermetic-llvm cc_toolchain) +search glibc's crypt.h (Unix password hashing) first, and the build +fails with implicit declarations of init_keys/decrypt_byte/zencode. +Include the intended header by its unambiguous path relative to the +compat/zlib include root instead. + +--- a/generic/tclZipfs.c ++++ b/generic/tclZipfs.c +@@ -75,7 +75,7 @@ + } while (0) + + #include "zlib.h" +-#include "crypt.h" ++#include "contrib/minizip/crypt.h" + #include "zutil.h" + #include "crc32.h" + diff --git a/bazel/tcl-patches/BUILD.bazel b/bazel/tcl-patches/BUILD.bazel new file mode 100644 index 00000000000..d518110449e --- /dev/null +++ b/bazel/tcl-patches/BUILD.bazel @@ -0,0 +1 @@ +exports_files(glob(["*.patch"])) diff --git a/docs/agents/ci.md b/docs/agents/ci.md index 8b9b48f1f37..f02062daada 100644 --- a/docs/agents/ci.md +++ b/docs/agents/ci.md @@ -23,7 +23,7 @@ bazel build --config=lint //src/utl/... # Full lint scope (excludes submodules): bazel build --config=lint -- //src/... //third-party/... -//src/sta/... -//third-party/abc/... ``` -- Uses `clang-tidy` from `@llvm_toolchain` (the same binary `etc/run-clang-tidy.sh` uses). +- Uses `clang-tidy` from `@llvm//tools` (the same binary `etc/run-clang-tidy.sh` uses). - Reports land at `$(bazel info bazel-bin)//_rules_lint/.AspectRulesLintClangTidy.out`. - Generated files (SWIG, bison, flex) are auto-skipped. Targets tagged `no-lint` are skipped. - Aspect + config defined in `tools/lint/` and `//:.bazelrc` (`--config=lint`). diff --git a/docs/user/Bazel.md b/docs/user/Bazel.md index d00df167f74..964068a359b 100644 --- a/docs/user/Bazel.md +++ b/docs/user/Bazel.md @@ -108,18 +108,18 @@ git_override( ### Suggested: pin the C++ toolchain for reproducibility -OpenROAD uses `toolchains_llvm` internally to lock the compiler version -and ensure reproducible builds across developers and CI. Downstream -consumers can use any C++20-capable compiler, but pinning the same -toolchain is recommended to avoid compiler-specific issues: +OpenROAD uses [hermetic-llvm](https://github.com/hermeticbuild/hermetic-llvm) +(BCR module `llvm`) internally to lock the compiler version and ensure +reproducible builds across developers and CI: statically linked LLVM +binaries and a zero-sysroot cc_toolchain, so no host compiler, headers +or libraries are involved. Downstream consumers can use any +C++20-capable compiler, but pinning the same toolchain is recommended +to avoid compiler-specific issues: ```starlark -bazel_dep(name = "toolchains_llvm", version = "1.5.0") +bazel_dep(name = "llvm", version = "0.8.11") -llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm") -llvm.toolchain(llvm_version = "20.1.8") -use_repo(llvm, "llvm_toolchain") -register_toolchains("@llvm_toolchain//:all") +register_toolchains("@llvm//toolchain:all") ``` ### Dev dependencies not leaked to consumers @@ -129,7 +129,7 @@ on downstream projects via MVS: - rules_pkg — only needed for //:install - `rules_verilator`, `verilator` — only needed for test/orfs simulation -- `toolchains_llvm` extension and toolchain registration +- `llvm` (hermetic-llvm) toolchain registration The downstream test at `test/downstream/` verifies these invariants. diff --git a/etc/bazel-hermetic b/etc/bazel-hermetic new file mode 100755 index 00000000000..bf49b380f5c --- /dev/null +++ b/etc/bazel-hermetic @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# Launch bazel in a pruned environment so that toolchains and tools the +# build is supposed to provide hermetically cannot be picked up from the +# host. This is a ratchet, not a proof: it removes host compilers, +# linkers and interpreters from PATH and disables Bazel's local C++ +# toolchain autodetection, but the C++ toolchain still compiles against +# the host glibc sysroot (/usr/include, /usr/lib), and Bazel itself, +# a JVM-independent binary, comes from the host. +# +# Usage: etc/bazel-hermetic [args...] +# e.g. etc/bazel-hermetic build //:openroad +# +# The allowlist below is the contract for what the build may use from the +# host. Grow it only with a comment stating which repository rule or +# action needs the entry and why bazel cannot provide it. +set -euo pipefail + +# Base shell utilities: repository rules (patch_cmds, configure scripts of +# module extensions) run through /bin/sh and expect a POSIX userland. +ALLOWED_TOOLS=( + bash sh env + basename dirname readlink realpath pwd + cat cp ln ls mkdir mktemp mv rm rmdir touch chmod stat find + cut grep head sed sort tail tr uniq wc od printf true false test expr sleep + awk # module extension configure steps + diff cmp patch # single_version_override / archive_override patches + tar gzip bzip2 xz zstd unzip # archive extraction fallbacks + uname id hostname date which getconf nproc + git # tools/workspace_status.sh embeds version info + gh # optional; used by some developer workflows +) + +TOOLBOX=$(mktemp -d "${TMPDIR:-/tmp}/bazel-hermetic.XXXXXX") +trap 'rm -rf "${TOOLBOX}"' EXIT + +for tool in "${ALLOWED_TOOLS[@]}"; do + if path=$(command -v -- "${tool}" 2>/dev/null); then + ln -s "${path}" "${TOOLBOX}/${tool}" 2>/dev/null || true + fi +done + +# The bazel binary itself. bazelisk is often a node or shell wrapper whose +# interpreter must not leak into the pruned PATH, so resolve the real ELF +# binary it manages (~/.cache/bazelisk) instead of symlinking the wrapper. +BAZEL=$(command -v bazelisk || command -v bazel) || + { echo >&2 "bazel-hermetic: no bazel or bazelisk on PATH"; exit 1; } +if [[ $(head -c 4 "$(readlink -f "${BAZEL}")") != $'\x7fELF' ]]; then + version=$("${BAZEL}" --version | awk '{print $2}') + arch=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/x86_64/') + meta="${HOME}/.cache/bazelisk/downloads/metadata/bazelbuild/bazel-${version}-linux-${arch}" + real="${HOME}/.cache/bazelisk/downloads/sha256/$(cat "${meta}" 2>/dev/null)/bin/bazel" + [[ -x ${real} ]] || + { echo >&2 "bazel-hermetic: ${BAZEL} is a wrapper and no real bazel-${version} found under ~/.cache/bazelisk"; exit 1; } + BAZEL=${real} +fi +ln -s "${BAZEL}" "${TOOLBOX}/bazel" + +if [[ $# -eq 0 ]]; then + echo >&2 "usage: etc/bazel-hermetic [startup flags] [args...]" + exit 1 +fi + +# Leading --flags are bazel startup options (e.g. --output_base=...). +startup_flags=() +while [[ $# -gt 0 && $1 == --* ]]; do + startup_flags+=("$1") + shift +done +if [[ $# -eq 0 ]]; then + echo >&2 "usage: etc/bazel-hermetic [startup flags] [args...]" + exit 1 +fi +command=$1 +shift + +# Flags only exist on build-ish commands; startup flags go before the command. +extra_flags=() +case "${command}" in + build|test|run|cquery|aquery|coverage|fetch|vendor|sync|mod) + extra_flags=( + # Fail local C++ toolchain autodetection instead of silently using + # /usr/bin/gcc; the hermetic toolchain must win resolution anyway. + "--repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1" + # Repository rules and actions see only the allowlisted tools. + "--repo_env=PATH=${TOOLBOX}" + "--action_env=PATH=${TOOLBOX}" + ) + ;; +esac + +# --nosystem_rc/--nohome_rc: only the workspace .bazelrc applies, so host- +# specific configuration (e.g. remote caches) cannot mask missing hermeticity. +exec env -i \ + HOME="${HOME}" \ + USER="${USER:-$(id -un)}" \ + LOGNAME="${LOGNAME:-$(id -un)}" \ + TERM="${TERM:-dumb}" \ + LANG=C.UTF-8 \ + PATH="${TOOLBOX}" \ + "${TOOLBOX}/bazel" --nosystem_rc --nohome_rc "${startup_flags[@]}" "${command}" "${extra_flags[@]}" "$@" diff --git a/etc/run-clang-tidy.sh b/etc/run-clang-tidy.sh index e66495f2275..b1da327ce3a 100755 --- a/etc/run-clang-tidy.sh +++ b/etc/run-clang-tidy.sh @@ -24,7 +24,7 @@ fi # Use either CLANG_TIDY provided by the user as environment variable or use # our own from the toolchain we configured in the MODULE.bazel -export CLANG_TIDY="${CLANG_TIDY:-$("${BAZEL}" run -c opt --run_under='echo' @llvm_toolchain//:clang-tidy 2>/dev/null)}" +export CLANG_TIDY="${CLANG_TIDY:-$("${BAZEL}" run -c opt --run_under='echo' @llvm//tools:clang-tidy 2>/dev/null)}" # The user should keep the compilation DB fresh, but refresh here # if substantial things changed or it is not there in the first place. diff --git a/test/downstream/MODULE.bazel b/test/downstream/MODULE.bazel index 5b5612a9916..9e8365835d4 100644 --- a/test/downstream/MODULE.bazel +++ b/test/downstream/MODULE.bazel @@ -20,18 +20,13 @@ git_override( remote = "https://github.com/The-OpenROAD-Project/qt_bazel_prebuilts", ) -# toolchains_llvm is dev_dependency in OpenROAD, so the root module must -# configure the C++ toolchain. -bazel_dep(name = "toolchains_llvm", version = "1.5.0") - -llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm") -llvm.toolchain( - llvm_version = "20.1.8", -) -use_repo(llvm, "llvm_toolchain") +# The C++ toolchain is a dev_dependency in OpenROAD, so the root module +# must configure its own. hermetic-llvm provides a statically linked, +# zero-sysroot LLVM toolchain. +bazel_dep(name = "llvm", version = "0.8.11") register_toolchains( - "@llvm_toolchain//:all", + "@llvm//toolchain:all", ) python = use_extension("@rules_python//python/extensions:python.bzl", "python") diff --git a/tools/lint/BUILD.bazel b/tools/lint/BUILD.bazel index fe4f479de21..25d6efe8799 100644 --- a/tools/lint/BUILD.bazel +++ b/tools/lint/BUILD.bazel @@ -7,6 +7,6 @@ package(default_visibility = ["//visibility:public"]) native_binary( name = "clang_tidy", - src = "@llvm_toolchain_llvm//:bin/clang-tidy", + src = "@llvm//tools:clang-tidy", out = "clang_tidy", ) From 6b35dd497a5c6e1c2e7bb26c7c5b643b3f340f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 15:00:56 +0200 Subject: [PATCH 02/33] bazel: add etc/bazel-hermetic pruned-environment launcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Launches bazel with an allowlisted PATH (no host compilers, linkers or interpreters), BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1, and only the workspace bazelrc, so builds fail instead of silently reaching for host tools the build should provide. Proxy and cache environment variables pass through; the bazelisk-managed bazel binary is resolved portably. First finding: rules_python's legacy py_binary stub needs a host python3 ('#!/usr/bin/env python3') before the hermetic interpreter takes over. bootstrap_impl=script uses a shell stub instead. Signed-off-by: Øyvind Harboe --- .bazelrc | 5 +++++ etc/bazel-hermetic | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/.bazelrc b/.bazelrc index 1e8a491897f..a8b5772c9e9 100644 --- a/.bazelrc +++ b/.bazelrc @@ -142,6 +142,11 @@ build:ci --disk_cache= # Tell the 'ci' config to include 'opt' build:ci --config=opt +# Launch py_binary via a shell stub instead of rules_python's legacy python +# stub, whose '#!/usr/bin/env python3' shebang needs a host python3 before +# the hermetic interpreter takes over (found by etc/bazel-hermetic). +common --@rules_python//python/config_settings:bootstrap_impl=script + # Setup remote cache # Anon: HTTPS read-only cache build --remote_cache=https://bazel.precisioninno.com diff --git a/etc/bazel-hermetic b/etc/bazel-hermetic index bf49b380f5c..19af41ef362 100755 --- a/etc/bazel-hermetic +++ b/etc/bazel-hermetic @@ -3,9 +3,10 @@ # build is supposed to provide hermetically cannot be picked up from the # host. This is a ratchet, not a proof: it removes host compilers, # linkers and interpreters from PATH and disables Bazel's local C++ -# toolchain autodetection, but the C++ toolchain still compiles against -# the host glibc sysroot (/usr/include, /usr/lib), and Bazel itself, -# a JVM-independent binary, comes from the host. +# toolchain autodetection. The C++ toolchain (hermetic-llvm) is +# zero-sysroot, so no host headers or libraries are involved; the +# remaining host surface is the bazel binary itself and the allowlisted +# shell tools below. # # Usage: etc/bazel-hermetic [args...] # e.g. etc/bazel-hermetic build //:openroad @@ -46,11 +47,22 @@ BAZEL=$(command -v bazelisk || command -v bazel) || { echo >&2 "bazel-hermetic: no bazel or bazelisk on PATH"; exit 1; } if [[ $(head -c 4 "$(readlink -f "${BAZEL}")") != $'\x7fELF' ]]; then version=$("${BAZEL}" --version | awk '{print $2}') - arch=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/x86_64/') - meta="${HOME}/.cache/bazelisk/downloads/metadata/bazelbuild/bazel-${version}-linux-${arch}" - real="${HOME}/.cache/bazelisk/downloads/sha256/$(cat "${meta}" 2>/dev/null)/bin/bazel" + os=$(uname -s | tr '[:upper:]' '[:lower:]') + arch=$(uname -m | sed 's/aarch64/arm64/') + # Same cache-directory precedence as bazelisk itself. + if [[ -n ${BAZELISK_HOME:-} ]]; then + bazelisk_home=${BAZELISK_HOME} + elif [[ -n ${XDG_CACHE_HOME:-} ]]; then + bazelisk_home=${XDG_CACHE_HOME}/bazelisk + elif [[ ${os} == darwin ]]; then + bazelisk_home=${HOME}/Library/Caches/bazelisk + else + bazelisk_home=${HOME}/.cache/bazelisk + fi + meta="${bazelisk_home}/downloads/metadata/bazelbuild/bazel-${version}-${os}-${arch}" + real="${bazelisk_home}/downloads/sha256/$(cat "${meta}" 2>/dev/null)/bin/bazel" [[ -x ${real} ]] || - { echo >&2 "bazel-hermetic: ${BAZEL} is a wrapper and no real bazel-${version} found under ~/.cache/bazelisk"; exit 1; } + { echo >&2 "bazel-hermetic: ${BAZEL} is a wrapper and no real bazel-${version} found under ${bazelisk_home}"; exit 1; } BAZEL=${real} fi ln -s "${BAZEL}" "${TOOLBOX}/bazel" @@ -88,6 +100,16 @@ case "${command}" in ;; esac +# Network and cache configuration is not toolchain leakage: keep proxy +# settings (downloads must work behind corporate proxies) and cache +# location preferences. +preserved_env=() +for var in http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY XDG_CACHE_HOME; do + if [[ -n ${!var:-} ]]; then + preserved_env+=("${var}=${!var}") + fi +done + # --nosystem_rc/--nohome_rc: only the workspace .bazelrc applies, so host- # specific configuration (e.g. remote caches) cannot mask missing hermeticity. exec env -i \ @@ -97,4 +119,5 @@ exec env -i \ TERM="${TERM:-dumb}" \ LANG=C.UTF-8 \ PATH="${TOOLBOX}" \ + "${preserved_env[@]}" \ "${TOOLBOX}/bazel" --nosystem_rc --nohome_rc "${startup_flags[@]}" "${command}" "${extra_flags[@]}" "$@" From e7fa947f4828adaed5e07a6e45838b01a5a59af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 15:11:55 +0200 Subject: [PATCH 03/33] bazel-hermetic: address review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - type -P instead of command -v: builtins (pwd, test, true, printf) returned bare names and produced self-referential toolbox symlinks. - Detect wrappers by bazelisk name or shebang instead of requiring ELF, so native macOS (Mach-O) bazel binaries are symlinked directly. - Preserve SSH_AUTH_SOCK, SSL_CERT_FILE/DIR and TMPDIR alongside proxy and cache variables. - Drop exec so the EXIT trap runs and the toolbox directory is not leaked on every invocation. Signed-off-by: Øyvind Harboe --- etc/bazel-hermetic | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/etc/bazel-hermetic b/etc/bazel-hermetic index 19af41ef362..7b93dc3ce6f 100755 --- a/etc/bazel-hermetic +++ b/etc/bazel-hermetic @@ -34,8 +34,10 @@ ALLOWED_TOOLS=( TOOLBOX=$(mktemp -d "${TMPDIR:-/tmp}/bazel-hermetic.XXXXXX") trap 'rm -rf "${TOOLBOX}"' EXIT +# type -P forces a PATH search: command -v would return bare builtin names +# (pwd, test, true, ...) and create self-referential symlinks. for tool in "${ALLOWED_TOOLS[@]}"; do - if path=$(command -v -- "${tool}" 2>/dev/null); then + if path=$(type -P -- "${tool}" 2>/dev/null); then ln -s "${path}" "${TOOLBOX}/${tool}" 2>/dev/null || true fi done @@ -45,7 +47,10 @@ done # binary it manages (~/.cache/bazelisk) instead of symlinking the wrapper. BAZEL=$(command -v bazelisk || command -v bazel) || { echo >&2 "bazel-hermetic: no bazel or bazelisk on PATH"; exit 1; } -if [[ $(head -c 4 "$(readlink -f "${BAZEL}")") != $'\x7fELF' ]]; then +# Wrapper detection: the bazelisk launcher by name, or any script shebang +# (npm/shell wrappers). Native binaries (ELF on Linux, Mach-O on macOS) +# are symlinked directly. +if [[ ${BAZEL} == *bazelisk* || $(head -c 2 "$(readlink -f "${BAZEL}")") == '#!' ]]; then version=$("${BAZEL}" --version | awk '{print $2}') os=$(uname -s | tr '[:upper:]' '[:lower:]') arch=$(uname -m | sed 's/aarch64/arm64/') @@ -100,19 +105,22 @@ case "${command}" in ;; esac -# Network and cache configuration is not toolchain leakage: keep proxy -# settings (downloads must work behind corporate proxies) and cache -# location preferences. +# Network, auth, TLS and cache/tmp configuration is not toolchain leakage: +# downloads must work behind corporate proxies, custom CAs, and git-over-ssh +# overrides, and users may relocate caches and tmp. preserved_env=() -for var in http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY XDG_CACHE_HOME; do +for var in http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY \ + SSH_AUTH_SOCK SSL_CERT_FILE SSL_CERT_DIR XDG_CACHE_HOME TMPDIR; do if [[ -n ${!var:-} ]]; then preserved_env+=("${var}=${!var}") fi done +# No exec: the shell must survive to run the EXIT trap that removes the +# toolbox directory. set -e propagates bazel's exit status. # --nosystem_rc/--nohome_rc: only the workspace .bazelrc applies, so host- # specific configuration (e.g. remote caches) cannot mask missing hermeticity. -exec env -i \ +env -i \ HOME="${HOME}" \ USER="${USER:-$(id -un)}" \ LOGNAME="${LOGNAME:-$(id -un)}" \ From 544c3c0939d893751810b4ac5ad15d5b6b3ce2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 15:27:58 +0200 Subject: [PATCH 04/33] bazel: raise macOS deployment floor to 10.15 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit std::filesystem (src/tcl_readline_setup.cc) requires macOS 10.15. The previous 10.13 floor was never enforced: toolchains_llvm ignored --macos_minimum_os and compiled against the host SDK default. hermetic-llvm honors it, failing the Mac build with "'path' is unavailable: introduced in macOS 10.15". Signed-off-by: Øyvind Harboe --- .bazelrc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.bazelrc b/.bazelrc index a8b5772c9e9..fe33888f3b1 100644 --- a/.bazelrc +++ b/.bazelrc @@ -96,9 +96,12 @@ common --enable_platform_specific_config # macOS: boost.stacktrace needs this since backtrace() doesn't require GNU source build:macos --copt=-DBOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED build:macos --host_copt=-DBOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED -# Aligned allocation/deallocation for abseil is only available since 10.13 -build:macos --macos_minimum_os=10.13 -build:macos --host_macos_minimum_os=10.13 +# std::filesystem (used e.g. in src/tcl_readline_setup.cc) is available +# from macOS 10.15; abseil's aligned allocation needs 10.13. The previous +# 10.13 floor was never enforced: toolchains_llvm ignored it and compiled +# against the host SDK's default. hermetic-llvm honors it. +build:macos --macos_minimum_os=10.15 +build:macos --host_macos_minimum_os=10.15 # Settings for --config=asan address sanitizer build build:asan --strip=never From f9052d0d7b4710cc3cbbfd6a0f04f5f1eb21a93c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 15:27:58 +0200 Subject: [PATCH 05/33] bazel: buildifier formatting; export RUNFILES_DIR in lint scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With bootstrap_impl=script, a py_binary (tclint) invoked via realpath cannot locate its runfiles after the lint scripts cd to the workspace. Export the runfiles root so nested tools resolve it from the environment. No-op under 'bazel test', where the runner sets it. Signed-off-by: Øyvind Harboe --- MODULE.bazel | 1 + bazel/bzl_lint_test.sh | 6 ++++++ bazel/tcl_lint_test.sh | 6 ++++++ 3 files changed, 13 insertions(+) diff --git a/MODULE.bazel b/MODULE.bazel index 69e6eca7cc9..dbcb8ab7a80 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -95,6 +95,7 @@ single_version_override( "//bazel/tcl-patches:0001-tclZipfs-include-vendored-minizip-crypt.h-by-path.patch", ], ) + bazel_dep(name = "tcmalloc", version = "0.0.0-20250927-12f2552") bazel_dep(name = "yaml-cpp", version = "0.9.0") bazel_dep(name = "zlib", version = "1.3.1.bcr.8") diff --git a/bazel/bzl_lint_test.sh b/bazel/bzl_lint_test.sh index 72d20002e08..6b750695003 100755 --- a/bazel/bzl_lint_test.sh +++ b/bazel/bzl_lint_test.sh @@ -9,6 +9,12 @@ set -euo pipefail TOOL="$(realpath "$1")" GIT="$(realpath "$2")" +# With rules_python's script bootstrap (bootstrap_impl=script), a py_binary +# invoked via its realpath cannot locate its runfiles once we cd away. +# Export the runfiles root we start in (.runfiles/_main) so nested +# tools resolve it from the environment instead. +export RUNFILES_DIR="${RUNFILES_DIR:-${PWD%/_main}}" + # MODULE.bazel must be in the sh_test `data` deps so it appears as a # runfiles symlink pointing at the real workspace. `readlink` (no -f, # for macOS portability) resolves the absolute path Bazel wrote. diff --git a/bazel/tcl_lint_test.sh b/bazel/tcl_lint_test.sh index 5669edda6ee..4ca0f91dc0b 100755 --- a/bazel/tcl_lint_test.sh +++ b/bazel/tcl_lint_test.sh @@ -9,6 +9,12 @@ set -euo pipefail TOOL="$(realpath "$1")" GIT="$(realpath "$2")" +# With rules_python's script bootstrap (bootstrap_impl=script), a py_binary +# invoked via its realpath cannot locate its runfiles once we cd away. +# Export the runfiles root we start in (.runfiles/_main) so nested +# tools resolve it from the environment instead. +export RUNFILES_DIR="${RUNFILES_DIR:-${PWD%/_main}}" + WORKSPACE="$(dirname "$(readlink -f tclint.toml)")" cd "$WORKSPACE" From 4325f7d563e152aa0698279cebba18e3d635e739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 15:47:44 +0200 Subject: [PATCH 06/33] bazel: add macOS SDK frameworks Qt needs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hermetic-llvm ships a deliberately minimal macOS SDK subset; frameworks beyond its six defaults are opt-in via the osx.frameworks extension tag. Qt's bootstrap failed on the first missing one (ApplicationServices/ApplicationServices.h). List the desktop frameworks qtbase links against. Signed-off-by: Øyvind Harboe --- MODULE.bazel | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/MODULE.bazel b/MODULE.bazel index dbcb8ab7a80..43efb008720 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -186,6 +186,39 @@ register_toolchains( dev_dependency = True, ) +# hermetic-llvm ships a minimal macOS SDK subset (frameworks are opt-in). +# Qt (qt-bazel) needs the desktop frameworks; the first six entries are +# hermetic-llvm's defaults, which listing any framework replaces. +osx = use_extension("@llvm//extensions:osx.bzl", "osx", dev_dependency = True) +osx.frameworks(names = [ + "CoreFoundation", + "Foundation", + "Kernel", + "OSLog", + "Security", + "SystemConfiguration", + "AppKit", + "ApplicationServices", + "CFNetwork", + "Carbon", + "Cocoa", + "ColorSync", + "CoreData", + "CoreGraphics", + "CoreServices", + "CoreText", + "CoreVideo", + "DiskArbitration", + "GSS", + "IOKit", + "IOSurface", + "ImageIO", + "Metal", + "OpenGL", + "QuartzCore", + "UniformTypeIdentifiers", +]) + python = use_extension("@rules_python//python/extensions:python.bzl", "python") python.toolchain( ignore_root_user_error = True, From 1157d4d94f1380286f2b2bf605e0b39f87a36db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 15:57:32 +0200 Subject: [PATCH 07/33] bazel: include FontServices private framework in macOS SDK subset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ATS (inside the ApplicationServices umbrella) ships .tbd symlinks into the FontServices private framework. Without it in the subset they dangle, and bazel rejects the whole sysroot as an action input ('The file type ... is not supported'). Zero dangling symlinks remain in the extracted SDK after this. Signed-off-by: Øyvind Harboe --- MODULE.bazel | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MODULE.bazel b/MODULE.bazel index 43efb008720..1f875926bcf 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -209,6 +209,10 @@ osx.frameworks(names = [ "CoreText", "CoreVideo", "DiskArbitration", + # ATS (inside the ApplicationServices umbrella) has .tbd symlinks into + # the FontServices private framework; without it they dangle and bazel + # rejects the sysroot ("file type is not supported"). + "FontServices", "GSS", "IOKit", "IOSurface", From ae46bf256f1727e7bf91124a4ebdfad9c3a58828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 16:19:36 +0200 Subject: [PATCH 08/33] bazel: name PrintCore so the macOS SDK subset keeps usr/include/cups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hermetic-llvm's SDK extraction excludes usr/include/cups unless PrintCore is listed explicitly; naming only its ApplicationServices umbrella left PDEPluginInterface.h's '#import ' dangling when Qt compiles Objective-C++. Signed-off-by: Øyvind Harboe --- MODULE.bazel | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MODULE.bazel b/MODULE.bazel index 1f875926bcf..ec572a55873 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -219,6 +219,10 @@ osx.frameworks(names = [ "ImageIO", "Metal", "OpenGL", + # PrintCore is inside the ApplicationServices umbrella, but must be + # named for the SDK subset to keep usr/include/cups (its ObjC headers + # import ). + "PrintCore", "QuartzCore", "UniformTypeIdentifiers", ]) From 36f55f90fb7a45d54a8693e189ae33a116327fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 16:37:27 +0200 Subject: [PATCH 09/33] bazel: add CoreImage to the macOS SDK subset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AppKit's NSColor.h includes CoreImage/CIColor.h unconditionally. Signed-off-by: Øyvind Harboe --- MODULE.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/MODULE.bazel b/MODULE.bazel index ec572a55873..8684b4fe75b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -205,6 +205,7 @@ osx.frameworks(names = [ "ColorSync", "CoreData", "CoreGraphics", + "CoreImage", "CoreServices", "CoreText", "CoreVideo", From 370e2554c26f54df1888ca9decebbe2275525d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 16:42:09 +0200 Subject: [PATCH 10/33] bazel: move hermetic launcher to tools/bazel; wrapper refinements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suggestions from review: - tools/bazel is bazelisk's wrapper convention, so the pruned environment is applied automatically to every bazelisk invocation (Go bazelisk; the npm package does not implement the convention). $BAZEL_REAL is used when bazelisk provides it; direct invocation and BAZELISK_SKIP_WRAPPER=1 remain available. - BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 moves to .bazelrc so it applies to unwrapped invocations too. - --host_action_env=PATH complements --action_env for exec-config actions. DISPLAY/WAYLAND_DISPLAY/XAUTHORITY pass through for 'bazel run' of GUI targets. Signed-off-by: Øyvind Harboe --- .bazelrc | 5 +++- etc/bazel-hermetic => tools/bazel | 44 +++++++++++++++++++------------ 2 files changed, 31 insertions(+), 18 deletions(-) rename etc/bazel-hermetic => tools/bazel (74%) diff --git a/.bazelrc b/.bazelrc index fe33888f3b1..7aa2cc02073 100644 --- a/.bazelrc +++ b/.bazelrc @@ -126,6 +126,9 @@ build:profile --copt -fno-omit-frame-pointer --host_copt -fno-omit-frame-pointer # Improve hermeticity by disallowing user envars and network access build --incompatible_strict_action_env build --nosandbox_default_allow_network +# Refuse to autodetect a local C++ toolchain (/usr/bin/gcc); the hermetic +# toolchain must win resolution, wrapped by tools/bazel or not. +common --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 test --build_tests_only @@ -147,7 +150,7 @@ build:ci --config=opt # Launch py_binary via a shell stub instead of rules_python's legacy python # stub, whose '#!/usr/bin/env python3' shebang needs a host python3 before -# the hermetic interpreter takes over (found by etc/bazel-hermetic). +# the hermetic interpreter takes over (found by tools/bazel). common --@rules_python//python/config_settings:bootstrap_impl=script # Setup remote cache diff --git a/etc/bazel-hermetic b/tools/bazel similarity index 74% rename from etc/bazel-hermetic rename to tools/bazel index 7b93dc3ce6f..c14b103df32 100755 --- a/etc/bazel-hermetic +++ b/tools/bazel @@ -2,14 +2,18 @@ # Launch bazel in a pruned environment so that toolchains and tools the # build is supposed to provide hermetically cannot be picked up from the # host. This is a ratchet, not a proof: it removes host compilers, -# linkers and interpreters from PATH and disables Bazel's local C++ -# toolchain autodetection. The C++ toolchain (hermetic-llvm) is -# zero-sysroot, so no host headers or libraries are involved; the +# linkers and interpreters from PATH. The C++ toolchain (hermetic-llvm) +# is zero-sysroot, so no host headers or libraries are involved; the # remaining host surface is the bazel binary itself and the allowlisted # shell tools below. # -# Usage: etc/bazel-hermetic [args...] -# e.g. etc/bazel-hermetic build //:openroad +# bazelisk invokes this wrapper automatically for every command run in +# this workspace (a bazelisk convention for tools/bazel; it provides the +# real binary via $BAZEL_REAL). The Go bazelisk implements the +# convention; the npm @bazel/bazelisk package does not, so there use +# direct invocation: +# tools/bazel [startup flags] [args...] +# To bypass the wrapper: BAZELISK_SKIP_WRAPPER=1 bazelisk ... # # The allowlist below is the contract for what the build may use from the # host. Grow it only with a comment stating which repository rule or @@ -42,11 +46,16 @@ for tool in "${ALLOWED_TOOLS[@]}"; do fi done -# The bazel binary itself. bazelisk is often a node or shell wrapper whose -# interpreter must not leak into the pruned PATH, so resolve the real ELF -# binary it manages (~/.cache/bazelisk) instead of symlinking the wrapper. -BAZEL=$(command -v bazelisk || command -v bazel) || - { echo >&2 "bazel-hermetic: no bazel or bazelisk on PATH"; exit 1; } +# The bazel binary itself. When bazelisk invokes this wrapper it hands us +# the real binary in $BAZEL_REAL. Otherwise resolve it: bazelisk is often +# a node or shell wrapper whose interpreter must not leak into the pruned +# PATH, so find the real binary it manages (~/.cache/bazelisk) instead of +# symlinking the wrapper. +BAZEL=${BAZEL_REAL:-} +if [[ -z ${BAZEL} ]]; then + BAZEL=$(command -v bazelisk || command -v bazel) || + { echo >&2 "tools/bazel: no bazel or bazelisk on PATH"; exit 1; } +fi # Wrapper detection: the bazelisk launcher by name, or any script shebang # (npm/shell wrappers). Native binaries (ELF on Linux, Mach-O on macOS) # are symlinked directly. @@ -73,7 +82,7 @@ fi ln -s "${BAZEL}" "${TOOLBOX}/bazel" if [[ $# -eq 0 ]]; then - echo >&2 "usage: etc/bazel-hermetic [startup flags] [args...]" + echo >&2 "usage: tools/bazel [startup flags] [args...]" exit 1 fi @@ -84,7 +93,7 @@ while [[ $# -gt 0 && $1 == --* ]]; do shift done if [[ $# -eq 0 ]]; then - echo >&2 "usage: etc/bazel-hermetic [startup flags] [args...]" + echo >&2 "usage: tools/bazel [startup flags] [args...]" exit 1 fi command=$1 @@ -95,12 +104,12 @@ extra_flags=() case "${command}" in build|test|run|cquery|aquery|coverage|fetch|vendor|sync|mod) extra_flags=( - # Fail local C++ toolchain autodetection instead of silently using - # /usr/bin/gcc; the hermetic toolchain must win resolution anyway. - "--repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1" - # Repository rules and actions see only the allowlisted tools. + # Repository rules and actions (target and exec configuration) see + # only the allowlisted tools. BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN + # lives in .bazelrc so it applies to unwrapped invocations too. "--repo_env=PATH=${TOOLBOX}" "--action_env=PATH=${TOOLBOX}" + "--host_action_env=PATH=${TOOLBOX}" ) ;; esac @@ -110,7 +119,8 @@ esac # overrides, and users may relocate caches and tmp. preserved_env=() for var in http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY \ - SSH_AUTH_SOCK SSL_CERT_FILE SSL_CERT_DIR XDG_CACHE_HOME TMPDIR; do + SSH_AUTH_SOCK SSL_CERT_FILE SSL_CERT_DIR XDG_CACHE_HOME TMPDIR \ + DISPLAY WAYLAND_DISPLAY XAUTHORITY; do if [[ -n ${!var:-} ]]; then preserved_env+=("${var}=${!var}") fi From 993b9f1717369f9bc87e4e86a06bb5df26a2e432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 17:10:58 +0200 Subject: [PATCH 11/33] bazel: add Symbols, CloudKit, UIFoundation to the macOS SDK subset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AppKit reaches all three on macOS: NSImageView.h includes Symbols/NSSymbolEffect.h unconditionally, the NSText headers take the non-UIKit __has_include branch into the UIFoundation private framework, and NSSharingService pulls CloudKit. Signed-off-by: Øyvind Harboe --- MODULE.bazel | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MODULE.bazel b/MODULE.bazel index 8684b4fe75b..66fefdbed5d 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -225,6 +225,12 @@ osx.frameworks(names = [ # import ). "PrintCore", "QuartzCore", + # AppKit reaches these on macOS: NSImageView.h includes Symbols + # unconditionally; NSText*.h take the non-UIKit branch into the + # UIFoundation private framework; NSSharingService pulls CloudKit. + "CloudKit", + "Symbols", + "UIFoundation", "UniformTypeIdentifiers", ]) From 3fe0376d87e02fec1b6178355a44459ed89b8ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 18:00:20 +0200 Subject: [PATCH 12/33] tools/bazel: scope pruning to repository rules; fix wrapper reentry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Jenkins surfaced three problems with the first wrapper cut: - --action_env=PATH broke test actions: the harness uses tee/file and standalone_python tests invoke host python3 (bazel's own test-setup.sh also uses file). Actions now keep bazel's strict default PATH; per-action pruning returns when the harness stops using host tools (see the PR cleanup list). - An --action_env PATH naming a per-run mktemp directory would also fragment action cache keys across runs and machines. - The npm bazelisk implements the tools/bazel wrapper convention too, so the wrapper's own 'bazelisk --version' probe re-entered the wrapper; probe with BAZELISK_SKIP_WRAPPER=1 and pass flags-only invocations (e.g. --version) through verbatim. Signed-off-by: Øyvind Harboe --- tools/bazel | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/tools/bazel b/tools/bazel index c14b103df32..34583e1ec71 100755 --- a/tools/bazel +++ b/tools/bazel @@ -9,9 +9,7 @@ # # bazelisk invokes this wrapper automatically for every command run in # this workspace (a bazelisk convention for tools/bazel; it provides the -# real binary via $BAZEL_REAL). The Go bazelisk implements the -# convention; the npm @bazel/bazelisk package does not, so there use -# direct invocation: +# real binary via $BAZEL_REAL). Direct invocation also works: # tools/bazel [startup flags] [args...] # To bypass the wrapper: BAZELISK_SKIP_WRAPPER=1 bazelisk ... # @@ -30,6 +28,7 @@ ALLOWED_TOOLS=( awk # module extension configure steps diff cmp patch # single_version_override / archive_override patches tar gzip bzip2 xz zstd unzip # archive extraction fallbacks + tee file xargs # base utilities used by module extension scripts uname id hostname date which getconf nproc git # tools/workspace_status.sh embeds version info gh # optional; used by some developer workflows @@ -60,7 +59,7 @@ fi # (npm/shell wrappers). Native binaries (ELF on Linux, Mach-O on macOS) # are symlinked directly. if [[ ${BAZEL} == *bazelisk* || $(head -c 2 "$(readlink -f "${BAZEL}")") == '#!' ]]; then - version=$("${BAZEL}" --version | awk '{print $2}') + version=$(BAZELISK_SKIP_WRAPPER=1 "${BAZEL}" --version | awk '{print $2}') os=$(uname -s | tr '[:upper:]' '[:lower:]') arch=$(uname -m | sed 's/aarch64/arm64/') # Same cache-directory precedence as bazelisk itself. @@ -81,35 +80,29 @@ if [[ ${BAZEL} == *bazelisk* || $(head -c 2 "$(readlink -f "${BAZEL}")") == '#!' fi ln -s "${BAZEL}" "${TOOLBOX}/bazel" -if [[ $# -eq 0 ]]; then - echo >&2 "usage: tools/bazel [startup flags] [args...]" - exit 1 -fi - # Leading --flags are bazel startup options (e.g. --output_base=...). +# A flags-only invocation (e.g. `bazel --version`) is valid: no command. startup_flags=() while [[ $# -gt 0 && $1 == --* ]]; do startup_flags+=("$1") shift done -if [[ $# -eq 0 ]]; then - echo >&2 "usage: tools/bazel [startup flags] [args...]" - exit 1 -fi -command=$1 -shift +command=${1:-} +[[ $# -gt 0 ]] && shift # Flags only exist on build-ish commands; startup flags go before the command. extra_flags=() case "${command}" in build|test|run|cquery|aquery|coverage|fetch|vendor|sync|mod) extra_flags=( - # Repository rules and actions (target and exec configuration) see - # only the allowlisted tools. BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN - # lives in .bazelrc so it applies to unwrapped invocations too. + # Repository rules see only the allowlisted tools. Actions keep + # bazel's strict default PATH (/bin:/usr/bin): the test harness + # still uses host python3/tee/file (see the PR cleanup list), and + # an --action_env PATH carrying this per-run directory would + # fragment action cache keys across runs and machines. + # BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN lives in .bazelrc so it + # applies to unwrapped invocations too. "--repo_env=PATH=${TOOLBOX}" - "--action_env=PATH=${TOOLBOX}" - "--host_action_env=PATH=${TOOLBOX}" ) ;; esac @@ -130,6 +123,16 @@ done # toolbox directory. set -e propagates bazel's exit status. # --nosystem_rc/--nohome_rc: only the workspace .bazelrc applies, so host- # specific configuration (e.g. remote caches) cannot mask missing hermeticity. +bazel_argv=(--nosystem_rc --nohome_rc "${startup_flags[@]}") +if [[ -n ${command} ]]; then + bazel_argv+=("${command}" "${extra_flags[@]}" "$@") +fi +# Flags-only invocations (e.g. `bazel --version`) go to the binary verbatim; +# rc-file options would be rejected there. +if [[ -z ${command} ]]; then + bazel_argv=("${startup_flags[@]}") +fi + env -i \ HOME="${HOME}" \ USER="${USER:-$(id -un)}" \ @@ -138,4 +141,4 @@ env -i \ LANG=C.UTF-8 \ PATH="${TOOLBOX}" \ "${preserved_env[@]}" \ - "${TOOLBOX}/bazel" --nosystem_rc --nohome_rc "${startup_flags[@]}" "${command}" "${extra_flags[@]}" "$@" + "${TOOLBOX}/bazel" "${bazel_argv[@]}" From cbd092dd98885021413c909785f54a7d419585cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 18:47:45 +0200 Subject: [PATCH 13/33] bazel: carry upstream boost.icl strict-weak-ordering fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit boost::icl's exclusive_less_than comparator violates strict weak ordering (boostorg/icl#51). libstdc++ tolerates it; libc++'s std::map does not: interval_set::operator-= subtracts only the first overlapping interval and leaves phantom coverage (boostorg/icl#55). With the hermetic toolchain's libc++, pad placed IO filler cells on top of pads (seven src/pad tests) and grt's overlapping_edges golden diverged. Carry upstream boostorg/icl@7de3f55655 ('Refactored interval lookup operations', #54, merged after boost 1.90), trimmed to include/, as a single_version_override patch. Verified: an isolated interval_set subtraction repro is wrong with libc++ headers and correct with the patch at -O0 through -O3; all 61 //src/pad tests and //src/grt/test:overlapping_edges pass. Drop the patch when a boost.icl release containing the fix lands in BCR. Signed-off-by: Øyvind Harboe --- MODULE.bazel | 13 + ...or-interval-lookup-operations-icl-54.patch | 582 ++++++++++++++++++ bazel/boost-icl-patches/BUILD.bazel | 1 + 3 files changed, 596 insertions(+) create mode 100644 bazel/boost-icl-patches/0001-refactor-interval-lookup-operations-icl-54.patch create mode 100644 bazel/boost-icl-patches/BUILD.bazel diff --git a/MODULE.bazel b/MODULE.bazel index 66fefdbed5d..5a12431e05b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -36,6 +36,19 @@ bazel_dep(name = "swig", version = "4.3.0.bcr.2") BOOST_VERSION = "1.89.0.bcr.2" +# boost::icl's exclusive_less_than comparator violates strict weak ordering +# (boostorg/icl#51); libc++'s std::map exposes it, leaving phantom intervals +# after interval_set subtraction (boostorg/icl#55) — pad IO fillers were +# placed on top of pads. Upstream fix boostorg/icl@7de3f55655 (#54), merged +# after boost 1.90. Drop when a boost.icl release containing it lands in BCR. +single_version_override( + module_name = "boost.icl", + patch_strip = 1, + patches = [ + "//bazel/boost-icl-patches:0001-refactor-interval-lookup-operations-icl-54.patch", + ], +) + bazel_dep(name = "boost.algorithm", version = BOOST_VERSION) bazel_dep(name = "boost.asio", version = BOOST_VERSION) bazel_dep(name = "boost.beast", version = BOOST_VERSION) diff --git a/bazel/boost-icl-patches/0001-refactor-interval-lookup-operations-icl-54.patch b/bazel/boost-icl-patches/0001-refactor-interval-lookup-operations-icl-54.patch new file mode 100644 index 00000000000..0923bba655b --- /dev/null +++ b/bazel/boost-icl-patches/0001-refactor-interval-lookup-operations-icl-54.patch @@ -0,0 +1,582 @@ +boost::icl's exclusive_less_than comparator violates strict weak +ordering (boostorg/icl#51), which libc++'s std::map exposes: +interval_set::operator-= subtracts only the first overlapping interval +and leaves phantom coverage (boostorg/icl#55). This is upstream commit +boostorg/icl@7de3f55655 ("Refactored interval lookup operations", #54), +merged after boost 1.90, trimmed to include/. Drop this patch when a +boost.icl release containing it lands in BCR. + +diff --git a/include/boost/icl/concept/element_associator.hpp b/include/boost/icl/concept/element_associator.hpp +index 98280ece..7c4cbfd3 100644 +--- a/include/boost/icl/concept/element_associator.hpp ++++ b/include/boost/icl/concept/element_associator.hpp +@@ -143,7 +143,7 @@ is_element_equal(const Type& left, const Type& right) + { return left == right; } + + +-/* Strict weak less ordering which is given by the Compare order */ ++/* Strict partial less ordering which is given by the Compare order */ + template + inline typename enable_if, bool>::type + operator < (const Type& left, const Type& right) +diff --git a/include/boost/icl/concept/interval.hpp b/include/boost/icl/concept/interval.hpp +index fab7dd99..3a1e69f9 100644 +--- a/include/boost/icl/concept/interval.hpp ++++ b/include/boost/icl/concept/interval.hpp +@@ -565,8 +565,10 @@ template + typename boost::enable_if, bool>::type + is_empty(const Type& object) + { +- return domain_less_equal(upper(object), lower(object) ) +- || domain_less_equal(upper(object), domain_next(lower(object))); ++ // we use ternary operator instead of || to prevent a codegen bug with GCC 10-11 ++ return domain_less_equal(upper(object), lower(object))? ++ true: ++ domain_less_equal(upper(object), domain_next(lower(object))); + } + + template +@@ -576,8 +578,10 @@ is_empty(const Type& object) + if(object.bounds() == interval_bounds::closed()) + return domain_less(upper(object), lower(object)); + else if(object.bounds() == interval_bounds::open()) +- return domain_less_equal(upper(object), lower(object) ) +- || domain_less_equal(upper(object), domain_next(lower(object))); ++ // we use ternary operator instead of || to prevent a codegen bug with GCC 10-11 ++ return domain_less_equal(upper(object), lower(object))? ++ true: ++ domain_less_equal(upper(object), domain_next(lower(object))); + else + return domain_less_equal(upper(object), lower(object)); + } +diff --git a/include/boost/icl/detail/assoc_container_adaptor.hpp b/include/boost/icl/detail/assoc_container_adaptor.hpp +new file mode 100644 +index 00000000..63e4975f +--- /dev/null ++++ b/include/boost/icl/detail/assoc_container_adaptor.hpp +@@ -0,0 +1,187 @@ ++/*-----------------------------------------------------------------------------+ ++Copyright (c) 2026: Joaquin M Lopez Munoz +++------------------------------------------------------------------------------+ ++ Distributed under the Boost Software License, Version 1.0. ++ (See accompanying file LICENCE.txt or copy at ++ http://www.boost.org/LICENSE_1_0.txt) +++-----------------------------------------------------------------------------*/ ++#ifndef BOOST_ICL_DETAIL_ASSOC_CONTAINER_ADAPTOR_HPP_JMLM_260320 ++#define BOOST_ICL_DETAIL_ASSOC_CONTAINER_ADAPTOR_HPP_JMLM_260320 ++ ++#include ++#include ++#include ++#include ++ ++namespace boost{namespace icl{namespace detail ++{ ++ ++/*-----------------------------------------------------------------------------+ ++| Interval comparison is generally a partial order rather than a strict weak | ++| order (SWO). This does not pose any problem with associative containers as | ++| long as the intervals in the container are disjoint, since the induced order | ++| restricted to those is a SWO (indeed, a total order). A difficulty may arise | ++| when doing a lookup operation for an interval k that overlaps with | ++| E = elements(container), as the induced order over {k} U E may not be a SWO. | ++| All stdlib implementations support this case except libc++ v22 or higher: | ++| https://github.com/llvm/llvm-project/issues/183189 | ++| https://github.com/boostorg/icl/issues/51 . | ++| Whether libc++'s behavior is conformant or not is contested, see | ++| https://github.com/llvm/llvm-project/issues/187667 , | ++| but, regardless, we can solve the problem by resorting to heterogeneous | ++| lookup. When k is of a type other than key_type and the compare predicate is | ++| transparent, lookup operations on k are defined by the standard in terms of | ++| elements being _partitioned_ by k, without any reference to SWO compliance. | ++| | ++| assoc_container_adaptor, in combination with transparent_compare, forces | ++| lookup operations on the adapted container to be routed through its | ++| heterogeneous lookup overloads, and does nothing for C++11 containers | ++| without het lookup. This circumvents libc++'s singular behavior except when | ++| in C++11 mode: in this case, we use Boost.Container, which supports het | ++| lookup even in C++11 (see impl_config.hpp). Insert functions are also | ++| provided that prevent UB when the element would violate SWO (this is UB | ++| regardless of the resolution of libc++'s issue). | ++| | ++| Additionally, Boost.ICL interval find functions are documented to return the | ++| _first_ eligible element, which is not guaranteed by std::(set|map)::find; | ++| assoc_container_adaptor fixes that. +++-----------------------------------------------------------------------------*/ ++ ++template ++struct transparent_compare: Compare ++{ ++ using is_transparent = void; ++ using super = Compare; ++ ++ using super::super; ++}; ++ ++template ++using assoc_container_is_set = std::is_same< ++ typename AssocContainer::key_type, typename AssocContainer::value_type>; ++ ++template ++using assoc_container_enable_if_is_input_iterator_t = ++ typename std::enable_if< ++ std::is_convertible< ++ typename std::iterator_traits::iterator_category, ++ std::input_iterator_tag ++ >::value ++ >::type; ++ ++template ++struct assoc_container_adaptor: AssocContainer ++{ ++ using key_type = typename AssocContainer::key_type; ++ using value_type = typename AssocContainer::value_type; ++ using size_type = typename AssocContainer::size_type; ++ using key_compare = typename AssocContainer::key_compare::super; ++ using iterator = typename AssocContainer::iterator; ++ using const_iterator = typename AssocContainer::const_iterator; ++ ++ using AssocContainer::AssocContainer; ++ ++ template< ++ typename InputIterator, ++ typename = assoc_container_enable_if_is_input_iterator_t ++ > ++ void insert(InputIterator first, InputIterator last) ++ { ++ while(first != last) insert(*first++); ++ } ++ ++ std::pair insert(const value_type& x) ++ { ++ auto it = lower_bound(key_from_value(x)); ++ if(it == AssocContainer::end() || AssocContainer::value_comp()(x, *it)) { ++ return {AssocContainer::insert(it, x), true}; ++ } ++ else { ++ return {it, false}; ++ } ++ } ++ ++ iterator insert(const_iterator pos, const value_type& x) ++ { ++ if((pos == AssocContainer::end() || AssocContainer::value_comp()(x, *pos)) && ++ (pos == AssocContainer::begin() || AssocContainer::value_comp()(*std::prev(pos), x))){ ++ return AssocContainer::insert(pos, x); ++ } ++ return insert(x).first; ++ } ++ ++ size_type count(const key_type& key) ++ { ++ return AssocContainer::count(std::cref(key)); ++ } ++ ++ size_type count(const key_type& key) const ++ { ++ return AssocContainer::count(std::cref(key)); ++ } ++ ++ iterator find(const key_type& key) ++ { ++ auto it = AssocContainer::lower_bound(std::cref(key)); ++ return ++ it == AssocContainer::end() || ++ AssocContainer::key_comp()(key, key_from_value(*it))? ++ AssocContainer::end(): it; ++ } ++ ++ const_iterator find(const key_type& key) const ++ { ++ return const_cast(this)->find(key); ++ } ++ ++ std::pair equal_range(const key_type& key) ++ { ++ return AssocContainer::equal_range(std::cref(key)); ++ } ++ ++ std::pair equal_range(const key_type& key) const ++ { ++ return AssocContainer::equal_range(std::cref(key)); ++ } ++ ++ iterator lower_bound(const key_type& key) ++ { ++ return AssocContainer::lower_bound(std::cref(key)); ++ } ++ ++ const_iterator lower_bound(const key_type& key) const ++ { ++ return AssocContainer::lower_bound(std::cref(key)); ++ } ++ ++ iterator upper_bound(const key_type& key) ++ { ++ return AssocContainer::upper_bound(std::cref(key)); ++ } ++ ++ const_iterator upper_bound(const key_type& key) const ++ { ++ return AssocContainer::upper_bound(std::cref(key)); ++ } ++ ++private: ++ template< ++ class IsSet = assoc_container_is_set, ++ typename std::enable_if::type* = nullptr> ++ static const key_type& key_from_value(const value_type& x) ++ { ++ return x; ++ } ++ ++ template< ++ class IsSet = assoc_container_is_set, ++ typename std::enable_if::type* = nullptr> ++ static const key_type& key_from_value(const value_type& x) ++ { ++ return x.first; ++ } ++}; ++ ++}}} // namespace detail icl boost ++ ++#endif +diff --git a/include/boost/icl/detail/element_iterator.hpp b/include/boost/icl/detail/element_iterator.hpp +index 6c06f494..d1f3c9e9 100644 +--- a/include/boost/icl/detail/element_iterator.hpp ++++ b/include/boost/icl/detail/element_iterator.hpp +@@ -8,11 +8,22 @@ Copyright (c) 2009-2009: Joachim Faulhaber + #ifndef BOOST_ICL_DETAIL_ELEMENT_ITERATOR_HPP_JOFA_091104 + #define BOOST_ICL_DETAIL_ELEMENT_ITERATOR_HPP_JOFA_091104 + ++#include + #include + #include + #include + #include + ++#if BOOST_CXX_VERSION >= 202002L ++#include ++#endif ++ ++namespace boost{namespace movelib{ ++ ++template class reverse_iterator; ++ ++}} // namespace movelib boost ++ + namespace boost{namespace icl + { + +@@ -62,6 +73,13 @@ struct is_reverse > + BOOST_STATIC_CONSTANT(bool, value = true); + }; + ++template ++struct is_reverse > ++{ ++ typedef is_reverse > type; ++ BOOST_STATIC_CONSTANT(bool, value = true); ++}; ++ + template + struct is_reverse > + { +@@ -333,6 +351,32 @@ class element_iterator + + }} // namespace icl boost + ++#if BOOST_CXX_VERSION >= 202002L ++// std::iter_value_t falls back to std::indirectly_readable_traits::value_type when ++// std::iterator_traits is not specialized, but std::indirectly_readable_traits is ++// ill-defined if It::value_type and It::element_type _both_ exist and are _not_ the same, which ++// is precisely the case for element_iterator. ++// Workaround is to specialize std::iterator_traits>. We do this ++// by deriving from std::iterator_traits>> so ++// that we don't have to revisit this ever even if more typedefs are added to std::iterator_traits ++// in future versions of the C++ standard. ++ ++namespace boost{namespace icl{namespace detail{ ++ ++template struct derived_wrapper: Class {}; ++ ++}}} // namespace detail icl boost ++ ++namespace std{ ++ ++template ++struct iterator_traits< ::boost::icl::element_iterator> : ++ iterator_traits< ::boost::icl::detail::derived_wrapper< ::boost::icl::element_iterator>> ++{}; ++ ++} // namespace std ++#endif ++ + #endif // BOOST_ICL_DETAIL_ELEMENT_ITERATOR_HPP_JOFA_091104 + + +diff --git a/include/boost/icl/detail/exclusive_less_than.hpp b/include/boost/icl/detail/exclusive_less_than.hpp +index 3a4aa650..fbdaa5f9 100644 +--- a/include/boost/icl/detail/exclusive_less_than.hpp ++++ b/include/boost/icl/detail/exclusive_less_than.hpp +@@ -17,7 +17,7 @@ namespace boost{ namespace icl + template + struct exclusive_less_than + { +- /** Operator operator() implements a strict weak ordering on intervals. */ ++ /** Operator operator() implements a strict partial ordering on intervals. */ + bool operator()(const IntervalT& left, const IntervalT& right)const + { + return icl::non_empty::exclusive_less(left, right); +diff --git a/include/boost/icl/detail/map_adaptor.hpp b/include/boost/icl/detail/map_adaptor.hpp +new file mode 100644 +index 00000000..790490ed +--- /dev/null ++++ b/include/boost/icl/detail/map_adaptor.hpp +@@ -0,0 +1,29 @@ ++/*-----------------------------------------------------------------------------+ ++Copyright (c) 2026: Joaquin M Lopez Munoz +++------------------------------------------------------------------------------+ ++ Distributed under the Boost Software License, Version 1.0. ++ (See accompanying file LICENCE.txt or copy at ++ http://www.boost.org/LICENSE_1_0.txt) +++-----------------------------------------------------------------------------*/ ++#ifndef BOOST_ICL_DETAIL_MAP_ADAPTOR_HPP_JMLM_260321 ++#define BOOST_ICL_DETAIL_MAP_ADAPTOR_HPP_JMLM_260321 ++ ++#include ++ ++#include ICL_IMPL_PATH(map) ++ ++// let boostdep know about this potential dependency hidden by ICL_IMPL_PATH ++#if 0 ++#include ++#endif ++ ++namespace boost{namespace icl{namespace detail ++{ ++ ++template ++using map_adaptor = assoc_container_adaptor< ++ ICL_IMPL_SPACE::map, Allocator>>; ++ ++}}} // namespace detail icl boost ++ ++#endif +diff --git a/include/boost/icl/detail/mapped_reference.hpp b/include/boost/icl/detail/mapped_reference.hpp +index 67f7d041..212059cb 100644 +--- a/include/boost/icl/detail/mapped_reference.hpp ++++ b/include/boost/icl/detail/mapped_reference.hpp +@@ -134,7 +134,7 @@ inline typename enable_if, bool>::type + operator == ( const StdPairT& left, + const mapped_reference& right) + { +- return right == left; ++ return right.operator ==(left); + } + + template +@@ -142,7 +142,7 @@ inline typename enable_if, bool>::type + operator != ( const StdPairT& left, + const mapped_reference& right) + { +- return !(right == left); ++ return !(right.operator ==(left)); + } + + //------------------------------------------------------------------------------ +diff --git a/include/boost/icl/detail/set_adaptor.hpp b/include/boost/icl/detail/set_adaptor.hpp +new file mode 100644 +index 00000000..2a44b8a5 +--- /dev/null ++++ b/include/boost/icl/detail/set_adaptor.hpp +@@ -0,0 +1,44 @@ ++/*-----------------------------------------------------------------------------+ ++Copyright (c) 2026: Joaquin M Lopez Munoz +++------------------------------------------------------------------------------+ ++ Distributed under the Boost Software License, Version 1.0. ++ (See accompanying file LICENCE.txt or copy at ++ http://www.boost.org/LICENSE_1_0.txt) +++-----------------------------------------------------------------------------*/ ++#ifndef BOOST_ICL_DETAIL_SET_ADAPTOR_HPP_JMLM_260321 ++#define BOOST_ICL_DETAIL_SET_ADAPTOR_HPP_JMLM_260321 ++ ++#include ++ ++#include ICL_IMPL_PATH(set) ++ ++// let boostdep know about this potential dependency hidden by ICL_IMPL_PATH ++#if 0 ++#include ++#endif ++ ++namespace boost{namespace icl{namespace detail ++{ ++ ++template ++struct set_adaptor: assoc_container_adaptor< ++ ICL_IMPL_SPACE::set, Allocator>> ++{ ++ using super = assoc_container_adaptor< ++ ICL_IMPL_SPACE::set, Allocator>>; ++ ++ using super::super; ++ ++ // Boost.ICL assumes set::iterator is the same type as set::const_iterator, ++ // which is not guaranteed generally and is certainly not the case for ++ // boost::container::set. We extend to reverse_iterator out of sympathy. ++ ++ using const_iterator = typename super::const_iterator; ++ using iterator = const_iterator; ++ using const_reverse_iterator = typename super::const_reverse_iterator; ++ using reverse_iterator = const_reverse_iterator; ++}; ++ ++}}} // namespace detail icl boost ++ ++#endif +diff --git a/include/boost/icl/impl_config.hpp b/include/boost/icl/impl_config.hpp +index 55297396..601d4bcc 100644 +--- a/include/boost/icl/impl_config.hpp ++++ b/include/boost/icl/impl_config.hpp +@@ -32,14 +32,19 @@ Copyright (c) 2009-2011: Joachim Faulhaber + | | + | NOTE: ICL_USE_BOOST_INTERPROCESS_IMPLEMENTATION: This define has been | + | available until boost version 1.48.0 and is no longer supported. | ++| | ++| NOTE: With libc++ v22 or higher in C++11, boost::container is used | ++| regardless of user indications. See assoc_container_adaptor.hpp for | ++| the rationale. | + +-----------------------------------------------------------------------------*/ + +-#if defined(ICL_USE_BOOST_MOVE_IMPLEMENTATION) ++#if defined(ICL_USE_BOOST_MOVE_IMPLEMENTATION) ||\ ++ (BOOST_CXX_VERSION < 201402L && defined(_LIBCPP_VERSION) && _LIBCPP_VERSION > 220000) + # define ICL_IMPL_SPACE boost::container +-#elif defined(ICL_USE_STD_IMPLEMENTATION) +-# define ICL_IMPL_SPACE std ++# define ICL_IMPL_PATH(header) + #else + # define ICL_IMPL_SPACE std ++# define ICL_IMPL_PATH(header)
+ #endif + + /*-----------------------------------------------------------------------------+ +diff --git a/include/boost/icl/interval_base_map.hpp b/include/boost/icl/interval_base_map.hpp +index f464a6a4..89555c7c 100644 +--- a/include/boost/icl/interval_base_map.hpp ++++ b/include/boost/icl/interval_base_map.hpp +@@ -19,6 +19,7 @@ Copyright (c) 1999-2006: Cortex Software GmbH, Kantstrasse 57, Berlin + #include + #include + #include ++#include + + #include + +@@ -145,8 +146,7 @@ class interval_base_map + allocator_type; + + /// Container type for the implementation +- typedef ICL_IMPL_SPACE::map ImplMapT; ++ typedef detail::map_adaptor ImplMapT; + + /// key type of the implementing container + typedef typename ImplMapT::key_type key_type; +diff --git a/include/boost/icl/interval_base_set.hpp b/include/boost/icl/interval_base_set.hpp +index d693bd43..a0b3968d 100644 +--- a/include/boost/icl/interval_base_set.hpp ++++ b/include/boost/icl/interval_base_set.hpp +@@ -11,14 +11,6 @@ Copyright (c) 1999-2006: Cortex Software GmbH, Kantstrasse 57, Berlin + + #include + +-#if defined(ICL_USE_BOOST_MOVE_IMPLEMENTATION) +-# include +-#elif defined(ICL_USE_STD_IMPLEMENTATION) +-# include +-#else // Default for implementing containers +-# include +-#endif +- + #include + #include + #include +@@ -30,6 +22,7 @@ Copyright (c) 1999-2006: Cortex Software GmbH, Kantstrasse 57, Berlin + #include + #include + #include ++#include + + #include + #include +@@ -116,7 +109,7 @@ class interval_base_set + typedef Alloc domain_allocator_type; + + /// Container type for the implementation +- typedef typename ICL_IMPL_SPACE::set ImplSetT; ++ typedef detail::set_adaptor ImplSetT; + + /// key type of the implementing container + typedef typename ImplSetT::key_type key_type; +diff --git a/include/boost/icl/map.hpp b/include/boost/icl/map.hpp +index 3877745d..8d67de1e 100644 +--- a/include/boost/icl/map.hpp ++++ b/include/boost/icl/map.hpp +@@ -10,16 +10,8 @@ Copyright (c) 2007-2011: Joachim Faulhaber + + #include + +-#if defined(ICL_USE_BOOST_MOVE_IMPLEMENTATION) +-# include +-# include +-#elif defined(ICL_USE_STD_IMPLEMENTATION) +-# include +-# include +-#else // Default for implementing containers +-# include +-# include +-#endif ++#include ++#include ICL_IMPL_PATH(set) + + #include + #include +@@ -89,14 +81,14 @@ template + ICL_SECTION Section = ICL_SECTION_INSTANCE(icl::inter_section, CodomainT), + ICL_ALLOC Alloc = std::allocator + > +-class map: private ICL_IMPL_SPACE::map > > + { + public: + typedef Alloc > allocator_type; + + typedef typename icl::map type; +- typedef typename ICL_IMPL_SPACE::map base_type; + + typedef Traits traits; +diff --git a/include/boost/icl/set.hpp b/include/boost/icl/set.hpp +index db44b5e8..aa1a68ed 100644 +--- a/include/boost/icl/set.hpp ++++ b/include/boost/icl/set.hpp +@@ -10,13 +10,7 @@ Copyright (c) 2007-2011: Joachim Faulhaber + + #include + +-#if defined(ICL_USE_BOOST_MOVE_IMPLEMENTATION) +-# include +-#elif defined(ICL_USE_STD_IMPLEMENTATION) +-# include +-#else +-# include +-#endif ++#include ICL_IMPL_PATH(set) + + #include + #include diff --git a/bazel/boost-icl-patches/BUILD.bazel b/bazel/boost-icl-patches/BUILD.bazel new file mode 100644 index 00000000000..d518110449e --- /dev/null +++ b/bazel/boost-icl-patches/BUILD.bazel @@ -0,0 +1 @@ +exports_files(glob(["*.patch"])) From 3173310e2143b0bfee34af852ee44d327e5c4f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 19:53:43 +0200 Subject: [PATCH 14/33] bazel: give libc headers sysroot-like include precedence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hermetic-llvm injects Linux kernel and glibc headers as the first -isystem entries, outranking -isystem/-I directories of libraries that deliberately shadow libc headers. BCR sed (gnulib's stdio.h replacement) fails to compile (SETLOCALE_NULL_MAX, _GL_ATTRIBUTE_* undeclared) — breaking test/orfs/gcd targets on Jenkins — and tcl's vendored minizip crypt.h loses to glibc's crypt.h. Host sysroots provide libc via the default search path, searched after all user includes; carry a patch switching the two cc_args to -idirafter to restore that precedence. Verified: unpatched BCR sed 4.9 builds; //:openroad builds; 327 pad/grt/odb tests pass. Propose upstream after this stabilizes; drop the patch when released. Signed-off-by: Øyvind Harboe --- MODULE.bazel | 13 +++++++++ ...01-libc-headers-idirafter-precedence.patch | 28 +++++++++++++++++++ bazel/hermetic-llvm-patches/BUILD.bazel | 1 + {tools => etc}/bazel | 0 4 files changed, 42 insertions(+) create mode 100644 bazel/hermetic-llvm-patches/0001-libc-headers-idirafter-precedence.patch create mode 100644 bazel/hermetic-llvm-patches/BUILD.bazel rename {tools => etc}/bazel (100%) diff --git a/MODULE.bazel b/MODULE.bazel index 5a12431e05b..e22f069662d 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -134,6 +134,19 @@ git_override( ## without FHS paths (e.g. NixOS). bazel_dep(name = "llvm", version = "0.8.11", dev_dependency = True) +# hermetic-llvm injects kernel/glibc headers as the first -isystem entries, +# outranking libraries that deliberately shadow libc headers (gnulib's +# stdio.h in BCR sed; tcl's vendored minizip crypt.h). Host sysroots are +# searched last; -idirafter restores that precedence. Drop when fixed +# upstream. +single_version_override( + module_name = "llvm", + patch_strip = 1, + patches = [ + "//bazel/hermetic-llvm-patches:0001-libc-headers-idirafter-precedence.patch", + ], +) + # --- Dev dependencies (not propagated to downstream consumers) --- bazel_dep(name = "aspect_rules_lint", version = "2.5.2", dev_dependency = True) diff --git a/bazel/hermetic-llvm-patches/0001-libc-headers-idirafter-precedence.patch b/bazel/hermetic-llvm-patches/0001-libc-headers-idirafter-precedence.patch new file mode 100644 index 00000000000..25f1d2a7f90 --- /dev/null +++ b/bazel/hermetic-llvm-patches/0001-libc-headers-idirafter-precedence.patch @@ -0,0 +1,28 @@ +Linux kernel and glibc headers are injected as the first -isystem +entries, which outranks -isystem/-I directories of libraries that +deliberately shadow libc headers: gnulib's stdio.h replacement in BCR +sed fails to take effect (SETLOCALE_NULL_MAX / _GL_ATTRIBUTE_* errors) +and tcl's vendored minizip crypt.h loses to glibc's crypt.h. Host +sysroots provide libc via the default search path, searched after all +user includes; -idirafter restores that precedence. + +--- a/toolchain/args/linux/BUILD.bazel ++++ b/toolchain/args/linux/BUILD.bazel +@@ -24,7 +24,7 @@ + "@kernel_headers//:kernel_headers_directory", + ], + args = [ +- "-isystem", ++ "-idirafter", + "{kernel_headers_include_search_path}", + ], + data = [ +@@ -46,7 +46,7 @@ + ], + args = [ + # "__GLIBC_MINOR__={d}", version.minor +- "-isystem", ++ "-idirafter", + "{libc_headers_include_search_path}", + ], + data = [ diff --git a/bazel/hermetic-llvm-patches/BUILD.bazel b/bazel/hermetic-llvm-patches/BUILD.bazel new file mode 100644 index 00000000000..d518110449e --- /dev/null +++ b/bazel/hermetic-llvm-patches/BUILD.bazel @@ -0,0 +1 @@ +exports_files(glob(["*.patch"])) diff --git a/tools/bazel b/etc/bazel similarity index 100% rename from tools/bazel rename to etc/bazel From a8f056067e4d7d469be5a9b75011e881b70cc7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 19:53:43 +0200 Subject: [PATCH 15/33] bazel: make the hermetic wrapper opt-in as etc/bazel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback: bazelisk auto-invoking tools/bazel forced the pruned environment on everyone — a developer whose bazel is not bazelisk-managed got a silent exit, and repository rules of dependencies legitimately probe for host tools the wrapper prunes (GNU Make's configure looks for ld in bazel-orfs's gnumake rule, failing the Jenkins orfs targets). etc/bazel is invoked explicitly; the toolchain-autodetection guard stays in .bazelrc for everyone. Signed-off-by: Øyvind Harboe --- .bazelrc | 4 ++-- etc/bazel | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.bazelrc b/.bazelrc index 7aa2cc02073..7e1cca8a13e 100644 --- a/.bazelrc +++ b/.bazelrc @@ -127,7 +127,7 @@ build:profile --copt -fno-omit-frame-pointer --host_copt -fno-omit-frame-pointer build --incompatible_strict_action_env build --nosandbox_default_allow_network # Refuse to autodetect a local C++ toolchain (/usr/bin/gcc); the hermetic -# toolchain must win resolution, wrapped by tools/bazel or not. +# toolchain must win resolution, with or without the etc/bazel wrapper. common --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 test --build_tests_only @@ -150,7 +150,7 @@ build:ci --config=opt # Launch py_binary via a shell stub instead of rules_python's legacy python # stub, whose '#!/usr/bin/env python3' shebang needs a host python3 before -# the hermetic interpreter takes over (found by tools/bazel). +# the hermetic interpreter takes over (found by etc/bazel). common --@rules_python//python/config_settings:bootstrap_impl=script # Setup remote cache diff --git a/etc/bazel b/etc/bazel index 34583e1ec71..abffc7763c3 100755 --- a/etc/bazel +++ b/etc/bazel @@ -7,11 +7,12 @@ # remaining host surface is the bazel binary itself and the allowlisted # shell tools below. # -# bazelisk invokes this wrapper automatically for every command run in -# this workspace (a bazelisk convention for tools/bazel; it provides the -# real binary via $BAZEL_REAL). Direct invocation also works: -# tools/bazel [startup flags] [args...] -# To bypass the wrapper: BAZELISK_SKIP_WRAPPER=1 bazelisk ... +# Opt-in developer tool; invoke directly: +# etc/bazel [startup flags] [args...] +# (Deliberately not named tools/bazel: bazelisk would auto-invoke that +# for every command, and repository rules of some dependencies still +# expect host tools this wrapper prunes, e.g. GNU Make's configure +# probing for ld in bazel-orfs's gnumake rule.) # # The allowlist below is the contract for what the build may use from the # host. Grow it only with a comment stating which repository rule or From 01a5264e265c99b21ee2d56169aa4bf821819318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 20:30:37 +0200 Subject: [PATCH 16/33] bazel: raise macOS deployment floor to 13.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libc++ marks floating-point std::to_chars (used by std::format in OpenSTA) as introduced in macOS 13.3. The SDK's libc++ headers enforce availability annotations; earlier runs compiled against header sets with annotations disabled, so the 10.15 floor only appeared sufficient. Signed-off-by: Øyvind Harboe --- .bazelrc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.bazelrc b/.bazelrc index 7e1cca8a13e..b39b8eba15a 100644 --- a/.bazelrc +++ b/.bazelrc @@ -96,12 +96,13 @@ common --enable_platform_specific_config # macOS: boost.stacktrace needs this since backtrace() doesn't require GNU source build:macos --copt=-DBOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED build:macos --host_copt=-DBOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED -# std::filesystem (used e.g. in src/tcl_readline_setup.cc) is available -# from macOS 10.15; abseil's aligned allocation needs 10.13. The previous -# 10.13 floor was never enforced: toolchains_llvm ignored it and compiled +# Floating-point std::to_chars (behind std::format, used by OpenSTA) +# needs macOS 13.3; std::filesystem (src/tcl_readline_setup.cc) needs +# 10.15; abseil's aligned allocation needs 10.13. The historical 10.13 +# floor was never enforced: toolchains_llvm ignored it and compiled # against the host SDK's default. hermetic-llvm honors it. -build:macos --macos_minimum_os=10.15 -build:macos --host_macos_minimum_os=10.15 +build:macos --macos_minimum_os=13.3 +build:macos --host_macos_minimum_os=13.3 # Settings for --config=asan address sanitizer build build:asan --strip=never From ded89f51fbd8054a61d409e4040de48c8779dd94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 4 Jul 2026 21:37:48 +0200 Subject: [PATCH 17/33] bazel: add MetalKit to the macOS SDK subset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Qt's Cocoa QPA plugin (qnsview.cpp) includes MetalKit/MetalKit.h. Signed-off-by: Øyvind Harboe --- MODULE.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/MODULE.bazel b/MODULE.bazel index e22f069662d..7cd368f9c49 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -245,6 +245,7 @@ osx.frameworks(names = [ "IOSurface", "ImageIO", "Metal", + "MetalKit", "OpenGL", # PrintCore is inside the ApplicationServices umbrella, but must be # named for the SDK subset to keep usr/include/cups (its ObjC headers From fd8ace3abd4c33d8ab5b950b746b2e74314ae0ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sun, 5 Jul 2026 02:06:53 +0200 Subject: [PATCH 18/33] bazel: add ModelIO to the macOS SDK subset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MetalKit's MTKModel.h includes ModelIO/ModelIO.h; the MetalKit+ModelIO header closure resolves entirely within the subset now (verified by scanning their framework imports against the extracted tree). Signed-off-by: Øyvind Harboe --- MODULE.bazel | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MODULE.bazel b/MODULE.bazel index 7cd368f9c49..3934c4355dc 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -245,7 +245,9 @@ osx.frameworks(names = [ "IOSurface", "ImageIO", "Metal", + # MetalKit's MTKModel.h includes ModelIO/ModelIO.h. "MetalKit", + "ModelIO", "OpenGL", # PrintCore is inside the ApplicationServices umbrella, but must be # named for the SDK subset to keep usr/include/cups (its ObjC headers From 0cba112d8af3549b03392435bcab9ea38c85e472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sun, 5 Jul 2026 09:41:25 +0200 Subject: [PATCH 19/33] bazel: fixed toolbox path so --repo_env=PATH keeps the analysis cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wrapper built its pruned-PATH toolbox in a fresh mktemp directory on every invocation and passed it as --repo_env=PATH. That value is part of bazel's analysis cache key, so every run invalidated loading/analysis and re-ran repository rules, recompiling work that was already done. The toolbox is now a fixed per-checkout directory, tmp/bazel-toolbox under the workspace root (gitignored via tmp/): the --repo_env value is byte-identical across runs, per-user and per-checkout with no /tmp cross-user collisions, and not on a RAM-backed tmpfs. Entries are refreshed with ln -sfn (atomic replace) and entries no longer in the allowlist are pruned, so the allowlist remains the contract. The EXIT cleanup trap is gone, which also lets the wrapper exec bazel directly. Verified: second 'etc/bazel build //src/utl' is 1 internal action / 0.8s with no re-analysis, invoked from a subdirectory to confirm the path does not depend on the caller's working directory. Signed-off-by: Øyvind Harboe --- etc/bazel | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/etc/bazel b/etc/bazel index abffc7763c3..025cea8fc2b 100755 --- a/etc/bazel +++ b/etc/bazel @@ -35,14 +35,39 @@ ALLOWED_TOOLS=( gh # optional; used by some developer workflows ) -TOOLBOX=$(mktemp -d "${TMPDIR:-/tmp}/bazel-hermetic.XXXXXX") -trap 'rm -rf "${TOOLBOX}"' EXIT +# A fixed per-checkout toolbox: its path feeds --repo_env=PATH below, +# which is part of bazel's analysis cache key, so it must be +# byte-identical across runs — a random mktemp path forced a full +# re-analysis and repository refetch on every invocation. It lives under +# the workspace tmp/ (gitignored): per-user and per-checkout by +# construction (no /tmp cross-user collisions) and not on a RAM-backed +# tmpfs as /tmp commonly is. +ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +TOOLBOX="${ROOT}/tmp/bazel-toolbox" +mkdir -p "${TOOLBOX}" + +# Entries not in the allowlist (edited allowlist, older wrapper) are +# pruned so the toolbox never offers more than the contract above. +for entry in "${TOOLBOX}"/*; do + [[ -e ${entry} || -L ${entry} ]] || continue + name=$(basename "${entry}") + keep=0 + [[ ${name} == bazel ]] && keep=1 + for tool in "${ALLOWED_TOOLS[@]}"; do + [[ ${name} == "${tool}" ]] && { keep=1; break; } + done + [[ ${keep} -eq 1 ]] || rm -f "${entry}" +done # type -P forces a PATH search: command -v would return bare builtin names -# (pwd, test, true, ...) and create self-referential symlinks. +# (pwd, test, true, ...) and create self-referential symlinks. ln -sfn +# replaces atomically, so a concurrent invocation never sees a missing +# tool; a tool that vanished from the host PATH is dropped. for tool in "${ALLOWED_TOOLS[@]}"; do if path=$(type -P -- "${tool}" 2>/dev/null); then - ln -s "${path}" "${TOOLBOX}/${tool}" 2>/dev/null || true + ln -sfn "${path}" "${TOOLBOX}/${tool}" 2>/dev/null || true + else + rm -f "${TOOLBOX}/${tool}" fi done @@ -79,7 +104,7 @@ if [[ ${BAZEL} == *bazelisk* || $(head -c 2 "$(readlink -f "${BAZEL}")") == '#!' { echo >&2 "bazel-hermetic: ${BAZEL} is a wrapper and no real bazel-${version} found under ${bazelisk_home}"; exit 1; } BAZEL=${real} fi -ln -s "${BAZEL}" "${TOOLBOX}/bazel" +ln -sfn "${BAZEL}" "${TOOLBOX}/bazel" # Leading --flags are bazel startup options (e.g. --output_base=...). # A flags-only invocation (e.g. `bazel --version`) is valid: no command. @@ -99,8 +124,8 @@ case "${command}" in # Repository rules see only the allowlisted tools. Actions keep # bazel's strict default PATH (/bin:/usr/bin): the test harness # still uses host python3/tee/file (see the PR cleanup list), and - # an --action_env PATH carrying this per-run directory would - # fragment action cache keys across runs and machines. + # an --action_env PATH carrying this checkout-specific directory + # would fragment action cache keys across machines. # BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN lives in .bazelrc so it # applies to unwrapped invocations too. "--repo_env=PATH=${TOOLBOX}" @@ -120,8 +145,6 @@ for var in http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY \ fi done -# No exec: the shell must survive to run the EXIT trap that removes the -# toolbox directory. set -e propagates bazel's exit status. # --nosystem_rc/--nohome_rc: only the workspace .bazelrc applies, so host- # specific configuration (e.g. remote caches) cannot mask missing hermeticity. bazel_argv=(--nosystem_rc --nohome_rc "${startup_flags[@]}") @@ -134,7 +157,7 @@ if [[ -z ${command} ]]; then bazel_argv=("${startup_flags[@]}") fi -env -i \ +exec env -i \ HOME="${HOME}" \ USER="${USER:-$(id -un)}" \ LOGNAME="${LOGNAME:-$(id -un)}" \ From cc2b5b07d5b47430c6e4219c0e53d55f25906e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 6 Jul 2026 17:28:37 +0200 Subject: [PATCH 20/33] bazel: drop -idirafter toolchain patch; fix gnulib deps at the source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hermetic-llvm patch demoted the toolchain's libc -isystem entries to -idirafter so that packages whose vendored gnulib headers shadow libc headers (sed, gawk, m4, bison) kept compiling. dzbarsky pointed out the patch cannot be upstreamed (rulesets munge compiler flags) and the real bugs live in the libraries. Fix them there instead: - sed 4.9.bcr.3 -> 4.9.bcr.5 (BCR #7915). Branch-only: bcr.5's rewritten static config breaks under master's toolchains_llvm + host glibc, so this bump cannot land on master first. - gawk 5.3.2.bcr.2 -> 5.3.2.bcr.7 (BCR #7989), also PR #10828. - m4 1.4.21 -> 1.4.21.bcr.4 (BCR #9338 family), also PR #10829. - bison 3.8.2.bcr.5 -> 3.8.2.bcr.7 (BCR #9338), also PR #10830, plus a 3-hunk overlay patch (bazel/bison-patches/) making the gnulib wrapper headers win via -I instead of includes=["lib"]'s -isystem; to be proposed upstream as bison@3.8.2.bcr.8. Verified: @sed, @gawk, @m4, @bison, @flex, @readline, @ncurses, @tcl_lang and full //:openroad build under hermetic-llvm without the toolchain patch; each bumped module's old version fails without it. Co-Authored-By: Claude Fable 5 Signed-off-by: Øyvind Harboe --- MODULE.bazel | 54 ++++++++++++---- MODULE.bazel.lock | 24 ++++---- ...ib-wrapper-headers-use-I-not-isystem.patch | 61 +++++++++++++++++++ .../BUILD.bazel | 0 ...01-libc-headers-idirafter-precedence.patch | 28 --------- 5 files changed, 113 insertions(+), 54 deletions(-) create mode 100644 bazel/bison-patches/0001-gnulib-wrapper-headers-use-I-not-isystem.patch rename bazel/{hermetic-llvm-patches => bison-patches}/BUILD.bazel (100%) delete mode 100644 bazel/hermetic-llvm-patches/0001-libc-headers-idirafter-precedence.patch diff --git a/MODULE.bazel b/MODULE.bazel index 3934c4355dc..08d9667edc5 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -34,6 +34,20 @@ bazel_dep(name = "rules_python", version = "1.8.5") bazel_dep(name = "rules_shell", version = "0.6.1") bazel_dep(name = "swig", version = "4.3.0.bcr.2") +# bison is transitive (rules_bison -> bison). Same gnulib libc-header +# shadowing class as sed/gawk/m4 under hermetic-llvm (BCR #7642). +# 3.8.2.bcr.7 fixes lib/ but src/ still picks up raw libc headers; the +# patch restores gnulib wrapper-header precedence via -I (to be proposed +# upstream as 3.8.2.bcr.8, then drop). +single_version_override( + module_name = "bison", + patch_strip = 1, + patches = [ + "//bazel/bison-patches:0001-gnulib-wrapper-headers-use-I-not-isystem.patch", + ], + version = "3.8.2.bcr.7", +) + BOOST_VERSION = "1.89.0.bcr.2" # boost::icl's exclusive_less_than comparator violates strict weak ordering @@ -89,10 +103,37 @@ bazel_dep(name = "boost.utility", version = BOOST_VERSION) bazel_dep(name = "cudd", version = "3.0.0.bcr.2") bazel_dep(name = "eigen", version = "3.4.0.bcr.3") bazel_dep(name = "fmt", version = "11.2.0.bcr.1") + +# gawk is transitive (abc -> ncurses -> gawk; yosys -> gawk). Same gnulib +# libc-header shadowing as sed: gawk@5.3.2.bcr.2 fails to compile against +# hermetic-llvm's explicit libc -isystem entries (BCR #7642). 5.3.2.bcr.7 +# carries the upstream fix (BCR #7989). +single_version_override( + module_name = "gawk", + version = "5.3.2.bcr.7", +) + bazel_dep(name = "git", version = "2.54.0") bazel_dep(name = "googletest", version = "1.17.0.bcr.2") +# m4 is transitive (rules_bison/rules_flex -> m4). Same gnulib libc-header +# shadowing class as sed/gawk under hermetic-llvm (BCR #7642). +single_version_override( + module_name = "m4", + version = "1.4.21.bcr.4", +) + bazel_dep(name = "openmp", version = "21.1.5.bcr.1") bazel_dep(name = "or-tools", version = "9.15") + +# sed is transitive (abc -> ncurses -> sed). Its vendored gnulib headers +# deliberately shadow libc headers (stdio.h, error.h); hermetic-llvm's +# explicit libc -isystem entries outrank them and sed@4.9.bcr.3 fails to +# compile (BCR #7642). 4.9.bcr.5 carries the upstream fix (BCR #7915). +single_version_override( + module_name = "sed", + version = "4.9.bcr.5", +) + bazel_dep(name = "spdlog", version = "1.15.1") bazel_dep(name = "sv-lang", version = "10.0.1-20260316-f04e8156") bazel_dep(name = "tcl_lang", version = "9.0.2.bcr.1") @@ -134,19 +175,6 @@ git_override( ## without FHS paths (e.g. NixOS). bazel_dep(name = "llvm", version = "0.8.11", dev_dependency = True) -# hermetic-llvm injects kernel/glibc headers as the first -isystem entries, -# outranking libraries that deliberately shadow libc headers (gnulib's -# stdio.h in BCR sed; tcl's vendored minizip crypt.h). Host sysroots are -# searched last; -idirafter restores that precedence. Drop when fixed -# upstream. -single_version_override( - module_name = "llvm", - patch_strip = 1, - patches = [ - "//bazel/hermetic-llvm-patches:0001-libc-headers-idirafter-precedence.patch", - ], -) - # --- Dev dependencies (not propagated to downstream consumers) --- bazel_dep(name = "aspect_rules_lint", version = "2.5.2", dev_dependency = True) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index fc6d726abc0..794eb09c909 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -88,6 +88,7 @@ "https://bcr.bazel.build/modules/bazel_features/1.41.0/MODULE.bazel": "6e0f87fafed801273c371d41e22a15a6f8abf83fdd7f87d5e44ad317b94433d0", "https://bcr.bazel.build/modules/bazel_features/1.42.0/MODULE.bazel": "e8ca15cb2639c5f12183db6dcb678735555d0cdd739b32a0418b6532b5e565f8", "https://bcr.bazel.build/modules/bazel_features/1.43.0/MODULE.bazel": "defa2226f06ba20550d6548c3a2ea2a7929634437a52973869c20c225450eb91", + "https://bcr.bazel.build/modules/bazel_features/1.47.1/MODULE.bazel": "e6be691539341681678a68c2076771f6a73fcdc9823c5b6bf280d2e916d1ad80", "https://bcr.bazel.build/modules/bazel_features/1.50.0/MODULE.bazel": "2083ef9c7a469f520890483ccf8e0189d6e71e2117e7752e15e6554433d5ae3e", "https://bcr.bazel.build/modules/bazel_features/1.50.0/source.json": "e0ee3debde2789ff56e4452e612d126925ba9ab64d4bde79c67f099d2902df9b", "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", @@ -112,8 +113,8 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/MODULE.bazel": "72997b29dfd95c3fa0d0c48322d05590418edef451f8db8db5509c57875fb4b7", "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/source.json": "7ad77c1e8c1b84222d9b3f3cae016a76639435744c19330b0b37c0a3c9da7dc0", - "https://bcr.bazel.build/modules/bison/3.8.2.bcr.5/MODULE.bazel": "96e976881e5670bdb2461157027ed6f3e25084b727aab6b7047210da9af7f678", - "https://bcr.bazel.build/modules/bison/3.8.2.bcr.5/source.json": "3cde6dd5a399b67f8124e25e7c7d6eb754807ee7b5a88aab28cf593ff37587ca", + "https://bcr.bazel.build/modules/bison/3.8.2.bcr.7/MODULE.bazel": "92dd6e702fb01134ff70c8d035ac437b68d79371bb1b0181cd78e8c1cf90af05", + "https://bcr.bazel.build/modules/bison/3.8.2.bcr.7/source.json": "c37d58def4305e8d243511d8d823611847a228a8c6186794498f5d30f003430a", "https://bcr.bazel.build/modules/bliss/0.73/MODULE.bazel": "26b5476884b67df20a8b87ab2806657123b439e978da76f484427a15fb552b26", "https://bcr.bazel.build/modules/bliss/0.73/source.json": "5cb395710670662321f492fc3d4fce3551e3d5708682e4f2320bacaadf6e5e36", "https://bcr.bazel.build/modules/boost.algorithm/1.87.0/MODULE.bazel": "d1c8f1466cc1a04a16eebeefe0f54a9508cc9f1a3c21d65d806acbc36593e204", @@ -408,10 +409,8 @@ "https://bcr.bazel.build/modules/fmt/12.1.0/source.json": "d7b35221043d8d7c69e2a64d6a0783c97aa49c5ce198ee0a5ccd18c99f070b1a", "https://bcr.bazel.build/modules/freetype/2.13.3/MODULE.bazel": "9931a69ef01caba64cc7516c03c2c6c8ad0707526185d31eb81d2987163880e0", "https://bcr.bazel.build/modules/freetype/2.13.3/source.json": "a051388a7fa6b0e2ccf8e70bc30ecb00d9708fa98e5c2adac1d67514d8332cc3", - "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", - "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.2/MODULE.bazel": "ae318680f31d1960f1d102db3b7e04cfa6fb38ae9ba54319b6b9b104b49e7c65", - "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/MODULE.bazel": "f1b7bb2dd53e8f2ef984b39485ec8a44e9076dda5c4b8efd2fb4c6a6e856a31d", - "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/source.json": "ebe931bfe362e4b41e59ee00a528db6074157ff2ced92eb9e970acab2e1089c9", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.7/MODULE.bazel": "1e5e03c383fa64ebbe0942a225c32168fd6ef6b270351bbec481d47d19d1b96d", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.7/source.json": "34a83d58ae2f1ef6731d1fc8a1b6f07825741aff3f5993b67b67ec21d2c2946e", "https://bcr.bazel.build/modules/gazelle/0.27.0/MODULE.bazel": "3446abd608295de6d90b4a8a118ed64a9ce11dcb3dda2dc3290a22056bd20996", "https://bcr.bazel.build/modules/gazelle/0.30.0/MODULE.bazel": "f888a1effe338491f35f0e0e85003b47bb9d8295ccba73c37e07702d8d31c65b", "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", @@ -488,9 +487,8 @@ "https://bcr.bazel.build/modules/libxcb/1.17.0.bcr.2/source.json": "58c8c30c5d0f6253c94d7a38ab95fd5174683d613d47b9114520da6acef52ae8", "https://bcr.bazel.build/modules/llvm/0.8.11/MODULE.bazel": "0f8c30b74be64f0e91764e925d0f562c70e8d85b6cea912f724d6b3753d6a33f", "https://bcr.bazel.build/modules/llvm/0.8.11/source.json": "b40edb2bb2ed271bf613b396d245cb473c42fb057a2b26a3bc7d7e8bfcf6aa71", - "https://bcr.bazel.build/modules/m4/1.4.20.bcr.4/MODULE.bazel": "582008fee330b47fe8db3e786cf78f05c926d2b37fcde0178316bbc5a717e096", - "https://bcr.bazel.build/modules/m4/1.4.21/MODULE.bazel": "f0228f83067f5b7c3ecb5dd5269ca4337e935c31305e26d5a60a8876f6fc2620", - "https://bcr.bazel.build/modules/m4/1.4.21/source.json": "e6df963f42bf3e7c227274f583a3bccb5c4e8e2ecd5b730b6e47bdb919e02950", + "https://bcr.bazel.build/modules/m4/1.4.21.bcr.4/MODULE.bazel": "40fef4b9091aea8e97b76a5706b3490b8d731ba008b02a883751ee2abf84daf4", + "https://bcr.bazel.build/modules/m4/1.4.21.bcr.4/source.json": "c4add456cedd43c0c0d1329bd52120f8eedb6191c6d8299448bce1ed81be60a4", "https://bcr.bazel.build/modules/mbedtls/3.6.0/MODULE.bazel": "8e380e4698107c5f8766264d4df92e36766248447858db28187151d884995a09", "https://bcr.bazel.build/modules/mbedtls/3.6.0/source.json": "1dbe7eb5258050afcc3806b9d43050f71c6f539ce0175535c670df606790b30c", "https://bcr.bazel.build/modules/ncurses/6.4.20221231.bcr.11/MODULE.bazel": "ef03f49137ca4abaf6648c795635abb21024d74e97822016cda9dc817e1418ae", @@ -637,9 +635,9 @@ "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", "https://bcr.bazel.build/modules/rules_cc/0.2.9/MODULE.bazel": "34263f1dca62ea664265438cef714d7db124c03e1ed55ebb4f1dc860164308d1", "https://bcr.bazel.build/modules/rules_cc_autoconf/0.10.0/MODULE.bazel": "1b289095784d6d4253b45de18e380ee5f5c179b35223ad067ae1b17d8a537429", - "https://bcr.bazel.build/modules/rules_cc_autoconf/0.10.0/source.json": "f79fac9d16e44311c0a6edc53d974feaac72aa83ea332053934ead1b920ac696", + "https://bcr.bazel.build/modules/rules_cc_autoconf/0.13.2/MODULE.bazel": "3ec13b93183a28b7088563e0fef1a51835dde06c272a1dabc5900e8bc26b2188", + "https://bcr.bazel.build/modules/rules_cc_autoconf/0.13.2/source.json": "e56c70d9525a6512263c562962d9087091da968945a0196f568d61866f23667a", "https://bcr.bazel.build/modules/rules_cc_autoconf/0.7.15/MODULE.bazel": "0897d104c122e89a9a4e320b4f5b2c6fdded368052bc3e6c3d1f0728b4034187", - "https://bcr.bazel.build/modules/rules_cc_autoconf/0.9.0/MODULE.bazel": "cd81eb3ceb1e92326aa48c205937dcb79229222e1364c58166d0fdec27352c5d", "https://bcr.bazel.build/modules/rules_flex/0.3.1/MODULE.bazel": "5aea738f59e47769d219f972fc8426c53693c262895787efafa71fe9795bd7e3", "https://bcr.bazel.build/modules/rules_flex/0.4/MODULE.bazel": "543edf994d2c044f3d723374e50eaa739ed7ee9d82b8185adb8413bce269dcf3", "https://bcr.bazel.build/modules/rules_flex/0.4/source.json": "98bc56c2139b6ed6387dbabd06f625e82500bed8ba1689a5f4f3c9e8c3289f82", @@ -775,8 +773,8 @@ "https://bcr.bazel.build/modules/rules_verilog/1.1.1/source.json": "32a02fe6f97e1a233cff48e0eae70243e2e25d4c57fc684f933e06ef67440d8b", "https://bcr.bazel.build/modules/scip/9.2.3/MODULE.bazel": "392d8e76efeab5ef5978e66d15c2ce5e2607b80ea0804163861dd721eed93121", "https://bcr.bazel.build/modules/scip/9.2.3/source.json": "5ffc88567e8ff0f3ef59f20364af7cf903108f137fe36420f380e38b6cc92cb1", - "https://bcr.bazel.build/modules/sed/4.9.bcr.3/MODULE.bazel": "3aca45895b85b6ef65366cc12a45217ba6870f8931d2d62e09c99c772d9736ab", - "https://bcr.bazel.build/modules/sed/4.9.bcr.3/source.json": "31c0cf4c135ed3fa58298cd7bcfd4301c54ea4cf59d7c4e2ea0a180ce68eb34f", + "https://bcr.bazel.build/modules/sed/4.9.bcr.5/MODULE.bazel": "aa15eaf52261c9ead26a894939afe906511c9316970451a80806da73dbb2d1be", + "https://bcr.bazel.build/modules/sed/4.9.bcr.5/source.json": "a8f0b18e59882ed5a5c9293c960f5ffa01f14cec660fe4f997f77a61716f9d18", "https://bcr.bazel.build/modules/soplex/7.1.4.bcr.1/MODULE.bazel": "dbba514d47728de2ebd08ca7d02ee9bb3d6349dee1b4fbe78f6f15694acb94ff", "https://bcr.bazel.build/modules/soplex/7.1.4.bcr.1/source.json": "ca82ab37a51da4880fdf49c2f75531368033d7c50833b621025293b25e74b13c", "https://bcr.bazel.build/modules/spdlog/1.15.1/MODULE.bazel": "ac00f1ace2e0ec518f1cdcfd41b3f016e8257186d015324e33a5644149a9c327", diff --git a/bazel/bison-patches/0001-gnulib-wrapper-headers-use-I-not-isystem.patch b/bazel/bison-patches/0001-gnulib-wrapper-headers-use-I-not-isystem.patch new file mode 100644 index 00000000000..01d22abe776 --- /dev/null +++ b/bazel/bison-patches/0001-gnulib-wrapper-headers-use-I-not-isystem.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Oyvind Harboe +Date: Mon, 6 Jul 2026 00:00:00 +0000 +Subject: [PATCH] BUILD: let gnulib wrapper headers shadow libc via -I + +Gnulib generates wrapper headers (lib/stdio.h, lib/fcntl.h, ...) that +must be found before the libc ones; they forward with #include_next and +add gnulib decorations (_GL_ATTRIBUTE_DEALLOC etc.) that lib/*-safer.h +and src/*.c rely on. The overlay's 'includes = ["lib"]' emits -isystem, +which loses to toolchains that inject libc headers as earlier -isystem +entries (e.g. the zero-sysroot hermetic-llvm BCR toolchain), and a dir +listed as both -I and -isystem is kept as -isystem, so the -I must +replace it, not merely accompany it. + +Plain -I outranks every -isystem, restoring autotools' AM_CPPFLAGS = +-I./lib ordering. $(BINDIR) covers the generated wrappers, the source +path the checked-in ones. + +Same class of fix as sed@4.9.bcr.5 (BCR #7915) and gawk@5.3.2.bcr.7 +(BCR #7989); to be proposed upstream as bison@3.8.2.bcr.8. +--- +diff --git a/BUILD.bazel b/BUILD.bazel +--- a/BUILD.bazel ++++ b/BUILD.bazel +@@ -711,7 +711,19 @@ + tags = ["manual"], + ) + +-BISON_COPTS = select({ ++# Gnulib's wrapper headers (generated lib/stdio.h, lib/fcntl.h, ... plus ++# the checked-in lib/*-safer.h helpers) must shadow the libc headers: they ++# forward via #include_next and add gnulib decorations ++# (_GL_ATTRIBUTE_DEALLOC etc.) that lib/ and src/ sources rely on. ++# `includes = ["lib"]` emits -isystem, which loses against toolchains that ++# inject libc headers as earlier -isystem entries (e.g. the zero-sysroot ++# hermetic-llvm BCR toolchain). Plain -I outranks every -isystem (and a dir ++# listed as both -I and -isystem stays -isystem, so the -isystem form must ++# go entirely). This restores autotools' AM_CPPFLAGS = -I./lib ordering. ++BISON_COPTS = [ ++ "-Iexternal/bison+/lib", ++ "-I$(BINDIR)/external/bison+/lib", ++] + select({ + "@rules_cc//cc/compiler:clang": [ + "-w", + "-std=c11", +@@ -813,7 +825,6 @@ + "src/scan-skel.c", + ":configmake_h_src", + ], +- includes = ["lib"], + textual_hdrs = [ + "lib/timevar.def", + "lib/printf-frexp.c", +@@ -997,6 +1008,7 @@ + "lib/main.c", + "lib/yyerror.c", + ], ++ copts = BISON_COPTS, + implementation_deps = [":headers"], + local_defines = LOCAL_DEFINES, + ) diff --git a/bazel/hermetic-llvm-patches/BUILD.bazel b/bazel/bison-patches/BUILD.bazel similarity index 100% rename from bazel/hermetic-llvm-patches/BUILD.bazel rename to bazel/bison-patches/BUILD.bazel diff --git a/bazel/hermetic-llvm-patches/0001-libc-headers-idirafter-precedence.patch b/bazel/hermetic-llvm-patches/0001-libc-headers-idirafter-precedence.patch deleted file mode 100644 index 25f1d2a7f90..00000000000 --- a/bazel/hermetic-llvm-patches/0001-libc-headers-idirafter-precedence.patch +++ /dev/null @@ -1,28 +0,0 @@ -Linux kernel and glibc headers are injected as the first -isystem -entries, which outranks -isystem/-I directories of libraries that -deliberately shadow libc headers: gnulib's stdio.h replacement in BCR -sed fails to take effect (SETLOCALE_NULL_MAX / _GL_ATTRIBUTE_* errors) -and tcl's vendored minizip crypt.h loses to glibc's crypt.h. Host -sysroots provide libc via the default search path, searched after all -user includes; -idirafter restores that precedence. - ---- a/toolchain/args/linux/BUILD.bazel -+++ b/toolchain/args/linux/BUILD.bazel -@@ -24,7 +24,7 @@ - "@kernel_headers//:kernel_headers_directory", - ], - args = [ -- "-isystem", -+ "-idirafter", - "{kernel_headers_include_search_path}", - ], - data = [ -@@ -46,7 +46,7 @@ - ], - args = [ - # "__GLIBC_MINOR__={d}", version.minor -- "-isystem", -+ "-idirafter", - "{libc_headers_include_search_path}", - ], - data = [ From 161aea40dc2dc77f1b32e9c3c18e686440c620a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 6 Jul 2026 19:43:43 +0200 Subject: [PATCH 21/33] bazel: buildifier-format MODULE.bazel (fix fmt/lint tests) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the missing blank line before the m4 single_version_override comment. buildifier requires a blank line between a bazel_dep and a following comment-led block; without it //:fmt_bzl_test and //:lint_bzl_test fail. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Øyvind Harboe --- MODULE.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/MODULE.bazel b/MODULE.bazel index 08d9667edc5..cc37d9b10e6 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -115,6 +115,7 @@ single_version_override( bazel_dep(name = "git", version = "2.54.0") bazel_dep(name = "googletest", version = "1.17.0.bcr.2") + # m4 is transitive (rules_bison/rules_flex -> m4). Same gnulib libc-header # shadowing class as sed/gawk under hermetic-llvm (BCR #7642). single_version_override( From 5627d9e204900becc075562d64a82a08afd5ec92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 22:03:48 +0200 Subject: [PATCH 22/33] bazel: rename etc/bazel to etc/bazel-hermetic; mark as local diagnostic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback (hzeller, maliberty): the pruned-environment wrapper is a developer diagnostic, not something to force on people. The opt-in mechanism was already in place — the file lives at etc/bazel, not tools/bazel, so bazelisk never auto-invokes it — but that was easy to miss: the tools/bazel -> etc/bazel rename rode along in an unrelated commit, and the follow-up that documented the intent (a8f056067e) touched only comments, so it read as "comment-only" against the fragility concern. Make the intent unmistakable at the name and in the header: - Rename etc/bazel -> etc/bazel-hermetic. A distinct name cannot be confused with the default bazel and reinforces that bazelisk will not pick it up (it only auto-invokes tools/bazel). - Rewrite the header to state plainly that this is an opt-in local developer diagnostic, not part of the normal build, and why forcing it (as tools/bazel) would break ordinary builds. - Update the internal error string and the two .bazelrc comment references to the new name. No behavior change. git grep confirms these are the only in-tree references to the path. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Øyvind Harboe --- .bazelrc | 4 ++-- etc/{bazel => bazel-hermetic} | 28 ++++++++++++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) rename etc/{bazel => bazel-hermetic} (85%) diff --git a/.bazelrc b/.bazelrc index b39b8eba15a..f1bede0caee 100644 --- a/.bazelrc +++ b/.bazelrc @@ -128,7 +128,7 @@ build:profile --copt -fno-omit-frame-pointer --host_copt -fno-omit-frame-pointer build --incompatible_strict_action_env build --nosandbox_default_allow_network # Refuse to autodetect a local C++ toolchain (/usr/bin/gcc); the hermetic -# toolchain must win resolution, with or without the etc/bazel wrapper. +# toolchain must win resolution, with or without the etc/bazel-hermetic wrapper. common --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 test --build_tests_only @@ -151,7 +151,7 @@ build:ci --config=opt # Launch py_binary via a shell stub instead of rules_python's legacy python # stub, whose '#!/usr/bin/env python3' shebang needs a host python3 before -# the hermetic interpreter takes over (found by etc/bazel). +# the hermetic interpreter takes over (found by etc/bazel-hermetic). common --@rules_python//python/config_settings:bootstrap_impl=script # Setup remote cache diff --git a/etc/bazel b/etc/bazel-hermetic similarity index 85% rename from etc/bazel rename to etc/bazel-hermetic index 025cea8fc2b..79ddcdd2297 100755 --- a/etc/bazel +++ b/etc/bazel-hermetic @@ -1,18 +1,26 @@ #!/usr/bin/env bash -# Launch bazel in a pruned environment so that toolchains and tools the -# build is supposed to provide hermetically cannot be picked up from the -# host. This is a ratchet, not a proof: it removes host compilers, +# LOCAL DEVELOPER DIAGNOSTIC — opt-in, not part of the normal build. +# Nothing auto-invokes this; you never need it to build or test OpenROAD. +# (Authored with Claude to catch host-toolchain leaks during the +# hermetic-llvm bring-up.) +# +# It launches bazel in a pruned environment so that toolchains and tools +# the build is supposed to provide hermetically cannot be picked up from +# the host. This is a ratchet, not a proof: it removes host compilers, # linkers and interpreters from PATH. The C++ toolchain (hermetic-llvm) # is zero-sysroot, so no host headers or libraries are involved; the # remaining host surface is the bazel binary itself and the allowlisted # shell tools below. # -# Opt-in developer tool; invoke directly: -# etc/bazel [startup flags] [args...] -# (Deliberately not named tools/bazel: bazelisk would auto-invoke that -# for every command, and repository rules of some dependencies still -# expect host tools this wrapper prunes, e.g. GNU Make's configure -# probing for ld in bazel-orfs's gnumake rule.) +# Invoke it explicitly when you want the check: +# etc/bazel-hermetic [startup flags] [args...] +# Deliberately named etc/bazel-hermetic, NOT tools/bazel: bazelisk +# auto-invokes a tools/bazel wrapper for every command, which would force +# the pruned environment on everyone — but repository rules of some +# dependencies legitimately expect host tools this wrapper prunes (e.g. +# GNU Make's configure probing for ld in bazel-orfs's gnumake rule), so +# forcing it would break ordinary builds. Keeping it opt-in and +# out-of-the-way is intentional. # # The allowlist below is the contract for what the build may use from the # host. Grow it only with a comment stating which repository rule or @@ -79,7 +87,7 @@ done BAZEL=${BAZEL_REAL:-} if [[ -z ${BAZEL} ]]; then BAZEL=$(command -v bazelisk || command -v bazel) || - { echo >&2 "tools/bazel: no bazel or bazelisk on PATH"; exit 1; } + { echo >&2 "etc/bazel-hermetic: no bazel or bazelisk on PATH"; exit 1; } fi # Wrapper detection: the bazelisk launcher by name, or any script shebang # (npm/shell wrappers). Native binaries (ELF on Linux, Mach-O on macOS) From c7b4bb53fac1b258ff9a5873e89c62da309b80d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 22:05:46 +0200 Subject: [PATCH 23/33] bazel-hermetic: fail loud on unrecognized command (Codex P2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A bazel startup option can pass its value as a separate word (`--output_base /tmp/x build //...`). The startup-flag loop stops at the first argument that does not begin with --, so that value ('/tmp/x') was taken as the command. It then missed the build|test|... case, so --repo_env=PATH=${TOOLBOX} was not added and the repository rules ran with the unpruned host PATH — silently bypassing the very hermeticity check this wrapper exists to enforce (chatgpt-codex-connector, P2). Make the command classification exhaustive and fail loud: build-ish commands get --repo_env, other known verbs pass through untouched, a flags-only invocation is handled as before, and anything else is an error that tells the user to join the startup value to its flag (--flag=value). An unknown command can no longer silently skip pruning. Verified with a stub bazel that prints its argv: - `build //x` -> --repo_env present - `--output_base=/tmp/x build //x` -> --repo_env present (safe form) - `--output_base /tmp/x build //x` -> exits 1, bazel not run (bug case) - `info` -> passthrough, no --repo_env - `--version` -> flags-only, verbatim Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Øyvind Harboe --- etc/bazel-hermetic | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/etc/bazel-hermetic b/etc/bazel-hermetic index 79ddcdd2297..90967c7dcc6 100755 --- a/etc/bazel-hermetic +++ b/etc/bazel-hermetic @@ -124,7 +124,14 @@ done command=${1:-} [[ $# -gt 0 ]] && shift -# Flags only exist on build-ish commands; startup flags go before the command. +# Classify the command. The set is exhaustive on purpose: a bazel startup +# option can take its value as a separate word (`--output_base /tmp/x`), +# and the loop above stops at the first non---flag, so that value would be +# mistaken for the command. If we silently treated an unrecognized token +# as a no-repo_env command, a build invoked that way would run repository +# rules with the unpruned host PATH — exactly the leak this tool exists to +# catch. So fail loud instead: an unknown command is an error, not a +# silent bypass. extra_flags=() case "${command}" in build|test|run|cquery|aquery|coverage|fetch|vendor|sync|mod) @@ -139,6 +146,21 @@ case "${command}" in "--repo_env=PATH=${TOOLBOX}" ) ;; + # Commands that never fetch/analyze external repositories: no pruning + # needed, pass through untouched. + query|info|clean|version|help|shutdown|dump|license|\ + canonicalize-flags|print_action|analyze-profile|config|mobile-install) + ;; + '') + # Flags-only invocation (e.g. `--version`); handled below. + ;; + *) + echo >&2 "etc/bazel-hermetic: unrecognized command '${command}'." + echo >&2 "If this was a startup option value, join it to the flag" \ + "(--flag=value, one word) so the command is parsed correctly" \ + "and the hermetic PATH is not silently bypassed." + exit 1 + ;; esac # Network, auth, TLS and cache/tmp configuration is not toolchain leakage: From 35e34afb689c187c2ea820c2d6f248c2c458f971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 22:13:32 +0200 Subject: [PATCH 24/33] bazel-hermetic: portable env preservation and symlink resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two findings from the latest Gemini pass, both fixed with forms that are actually portable to the bash 3.2 / BSD userland macOS ships (Gemini's own suggestions would have regressed there): - Env preservation: read each variable with `printenv "$var"` instead of `${!var:-}`. Gemini flagged `${!var:-}` as a syntax error; it is valid on modern bash (5.x), but composing indirect expansion with a modifier is unreliable on bash 3.2, and the suggested `[[ -v $var ]]` needs bash 4.2 — so it would break the macOS /bin/bash it was meant to protect. These are all exported environment variables, so printenv reads them directly and set -u never sees an unset name. Functionally identical: only set/non-empty variables are preserved. - Symlink resolution: `realpath "${BAZEL}"` instead of `readlink -f`. BSD readlink on macOS has no -f, which would fail under set -e; realpath is already in ALLOWED_TOOLS and is standard on Linux and macOS 10.11+. Re-verified with the stub-bazel argv harness (build / bug case / info / --version) and a functional check of the printenv preservation loop. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Øyvind Harboe --- etc/bazel-hermetic | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/etc/bazel-hermetic b/etc/bazel-hermetic index 90967c7dcc6..5eed5d50a54 100755 --- a/etc/bazel-hermetic +++ b/etc/bazel-hermetic @@ -92,7 +92,7 @@ fi # Wrapper detection: the bazelisk launcher by name, or any script shebang # (npm/shell wrappers). Native binaries (ELF on Linux, Mach-O on macOS) # are symlinked directly. -if [[ ${BAZEL} == *bazelisk* || $(head -c 2 "$(readlink -f "${BAZEL}")") == '#!' ]]; then +if [[ ${BAZEL} == *bazelisk* || $(head -c 2 "$(realpath "${BAZEL}")") == '#!' ]]; then version=$(BAZELISK_SKIP_WRAPPER=1 "${BAZEL}" --version | awk '{print $2}') os=$(uname -s | tr '[:upper:]' '[:lower:]') arch=$(uname -m | sed 's/aarch64/arm64/') @@ -170,8 +170,14 @@ preserved_env=() for var in http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY \ SSH_AUTH_SOCK SSL_CERT_FILE SSL_CERT_DIR XDG_CACHE_HOME TMPDIR \ DISPLAY WAYLAND_DISPLAY XAUTHORITY; do - if [[ -n ${!var:-} ]]; then - preserved_env+=("${var}=${!var}") + # printenv, not ${!var} or -v: these run before the pruned exec, so keep + # them portable to the bash 3.2 that macOS ships as /bin/bash. -v needs + # bash 4.2, and indirect expansion composed with a modifier (${!var:-}) + # is unreliable on 3.2. All of these are exported environment variables, + # so printenv reads them directly and set -u never sees an unset name. + value=$(printenv "${var}" || true) + if [[ -n ${value} ]]; then + preserved_env+=("${var}=${value}") fi done From a7569b6a6291f6a43e7364d32c2e9795b8bcf69b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 22:21:17 +0200 Subject: [PATCH 25/33] bazel-hermetic: guard empty-array/unset expansion under set -u (bash 3.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Latest Gemini pass, two high-severity bash 3.2 findings (macOS /bin/bash): - A bare "${arr[@]}" on an empty array is an unbound-variable error under set -u on bash < 4.4. startup_flags, extra_flags and preserved_env are routinely empty (no startup flags, non-build command, or no proxy/TLS vars), so `etc/bazel-hermetic build //x` would crash on macOS. Use the ${arr[@]+"${arr[@]}"} idiom, which expands to nothing when empty. - HOME="${HOME}" would trip the same error if HOME is unset in a minimal environment; default it with ${HOME:-}. Consistent with the bash-3.2 portability fixed in the previous commit. Re-verified with the stub-bazel argv harness in a cleared environment (empty preserved_env and startup_flags) across build / --output_base= / info / --version: argv unchanged, no crash. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Øyvind Harboe --- etc/bazel-hermetic | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/etc/bazel-hermetic b/etc/bazel-hermetic index 5eed5d50a54..207a2ab0ae7 100755 --- a/etc/bazel-hermetic +++ b/etc/bazel-hermetic @@ -183,22 +183,26 @@ done # --nosystem_rc/--nohome_rc: only the workspace .bazelrc applies, so host- # specific configuration (e.g. remote caches) cannot mask missing hermeticity. -bazel_argv=(--nosystem_rc --nohome_rc "${startup_flags[@]}") +# ${arr[@]+"${arr[@]}"} expands to nothing when the array is empty: a bare +# "${arr[@]}" is an unbound-variable error under set -u on bash < 4.4 (the +# 3.2 that macOS ships as /bin/bash). +bazel_argv=(--nosystem_rc --nohome_rc ${startup_flags[@]+"${startup_flags[@]}"}) if [[ -n ${command} ]]; then - bazel_argv+=("${command}" "${extra_flags[@]}" "$@") + bazel_argv+=("${command}" ${extra_flags[@]+"${extra_flags[@]}"} "$@") fi # Flags-only invocations (e.g. `bazel --version`) go to the binary verbatim; # rc-file options would be rejected there. if [[ -z ${command} ]]; then - bazel_argv=("${startup_flags[@]}") + bazel_argv=(${startup_flags[@]+"${startup_flags[@]}"}) fi +# HOME:- and the array guards: same set -u / bash 3.2 safety as above. exec env -i \ - HOME="${HOME}" \ + HOME="${HOME:-}" \ USER="${USER:-$(id -un)}" \ LOGNAME="${LOGNAME:-$(id -un)}" \ TERM="${TERM:-dumb}" \ LANG=C.UTF-8 \ PATH="${TOOLBOX}" \ - "${preserved_env[@]}" \ - "${TOOLBOX}/bazel" "${bazel_argv[@]}" + ${preserved_env[@]+"${preserved_env[@]}"} \ + "${TOOLBOX}/bazel" ${bazel_argv[@]+"${bazel_argv[@]}"} From ad51f2aa5ac564053449d54408314c86ace6bc82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 22:23:27 +0200 Subject: [PATCH 26/33] bazel: mirror macOS SDK framework selection in downstream test module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex P2: the root MODULE.bazel selects the desktop frameworks Qt needs from hermetic-llvm's opt-in macOS SDK subset, but does so on a dev_dependency module extension. dev_dependency tags do not apply when OpenROAD is consumed as a dependency, so a downstream macOS consumer building the Qt GUI would hit the same missing-framework / dangling-.tbd failures the root module avoids. test/downstream/MODULE.bazel exists precisely to model what an external consumer must repeat (it already re-declares the qt-bazel git_override for the same root-module-only reason). Mirror the osx.frameworks(...) selection there too, with a note to keep the two lists in sync. Buildifier/lint clean. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Øyvind Harboe --- test/downstream/MODULE.bazel | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/downstream/MODULE.bazel b/test/downstream/MODULE.bazel index 9e8365835d4..bff693b9ea4 100644 --- a/test/downstream/MODULE.bazel +++ b/test/downstream/MODULE.bazel @@ -29,6 +29,50 @@ register_toolchains( "@llvm//toolchain:all", ) +# hermetic-llvm ships a minimal macOS SDK subset (frameworks are opt-in). +# The root MODULE.bazel selects the desktop frameworks Qt needs, but it does +# so on a dev_dependency extension, which does not apply when OpenROAD is +# consumed as a dependency -- so a downstream macOS consumer building the Qt +# GUI must repeat the selection here, just like the qt-bazel git_override +# above. Keep this list in sync with osx.frameworks(...) in ../../MODULE.bazel. +osx = use_extension("@llvm//extensions:osx.bzl", "osx") +osx.frameworks(names = [ + "CoreFoundation", + "Foundation", + "Kernel", + "OSLog", + "Security", + "SystemConfiguration", + "AppKit", + "ApplicationServices", + "CFNetwork", + "Carbon", + "Cocoa", + "ColorSync", + "CoreData", + "CoreGraphics", + "CoreImage", + "CoreServices", + "CoreText", + "CoreVideo", + "DiskArbitration", + "FontServices", + "GSS", + "IOKit", + "IOSurface", + "ImageIO", + "Metal", + "MetalKit", + "ModelIO", + "OpenGL", + "PrintCore", + "QuartzCore", + "CloudKit", + "Symbols", + "UIFoundation", + "UniformTypeIdentifiers", +]) + python = use_extension("@rules_python//python/extensions:python.bzl", "python") python.toolchain( ignore_root_user_error = True, From 83e3846791d272b9a322325f50f629e43196210e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 22:30:11 +0200 Subject: [PATCH 27/33] bazel-hermetic: drop realpath from shebang check; macOS-safe LANG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two macOS findings from the latest Gemini pass: - Shebang detection no longer shells out to realpath (nor readlink -f): read the candidate via redirection, `head -c 2 < "${BAZEL}"`. open() follows the symlink itself, so no path-resolution tool is needed — which also settles whether realpath/readlink -f is present on macOS (it need not be). Verified reading through a symlink yields the target file's first bytes. - LANG: macOS has no C.UTF-8 locale, so forcing it makes programs emit "cannot change locale" warnings. Pick en_US.UTF-8 on Darwin, C.UTF-8 elsewhere; both give UTF-8 byte handling. Re-verified with the stub-bazel argv harness (build / bug case / info). Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Øyvind Harboe --- etc/bazel-hermetic | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/etc/bazel-hermetic b/etc/bazel-hermetic index 207a2ab0ae7..da5c482cac0 100755 --- a/etc/bazel-hermetic +++ b/etc/bazel-hermetic @@ -91,8 +91,10 @@ if [[ -z ${BAZEL} ]]; then fi # Wrapper detection: the bazelisk launcher by name, or any script shebang # (npm/shell wrappers). Native binaries (ELF on Linux, Mach-O on macOS) -# are symlinked directly. -if [[ ${BAZEL} == *bazelisk* || $(head -c 2 "$(realpath "${BAZEL}")") == '#!' ]]; then +# are symlinked directly. Read via redirection: open() follows the +# symlink for us, so no realpath/readlink -f is needed (neither is +# reliably present on macOS). +if [[ ${BAZEL} == *bazelisk* || $(head -c 2 < "${BAZEL}") == '#!' ]]; then version=$(BAZELISK_SKIP_WRAPPER=1 "${BAZEL}" --version | awk '{print $2}') os=$(uname -s | tr '[:upper:]' '[:lower:]') arch=$(uname -m | sed 's/aarch64/arm64/') @@ -196,13 +198,21 @@ if [[ -z ${command} ]]; then bazel_argv=(${startup_flags[@]+"${startup_flags[@]}"}) fi +# macOS has no C.UTF-8 locale, so forcing it there makes programs print +# "cannot change locale" warnings; use en_US.UTF-8 on Darwin. Both give +# UTF-8 byte handling. +case "$(uname -s)" in + Darwin) lang=en_US.UTF-8 ;; + *) lang=C.UTF-8 ;; +esac + # HOME:- and the array guards: same set -u / bash 3.2 safety as above. exec env -i \ HOME="${HOME:-}" \ USER="${USER:-$(id -un)}" \ LOGNAME="${LOGNAME:-$(id -un)}" \ TERM="${TERM:-dumb}" \ - LANG=C.UTF-8 \ + LANG="${lang}" \ PATH="${TOOLBOX}" \ ${preserved_env[@]+"${preserved_env[@]}"} \ "${TOOLBOX}/bazel" ${bazel_argv[@]+"${bazel_argv[@]}"} From 8616be0ca96fb92a12210542d7ec5498ba6bfcee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 22:30:34 +0200 Subject: [PATCH 28/33] bazel: resolve runfiles root without hardcoding the workspace name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gemini: the lint test scripts derived RUNFILES_DIR with ${PWD%/_main}, which strips nothing when the main workspace is not named "_main" (e.g. when OpenROAD is consumed as a dependency), leaving RUNFILES_DIR pointing one level too deep. Strip the last path component with ${PWD%/*} instead — the workspace subdirectory we start in — so the .runfiles root is found regardless of the workspace name. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Øyvind Harboe --- bazel/bzl_lint_test.sh | 9 ++++++--- bazel/tcl_lint_test.sh | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/bazel/bzl_lint_test.sh b/bazel/bzl_lint_test.sh index 6b750695003..02d1cb999bc 100755 --- a/bazel/bzl_lint_test.sh +++ b/bazel/bzl_lint_test.sh @@ -11,9 +11,12 @@ GIT="$(realpath "$2")" # With rules_python's script bootstrap (bootstrap_impl=script), a py_binary # invoked via its realpath cannot locate its runfiles once we cd away. -# Export the runfiles root we start in (.runfiles/_main) so nested -# tools resolve it from the environment instead. -export RUNFILES_DIR="${RUNFILES_DIR:-${PWD%/_main}}" +# Export the runfiles root (the .runfiles directory) so nested tools +# resolve it from the environment instead. We start in the workspace +# subdirectory of it (.runfiles/); strip the last path +# component rather than a hardcoded workspace name so this also holds when +# the workspace is not "_main" (e.g. consumed as a dependency). +export RUNFILES_DIR="${RUNFILES_DIR:-${PWD%/*}}" # MODULE.bazel must be in the sh_test `data` deps so it appears as a # runfiles symlink pointing at the real workspace. `readlink` (no -f, diff --git a/bazel/tcl_lint_test.sh b/bazel/tcl_lint_test.sh index 4ca0f91dc0b..4d7313fd57c 100755 --- a/bazel/tcl_lint_test.sh +++ b/bazel/tcl_lint_test.sh @@ -11,9 +11,12 @@ GIT="$(realpath "$2")" # With rules_python's script bootstrap (bootstrap_impl=script), a py_binary # invoked via its realpath cannot locate its runfiles once we cd away. -# Export the runfiles root we start in (.runfiles/_main) so nested -# tools resolve it from the environment instead. -export RUNFILES_DIR="${RUNFILES_DIR:-${PWD%/_main}}" +# Export the runfiles root (the .runfiles directory) so nested tools +# resolve it from the environment instead. We start in the workspace +# subdirectory of it (.runfiles/); strip the last path +# component rather than a hardcoded workspace name so this also holds when +# the workspace is not "_main" (e.g. consumed as a dependency). +export RUNFILES_DIR="${RUNFILES_DIR:-${PWD%/*}}" WORKSPACE="$(dirname "$(readlink -f tclint.toml)")" cd "$WORKSPACE" From 1c303c716a404abae58a86e0062527537c2f758c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 22:38:19 +0200 Subject: [PATCH 29/33] bazel-hermetic: tolerate custom bazel wrappers; pure-bash env lookup; require HOME MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three findings from the latest Gemini pass, all on etc/bazel-hermetic: - Custom (non-bazelisk) bazel wrappers no longer hard-fail. A wrapper script starts with #!, so it entered the bazelisk-resolution block and then exited 1 when no bazelisk-managed binary was found under the cache. Now it warns and uses the wrapper directly; if the wrapper's interpreter is not on the pruned PATH the launch fails on its own, which beats refusing to run for everyone with a custom wrapper. (That error path was in fact unreachable before: the failing `cat "${meta}"` aborted the `real=` assignment under set -e; guarded with `|| true`.) - Environment preservation uses a pure-bash `eval "value=\${$var:-}"` instead of `printenv "$var"`: no subprocess per variable, and no silent loss of proxy/SSH/TLS vars if printenv is off PATH. Names come from the fixed list, never input, so eval is not an injection surface. Still bash-3.2-safe (no ${!var}/-v). - HOME uses ${HOME:?...}: fail early with a clear message if HOME is unset/empty (bazel needs it for caches and user bazelrc) rather than passing an empty HOME. Also satisfies the earlier set -u concern. Re-verified with the stub-bazel harness: normal ELF path, the bug/info paths, an empty-HOME early exit, and a non-bazelisk shebang wrapper now warning and running. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Øyvind Harboe --- etc/bazel-hermetic | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/etc/bazel-hermetic b/etc/bazel-hermetic index da5c482cac0..3af32986e71 100755 --- a/etc/bazel-hermetic +++ b/etc/bazel-hermetic @@ -109,10 +109,21 @@ if [[ ${BAZEL} == *bazelisk* || $(head -c 2 < "${BAZEL}") == '#!' ]]; then bazelisk_home=${HOME}/.cache/bazelisk fi meta="${bazelisk_home}/downloads/metadata/bazelbuild/bazel-${version}-${os}-${arch}" - real="${bazelisk_home}/downloads/sha256/$(cat "${meta}" 2>/dev/null)/bin/bazel" - [[ -x ${real} ]] || - { echo >&2 "bazel-hermetic: ${BAZEL} is a wrapper and no real bazel-${version} found under ${bazelisk_home}"; exit 1; } - BAZEL=${real} + # || true: a missing metadata file (non-bazelisk wrapper) must fall + # through to the check below, not abort the assignment under set -e. + real="${bazelisk_home}/downloads/sha256/$(cat "${meta}" 2>/dev/null || true)/bin/bazel" + if [[ -x ${real} ]]; then + BAZEL=${real} + else + # A wrapper we can't resolve to a bazelisk-managed binary — e.g. a + # custom (non-bazelisk) bazel wrapper. Use it directly rather than + # refusing to run: if its interpreter isn't on the pruned PATH the + # launch fails loudly on its own, which is better than blocking every + # non-bazelisk wrapper up front. + echo >&2 "etc/bazel-hermetic: ${BAZEL} looks like a wrapper but no" \ + "bazelisk-managed bazel-${version} was found under" \ + "${bazelisk_home}; using it directly" + fi fi ln -sfn "${BAZEL}" "${TOOLBOX}/bazel" @@ -172,12 +183,12 @@ preserved_env=() for var in http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY \ SSH_AUTH_SOCK SSL_CERT_FILE SSL_CERT_DIR XDG_CACHE_HOME TMPDIR \ DISPLAY WAYLAND_DISPLAY XAUTHORITY; do - # printenv, not ${!var} or -v: these run before the pruned exec, so keep - # them portable to the bash 3.2 that macOS ships as /bin/bash. -v needs - # bash 4.2, and indirect expansion composed with a modifier (${!var:-}) - # is unreliable on 3.2. All of these are exported environment variables, - # so printenv reads them directly and set -u never sees an unset name. - value=$(printenv "${var}" || true) + # Pure-bash lookup via eval: no subprocess (vs printenv, which also + # silently drops everything if it isn't on PATH), and no ${!var}/-v, + # which are unreliable on the bash 3.2 macOS ships. The names come from + # the fixed list above, never from input, so the eval is not an + # injection surface. ${var:-} keeps set -u quiet when the var is unset. + eval "value=\${${var}:-}" if [[ -n ${value} ]]; then preserved_env+=("${var}=${value}") fi @@ -206,9 +217,11 @@ case "$(uname -s)" in *) lang=C.UTF-8 ;; esac -# HOME:- and the array guards: same set -u / bash 3.2 safety as above. +# HOME:? fails early with a clear message if HOME is unset/empty (bazel +# needs it for its caches and user bazelrc) instead of silently passing +# an empty HOME; the array guards give the set -u / bash 3.2 safety. exec env -i \ - HOME="${HOME:-}" \ + HOME="${HOME:?etc/bazel-hermetic: HOME must be set}" \ USER="${USER:-$(id -un)}" \ LOGNAME="${LOGNAME:-$(id -un)}" \ TERM="${TERM:-dumb}" \ From 3d795c87bd371f3f525c8362192bcfc3c4369d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 22:46:15 +0200 Subject: [PATCH 30/33] bazel-hermetic: preserve bazelisk/token env; robust id -un fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two findings from the latest Gemini pass: - Preserve BAZELISK_HOME, BAZELISK_BASE_URL, BAZELISK_GITHUB_TOKEN and GITHUB_TOKEN through the pruned env. These are cache-location / mirror / auth configuration (same class as the proxy and TLS vars already passed), and they matter when the resolved bazel is a bazelisk wrapper used directly via the fallback path: a custom cache dir, an enterprise mirror, or GitHub rate-limit tokens would otherwise be lost. - USER/LOGNAME fall back to `id -un 2>/dev/null || echo nobody`. In a minimal container running under an arbitrary UID with no /etc/passwd entry, `id -un` fails; under set -e the bare `$(id -un)` aborted the exec. Verified with a stubbed failing id: the script now completes. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Øyvind Harboe --- etc/bazel-hermetic | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/etc/bazel-hermetic b/etc/bazel-hermetic index 3af32986e71..ba4eaf5f7f6 100755 --- a/etc/bazel-hermetic +++ b/etc/bazel-hermetic @@ -178,11 +178,14 @@ esac # Network, auth, TLS and cache/tmp configuration is not toolchain leakage: # downloads must work behind corporate proxies, custom CAs, and git-over-ssh -# overrides, and users may relocate caches and tmp. +# overrides, and users may relocate caches and tmp. The BAZELISK_* / token +# vars matter when the resolved bazel is a bazelisk wrapper used directly +# (custom cache location, enterprise mirror, GitHub rate limits). preserved_env=() for var in http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY \ SSH_AUTH_SOCK SSL_CERT_FILE SSL_CERT_DIR XDG_CACHE_HOME TMPDIR \ - DISPLAY WAYLAND_DISPLAY XAUTHORITY; do + DISPLAY WAYLAND_DISPLAY XAUTHORITY \ + BAZELISK_HOME BAZELISK_BASE_URL BAZELISK_GITHUB_TOKEN GITHUB_TOKEN; do # Pure-bash lookup via eval: no subprocess (vs printenv, which also # silently drops everything if it isn't on PATH), and no ${!var}/-v, # which are unreliable on the bash 3.2 macOS ships. The names come from @@ -222,8 +225,8 @@ esac # an empty HOME; the array guards give the set -u / bash 3.2 safety. exec env -i \ HOME="${HOME:?etc/bazel-hermetic: HOME must be set}" \ - USER="${USER:-$(id -un)}" \ - LOGNAME="${LOGNAME:-$(id -un)}" \ + USER="${USER:-$(id -un 2>/dev/null || echo nobody)}" \ + LOGNAME="${LOGNAME:-$(id -un 2>/dev/null || echo nobody)}" \ TERM="${TERM:-dumb}" \ LANG="${lang}" \ PATH="${TOOLBOX}" \ From 5f9a5d5e35db22478d73af4c5638fe6cdd8d17fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 22:54:46 +0200 Subject: [PATCH 31/33] bazel: bison patch resolves repo name with repo_name(), not hardcoded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gemini flagged the hardcoded `external/bison+/lib` canonical repo name as non-portable (Bazel 7 uses `~`, Bazel 8 `+`). Correct in principle — this repo pins Bazel 8.6.0 so `+` is right for what ships, but the patch is headed upstream (bison@3.8.2.bcr.8) where it must survive both. Gemini's suggested `native.repo_name()` does NOT work here: `native` is undefined at BUILD-file top level (verified: "name 'native' is not defined"), and BISON_COPTS is a top-level assignment. The bare builtin `repo_name()` is the one callable there — verified in a scratch Bazel 8.6.0 module that it returns the canonical name with the separator (`depb+`), so bison resolves to `bison+`, byte-identical to the previous hardcoding on Bazel 8 and correct on Bazel 7.1+. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Øyvind Harboe --- .../0001-gnulib-wrapper-headers-use-I-not-isystem.patch | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bazel/bison-patches/0001-gnulib-wrapper-headers-use-I-not-isystem.patch b/bazel/bison-patches/0001-gnulib-wrapper-headers-use-I-not-isystem.patch index 01d22abe776..feb39111526 100644 --- a/bazel/bison-patches/0001-gnulib-wrapper-headers-use-I-not-isystem.patch +++ b/bazel/bison-patches/0001-gnulib-wrapper-headers-use-I-not-isystem.patch @@ -37,8 +37,8 @@ diff --git a/BUILD.bazel b/BUILD.bazel +# listed as both -I and -isystem stays -isystem, so the -isystem form must +# go entirely). This restores autotools' AM_CPPFLAGS = -I./lib ordering. +BISON_COPTS = [ -+ "-Iexternal/bison+/lib", -+ "-I$(BINDIR)/external/bison+/lib", ++ "-Iexternal/" + repo_name() + "/lib", ++ "-I$(BINDIR)/external/" + repo_name() + "/lib", +] + select({ "@rules_cc//cc/compiler:clang": [ "-w", From b61f9d3401a579506ba0c79bb656a433d22aa070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 22:54:46 +0200 Subject: [PATCH 32/33] bazel-hermetic: read builtin for signature; indirect env lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two style/robustness findings from the latest Gemini pass: - Read the bazel signature with `read -r -n 2` instead of a `head` subprocess; the redirection still follows the symlink via open(), so no head/realpath/readlink is involved. Verified it reads the ELF magic through a symlink and matches "#!" for a shebang wrapper. - Preserve environment via pure-bash indirect expansion: `${!var+x}` tests whether the named variable is set (set -u-safe) and `${!var}` reads it — no eval, no subprocess. Full stub-bazel harness still green: build/repo_env, bug-case exit, info passthrough, and the non-bazelisk-wrapper fallback. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Øyvind Harboe --- etc/bazel-hermetic | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/etc/bazel-hermetic b/etc/bazel-hermetic index ba4eaf5f7f6..0639c4ea56b 100755 --- a/etc/bazel-hermetic +++ b/etc/bazel-hermetic @@ -91,10 +91,12 @@ if [[ -z ${BAZEL} ]]; then fi # Wrapper detection: the bazelisk launcher by name, or any script shebang # (npm/shell wrappers). Native binaries (ELF on Linux, Mach-O on macOS) -# are symlinked directly. Read via redirection: open() follows the -# symlink for us, so no realpath/readlink -f is needed (neither is -# reliably present on macOS). -if [[ ${BAZEL} == *bazelisk* || $(head -c 2 < "${BAZEL}") == '#!' ]]; then +# are symlinked directly. Read the first two bytes with the `read` +# builtin (no head subprocess); the redirection's open() follows the +# symlink for us, so no realpath/readlink -f is needed either. +sig="" +read -r -n 2 sig < "${BAZEL}" 2>/dev/null || true +if [[ ${BAZEL} == *bazelisk* || ${sig} == '#!' ]]; then version=$(BAZELISK_SKIP_WRAPPER=1 "${BAZEL}" --version | awk '{print $2}') os=$(uname -s | tr '[:upper:]' '[:lower:]') arch=$(uname -m | sed 's/aarch64/arm64/') @@ -186,14 +188,14 @@ for var in http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY \ SSH_AUTH_SOCK SSL_CERT_FILE SSL_CERT_DIR XDG_CACHE_HOME TMPDIR \ DISPLAY WAYLAND_DISPLAY XAUTHORITY \ BAZELISK_HOME BAZELISK_BASE_URL BAZELISK_GITHUB_TOKEN GITHUB_TOKEN; do - # Pure-bash lookup via eval: no subprocess (vs printenv, which also - # silently drops everything if it isn't on PATH), and no ${!var}/-v, - # which are unreliable on the bash 3.2 macOS ships. The names come from - # the fixed list above, never from input, so the eval is not an - # injection surface. ${var:-} keeps set -u quiet when the var is unset. - eval "value=\${${var}:-}" - if [[ -n ${value} ]]; then - preserved_env+=("${var}=${value}") + # Pure-bash indirect lookup, no subprocess and no eval: ${!var+x} tests + # whether the variable named by $var is set (set -u-safe), then ${!var} + # reads its value. + if [[ -n ${!var+x} ]]; then + value=${!var} + if [[ -n ${value} ]]; then + preserved_env+=("${var}=${value}") + fi fi done From 74eca7d537e3969159361e575583954699d8d4db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Tue, 7 Jul 2026 23:02:49 +0200 Subject: [PATCH 33/33] bazel-hermetic: preserve cloud credential env for remote cache auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gemini: env -i strips the credentials bazel needs to authenticate to cloud remote-cache / remote-execution backends (GCS, S3), so a build that relies on the remote cache would fail to authenticate. Preserve GOOGLE_APPLICATION_CREDENTIALS and the common AWS_* variables alongside the proxy/TLS/bazelisk config already passed through — same rationale: auth/cache configuration is not toolchain leakage. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Øyvind Harboe --- etc/bazel-hermetic | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/etc/bazel-hermetic b/etc/bazel-hermetic index 0639c4ea56b..73a995de5a4 100755 --- a/etc/bazel-hermetic +++ b/etc/bazel-hermetic @@ -182,12 +182,18 @@ esac # downloads must work behind corporate proxies, custom CAs, and git-over-ssh # overrides, and users may relocate caches and tmp. The BAZELISK_* / token # vars matter when the resolved bazel is a bazelisk wrapper used directly -# (custom cache location, enterprise mirror, GitHub rate limits). +# (custom cache location, enterprise mirror, GitHub rate limits). The cloud +# credential vars keep remote cache/execution auth (GCS, S3) working under +# env -i. preserved_env=() for var in http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY \ SSH_AUTH_SOCK SSL_CERT_FILE SSL_CERT_DIR XDG_CACHE_HOME TMPDIR \ DISPLAY WAYLAND_DISPLAY XAUTHORITY \ - BAZELISK_HOME BAZELISK_BASE_URL BAZELISK_GITHUB_TOKEN GITHUB_TOKEN; do + BAZELISK_HOME BAZELISK_BASE_URL BAZELISK_GITHUB_TOKEN GITHUB_TOKEN \ + GOOGLE_APPLICATION_CREDENTIALS \ + AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN \ + AWS_ROLE_ARN AWS_WEB_IDENTITY_TOKEN_FILE AWS_SHARED_CREDENTIALS_FILE \ + AWS_PROFILE AWS_CONFIG_FILE AWS_DEFAULT_REGION AWS_REGION; do # Pure-bash indirect lookup, no subprocess and no eval: ${!var+x} tests # whether the variable named by $var is set (set -u-safe), then ${!var} # reads its value.