Skip to content

Commit ea8c191

Browse files
authored
Preserve LLVM archive alternatives and add feature changelog (#878)
Fix the remaining xz/zstd mismatch in the single-release distribution helper and add a history-backed changelog. # AG;DR ## Fix #870 changed the Python updater to retain all archive formats and made the toolchain selector exclude zstd until Bazel can extract LLVM's large-window archives. The shell helper still had #836's old filter, which discarded xz whenever the same archive was available as zstd. - Retain xz, zstd, and gzip archives and their individual checksums in `extra_distributions.sh`. - Leave extraction compatibility decisions in the toolchain selector; this does not enable zstd selection. - Correct the README's stale zstd-preference claim, updater scope, and dependencies. ## Changelog Add `CHANGELOG.md` with release headings and bullets, newest first. Historical feature introductions are checked against the first containing release tags; current changes are grouped under the version heading `1.11.0`. Distinguish earlier functionality from later enhancements, including Linux versus macOS ThinLTO, initial versus expanded dynamic-libstdc++ support, and MSan introduction versus correctness fixes. ## Regression coverage - Execute the real shell helper against offline release metadata, allowing only the metadata request and rejecting unexpected archive downloads. - Cover stable and prerelease versions, both LLVM naming schemes, xz/zstd/gzip alternatives and individual formats, exact checksum preservation, unrelated-asset filtering, and no-match errors. - Extend the Python updater test to preserve all three formats and checksums. - Run both script suites in a Linux/macOS CI job and include it in `required_checks_done.needs`. ## Verification - Demonstrated the new shell regression fails before the fix in all four stable/prerelease and naming-scheme combinations, specifically because xz is missing. - `python3 -m unittest discover -s utils -p '*_test.py' -v`: all 8 tests pass on macOS. - `USE_BAZEL_VERSION=9.0.0 bazel test //... --test_output=errors --nocache_test_results`: all 21 tests executed and passed on the rebased head. - `bash -n` for both shell entry points. - Repository lint/push hooks, `git diff --check`, and YAML validation that the required-check gate includes every declared job and has no unknown dependencies. Fresh CI: [Tests run 36820892858](https://github.com/bazel-contrib/toolchains_llvm/actions/runs/36820892858). Protected squash auto-merge is enabled; this is the final PR in the #873 → #875 → #876 → #877 → #878 sequence. The changelog heading remains `## 1.11.0`.
1 parent 50d8da8 commit ea8c191

7 files changed

Lines changed: 313 additions & 26 deletions

File tree

‎.github/workflows/tests.yml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ jobs:
2323
uses: actions/checkout@v7
2424
- name: Trunk Check
2525
uses: trunk-io/trunk-action@v2.0.0
26+
distribution_scripts_test:
27+
strategy:
28+
fail-fast: false
29+
matrix:
30+
os: [macos-latest, ubuntu-latest]
31+
runs-on: ${{ matrix.os }}
32+
steps:
33+
- uses: actions/checkout@v7
34+
- name: Test distribution scripts without network downloads
35+
run: python3 -m unittest discover -s utils -p '*_test.py' -v
2636
test:
2737
strategy:
2838
fail-fast: false
@@ -336,6 +346,7 @@ jobs:
336346
name: required_checks_done
337347
needs:
338348
- lint
349+
- distribution_scripts_test
339350
- test
340351
- lsan_macos_test
341352
- cpp_modules_test

‎CHANGELOG.md‎

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Changelog
2+
3+
Versions below refer to **toolchains_llvm**, not LLVM or Bazel. Historical
4+
entries record notable user-facing feature introductions, checked against
5+
the first release tag containing their implementation. They are not an
6+
exhaustive list of fixes, dependency updates, or newly catalogued LLVM archives.
7+
See the [GitHub releases](https://github.com/bazel-contrib/toolchains_llvm/releases)
8+
for release artifacts and the [README](README.md) for configuration and requirements.
9+
10+
## 1.11.0
11+
12+
### Added
13+
14+
- Generic, checksum-pinned downloadable linkers selected by name through
15+
`linker`, with `linker_version` / `linker_versions` supporting exact versions
16+
and `first` / `latest` requirements. The built-in catalogue initially contains
17+
mold; other linker names can be supplied through inline or JSONC catalogues.
18+
- Linker catalogue controls, including custom entries, disabling the built-in
19+
catalogue, and the `linker_features` capability list.
20+
- Optional mold version inference from an explicitly injected mold Bazel
21+
module. Depending on that module is not required to use a catalogued mold
22+
release with an explicit version or requirement.
23+
- Feature-based toolchain overrides with signed predicates such as
24+
`features = ["thin_lto", "-msan"]`, optional target filters, default fallback,
25+
ambiguity checks, and configurable merge/reset behavior. Matching uses
26+
configuration-level `--features` / `--host_features`, not rule-local features.
27+
- Linker-managed macOS ThinLTO through configuration-level `--features=thin_lto`,
28+
including independent `--host_features` handling for build tools. Linux's
29+
existing distributed ThinLTO path is unchanged.
30+
- Python-based distribution updater that refreshes both the LLVM catalogue and
31+
the mold catalogue, retaining the BCR mold version and the three newest stable
32+
releases. `utils/update_distributions.sh` remains the shell entry point.
33+
34+
### Fixed
35+
36+
- Retain xz, zstd, and gzip archive metadata, but automatically select only xz
37+
or gzip until Bazel can extract LLVM's large-window zstd archives. Both
38+
distribution helper scripts now retain all formats instead of dropping xz
39+
when an equivalent zstd asset exists.
40+
- Warn when LLVM 23 on macOS uses its bundled linker with the known SDK/TAPI
41+
incompatibility, suggesting the native `auto` linker override.
42+
- Include `libLTO.dylib` in Apple linker sandbox inputs and omit redundant `-lm`
43+
on macOS.
44+
- Apply MSan instrumentation to C compilation as well as C++, and fail loudly
45+
when MSan is requested on an unsupported target, including macOS.
46+
- Correct the README's archive-selection policy and distribution updater
47+
dependencies; exercise both distribution scripts in Linux/macOS CI.
48+
49+
## [1.10.0](https://github.com/bazel-contrib/toolchains_llvm/tree/v1.10.0)
50+
51+
- Configurable linker selection: empty/default for the linker bundled with
52+
LLVM, `auto` for the execution platform's native linker, or an explicit
53+
absolute path.
54+
55+
## [1.9.1](https://github.com/bazel-contrib/toolchains_llvm/tree/v1.9.1)
56+
57+
- Experimental C++ named modules, including `clang-scan-deps` integration.
58+
59+
## [1.9.0](https://github.com/bazel-contrib/toolchains_llvm/tree/v1.9.0)
60+
61+
- Explicit LLVM prerelease selection and prerelease-aware requirements.
62+
- External JSONC distribution files and disabling the built-in LLVM catalogue.
63+
- Direct zstd archive recognition.
64+
- LeakSanitizer integration.
65+
66+
## [1.8.0](https://github.com/bazel-contrib/toolchains_llvm/tree/v1.8.0)
67+
68+
- JSONC distribution buckets and the named distribution helper scripts.
69+
- Per-target toolchain roots through `target_toolchain_roots`.
70+
- `extra_linker_files` and per-target extra-file extension tags.
71+
- Expanded dynamic-libstdc++/Yocto sysroot layouts and SDK libc++ on Darwin.
72+
- MSan with an instrumented libc++ overlay and the current sanitizer feature
73+
integration.
74+
- Bare-metal RISC-V 64-bit targets.
75+
76+
## [1.7.0](https://github.com/bazel-contrib/toolchains_llvm/tree/v1.7.0)
77+
78+
- Custom rules-based C++ features via `extra_known_features` and
79+
`extra_enabled_features`.
80+
- RISC-V 64-bit Linux targets.
81+
82+
## [1.6.0](https://github.com/bazel-contrib/toolchains_llvm/tree/v1.6.0)
83+
84+
- `first` / `latest` LLVM version requirements and environment-derived version
85+
requirements.
86+
- Additive `extra_*` flag attributes.
87+
88+
## [1.5.0](https://github.com/bazel-contrib/toolchains_llvm/tree/v1.5.0)
89+
90+
- `extra_llvm_distributions`, including full URL/path entries.
91+
- Header parsing and fastbuild compiler flags.
92+
- Bare-metal x86-64 and RISC-V 32-bit targets.
93+
94+
## [1.4.0](https://github.com/bazel-contrib/toolchains_llvm/tree/v1.4.0)
95+
96+
- More convenience tool aliases.
97+
- WebAssembly WASI Preview 1 and Linux ARMv7 targets.
98+
99+
## [1.3.0](https://github.com/bazel-contrib/toolchains_llvm/tree/v1.3.0)
100+
101+
- Initial `dynamic-stdc++` support.
102+
- C-only compiler flags.
103+
- Additional exported Clang tools/libraries.
104+
- WebAssembly `wasm32-unknown-unknown` and `wasm64-unknown-unknown` targets.
105+
106+
## [1.2.0](https://github.com/bazel-contrib/toolchains_llvm/tree/v1.2.0)
107+
108+
- Custom platform constraints and optional libunwind linking.
109+
110+
## [1.1.0](https://github.com/bazel-contrib/toolchains_llvm/tree/v1.1.0)
111+
112+
- `llvm-profdata` support for coverage data merging.
113+
114+
## [1.0.0](https://github.com/bazel-contrib/toolchains_llvm/tree/1.0.0)
115+
116+
- Strict header dependencies through `layering_check`.
117+
- Additional compiler sandbox inputs through `extra_compiler_files`.
118+
- Explicit execution-platform configuration distinct from the configuring host.
119+
120+
## [0.9](https://github.com/bazel-contrib/toolchains_llvm/tree/0.9)
121+
122+
- Bzlmod integration.
123+
- Per-host LLVM versions through `llvm_versions`.
124+
125+
## [0.8.1](https://github.com/bazel-contrib/toolchains_llvm/tree/0.8.1)
126+
127+
- Apple Silicon host support using LLVM 15 distributions.
128+
129+
## [0.8](https://github.com/bazel-contrib/toolchains_llvm/tree/0.8)
130+
131+
- `target_settings` for toolchain selection.
132+
- An `llvm-symbolizer` target.
133+
134+
## [0.7](https://github.com/bazel-contrib/toolchains_llvm/tree/0.7)
135+
136+
- Per-platform download URLs/checksums and mirror configuration.
137+
- C++ standard and standard-library selection, and flag overrides.
138+
- Convenience `clang-format`, `llvm-cov`, and `omp` targets.
139+
140+
## [0.6](https://github.com/bazel-contrib/toolchains_llvm/tree/0.6)
141+
142+
- Bring Your Own LLVM through `toolchain_roots` and a separate LLVM distribution
143+
repository.
144+
- Target-aware cross-compilation and AArch64 support.
145+
- Authenticated downloads.
146+
- Inheritance of Bazel's Unix toolchain configuration, including Linux ThinLTO.
147+
148+
## [0.4.4](https://github.com/bazel-contrib/toolchains_llvm/tree/0.4.4)
149+
150+
- Toolchain registration support.
151+
152+
## [0.4](https://github.com/bazel-contrib/toolchains_llvm/tree/0.4)
153+
154+
- Custom sysroots.
155+
156+
## [0.2](https://github.com/bazel-contrib/toolchains_llvm/tree/0.2)
157+
158+
- Automatic OS/distribution detection and explicit distribution selection.
159+
160+
## [0.1](https://github.com/bazel-contrib/toolchains_llvm/tree/0.1)
161+
162+
- Initial WORKSPACE-based LLVM toolchain.
163+
- Explicit LLVM version selection.
164+
- Absolute-path/sandbox configuration.
165+
166+
## Interpreting availability
167+
168+
- Introduction does not imply support for every Bazel version, LLVM release,
169+
platform, or later-added option. Consult the current README for requirements.
170+
- Dynamic libstdc++ first appeared in 1.3.0; its expanded sysroot/Yocto support
171+
arrived in 1.8.0.
172+
- MSan's instrumented-libc++ integration dates to 1.8.0. The C-instrumentation
173+
and unsupported-target fixes above do not make MSan a new 1.11.0 feature.
174+
- Linux ThinLTO is inherited from Bazel/rules_cc, so its behavior also depends
175+
on those versions. The 1.11.0 addition is the macOS integration.
176+
- Native and explicit-path linker selection shipped in 1.10.0. Downloadable
177+
catalogued linkers are a separate addition in 1.11.0.
178+
- Zstd recognition arrived in 1.9.0; it does not guarantee that Bazel can
179+
extract every zstd archive. The 1.11.0 compatibility fix deliberately
180+
excludes zstd from automatic selection while retaining its metadata.

‎README.md‎

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
## Quickstart
44

55
See notes on the [release](https://github.com/bazel-contrib/toolchains_llvm/releases)
6-
for how to get started.
6+
for how to get started, and the [changelog](CHANGELOG.md) for feature availability.
77

88
NOTE: For releases prior to 0.10.1, please also see [these notes](REPO_RENAME.md).
99

@@ -866,17 +866,21 @@ Entry keys are either a tarball **basename** (URL derived via `base_url`)
866866
or a **full URL/path** (used verbatim, bypassing `base_url`). Comments are
867867
stripped before parsing, and trailing commas are tolerated.
868868

869-
LLVM archives compressed as `.tar.zst`, `.tar.xz`, or `.tar.gz` are
870-
supported. When equivalent zstd and xz archives are available, automatic
871-
selection and the helper scripts prefer the much faster-to-extract zstd form.
869+
The distribution data and both helper scripts retain `.tar.zst`, `.tar.xz`,
870+
and `.tar.gz` archives with their checksums. Automatic selection currently
871+
uses only xz or gzip: released Bazel versions cannot unpack LLVM 23's
872+
large-window zstd archives. Retaining zstd metadata does not enable its use;
873+
archive compatibility is decided by the toolchain selector, not the scripts.
872874

873875
### Two helper scripts
874876

875-
- **`utils/update_distributions.sh`** — refreshes
876-
[`toolchain/distributions/github.jsonc`](toolchain/distributions/github.jsonc)
877-
by paging through the GitHub releases API for `llvm/llvm-project` and
878-
rewriting the file in place. Use this when contributing a new LLVM release
879-
to the bundled list. The script also regenerates the test golden file so
877+
- **`utils/update_distributions.sh`** — invokes `utils/update_distributions.py`
878+
to refresh [`github.jsonc`](toolchain/distributions/github.jsonc) from LLVM
879+
releases and [`linkers.jsonc`](toolchain/distributions/linkers.jsonc) from
880+
mold releases. The linker catalogue includes the mold version in the Bazel
881+
Central Registry and the three newest stable mold releases. Use this when
882+
contributing new releases to the bundled lists.
883+
The script also regenerates the distribution test golden files so
880884
the diff stays self-contained. No tarballs are downloaded — checksums come
881885
from GitHub's release-asset `.digest` field, with existing values
882886
preserved for older assets that predate that field. Set `GITHUB_TOKEN` to
@@ -890,10 +894,13 @@ selection and the helper scripts prefer the much faster-to-extract zstd form.
890894
tarballs and computing SHA-256 locally for older assets that don't have a
891895
`.digest`. See `-h` for details.
892896

893-
Both scripts run on Linux, macOS, and on Windows under Git Bash / MSYS2 /
894-
WSL. They require `bash`, `curl`, `jq`, and `awk`;
895-
`utils/extra_distributions.sh` additionally needs `sha256sum` (Linux, Git
896-
Bash) or `shasum` (macOS).
897+
The shell entry points run on Linux, macOS, and on Windows under Git Bash /
898+
MSYS2 / WSL. The updater requires Python 3.9 or newer and `bazel` to regenerate
899+
goldens; its shell wrapper additionally requires `bash`. It uses `gh` for
900+
authentication when available and `GITHUB_TOKEN` is unset, and optionally uses
901+
Trunk or the repository's Prettier launcher for formatting.
902+
`utils/extra_distributions.sh` requires `bash`, `curl`, `jq`, `awk`, and either
903+
`sha256sum` (Linux, Git Bash) or `shasum` (macOS).
897904

898905
## Prior Art
899906

‎utils/BUILD.bazel‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
load("@rules_python//python:defs.bzl", "py_test")
22

3+
py_test(
4+
name = "extra_distributions_test",
5+
srcs = ["extra_distributions_test.py"],
6+
data = ["extra_distributions.sh"],
7+
)
8+
39
py_test(
410
name = "update_distributions_test",
511
srcs = [

‎utils/extra_distributions.sh‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,18 +92,13 @@ curl "${curl_args[@]}" \
9292
>"${release_json}"
9393

9494
# Asset table: name <TAB> digest (may be empty) <TAB> download URL.
95+
# Keep every compression format, just like update_distributions.py. Archive
96+
# compatibility is decided by the toolchain selector, not this data collector.
9597
assets_tsv="${tmp_dir}/assets.tsv"
9698
jq -r --arg version "${llvm_version}" '
97-
.assets as $assets
98-
| .assets[]
99+
.assets[]
99100
| select(.name | test("^(clang[+]llvm|LLVM)-.*tar[.](zst|xz|gz)$"))
100101
| select(.name | contains("-" + $version + "-"))
101-
| .name as $name
102-
| select(
103-
($name | endswith(".tar.xz") | not) or
104-
(($name | sub("[.]tar[.]xz$"; ".tar.zst")) as $zstd_name |
105-
($assets | any(.name == $zstd_name) | not))
106-
)
107102
| [.name, ((.digest // "") | sub("^sha256:"; "")), .browser_download_url] | @tsv
108103
' "${release_json}" >"${assets_tsv}"
109104

‎utils/extra_distributions_test.py‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""Exercise the real extra-distributions helper without network downloads."""
2+
3+
import ast
4+
import json
5+
import os
6+
from pathlib import Path
7+
import subprocess
8+
import tempfile
9+
import unittest
10+
11+
12+
class ExtraDistributionsTest(unittest.TestCase):
13+
def run_helper(self, version, assets):
14+
with tempfile.TemporaryDirectory(
15+
prefix="llvm-extra-distributions-", dir=os.environ.get("TEST_TMPDIR")
16+
) as directory:
17+
root = Path(directory)
18+
metadata = root / "metadata.json"
19+
metadata.write_text(json.dumps({"assets": assets}))
20+
curl = root / "curl"
21+
# Only the release metadata request is allowed. Any attempt to
22+
# download an archive fails instead of reaching the network.
23+
curl.write_text("""#!/bin/sh
24+
for arg in "$@"; do url="$arg"; done
25+
if [ "$url" != "$LLVM_TEST_RELEASE_URL" ]; then
26+
echo "Unexpected download: $url" >&2
27+
exit 1
28+
fi
29+
cat "$LLVM_TEST_RELEASE_JSON"
30+
""")
31+
curl.chmod(0o755)
32+
env = dict(os.environ)
33+
env.pop("GITHUB_TOKEN", None)
34+
env.update({
35+
"PATH": str(root) + os.pathsep + os.environ.get("PATH", os.defpath),
36+
"LLVM_TEST_RELEASE_URL": "https://api.github.com/repos/llvm/llvm-project/releases/tags/llvmorg-" + version,
37+
"LLVM_TEST_RELEASE_JSON": str(metadata),
38+
})
39+
return subprocess.run(
40+
["bash", str(Path(__file__).with_name("extra_distributions.sh")),
41+
"-t", str(root / "output"), "-v", version],
42+
env=env, capture_output=True, text=True, check=False, timeout=30,
43+
)
44+
45+
def assert_archives(self, version, prefix, suffixes):
46+
names = [prefix + ".tar." + suffix for suffix in suffixes]
47+
expected = {name: format(index, "064x") for index, name in enumerate(names, start=1)}
48+
assets = [
49+
{"name": name, "digest": "sha256:" + digest,
50+
"browser_download_url": "https://example.invalid/" + name}
51+
for name, digest in expected.items()
52+
]
53+
# Neither source archives nor binaries for another version belong in
54+
# the output, even when GitHub attaches them to the requested release.
55+
assets.extend({"name": name} for name in [
56+
"llvm-project-" + version + ".src.tar.xz",
57+
"LLVM-99.0.0-Linux-X64.tar.xz",
58+
])
59+
result = self.run_helper(version, assets)
60+
self.assertEqual(result.returncode, 0, result.stderr)
61+
self.assertEqual(ast.literal_eval("{\n" + result.stdout + "\n}"), expected)
62+
63+
def test_keeps_all_compressions_and_checksums(self):
64+
for version in ["23.1.2", "23.1.0-rc3"]:
65+
for prefix in [
66+
"LLVM-" + version + "-Linux-X64",
67+
"clang+llvm-" + version + "-x86_64-linux-gnu",
68+
]:
69+
with self.subTest(version=version, prefix=prefix):
70+
self.assert_archives(version, prefix, ["xz", "zst", "gz"])
71+
72+
def test_keeps_single_compression(self):
73+
for suffix in ["xz", "zst", "gz"]:
74+
with self.subTest(suffix=suffix):
75+
self.assert_archives("23.1.2", "LLVM-23.1.2-Linux-X64", [suffix])
76+
77+
def test_no_matching_archives_fails(self):
78+
result = self.run_helper("23.1.2", [])
79+
self.assertNotEqual(result.returncode, 0)
80+
self.assertIn("no matching tarball assets found", result.stderr)
81+
82+
83+
if __name__ == "__main__":
84+
unittest.main()

0 commit comments

Comments
 (0)