Skip to content

Conversation

@willstranton
Copy link
Contributor

@willstranton willstranton commented Oct 29, 2025

Adapts the proposal:
#24043 (comment) by mattyclarkson@ to add support for bazel version variables when doing import or try-import of additional .bazelrc files. The 2 variables added are:

  • %bazel.version.major% - evaluates to 8 when build label is 8.4.2
  • %bazel.version.major.minor% - evaluates to 8.4 when build label is 8.4.2

Eg. If your bazel version was 8.4.2 and your .bazelrc had the following:

try-import %bazel.version.major%.bazelrc
import %bazel.version.major.minor%.bazelrc

It would be evaluated to:

try-import 8.bazelrc
import 8.4.bazelrc

Implementation details:

Build label extraction:

Before: The build label was extracted out only when calling bazel --version and when running in client server mode.

After: Now we always extract the the build label out early in the start of main

Piping of build label:

The build label is stored in the OptionProcessor so that it can be passed to RcFile when parsing occurs.

Parsing and mapping of variables:

The build label is parsed into a new SemVer struct before RcFile::ParseFile. Only if the build label is a proper semantic version, are the specific major and minor values of a semantic version extracted, otherwise, the SemVer struct will contain no_version for each SemVer value. Interpolation of the build label variables (%bazel.version.major% and %bazel.version.major.minor%) happens first, followed by the existing %workspace% variable.

Fixes: #24043

@github-actions github-actions bot added team-Rules-CPP Issues for C++ rules awaiting-review PR is awaiting review from an assigned reviewer labels Oct 29, 2025
@meisterT meisterT requested a review from lberki October 29, 2025 08:12
@brentleyjones
Copy link
Contributor

Does %bazel.version.minor% returning 4 instead of 8.4 make sense? Would anyone ever need that over just having these four?

  • %bazel.version.major%: 8
  • %bazel.version.minor%: 8.4
  • %bazel.version.patch%: 8.4.3
  • %bazel.version%: 8.4.3rc2

@willstranton
Copy link
Contributor Author

willstranton commented Oct 29, 2025

Re: %bazel.version.minor% returning 4 instead of 8.4

Note: Even though I put out a patch for this, I have little stake in this since I don't plan on using this feature. I'm happy to go with whatever the community decides (or what the official bazel maintainers decide). Here are some thoughts:

I put a small example in the commit message for just returning the individual number with the import 8.4-2.bazelrc filename. This doesn't follow the standard #.#.# format and has a dash for the last number instead. Having each part be its own value provides more flexibility in how they want to name the .bazelrc. They could have 8-4-2.bazelrc, 8_4_2.bazelrc. Is that a realistic use case? Maybe - I don't know.

I do see your point on how unlikely it is to need just the patch number by itself. To update your naming:

  • %bazel.version.major%: 8 (unchanged)
  • %bazel.version.minor% -> %bazel.version.major.minor% -> 8.4
  • %bazel.version.patch% -> %bazel.version.major.minor.patch% -> 8.4.3
  • %bazel.version%: 8.4.3rc2

So in total, still only 4 variables, but more verbose names to be clearer what it evaluates to.


Also, wanted to clarify in your example, because you have %bazel.version% evaluate to 8.4.3rc2, but the %bazel.version.patch% was only 8.4.3 - it should be 8.4.3rc2 right? The current patch treats everything after the second dot as the patch number. If we're going to try to go full semantic versioning, then I should fix that and follow https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions and treat the rc2 as the <prerelease> or <build> number.

@willstranton
Copy link
Contributor Author

Sorry, I feel like I'm bikeshedding when talking about <prerelease>/<build> numbers. Let's ignore that and focus on getting variables for what semver.org refers to as <version core>, which are just the major minor and patch numbers and <valid semver>, the full build label. For 8.4.3rc2, the rc2 should not be a part of any of the current patch variable name candidates: %bazel.version.patch%, %bazel.version.major.minor.patch%.

@willstranton
Copy link
Contributor Author

Updated the code. This now does proper semantic version processing instead of the naive split by dots I was doing earlier.

re: variables available - In the spirit of why not both, I just implemented the original proposal and the variant of your new proposal that I mentioned in the previous comment. So for the build label: 9.4.2-pre.20251022.1 the variables available and their values are:

  • %bazel.version%: 9.4.2-pre.20251022.1
  • %bazel.version.major%: 9
  • %bazel.version.minor%: 4
  • %bazel.version.patch%: 2
  • %bazel.version.prerelease%: pre.20251022.1
  • %bazel.version.buildmetadata%: (empty)
  • %bazel.version.major.minor%: 9.4
  • %bazel.version.major.minor.patch%: 9.4.2

I think this should handle all cases that a user would want with enough flexibility, clarity, and convenience baked in.

Follows the proposal:
bazelbuild#24043 (comment) by
mattyclarkson@ to add support for the following variables when importing
additional bazelrc files.  Variables are followed by their values for the
hypothetical build label 9.4.2-pre.20251022.1:

- %bazel.version% - 9.4.2-pre.20251022.1
- %bazel.version.major% - 9
- %bazel.version.minor% - 4
- %bazel.version.patch% - 2
- %bazel.version.prerelease% - pre.20251022.1
- %bazel.version.buildmetadata% - (empty)
- %bazel.version.major.minor% - 9.4
- %bazel.version.major.minor.patch% - 9.4.2

Eg. If your bazel version was 8.4.2-pre.20251022.1 and your .bazelrc had the
following:

> try-import %bazel.version%.bazelrc
> import %bazel.version.major%.%bazel.version.minor%-%bazel.version.patch%.bazelrc
> try-import %bazel.version.major%.bazelrc

It would be evaluated to:

> try-import 8.4.2.bazelrc
> import 8.4-2.bazelrc
> try-import 8.bazelrc

# Implementation details:

## Build label extraction:

Before: The build label was extracted out only when calling `bazel --version`
and when running in client server mode.

After: Now we always extract the the build label out early in the start of main

## Piping of build label:

The build label is stored in the OptionProcessor so that it can be passed to
`RcFile` when parsing occurs.

## Parsing and mapping of variables:

The build label is parsed during `RcFile::ParseFile` into its constituent parts
(major, minor, patch).  This occurs EACH TIME an import statement is found in a
.rc file. This is wasteful, but the logic is simple enough that it shouldn't
matter. Interpolation of the build label happens first, followed by
`%workspace%`.

Fixes: bazelbuild#24043
@fmeum
Copy link
Collaborator

fmeum commented Oct 30, 2025

I would suggest limiting this to just two variables, returning 8 and 8.4 respectively for Bazel version 8.4.2rc2. There's pretty much never a reason to use anything but the latest patch version for a given minor version anyway.

Every new variable becomes a public API surface that users will end up (possibly inadvertently) depending on, so there should be at least some potential use case for each variable we add.

@meisterT meisterT added team-Core Skyframe, bazel query, BEP, options parsing, bazelrc and removed team-Rules-CPP Issues for C++ rules labels Oct 30, 2025
The current use case/feature requested just requires these two.
Per fmeum in bazelbuild#27447 (comment)

> There's pretty much never a reason to use anything but the latest patch
> version for a given minor version anyway.
>
> Every new variable becomes a public API surface that users will end up
> (possibly inadvertently) depending on, so there should be at least some
> potential use case for each variable we add.
@willstranton
Copy link
Contributor Author

Ok, reduce the possible variables to just two:

  • %bazel.version.major% - 8 in 8.4.2
  • %bazel.version.major.minor% - 8.4 in 8.4.2

@brentleyjones
Copy link
Contributor

Can you also update the PR description with this change?

@willstranton
Copy link
Contributor Author

Can you also update the PR description with this change?

Done

Instead of evaluating the semantic version from the build_label for each import
call, the build_label is parsed once, before being used in rc_file reading.
@willstranton
Copy link
Contributor Author

Added a minor change to stop reparsing build_label multiple times, and instead parse once and store in a SemVer struct.

@lberki
Copy link
Contributor

lberki commented Nov 4, 2025

@meteorcloudy WDYT? I'm inclined to approve this because this is about the most minimal thing I can think of to solve the "version-specific flags in a bazelrc" problem.

I do see why this would be useful, but I'm also well aware that I'm way more prone to approving new features than I should be so I'd like a second pair of eyes on this.

In particular, I'm curious about two things:

  1. Whether it's reasonable to extend the bazelrc "little language" in this way (I'm not too fond of little languages, and bazelrc is definitely one)

  2. Whether it's better to provide some range-based mechanism instead; I'm not sure the exact match semantics of this PR are very useful because I think most people will want to say "add if version is higher than 8.3" instead of "add if version is exactly 8.3"

If we eventually go with the range-based approach, I think I'd rather we adapt the import syntax instead of substitution. So something like, off the top of my head, import-if minor-version GE 8.3 bazelrc.8.3 (this would apply to 8.3, 8.4, etc. but not 9.0, 8.2 or 7.4)

@meteorcloudy
Copy link
Member

I'm more inclined to stick with current simpler %bazel.version*% variables approach, because

  • This feature is likely to be used by Bazel rules or dependencies to support multiple Bazel versions, but not end users.
  • The Bazel versions those rules & dependencies want to test against are the latest release of each LTS since end users should always be able to upgrade to the latest release effortlessly.
  • That means for most cases, even %bazel.version.major% should be enough. But I'm fine with also adding %bazel.version.major.minor%

@meteorcloudy meteorcloudy requested a review from Copilot November 4, 2025 13:08

This comment was marked as spam.

@lberki
Copy link
Contributor

lberki commented Nov 5, 2025

I'd be fine with doing this as proposed for major versions only because those change much less frequently.

That said, how would a dependency add entries to a bazelrc? Am I missing something?

@brentleyjones
Copy link
Contributor

Incompatible flags can be introduced in minor versions, so we minimally need some way of specifying that level of granularity. I would prefer import-if, but substitution seems like a quick way to add support.

@meteorcloudy
Copy link
Member

@willstranton Are you willing to give it a try to implement the conditional import syntax?

@meteorcloudy
Copy link
Member

@bazel-io fork 9.0.0

@willstranton
Copy link
Contributor Author

Are you willing to give it a try to implement the conditional import syntax?

Only if there is clear direction on what that conditional import syntax would be and what it would entail. If the followup question is: can you help drive consensus, then the answer is no. I think someone closer to the issue (like the original issue filer, or more active participants in the discussion, i.e. brentleyjones@ would be better suited to that).

The "off the top of my head" proposal by iberki in #27447 (comment) is under-specified:

import-if minor-version GE 8.3 bazelrc.8.3 (this would apply to 8.3, 8.4, etc. but not 9.0, 8.2 or 7.4)

  • The import-if minor-version syntax works for minor versions. A major version constraint would then need a corresponding import-if major-version.
  • GE (>=) implies that there should be a G (>). And the reverse for L (<) and LE (<=)

At which point, this ends up making a new bazel version constraint syntax, when existing version constraint syntax conventions already exist (and could be used instead). Eg.


In any case, I think it would be better to go back to discussing this in the original issue if there's further debate: #24043

@willstranton
Copy link
Contributor Author

Updating here that the alternative conditional import syntax is in #27675

I'm also a bit confused since this tagged for 9.0 in #27447 (comment) (is that what that means?) but we're still talking/debating on what direction this should go.

@meteorcloudy
Copy link
Member

Thanks, let's get #27675 into 9.0.0 instead

@github-actions github-actions bot removed the awaiting-review PR is awaiting review from an assigned reviewer label Nov 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

team-Core Skyframe, bazel query, BEP, options parsing, bazelrc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support bazel-version specific flags in bazelrc

6 participants