Skip to content

Implement latest requirements #472

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
baca9d1
Add more missing distributions (all .gz).
helly25 Mar 13, 2025
ffc2fe5
Add the ne platforms test to the actions release workflow
helly25 Mar 13, 2025
e1c923d
Better naming: should be toolchain_tests not platform_tests
helly25 Mar 13, 2025
606b5e3
No switching directories for the new test
helly25 Mar 13, 2025
bfaa5de
Use var array `test_args` to pass around the erquired flag.
helly25 Mar 13, 2025
8eed849
Add ability to specify `llvm_version/s` as (semver) requirements.
helly25 Mar 15, 2025
594e53c
Add a requirements test and disable the tests in workspace mode as th…
helly25 Mar 15, 2025
e2ffc9b
Make this work for workspaces
helly25 Mar 15, 2025
9d83ae4
The dependency also needs to go into deps.bzl so run_tests.bzl works.
helly25 Mar 15, 2025
e5810a0
Update (redo) after sync.
helly25 May 11, 2025
baf774a
Example update#
helly25 May 11, 2025
1eea0e6
Readme update.
helly25 May 11, 2025
03d2540
Update configure and tests.
helly25 May 11, 2025
2c4f855
Use better requiement identification that is in line with the actual …
helly25 May 11, 2025
71912b3
Updates & tweaks
helly25 May 11, 2025
01e888e
Ensure common_test_args are present
helly25 May 11, 2025
313c56f
one test is not always supported.
helly25 May 11, 2025
d38fda7
Small improvements. Pre-filter eligible versions and ensure actual co…
helly25 May 12, 2025
d28ef85
Attribute `extra_llvm_distributions` must be a common attribute.
helly25 May 12, 2025
5bd90e9
sync
helly25 May 16, 2025
01cff84
Limit test to 20.1.3 to prevent unnecessary diffs.
helly25 May 16, 2025
01814d4
sync
helly25 Jun 5, 2025
83dc627
No more llvm_distributions_select_no_error_test
helly25 Jun 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,19 @@ jobs:
USE_BZLMOD: ${{ matrix.bzlmod }}
run: tests/scripts/run_tests.sh
toolchain_test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest]
bazel_version: [7.x, latest]
bzlmod: [true, false]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Test
env:
USE_BAZEL_VERSION: latest
USE_BZLMOD: true
USE_BAZEL_VERSION: ${{ matrix.bazel_version }}
USE_BZLMOD: ${{ matrix.bzlmod }}
run: tests/scripts/run_toolchain_tests.sh
external_test:
strategy:
Expand Down
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module(
bazel_dep(name = "bazel_skylib", version = "1.5.0")
bazel_dep(name = "rules_cc", version = "0.0.17")
bazel_dep(name = "platforms", version = "0.0.8")
bazel_dep(name = "helly25_bzl", version = "0.1.2")

# TODO: Remove when protobuf is released with a version of rules_python that supports 8.x
bazel_dep(name = "rules_python", version = "1.0.0", dev_dependency = True)
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,25 @@ See [bazel
tutorial](https://docs.bazel.build/versions/main/tutorial/cc-toolchain-config.html)
for how CC toolchains work in general.

### Requirements

Version attributes can be requirements of the form `first`, `first:<condition>`,
`latest` or `latest:<condition>`.

In case of `latest`, the latest distribution matching the optional `condition`
will be selected.

In case of `first`, the first distribution matching the optional `condition`
will be selected.

The condition consists of a comma separated list of semver version comparisons
supporting `<`, `<=`, `>`, `>=`, `==`, `!=`. Examples:

- `latest`
- `latest:>=20.1.0`
- `latest:>17.0.4,!=19.1.7,<=20.1.0`
- `first:>=15.0.6,<16`

### Selecting Toolchains

If toolchains are registered (see Quickstart section above), you do not need to
Expand Down
9 changes: 9 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,12 @@
workspace(
name = "toolchains_llvm",
)

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
name = "helly25_bzl",
sha256 = "404f8473bcaad2e370752e57d274d2093eb87ca94cb9a597c1a3553b76743206",
strip_prefix = "bzl-0.1.2",
url = "https://github.com/helly25/bzl/releases/download/0.1.2/bzl-0.1.2.tar.gz",
)
6 changes: 5 additions & 1 deletion tests/scripts/run_toolchain_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,9 @@ targets=(
"//toolchain/..."
)

if [[ -z "${common_test_args:-}" ]]; then
common_test_args=()
fi

"${bazel}" ${TEST_MIGRATION:+"--strict"} --bazelrc=/dev/null test \
"${common_test_args[@]}" "${test_args[@]}" "${targets[@]}"
"${common_test_args[@]}" "${test_args[@]}" -- "${targets[@]}"
10 changes: 9 additions & 1 deletion toolchain/deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,12 @@ def bazel_toolchain_dependencies():
sha256 = "bc283cdfcd526a52c3201279cda4bc298652efa898b10b4db0837dc51652756f",
)

# Skip bazel_skylib_workspace because we are not using lib/unittest.bzl
# Skip bazel_skylib_workspace because we are not using lib/unittest.bzl

if not native.existing_rule("helly25_bzl"):
http_archive(
name = "helly25_bzl",
strip_prefix = "bzl-0.1.2",
url = "https://github.com/helly25/bzl/releases/download/0.1.2/bzl-0.1.2.tar.gz",
sha256 = "404f8473bcaad2e370752e57d274d2093eb87ca94cb9a597c1a3553b76743206",
)
17 changes: 15 additions & 2 deletions toolchain/internal/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
# limitations under the License.

load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
load("llvm_distributions.bzl", "write_distributions")
load("llvm_distributions.bzl", "distributions_test_writer", "requirements_test_writer")

exports_files(["template.modulemap"])

write_distributions(
distributions_test_writer(
name = "llvm_distributions",
testonly = True,
output = "llvm_distributions.out.txt",
Expand All @@ -36,3 +36,16 @@ diff_test(
file1 = "llvm_distributions.golden.sel.txt",
file2 = "llvm_distributions.sel.txt",
)

requirements_test_writer(
name = "llvm_requirements_test_output",
testonly = True,
result = "llvm_requirements_test.output.txt",
visibility = ["//visibility:private"],
)

diff_test(
name = "llvm_requirements_test",
file1 = "llvm_requirements_test.golden.txt",
file2 = "llvm_requirements_test.output.txt",
)
10 changes: 10 additions & 0 deletions toolchain/internal/configure.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ load(
_supported_targets = "SUPPORTED_TARGETS",
_toolchain_tools = "toolchain_tools",
)
load("//toolchain/internal:llvm_distributions.bzl", "is_requirement", "required_llvm_release_name_rctx")
load(
"//toolchain/internal:sysroot.bzl",
_default_sysroot_path = "default_sysroot_path",
Expand Down Expand Up @@ -80,6 +81,15 @@ def llvm_config_impl(rctx):
if not toolchain_root:
fail("LLVM toolchain root missing for ({}, {})".format(os, arch))
(_key, llvm_version) = _exec_os_arch_dict_value(rctx, "llvm_versions")
if is_requirement(llvm_version):
llvm_version, distribution, error = required_llvm_release_name_rctx(rctx, llvm_version)
if error:
fail(error)
if llvm_version:
print("\nINFO: Resolved latest LLVM version to {llvm_version}: {distribution}".format(
distribution = distribution,
llvm_version = llvm_version,
)) # buildifier: disable=print
if not llvm_version:
# LLVM version missing for (os, arch)
_empty_repository(rctx)
Expand Down
181 changes: 165 additions & 16 deletions toolchain/internal/llvm_distributions.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

load("@bazel_tools//tools/build_defs/repo:utils.bzl", "read_netrc", "use_netrc")
load("@helly25_bzl//bzl/versions:versions.bzl", "versions")
load(
"//toolchain/internal:common.bzl",
"attr_dict",
Expand Down Expand Up @@ -788,7 +789,8 @@ def _get_llvm_version(rctx):

def _get_all_llvm_distributions(*, llvm_distributions, extra_llvm_distributions, parsed_llvm_version):
distributions = {}
for basename, sha256 in llvm_distributions.items():
for dist, sha256 in llvm_distributions.items() + (extra_llvm_distributions.items() if extra_llvm_distributions else []):
basename = _distribution_basename(dist)
version = _distribution_version(basename)
if parsed_llvm_version and parsed_llvm_version != version:
continue
Expand All @@ -797,16 +799,6 @@ def _get_all_llvm_distributions(*, llvm_distributions, extra_llvm_distributions,
sha256 = sha256,
version = version,
)
if extra_llvm_distributions:
for dist, sha256 in extra_llvm_distributions.items():
version = _distribution_version(dist)
if parsed_llvm_version and parsed_llvm_version != version:
continue
distributions[_distribution_basename(dist)] = struct(
distribution = dist,
sha256 = sha256,
version = version,
)
return distributions

_UBUNTU_NAMES = [
Expand Down Expand Up @@ -1069,6 +1061,10 @@ def _find_llvm_basename_list(llvm_version, all_llvm_distributions, host_info):
return []

def _find_llvm_basename_or_error(llvm_version, all_llvm_distributions, host_info):
all_llvm_distributions = _filter_llvm_distributions(
llvm_version = llvm_version,
all_llvm_distributions = all_llvm_distributions,
)
basenames = _find_llvm_basename_list(llvm_version, all_llvm_distributions, host_info)
if len(basenames) > 1:
return None, "ERROR: Multiple configurations found for version {llvm_version} on {os}/{dist_name}/{dist_version} with arch {arch}: [{basenames}].".format(
Expand All @@ -1095,8 +1091,69 @@ def _find_llvm_basename_or_error(llvm_version, all_llvm_distributions, host_info

return basenames[0], None

def _parse_version_or_requirements(version_or_requirements):
if version_or_requirements in ["latest", "first"]:
return None
for prefix in ["latest:", "first:"]:
if version_or_requirements.startswith(prefix):
return versions.parse_requirements(version_or_requirements.removeprefix(prefix))
fail("ERROR: Invalid version requirements: '{version_or_requirements}'.".format(
version_or_requirements = version_or_requirements,
))

def _get_version_from_distribution(distribution):
# We assume here that the `distribution` is a basename of the form `LLVM-<version>-...` or
# `clang+llvm-<version>-...`.
return distribution.split("-")[1]

def _get_llvm_versions(*, version_or_requirements, all_llvm_distributions):
llvm_version_dict = {}
for distribution in all_llvm_distributions.keys():
version = _get_version_from_distribution(distribution)
llvm_version_dict[_parse_version(version)] = version

return [v for k, v in sorted(llvm_version_dict.items(), reverse = version_or_requirements.startswith("latest"))]

def _required_llvm_release_name(*, version_or_requirements, all_llvm_distributions, host_info):
llvm_versions = _get_llvm_versions(version_or_requirements = version_or_requirements, all_llvm_distributions = all_llvm_distributions)
requirements = _parse_version_or_requirements(version_or_requirements)
for llvm_version in llvm_versions:
if requirements and not versions.check_all_requirements(llvm_version, requirements):
continue
basenames = _find_llvm_basename_list(llvm_version, all_llvm_distributions, host_info)
if len(basenames) == 1:
return llvm_version, basenames[0], None
return None, None, "ERROR: No matching distribution found."

def required_llvm_release_name_rctx(rctx, llvm_version):
all_llvm_distributions = _get_all_llvm_distributions(
llvm_distributions = _llvm_distributions,
extra_llvm_distributions = rctx.attr.extra_llvm_distributions,
parsed_llvm_version = _parse_version(llvm_version),
)
return _required_llvm_release_name(
version_or_requirements = llvm_version,
all_llvm_distributions = all_llvm_distributions,
host_info = host_info(rctx),
)

def is_requirement(version_or_requirement):
"""Return whether `version_or_requirement` is likely a requirement (True) or should be a version."""
for prefix in ["first:", "latest:"]:
if version_or_requirement.startswith(prefix) or version_or_requirement == prefix[:-1]:
return True
return False

def _filter_llvm_distributions(*, llvm_version, all_llvm_distributions):
"""Return (distribution: sha) entries from `all_llvm_distributions` that match `llvm_version`."""
result = {}
for k, v in all_llvm_distributions.items():
if _get_version_from_distribution(k) == llvm_version:
result[k] = v
return result

def _distribution_urls(rctx):
"""Return LLVM `urls`, `shha256` and `strip_prefix` for the given context."""
"""Return LLVM `urls`, `sha256` and `strip_prefix` for the given context."""
llvm_version = _get_llvm_version(rctx)
all_llvm_distributions = _get_all_llvm_distributions(
llvm_distributions = _llvm_distributions,
Expand All @@ -1106,7 +1163,15 @@ def _distribution_urls(rctx):
_, sha256, strip_prefix, _ = _key_attrs(rctx)

if rctx.attr.distribution == "auto":
basename, error = _find_llvm_basename_or_error(llvm_version, all_llvm_distributions, host_info(rctx))
rctx_host_info = host_info(rctx)
if is_requirement(llvm_version):
llvm_version, basename, error = _required_llvm_release_name(
version_or_requirements = llvm_version,
all_llvm_distributions = all_llvm_distributions,
host_info = rctx_host_info,
)
else:
basename, error = _find_llvm_basename_or_error(llvm_version, all_llvm_distributions, rctx_host_info)
if error:
fail(error)
dist_info = all_llvm_distributions[basename]
Expand Down Expand Up @@ -1143,7 +1208,7 @@ def _distribution_urls(rctx):

return urls, sha256, strip_prefix

def _write_distributions_impl(ctx):
def _distributions_test_writer_impl(ctx):
"""Analyze the configured versions and write to a file for test consumption.

The test generated file '<rule_name>.out' contains the following lines:
Expand Down Expand Up @@ -1353,10 +1418,94 @@ def _write_distributions_impl(ctx):
ctx.actions.write(ctx.outputs.output, "\n".join(output) + "\n")
ctx.actions.write(ctx.outputs.select, "\n".join(select) + "\n")

write_distributions = rule(
implementation = _write_distributions_impl,
distributions_test_writer = rule(
implementation = _distributions_test_writer_impl,
attrs = {
"output": attr.output(mandatory = True),
"select": attr.output(mandatory = True),
},
)

def _requirements_test_writer_impl(ctx):
"""Analyze the configured versions and write to a file for test consumption.
The test generated file '<rule_name>.out' contains the following lines:
[<arch>,<os>,<requirement>]: <llvm_distribution_basename>
"""
all_llvm_distributions = {
# In order to prevent new distributions to interfere we cut at 20.1.3.
k: v
for k, v in _llvm_distributions.items()
if _parse_version(_get_version_from_distribution(k)) <= (20, 1, 3)
}
requirement_list = [
"latest:<=20.1.0",
"latest:<=20.1.0,>17.0.4,!=19.1.7",
"latest:<20.1.0,>17.0.4,!=19.1.7",
"latest:<20.1.0,>17.0.4",
"latest:>=15.0.6,<16",
"first:>=15.0.6,<16",
"latest",
"first",
]
arch_list = [
"aarch64",
"armv7a",
"x86_64",
]
os_list = [
"darwin",
"linux",
"windows",
]
ANY_VERSION = "0" # Version does not matter, but must be a valid integer
dist_dict_list = {
"linux": [
# keep sorted
struct(name = "ubuntu", version = ANY_VERSION),
struct(name = "raspbian", version = ANY_VERSION),
struct(name = "rhel", version = ANY_VERSION),
],
}
result = []
for arch in arch_list:
for os in os_list:
dist_list = dist_dict_list.get(os, [struct(name = os, version = "")])
for dist in dist_list:
for requirement in requirement_list:
host_info = struct(
arch = arch,
os = os,
dist = dist,
)
llvm_version, basename, error = _required_llvm_release_name(
version_or_requirements = requirement,
all_llvm_distributions = all_llvm_distributions,
host_info = host_info,
)
if llvm_version and basename:
result.append("[{arch},{os}{dist_name}{dist_version},'{requirement}']: {llvm_version} = {basename}".format(
arch = arch,
os = os,
dist_name = "," + dist.name if os == "linux" else "",
dist_version = "," + dist.version if os == "linux" else "",
requirement = requirement,
llvm_version = llvm_version,
basename = basename,
))
else:
result.append("[{arch},{os},\"{requirement}\"]: {error}".format(
arch = arch,
os = os,
requirement = requirement,
llvm_version = llvm_version,
basename = basename,
error = error or "ERROR: N/A",
))
ctx.actions.write(ctx.outputs.result, "\n".join(result) + "\n")

requirements_test_writer = rule(
implementation = _requirements_test_writer_impl,
attrs = {
"result": attr.output(mandatory = True),
},
)
Loading