Strategy/Scala: route sbt 1.4+ with explicit DependencyTreePlugin to built-in command (TKT-15490)#1711
Conversation
… 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.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
WalkthroughThis PR refactors Scala sbt plugin detection from boolean tuple results to structured types. New types 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
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 winConsider 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 eitherdependencyBrowseTreeHTML(modern) ordependencyBrowseTreeHtml(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
📒 Files selected for processing (4)
Changelog.mdsrc/Strategy/Scala.hssrc/Strategy/Scala/Plugin.hstest/Scala/PluginSpec.hs
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
spatten
left a comment
There was a problem hiding this comment.
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.
|
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 |
Route sbt 1.4+
DependencyTreePluginto the uppercasedependencyBrowseTreeHTMLtask, and detect plugins from the realsbt pluginsoutput layout — restoring complete deep-dependency graphs on sbt 1.4+.What changed
addDependencyTreePlugin, fossa ran the lowercasedependencyBrowseTreeHtmltask (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.pluginslayout — bare FQCNs underEnabled plugins in <project>:, withdisablePlugins(...)entries excluded via the trailing not-enabled section. An interim: enabled insuffix anchor matched nothing in real output and dropped the built-inMiniDependencyTreePluginon plain sbt 1.4+ projects.hasDependencyPluginsreturns aDependencyPluginsDetectedrecord 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 verbatimsbt -batch -no-colors pluginscapture as a regression guard.References
-batchand-no-colorsfor sbt for compatibility with older SBT. #1356 (215a74f8).Why not to merge this
pluginssection headers; a future sbt reformat silently breaks detection again (no machine-readable contract).net.virtualvoidand modern-without-mini fixtures are hand-built to the same layout, not captured from old sbt.isInfixOfon the enabled section tolerates suffix variants but would miscount a plugin whose FQCN is a substring of another (none exist today).