Skip to content

Commit fdf709a

Browse files
ryanlinkclaude
andcommitted
[ANE-1322] Apply path filters at discovery stage for maven
Previously, Maven POM discovery would traverse and load all POMs during closure graph building, then apply path filters only during analysis. This caused issues when excluded POMs were still considered during discovery, leading to failures even when those paths were filtered out. This change applies path filters during the POM resolution phase by: - Adding AllFilters context to buildGlobalClosure and recursiveLoadPom - Implementing tryRecurse function that checks pathAllowed before recursively loading POMs - Ensuring excluded directories (like .m2) are not traversed during discovery Fixes issue where users couldn't work around discovery failures by excluding problematic paths with discovery filters. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent db46e95 commit fdf709a

3 files changed

Lines changed: 120 additions & 8 deletions

File tree

src/Strategy/Maven/Pom/Closure.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import Discovery.Filters (AllFilters)
3434
findProjects :: (Has ReadFS sig m, Has Diagnostics sig m, Has (Reader AllFilters) sig m) => Path Abs Dir -> m [MavenProjectClosure]
3535
findProjects basedir = do
3636
pomFiles <- context "Finding pom files" $ findPomFiles basedir
37-
globalClosure <- context "Building global closure" $ buildGlobalClosure pomFiles
37+
globalClosure <- context "Building global closure" $ buildGlobalClosure basedir pomFiles
3838
context "Building project closures" $ pure (buildProjectClosures basedir globalClosure)
3939

4040
findPomFiles :: (Has ReadFS sig m, Has Diagnostics sig m, Has (Reader AllFilters) sig m) => Path Abs Dir -> m [Path Abs File]

src/Strategy/Maven/Pom/Resolver.hs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ import Control.Effect.Diagnostics (
2020
recover,
2121
(<||>),
2222
)
23-
import Control.Monad (unless)
23+
import Control.Effect.Reader (Reader, ask)
24+
import Control.Monad (unless, when)
2425
import Data.Foldable (traverse_)
2526
import Data.Map.Strict (Map)
2627
import Data.Map.Strict qualified as Map
2728
import Data.Maybe (fromMaybe)
2829
import Data.Text (Text)
30+
import Discovery.Filters (AllFilters, pathAllowed)
2931
import Effect.ReadFS (
3032
ReadFS,
3133
ReadFSErr (FileReadError),
@@ -35,6 +37,7 @@ import Effect.ReadFS (
3537
resolveFile,
3638
)
3739
import Path (Abs, Dir, File, Path, mkRelFile, parent, (</>))
40+
import Path.IO qualified as PIO
3841
import Strategy.Maven.Pom.PomFile (
3942
MavenCoordinate,
4043
Pom (pomCoord, pomParentCoord),
@@ -49,9 +52,16 @@ data GlobalClosure = GlobalClosure
4952
}
5053
deriving (Eq, Ord, Show)
5154

52-
buildGlobalClosure :: (Has ReadFS sig m, Has Diagnostics sig m) => [Path Abs File] -> m GlobalClosure
53-
buildGlobalClosure files = do
54-
(loadResults, ()) <- runState @LoadResults Map.empty $ traverse_ recursiveLoadPom files
55+
buildGlobalClosure ::
56+
( Has ReadFS sig m
57+
, Has Diagnostics sig m
58+
, Has (Reader AllFilters) sig m
59+
) =>
60+
Path Abs Dir ->
61+
[Path Abs File] ->
62+
m GlobalClosure
63+
buildGlobalClosure rootDir files = do
64+
(loadResults, ()) <- runState @LoadResults Map.empty $ traverse_ (recursiveLoadPom rootDir) files
5565

5666
-- TODO: diagnostics/warnings?
5767
let validated :: Map (Path Abs File) Pom
@@ -85,8 +95,19 @@ indexBy f = Map.fromList . map (\v -> (f v, v))
8595
type LoadResults = Map (Path Abs File) (Maybe RawPom)
8696

8797
-- Recursively load a pom and its adjacent poms (parent, submodules)
88-
recursiveLoadPom :: forall sig m. (Has ReadFS sig m, Has (State LoadResults) sig m, Has Diagnostics sig m) => Path Abs File -> m ()
89-
recursiveLoadPom path = do
98+
recursiveLoadPom ::
99+
forall sig m.
100+
( Has ReadFS sig m
101+
, Has (State LoadResults) sig m
102+
, Has Diagnostics sig m
103+
, Has (Reader AllFilters) sig m
104+
) =>
105+
-- | analysis root
106+
Path Abs Dir ->
107+
-- | pom to load
108+
Path Abs File ->
109+
m ()
110+
recursiveLoadPom rootDir path = do
90111
results <- get @LoadResults
91112

92113
case Map.lookup path results of
@@ -113,7 +134,16 @@ recursiveLoadPom path = do
113134
recurseRelative :: Text {- relative filepath -} -> m ()
114135
recurseRelative rel = do
115136
resolvedPath :: Maybe (Path Abs File) <- recover $ resolvePomPath (parent path) rel
116-
traverse_ recursiveLoadPom resolvedPath
137+
traverse_ tryRecurse resolvedPath
138+
139+
-- Decide whether a resolved pom should be loaded based on path filters.
140+
tryRecurse :: Path Abs File -> m ()
141+
tryRecurse p = do
142+
filters <- ask @AllFilters
143+
let dirP = parent p
144+
case PIO.makeRelative rootDir dirP of
145+
Nothing -> pure () -- outside of analysis root, ignore
146+
Just relDir -> when (pathAllowed filters relDir) $ recursiveLoadPom rootDir p
117147

118148
-- resolve a Filepath (in Text) that may either point to a directory or an exact
119149
-- pom file. when it's a directory, we default to pointing at the "pom.xml" in

test/Maven/PathFilterSpec.hs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
{-# LANGUAGE TemplateHaskell #-}
2+
3+
module Maven.PathFilterSpec (
4+
spec,
5+
) where
6+
7+
import Control.Carrier.Reader (local)
8+
import Control.Effect.Lift (sendIO)
9+
import Data.Map.Strict qualified as Map
10+
import Data.Text qualified as T
11+
import Data.Text.IO qualified as TIO
12+
import Discovery.Filters (AllFilters (..), comboExclude)
13+
import Path (Abs, Dir, File, Path, mkRelDir, mkRelFile, (</>))
14+
import qualified Path
15+
import Path.IO (createDirIfMissing)
16+
import Strategy.Maven.Pom.Resolver (GlobalClosure (..), buildGlobalClosure)
17+
import Test.Effect (itWithTempDir', shouldBe', EffectStack)
18+
import Test.Hspec (Spec, describe)
19+
20+
spec :: Spec
21+
spec =
22+
describe "Maven path filtering" $ do
23+
itWithTempDir' "does not traverse POMs inside excluded directories" $ \rootDir ->
24+
testNoTraversal rootDir
25+
26+
-- | Build a minimal on-disk Maven project with a parent POM located in an
27+
-- excluded '.m2' directory. The test asserts that the parent POM is not
28+
-- loaded when discovery filters exclude that directory.
29+
30+
testNoTraversal :: Path Abs Dir -> EffectStack ()
31+
testNoTraversal rootDir = do
32+
-- Paths -------------------------------------------------------------
33+
let rootPomPath :: Path Abs File
34+
rootPomPath = rootDir </> $(mkRelFile "pom.xml")
35+
36+
parentDir :: Path Abs Dir
37+
parentDir =
38+
rootDir </> $(mkRelDir ".m2/repository/test/parent")
39+
40+
parentPomPath :: Path Abs File
41+
parentPomPath = parentDir </> $(mkRelFile "pom.xml")
42+
43+
-- File contents -----------------------------------------------------
44+
let rootPom :: T.Text
45+
rootPom =
46+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" <>
47+
"<project xmlns=\"http://maven.apache.org/POM/4.0.0\">\n" <>
48+
" <modelVersion>4.0.0</modelVersion>\n" <>
49+
" <parent>\n" <>
50+
" <groupId>test</groupId>\n" <>
51+
" <artifactId>parent</artifactId>\n" <>
52+
" <version>1.0.0</version>\n" <>
53+
" <relativePath>.m2/repository/test/parent/pom.xml</relativePath>\n" <>
54+
" </parent>\n" <>
55+
" <groupId>test</groupId>\n" <>
56+
" <artifactId>child</artifactId>\n" <>
57+
" <version>1.0.0</version>\n" <>
58+
"</project>\n"
59+
60+
parentPom :: T.Text
61+
parentPom =
62+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" <>
63+
"<project xmlns=\"http://maven.apache.org/POM/4.0.0\">\n" <>
64+
" <modelVersion>4.0.0</modelVersion>\n" <>
65+
" <groupId>test</groupId>\n" <>
66+
" <artifactId>parent</artifactId>\n" <>
67+
" <version>1.0.0</version>\n" <>
68+
"</project>\n"
69+
70+
-- Create directories + write files ---------------------------------
71+
sendIO $ createDirIfMissing True (parentDir)
72+
sendIO $ TIO.writeFile (Path.toFilePath rootPomPath) rootPom
73+
sendIO $ TIO.writeFile (Path.toFilePath parentPomPath) parentPom
74+
75+
-- Exclude the .m2 directory via filters -----------------------------
76+
let filters = AllFilters mempty (comboExclude [] [$(mkRelDir ".m2")])
77+
78+
-- Build global closure with filters --------------------------------
79+
GlobalClosure{globalPoms} <- local (const filters) $ buildGlobalClosure rootDir [rootPomPath]
80+
81+
-- We expect only the root POM to be present -------------------------
82+
Map.size globalPoms `shouldBe'` 1

0 commit comments

Comments
 (0)