From 0811568fa74120926a40e5f5a3ac1c76328ab3f1 Mon Sep 17 00:00:00 2001 From: himynameisdave Date: Wed, 2 Sep 2026 15:44:28 -0700 Subject: [PATCH] Maven: promote declared deps to direct in static analysis The static (pomxml) strategy builds a graph rooted at the project's own coordinate, with submodules as its children. Unlike the dynamic strategies, it never removed those first-party nodes, so the project artifact was reported as the only direct dependency and everything declared in pom.xml showed up as transitive. Shrink the project and submodule nodes out of the static graph after filtering, promoting their children to direct, matching what shrinkRoots does on the dynamic path. --- Changelog.md | 1 + src/Strategy/Maven.hs | 12 ++++++++--- test/Maven/PomClosureSpec.hs | 39 +++++++++++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/Changelog.md b/Changelog.md index 08a06be75..5b53f3f6d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ ## Unreleased +- Maven: Static analysis (`pomxml`, used when `mvn` is unavailable or with `--static-only-analysis`) no longer reports the project itself as the only direct dependency. Dependencies declared in `pom.xml` are now reported as direct and the project's own modules are removed from the graph, matching dynamic analysis. - Diagnostics: When an error or warning group contains multiple errors, each error's `Traceback:` header is now printed on its own line instead of being glued onto the last line of the preceding error message (e.g. `...none passed validationTraceback:`). ([#1758](https://github.com/fossas/fossa-cli/pull/1758)) ## 3.18.2 diff --git a/src/Strategy/Maven.hs b/src/Strategy/Maven.hs index 8f5bef247..4e9dbedb4 100644 --- a/src/Strategy/Maven.hs +++ b/src/Strategy/Maven.hs @@ -3,6 +3,7 @@ module Strategy.Maven ( mkProject, MavenProject (..), getDeps, + getStaticAnalysis, ) where import App.Fossa.Analyze.LicenseAnalyze (LicenseAnalyzeProject, licenseAnalyzeProject) @@ -18,14 +19,14 @@ import Data.Set (Set) import Data.Set qualified as Set import Data.Set.NonEmpty (nonEmpty, toSet) import Data.Text hiding (group, map) -import DepTypes (Dependency) +import DepTypes (Dependency (..)) import Diag.Common (MissingDeepDeps (MissingDeepDeps), MissingEdges (MissingEdges)) import Discovery.Filters (AllFilters, MavenScopeFilters, mavenScopeFilterSet) import Discovery.Simple (simpleDiscover) import Effect.Exec (CandidateCommandEffs, GetDepsEffs) import Effect.ReadFS (ReadFS) import GHC.Generics (Generic) -import Graphing (Graphing, gmap, shrinkRoots) +import Graphing (Graphing, gmap, shrink, shrinkRoots) import Path (Abs, Dir, Path, parent) import Strategy.Maven.Common (MavenDependency (..), filterMavenDependencyByScope, filterMavenSubmodules, mavenDependencyToDependency) import Strategy.Maven.DepTree qualified as DepTreeCmd @@ -188,7 +189,12 @@ getStaticAnalysis submoduleTargets closure = do let allSubmodules = PomClosure.closureSubmodules closure (graph, graphBreadth) <- context "Static analysis" $ pure (Pom.analyze' closure, Partial) filteredGraph <- applyMavenFilters submoduleTargets allSubmodules graph - pure (filteredGraph, graphBreadth) + pure (withoutProjectAsDep filteredGraph, graphBreadth) + where + -- The static graph is rooted at the project itself, with submodules as its children. + -- Those are the users' packages, not dependencies, so remove them and promote their + -- children to direct, like the dynamic strategies do with `shrinkRoots`. + withoutProjectAsDep = shrink (\dep -> dependencyName dep `Set.notMember` PomClosure.closureSubmodules closure) applyMavenFilters :: (Has Diagnostics sig m, Has (Reader MavenScopeFilters) sig m) => Set Text -> Set Text -> Graphing MavenDependency -> m (Graphing Dependency) applyMavenFilters targetSet submoduleSet graph = do diff --git a/test/Maven/PomClosureSpec.hs b/test/Maven/PomClosureSpec.hs index 73fa3fcda..a9d2baeb3 100644 --- a/test/Maven/PomClosureSpec.hs +++ b/test/Maven/PomClosureSpec.hs @@ -2,16 +2,19 @@ module Maven.PomClosureSpec (spec) where +import Control.Carrier.Reader (runReader) import Control.Effect.Lift (sendIO) import Data.ByteString.Char8 qualified as BS import Data.List (find, sort) import Data.Map.Strict qualified as Map import Data.Set qualified as Set import Data.Text (Text) -import GraphUtil (expectDeps') +import DepTypes (DepType (MavenType), Dependency (..), VerConstraint (CEq)) +import GraphUtil (expectDeps', expectDirect') import Graphing (shrinkRoots) import Path (Abs, Dir, Path, reldir, relfile, toFilePath, ()) import Path.IO qualified as PIO +import Strategy.Maven (getStaticAnalysis) import Strategy.Maven.Plugin (Artifact (..), Edge (Edge), PluginOutput (..)) import Strategy.Maven.PluginStrategy (buildGraph) import Strategy.Maven.Pom.Closure ( @@ -22,6 +25,7 @@ import Strategy.Maven.Pom.Closure ( ) import Strategy.Maven.Pom.PomFile (MavenCoordinate (..)) import Test.Effect (EffectStack, itWithTempDir', shouldBe') +import Test.Fixtures (mavenScopeFilterSet) import Test.Hspec spec :: Spec @@ -106,6 +110,32 @@ spec = do graph = shrinkRoots $ buildGraph submodules output expectDeps' [] graph + describe "getStaticAnalysis" $ do + -- The static (pomxml) graph is rooted at the project itself with its + -- modules as children. Those are first-party and must not be reported; + -- their declared dependencies must be promoted to direct, as the dynamic + -- strategies already do via shrinkRoots. + itWithTempDir' "reports declared dependencies as direct and drops the project and its modules" $ \dir -> do + createParentlessFixture dir + closures <- findProjects dir + case find ((== rootCoord) . closureRootCoord) closures of + Nothing -> sendIO $ expectationFailure "expected a closure rooted at com.example:root-a" + Just closure -> do + (graph, _) <- runReader mavenScopeFilterSet $ getStaticAnalysis (closureSubmodules closure) closure + expectDirect' [libDep] graph + expectDeps' [libDep] graph + +libDep :: Dependency +libDep = + Dependency + { dependencyType = MavenType + , dependencyName = "org.example:lib" + , dependencyVersion = Just (CEq "1.0") + , dependencyLocations = [] + , dependencyEnvironments = mempty + , dependencyTags = Map.empty + } + rootCoord :: MavenCoordinate rootCoord = MavenCoordinate "com.example" "root-a" "1.0" @@ -192,6 +222,13 @@ parentedChildPom = , " ../pom.xml\n" , " \n" , " child-parented\n" + , " \n" + , " \n" + , " org.example\n" + , " lib\n" + , " 1.0\n" + , " \n" + , " \n" , "\n" ]