Skip to content

Commit 0ab1dde

Browse files
committed
tools/bazel_adaptive: pass through non-jobs bazel commands
Do not add jobs or monitor bazel commands that are not known to support the --jobs argument. Pass them through so that the wrapper can be used as a generic bazel alias. Signed-off-by: Jarno Rajahalme <jarno@isovalent.com>
1 parent 262e70b commit 0ab1dde

2 files changed

Lines changed: 110 additions & 14 deletions

File tree

tools/bazel_adaptive.py

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
options. It only scans arguments before Bazel's "--" delimiter for --jobs,
2525
and when --jobs is absent inserts the adaptive --jobs value immediately before
2626
that delimiter or at the end of the argument list.
27+
- Only adapt commands that accept --jobs: build, test, run, coverage, fetch,
28+
cquery, and aquery. Other Bazel commands are exec'd directly without adding or
29+
rewriting --jobs, so the wrapper can be used as a general Bazel entry point.
30+
The detector is conservative around unknown startup options with separate
31+
values; ambiguous commands pass through unchanged.
2732
- Read the action timeout from BAZEL_ADAPTIVE_BUILD_TIMEOUT as a bare positive
2833
number of seconds; default to 150 seconds. This applies to builds and tests
2934
and is independent of Bazel's --test_timeout.
@@ -306,6 +311,8 @@
306311
DEFAULT_ACTION_TIMEOUT_SECONDS = 150
307312
BUILD_TIMEOUT_ENV = "BAZEL_ADAPTIVE_BUILD_TIMEOUT"
308313

314+
JOBS_COMMANDS = frozenset({"build", "test", "run", "coverage", "fetch", "cquery", "aquery"})
315+
309316
DEFAULT_LOW_MEMORY_THRESHOLD_MB = 1024
310317
LOW_MEMORY_THRESHOLD_ENV = "BAZEL_ADAPTIVE_LOW_MEMORY_THRESHOLD_MB"
311318

@@ -624,6 +631,7 @@ class ParsedArgs:
624631
initial_jobs: int
625632
action_timeout: int
626633
job_locations: list[tuple[str, int]]
634+
supports_jobs: bool
627635

628636

629637
@dataclass
@@ -806,27 +814,44 @@ def bazel_option_end(args: list[str]) -> int:
806814
return len(args)
807815

808816

817+
def bazel_command_supports_jobs(args: list[str]) -> bool:
818+
end = bazel_option_end(args)
819+
skip_possible_option_value = False
820+
for arg in args[:end]:
821+
if skip_possible_option_value:
822+
skip_possible_option_value = False
823+
continue
824+
if arg.startswith("-"):
825+
if "=" not in arg:
826+
skip_possible_option_value = True
827+
continue
828+
return arg in JOBS_COMMANDS
829+
return False
830+
831+
809832
# Parse Bazel arguments enough to find the initial jobs cap.
810833
def parse_bazel_args(args: list[str], action_timeout: int | None = None) -> ParsedArgs:
811834
initial_jobs = None
812835
job_locations: list[tuple[str, int]] = []
836+
supports_jobs = bazel_command_supports_jobs(args)
813837

814838
end = bazel_option_end(args)
815-
i = 0
816-
while i < end:
817-
arg = args[i]
818-
if arg.startswith("--jobs="):
819-
job_locations.append(("equals", i))
820-
parsed = jobs_value(arg.split("=", 1)[1])
821-
if parsed is not None:
822-
initial_jobs = parsed
823-
elif arg == "--jobs" and i + 1 < end:
824-
job_locations.append(("separate", i))
825-
parsed = jobs_value(args[i + 1])
826-
if parsed is not None:
827-
initial_jobs = parsed
839+
if supports_jobs:
840+
i = 0
841+
while i < end:
842+
arg = args[i]
843+
if arg.startswith("--jobs="):
844+
job_locations.append(("equals", i))
845+
parsed = jobs_value(arg.split("=", 1)[1])
846+
if parsed is not None:
847+
initial_jobs = parsed
848+
elif arg == "--jobs" and i + 1 < end:
849+
job_locations.append(("separate", i))
850+
parsed = jobs_value(args[i + 1])
851+
if parsed is not None:
852+
initial_jobs = parsed
853+
i += 1
828854
i += 1
829-
i += 1
830855

831856
if initial_jobs is None:
832857
initial_jobs = os.cpu_count() or 1
@@ -838,12 +863,15 @@ def parse_bazel_args(args: list[str], action_timeout: int | None = None) -> Pars
838863
initial_jobs=initial_jobs,
839864
action_timeout=action_timeout,
840865
job_locations=job_locations,
866+
supports_jobs=supports_jobs,
841867
)
842868

843869

844870
# Return Bazel args with this attempt's concrete --jobs value applied.
845871
def bazel_args_with_jobs(parsed: ParsedArgs, jobs: int) -> list[str]:
846872
bazel_args = list(parsed.original_args)
873+
if not parsed.supports_jobs:
874+
return bazel_args
847875
if parsed.job_locations:
848876
for kind, index in parsed.job_locations:
849877
if kind == "equals":
@@ -3406,6 +3434,9 @@ def begin_upscale(next_jobs: int, running_actions_seconds: float | None) -> None
34063434

34073435
# Retry Bazel attempts while adapting the current jobs value.
34083436
def run_adaptive(bazel_path: str, parsed: ParsedArgs) -> int:
3437+
if not parsed.supports_jobs:
3438+
os.execvpe(bazel_path, [bazel_path, *parsed.original_args], os.environ)
3439+
34093440
jobs = parsed.initial_jobs
34103441
max_jobs = parsed.initial_jobs
34113442
context = BuildContext(os.getcwd())
@@ -3576,6 +3607,9 @@ def main(argv: list[str]) -> int:
35763607
)
35773608
return normalize_returncode(exit_code)
35783609

3610+
if not bazel_command_supports_jobs(argv):
3611+
os.execvpe(bazel_path, [bazel_path, *argv], os.environ)
3612+
35793613
try:
35803614
action_timeout = build_timeout_from_env()
35813615
low_memory_threshold_kb()

tools/bazel_adaptive_test.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,68 @@ def test_jobs_after_bazel_delimiter_are_target_arguments(self) -> None:
183183
["run", "//tool", "--jobs=7", "--", "--jobs=2"],
184184
)
185185

186+
def test_does_not_add_jobs_to_commands_without_jobs_flag(self) -> None:
187+
parsed = bazel_adaptive.parse_bazel_args(
188+
["query", "deps(//tests:all)"],
189+
action_timeout=100,
190+
)
191+
192+
self.assertFalse(parsed.supports_jobs)
193+
self.assertEqual(parsed.job_locations, [])
194+
self.assertEqual(
195+
bazel_adaptive.bazel_args_with_jobs(parsed, 7),
196+
["query", "deps(//tests:all)"],
197+
)
198+
199+
def test_unknown_commands_pass_through_without_jobs(self) -> None:
200+
parsed = bazel_adaptive.parse_bazel_args(
201+
["future-command", "--some_flag"],
202+
action_timeout=100,
203+
)
204+
205+
self.assertFalse(parsed.supports_jobs)
206+
self.assertEqual(
207+
bazel_adaptive.bazel_args_with_jobs(parsed, 7),
208+
["future-command", "--some_flag"],
209+
)
210+
211+
def test_does_not_rewrite_jobs_on_commands_without_jobs_flag(self) -> None:
212+
parsed = bazel_adaptive.parse_bazel_args(
213+
["query", "--jobs=99", "deps(//tests:all)"],
214+
action_timeout=100,
215+
)
216+
217+
self.assertFalse(parsed.supports_jobs)
218+
self.assertEqual(parsed.job_locations, [])
219+
self.assertEqual(
220+
bazel_adaptive.bazel_args_with_jobs(parsed, 7),
221+
["query", "--jobs=99", "deps(//tests:all)"],
222+
)
223+
224+
def test_recognizes_jobs_commands_after_startup_options(self) -> None:
225+
parsed = bazel_adaptive.parse_bazel_args(
226+
["--future_startup_option", "value", "aquery", "//tests:all"],
227+
action_timeout=100,
228+
)
229+
230+
self.assertTrue(parsed.supports_jobs)
231+
self.assertEqual(
232+
bazel_adaptive.bazel_args_with_jobs(parsed, 7),
233+
["--future_startup_option", "value", "aquery", "//tests:all", "--jobs=7"],
234+
)
235+
236+
def test_jobs_command_word_as_possible_startup_option_value_passes_through(self) -> None:
237+
parsed = bazel_adaptive.parse_bazel_args(
238+
["--future_startup_option", "run"],
239+
action_timeout=100,
240+
)
241+
242+
self.assertFalse(parsed.supports_jobs)
243+
self.assertEqual(
244+
bazel_adaptive.bazel_args_with_jobs(parsed, 7),
245+
["--future_startup_option", "run"],
246+
)
247+
186248
def test_duration_parser(self) -> None:
187249
self.assertEqual(bazel_adaptive.parse_duration_seconds("Compiling x; 27s sandbox"), 27)
188250
self.assertEqual(bazel_adaptive.parse_duration_seconds("Compiling x; 27s remote"), 27)

0 commit comments

Comments
 (0)