{- | Module : HCovGuard.Coverage.MixDiscovery Description : Auto-discovery of HPC .mix file directories Copyright : (c) Trevis Elser, 2026 License : MIT Maintainer : oss@treviselser.com -} module HCovGuard.Coverage.MixDiscovery ( discoverMixDirs ) where import qualified Control.Monad as Monad import qualified Data.DList as DList import qualified System.Directory.OsPath as Directory import qualified System.OsPath as OsPath {- | Discover directories containing .mix files. Searches for: - .hpc directories in the current directory and one level down (for standalone GHC) - hpc subdirectories within .stack-work and dist-newstyle (for Stack\/Cabal builds) @since 0.1.0.0 -} discoverMixDirs :: OsPath.OsPath -> IO [OsPath.OsPath] discoverMixDirs root = do exists <- Directory.doesDirectoryExist root if not exists then pure mempty else do subDirs <- listSubdirectories root Monad.foldM collectMixDirs mempty (root : subDirs) collectMixDirs :: [OsPath.OsPath] -> OsPath.OsPath -> IO [OsPath.OsPath] collectMixDirs acc dir = do dotHpcName <- OsPath.encodeFS ".hpc" stackWorkName <- OsPath.encodeFS ".stack-work" distNewstyleName <- OsPath.encodeFS "dist-newstyle" let dotHpc = OsPath.combine dir dotHpcName stackWork = OsPath.combine dir stackWorkName distNewstyle = OsPath.combine dir distNewstyleName dotHpcExists <- Directory.doesDirectoryExist dotHpc stackWorkExists <- Directory.doesDirectoryExist stackWork distNewstyleExists <- Directory.doesDirectoryExist distNewstyle -- .hpc directories contain mix files directly let withDotHpc = if dotHpcExists then dotHpc : acc else acc -- Search for 'hpc' subdirectories within build directories hpcFromStack <- if stackWorkExists then findHpcDirs stackWork else pure mempty hpcFromDist <- if distNewstyleExists then findHpcDirs distNewstyle else pure mempty pure (hpcFromStack <> hpcFromDist <> withDotHpc) listSubdirectories :: OsPath.OsPath -> IO [OsPath.OsPath] listSubdirectories dir = do contents <- Directory.listDirectory dir Monad.foldM ( \acc name -> let fullPath = OsPath.combine dir name in fmap (\isDir -> if isDir then fullPath : acc else acc) (Directory.doesDirectoryExist fullPath) ) mempty contents -- | Recursively search for directories named "hpc" within a build directory. findHpcDirs :: OsPath.OsPath -> IO [OsPath.OsPath] findHpcDirs dir = fmap DList.toList (findHpcDirsDList dir) -- | Internal version using DList for O(1) append during recursion. findHpcDirsDList :: OsPath.OsPath -> IO (DList.DList OsPath.OsPath) findHpcDirsDList dir = do hpcName <- OsPath.encodeFS "hpc" contents <- Directory.listDirectory dir Monad.foldM ( \acc name -> do let fullPath = OsPath.combine dir name isDir <- Directory.doesDirectoryExist fullPath if not isDir then pure acc else if name == hpcName then pure (DList.snoc acc fullPath) else fmap (acc <>) (findHpcDirsDList fullPath) ) DList.empty contents