Skip to content

Commit c1987a9

Browse files
authored
fix(manifest): gradle --facts works with configuration cache + adds --configs/--ignore-unresolved (REA-484) (#1338)
* fix(manifest): gradle --facts works with configuration cache + adds --configs/--ignore-unresolved (REA-484) The init script reached into `Task.project` from a `doLast` action, which Gradle 7+ forbids when the configuration cache is on. Disable the cache for the facts run via `-Dorg.gradle.configuration-cache=false` (silently ignored on pre-6.6 Gradle, so we stay compatible with old versions) and hoist `findProperty`/`projectDir` reads to configuration time as defensive cleanup. Also bring `socket manifest gradle --facts` (and its `kotlin` alias) up to sbt-parity on resolution knobs: `--configs=<comma-separated suffixes>` for case-insensitive configuration-name filtering, and `--ignore-unresolved` to opt out of the new default of failing the run when a dep can't resolve. Both options also honored from `socket.json` and via the auto-manifest path. Verified end-to-end on OpenTelemetry's Java SDK (CC + parallel CC, Gradle 9.5.1): default emits 419 components, `--configs=compileClasspath, runtimeClasspath` drops to 335 with 0 tooling-tagged. Unresolved-dep fixture fails with exit 1 by default; `--ignore-unresolved` flips to exit 0 with the unresolvable surfaced as a `direct` entry. * refactor(manifest): gradle --facts --configs uses glob patterns instead of suffix match Suffix matching was arbitrary — `--configs=compileClasspath` silently matched every variant (testCompileClasspath, jvmMainCompileClasspath, ...). Switch to explicit glob patterns (`*` and `?` wildcards), case-insensitive. The old suffix behavior is now expressed as `*CompileClasspath`; bare names match exactly. Verified on OpenTelemetry's Java SDK: `*CompileClasspath,*RuntimeClasspath` preserves the 335-component / 0-tooling outcome; bare `runtimeClasspath` narrows to 215 (the standalone configs only); `*test*` matches every test-related config (345 components, all dev-tagged). * refactor(manifest): gradle --configs glob matching is case-sensitive When a user types a glob, they almost always mean what they typed. Silent lower-casing makes `*compileclasspath` and `*CompileClasspath` behave the same — convenient until it isn't, and surprising when matching a custom config whose canonical name has unusual casing. Drop the case-insensitive flag. * refactor(manifest): sbt --configs accepts glob patterns too Mirror the gradle command's `--configs` semantics so a user who learned one wildcard syntax gets the same behavior on the other. The sbt plugin replaces its prior set-membership check with a glob matcher (`*` and `?`, case-sensitive); bare names — the existing comma-separated-names form — keep working unchanged because no wildcards = literal-name match. Verified end-to-end on a fresh sbt 1.10 fixture: default emits 20 components (5-name DefaultConfs unchanged), `*test*` matches `test` giving the same 20, capitalized `*Test*` matches nothing (skips with "no resolvable dependencies") confirming case-sensitivity, and bare `compile` resolves only the compile-scope deps (2 components). * docs(manifest): clarify --ignore-unresolved description (PR review) The flag was described as "skip dependencies that fail to resolve", but in gradle/kotlin the implementation still emits unresolved deps as `direct` entries with their declared coordinates — the flag only controls whether the build aborts. Reword to match what actually happens, on mtorp's PR feedback. The sbt variant is reworded too (its `isEmittable` filter does drop unresolved deps from the output, so the wording there reflects that). * fix(manifest): gradle --facts no longer emits unresolved deps as nodes The gradle init script was upserting unresolved deps into the components output with their selector-only coordinates (no classifier, no ext, possibly empty version), so a `.socket.facts.json` from `--ignore- unresolved` carried half-formed entries that downstream tools (coana's `mvn dependency:get`) would attempt to resolve against Maven Central and 404 on. The sbt plugin's `isEmittable` filter already drops these the same way — this aligns gradle to match. `--ignore-unresolved` now means the same thing on both commands: unresolved deps drive the abort-vs-warn decision but never reach the emitted facts file. Flag descriptions and help text reworded to match. Per mtorp's PR feedback.
1 parent 8be8afb commit c1987a9

13 files changed

Lines changed: 302 additions & 37 deletions

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
1212
### Changed
1313
- **Bazel diagnostics**`socket manifest bazel --verbose` now emits bounded subprocess traces with argv, cwd, duration, exit status, output sizes, and failure stderr tails to make customer log-only triage safer and faster.
1414

15+
## [1.1.107](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.107) - 2026-05-28
16+
17+
### Changed
18+
- **`socket manifest gradle --facts [beta]`** (and its `kotlin` alias) gained `--configs` and `--ignore-unresolved`, matching `socket manifest scala --facts`. `--configs` takes comma-separated glob patterns (e.g. `*CompileClasspath,*RuntimeClasspath`) to restrict resolution to matching Gradle configurations; unresolved dependencies are now a fatal error by default — pass `--ignore-unresolved` for the previous lenient behavior.
19+
- **`socket manifest scala --facts --configs`** now accepts glob patterns too (e.g. `*Test*`) for consistency with the gradle command. Bare names (no `*`/`?`) keep working as exact-name filters, so existing usages are unchanged.
20+
21+
### Fixed
22+
- **`socket manifest gradle --facts`** now works on Gradle builds with the configuration cache enabled (default on Gradle 9), which previously failed with `Task.project at execution time` errors.
23+
1524
## [1.1.106](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.106) - 2026-05-27
1625

1726
### Added

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "socket",
3-
"version": "1.1.106",
3+
"version": "1.1.107",
44
"description": "CLI for Socket.dev",
55
"homepage": "https://github.com/SocketDev/socket-cli",
66
"license": "MIT AND OFL-1.1",

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

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ const config: CliCommandConfig = {
3434
description:
3535
'Emit a Socket facts JSON file (`.socket.facts.json`) describing the resolved dependency graph instead of generating `pom.xml` files',
3636
},
37+
configs: {
38+
type: 'string',
39+
description:
40+
'With --facts: comma-separated glob patterns matched against Gradle configuration names (case-sensitive, `*` and `?` wildcards). e.g. `*CompileClasspath,*RuntimeClasspath` to skip tooling configs. Default: every resolvable configuration except AGP instrumented-test classpaths',
41+
},
42+
ignoreUnresolved: {
43+
type: 'boolean',
44+
description:
45+
'With --facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)',
46+
},
3747
gradleOpts: {
3848
type: 'string',
3949
description:
@@ -69,11 +79,20 @@ const config: CliCommandConfig = {
6979
7080
- it works with your \`gradlew\` from your repo and local settings and config
7181
82+
Pass --facts to instead emit a single \`.socket.facts.json\` describing the
83+
resolved dependency graph of the whole build (no \`pom.xml\` files). An
84+
unresolved dependency is a fatal error. With --facts you can pass
85+
--configs=<comma-separated glob patterns> to restrict resolution to
86+
matching configurations (e.g. \`*CompileClasspath,*RuntimeClasspath\`),
87+
and --ignore-unresolved to warn on unresolved dependencies instead of
88+
failing the run.
89+
7290
Support is beta. Please report issues or give us feedback on what's missing.
7391
7492
Examples
7593
7694
$ ${command} .
95+
$ ${command} --facts .
7796
$ ${command} --bin=../gradlew .
7897
`,
7998
}
@@ -116,7 +135,7 @@ async function run(
116135
sockJson?.defaults?.manifest?.gradle,
117136
)
118137

119-
let { bin, facts, gradleOpts, verbose } = cli.flags
138+
let { bin, configs, facts, gradleOpts, ignoreUnresolved, verbose } = cli.flags
120139

121140
// Set defaults for any flag/arg that is not given. Check socket.json first.
122141
if (!bin) {
@@ -154,6 +173,40 @@ async function run(
154173
facts = false
155174
}
156175
}
176+
if (configs === undefined) {
177+
if (sockJson.defaults?.manifest?.gradle?.configs !== undefined) {
178+
configs = sockJson.defaults?.manifest?.gradle?.configs
179+
logger.info(`Using default --configs from ${SOCKET_JSON}:`, configs)
180+
} else {
181+
configs = ''
182+
}
183+
}
184+
if (ignoreUnresolved === undefined) {
185+
if (sockJson.defaults?.manifest?.gradle?.ignoreUnresolved !== undefined) {
186+
ignoreUnresolved = sockJson.defaults?.manifest?.gradle?.ignoreUnresolved
187+
logger.info(
188+
`Using default --ignore-unresolved from ${SOCKET_JSON}:`,
189+
ignoreUnresolved,
190+
)
191+
} else {
192+
ignoreUnresolved = false
193+
}
194+
}
195+
196+
// `--configs` and `--ignore-unresolved` only affect --facts; the pom path
197+
// (the legacy `socketGenerateMaven` task) has no equivalent knobs. Warn
198+
// rather than silently ignore an explicitly-passed flag. (socket.json
199+
// defaults don't trip this — only a flag actually present on the command
200+
// line does.)
201+
if (
202+
!facts &&
203+
(cli.flags['configs'] !== undefined ||
204+
cli.flags['ignoreUnresolved'] !== undefined)
205+
) {
206+
logger.warn(
207+
'The `--configs` and `--ignore-unresolved` options only apply with `--facts`; ignoring them.',
208+
)
209+
}
157210

158211
if (verbose) {
159212
logger.group('- ', parentName, config.commandName, ':')
@@ -197,8 +250,10 @@ async function run(
197250
if (facts) {
198251
await convertGradleToFacts({
199252
bin: String(bin),
253+
configs: String(configs || ''),
200254
cwd,
201255
gradleOpts: parsedGradleOpts,
256+
ignoreUnresolved: Boolean(ignoreUnresolved),
202257
verbose: Boolean(verbose),
203258
})
204259
return

src/commands/manifest/cmd-manifest-gradle.test.mts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ describe('socket manifest gradle', async () => {
2424
2525
Options
2626
--bin Location of gradlew binary to use, default: CWD/gradlew
27+
--configs With --facts: comma-separated glob patterns matched against Gradle configuration names (case-sensitive, \`*\` and \`?\` wildcards). e.g. \`*CompileClasspath,*RuntimeClasspath\` to skip tooling configs. Default: every resolvable configuration except AGP instrumented-test classpaths
2728
--facts Emit a Socket facts JSON file (\`.socket.facts.json\`) describing the resolved dependency graph instead of generating \`pom.xml\` files
2829
--gradle-opts Additional options to pass on to ./gradlew, see \`./gradlew --help\`
30+
--ignore-unresolved With --facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)
2931
--verbose Print debug messages
3032
3133
Uses gradle, preferably through your local project \`gradlew\`, to generate a
@@ -46,11 +48,20 @@ describe('socket manifest gradle', async () => {
4648
4749
- it works with your \`gradlew\` from your repo and local settings and config
4850
51+
Pass --facts to instead emit a single \`.socket.facts.json\` describing the
52+
resolved dependency graph of the whole build (no \`pom.xml\` files). An
53+
unresolved dependency is a fatal error. With --facts you can pass
54+
--configs=<comma-separated glob patterns> to restrict resolution to
55+
matching configurations (e.g. \`*CompileClasspath,*RuntimeClasspath\`),
56+
and --ignore-unresolved to warn on unresolved dependencies instead of
57+
failing the run.
58+
4959
Support is beta. Please report issues or give us feedback on what's missing.
5060
5161
Examples
5262
5363
$ socket manifest gradle .
64+
$ socket manifest gradle --facts .
5465
$ socket manifest gradle --bin=../gradlew ."
5566
`,
5667
)

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

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ const config: CliCommandConfig = {
3939
description:
4040
'Emit a Socket facts JSON file (`.socket.facts.json`) describing the resolved dependency graph instead of generating `pom.xml` files',
4141
},
42+
configs: {
43+
type: 'string',
44+
description:
45+
'With --facts: comma-separated glob patterns matched against Gradle configuration names (case-sensitive, `*` and `?` wildcards). e.g. `*CompileClasspath,*RuntimeClasspath` to skip tooling configs. Default: every resolvable configuration except AGP instrumented-test classpaths',
46+
},
47+
ignoreUnresolved: {
48+
type: 'boolean',
49+
description:
50+
'With --facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)',
51+
},
4252
gradleOpts: {
4353
type: 'string',
4454
description:
@@ -121,7 +131,7 @@ async function run(
121131
sockJson?.defaults?.manifest?.gradle,
122132
)
123133

124-
let { bin, facts, gradleOpts, verbose } = cli.flags
134+
let { bin, configs, facts, gradleOpts, ignoreUnresolved, verbose } = cli.flags
125135

126136
// Set defaults for any flag/arg that is not given. Check socket.json first.
127137
if (!bin) {
@@ -159,6 +169,35 @@ async function run(
159169
facts = false
160170
}
161171
}
172+
if (configs === undefined) {
173+
if (sockJson.defaults?.manifest?.gradle?.configs !== undefined) {
174+
configs = sockJson.defaults?.manifest?.gradle?.configs
175+
logger.info(`Using default --configs from ${SOCKET_JSON}:`, configs)
176+
} else {
177+
configs = ''
178+
}
179+
}
180+
if (ignoreUnresolved === undefined) {
181+
if (sockJson.defaults?.manifest?.gradle?.ignoreUnresolved !== undefined) {
182+
ignoreUnresolved = sockJson.defaults?.manifest?.gradle?.ignoreUnresolved
183+
logger.info(
184+
`Using default --ignore-unresolved from ${SOCKET_JSON}:`,
185+
ignoreUnresolved,
186+
)
187+
} else {
188+
ignoreUnresolved = false
189+
}
190+
}
191+
192+
if (
193+
!facts &&
194+
(cli.flags['configs'] !== undefined ||
195+
cli.flags['ignoreUnresolved'] !== undefined)
196+
) {
197+
logger.warn(
198+
'The `--configs` and `--ignore-unresolved` options only apply with `--facts`; ignoring them.',
199+
)
200+
}
162201

163202
if (verbose) {
164203
logger.group('- ', parentName, config.commandName, ':')
@@ -202,8 +241,10 @@ async function run(
202241
if (facts) {
203242
await convertGradleToFacts({
204243
bin: String(bin),
244+
configs: String(configs || ''),
205245
cwd,
206246
gradleOpts: parsedGradleOpts,
247+
ignoreUnresolved: Boolean(ignoreUnresolved),
207248
verbose: Boolean(verbose),
208249
})
209250
return

src/commands/manifest/cmd-manifest-kotlin.test.mts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ describe('socket manifest kotlin', async () => {
2424
2525
Options
2626
--bin Location of gradlew binary to use, default: CWD/gradlew
27+
--configs With --facts: comma-separated glob patterns matched against Gradle configuration names (case-sensitive, \`*\` and \`?\` wildcards). e.g. \`*CompileClasspath,*RuntimeClasspath\` to skip tooling configs. Default: every resolvable configuration except AGP instrumented-test classpaths
2728
--facts Emit a Socket facts JSON file (\`.socket.facts.json\`) describing the resolved dependency graph instead of generating \`pom.xml\` files
2829
--gradle-opts Additional options to pass on to ./gradlew, see \`./gradlew --help\`
30+
--ignore-unresolved With --facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)
2931
--verbose Print debug messages
3032
3133
Uses gradle, preferably through your local project \`gradlew\`, to generate a

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ const config: CliCommandConfig = {
3737
configs: {
3838
type: 'string',
3939
description:
40-
'With --facts: comma-separated sbt configurations to resolve (default: compile,optional,provided,runtime,test)',
40+
'With --facts: comma-separated glob patterns matched against sbt configuration names (case-sensitive, `*` and `?` wildcards). Bare names (no wildcards) act as exact-name filters. Default: compile,optional,provided,runtime,test',
4141
},
4242
ignoreUnresolved: {
4343
type: 'boolean',
4444
description:
45-
'With --facts: skip dependencies that fail to resolve instead of failing the run',
45+
'With --facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)',
4646
},
4747
out: {
4848
type: 'string',
@@ -95,8 +95,10 @@ const config: CliCommandConfig = {
9595
resolved dependency graph of the whole build (no \`pom.xml\` files). It reads
9696
dependency metadata only and never downloads artifacts; an unresolved
9797
dependency is a fatal error. With --facts you can pass
98-
--configs=compile,test to choose which sbt configurations to resolve, and
99-
--ignore-unresolved to skip dependencies that fail to resolve.
98+
--configs=<comma-separated glob patterns> to choose which sbt configurations
99+
to resolve (e.g. \`compile,test\` for exact names or \`*Test*\` for variants),
100+
and --ignore-unresolved to warn on unresolved dependencies instead of
101+
failing the run.
100102
101103
Support is beta. Please report issues or give us feedback on what's missing.
102104

src/commands/manifest/cmd-manifest-scala.test.mts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ describe('socket manifest scala', async () => {
2424
2525
Options
2626
--bin Location of sbt binary to use
27-
--configs With --facts: comma-separated sbt configurations to resolve (default: compile,optional,provided,runtime,test)
27+
--configs With --facts: comma-separated glob patterns matched against sbt configuration names (case-sensitive, \`*\` and \`?\` wildcards). Bare names (no wildcards) act as exact-name filters. Default: compile,optional,provided,runtime,test
2828
--facts Emit a Socket facts JSON file (\`.socket.facts.json\`) describing the resolved dependency graph instead of generating \`pom.xml\` files
29-
--ignore-unresolved With --facts: skip dependencies that fail to resolve instead of failing the run
29+
--ignore-unresolved With --facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file)
3030
--out Path of output file; where to store the resulting manifest, see also --stdout
3131
--sbt-opts Additional options to pass on to sbt, as per \`sbt --help\`
3232
--stdout Print resulting pom.xml to stdout (supersedes --out)
@@ -58,8 +58,10 @@ describe('socket manifest scala', async () => {
5858
resolved dependency graph of the whole build (no \`pom.xml\` files). It reads
5959
dependency metadata only and never downloads artifacts; an unresolved
6060
dependency is a fatal error. With --facts you can pass
61-
--configs=compile,test to choose which sbt configurations to resolve, and
62-
--ignore-unresolved to skip dependencies that fail to resolve.
61+
--configs=<comma-separated glob patterns> to choose which sbt configurations
62+
to resolve (e.g. \`compile,test\` for exact names or \`*Test*\` for variants),
63+
and --ignore-unresolved to warn on unresolved dependencies instead of
64+
failing the run.
6365
6466
Support is beta. Please report issues or give us feedback on what's missing.
6567

src/commands/manifest/convert-gradle-to-facts.mts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@ import constants from '../../constants.mts'
88

99
export async function convertGradleToFacts({
1010
bin,
11+
configs,
1112
cwd,
1213
gradleOpts,
14+
ignoreUnresolved,
1315
verbose,
1416
}: {
1517
bin: string
18+
configs: string
1619
cwd: string
1720
gradleOpts: string[]
21+
ignoreUnresolved: boolean
1822
verbose: boolean
1923
}): Promise<void> {
2024
const rBin = path.resolve(cwd, bin)
@@ -43,7 +47,34 @@ export async function convertGradleToFacts({
4347
constants.distPath,
4448
'socket-facts.init.gradle',
4549
)
50+
// Disable Gradle's configuration cache for the facts run. The init
51+
// script resolves dependencies via the legacy
52+
// `Configuration.resolvedConfiguration` API (the only public API that
53+
// surfaces classifier + extension metadata) and registers per-
54+
// subproject tasks that share a `gradle.ext` accumulator — neither
55+
// pattern is compatible with the configuration cache, which would
56+
// otherwise be on by default for projects with
57+
// `org.gradle.configuration-cache=true` in `gradle.properties`. The
58+
// Provider-based CC-safe alternatives (`ResolutionResult` /
59+
// `ArtifactView.resolvedArtifacts`) only exist in Gradle 7.4+ and
60+
// they don't expose classifier/extension, so they aren't a usable
61+
// replacement here. Using `-D` rather than `--no-configuration-cache`
62+
// keeps us compatible with older Gradle versions that don't recognize
63+
// the flag — the system property is silently ignored when the
64+
// feature doesn't exist.
65+
// Both knobs are passed as Gradle project properties so the init script
66+
// can read them via `rp.findProperty(...)`, matching how
67+
// `socket.outputDirectory` / `socket.outputFile` are already wired.
68+
const socketProps: string[] = []
69+
if (ignoreUnresolved) {
70+
socketProps.push('-Psocket.ignoreUnresolved=true')
71+
}
72+
if (configs) {
73+
socketProps.push(`-Psocket.configs=${configs}`)
74+
}
4675
const commandArgs = [
76+
'-Dorg.gradle.configuration-cache=false',
77+
...socketProps,
4778
'--init-script',
4879
initLocation,
4980
...gradleOpts,

src/commands/manifest/generate_auto_manifest.mts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,13 @@ export async function generateAutoManifest({
8989
logger.log(
9090
'Detected a gradle build (Gradle, Kotlin, Scala), generating Socket facts...',
9191
)
92-
await convertGradleToFacts(gradleArgs)
92+
await convertGradleToFacts({
93+
...gradleArgs,
94+
configs: sockJson.defaults?.manifest?.gradle?.configs ?? '',
95+
ignoreUnresolved: Boolean(
96+
sockJson.defaults?.manifest?.gradle?.ignoreUnresolved,
97+
),
98+
})
9399
} else {
94100
logger.log(
95101
'Detected a gradle build (Gradle, Kotlin, Scala), running default gradle generator...',

0 commit comments

Comments
 (0)