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 @@ -115,7 +115,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 @@ -130,7 +133,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 @@ -192,6 +192,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 @@ -99,7 +123,7 @@ function buildBazelModShowMavenExtensionArgv(
}

function buildBazelModShowPipExtensionArgv(opts: BazelQueryOptions): string[] {
const userFlags = splitBazelFlags(opts.bazelFlags)
const userFlags = userBazelFlags(opts)
return [
...buildStartupFlags(opts),
'mod',
Expand All @@ -123,7 +147,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 @@ -144,7 +168,7 @@ function buildBazelProbeCqueryArgv(
repoName: string,
opts: BazelQueryOptions,
): string[] {
const userFlags = splitBazelFlags(opts.bazelFlags)
const userFlags = userBazelFlags(opts)
return [
...buildStartupFlags(opts),
'cquery',
Expand Down
44 changes: 44 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 @@ -433,6 +466,17 @@ describe('buildPypiProbeFor', () => {
})
})

it('does nothing when extra flags are absent', async () => {
const probe = buildPypiProbeFor({
bin: 'bazel',
cwd: '/r',
invocationFlags: [],
})
await probe('pypi')
const argv = mocked.mock.calls[0]![1] as string[]
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
2 changes: 1 addition & 1 deletion src/commands/manifest/bazel/bazel-repo-discovery.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ describe('bazel-repo-discovery', () => {
probeResult({
code: 2,
stderr:
'ERROR: In extension argument @rules_jvm_external//:extensions.bzl%maven: No module with the apparent repo name @rules_jvm_external exists in the dependency graph. Type \'bazel help mod\' for syntax and help.\n',
"ERROR: In extension argument @rules_jvm_external//:extensions.bzl%maven: No module with the apparent repo name @rules_jvm_external exists in the dependency graph. Type 'bazel help mod' for syntax and help.\n",
}),
0,
),
Expand Down
48 changes: 46 additions & 2 deletions src/commands/manifest/bazel/cmd-manifest-bazel.mts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,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 @@ -45,6 +60,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 @@ -90,8 +112,15 @@ const config: CliCommandConfig = {
\`bazel mod show_extension\` and filtered to the root module's own hubs.
Under legacy WORKSPACE mode (no \`show_extension\`), only conventionally
named hubs are probed (\`maven\`, \`maven_install\`, \`maven_dev\`, …). A hub
with a non-conventional name that \`show_extension\` does not enumerate is
not discovered yet; a flag to name extra hubs is planned.
with a non-conventional name that \`show_extension\` does not enumerate
can be named explicitly with \`--bazel-maven-repo\`.

\`--bazel-flag\` forwards a flag to every subcommand (cquery, query, mod
show_extension, mod dump_repo_mapping), after the orchestrator's own
flags. Use it for matrix-cell selectors like
\`--repo_env=SCALA_VERSION=2.13.18\` or \`--platforms=//tools:linux_x86_64\`.
\`--bazel-startup-flag\` is inserted before the subcommand instead, for
host-side knobs like \`--host_jvm_args=-Xmx2g\`.

To generate AND upload in one step, use \`socket scan create --auto-manifest\`
instead — it detects Bazel workspaces, generates Maven manifests by
Expand All @@ -102,6 +131,9 @@ const config: CliCommandConfig = {
$ ${command} --ecosystem pypi .
$ ${command} --ecosystem maven --ecosystem pypi .
$ ${command} --bazel=/usr/local/bin/bazelisk .
$ ${command} --bazel-flag=--config=ci-scala-2-13 .
$ ${command} --bazel-startup-flag=--host_jvm_args=-Xmx2g .
$ ${command} --bazel-maven-repo=my_jars --bazel-maven-repo=test_maven .
`,
}

Expand Down Expand Up @@ -261,6 +293,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
let perRepoTimeout = cli.flags['perRepoTimeout'] as number | undefined

Expand Down Expand Up @@ -390,6 +427,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 }
: {}),
out: out as string,
perRepoTimeoutMs: perRepoTimeout,
verbose: Boolean(verbose),
Expand Down
12 changes: 6 additions & 6 deletions src/commands/manifest/bazel/cmd-manifest-bazel.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -243,18 +243,18 @@ describe('evaluateEcosystemOutcomes (explicit mode)', () => {
})

describe('perRepoTimeout flag wiring', () => {
const importMeta = { url: 'file:///cmd-manifest-bazel.test.mts' } as ImportMeta
const importMeta = {
url: 'file:///cmd-manifest-bazel.test.mts',
} as ImportMeta

beforeEach(() => {
vi.mocked(extractBazelToMaven).mockClear()
})

it('defaults the explicit command to a 120s per-repo timeout', async () => {
await cmdManifestBazel.run(
[FLAG_CONFIG, '{}', '.'],
importMeta,
{ parentName: 'manifest' } as CliCommandContext,
)
await cmdManifestBazel.run([FLAG_CONFIG, '{}', '.'], importMeta, {
parentName: 'manifest',
} as CliCommandContext)
expect(extractBazelToMaven).toHaveBeenCalledTimes(1)
expect(extractBazelToMaven).toHaveBeenCalledWith(
expect.objectContaining({ perRepoTimeoutMs: 120_000 }),
Expand Down
Loading
Loading