Skip to content

Commit bc48f4c

Browse files
committed
Add support for %bazel.version*% variables in bazelrc imports
Follows the proposal: #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: #24043
1 parent acac65d commit bc48f4c

File tree

8 files changed

+247
-25
lines changed

8 files changed

+247
-25
lines changed

src/main/cpp/blaze.cc

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,9 +1481,7 @@ void PrintBazelLeaf() {
14811481
printf("%s\n", leaf.c_str());
14821482
}
14831483

1484-
void PrintVersionInfo(const string &self_path, const string &product_name) {
1485-
string build_label;
1486-
ExtractBuildLabel(self_path, &build_label);
1484+
void PrintVersionInfo(const string &build_label, const string &product_name) {
14871485
printf("%s %s\n", product_name.c_str(), build_label.c_str());
14881486
}
14891487

@@ -1495,7 +1493,8 @@ static void RunLauncher(const string &self_path,
14951493
const WorkspaceLayout &workspace_layout,
14961494
const string &workspace, LoggingInfo *logging_info,
14971495
StartupInterceptor *interceptor,
1498-
CommandExtensionAdder *command_extension_adder) {
1496+
CommandExtensionAdder *command_extension_adder,
1497+
const string &build_label) {
14991498
blaze_server = new BlazeServer(startup_options, command_extension_adder);
15001499

15011500
const std::optional<DurationMillis> command_wait_duration =
@@ -1566,8 +1565,6 @@ static void RunLauncher(const string &self_path,
15661565
option_processor, startup_options, logging_info,
15671566
extract_data_duration, command_wait_duration, blaze_server);
15681567
} else {
1569-
string build_label;
1570-
ExtractBuildLabel(self_path, &build_label);
15711568
RunClientServerMode(
15721569
server_exe, server_exe_args, server_dir, workspace_layout, workspace,
15731570
option_processor, startup_options, logging_info, extract_data_duration,
@@ -1593,8 +1590,15 @@ int Main(int argc, const char *const *argv, WorkspaceLayout *workspace_layout,
15931590
return blaze_exit_code::SUCCESS;
15941591
}
15951592

1593+
// Extract build_label to be used in two places:
1594+
// 1) PrintVersionInfo()
1595+
// 2) Resolving %bazel.version*% variables in .bazelrc files.
1596+
string build_label;
1597+
ExtractBuildLabel(self_path, &build_label);
1598+
option_processor->SetBuildLabel(build_label);
1599+
15961600
if (argc == 2 && strcmp(argv[1], "--version") == 0) {
1597-
PrintVersionInfo(self_path, option_processor->GetLowercaseProductName());
1601+
PrintVersionInfo(build_label, option_processor->GetLowercaseProductName());
15981602
return blaze_exit_code::SUCCESS;
15991603
}
16001604

@@ -1671,7 +1675,7 @@ int Main(int argc, const char *const *argv, WorkspaceLayout *workspace_layout,
16711675

16721676
RunLauncher(self_path, archive_contents, install_md5, *startup_options,
16731677
*option_processor, *workspace_layout, workspace, &logging_info,
1674-
interceptor, command_extension_adder);
1678+
interceptor, command_extension_adder, build_label);
16751679
return 0;
16761680
}
16771681

src/main/cpp/blaze.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ namespace blaze {
2626

2727
// Prints client version information to standard output, e.g. when invoking the
2828
// client with "--version".
29-
void PrintVersionInfo(const std::string& self_path,
29+
void PrintVersionInfo(const std::string& build_label,
3030
const std::string& product_name);
3131

3232
int Main(int argc, const char* const* argv, WorkspaceLayout* workspace_layout,

src/main/cpp/option_processor.cc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,8 @@ blaze_exit_code::ExitCode OptionProcessor::GetRcFiles(
447447
for (const std::string& top_level_bazelrc_path : rc_files) {
448448
std::unique_ptr<RcFile> parsed_rc;
449449
blaze_exit_code::ExitCode parse_rcfile_exit_code = ParseRcFile(
450-
workspace_layout, workspace, top_level_bazelrc_path, &parsed_rc, error);
450+
workspace_layout, workspace, top_level_bazelrc_path, build_label_,
451+
&parsed_rc, error);
451452
if (parse_rcfile_exit_code != blaze_exit_code::SUCCESS) {
452453
return parse_rcfile_exit_code;
453454
}
@@ -488,14 +489,16 @@ blaze_exit_code::ExitCode OptionProcessor::GetRcFiles(
488489
blaze_exit_code::ExitCode ParseRcFile(const WorkspaceLayout* workspace_layout,
489490
const std::string& workspace,
490491
const std::string& rc_file_path,
492+
const std::string& build_label,
491493
std::unique_ptr<RcFile>* result_rc_file,
492494
std::string* error) {
493495
assert(!rc_file_path.empty());
494496
assert(result_rc_file != nullptr);
495497

496498
RcFile::ParseError parse_error;
497499
std::unique_ptr<RcFile> parsed_file = RcFile::Parse(
498-
rc_file_path, workspace_layout, workspace, &parse_error, error);
500+
rc_file_path, workspace_layout, workspace, &parse_error, error,
501+
build_label);
499502
if (parsed_file == nullptr) {
500503
return internal::ParseErrorToExitCode(parse_error);
501504
}

src/main/cpp/option_processor.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ class OptionProcessor {
127127
// the failure. Otherwise, the server will handle any required logging.
128128
void PrintStartupOptionsProvenanceMessage() const;
129129

130+
// Sets the build label.
131+
void SetBuildLabel(const std::string &build_label) {
132+
build_label_ = build_label;
133+
}
134+
130135
// Parse the files in `blazercs` and return all options that need to be passed
131136
// to the server. The options are returned in the order they should be appear
132137
// on the command line (later options have precedence over earlier ones).
@@ -180,12 +185,16 @@ class OptionProcessor {
180185
// Path to the system-wide bazelrc configuration file.
181186
// This is configurable for testing purposes only.
182187
const std::string system_bazelrc_path_;
188+
189+
// Build label for Bazel.
190+
std::string build_label_;
183191
};
184192

185193
// Parses and returns the contents of the rc file.
186194
blaze_exit_code::ExitCode ParseRcFile(const WorkspaceLayout* workspace_layout,
187195
const std::string& workspace,
188196
const std::string& rc_file_path,
197+
const std::string& build_label,
189198
std::unique_ptr<RcFile>* result_rc_file,
190199
std::string* error);
191200

src/main/cpp/rc_file.cc

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <memory>
1818
#include <optional>
19+
#include <regex>
1920
#include <string>
2021
#include <utility>
2122
#include <vector>
@@ -31,6 +32,7 @@
3132
#include "absl/strings/match.h"
3233
#include "absl/strings/str_cat.h"
3334
#include "absl/strings/str_format.h"
35+
#include "absl/strings/str_replace.h"
3436
#include "absl/strings/str_split.h"
3537
#include "absl/strings/string_view.h"
3638
#include "absl/types/span.h"
@@ -43,18 +45,20 @@ static constexpr absl::string_view kCommandTryImport = "try-import";
4345
/*static*/ std::unique_ptr<RcFile> RcFile::Parse(
4446
const std::string& filename, const WorkspaceLayout* workspace_layout,
4547
const std::string& workspace, ParseError* error, std::string* error_text,
46-
ReadFileFn read_file, CanonicalizePathFn canonicalize_path) {
48+
const std::string& build_label, ReadFileFn read_file,
49+
CanonicalizePathFn canonicalize_path) {
4750
auto rcfile = absl::WrapUnique(new RcFile());
4851
std::vector<std::string> initial_import_stack = {filename};
49-
*error = rcfile->ParseFile(filename, workspace, *workspace_layout, read_file,
50-
canonicalize_path, initial_import_stack,
51-
error_text);
52+
*error = rcfile->ParseFile(filename, workspace, *workspace_layout,
53+
build_label, read_file, canonicalize_path,
54+
initial_import_stack, error_text);
5255
return (*error == ParseError::NONE) ? std::move(rcfile) : nullptr;
5356
}
5457

5558
RcFile::ParseError RcFile::ParseFile(const std::string& filename,
5659
const std::string& workspace,
5760
const WorkspaceLayout& workspace_layout,
61+
const std::string& build_label,
5862
ReadFileFn read_file,
5963
CanonicalizePathFn canonicalize_path,
6064
std::vector<std::string>& import_stack,
@@ -109,17 +113,24 @@ RcFile::ParseError RcFile::ParseFile(const std::string& filename,
109113
return ParseError::INVALID_FORMAT;
110114
}
111115

112-
std::string& import_filename = words[1];
116+
std::string import_filename = ReplaceBuildVars(build_label, words[1]);
113117
if (absl::StartsWith(import_filename, WorkspaceLayout::kWorkspacePrefix)) {
114118
const auto resolved_filename =
115119
workspace_layout.ResolveWorkspaceRelativeRcFilePath(workspace,
116120
import_filename);
117121
if (!resolved_filename.has_value()) {
118122
if (command == kCommandImport) {
123+
// If build variables were replaced in the filename, print out the
124+
// evaluated path so they know the file lookup that was attempted.
125+
std::string evaluated_line = ReplaceBuildVars(build_label, line);
126+
std::string evaluated_warning;
127+
if (line != evaluated_line) {
128+
evaluated_warning = absl::StrFormat("file evaluated to '%s' - ", evaluated_line);
129+
}
119130
*error_text = absl::StrFormat(
120131
"Nonexistent path in import declaration in config file '%s': '%s'"
121-
" (are you in your source checkout/WORKSPACE?)",
122-
canonical_filename, line);
132+
" (%sare you in your source checkout/WORKSPACE?)",
133+
canonical_filename, line, evaluated_warning);
123134
return ParseError::INVALID_FORMAT;
124135
}
125136
// For try-import, we ignore it if we couldn't find a file.
@@ -144,8 +155,8 @@ RcFile::ParseError RcFile::ParseFile(const std::string& filename,
144155

145156
import_stack.push_back(import_filename);
146157
if (ParseError parse_error =
147-
ParseFile(import_filename, workspace, workspace_layout, read_file,
148-
canonicalize_path, import_stack, error_text);
158+
ParseFile(import_filename, workspace, workspace_layout, build_label,
159+
read_file, canonicalize_path, import_stack, error_text);
149160
parse_error != ParseError::NONE) {
150161
if (parse_error == ParseError::UNREADABLE_FILE &&
151162
command == kCommandTryImport) {
@@ -175,5 +186,60 @@ bool RcFile::ReadFileDefault(const std::string& filename, std::string* contents,
175186
std::string RcFile::CanonicalizePathDefault(const std::string& filename) {
176187
return blaze_util::MakeCanonical(filename.c_str());
177188
}
189+
namespace {
190+
// When the build label can't be parsed into a proper semantic version (per
191+
// semver.org), this will be the value for each of the variables below.
192+
constexpr char kNoVersion[] = "no_version";
193+
// Variables that can be interpolated in .bazelrc when importing files.
194+
constexpr char kBazelVersion[] =
195+
"%bazel.version%"; // The full build label Eg. 8.4.2 or 9.0.0-pre.20251022.1
196+
constexpr char kBazelVersionMajor[] =
197+
"%bazel.version.major%"; // Eg. "8" in 8.4.2
198+
constexpr char kBazelVersionMinor[] =
199+
"%bazel.version.minor%"; // Eg. "4" in 8.4.2
200+
constexpr char kBazelVersionPatch[] =
201+
"%bazel.version.patch%"; // Eg. "2" in 8.4.2
202+
constexpr char kBazelVersionPrerelease[] =
203+
"%bazel.version.prerelease%"; // Eg. "pre.20251022" in 9.0.0-pre.20251022
204+
constexpr char kBazelVersionBuildMetadata[] =
205+
"%bazel.version.buildmetadata%"; // Eg. "c0075e8a6b" in 9.0.0+c0075e8a6b
206+
constexpr char kBazelVersionMajorMinor[] =
207+
"%bazel.version.major.minor%"; // Eg. "8.4" in 8.4.2
208+
constexpr char kBazelVersionMajorMinorPatch[] =
209+
"%bazel.version.major.minor.patch%"; // Eg. "9.0.0" in 9.0.0-pre.20251022.1
210+
211+
// Semantic version regex copied verbatim from
212+
// https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
213+
const std::regex kSemverRe(
214+
R"(^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$)");
215+
} // namespace
216+
217+
std::string ReplaceBuildVars(const std::string& build_label,
218+
absl::string_view import_filename) {
219+
std::string version = kNoVersion;
220+
std::string major = kNoVersion;
221+
std::string minor = kNoVersion;
222+
std::string patch = kNoVersion;
223+
std::string prerelease = kNoVersion;
224+
std::string buildmetadata = kNoVersion;
225+
if (std::smatch m; std::regex_match (build_label, m, kSemverRe)) {
226+
version = m[0];
227+
major = m[1];
228+
minor = m[2];
229+
patch = m[3];
230+
prerelease = m[4];
231+
buildmetadata = m[5];
232+
}
233+
return absl::StrReplaceAll(
234+
import_filename,
235+
{{kBazelVersion, version},
236+
{kBazelVersionMajor, major},
237+
{kBazelVersionMinor, minor},
238+
{kBazelVersionPatch, patch},
239+
{kBazelVersionPrerelease, prerelease},
240+
{kBazelVersionBuildMetadata, buildmetadata},
241+
{kBazelVersionMajorMinor, absl::StrCat(major, ".", minor)},
242+
{kBazelVersionMajorMinorPatch, absl::StrCat(major, ".", minor, "." + patch)}});
243+
}
178244

179245
} // namespace blaze

src/main/cpp/rc_file.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#define BAZEL_SRC_MAIN_CPP_RC_FILE_H_
1616

1717
#include <memory>
18+
#include <regex>
1819
#include <string>
1920
#include <vector>
2021

@@ -44,7 +45,7 @@ class RcFile {
4445
static std::unique_ptr<RcFile> Parse(
4546
const std::string& filename, const WorkspaceLayout* workspace_layout,
4647
const std::string& workspace, ParseError* error, std::string* error_text,
47-
ReadFileFn read_file = &ReadFileDefault,
48+
const std::string &build_label, ReadFileFn read_file = &ReadFileDefault,
4849
CanonicalizePathFn canonicalize_path = &CanonicalizePathDefault);
4950

5051
// Movable and copyable.
@@ -69,6 +70,7 @@ class RcFile {
6970
ParseError ParseFile(const std::string& filename,
7071
const std::string& workspace,
7172
const WorkspaceLayout& workspace_layout,
73+
const std::string& build_label,
7274
ReadFileFn read_file,
7375
CanonicalizePathFn canonicalize_path,
7476
std::vector<std::string>& import_stack,
@@ -86,6 +88,8 @@ class RcFile {
8688
OptionMap options_;
8789
};
8890

91+
std::string ReplaceBuildVars(const std::string& build_label,
92+
absl::string_view import_filename);
8993
} // namespace blaze
9094

9195
#endif // BAZEL_SRC_MAIN_CPP_RC_FILE_H_

0 commit comments

Comments
 (0)