Gradle dependency tool resolution tier is advertised but ignored #22

Closed
opened 2026-06-04 16:19:27 +00:00 by KofTwentyTwo · 2 comments
KofTwentyTwo commented 2026-06-04 16:19:27 +00:00 (Migrated from github.com)

Finding

The audit interface advertises an optional native-tool dependency resolution tier, but the Gradle resolver ignores the option and always returns unresolved detect-only results.

Evidence

  • docs/license-tool-requirements.md:19 says dependency resolution can optionally shell out to the ecosystem's native tool.
  • docs/license-tool-design.md:25 documents the resolver tier option.
  • cmd/license-tool/commands.go:387 to cmd/license-tool/commands.go:395 passes the tool-shell-out boolean to every detected resolver.
  • internal/resolve/gradle.go:13 to internal/resolve/gradle.go:23 documents Gradle as detect-only.
  • internal/resolve/gradle.go:53 to internal/resolve/gradle.go:63 ignores resolver options and always uses the v1 not-implemented reason.

Reproduction

  1. Create or use a Gradle project with declared dependencies.
  2. Run audit using the native-tool resolver tier.
  3. Observe that Gradle dependencies remain unresolved with the detect-only v1 reason.

Why It Matters

Gradle users explicitly choosing the tool tier expect better results than on-disk detection. Instead, the command silently provides the same unresolved output, which creates false confidence that the native tool path was attempted.

Suggested Fix

Either implement a Gradle tool-tier adapter, or reject the tool tier for Gradle with an explicit message that the ecosystem does not support it yet. Update docs and tests to match.

## Finding The audit interface advertises an optional native-tool dependency resolution tier, but the Gradle resolver ignores the option and always returns unresolved detect-only results. ## Evidence - `docs/license-tool-requirements.md:19` says dependency resolution can optionally shell out to the ecosystem's native tool. - `docs/license-tool-design.md:25` documents the resolver tier option. - `cmd/license-tool/commands.go:387` to `cmd/license-tool/commands.go:395` passes the tool-shell-out boolean to every detected resolver. - `internal/resolve/gradle.go:13` to `internal/resolve/gradle.go:23` documents Gradle as detect-only. - `internal/resolve/gradle.go:53` to `internal/resolve/gradle.go:63` ignores resolver options and always uses the v1 not-implemented reason. ## Reproduction 1. Create or use a Gradle project with declared dependencies. 2. Run audit using the native-tool resolver tier. 3. Observe that Gradle dependencies remain unresolved with the detect-only v1 reason. ## Why It Matters Gradle users explicitly choosing the tool tier expect better results than on-disk detection. Instead, the command silently provides the same unresolved output, which creates false confidence that the native tool path was attempted. ## Suggested Fix Either implement a Gradle tool-tier adapter, or reject the tool tier for Gradle with an explicit message that the ecosystem does not support it yet. Update docs and tests to match.
KofTwentyTwo commented 2026-06-04 16:29:47 +00:00 (Migrated from github.com)

Status: confirmed — Gradle's Resolve ignores opts entirely and always emits the same v1 not-implemented reason, regardless of the tool tier. Reproduced with the built binary: audit --resolve-deps tool on a Gradle project returns resolution: unresolved with the detect-only v1 reason for com.google.guava:guava.

Severity: low (label says bug, which fits). No incorrect license is ever produced; the gap is honestly surfaced as unresolved with a reason, and that triggers the unresolved-dependency violation. The defect is a UX/expectation mismatch (tool tier silently no-ops for Gradle), not a correctness or safety bug. dependencies label is a Dependabot auto-label and not meaningful here.

Verified root cause: internal/resolve/gradle.go:56 (*GradleResolver).Resolve — its signature takes opts model.ResolveOptions but never reads opts.AllowToolShellOut; the const reason at gradle.go:62 is emitted unconditionally for every coordinate (gradle.go:71-79). Contrast with internal/resolve/maven.go:103 (*MavenResolver).Resolve, which gates r.augmentFromTool(...) on opts.AllowToolShellOut && hasUnresolved(out). The CLI wiring at cmd/license-tool/commands.go:390 does correctly thread AllowToolShellOut: allowToolShellOut into every detected resolver, so the boundary is sound — the omission is purely inside the Gradle resolver.

Suggested fix: Decide between the two options the issue proposes; either way, make Gradle's contract explicit rather than silently identical across tiers.

  • Minimal (recommended for now): mirror the npm precedent at internal/resolve/npm.go:53-56, which explicitly documents opts.AllowToolShellOut is unused with a justification. Gradle's doc comment (gradle.go:13-23, "enable a Gradle license plugin and re-run with the tool tier once v2 wires it in") already acknowledges the gap but the Resolve doc/code don't note that opts is intentionally ignored — tighten that. Optionally branch on opts.AllowToolShellOut to emit a distinct reason (e.g. "...tool tier not yet supported for gradle") so a user who chose --resolve-deps tool sees the tier was considered but is unimplemented, rather than the generic detect-only message. This keeps the 100% gate green by adding one branch + one test in internal/resolve/gradle_test.go (currently both cases call Resolve(dir, model.ResolveOptions{}) with the tool tier untested — see gradle_test.go:65,98).
  • Full: implement an augmentFromTool adapter as Maven did. Larger scope; needs a Gradle plugin invocation strategy.

This is a resolver-tier concern, not a header-insertion one, so the v0.2.1 universal-shebang precedent (which had to touch both the render INSERT boundary and the detect PreserveBoundary plus the filetype table) does not apply here — there is no second boundary or filetype table to update. The relevant "touch both" precedent for this issue is instead "match the npm/maven pattern for handling opts.AllowToolShellOut."

Corrections to the issue:

  • The body frames this as "advertised but ignored" specifically for Gradle. Precisely: the tool tier is advertised generically (design doc decision #3 and the --resolve-deps ondisk|tool|off flag in docs/license-tool-design.md:25), not per-ecosystem. npm (npm.go:53-56) is in the same boat as Gradle (tier accepted, no-op) but does so deliberately and documented; only Maven actually implements it. So the over-claim is implying Gradle uniquely promises the tier — the promise is tool-wide and partially unfulfilled.
  • "creates false confidence that the native tool path was attempted" is slightly strong: output is unresolved with an explicit reason and produces a violation, so it doesn't claim success — but it also doesn't disclose that the chosen tier was a no-op. That nuance is the real defect.
  • Line refs check out (commands.go:387-395, gradle.go:13-23, gradle.go:53-63, doc lines).

Open questions:

  • Scope decision the maintainer must make: implement a real Gradle tool adapter (v2 work) vs. document/reject the tier now (v1 polish)? The doc comment already promises "v2," suggesting the reject-with-clear-message path for now.
  • If rejecting: should an explicit tool-tier request for an unsupported ecosystem be a soft no-op (current), a distinct reason string, or a hard usage error (exit 2)? This is a cross-resolver policy choice and ideally applies to npm too, for consistency.

Overlaps: none identified from this issue alone; if there is a sibling issue tracking npm's identical no-op tier or a general "tool tier consistency" item, this should be grouped with it.

Automated maintainer review.

**Status**: confirmed — Gradle's `Resolve` ignores `opts` entirely and always emits the same v1 not-implemented reason, regardless of the tool tier. Reproduced with the built binary: `audit --resolve-deps tool` on a Gradle project returns `resolution: unresolved` with the detect-only v1 reason for `com.google.guava:guava`. **Severity**: low (label says `bug`, which fits). No incorrect license is ever produced; the gap is honestly surfaced as `unresolved` with a reason, and that triggers the `unresolved-dependency` violation. The defect is a UX/expectation mismatch (tool tier silently no-ops for Gradle), not a correctness or safety bug. `dependencies` label is a Dependabot auto-label and not meaningful here. **Verified root cause**: `internal/resolve/gradle.go:56` `(*GradleResolver).Resolve` — its signature takes `opts model.ResolveOptions` but never reads `opts.AllowToolShellOut`; the `const reason` at `gradle.go:62` is emitted unconditionally for every coordinate (`gradle.go:71-79`). Contrast with `internal/resolve/maven.go:103` `(*MavenResolver).Resolve`, which gates `r.augmentFromTool(...)` on `opts.AllowToolShellOut && hasUnresolved(out)`. The CLI wiring at `cmd/license-tool/commands.go:390` does correctly thread `AllowToolShellOut: allowToolShellOut` into every detected resolver, so the boundary is sound — the omission is purely inside the Gradle resolver. **Suggested fix**: Decide between the two options the issue proposes; either way, make Gradle's contract explicit rather than silently identical across tiers. - Minimal (recommended for now): mirror the npm precedent at `internal/resolve/npm.go:53-56`, which explicitly documents `opts.AllowToolShellOut is unused` with a justification. Gradle's doc comment (`gradle.go:13-23`, "enable a Gradle license plugin and re-run with the tool tier once v2 wires it in") already acknowledges the gap but the `Resolve` doc/code don't note that `opts` is intentionally ignored — tighten that. Optionally branch on `opts.AllowToolShellOut` to emit a distinct reason (e.g. "...tool tier not yet supported for gradle") so a user who chose `--resolve-deps tool` sees the tier was considered but is unimplemented, rather than the generic detect-only message. This keeps the 100% gate green by adding one branch + one test in `internal/resolve/gradle_test.go` (currently both cases call `Resolve(dir, model.ResolveOptions{})` with the tool tier untested — see `gradle_test.go:65,98`). - Full: implement an `augmentFromTool` adapter as Maven did. Larger scope; needs a Gradle plugin invocation strategy. This is a resolver-tier concern, not a header-insertion one, so the v0.2.1 universal-shebang precedent (which had to touch both the render INSERT boundary and the detect `PreserveBoundary` plus the filetype table) does **not** apply here — there is no second boundary or filetype table to update. The relevant "touch both" precedent for this issue is instead "match the npm/maven pattern for handling `opts.AllowToolShellOut`." **Corrections to the issue**: - The body frames this as "advertised but ignored" specifically for Gradle. Precisely: the tool tier is advertised *generically* (design doc decision #3 and the `--resolve-deps ondisk|tool|off` flag in `docs/license-tool-design.md:25`), not per-ecosystem. npm (`npm.go:53-56`) is in the same boat as Gradle (tier accepted, no-op) but does so deliberately and documented; only Maven actually implements it. So the over-claim is implying Gradle uniquely promises the tier — the promise is tool-wide and partially unfulfilled. - "creates false confidence that the native tool path was attempted" is slightly strong: output is `unresolved` with an explicit reason and produces a violation, so it doesn't claim success — but it also doesn't disclose that the chosen tier was a no-op. That nuance is the real defect. - Line refs check out (`commands.go:387-395`, `gradle.go:13-23`, `gradle.go:53-63`, doc lines). **Open questions**: - Scope decision the maintainer must make: implement a real Gradle tool adapter (v2 work) vs. document/reject the tier now (v1 polish)? The doc comment already promises "v2," suggesting the reject-with-clear-message path for now. - If rejecting: should an explicit tool-tier request for an unsupported ecosystem be a soft no-op (current), a distinct reason string, or a hard usage error (exit 2)? This is a cross-resolver policy choice and ideally applies to npm too, for consistency. **Overlaps**: none identified from this issue alone; if there is a sibling issue tracking npm's identical no-op tier or a general "tool tier consistency" item, this should be grouped with it. _Automated maintainer review._
KofTwentyTwo commented 2026-06-04 20:28:17 +00:00 (Migrated from github.com)

Resolved on develop in commit 742c124e6ec2afa9312075db3112bc2def3c91ff.

Validation:

Closing as fixed.

Resolved on `develop` in commit `742c124e6ec2afa9312075db3112bc2def3c91ff`. Validation: - CI: success, https://github.com/KofTwentyTwo/license-tool/actions/runs/26977377097 - CodeQL: success, https://github.com/KofTwentyTwo/license-tool/actions/runs/26977376421 - Coverage gate: 100% (2087/2087) Closing as fixed.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: KofTwentyTwo/license-tool#22