Skip to content

Strategy/Scala: route sbt 1.4+ with explicit DependencyTreePlugin to built-in command (TKT-15490)#1711

Merged
zlav merged 11 commits into
masterfrom
fix-sbt-dependency-tree-html-routing
Jun 24, 2026
Merged

Strategy/Scala: route sbt 1.4+ with explicit DependencyTreePlugin to built-in command (TKT-15490)#1711
zlav merged 11 commits into
masterfrom
fix-sbt-dependency-tree-html-routing

Conversation

@zlav

@zlav zlav commented May 12, 2026

Copy link
Copy Markdown
Member

Route sbt 1.4+ DependencyTreePlugin to the uppercase dependencyBrowseTreeHTML task, and detect plugins from the real sbt plugins output layout — restoring complete deep-dependency graphs on sbt 1.4+.

What changed

  • On sbt 1.4+ with addDependencyTreePlugin, fossa ran the lowercase dependencyBrowseTreeHtml task (added for sbt <1.4 compat); modern sbt only exposes the uppercase task, so it failed and analysis silently fell back to a poms-only graph. Detection now classifies legacy (net.virtualvoid…DependencyGraphPlugin, lowercase) vs modern (sbt.plugins.DependencyTreePlugin, uppercase) and runs the matching task.
  • Plugin detection now parses sbt's actual plugins layout — bare FQCNs under Enabled plugins in <project>:, with disablePlugins(...) entries excluded via the trailing not-enabled section. An interim : enabled in suffix anchor matched nothing in real output and dropped the built-in MiniDependencyTreePlugin on plain sbt 1.4+ projects.
  • hasDependencyPlugins returns a DependencyPluginsDetected record instead of (Bool, Bool); the one caller (Strategy.Scala.findProjects) is updated.

Validation

  • cabal build; fourmolu --mode check.
  • unit-tests --match "detectDependencyPlugins" → 8/8 pass, including a verbatim sbt -batch -no-colors plugins capture as a regression guard.
  • Full Scala integration test needs nix+sbt+network (~15 min) — relying on CI.

References

Why not to merge this

  • String-parse coupling — keys off sbt's human-readable plugins section headers; a future sbt reformat silently breaks detection again (no machine-readable contract).
  • Legacy branches unverified live — only the sbt 1.9.8 fixtures are real captures; the net.virtualvoid and modern-without-mini fixtures are hand-built to the same layout, not captured from old sbt.
  • Substring, not exact-line matchisInfixOf on the enabled section tolerates suffix variants but would miscount a plugin whose FQCN is a substring of another (none exist today).
  • Symptom vs source — the detection regression was self-inflicted by earlier commits on this branch; squashing them would avoid shipping the bug+fix pair, though keeping them documents the trap.
  • Worth merging anyway: restores complete deep graphs for the common sbt 1.4+ case, proven against the exact fixture the integration test exercises.

… task

When a project explicitly enables `addDependencyTreePlugin` on sbt 1.4+,
fossa-cli detected `sbt.plugins.DependencyTreePlugin` and dispatched to
the same path used by the legacy `net.virtualvoid.sbt.graph.DependencyGraphPlugin`,
which runs the lowercase `dependencyBrowseTreeHtml` task. sbt 1.4+ only
exposes the uppercase `dependencyBrowseTreeHTML`, so the task failed and
the analyzer silently dropped deep dependencies.

Distinguish the two plugins at detection time and pick the correct task
casing per plugin.

TKT-15490 / ANE-2718.
@zlav

zlav commented May 13, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR refactors Scala sbt plugin detection from boolean tuple results to structured types. New types DependencyTreePluginKind and DependencyPluginsDetected replace tuple returns in hasDependencyPlugins, detectDependencyPlugins, and genTreeJson. The detection logic now classifies plugins as modern or legacy with modern preference when both exist. A new mkDependencyBrowseTreeCmd helper maps plugin kinds to correct sbt task names (dependencyBrowseTreeHTML for modern, dependencyBrowseTreeHtml for legacy). The strategy layer imports and destructures the new record type, extracting the optional plugin kind and passing it to tree generation. Tests and fixtures are updated to validate the new behavior.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description includes overview, validation, references, and acknowledgment of risks, but lacks explicit sections for acceptance criteria, testing plan, metrics, and checklist items as specified in the template. Add the missing template sections: expand acceptance criteria, provide a numbered testing plan with concrete reproduction steps, document metrics if applicable, and include the full checklist with checkmarks or explanations for each item.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: routing sbt 1.4+ projects with explicit DependencyTreePlugin to the correct built-in command, with a reference to the issue tracker.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Strategy/Scala.hs (1)

206-212: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider making context and error messages task-agnostic.

The context message on line 206 and error message on line 212 both hardcode dependencyBrowseTreeHTML, but with the new routing logic the actual task executed could be either dependencyBrowseTreeHTML (modern) or dependencyBrowseTreeHtml (legacy) depending on the detected plugin kind. While these messages appear in a code path that runs after tree JSON generation (and thus don't directly cause incorrect behavior), using a generic message like "Analyzing sbt dependencies using dependency tree JSON" would be more accurate and less confusing for users who might see the legacy task invoked.

📝 Suggested message updates
-analyzeWithDepTreeJson (ScalaProject _ treeJson closure) = context "Analyzing sbt dependencies using dependencyBrowseTreeHTML" $ do
+analyzeWithDepTreeJson (ScalaProject _ treeJson closure) = context "Analyzing sbt dependencies using dependency tree JSON" $ do
   treeJson' <-
     errCtx MissingFullDependencyPluginCtx $
       errHelp MissingFullDependencyPluginHelp $
         errDoc sbtDepsGraphPluginUrl $
           errDoc scalaFossaDocUrl $
-            fromMaybeText "Could not retrieve output from sbt dependencyBrowseTreeHTML" treeJson
+            fromMaybeText "Could not retrieve dependency tree JSON output from sbt" treeJson
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Strategy/Scala.hs` around lines 206 - 212, The context and error messages
in analyzeWithDepTreeJson (pattern ScalaProject _ treeJson closure) hardcode
dependencyBrowseTreeHTML which can be either dependencyBrowseTreeHTML or
dependencyBrowseTreeHtml; update the context string and the fromMaybeText error
message to be task-agnostic (e.g., "Analyzing sbt dependencies using dependency
tree JSON" and "Could not retrieve output from sbt dependency tree JSON") so
they accurately reflect either modern or legacy task execution while still
referencing treeJson/ScalaProject/analyzeWithDepTreeJson to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/Strategy/Scala.hs`:
- Around line 206-212: The context and error messages in analyzeWithDepTreeJson
(pattern ScalaProject _ treeJson closure) hardcode dependencyBrowseTreeHTML
which can be either dependencyBrowseTreeHTML or dependencyBrowseTreeHtml; update
the context string and the fromMaybeText error message to be task-agnostic
(e.g., "Analyzing sbt dependencies using dependency tree JSON" and "Could not
retrieve output from sbt dependency tree JSON") so they accurately reflect
either modern or legacy task execution while still referencing
treeJson/ScalaProject/analyzeWithDepTreeJson to locate the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: f374c435-f539-45fd-b422-6fda86503657

📥 Commits

Reviewing files that changed from the base of the PR and between ae690f8 and fe7b09d.

📒 Files selected for processing (4)
  • Changelog.md
  • src/Strategy/Scala.hs
  • src/Strategy/Scala/Plugin.hs
  • test/Scala/PluginSpec.hs

zlav and others added 8 commits May 12, 2026 17:23
The context label and error text mentioned dependencyBrowseTreeHTML, but
the routing change can also pick the legacy dependencyBrowseTreeHtml task.
Reword to "dependency tree JSON" so the message is correct in both cases.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`sbt plugins` lists user-disabled plugins (`disablePlugins(...)`) with
a ": disabled in <scope>" suffix. The pre-existing substring match on
the bare FQCN counted those as present, which routed `findProjects`
to a task the active plugin set does not provide — same shape as the
TKT-15490 regression, different trigger.

Detection now requires "<FQCN>: enabled in" verbatim. Fixtures cover
the two disabled cases (mini disabled, modern disabled), both
expected to classify as Nothing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the nested if-then-else with a `find` over a precedence-ordered
lookup table. Behavior is identical: `find` returns the first match so
ModernDependencyTreePlugin still wins over Legacy, and the shared
": enabled in" suffix keeps the three effective marker strings unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the format of older entries and link the PR instead of the ticket.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The find-based detection refactor anchored on a "<FQCN>: enabled in"
suffix that `sbt plugins` never prints. Real output (verified on sbt
1.9.8) lists bare plugin FQCNs under "Enabled plugins in <project>:"
sections, with disabled plugins moved to a trailing "Plugins that are
loaded to the build but not enabled in any subprojects:" section.

The suffix anchor matched nothing, so the built-in MiniDependencyTreePlugin
went undetected on sbt 1.4+ projects with no plugins.sbt (e.g.
scala3-example-project): the deep `dependencyTree` path never ran and
analysis fell back to generated poms (Partial graph), failing the
Analysis.Scala.scalaExampleProject integration test.

Detect a plugin by searching for its FQCN in the text before the
not-enabled marker, which preserves the disablePlugins guard. Replace
the fabricated unit fixtures with verbatim captures from
`sbt -batch -no-colors plugins`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"silently" was inaccurate: NonStrict emitted a MissingDeepDeps warning
before falling back to poms (Strict errored), so the drop was never
silent. The real fix is routing to the uppercase dependencyBrowseTreeHTML
task.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tree-html-routing

# Conflicts:
#	Changelog.md
@zlav zlav marked this pull request as ready for review June 19, 2026 01:37
@zlav zlav requested a review from a team as a code owner June 19, 2026 01:37
@zlav zlav requested a review from spatten June 19, 2026 01:37

@spatten spatten left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

Claude is calling out that there might be a separate mechanism that would cause missing dependencies. If hasMini = True and they have a multi-module project, then they get to line 184 of src/Strategy/Scala.hs, and fallback to a pom-only partial graph.

I think that's probably not the case here. I'm guessing we have a debug log to show us that the problem was the casing of the task. But I thought I'd mention it just in case it was useful.

Either way, I think that's a separate PR and this one is good to go.

@spatten

spatten commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

I'm going to fix the vulns while I'm in here. This is the same problem we saw on Sparkle - they're all Rust vulns

@zlav zlav enabled auto-merge (squash) June 23, 2026 23:56
@zlav zlav merged commit b5a1c53 into master Jun 24, 2026
19 checks passed
@zlav zlav deleted the fix-sbt-dependency-tree-html-routing branch June 24, 2026 00:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants