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 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fdc37eb
Add more missing distributions (all .gz).
helly25 Mar 13, 2025
8282646
Add the ne platforms test to the actions release workflow
helly25 Mar 13, 2025
61c3b85
Better naming: should be toolchain_tests not platform_tests
helly25 Mar 13, 2025
b2ee5fe
No switching directories for the new test
helly25 Mar 13, 2025
0d50cac
Use var array `test_args` to pass around the erquired flag.
helly25 Mar 13, 2025
bed5b92
Add ability to specify `llvm_version/s` as (semver) requirements.
helly25 Mar 15, 2025
5a527ee
Add a requirements test and disable the tests in workspace mode as th…
helly25 Mar 15, 2025
b419a9c
Make this work for workspaces
helly25 Mar 15, 2025
fece8bd
The dependency also needs to go into deps.bzl so run_tests.bzl works.
helly25 Mar 15, 2025
5fb7939
Update (redo) after sync.
helly25 May 11, 2025
a05cdb8
Example update#
helly25 May 11, 2025
bb053fc
Readme update.
helly25 May 11, 2025
75baa87
Update configure and tests.
helly25 May 11, 2025
96b2607
Use better requiement identification that is in line with the actual …
helly25 May 11, 2025
7499808
Updates & tweaks
helly25 May 11, 2025
1fec8f3
Ensure common_test_args are present
helly25 May 11, 2025
f7239c2
one test is not always supported.
helly25 May 11, 2025
46f5608
Small improvements. Pre-filter eligible versions and ensure actual co…
helly25 May 12, 2025
5918bb3
Attribute `extra_llvm_distributions` must be a common attribute.
helly25 May 12, 2025
5467126
sync
helly25 May 16, 2025
09b2483
Limit test to 20.1.3 to prevent unnecessary diffs.
helly25 May 16, 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",
)
10 changes: 9 additions & 1 deletion tests/scripts/run_toolchain_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,13 @@ targets=(
"//toolchain/..."
)

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

if [[ "${USE_BAZEL_VERSION}" == "7.x" ]] || [[ "${USE_BZLMOD}" == "false" ]]; then
targets+=("-//toolchain/internal:llvm_distributions_select_no_error_test")
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",
)
15 changes: 14 additions & 1 deletion toolchain/internal/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

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

exports_files(["template.modulemap"])

Expand Down Expand Up @@ -50,3 +50,16 @@ sh_test(
"//conditions:default": ["@platforms//:incompatible"],
}),
)

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 @@ -36,6 +36,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 @@ -79,6 +80,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
158 changes: 156 additions & 2 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 @@ -989,6 +990,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 @@ -1015,14 +1020,79 @@ 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(rctx)
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(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)
if sha256 and sha256 != all_llvm_distributions[basename]:
Expand Down Expand Up @@ -1263,3 +1333,87 @@ write_distributions = rule(
"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