diff --git a/Changelog.md b/Changelog.md index bfa16847a..5491f5544 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ - Config: `paths.only` and `paths.exclude` in `.fossa.yml` now accept glob patterns. ([#1703](https://github.com/fossas/fossa-cli/pull/1703)) - Licensing - Fix two bad GPL matches [No PR] - NuGet: PackageReference discovery now analyzes every `.csproj`/`.xproj`/`.vbproj`/`.dbproj`/`.fsproj` in a directory. Previously only the first match returned by the directory listing was analyzed, so sibling project files were silently dropped. ([#1712](https://github.com/fossas/fossa-cli/pull/1712)) +- Go: `go list`-based analysis now tags test-only dependencies (reached via a main-module package's `TestImports`) with the testing environment. They are filtered out of uploads by default, matching prior behavior, but can now be surfaced with `--include-unused-deps`. Previously test dependencies were dropped from the graph entirely and `--include-unused-deps` had no effect for Go projects. ([#1714](https://github.com/fossas/fossa-cli/pull/1714)) ## 3.17.5 diff --git a/src/Strategy/Go/GoListPackages.hs b/src/Strategy/Go/GoListPackages.hs index 6afafc89d..e5869efda 100644 --- a/src/Strategy/Go/GoListPackages.hs +++ b/src/Strategy/Go/GoListPackages.hs @@ -179,10 +179,21 @@ type GoLabeledGrapher m a = LabeledGrapherC GoPackage DepEnvironment m a -- * Removes path dependencies and their transitive deps that aren't used elsewhere in the graph. -- The go tools give us this data but we haven't decided yet how to present it. -- * Replaces modules according to the module's 'replacement' field. --- * Skips over test dependencies and their children. +-- * Tags test-only dependencies (reached via 'TestImports' of a main module +-- package) with 'EnvTesting' so they can be filtered out at upload time by +-- 'shouldPublishDep'. Production dependencies are always tagged with +-- 'EnvProduction'; a module reached through both paths gets both labels. buildGraph :: (Has Diagnostics sig m) => Path Abs Dir -> [GoPackage] -> m (Graphing.Graphing Dependency, GraphBreadth) buildGraph goModDir rawPackages = do - g <- runLabeledGrapher . traverse_ (makeGraph EnvProduction) =<< getMainPackages + mains <- getMainPackages + -- Production traversal must run first so that any module reachable from both + -- production and test code keeps its 'EnvProduction' label and a fully + -- traversed production subtree. The subsequent test traversal short-circuits + -- when it hits an already-graphed vertex, but still appends 'EnvTesting' at + -- the vertex it re-encounters. + g <- runLabeledGrapher $ do + traverse_ (makeGraph EnvProduction) mains + traverse_ traverseTestDeps mains fmap ((,Complete)) . uncurry pkgGraphToDepGraph $ g where (mainPackages, stdLibImportPaths, pkgsNoStdLibImports) = foldl' go ([], HashSet.empty, HashMap.empty) rawPackages @@ -232,6 +243,15 @@ buildGraph goModDir rawPackages = do maybeEdge :: (Has Diagnostics sig m, Has (Grapher GoPackage) sig m) => GoPackage -> Maybe GoPackage -> m () maybeEdge d = maybe (pure ()) (edge d) + -- For each TestImport of a main-module package, graph the imported package + -- (and its transitive 'packageDeps') tagged as 'EnvTesting', wiring it as + -- an edge from the main-module package. We do not recurse into the + -- 'testDeps' of non-main packages: when compiling tests of the main + -- module, Go does not pull in the test imports of dependencies. + traverseTestDeps :: (Has Diagnostics sig m) => GoPackage -> GoLabeledGrapher m () + traverseTestDeps pkg@GoPackage{testDeps} = + traverse_ (lookupPackage >=> makeGraph EnvTesting >=> maybeEdge pkg) testDeps + lookupPackage :: Has Diagnostics sig m => ImportPath -> m GoPackage lookupPackage impPath = Diagnostics.fromMaybe (MissingModuleErr getSourceLocation impPath) $ HashMap.lookup impPath pkgsNoStdLibImports diff --git a/test/Go/GoListPackagesSpec.hs b/test/Go/GoListPackagesSpec.hs index b4c0168ca..7395ef190 100644 --- a/test/Go/GoListPackagesSpec.hs +++ b/test/Go/GoListPackagesSpec.hs @@ -8,7 +8,7 @@ import Control.Carrier.Stack (runStack) import Data.Map.Strict qualified as Map import Data.Set qualified as Set import DepTypes ( - DepEnvironment (EnvProduction), + DepEnvironment (EnvProduction, EnvTesting), DepType (GoType, UnresolvedPathType), Dependency (..), VerConstraint (CEq), @@ -294,11 +294,26 @@ pathDepPkgModule = , dependencyTags = Map.empty } +-- 'transitiveTestMod' is reachable only via the 'testDeps' of another test +-- dep, which we intentionally do not traverse, so it should not appear in +-- 'expectedGraph'. +testMod :: Dependency +testMod = + Dependency + { dependencyType = GoType + , dependencyName = "testMod" + , dependencyVersion = Just $ CEq "1.0.0" + , dependencyLocations = [] + , dependencyEnvironments = Set.singleton EnvTesting + , dependencyTags = Map.empty + } + expectedGraph :: Graphing.Graphing Dependency expectedGraph = Graphing.direct replacedModule <> Graphing.direct moduleA <> Graphing.direct pathModule + <> Graphing.direct testMod <> Graphing.edge replacedModule moduleA <> Graphing.edge pathModule pathDepPkgModule