Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/commands/manifest/bazel/bazel-cquery.mts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ function buildMetadataCqueryExpr(repoName: string): string {
}

// Build the full cquery argv for a per-repo metadata cquery. Exposed for
// argv-shape unit tests without touching `spawn`.
// argv-shape unit tests without touching `spawn`. The startup-flag
// composition mirrors `bazel-query-runner`'s `buildStartupFlags` so
// customer `--bazel-startup-flag` values land before the subcommand and
// `--bazel-flag` values land after the standard cquery flags.
export function buildMetadataCqueryArgv(
repoName: string,
opts: BazelQueryOptions,
Expand All @@ -90,7 +93,13 @@ export function buildMetadataCqueryArgv(
if (opts.bazelOutputBase) {
startup.push(`--output_base=${opts.bazelOutputBase}`)
}
const userFlags = splitBazelFlags(opts.bazelFlags)
if (opts.extraBazelStartupFlags?.length) {
startup.push(...opts.extraBazelStartupFlags)
}
const userFlags = [
...splitBazelFlags(opts.bazelFlags),
...(opts.extraBazelFlags ?? []),
]
return [
...startup,
'cquery',
Expand Down
15 changes: 15 additions & 0 deletions src/commands/manifest/bazel/bazel-cquery.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,21 @@ describe('buildMetadataCqueryArgv', () => {
expect(argv).toContain('--repo_env=SCALA_VERSION=2.13.18')
})

it('threads extraBazelStartupFlags ahead of cquery and extraBazelFlags after the standard flags', () => {
const argv = buildMetadataCqueryArgv('maven', {
bin: 'bazel',
cwd: '/r',
invocationFlags: [],
extraBazelStartupFlags: ['--host_jvm_args=-Xmx2g'],
extraBazelFlags: ['--config=ci'],
})
const cqueryIdx = argv.indexOf('cquery')
const jvmIdx = argv.indexOf('--host_jvm_args=-Xmx2g')
const configIdx = argv.indexOf('--config=ci')
expect(jvmIdx).toBeLessThan(cqueryIdx)
expect(configIdx).toBeGreaterThan(cqueryIdx)
})

it('includes invocationFlags between subcommand and target expression', () => {
const argv = buildMetadataCqueryArgv('maven', {
bin: 'bazel',
Expand Down
36 changes: 30 additions & 6 deletions src/commands/manifest/bazel/bazel-query-runner.mts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ export type BazelQueryOptions = {
// orchestrator mkdtemp's a fresh path per invocation; the legacy PyPI
// path may leave it unset for now.
outputUserRoot?: string
// Customer-supplied `--bazel-flag=<arg>` values (repeatable on the CLI).
// Each entry is appended verbatim to every subcommand invocation, after
// the orchestrator's own flags and after `bazelFlags`. Used to thread
// matrix-cell selectors like `--config=ci-scala-2-13` or
// `--repo_env=SCALA_VERSION=2.13.18` per CI shard.
extraBazelFlags?: string[]
// Customer-supplied `--bazel-startup-flag=<arg>` values (repeatable).
// Each entry is appended to the startup-flag prefix, after the
// orchestrator's own startup flags (--bazelrc, --output_user_root,
// --output_base) and before the subcommand.
extraBazelStartupFlags?: string[]
env?: NodeJS.ProcessEnv
verbose?: boolean
}
Expand Down Expand Up @@ -49,7 +60,8 @@ export function splitBazelFlags(flags: string | undefined): string[] {
// Build the shared startup-flag prefix for any bazel invocation. Centralised
// so `--output_user_root` propagates to every spawn — principle 7 of the
// Maven design requires per-invocation server isolation across query,
// cquery, and `bazel mod` commands alike.
// cquery, and `bazel mod` commands alike. Customer `--bazel-startup-flag`
// values are appended last so they appear before the subcommand.
function buildStartupFlags(opts: BazelQueryOptions): string[] {
const startup: string[] = []
if (opts.bazelRc) {
Expand All @@ -61,11 +73,23 @@ function buildStartupFlags(opts: BazelQueryOptions): string[] {
if (opts.bazelOutputBase) {
startup.push(`--output_base=${opts.bazelOutputBase}`)
}
if (opts.extraBazelStartupFlags?.length) {
startup.push(...opts.extraBazelStartupFlags)
}
return startup
}

// Compose the user-flag suffix appended after every subcommand's standard
// flags. The legacy whitespace-split `bazelFlags` string and the new
// repeatable `extraBazelFlags` array are concatenated in that order so a
// socket.json default plus a CLI override applies both. No deduplication
// — Bazel's last-wins semantics handle conflicts.
function userBazelFlags(opts: BazelQueryOptions): string[] {
return [...splitBazelFlags(opts.bazelFlags), ...(opts.extraBazelFlags ?? [])]
}

function buildBazelModShowVisibleReposArgv(opts: BazelQueryOptions): string[] {
const userFlags = splitBazelFlags(opts.bazelFlags)
const userFlags = userBazelFlags(opts)
return [
...buildStartupFlags(opts),
'mod',
Expand All @@ -79,7 +103,7 @@ function buildBazelModShowVisibleReposArgv(opts: BazelQueryOptions): string[] {
function buildBazelModShowMavenExtensionArgv(
opts: BazelQueryOptions,
): string[] {
const userFlags = splitBazelFlags(opts.bazelFlags)
const userFlags = userBazelFlags(opts)
return [
...buildStartupFlags(opts),
'mod',
Expand All @@ -90,7 +114,7 @@ function buildBazelModShowMavenExtensionArgv(
}

function buildBazelModShowPipExtensionArgv(opts: BazelQueryOptions): string[] {
const userFlags = splitBazelFlags(opts.bazelFlags)
const userFlags = userBazelFlags(opts)
return [
...buildStartupFlags(opts),
'mod',
Expand All @@ -110,7 +134,7 @@ function buildBazelArgv(
// Bazel argv shape: <startup> query <queryFlags> <invocationFlags> <queryStr> --output=<output> <userFlags>
// Keep query output stable and avoid updating Bazel lockfiles while extracting.
const queryFlags = ['--lockfile_mode=off', '--noshow_progress']
const userFlags = splitBazelFlags(opts.bazelFlags)
const userFlags = userBazelFlags(opts)
return [
...buildStartupFlags(opts),
'query',
Expand All @@ -131,7 +155,7 @@ function buildBazelProbeCqueryArgv(
repoName: string,
opts: BazelQueryOptions,
): string[] {
const userFlags = splitBazelFlags(opts.bazelFlags)
const userFlags = userBazelFlags(opts)
return [
...buildStartupFlags(opts),
'cquery',
Expand Down
46 changes: 46 additions & 0 deletions src/commands/manifest/bazel/bazel-query-runner.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,39 @@ describe('runBazelQuery', () => {
expect(argv).toContain('--keep_going')
})

it('appends extraBazelFlags after bazelFlags (CLI overrides socket.json default)', async () => {
await runBazelQuery('q', {
bin: 'bazel',
cwd: '/r',
invocationFlags: [],
bazelFlags: '--config=default',
extraBazelFlags: ['--config=override', '--repo_env=K=V'],
})
const argv = mocked.mock.calls[0]![1] as string[]
const def = argv.indexOf('--config=default')
const override = argv.indexOf('--config=override')
const envFlag = argv.indexOf('--repo_env=K=V')
expect(def).toBeGreaterThanOrEqual(0)
expect(override).toBeGreaterThan(def)
expect(envFlag).toBeGreaterThan(override)
})

it('threads extraBazelStartupFlags ahead of the subcommand but after the orchestrator startup flags', async () => {
await runBazelQuery('q', {
bin: 'bazel',
cwd: '/r',
invocationFlags: [],
outputUserRoot: '/tmp/x',
extraBazelStartupFlags: ['--host_jvm_args=-Xmx2g'],
})
const argv = mocked.mock.calls[0]![1] as string[]
const root = argv.indexOf('--output_user_root=/tmp/x')
const jvm = argv.indexOf('--host_jvm_args=-Xmx2g')
const subcmd = argv.indexOf('query')
expect(root).toBeLessThan(jvm)
expect(jvm).toBeLessThan(subcmd)
})

it('forwards env to spawn when provided', async () => {
const env = { ...process.env, BAZEL_BENCH: 'yes' }
await runBazelQuery('q', {
Expand Down Expand Up @@ -418,6 +451,19 @@ describe('buildPypiProbeFor', () => {
})
})

it('does nothing when extra flags are absent', async () => {
// Sanity-check the new flag arrays don't pollute argv when empty.
const probe = buildPypiProbeFor({
bin: 'bazel',
cwd: '/r',
invocationFlags: [],
})
await probe('pypi')
const argv = mocked.mock.calls[0]![1] as string[]
// No --bazel-startup-flag / --bazel-flag entries should have appeared.
expect(argv.some(a => a.startsWith('--config='))).toBe(false)
})

it('returns the full triple when the hub has no :pkg targets', async () => {
mocked.mockReset()
// @ts-ignore — narrow return shape for the test's purposes.
Expand Down
34 changes: 34 additions & 0 deletions src/commands/manifest/bazel/cmd-manifest-bazel.mts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,26 @@ const config: CliCommandConfig = {
description:
'Path to bazel/bazelisk binary; default: $(which bazelisk) || $(which bazel)',
},
bazelFlag: {
type: 'string',
isMultiple: true,
description:
'Bazel flag forwarded to every subcommand (repeatable). E.g. ' +
'`--bazel-flag=--config=ci-scala-2-13` to scan a matrix cell.',
},
bazelFlags: {
type: 'string',
description:
'Flags forwarded to every bazel invocation (single quoted string)',
},
bazelMavenRepo: {
type: 'string',
isMultiple: true,
description:
'Maven hub repo name to extract in addition to the auto-discovered ' +
'set (repeatable). For legacy WORKSPACE workspaces with hubs that ' +
'use non-conventional names. E.g. `--bazel-maven-repo=my_jars`.',
},
bazelOutputBase: {
type: 'string',
description: 'Bazel --output_base for read-only-cache CI environments',
Expand All @@ -59,6 +74,13 @@ const config: CliCommandConfig = {
type: 'string',
description: 'Path to additional .bazelrc fragments forwarded to bazel',
},
bazelStartupFlag: {
type: 'string',
isMultiple: true,
description:
'Bazel startup flag inserted before the subcommand (repeatable). ' +
'E.g. `--bazel-startup-flag=--host_jvm_args=-Xmx2g`.',
},
ecosystem: {
type: 'string',
isMultiple: true,
Expand Down Expand Up @@ -203,6 +225,11 @@ async function run(
)

const { ecosystem } = cli.flags
const bazelFlag = (cli.flags['bazelFlag'] as string[] | undefined) ?? []
const bazelStartupFlag =
(cli.flags['bazelStartupFlag'] as string[] | undefined) ?? []
const bazelMavenRepo =
(cli.flags['bazelMavenRepo'] as string[] | undefined) ?? []
let { bazel, bazelFlags, bazelOutputBase, bazelRc, out, verbose } = cli.flags

// Set defaults for any flag/arg that is not given. Check socket.json first.
Expand Down Expand Up @@ -318,6 +345,13 @@ async function run(
bazelRc: bazelRc as string | undefined,
bin: bazel as string | undefined,
cwd,
...(bazelFlag.length ? { extraBazelFlags: bazelFlag } : {}),
...(bazelStartupFlag.length
? { extraBazelStartupFlags: bazelStartupFlag }
: {}),
...(bazelMavenRepo.length
? { extraMavenRepoNames: bazelMavenRepo }
: {}),
ignoreDirNames: BAZEL_WALKER_IGNORE_DIR_NAMES,
ignoreDirPrefixes: BAZEL_WALKER_IGNORE_DIR_PREFIXES,
out: out as string,
Expand Down
13 changes: 13 additions & 0 deletions src/commands/manifest/bazel/extract_bazel_to_maven.mts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ export type ExtractBazelOptions = {
cwd: string
// Optional env override used for python-shim PATH augmentation.
env?: NodeJS.ProcessEnv
// Customer-supplied `--bazel-flag=<arg>` values; threaded into every
// bazel subcommand after the orchestrator's own flags. Repeatable on
// the CLI; entries are forwarded verbatim.
extraBazelFlags?: string[] | undefined
// Customer-supplied `--bazel-startup-flag=<arg>` values; injected into
// the startup-flag prefix before the subcommand.
extraBazelStartupFlags?: string[] | undefined
// Customer-supplied Maven hub names augmenting the auto-discovery
// candidate set. Wired in by the `--bazel-maven-repo=<name>` flag for
// legacy WORKSPACE workspaces whose hubs use non-conventional names
Expand Down Expand Up @@ -369,6 +376,12 @@ function buildQueryOpts(args: {
...(opts.bazelRc ? { bazelRc: opts.bazelRc } : {}),
...(opts.bazelFlags ? { bazelFlags: opts.bazelFlags } : {}),
...(opts.bazelOutputBase ? { bazelOutputBase: opts.bazelOutputBase } : {}),
...(opts.extraBazelFlags?.length
? { extraBazelFlags: opts.extraBazelFlags }
: {}),
...(opts.extraBazelStartupFlags?.length
? { extraBazelStartupFlags: opts.extraBazelStartupFlags }
: {}),
...(baseEnv ? { env: baseEnv } : {}),
verbose,
}
Expand Down