@@ -4,10 +4,13 @@ module Strategy.Scala.Plugin (
44 hasDependencyPlugins ,
55 detectDependencyPlugins ,
66 genTreeJson ,
7+ DependencyTreePluginKind (.. ),
8+ DependencyPluginsDetected (.. ),
79) where
810
911import Control.Effect.Diagnostics (Diagnostics , fatalText )
1012import Control.Effect.Stack (context )
13+ import Data.List (find )
1114import Data.Maybe (mapMaybe )
1215import Data.String.Conversion (ConvertUtf8 (decodeUtf8 ), toString )
1316import Data.Text (Text )
@@ -22,45 +25,112 @@ import Effect.Exec (
2225import Path (Abs , Dir , File , Path , mkRelFile , parent , parseAbsFile , (</>) )
2326import Strategy.Scala.Common (mkSbtCommand )
2427
28+ -- | Which non-mini dependency-tree plugin (if any) the project has installed.
29+ --
30+ -- The two plugins differ in the casing of their @dependencyBrowseTree@ task.
31+ -- See 'mkDependencyBrowseTreeCmd' for the command names.
32+ data DependencyTreePluginKind
33+ = -- | @sbt.plugins.DependencyTreePlugin@. Built into sbt 1.4+ and enabled
34+ -- explicitly via @addDependencyTreePlugin@ in @plugins.sbt@. Provides the
35+ -- uppercase @dependencyBrowseTreeHTML@ task.
36+ ModernDependencyTreePlugin
37+ | -- | @net.virtualvoid.sbt.graph.DependencyGraphPlugin@. The third-party
38+ -- @sbt-dependency-graph@ plugin used on sbt < 1.4. Provides the lowercase
39+ -- @dependencyBrowseTreeHtml@ task.
40+ LegacyDependencyGraphPlugin
41+ deriving (Eq , Ord , Show )
42+
43+ -- | What the @sbt plugins@ output told us about dependency-tree plugins.
44+ data DependencyPluginsDetected = DependencyPluginsDetected
45+ { hasMiniDependencyTreePlugin :: Bool
46+ , dependencyTreePlugin :: Maybe DependencyTreePluginKind
47+ }
48+ deriving (Eq , Ord , Show )
49+
2550-- | Returns list of plugins used by sbt.
2651-- Ref: https://www.scala-sbt.org/1.x/docs/Plugins.html
2752getPlugins :: Command
2853getPlugins = mkSbtCommand " plugins"
2954
30- -- | Returns (hasMiniDependencyTreePlugin, hasDependencyTreePlugin) by running sbt plugins.
31- hasDependencyPlugins :: (Has Exec sig m , Has Diagnostics sig m ) => Path Abs Dir -> m ( Bool , Bool )
55+ -- | Detect which dependency-tree plugins are loaded by running @ sbt plugins@ .
56+ hasDependencyPlugins :: (Has Exec sig m , Has Diagnostics sig m ) => Path Abs Dir -> m DependencyPluginsDetected
3257hasDependencyPlugins projectDir = do
3358 stdoutText <- (TextLazy. toStrict . decodeUtf8) <$> context " Identifying plugins" (execThrow projectDir getPlugins)
3459 pure $ detectDependencyPlugins stdoutText
3560
36- -- | Detect dependency plugins from sbt plugins output.
37- -- Returns (hasMiniDependencyTreePlugin, hasDependencyTreePlugin).
38- detectDependencyPlugins :: Text -> (Bool , Bool )
61+ -- | Classify dependency-tree plugins from the @sbt plugins@ output.
62+ --
63+ -- The plugin names mapped here:
64+ --
65+ -- * @sbt.plugins.MiniDependencyTreePlugin@ — bundled with sbt 1.4+, gives
66+ -- us the @dependencyTree@ task used by 'Strategy.Scala.SbtDependencyTree'.
67+ -- * @sbt.plugins.DependencyTreePlugin@ — opt-in on sbt 1.4+ via
68+ -- @addDependencyTreePlugin@. Provides the uppercase
69+ -- @dependencyBrowseTreeHTML@ task.
70+ -- * @net.virtualvoid.sbt.graph.DependencyGraphPlugin@ — third-party plugin
71+ -- used on sbt < 1.4. Provides the lowercase @dependencyBrowseTreeHtml@
72+ -- task.
73+ --
74+ -- @sbt plugins@ groups its output into per-project @Enabled plugins in
75+ -- \<project\>:@ sections, listing one bare plugin FQCN per indented line,
76+ -- followed by a trailing @Plugins that are loaded to the build but not enabled
77+ -- in any subprojects:@ section. A plugin the user disabled via
78+ -- @disablePlugins(...)@ moves into that trailing section. We therefore search
79+ -- only the text *before* that marker, so a disabled (loaded-but-not-enabled)
80+ -- plugin is not mistaken for an active one.
81+ --
82+ -- The plugin FQCN appears on its own line with no @: enabled in@ suffix. An
83+ -- earlier attempt to anchor detection on such a suffix matched nothing in real
84+ -- sbt output, which silently dropped deep dependencies and fell back to poms
85+ -- (regressed TKT-15490). See @test/Scala/PluginSpec.hs@ for fixtures captured
86+ -- from actual @sbt -batch -no-colors plugins@ runs.
87+ --
88+ -- When both modern and legacy non-mini plugins are present we prefer the
89+ -- modern one (sbt 1.4+ wins) since legacy plugin presence on a modern sbt
90+ -- typically means the user has both kinds of declarations in their build.
91+ detectDependencyPlugins :: Text -> DependencyPluginsDetected
3992detectDependencyPlugins stdoutText =
40- ( Text. count " .MiniDependencyTreePlugin" stdoutText > 0
41- , Text. count " .DependencyTreePlugin" stdoutText > 0
42- || Text. count " net.virtualvoid.sbt.graph.DependencyGraphPlugin" stdoutText > 0 -- sbt < 1.4
43- )
93+ DependencyPluginsDetected
94+ { hasMiniDependencyTreePlugin = enabled " sbt.plugins.MiniDependencyTreePlugin"
95+ , dependencyTreePlugin = snd <$> find (enabled . fst ) treePlugins
96+ }
97+ where
98+ enabledSection = fst $ Text. breakOn notEnabledMarker stdoutText
99+ notEnabledMarker = " Plugins that are loaded to the build but not enabled"
100+ enabled name = name `Text.isInfixOf` enabledSection
101+ treePlugins =
102+ [ (" sbt.plugins.DependencyTreePlugin" , ModernDependencyTreePlugin )
103+ , (" net.virtualvoid.sbt.graph.DependencyGraphPlugin" , LegacyDependencyGraphPlugin )
104+ ]
44105
45- -- | Generates Dependency Trees.
46- -- Ref: https://github.com/sbt/sbt/blob/master/main/src/main/scala/sbt/plugins/DependencyTreeSettings.scala#L101
47- --
48- -- This command unlike 'dependencyBrowseTree', does not open
49- -- the browser when executed.
106+ -- | The sbt task that writes @tree.html@/@tree.json@ alongside its dependency
107+ -- output. Plugin name vs task casing:
50108--
51- -- It writes following files in target directory:
52- -- ./tree.json
53- -- ./tree.html
54- -- ./tree.data.js
109+ -- * 'ModernDependencyTreePlugin' (sbt 1.4+, @addDependencyTreePlugin@) →
110+ -- @dependencyBrowseTreeHTML@.
111+ -- * 'LegacyDependencyGraphPlugin' (sbt < 1.4, @sbt-dependency-graph@) →
112+ -- @dependencyBrowseTreeHtml@.
55113--
56- -- This command is only used when the plugin is installed explicitly, i.e. sbt < 1.4.
57- -- Newer versions of sbt will use the built-in dependency graph plugin.
58- mkDependencyBrowseTreeHTMLCmd :: Command
59- mkDependencyBrowseTreeHTMLCmd = mkSbtCommand " dependencyBrowseTreeHtml"
114+ -- Picking the wrong casing produces an sbt error like
115+ -- @[error] Not a valid command: dependencyBrowseTreeHTML@ which surfaces to
116+ -- the user as "Could not retrieve output from sbt dependencyBrowseTreeHTML".
117+ -- That regression (CLI 3.8.30) is tracked under TKT-15490 / ANE-2718.
118+ mkDependencyBrowseTreeCmd :: DependencyTreePluginKind -> Command
119+ mkDependencyBrowseTreeCmd ModernDependencyTreePlugin = mkSbtCommand " dependencyBrowseTreeHTML"
120+ mkDependencyBrowseTreeCmd LegacyDependencyGraphPlugin = mkSbtCommand " dependencyBrowseTreeHtml"
60121
61- genTreeJson :: (Has Exec sig m , Has Diagnostics sig m ) => Path Abs Dir -> m [Path Abs File ]
62- genTreeJson projectDir = do
63- stdoutBL <- context " Generating dependency tree html" $ execThrow projectDir mkDependencyBrowseTreeHTMLCmd
122+ -- | Generates dependency trees by invoking the appropriate
123+ -- @dependencyBrowseTree*@ task. Unlike @dependencyBrowseTree@, this does not
124+ -- open a browser when executed.
125+ --
126+ -- It writes the following files in the target directory:
127+ --
128+ -- * @./tree.json@
129+ -- * @./tree.html@
130+ -- * @./tree.data.js@
131+ genTreeJson :: (Has Exec sig m , Has Diagnostics sig m ) => DependencyTreePluginKind -> Path Abs Dir -> m [Path Abs File ]
132+ genTreeJson pluginKind projectDir = do
133+ stdoutBL <- context " Generating dependency tree html" $ execThrow projectDir (mkDependencyBrowseTreeCmd pluginKind)
64134
65135 -- stdout for "sbt dependencyBrowseTreeHTML" looks like:
66136 -- -
0 commit comments