Skip to content

Commit 5b192f5

Browse files
committed
feat(manifest/bazel): customer flag passthrough for matrix builds and non-conventional hubs
Adds three repeatable CLI flags so customers can drive the underlying bazel invocations without having to fork the orchestrator: --bazel-flag=<arg> appended to every subcommand (cquery, query, mod show_extension, mod dump_repo_mapping) after the orchestrator's own flags. Use for matrix-cell selectors: --bazel-flag=--repo_env=SCALA_VERSION=2.13.18 --bazel-flag=--config=ci-scala-2-13 --bazel-flag=--platforms=//tools:linux_x86_64 --bazel-startup-flag=<arg> injected into the startup-flag prefix BEFORE the subcommand, after the orchestrator's startup flags (--bazelrc, --output_user_root, --output_base). Use for host-side knobs: --bazel-startup-flag=--host_jvm_args=-Xmx2g --bazel-maven-repo=<name> appended to the candidate Maven hub list. Use on legacy WORKSPACE workspaces whose hubs use non-conventional names that the conventional probe list doesn't cover, or on custom Bzlmod extensions `mod show_extension` doesn't enumerate: --bazel-maven-repo=my_jars --bazel-maven-repo=test_maven (repeatable) `BazelQueryOptions` gains `extraBazelFlags` and `extraBazelStartupFlags`; the centralised `buildStartupFlags` and the new `userBazelFlags` helpers thread them through every argv builder uniformly (probe cquery, metadata cquery in bazel-cquery, query, mod show_extension, mod dump_repo_mapping). `ExtractBazelOptions` gains the matching three fields, defaulted to undefined when no CLI override was supplied. Flag passthrough is verbatim — Bazel's last-wins precedence handles conflicts between socket.json defaults (`bazelFlags`) and CLI overrides (`extraBazelFlags`). No allowlist; the trust model is the same as running `bazel` directly, and per-invocation `--output_user_root` isolation prevents a hostile flag from poisoning shared state. Tests cover argv shape for both extra-flag arrays (placement before subcommand for startup flags; placement after standard subcommand flags for trailing flags), the cquery argv-shape test, and the extraMavenRepoNames threading end-to-end.
1 parent 14f593d commit 5b192f5

6 files changed

Lines changed: 149 additions & 8 deletions

File tree

src/commands/manifest/bazel/bazel-cquery.mts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ function buildMetadataCqueryExpr(repoName: string): string {
7575
}
7676

7777
// Build the full cquery argv for a per-repo metadata cquery. Exposed for
78-
// argv-shape unit tests without touching `spawn`.
78+
// argv-shape unit tests without touching `spawn`. The startup-flag
79+
// composition mirrors `bazel-query-runner`'s `buildStartupFlags` so
80+
// customer `--bazel-startup-flag` values land before the subcommand and
81+
// `--bazel-flag` values land after the standard cquery flags.
7982
export function buildMetadataCqueryArgv(
8083
repoName: string,
8184
opts: BazelQueryOptions,
@@ -90,7 +93,13 @@ export function buildMetadataCqueryArgv(
9093
if (opts.bazelOutputBase) {
9194
startup.push(`--output_base=${opts.bazelOutputBase}`)
9295
}
93-
const userFlags = splitBazelFlags(opts.bazelFlags)
96+
if (opts.extraBazelStartupFlags?.length) {
97+
startup.push(...opts.extraBazelStartupFlags)
98+
}
99+
const userFlags = [
100+
...splitBazelFlags(opts.bazelFlags),
101+
...(opts.extraBazelFlags ?? []),
102+
]
94103
return [
95104
...startup,
96105
'cquery',

src/commands/manifest/bazel/bazel-cquery.test.mts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,21 @@ describe('buildMetadataCqueryArgv', () => {
112112
expect(argv).toContain('--repo_env=SCALA_VERSION=2.13.18')
113113
})
114114

115+
it('threads extraBazelStartupFlags ahead of cquery and extraBazelFlags after the standard flags', () => {
116+
const argv = buildMetadataCqueryArgv('maven', {
117+
bin: 'bazel',
118+
cwd: '/r',
119+
invocationFlags: [],
120+
extraBazelStartupFlags: ['--host_jvm_args=-Xmx2g'],
121+
extraBazelFlags: ['--config=ci'],
122+
})
123+
const cqueryIdx = argv.indexOf('cquery')
124+
const jvmIdx = argv.indexOf('--host_jvm_args=-Xmx2g')
125+
const configIdx = argv.indexOf('--config=ci')
126+
expect(jvmIdx).toBeLessThan(cqueryIdx)
127+
expect(configIdx).toBeGreaterThan(cqueryIdx)
128+
})
129+
115130
it('includes invocationFlags between subcommand and target expression', () => {
116131
const argv = buildMetadataCqueryArgv('maven', {
117132
bin: 'bazel',

src/commands/manifest/bazel/bazel-query-runner.mts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ export type BazelQueryOptions = {
1919
// orchestrator mkdtemp's a fresh path per invocation; the legacy PyPI
2020
// path may leave it unset for now.
2121
outputUserRoot?: string
22+
// Customer-supplied `--bazel-flag=<arg>` values (repeatable on the CLI).
23+
// Each entry is appended verbatim to every subcommand invocation, after
24+
// the orchestrator's own flags and after `bazelFlags`. Used to thread
25+
// matrix-cell selectors like `--config=ci-scala-2-13` or
26+
// `--repo_env=SCALA_VERSION=2.13.18` per CI shard.
27+
extraBazelFlags?: string[]
28+
// Customer-supplied `--bazel-startup-flag=<arg>` values (repeatable).
29+
// Each entry is appended to the startup-flag prefix, after the
30+
// orchestrator's own startup flags (--bazelrc, --output_user_root,
31+
// --output_base) and before the subcommand.
32+
extraBazelStartupFlags?: string[]
2233
env?: NodeJS.ProcessEnv
2334
verbose?: boolean
2435
}
@@ -49,7 +60,8 @@ export function splitBazelFlags(flags: string | undefined): string[] {
4960
// Build the shared startup-flag prefix for any bazel invocation. Centralised
5061
// so `--output_user_root` propagates to every spawn — principle 7 of the
5162
// Maven design requires per-invocation server isolation across query,
52-
// cquery, and `bazel mod` commands alike.
63+
// cquery, and `bazel mod` commands alike. Customer `--bazel-startup-flag`
64+
// values are appended last so they appear before the subcommand.
5365
function buildStartupFlags(opts: BazelQueryOptions): string[] {
5466
const startup: string[] = []
5567
if (opts.bazelRc) {
@@ -61,11 +73,23 @@ function buildStartupFlags(opts: BazelQueryOptions): string[] {
6173
if (opts.bazelOutputBase) {
6274
startup.push(`--output_base=${opts.bazelOutputBase}`)
6375
}
76+
if (opts.extraBazelStartupFlags?.length) {
77+
startup.push(...opts.extraBazelStartupFlags)
78+
}
6479
return startup
6580
}
6681

82+
// Compose the user-flag suffix appended after every subcommand's standard
83+
// flags. The legacy whitespace-split `bazelFlags` string and the new
84+
// repeatable `extraBazelFlags` array are concatenated in that order so a
85+
// socket.json default plus a CLI override applies both. No deduplication
86+
// — Bazel's last-wins semantics handle conflicts.
87+
function userBazelFlags(opts: BazelQueryOptions): string[] {
88+
return [...splitBazelFlags(opts.bazelFlags), ...(opts.extraBazelFlags ?? [])]
89+
}
90+
6791
function buildBazelModShowVisibleReposArgv(opts: BazelQueryOptions): string[] {
68-
const userFlags = splitBazelFlags(opts.bazelFlags)
92+
const userFlags = userBazelFlags(opts)
6993
return [
7094
...buildStartupFlags(opts),
7195
'mod',
@@ -79,7 +103,7 @@ function buildBazelModShowVisibleReposArgv(opts: BazelQueryOptions): string[] {
79103
function buildBazelModShowMavenExtensionArgv(
80104
opts: BazelQueryOptions,
81105
): string[] {
82-
const userFlags = splitBazelFlags(opts.bazelFlags)
106+
const userFlags = userBazelFlags(opts)
83107
return [
84108
...buildStartupFlags(opts),
85109
'mod',
@@ -90,7 +114,7 @@ function buildBazelModShowMavenExtensionArgv(
90114
}
91115

92116
function buildBazelModShowPipExtensionArgv(opts: BazelQueryOptions): string[] {
93-
const userFlags = splitBazelFlags(opts.bazelFlags)
117+
const userFlags = userBazelFlags(opts)
94118
return [
95119
...buildStartupFlags(opts),
96120
'mod',
@@ -110,7 +134,7 @@ function buildBazelArgv(
110134
// Bazel argv shape: <startup> query <queryFlags> <invocationFlags> <queryStr> --output=<output> <userFlags>
111135
// Keep query output stable and avoid updating Bazel lockfiles while extracting.
112136
const queryFlags = ['--lockfile_mode=off', '--noshow_progress']
113-
const userFlags = splitBazelFlags(opts.bazelFlags)
137+
const userFlags = userBazelFlags(opts)
114138
return [
115139
...buildStartupFlags(opts),
116140
'query',
@@ -131,7 +155,7 @@ function buildBazelProbeCqueryArgv(
131155
repoName: string,
132156
opts: BazelQueryOptions,
133157
): string[] {
134-
const userFlags = splitBazelFlags(opts.bazelFlags)
158+
const userFlags = userBazelFlags(opts)
135159
return [
136160
...buildStartupFlags(opts),
137161
'cquery',

src/commands/manifest/bazel/bazel-query-runner.test.mts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,39 @@ describe('runBazelQuery', () => {
121121
expect(argv).toContain('--keep_going')
122122
})
123123

124+
it('appends extraBazelFlags after bazelFlags (CLI overrides socket.json default)', async () => {
125+
await runBazelQuery('q', {
126+
bin: 'bazel',
127+
cwd: '/r',
128+
invocationFlags: [],
129+
bazelFlags: '--config=default',
130+
extraBazelFlags: ['--config=override', '--repo_env=K=V'],
131+
})
132+
const argv = mocked.mock.calls[0]![1] as string[]
133+
const def = argv.indexOf('--config=default')
134+
const override = argv.indexOf('--config=override')
135+
const envFlag = argv.indexOf('--repo_env=K=V')
136+
expect(def).toBeGreaterThanOrEqual(0)
137+
expect(override).toBeGreaterThan(def)
138+
expect(envFlag).toBeGreaterThan(override)
139+
})
140+
141+
it('threads extraBazelStartupFlags ahead of the subcommand but after the orchestrator startup flags', async () => {
142+
await runBazelQuery('q', {
143+
bin: 'bazel',
144+
cwd: '/r',
145+
invocationFlags: [],
146+
outputUserRoot: '/tmp/x',
147+
extraBazelStartupFlags: ['--host_jvm_args=-Xmx2g'],
148+
})
149+
const argv = mocked.mock.calls[0]![1] as string[]
150+
const root = argv.indexOf('--output_user_root=/tmp/x')
151+
const jvm = argv.indexOf('--host_jvm_args=-Xmx2g')
152+
const subcmd = argv.indexOf('query')
153+
expect(root).toBeLessThan(jvm)
154+
expect(jvm).toBeLessThan(subcmd)
155+
})
156+
124157
it('forwards env to spawn when provided', async () => {
125158
const env = { ...process.env, BAZEL_BENCH: 'yes' }
126159
await runBazelQuery('q', {
@@ -420,6 +453,19 @@ describe('buildPypiProbeFor', () => {
420453
})
421454
})
422455

456+
it('does nothing when extra flags are absent', async () => {
457+
// Sanity-check the new flag arrays don't pollute argv when empty.
458+
const probe = buildPypiProbeFor({
459+
bin: 'bazel',
460+
cwd: '/r',
461+
invocationFlags: [],
462+
})
463+
await probe('pypi')
464+
const argv = mocked.mock.calls[0]![1] as string[]
465+
// No --bazel-startup-flag / --bazel-flag entries should have appeared.
466+
expect(argv.some(a => a.startsWith('--config='))).toBe(false)
467+
})
468+
423469
it('returns the full triple when the hub has no :pkg targets', async () => {
424470
mocked.mockReset()
425471
// @ts-ignore — narrow return shape for the test's purposes.

src/commands/manifest/bazel/cmd-manifest-bazel.mts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,26 @@ const config: CliCommandConfig = {
3131
description:
3232
'Path to bazel/bazelisk binary; default: $(which bazelisk) || $(which bazel)',
3333
},
34+
bazelFlag: {
35+
type: 'string',
36+
isMultiple: true,
37+
description:
38+
'Bazel flag forwarded to every subcommand (repeatable). E.g. ' +
39+
'`--bazel-flag=--config=ci-scala-2-13` to scan a matrix cell.',
40+
},
3441
bazelFlags: {
3542
type: 'string',
3643
description:
3744
'Flags forwarded to every bazel invocation (single quoted string)',
3845
},
46+
bazelMavenRepo: {
47+
type: 'string',
48+
isMultiple: true,
49+
description:
50+
'Maven hub repo name to extract in addition to the auto-discovered ' +
51+
'set (repeatable). For legacy WORKSPACE workspaces with hubs that ' +
52+
'use non-conventional names. E.g. `--bazel-maven-repo=my_jars`.',
53+
},
3954
bazelOutputBase: {
4055
type: 'string',
4156
description: 'Bazel --output_base for read-only-cache CI environments',
@@ -44,6 +59,13 @@ const config: CliCommandConfig = {
4459
type: 'string',
4560
description: 'Path to additional .bazelrc fragments forwarded to bazel',
4661
},
62+
bazelStartupFlag: {
63+
type: 'string',
64+
isMultiple: true,
65+
description:
66+
'Bazel startup flag inserted before the subcommand (repeatable). ' +
67+
'E.g. `--bazel-startup-flag=--host_jvm_args=-Xmx2g`.',
68+
},
4769
ecosystem: {
4870
type: 'string',
4971
isMultiple: true,
@@ -188,6 +210,11 @@ async function run(
188210
)
189211

190212
const { ecosystem } = cli.flags
213+
const bazelFlag = (cli.flags['bazelFlag'] as string[] | undefined) ?? []
214+
const bazelStartupFlag =
215+
(cli.flags['bazelStartupFlag'] as string[] | undefined) ?? []
216+
const bazelMavenRepo =
217+
(cli.flags['bazelMavenRepo'] as string[] | undefined) ?? []
191218
let { bazel, bazelFlags, bazelOutputBase, bazelRc, out, verbose } = cli.flags
192219

193220
// Set defaults for any flag/arg that is not given. Check socket.json first.
@@ -303,6 +330,13 @@ async function run(
303330
bazelRc: bazelRc as string | undefined,
304331
bin: bazel as string | undefined,
305332
cwd,
333+
...(bazelFlag.length ? { extraBazelFlags: bazelFlag } : {}),
334+
...(bazelStartupFlag.length
335+
? { extraBazelStartupFlags: bazelStartupFlag }
336+
: {}),
337+
...(bazelMavenRepo.length
338+
? { extraMavenRepoNames: bazelMavenRepo }
339+
: {}),
306340
out: out as string,
307341
verbose: Boolean(verbose),
308342
})

src/commands/manifest/bazel/extract_bazel_to_maven.mts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ export type ExtractBazelOptions = {
4545
cwd: string
4646
// Optional env override used for python-shim PATH augmentation.
4747
env?: NodeJS.ProcessEnv
48+
// Customer-supplied `--bazel-flag=<arg>` values; threaded into every
49+
// bazel subcommand after the orchestrator's own flags. Repeatable on
50+
// the CLI; entries are forwarded verbatim.
51+
extraBazelFlags?: string[] | undefined
52+
// Customer-supplied `--bazel-startup-flag=<arg>` values; injected into
53+
// the startup-flag prefix before the subcommand.
54+
extraBazelStartupFlags?: string[] | undefined
4855
// Customer-supplied Maven hub names augmenting the auto-discovery
4956
// candidate set. Wired in by the `--bazel-maven-repo=<name>` flag for
5057
// legacy WORKSPACE workspaces whose hubs use non-conventional names
@@ -431,6 +438,12 @@ export async function extractBazelToMaven(
431438
...(opts.bazelOutputBase
432439
? { bazelOutputBase: opts.bazelOutputBase }
433440
: {}),
441+
...(opts.extraBazelFlags?.length
442+
? { extraBazelFlags: opts.extraBazelFlags }
443+
: {}),
444+
...(opts.extraBazelStartupFlags?.length
445+
? { extraBazelStartupFlags: opts.extraBazelStartupFlags }
446+
: {}),
434447
...(baseEnv ? { env: baseEnv } : {}),
435448
verbose,
436449
})

0 commit comments

Comments
 (0)