Skip to content

Commit 61da1d2

Browse files
wisechengyicopybara-github
authored andcommitted
Support multiple --bazelrc on command line
Address #7489 ### Motivation Multiple --bazelrc on CLI would be useful for us for various reasons: Mostly importantly, we will have very long argument lists to pass to bazel query, so they have to be in a bazelrc file. `import/try import` in bazelrc would still work but very awkwardly. For example, if there are multiple bazelrc files to import, say `A` and `B`, import `A` needs to be added into `$WORKSPACE/.bazelrc` import `B` needs to be added into `A` meaning the former bazelrc file needs to know what comes next. Therefore allowing multiple --bazelrc would greatly improve the ergonomics, so the caller can create and append the new bazelrc without modifying the previous rc files. ### Note Options passed on CLI will still overwrite any options specified in any bazelrcs. ### Result `bazel --bazelrc=x.rc --bazelrc=y.rc ...` now works. ``` --bazelrc (a string; default: see description) The location of the user .bazelrc file containing default values of Bazel options. This option can also be chained together. E.g. `--bazelrc=x.rc --bazelrc=y.rc` so options in both RCs will be read. Note: `--bazelrc x.rc y.rc` is illegal, and each bazelrc file needs to be accompanied by --bazelrc flag before it. If unspecified, Bazel uses the first .bazelrc file it finds in the following two locations: the workspace directory, then the user's home directory. Use /dev/null to disable the search for a user rc file, e.g. in release builds. ``` Closes #12740. PiperOrigin-RevId: 364407234
1 parent 29e9369 commit 61da1d2

File tree

7 files changed

+263
-15
lines changed

7 files changed

+263
-15
lines changed

site/docs/guide.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -776,8 +776,17 @@ before the command (`build`, `test`, etc).
776776
4. **The user-specified RC file**, if specified with
777777
<code>--bazelrc=<var>file</var></code>
778778

779-
This flag is optional. However, if the flag is specified, then the file must
780-
exist.
779+
This flag is optional but can also be specified multiple times.
780+
781+
`/dev/null` indicates that all further `--bazelrc`s will be ignored, which
782+
is useful to disable the search for a user rc file, e.g. in release builds.
783+
784+
For example:
785+
```
786+
--bazelrc=x.rc --bazelrc=y.rc --bazelrc=/dev/null --bazelrc=z.rc
787+
```
788+
1. `x.rc` and `y.rc` are read.
789+
2. `z.rc` is ignored due to the prior `/dev/null`.
781790
782791
In addition to this optional configuration file, Bazel looks for a global rc
783792
file. For more details, see the [global bazelrc section](#global_bazelrc).

src/main/cpp/blaze_util.cc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include <stdio.h>
2020
#include <stdlib.h>
2121
#include <string.h>
22+
23+
#include <algorithm>
2224
#include <cassert>
2325
#include <iostream>
2426

@@ -35,6 +37,7 @@
3537
namespace blaze {
3638

3739
using std::map;
40+
using std::min;
3841
using std::string;
3942
using std::vector;
4043

@@ -74,6 +77,40 @@ bool GetNullaryOption(const char *arg, const char *key) {
7477
return true;
7578
}
7679

80+
std::vector<std::string> GetAllUnaryOptionValues(
81+
const vector<string>& args, const char* key,
82+
const char* ignore_after_value) {
83+
vector<std::string> values;
84+
for (vector<string>::size_type i = 0; i < args.size(); ++i) {
85+
if (args[i] == "--") {
86+
// "--" means all remaining args aren't options
87+
return values;
88+
}
89+
90+
const char* next_arg = args[std::min(i + 1, args.size() - 1)].c_str();
91+
const char* result = GetUnaryOption(args[i].c_str(), next_arg, key);
92+
if (result != nullptr) {
93+
// 'key' was found and 'result' has its value.
94+
values.push_back(result);
95+
96+
if (ignore_after_value != nullptr &&
97+
strcmp(result, ignore_after_value) == 0) {
98+
break;
99+
}
100+
}
101+
102+
// This is a pointer comparison, so equality means that the result must be
103+
// from the next arg instead of happening to match the value from
104+
// "--key=<value>" string, in which case we need to advance the index to
105+
// skip the next arg for later iterations.
106+
if (result == next_arg) {
107+
i++;
108+
}
109+
}
110+
111+
return values;
112+
}
113+
77114
const char* SearchUnaryOption(const vector<string>& args,
78115
const char *key, bool warn_if_dupe) {
79116
if (args.empty()) {

src/main/cpp/blaze_util.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ bool GetNullaryOption(const char *arg, const char *key);
5353
const char* SearchUnaryOption(const std::vector<std::string>& args,
5454
const char* key, bool warn_if_dupe);
5555

56+
// Searches for 'key' in 'args' using GetUnaryOption. Arguments found after '--'
57+
// are omitted from the search.
58+
// If 'ignore_after_value' is not nullptr, all values matching the 'key'
59+
// following 'ignore_after_value' will be ignored. Returns the values of the
60+
// 'key' flag iff it occurs in args. Returns empty vector otherwise.
61+
std::vector<std::string> GetAllUnaryOptionValues(
62+
const std::vector<std::string>& args, const char* key,
63+
const char* ignore_after_value = nullptr);
64+
5665
// Searches for '--flag_name' and '--noflag_name' in 'args' using
5766
// GetNullaryOption. Arguments found after '--' are omitted from the search.
5867
// Returns true if '--flag_name' is a flag in args and '--noflag_name' does not

src/main/cpp/option_processor.cc

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,24 @@ std::set<std::string> GetOldRcPaths(
201201
internal::FindRcAlongsideBinary(cwd, path_to_binary);
202202
candidate_bazelrc_paths = {workspace_rc, binary_rc, system_bazelrc_path};
203203
}
204-
string user_bazelrc_path = internal::FindLegacyUserBazelrc(
205-
SearchUnaryOption(startup_args, "--bazelrc", /* warn_if_dupe */ true),
206-
workspace);
207-
if (!user_bazelrc_path.empty()) {
208-
candidate_bazelrc_paths.push_back(user_bazelrc_path);
204+
vector<std::string> cmd_line_rc_files =
205+
GetAllUnaryOptionValues(startup_args, "--bazelrc", "/dev/null");
206+
// Divide the cases where the vector is empty vs not, as
207+
// `FindLegacyUserBazelrc` has a case for rc_file to be a nullptr.
208+
if (cmd_line_rc_files.empty()) {
209+
string user_bazelrc_path =
210+
internal::FindLegacyUserBazelrc(nullptr, workspace);
211+
if (!user_bazelrc_path.empty()) {
212+
candidate_bazelrc_paths.push_back(user_bazelrc_path);
213+
}
214+
} else {
215+
for (auto& rc_file : cmd_line_rc_files) {
216+
string user_bazelrc_path =
217+
internal::FindLegacyUserBazelrc(rc_file.c_str(), workspace);
218+
if (!user_bazelrc_path.empty()) {
219+
candidate_bazelrc_paths.push_back(user_bazelrc_path);
220+
}
221+
}
209222
}
210223
// DedupeBlazercPaths returns paths whose canonical path could be computed,
211224
// therefore these paths must exist.
@@ -370,11 +383,10 @@ blaze_exit_code::ExitCode OptionProcessor::GetRcFiles(
370383

371384
// Get the command-line provided rc, passed as --bazelrc or nothing if the
372385
// flag is absent.
373-
const char* cmd_line_rc_file =
374-
SearchUnaryOption(cmd_line->startup_args, "--bazelrc",
375-
/* warn_if_dupe */ true);
376-
if (cmd_line_rc_file != nullptr) {
377-
string absolute_cmd_line_rc = blaze::AbsolutePathFromFlag(cmd_line_rc_file);
386+
vector<std::string> cmd_line_rc_files =
387+
GetAllUnaryOptionValues(cmd_line->startup_args, "--bazelrc", "/dev/null");
388+
for (auto& rc_file : cmd_line_rc_files) {
389+
string absolute_cmd_line_rc = blaze::AbsolutePathFromFlag(rc_file);
378390
// Unlike the previous 3 paths, where we ignore it if the file does not
379391
// exist or is unreadable, since this path is explicitly passed, this is an
380392
// error. Check this condition here.

src/main/java/com/google/devtools/build/lib/bazel/BazelStartupOptionsModule.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,19 @@ public static final class Options extends OptionsBase {
3333
valueHelp = "<path>",
3434
help =
3535
"The location of the user .bazelrc file containing default values of "
36-
+ "Bazel options. If unspecified, Bazel uses the first .bazelrc file it finds in "
36+
+ "Bazel options. "
37+
+ "/dev/null indicates that all further `--bazelrc`s will be ignored, "
38+
+ "which is useful to disable the search for a user rc file, "
39+
+ "e.g. in release builds.\n"
40+
+ "This option can also be specified multiple times.\n"
41+
+ "E.g. with "
42+
+ "`--bazelrc=x.rc --bazelrc=y.rc --bazelrc=/dev/null --bazelrc=z.rc`,\n"
43+
+ " 1) x.rc and y.rc are read.\n"
44+
+ " 2) z.rc is ignored due to the prior /dev/null.\n"
45+
+ "If unspecified, Bazel uses the first .bazelrc file it finds in "
3746
+ "the following two locations: the workspace directory, then the user's home "
38-
+ "directory. Use /dev/null to disable the search for a user rc file, e.g. in "
39-
+ "release builds.")
47+
+ "directory.\n"
48+
+ "Note: command line options will always supersede any option in bazelrc.")
4049
public String blazerc;
4150

4251
// TODO(b/36168162): Remove this after the transition period is ower. This now only serves to

src/test/cpp/blaze_util_test.cc

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,4 +234,140 @@ TEST_F(BlazeUtilTest, TestSearchUnaryCommandOptionWarnsAboutDuplicates) {
234234
}
235235
}
236236

237+
void assert_equal_vector_char_pointer(std::vector<std::string> expected,
238+
std::vector<std::string> actual) {
239+
ASSERT_EQ(actual.size(), expected.size())
240+
<< "Vectors expected and actual are of unequal length";
241+
242+
for (int i = 0; i < actual.size(); ++i) {
243+
ASSERT_EQ(actual[i], expected[i])
244+
<< "Vectors expected and actual differ at index " << i;
245+
}
246+
}
247+
248+
TEST_F(BlazeUtilTest, TestSearchAllUnaryForEmpty) {
249+
assert_equal_vector_char_pointer(
250+
{}, GetAllUnaryOptionValues({"bazel", "build", ":target"}, ""));
251+
}
252+
253+
TEST_F(BlazeUtilTest, TestSearchAllUnaryFlagNotPresent) {
254+
assert_equal_vector_char_pointer(
255+
{}, GetAllUnaryOptionValues({"bazel", "build", ":target"}, "--flag"));
256+
}
257+
258+
TEST_F(BlazeUtilTest, TestSearchAllUnaryOptionWithEquals) {
259+
assert_equal_vector_char_pointer(
260+
{"value"}, GetAllUnaryOptionValues(
261+
{"bazel", "--flag=value", "build", ":target"}, "--flag"));
262+
}
263+
264+
TEST_F(BlazeUtilTest, TestSearchAllUnaryOptionWithEquals2) {
265+
assert_equal_vector_char_pointer(
266+
{"value1", "value2"},
267+
GetAllUnaryOptionValues(
268+
{"bazel", "--flag=value1", "--flag=value2", "build", ":target"},
269+
"--flag"));
270+
}
271+
272+
TEST_F(BlazeUtilTest, TestSearchAllUnaryOptionWithRepeatingFlag) {
273+
assert_equal_vector_char_pointer(
274+
{"--flag"}, GetAllUnaryOptionValues({"bazel", "--flag", "--flag",
275+
"value1", "build", ":target"},
276+
"--flag"));
277+
}
278+
279+
TEST_F(BlazeUtilTest, TestSearchAllUnaryOptionWithRepeatingFlagOptions) {
280+
assert_equal_vector_char_pointer(
281+
{"--flag"}, GetAllUnaryOptionValues(
282+
{"bazel", "--flag", "--flag", "value1"}, "--flag"));
283+
}
284+
285+
TEST_F(BlazeUtilTest, TestSearchAllUnaryOptionValuesWithEquals) {
286+
assert_equal_vector_char_pointer(
287+
{"--flag", "value1"},
288+
GetAllUnaryOptionValues({"bazel", "--flag=--flag", "--flag", "value1"},
289+
"--flag"));
290+
}
291+
292+
TEST_F(BlazeUtilTest, TestSearchAllUnaryOptionWithEquals3) {
293+
assert_equal_vector_char_pointer(
294+
{"value1", "value2", "value3"},
295+
GetAllUnaryOptionValues({"bazel", "--flag=value1", "--flag=value2",
296+
"--flag=value3", "build", ":target"},
297+
"--flag"));
298+
}
299+
300+
TEST_F(BlazeUtilTest, TestSearchAllUnaryOptionWithoutEquals) {
301+
assert_equal_vector_char_pointer(
302+
{"value"},
303+
GetAllUnaryOptionValues({"bazel", "--flag", "value", "build", ":target"},
304+
"--flag"));
305+
}
306+
307+
TEST_F(BlazeUtilTest, TestSearchAllUnaryOptionWithoutEquals2) {
308+
assert_equal_vector_char_pointer(
309+
{"value1", "value2"},
310+
GetAllUnaryOptionValues(
311+
{"bazel", "--flag", "value1", "--flag", "value2", "build", ":target"},
312+
"--flag"));
313+
}
314+
315+
TEST_F(BlazeUtilTest, TestSearchAllUnaryCommandOptionWithEquals) {
316+
assert_equal_vector_char_pointer(
317+
{"value"},
318+
GetAllUnaryOptionValues({"bazel", "build", ":target", "--flag", "value"},
319+
"--flag"));
320+
}
321+
322+
TEST_F(BlazeUtilTest, TestSearchAllUnaryCommandOptionWithEquals2) {
323+
assert_equal_vector_char_pointer(
324+
{"value1", "value2"},
325+
GetAllUnaryOptionValues(
326+
{"bazel", "build", ":target", "--flag", "value1", "--flag", "value2"},
327+
"--flag"));
328+
}
329+
330+
TEST_F(BlazeUtilTest, TestSearchAllUnaryCommandOptionWithoutEquals) {
331+
assert_equal_vector_char_pointer(
332+
{"value"}, GetAllUnaryOptionValues(
333+
{"bazel", "build", ":target", "--flag=value"}, "--flag"));
334+
}
335+
336+
TEST_F(BlazeUtilTest, TestSearchAllUnaryCommandOptionWithoutEquals2) {
337+
assert_equal_vector_char_pointer(
338+
{"value1", "value2"},
339+
GetAllUnaryOptionValues(
340+
{"bazel", "build", ":target", "--flag=value1", "--flag=value2"},
341+
"--flag"));
342+
}
343+
344+
TEST_F(BlazeUtilTest, TestSearchAllUnarySkipsAfterDashDashWithEquals) {
345+
assert_equal_vector_char_pointer(
346+
{},
347+
GetAllUnaryOptionValues(
348+
{"bazel", "build", ":target", "--", "--flag", "value"}, "--flag"));
349+
}
350+
351+
TEST_F(BlazeUtilTest, TestSearchAllUnarySkipsAfterDashDashWithoutEquals) {
352+
assert_equal_vector_char_pointer(
353+
{}, GetAllUnaryOptionValues(
354+
{"bazel", "build", ":target", "--", "--flag=value"}, "--flag"));
355+
}
356+
357+
TEST_F(BlazeUtilTest, TestSearchAllUnaryCommandOptionWithIgnoreAfter) {
358+
assert_equal_vector_char_pointer(
359+
{"value1", "/dev/null"},
360+
GetAllUnaryOptionValues({"bazel", "build", ":target", "--flag", "value1",
361+
"--flag", "/dev/null", "--flag", "value3"},
362+
"--flag", "/dev/null"));
363+
}
364+
365+
TEST_F(BlazeUtilTest, TestSearchAllUnaryCommandOptionWithIgnoreAfterDevNull) {
366+
assert_equal_vector_char_pointer(
367+
{"/dev/null"}, GetAllUnaryOptionValues(
368+
{"bazel", "build", ":target", "--flag", "/dev/null",
369+
"--flag", "value2", "--flag", "value3"},
370+
"--flag", "/dev/null"));
371+
}
372+
237373
} // namespace blaze

src/test/shell/integration/startup_options_test.sh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,40 @@ function test_autodetect_server_javabase() {
8282
bazel --noautodetect_server_javabase version &> $TEST_log || fail "Should pass"
8383
}
8484

85+
# Below are the regression tests for Issue #7489
86+
function test_multiple_bazelrc_later_overwrites_earlier() {
87+
# Help message only visible with --help_verbosity=medium
88+
help_message_in_description="--${PRODUCT_NAME}rc (a string; default: see description)"
89+
90+
echo "help --help_verbosity=short" > 1.rc
91+
echo "help --help_verbosity=medium" > 2.rc
92+
bazel "--${PRODUCT_NAME}rc=1.rc" "--${PRODUCT_NAME}rc=2.rc" help startup_options &> $TEST_log || fail "Should pass"
93+
expect_log "$help_message_in_description"
94+
95+
echo "help --help_verbosity=medium" > 1.rc
96+
echo "help --help_verbosity=short" > 2.rc
97+
bazel "--${PRODUCT_NAME}rc=1.rc" "--${PRODUCT_NAME}rc=2.rc" help startup_options &> $TEST_log || fail "Should pass"
98+
expect_not_log "$help_message_in_description"
99+
}
100+
101+
function test_multiple_bazelrc_set_different_options() {
102+
echo "common --verbose_failures" > 1.rc
103+
echo "common --test_output=all" > 2.rc
104+
bazel "--${PRODUCT_NAME}rc=1.rc" "--${PRODUCT_NAME}rc=2.rc" info --announce_rc &> $TEST_log || fail "Should pass"
105+
expect_log "Inherited 'common' options: --verbose_failures"
106+
expect_log "Inherited 'common' options: --test_output=all"
107+
}
108+
109+
function test_bazelrc_after_devnull_ignored() {
110+
echo "common --verbose_failures" > 1.rc
111+
echo "common --test_output=all" > 2.rc
112+
echo "common --definitely_invalid_config" > 3.rc
113+
114+
bazel "--${PRODUCT_NAME}rc=1.rc" "--${PRODUCT_NAME}rc=2.rc" "--${PRODUCT_NAME}rc=/dev/null" \
115+
"--${PRODUCT_NAME}rc=3.rc" info --announce_rc &> $TEST_log || fail "Should pass"
116+
expect_log "Inherited 'common' options: --verbose_failures"
117+
expect_log "Inherited 'common' options: --test_output=all"
118+
expect_not_log "--definitely_invalid_config"
119+
}
120+
85121
run_suite "${PRODUCT_NAME} startup options test"

0 commit comments

Comments
 (0)