diff --git a/.github/workflows/default.yaml b/.github/workflows/default.yaml
index b317919..fb73e47 100644
--- a/.github/workflows/default.yaml
+++ b/.github/workflows/default.yaml
@@ -60,6 +60,30 @@ jobs:
run: |
ln -s .github/workflows/.bazelrc.ci .bazelrc.ci
bazel test --compilation_mode ${{ matrix.compilation_mode }} //...
+ gcc_versions:
+ strategy:
+ matrix:
+ gcc_version:
+ - 12.5.0
+ - 13.4.0
+ - 14.3.0
+ - 15.2.0
+ - 16.1.0
+ - 16.2.0
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ lfs: true
+ - uses: bazel-contrib/setup-bazel@0.15.0
+ with:
+ bazelisk-cache: true
+ disk-cache: ${{ github.workflow }}
+ repository-cache: true
+ - name: Test GCC ${{ matrix.gcc_version }}
+ run: |
+ ln -s .github/workflows/.bazelrc.ci .bazelrc.ci
+ bazel test --//toolchain:gcc_version=${{ matrix.gcc_version }} //...
sanitizers:
strategy:
matrix:
diff --git a/MODULE.bazel b/MODULE.bazel
index 4375ba1..098a09e 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -19,8 +19,7 @@ gcc_toolchains = use_extension("//toolchain:module_extensions.bzl", "gcc_toolcha
),
use_repo(gcc_toolchains, "gcc_toolchain_{}".format(arch)),
register_toolchains(
- "@gcc_toolchain_{}//:cc_toolchain".format(arch),
- "@gcc_toolchain_{}//:fortran_toolchain".format(arch),
+ "@gcc_toolchain_{}//:all".format(arch),
# Register toolchains as dev dependencies so that we don't pollute the toolchain resolution of consumers.
dev_dependency = True,
),
diff --git a/docs/README.md b/docs/README.md
index 60ed94d..c674d46 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -45,6 +45,48 @@ gcc_register_toolchain(
)
```
+## Selecting the GCC version
+
+Every GCC version listed in `AVAILABLE_GCC_VERSIONS` is registered as a toolchain, and the
+`@gcc_toolchain//toolchain:gcc_version` flag picks which one resolves:
+
+```shell
+bazel build --@gcc_toolchain//toolchain:gcc_version=15.2.0 //...
+```
+
+Leaving the flag unset uses the `gcc_version` the toolchain was declared with, which defaults to
+the newest available version. Only the selected version is downloaded — the others are declared but
+never fetched.
+
+Because it is an ordinary build flag, it can be bound to a `.bazelrc` config:
+
+```shell
+build:gcc15 --@gcc_toolchain//toolchain:gcc_version=15.2.0
+```
+
+To change which version is used while the flag is unset, set it when declaring the toolchain. An
+explicit `--@gcc_toolchain//toolchain:gcc_version` still overrides it:
+
+```bazel
+gcc_toolchains.toolchain(
+ name = "gcc_toolchain_x86_64",
+ gcc_version = "15.2.0",
+ target_arch = "x86_64",
+)
+```
+
+Each version also gets a `config_setting`, so build rules can branch on the selected compiler:
+
+```bazel
+copts = select({
+ "@gcc_toolchain//toolchain:gcc_version_12_5_0": ["-Wno-maybe-uninitialized"],
+ "//conditions:default": [],
+})
+```
+
+Note that these settings only match when the flag is set explicitly, so a `select()` over them
+needs a `//conditions:default` branch to cover the unset case.
+
## Language Support
### Pure C
diff --git a/docs/defs.md b/docs/defs.md
index 4944ba7..5cbb763 100644
--- a/docs/defs.md
+++ b/docs/defs.md
@@ -9,9 +9,8 @@ This module provides the definitions for registering a GCC toolchain for C and C
gcc_toolchain(name, binary_prefix, enable_fortran, extra_asmflags, extra_cflags, extra_cxxflags,
- extra_fflags, extra_ldflags, extra_target_compatible_with, fincludes,
- gcc_toolchain_workspace_name, gcc_version, gcc_versions, includes, repo_mapping,
- supports_param_files, target_arch, target_compatible_with, target_settings)
+ extra_fflags, extra_ldflags, fincludes, gcc_toolchain_workspace_name, gcc_version,
+ gcc_versions, includes, repo_mapping, supports_param_files, target_arch)
@@ -29,7 +28,6 @@ gcc_toolchain(name, extra_cxxflags | Extra flags for compiling C++. | List of strings | optional | [] |
| extra_fflags | Extra flags for compiling Fortran, if enabled. | List of strings | optional | [] |
| extra_ldflags | Extra flags for linking. %workspace% is rendered to the toolchain root path. See https://github.com/bazelbuild/bazel/blob/a48e246e/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProviderHelper.java#L234-L254. | List of strings | optional | [] |
-| extra_target_compatible_with | Additional constraint_values appended to target_compatible_with of the toolchain, on top of the values from the target_compatible_with attribute (including its defaults). Unlike target_compatible_with, {target_arch} is not rendered. | List of labels | optional | [] |
| fincludes | Extra includes for compiling Fortran, if enabled. %workspace% is rendered to the toolchain root path. | List of strings | optional | [] |
| gcc_toolchain_workspace_name | The name given to the gcc-toolchain repository, if the default was not used. | String | optional | "gcc_toolchain" |
| gcc_version | The version of GCC. | String | optional | "16.2.0" |
@@ -38,8 +36,6 @@ gcc_toolchain(name, repo_mapping | A dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.<p>For example, an entry "@foo": "@bar" declares that, for any time this repository depends on @foo (such as a dependency on @foo//some:target, it should actually resolve that dependency within globally-declared @bar (@bar//some:target). | Dictionary: String -> String | required | |
| supports_param_files | Set supports_param_files = 1 on the generated cc_toolchain, which lets Bazel pass linker arguments via an @params file. Enable this when link command lines for large targets overflow ARG_MAX (e.g. collect2: posix_spawn: Argument list too long). Off by default to preserve historical behavior. | Boolean | optional | False |
| target_arch | The target architecture this toolchain produces. E.g. x86_64. | String | required | |
-| target_compatible_with | constraint_values passed to target_compatible_with of the toolchain. {target_arch} is rendered to the target_arch attribute value. | List of strings | optional | ["@platforms//os:linux", "@platforms//cpu:{target_arch}"] |
-| target_settings | config_settings passed to target_compatible_with of the toolchain. {target_arch} is rendered to the target_arch attribute value. | List of strings | optional | [] |
@@ -50,7 +46,11 @@ gcc_toolchain(name, name, target_arch, kwargs)
-Declares a `gcc_toolchain`.
+Declares a `gcc_toolchain` for every available GCC version.
+
+`name` is the hub repository holding the `toolchain` declarations. Each GCC version gets its
+own repository holding the `cc_toolchain`, fetched only when that version is selected through
+the `@gcc_toolchain//toolchain:gcc_version` flag.
You should use `gcc_register_toolchain` unless you need to register toolchains manually,
e.g. if you are consuming this repository as a Bzlmod dependency.
@@ -61,9 +61,9 @@ e.g. if you are consuming this repository as a Bzlmod dependency.
| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
-| name | The name passed to gcc_toolchain. | none |
+| name | The name of the hub repository holding the toolchain declarations. | none |
| target_arch | The target architecture of the toolchain. | none |
-| kwargs | The extra arguments passed to gcc_toolchain. See gcc_toolchain for more info. | none |
+| kwargs | The extra arguments passed to gcc_toolchain. See gcc_toolchain for more info. The attributes of the toolchain declarations themselves are also accepted here, since they apply to the hub rather than to gcc_toolchain:
target_compatible_with: constraint_values passed to target_compatible_with of the toolchain. {target_arch} is rendered to the target_arch argument value. Defaults to ["@platforms//os:linux", "@platforms//cpu:{target_arch}"].
extra_target_compatible_with: Additional constraint_values appended to target_compatible_with of the toolchain, on top of the values from the target_compatible_with argument (including its defaults). Unlike target_compatible_with, {target_arch} is not rendered.
target_settings: Additional config_settings passed to target_settings of the toolchain, on top of the GCC version selection. {target_arch} is rendered to the target_arch argument value. | none |
@@ -74,15 +74,18 @@ e.g. if you are consuming this repository as a Bzlmod dependency.
gcc_register_toolchain(name, target_arch, kwargs)
-Declares a `gcc_toolchain` and calls `register_toolchain` for it.
+Declares a `gcc_toolchain` for every available GCC version and registers all of them.
+
+Which one resolves is selected by the `@gcc_toolchain//toolchain:gcc_version` flag.
+
**PARAMETERS**
| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
-| name | The name passed to gcc_toolchain. | none |
+| name | The name of the hub repository holding the toolchain declarations. | none |
| target_arch | The target architecture of the toolchain. | none |
-| kwargs | The extra arguments passed to gcc_toolchain. See gcc_toolchain for more info. | none |
+| kwargs | The extra arguments passed to gcc_declare_toolchain. See gcc_declare_toolchain for more info. | none |
diff --git a/docs/updating-gcc-builds.md b/docs/updating-gcc-builds.md
index ab199c7..402aee6 100644
--- a/docs/updating-gcc-builds.md
+++ b/docs/updating-gcc-builds.md
@@ -55,6 +55,15 @@ Edit `AVAILABLE_GCC_VERSIONS` in [toolchain/defs.bzl](../toolchain/defs.bzl). Wh
new GCC version, add a new entry; bump `DEFAULT_GCC_VERSION` if that version becomes the default.
Leave versions that the newer releases do not publish pointing at their existing release tag.
+Every entry becomes a selectable toolchain and a `//toolchain:gcc_version_*` config setting, so
+adding or removing a version also means:
+
+- adding it to the `gcc_versions` matrix in
+ [.github/workflows/default.yaml](../.github/workflows/default.yaml);
+- checking whether the tarball ships `bin/ld.lld`. GCC versions built without it cannot use the
+ `linker-lld` feature, and `//tests/lld` marks those versions incompatible so the test is skipped
+ rather than failing.
+
## 4. Regenerate the docs
`AVAILABLE_GCC_VERSIONS` is serialized into the `gcc_versions` attribute default, so
@@ -71,6 +80,11 @@ New tarballs can change more than their contents, so exercise every architecture
```shell
bazel test //...
+# Every selectable GCC version, which is what the CI matrix runs.
+for gcc_version in 12.5.0 13.4.0 14.3.0 15.2.0 16.1.0 16.2.0; do
+ bazel test --//toolchain:gcc_version=${gcc_version} //...
+done
+
# Cross-compilation, which the default test run does not cover.
for platform in aarch64_linux armv7_linux x86_64_linux; do
bazel build --platforms=//platforms:${platform} \
diff --git a/examples/bzlmod/MODULE.bazel b/examples/bzlmod/MODULE.bazel
index 7251e25..61d2a02 100644
--- a/examples/bzlmod/MODULE.bazel
+++ b/examples/bzlmod/MODULE.bazel
@@ -18,8 +18,7 @@ gcc_toolchains = use_extension("@gcc_toolchain//toolchain:module_extensions.bzl"
),
use_repo(gcc_toolchains, "gcc_toolchain_{}".format(arch)),
register_toolchains(
- "@gcc_toolchain_{}//:cc_toolchain".format(arch),
- "@gcc_toolchain_{}//:fortran_toolchain".format(arch),
+ "@gcc_toolchain_{}//:all".format(arch),
dev_dependency = True,
),
]
diff --git a/tests/gcc_version/BUILD.bazel b/tests/gcc_version/BUILD.bazel
new file mode 100644
index 0000000..c1a13dc
--- /dev/null
+++ b/tests/gcc_version/BUILD.bazel
@@ -0,0 +1,39 @@
+# Copyright (c) Thulio Ferraz Assis 2026
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+load("@bazel_skylib//lib:dicts.bzl", "dicts")
+load("//rules_cc:defs.bzl", "cc_test")
+load("//toolchain:defs.bzl", "AVAILABLE_GCC_VERSIONS", "DEFAULT_GCC_VERSION")
+
+# Asserts that the compiler picked by toolchain resolution is the one the gcc_version flag asked
+# for. The build fails to compile when the selected toolchain reports a different version.
+cc_test(
+ name = "gcc_version_test",
+ srcs = ["main.c"],
+ copts = select(dicts.add(
+ {
+ "//toolchain:gcc_version_{}".format(gcc_version.replace(".", "_")): [
+ "-DEXPECTED_GCC_MAJOR={}".format(gcc_version.split(".")[0]),
+ "-DEXPECTED_GCC_MINOR={}".format(gcc_version.split(".")[1]),
+ ]
+ for gcc_version in AVAILABLE_GCC_VERSIONS
+ },
+ {
+ "//conditions:default": [
+ "-DEXPECTED_GCC_MAJOR={}".format(DEFAULT_GCC_VERSION.split(".")[0]),
+ "-DEXPECTED_GCC_MINOR={}".format(DEFAULT_GCC_VERSION.split(".")[1]),
+ ],
+ },
+ )),
+)
diff --git a/tests/gcc_version/main.c b/tests/gcc_version/main.c
new file mode 100644
index 0000000..a44506b
--- /dev/null
+++ b/tests/gcc_version/main.c
@@ -0,0 +1,28 @@
+// Copyright (c) Thulio Ferraz Assis 2026
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include
+
+#if __GNUC__ != EXPECTED_GCC_MAJOR
+#error "The compiling GCC major version does not match the gcc_version flag."
+#endif
+
+#if __GNUC_MINOR__ != EXPECTED_GCC_MINOR
+#error "The compiling GCC minor version does not match the gcc_version flag."
+#endif
+
+int main(void) {
+ printf("%d.%d.%d\n", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
+ return 0;
+}
diff --git a/tests/lld/BUILD.bazel b/tests/lld/BUILD.bazel
index d84d978..66623c0 100644
--- a/tests/lld/BUILD.bazel
+++ b/tests/lld/BUILD.bazel
@@ -12,15 +12,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-load("//rules_cc:defs.bzl", "cc_binary")
load("@rules_shell//shell:sh_test.bzl", "sh_test")
+load("//rules_cc:defs.bzl", "cc_binary")
# This binary is always linked with lld via the target-level `linker-lld`
-# feature, which requires the toolchain archive to ship `ld.lld`.
+# feature, which requires the toolchain archive to ship `ld.lld`. GCC versions
+# older than 15.2.0 are built without lld, so the test is skipped for them.
cc_binary(
name = "hello_world_lld",
srcs = ["main.c"],
features = ["linker-lld"],
+ target_compatible_with = select({
+ "//toolchain:gcc_version_12_5_0": ["@platforms//:incompatible"],
+ "//toolchain:gcc_version_13_4_0": ["@platforms//:incompatible"],
+ "//toolchain:gcc_version_14_3_0": ["@platforms//:incompatible"],
+ "//conditions:default": [],
+ }),
)
sh_test(
diff --git a/toolchain/BUILD.bazel b/toolchain/BUILD.bazel
index b487911..44c53ea 100644
--- a/toolchain/BUILD.bazel
+++ b/toolchain/BUILD.bazel
@@ -16,6 +16,27 @@
# limitations under the License.
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
+load(":defs.bzl", "AVAILABLE_GCC_VERSIONS")
+
+# Selects which GCC version the registered toolchains resolve to. The empty default defers to the
+# gcc_version each toolchain was declared with.
+string_flag(
+ name = "gcc_version",
+ build_setting_default = "",
+ visibility = ["//visibility:public"],
+)
+
+# Matches when gcc_version is set to that exact version. None of these match while the flag is left
+# at its empty default, so a select over them needs a //conditions:default branch.
+[
+ config_setting(
+ name = "gcc_version_{}".format(gcc_version.replace(".", "_")),
+ flag_values = {":gcc_version": gcc_version},
+ visibility = ["//visibility:public"],
+ )
+ for gcc_version in AVAILABLE_GCC_VERSIONS
+]
exports_files(
["config.bzl"],
diff --git a/toolchain/defs.bzl b/toolchain/defs.bzl
index e490480..f96cc79 100644
--- a/toolchain/defs.bzl
+++ b/toolchain/defs.bzl
@@ -144,17 +144,6 @@ def _gcc_toolchain_impl(rctx):
]
]
- target_compatible_with = [
- v.format(target_arch = target_arch)
- for v in rctx.attr.target_compatible_with
- ]
- target_compatible_with.extend([str(c) for c in rctx.attr.extra_target_compatible_with])
-
- target_settings = [
- v.format(target_arch = target_arch)
- for v in rctx.attr.target_settings
- ]
-
builtin_include_directories = []
builtin_include_directories.extend(c_builtin_includes)
builtin_include_directories.extend(cxx_builtin_includes)
@@ -261,8 +250,6 @@ def _gcc_toolchain_impl(rctx):
rctx.file("BUILD.bazel", _TOOLCHAIN_BUILD_FILE_CONTENT.format(
gcc_toolchain_workspace_name = rctx.attr.gcc_toolchain_workspace_name,
enable_fortran = str(rctx.attr.enable_fortran),
- target_compatible_with = target_compatible_with,
- target_settings = target_settings,
binary_prefix = binary_prefix,
include_prefix = include_prefix,
@@ -283,13 +270,12 @@ def _gcc_toolchain_impl(rctx):
**_fortran_vars(
rctx.attr.enable_fortran,
rctx.attr.gcc_toolchain_workspace_name,
- target_compatible_with,
include_prefix,
binary_prefix,
)
))
-def _fortran_vars(enable_fortran, gcc_toolchain_workspace_name, target_compatible_with, include_prefix, binary_prefix):
+def _fortran_vars(enable_fortran, gcc_toolchain_workspace_name, include_prefix, binary_prefix):
load_ = ""
toolchain = ""
includes = ""
@@ -302,24 +288,10 @@ def _fortran_vars(enable_fortran, gcc_toolchain_workspace_name, target_compatibl
load_ = 'load("@{gcc_toolchain_workspace_name}//toolchain/fortran:defs.bzl", "fortran_toolchain")'.format(
gcc_toolchain_workspace_name = gcc_toolchain_workspace_name,
)
- toolchain = '''toolchain(
- name = "fortran_toolchain",
- exec_compatible_with = [
- "@platforms//os:linux",
- "@platforms//cpu:x86_64",
- ],
- target_compatible_with = {target_compatible_with},
- toolchain = ":_fortran_toolchain",
- toolchain_type = "@{gcc_toolchain_workspace_name}//toolchain/fortran:toolchain_type",
-)
-
-fortran_toolchain(
+ toolchain = '''fortran_toolchain(
name = "_fortran_toolchain",
cc_toolchain = ":_cc_toolchain",
-)'''.format(
- gcc_toolchain_workspace_name = gcc_toolchain_workspace_name,
- target_compatible_with = target_compatible_with,
- )
+)'''
includes = '''
# Fortran includes
"lib/gcc/{include_prefix}*/finclude/**",'''.format(include_prefix = include_prefix)
@@ -496,6 +468,17 @@ _FEATURE_ATTRS = {
doc = "The target architecture this toolchain produces. E.g. x86_64.",
mandatory = True,
),
+}
+
+# Attributes of the `toolchain` declarations, which live in the hub rather than in the repository
+# holding the compiler itself.
+_TOOLCHAIN_DECLARATION_ATTRS = {
+ "extra_target_compatible_with": attr.label_list(
+ doc = "Additional constraint_values appended to target_compatible_with of the toolchain," +
+ " on top of the values from the target_compatible_with attribute (including its defaults)." +
+ " Unlike target_compatible_with, {target_arch} is not rendered.",
+ mandatory = False,
+ ),
"target_compatible_with": attr.string_list(
default = [
"@platforms//os:linux",
@@ -504,15 +487,9 @@ _FEATURE_ATTRS = {
doc = "constraint_values passed to target_compatible_with of the toolchain. {target_arch} is rendered to the target_arch attribute value.",
mandatory = False,
),
- "extra_target_compatible_with": attr.label_list(
- doc = "Additional constraint_values appended to target_compatible_with of the toolchain," +
- " on top of the values from the target_compatible_with attribute (including its defaults)." +
- " Unlike target_compatible_with, {target_arch} is not rendered.",
- mandatory = False,
- ),
"target_settings": attr.string_list(
default = [],
- doc = "config_settings passed to target_compatible_with of the toolchain. {target_arch} is rendered to the target_arch attribute value.",
+ doc = "Additional config_settings passed to target_settings of the toolchain, on top of the GCC version selection. {target_arch} is rendered to the target_arch attribute value.",
mandatory = False,
),
}
@@ -531,8 +508,129 @@ gcc_toolchain = repository_rule(
),
)
+def _sanitize_version(gcc_version):
+ return gcc_version.replace(".", "_")
+
+def _gcc_toolchains_hub_impl(rctx):
+ target_arch = rctx.attr.target_arch
+ gcc_version_flag = str(rctx.attr._gcc_version_flag)
+
+ target_compatible_with = [
+ v.format(target_arch = target_arch)
+ for v in rctx.attr.target_compatible_with
+ ]
+ target_compatible_with.extend([str(c) for c in rctx.attr.extra_target_compatible_with])
+
+ extra_target_settings = [
+ v.format(target_arch = target_arch)
+ for v in rctx.attr.target_settings
+ ]
+
+ toolchain_repos = rctx.attr.toolchain_repos
+ default_gcc_version = rctx.attr.default_gcc_version
+ if default_gcc_version not in toolchain_repos:
+ fail("The default GCC version {} has no toolchain for {}.".format(default_gcc_version, target_arch))
+
+ content = [_HUB_BUILD_FILE_HEADER]
+
+ content.append('''config_setting(
+ name = "_gcc_version_unset",
+ flag_values = {{"{gcc_version_flag}": ""}},
+)'''.format(gcc_version_flag = gcc_version_flag))
+
+ for gcc_version in sorted(toolchain_repos.keys()):
+ content.append('''config_setting(
+ name = "_gcc_version_{suffix}",
+ flag_values = {{"{gcc_version_flag}": "{gcc_version}"}},
+)'''.format(
+ suffix = _sanitize_version(gcc_version),
+ gcc_version = gcc_version,
+ gcc_version_flag = gcc_version_flag,
+ ))
+
+ # The default version is selected both when the flag is left at its empty default and when it
+ # is set to that version explicitly.
+ content.append('''selects.config_setting_group(
+ name = "_gcc_version_default",
+ match_any = [
+ ":_gcc_version_unset",
+ ":_gcc_version_{suffix}",
+ ],
+)'''.format(suffix = _sanitize_version(default_gcc_version)))
+
+ for gcc_version in sorted(toolchain_repos.keys()):
+ is_default = gcc_version == default_gcc_version
+ suffix = "" if is_default else "_" + _sanitize_version(gcc_version)
+ setting = ":_gcc_version_default" if is_default else ":_gcc_version_" + _sanitize_version(gcc_version)
+ content.append(_HUB_TOOLCHAIN_TEMPLATE.format(
+ suffix = suffix,
+ target_compatible_with = target_compatible_with,
+ target_settings = [setting] + extra_target_settings,
+ toolchain_repo = toolchain_repos[gcc_version],
+ ))
+ if rctx.attr.enable_fortran:
+ content.append(_HUB_FORTRAN_TOOLCHAIN_TEMPLATE.format(
+ suffix = suffix,
+ target_compatible_with = target_compatible_with,
+ target_settings = [setting] + extra_target_settings,
+ toolchain_repo = toolchain_repos[gcc_version],
+ fortran_toolchain_type = str(rctx.attr._fortran_toolchain_type),
+ ))
+
+ # Keep the targets of the selected toolchain reachable under the hub name, so that labels like
+ # @gcc_toolchain_x86_64//:libstdcxx keep resolving and follow the selected version.
+ aliases = list(_HUB_ALIASED_TARGETS)
+ if rctx.attr.enable_fortran:
+ aliases.append("_fortran_toolchain")
+ for target in aliases:
+ actual = {
+ ":_gcc_version_" + _sanitize_version(gcc_version): "@{}//:{}".format(toolchain_repo, target)
+ for gcc_version, toolchain_repo in toolchain_repos.items()
+ if gcc_version != default_gcc_version
+ }
+ actual["//conditions:default"] = "@{}//:{}".format(toolchain_repos[default_gcc_version], target)
+ content.append('''alias(
+ name = "{target}",
+ actual = select({actual}),
+)'''.format(target = target, actual = actual))
+
+ rctx.file("BUILD.bazel", "\n\n".join(content) + "\n")
+
+_gcc_toolchains_hub = repository_rule(
+ _gcc_toolchains_hub_impl,
+ attrs = dicts.add(
+ _TOOLCHAIN_DECLARATION_ATTRS,
+ {
+ "default_gcc_version": attr.string(
+ doc = "The GCC version used when the gcc_version flag is not set.",
+ mandatory = True,
+ ),
+ "enable_fortran": attr.bool(
+ doc = "Whether to declare the Fortran toolchains.",
+ default = True,
+ ),
+ "target_arch": attr.string(
+ doc = "The target architecture the toolchains produce. E.g. x86_64.",
+ mandatory = True,
+ ),
+ "toolchain_repos": attr.string_dict(
+ doc = "Maps each available GCC version to the repository holding its cc_toolchain.",
+ mandatory = True,
+ ),
+ "_fortran_toolchain_type": attr.label(
+ default = Label("//toolchain/fortran:toolchain_type"),
+ ),
+ "_gcc_version_flag": attr.label(
+ default = Label("//toolchain:gcc_version"),
+ ),
+ },
+ ),
+)
+
+_SHAREABLE_ATTRS = dicts.add(_FEATURE_ATTRS, _TOOLCHAIN_DECLARATION_ATTRS)
+
ATTRS_SHARED_WITH_MODULE_EXTENSION = {
- attr_name: _FEATURE_ATTRS[attr_name]
+ attr_name: _SHAREABLE_ATTRS[attr_name]
for attr_name in [
"gcc_version",
"gcc_versions",
@@ -630,15 +728,34 @@ def gcc_declare_toolchain(
name,
target_arch,
**kwargs):
- """Declares a `gcc_toolchain`.
+ """Declares a `gcc_toolchain` for every available GCC version.
+
+ `name` is the hub repository holding the `toolchain` declarations. Each GCC version gets its
+ own repository holding the `cc_toolchain`, fetched only when that version is selected through
+ the `@gcc_toolchain//toolchain:gcc_version` flag.
You should use `gcc_register_toolchain` unless you need to register toolchains manually,
e.g. if you are consuming this repository as a Bzlmod dependency.
Args:
- name: The name passed to `gcc_toolchain`.
+ name: The name of the hub repository holding the toolchain declarations.
target_arch: The target architecture of the toolchain.
**kwargs: The extra arguments passed to `gcc_toolchain`. See `gcc_toolchain` for more info.
+ The attributes of the `toolchain` declarations themselves are also accepted here,
+ since they apply to the hub rather than to `gcc_toolchain`:
+
+ `target_compatible_with`: constraint_values passed to `target_compatible_with` of the
+ toolchain. `{target_arch}` is rendered to the `target_arch` argument value. Defaults to
+ `["@platforms//os:linux", "@platforms//cpu:{target_arch}"]`.
+
+ `extra_target_compatible_with`: Additional constraint_values appended to
+ `target_compatible_with` of the toolchain, on top of the values from the
+ `target_compatible_with` argument (including its defaults). Unlike
+ `target_compatible_with`, `{target_arch}` is not rendered.
+
+ `target_settings`: Additional config_settings passed to `target_settings` of the
+ toolchain, on top of the GCC version selection. `{target_arch}` is rendered to the
+ `target_arch` argument value.
"""
binary_prefix = kwargs.pop("binary_prefix", None)
if binary_prefix == None:
@@ -646,37 +763,85 @@ def gcc_declare_toolchain(
fail("Unsupported target architecture: {}".format(target_arch))
binary_prefix = _AUTO_BINARY_PREFIX
- gcc_toolchain(
+ default_gcc_version = kwargs.pop("gcc_version", DEFAULT_GCC_VERSION)
+ gcc_versions = kwargs.pop("gcc_versions", json.encode(AVAILABLE_GCC_VERSIONS))
+ enable_fortran = kwargs.pop("enable_fortran", True)
+ extra_target_compatible_with = kwargs.pop("extra_target_compatible_with", [])
+ target_compatible_with = kwargs.pop("target_compatible_with", None)
+ target_settings = kwargs.pop("target_settings", [])
+
+ # Left in kwargs so that the per-version repositories keep receiving it.
+ repo_mapping = kwargs.get("repo_mapping", None)
+
+ extra_cflags = kwargs.pop("extra_cflags", [])
+ extra_cxxflags = kwargs.pop("extra_cxxflags", [])
+ extra_fflags = kwargs.pop("extra_fflags", [])
+ extra_ldflags = kwargs.pop("extra_ldflags", [])
+ extra_asmflags = kwargs.pop("extra_asmflags", [])
+ includes = kwargs.pop("includes", [])
+ fincludes = kwargs.pop("fincludes", [])
+
+ toolchain_repos = {}
+ for gcc_version in json.decode(gcc_versions):
+ toolchain_repo = "{}_{}".format(name, _sanitize_version(gcc_version))
+ toolchain_repos[gcc_version] = toolchain_repo
+ gcc_toolchain(
+ name = toolchain_repo,
+ binary_prefix = binary_prefix,
+ enable_fortran = enable_fortran,
+ extra_cflags = extra_cflags,
+ extra_cxxflags = extra_cxxflags,
+ extra_fflags = extra_fflags,
+ extra_ldflags = extra_ldflags,
+ extra_asmflags = extra_asmflags,
+ includes = includes,
+ fincludes = fincludes,
+ gcc_version = gcc_version,
+ gcc_versions = gcc_versions,
+ target_arch = target_arch,
+ **kwargs
+ )
+
+ hub_kwargs = {}
+
+ # An explicitly empty target_compatible_with drops the default constraints, so only an omitted
+ # one may fall back to the attribute default.
+ if target_compatible_with != None:
+ hub_kwargs["target_compatible_with"] = target_compatible_with
+
+ # The hub BUILD file resolves @bazel_skylib, @platforms and @rules_cc as well, so it needs the
+ # same repository mapping as the per-version repositories.
+ if repo_mapping != None:
+ hub_kwargs["repo_mapping"] = repo_mapping
+
+ _gcc_toolchains_hub(
name = name,
- binary_prefix = binary_prefix,
- extra_cflags = kwargs.pop("extra_cflags", []),
- extra_cxxflags = kwargs.pop("extra_cxxflags", []),
- extra_fflags = kwargs.pop("extra_fflags", []),
- extra_ldflags = kwargs.pop("extra_ldflags", []),
- extra_asmflags = kwargs.pop("extra_asmflags", []),
- extra_target_compatible_with = kwargs.pop("extra_target_compatible_with", []),
- includes = kwargs.pop("includes", []),
- fincludes = kwargs.pop("fincludes", []),
+ default_gcc_version = default_gcc_version,
+ enable_fortran = enable_fortran,
+ extra_target_compatible_with = extra_target_compatible_with,
target_arch = target_arch,
- **kwargs
+ target_settings = target_settings,
+ toolchain_repos = toolchain_repos,
+ **hub_kwargs
)
def gcc_register_toolchain(
name,
target_arch,
**kwargs):
- """Declares a `gcc_toolchain` and calls `register_toolchain` for it.
+ """Declares a `gcc_toolchain` for every available GCC version and registers all of them.
+
+ Which one resolves is selected by the `@gcc_toolchain//toolchain:gcc_version` flag.
Args:
- name: The name passed to `gcc_toolchain`.
+ name: The name of the hub repository holding the toolchain declarations.
target_arch: The target architecture of the toolchain.
- **kwargs: The extra arguments passed to `gcc_toolchain`. See `gcc_toolchain` for more info.
+ **kwargs: The extra arguments passed to `gcc_declare_toolchain`. See
+ `gcc_declare_toolchain` for more info.
"""
enable_fortran = kwargs.pop("enable_fortran", True)
gcc_declare_toolchain(name, target_arch, enable_fortran = enable_fortran, **kwargs)
- native.register_toolchains("@{}//:cc_toolchain".format(name))
- if enable_fortran:
- native.register_toolchains("@{}//:fortran_toolchain".format(name))
+ native.register_toolchains("@{}//:all".format(name))
ARCHS = struct(
aarch64 = "aarch64",
@@ -684,27 +849,84 @@ ARCHS = struct(
x86_64 = "x86_64",
)
-_TOOLCHAIN_BUILD_FILE_CONTENT = """\
-load("@rules_cc//cc:defs.bzl", "cc_toolchain", "cc_library")
-load("@{gcc_toolchain_workspace_name}//toolchain:cc_toolchain_config.bzl", "cc_toolchain_config")
-{fortran_load}
-load("//:tool_paths.bzl", "tool_paths")
+# The public targets of a toolchain repository, aliased by the hub so that they track the GCC
+# version selected by the flag.
+_HUB_ALIASED_TARGETS = [
+ "_cc_toolchain",
+ "all_files",
+ "ar",
+ "ar_files",
+ "as",
+ "as_files",
+ "cc_toolchain_config",
+ "compiler_files",
+ "coverage_files",
+ "dwp_files",
+ "gcc",
+ "gcov",
+ "include",
+ "ld",
+ "ld.bfd",
+ "ld_files",
+ "lib",
+ "libasan",
+ "liblsan",
+ "libstdcxx",
+ "libstdcxx_static",
+ "libtsan",
+ "libubsan",
+ "linker_files",
+ "lld_files",
+ "nm",
+ "objcopy",
+ "objcopy_files",
+ "objdump",
+ "ranlib",
+ "readelf",
+ "strip",
+ "strip_files",
+]
-package(default_visibility = ["//visibility:public"])
+_HUB_BUILD_FILE_HEADER = '''\
+load("@bazel_skylib//lib:selects.bzl", "selects")
-{fortran_toolchain}
+package(default_visibility = ["//visibility:public"])'''
+_HUB_TOOLCHAIN_TEMPLATE = '''\
toolchain(
- name = "cc_toolchain",
+ name = "cc_toolchain{suffix}",
exec_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
target_compatible_with = {target_compatible_with},
target_settings = {target_settings},
- toolchain = ":_cc_toolchain",
+ toolchain = "@{toolchain_repo}//:_cc_toolchain",
toolchain_type = "@rules_cc//cc:toolchain_type",
-)
+)'''
+
+_HUB_FORTRAN_TOOLCHAIN_TEMPLATE = '''\
+toolchain(
+ name = "fortran_toolchain{suffix}",
+ exec_compatible_with = [
+ "@platforms//os:linux",
+ "@platforms//cpu:x86_64",
+ ],
+ target_compatible_with = {target_compatible_with},
+ target_settings = {target_settings},
+ toolchain = "@{toolchain_repo}//:_fortran_toolchain",
+ toolchain_type = "{fortran_toolchain_type}",
+)'''
+
+_TOOLCHAIN_BUILD_FILE_CONTENT = """\
+load("@rules_cc//cc:defs.bzl", "cc_toolchain", "cc_library")
+load("@{gcc_toolchain_workspace_name}//toolchain:cc_toolchain_config.bzl", "cc_toolchain_config")
+{fortran_load}
+load("//:tool_paths.bzl", "tool_paths")
+
+package(default_visibility = ["//visibility:public"])
+
+{fortran_toolchain}
cc_toolchain(
name = "_cc_toolchain",