diff --git a/Cabal-tests/tests/UnitTests/Distribution/Simple/Glob.hs b/Cabal-tests/tests/UnitTests/Distribution/Simple/Glob.hs index 22e3af46843..4efe4b79437 100644 --- a/Cabal-tests/tests/UnitTests/Distribution/Simple/Glob.hs +++ b/Cabal-tests/tests/UnitTests/Distribution/Simple/Glob.hs @@ -101,13 +101,13 @@ testMatchesVersion version pat expected = do where isEqual = (==) `on` (sort . fmap (fmap normalise)) checkPure globPat = do - let actual = mapMaybe (fileGlobMatches globPat) sampleFileNames + let actual = mapMaybe (\p -> (p <$) <$> fileGlobMatches version globPat p) sampleFileNames unless (sort expected == sort actual) $ assertFailure $ "Unexpected result (pure matcher): " ++ show actual checkIO globPat = withSystemTempDirectory "globstar-sample" $ \tmpdir -> do makeSampleFiles tmpdir - actual <- runDirFileGlob Verbosity.normal tmpdir globPat + actual <- runDirFileGlob Verbosity.normal (Just version) tmpdir globPat unless (isEqual actual expected) $ assertFailure $ "Unexpected result (impure matcher): " ++ show actual diff --git a/Cabal/src/Distribution/PackageDescription/Check.hs b/Cabal/src/Distribution/PackageDescription/Check.hs index 1893d05014c..fc7a25895e7 100644 --- a/Cabal/src/Distribution/PackageDescription/Check.hs +++ b/Cabal/src/Distribution/PackageDescription/Check.hs @@ -170,7 +170,7 @@ checkPackageFilesGPD verbosity gpd root = checkPreIO = CheckPreDistributionOps - { runDirFileGlobM = \fp g -> runDirFileGlob verbosity (root fp) g + { runDirFileGlobM = \fp g -> runDirFileGlob verbosity (Just $ specVersion $ packageDescription gpd) (root fp) g , getDirectoryContentsM = System.Directory.getDirectoryContents . relative } @@ -854,7 +854,8 @@ checkGlobResult title fp rs = dirCheck ++ catMaybes (map getWarning rs) | otherwise = [] -- If there's a missing directory in play, since our globs don't - -- (currently) support disjunction, that will always mean there are + -- (currently) support disjunction, (ROMES:TODO: We do now...) + -- that will always mean there are -- no matches. The no matches error in this case is strictly less -- informative than the missing directory error. withoutNoMatchesWarning (GlobMatch _) = True @@ -969,9 +970,9 @@ pd2gpd pd = gpd -- present in our .cabal file. checkMissingDocs :: Monad m - => [Glob] -- data-files globs. - -> [Glob] -- extra-source-files globs. - -> [Glob] -- extra-doc-files globs. + => [FilePathGlobRel] -- data-files globs. + -> [FilePathGlobRel] -- extra-source-files globs. + -> [FilePathGlobRel] -- extra-doc-files globs. -> CheckM m () checkMissingDocs dgs esgs edgs = do extraDocSupport <- (>= CabalSpecV1_18) <$> asksCM ccSpecVersion @@ -1012,10 +1013,6 @@ checkMissingDocs dgs esgs edgs = do return (mcs ++ pcs) ) where - -- From Distribution.Simple.Glob. - globMatches :: [GlobResult a] -> [a] - globMatches input = [a | GlobMatch a <- input] - checkDoc :: Bool -- Cabal spec ≥ 1.18? -> [FilePath] -- Desirables. diff --git a/Cabal/src/Distribution/PackageDescription/Check/Monad.hs b/Cabal/src/Distribution/PackageDescription/Check/Monad.hs index 23d37570800..f1775506941 100644 --- a/Cabal/src/Distribution/PackageDescription/Check/Monad.hs +++ b/Cabal/src/Distribution/PackageDescription/Check/Monad.hs @@ -48,7 +48,7 @@ import Distribution.CabalSpecVersion (CabalSpecVersion) import Distribution.Package (packageName) import Distribution.PackageDescription.Check.Warning import Distribution.Simple.BuildToolDepends (desugarBuildToolSimple) -import Distribution.Simple.Glob (Glob, GlobResult) +import Distribution.Simple.Glob import Distribution.Types.ExeDependency (ExeDependency) import Distribution.Types.GenericPackageDescription import Distribution.Types.LegacyExeDependency (LegacyExeDependency) @@ -101,7 +101,7 @@ data CheckPackageContentOps m = CheckPackageContentOps -- in case in the future we can obtain the same infos other than from IO -- (e.g. a VCS work tree). data CheckPreDistributionOps m = CheckPreDistributionOps - { runDirFileGlobM :: FilePath -> Glob -> m [GlobResult FilePath] + { runDirFileGlobM :: FilePath -> FilePathGlobRel -> m [GlobResult FilePath] , getDirectoryContentsM :: FilePath -> m [FilePath] } diff --git a/Cabal/src/Distribution/PackageDescription/Check/Paths.hs b/Cabal/src/Distribution/PackageDescription/Check/Paths.hs index f389c6797be..5bef3d04b7e 100644 --- a/Cabal/src/Distribution/PackageDescription/Check/Paths.hs +++ b/Cabal/src/Distribution/PackageDescription/Check/Paths.hs @@ -164,7 +164,7 @@ checkGlob :: Monad m => CabalField -- .cabal field we are checking. -> FilePath -- glob filepath pattern - -> CheckM m (Maybe Glob) + -> CheckM m (Maybe FilePathGlobRel) checkGlob title pat = do ver <- asksCM ccSpecVersion diff --git a/Cabal/src/Distribution/Simple/Glob.hs b/Cabal/src/Distribution/Simple/Glob.hs index 67abe7e2da4..1a6c0594575 100644 --- a/Cabal/src/Distribution/Simple/Glob.hs +++ b/Cabal/src/Distribution/Simple/Glob.hs @@ -1,5 +1,8 @@ {-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE RankNTypes #-} ----------------------------------------------------------------------------- @@ -14,59 +17,330 @@ -- Portability : portable -- -- Simple file globbing. -module Distribution.Simple.Glob - ( GlobSyntaxError (..) - , GlobResult (..) - , matchDirFileGlob - , matchDirFileGlobWithDie - , runDirFileGlob - , fileGlobMatches - , parseFileGlob - , explainGlobSyntaxError - , isRecursiveInRoot - , Glob - ) where +module Distribution.Simple.Glob where import Distribution.Compat.Prelude import Prelude () +import Control.Monad (mapM) + +import Distribution.Parsec +import Distribution.Pretty + import Distribution.CabalSpecVersion import Distribution.Simple.Utils -import Distribution.Verbosity +import Distribution.Verbosity hiding (normal) -import System.Directory (doesDirectoryExist, doesFileExist, getDirectoryContents) -import System.FilePath (joinPath, splitDirectories, splitExtensions, takeFileName, (<.>), ()) +import Data.List (stripPrefix) +import System.Directory +import System.FilePath -import qualified Data.List.NonEmpty as NE import Distribution.Simple.Errors --- Note throughout that we use splitDirectories, not splitPath. On --- Posix, this makes no difference, but, because Windows accepts both --- slash and backslash as its path separators, if we left in the --- separators from the glob we might not end up properly normalised. +import qualified Distribution.Compat.CharParsing as P +import qualified Text.PrettyPrint as Disp -data GlobResult a - = -- | The glob matched the value supplied. - GlobMatch a - | -- | The glob did not match the value supplied because the - -- cabal-version is too low and the extensions on the file did - -- not precisely match the glob's extensions, but rather the - -- glob was a proper suffix of the file's extensions; i.e., if - -- not for the low cabal-version, it would have matched. - GlobWarnMultiDot a - | -- | The glob couldn't match because the directory named doesn't - -- exist. The directory will be as it appears in the glob (i.e., - -- relative to the directory passed to 'matchDirFileGlob', and, - -- for 'data-files', relative to 'data-dir'). - GlobMissingDirectory FilePath - deriving (Show, Eq, Ord, Functor) +-------------------------------------------------------------------------------- --- | Extract the matches from a list of 'GlobResult's. +-- | A file path specified by globbing +data FilePathGlob = FilePathGlob FilePathRoot FilePathGlobRel + deriving (Eq, Show, Generic) + +data FilePathGlobRel + = GlobDir !Glob !FilePathGlobRel + | -- | A glob ending in @**/@, where @**@ denotes recursively traversing + -- all directories and matching filenames on . + GlobDirRecursive !Glob + | GlobFile !Glob + | -- | trailing dir, a glob ending in @/@ + GlobDirTrailing + deriving (Eq, Show, Generic) + +-- | A single directory or file component of a globbed path +type Glob = [GlobPiece] + +-- | A piece of a globbing pattern +data GlobPiece + = -- | A wildcard @*@ + WildCard + | -- | A literal string @dirABC@ + Literal String + | -- | A union of patterns, e.g. @dir/{a,*.txt,c}/...@ + Union [Glob] + deriving (Eq, Show, Generic) + +data FilePathRoot + = FilePathRelative + | -- | e.g. @"/"@, @"c:\"@ or result of 'takeDrive' + FilePathRoot FilePath + | FilePathHomeDir + deriving (Eq, Show, Generic) + +instance Binary FilePathGlob +instance Binary FilePathRoot +instance Binary FilePathGlobRel +instance Binary GlobPiece + +instance Structured FilePathGlob +instance Structured FilePathRoot +instance Structured FilePathGlobRel +instance Structured GlobPiece + +-- | Check if a 'FilePathGlob' doesn't actually make use of any globbing and +-- is in fact equivalent to a non-glob 'FilePath'. -- --- Note: throws away the 'GlobMissingDirectory' results; chances are --- that you want to check for these and error out if any are present. -globMatches :: [GlobResult a] -> [a] -globMatches input = [a | GlobMatch a <- input] +-- If it is trivial in this sense then the result is the equivalent constant +-- 'FilePath'. On the other hand if it is not trivial (so could in principle +-- match more than one file) then the result is @Nothing@. +isTrivialFilePathGlob :: FilePathGlob -> Maybe FilePath +isTrivialFilePathGlob (FilePathGlob root pathglob) = + case root of + FilePathRelative -> go [] pathglob + FilePathRoot root' -> go [root'] pathglob + FilePathHomeDir -> Nothing + where + go paths (GlobDir [Literal path] globs) = go (path : paths) globs + go paths (GlobFile [Literal path]) = Just (joinPath (reverse (path : paths))) + go paths GlobDirTrailing = + Just + ( addTrailingPathSeparator + (joinPath (reverse paths)) + ) + go _ _ = Nothing + +-- | Get the 'FilePath' corresponding to a 'FilePathRoot'. +-- +-- The 'FilePath' argument is required to supply the path for the +-- 'FilePathRelative' case. +getFilePathRootDirectory + :: FilePathRoot + -> FilePath + -- ^ root for relative paths + -> IO FilePath +getFilePathRootDirectory FilePathRelative root = return root +getFilePathRootDirectory (FilePathRoot root) _ = return root +getFilePathRootDirectory FilePathHomeDir _ = getHomeDirectory + +------------------------------------------------------------------------------- + +-- * Matching + +-------------------------------------------------------------------------------- + +-- | Match a 'FilePathGlob' against the file system, starting from a given +-- root directory for relative paths. The results of relative globs are +-- relative to the given root. Matches for absolute globs are absolute. +matchFileGlob :: FilePath -> FilePathGlob -> IO [FilePath] +matchFileGlob relroot (FilePathGlob globroot glob) = do + root <- getFilePathRootDirectory globroot relroot + matches <- matchFileGlobRel root glob + case globroot of + FilePathRelative -> return matches + _ -> return (map (root ) matches) + +-- | Match a 'FilePathGlobRel' against the file system, starting from a +-- given root directory. The results are all relative to the given root. +matchFileGlobRel :: FilePath -> FilePathGlobRel -> IO [FilePath] +matchFileGlobRel root glob = + -- For this function, which is the general globbing one (doesn't care about + -- cabal spec, used e.g. for monitoring), we consider all matches. + mapMaybe + ( \case + GlobMatch a -> Just a + GlobWarnMultiDot a -> Just a + GlobMissingDirectory{} -> Nothing + ) + <$> runDirFileGlob silent Nothing root glob + +-- | Match a globbing pattern against a file path component +matchGlob :: Glob -> String -> Bool +matchGlob = goStart + where + -- From the man page, glob(7): + -- "If a filename starts with a '.', this character must be + -- matched explicitly." + + go, goStart :: [GlobPiece] -> String -> Bool + + goStart (WildCard : _) ('.' : _) = False + goStart (Union globs : rest) cs = + any + (\glob -> goStart (glob ++ rest) cs) + globs + goStart rest cs = go rest cs + + go [] "" = True + go (Literal lit : rest) cs + | Just cs' <- stripPrefix lit cs = + go rest cs' + | otherwise = False + go [WildCard] "" = True + go (WildCard : rest) (c : cs) = go rest (c : cs) || go (WildCard : rest) cs + go (Union globs : rest) cs = any (\glob -> go (glob ++ rest) cs) globs + go [] (_ : _) = False + go (_ : _) "" = False + +------------------------------------------------------------------------------- + +-- * Parsing & printing + +-------------------------------------------------------------------------------- +-- Filepaths with globs may be parsed in the special context is globbing in +-- cabal package fields, such as `data-files`. In that case, we restrict the +-- globbing syntax to that supported by the cabal spec version in use. +-- Otherwise, we parse the globs to the extent of our globbing features +-- (wildcards `*`, unions `{a,b,c}`, and directory-recursive wildcards `**`). + +-- ** Parsing globs in a cabal package + +parseFileGlob :: CabalSpecVersion -> FilePath -> Either GlobSyntaxError FilePathGlobRel +parseFileGlob version filepath = case reverse (splitDirectories filepath) of + [] -> + Left EmptyGlob + (filename : "**" : segments) + | allowGlobStar -> do + finalSegment <- case splitExtensions filename of + ("*", ext) + | '*' `elem` ext -> Left StarInExtension + | null ext -> Left NoExtensionOnStar + | otherwise -> Right (GlobDirRecursive [WildCard, Literal ext]) + _ + | allowLiteralFilenameGlobStar -> + Right (GlobDirRecursive [Literal filename]) + | otherwise -> + Left LiteralFileNameGlobStar + + foldM addStem finalSegment segments + | otherwise -> Left VersionDoesNotSupportGlobStar + (filename : segments) -> do + pat <- case splitExtensions filename of + ("*", ext) + | not allowGlob -> Left VersionDoesNotSupportGlob + | '*' `elem` ext -> Left StarInExtension + | null ext -> Left NoExtensionOnStar + | otherwise -> Right (GlobFile [WildCard, Literal ext]) + (_, ext) + | '*' `elem` ext -> Left StarInExtension + | '*' `elem` filename -> Left StarInFileName + | otherwise -> Right (GlobFile [Literal filename]) + + foldM addStem pat segments + where + addStem pat seg + | '*' `elem` seg = Left StarInDirectory + | otherwise = Right (GlobDir [Literal seg] pat) + allowGlob = version >= CabalSpecV1_6 + allowGlobStar = version >= CabalSpecV2_4 + allowLiteralFilenameGlobStar = version >= CabalSpecV3_8 + +enableMultidot :: CabalSpecVersion -> Bool +enableMultidot version + | version >= CabalSpecV2_4 = True + | otherwise = False + +-- ** Parsing globs otherwise + +instance Pretty FilePathGlob where + pretty (FilePathGlob root pathglob) = pretty root Disp.<> pretty pathglob + +instance Parsec FilePathGlob where + parsec = do + root <- parsec + case root of + FilePathRelative -> FilePathGlob root <$> parsec + _ -> FilePathGlob root <$> parsec <|> pure (FilePathGlob root GlobDirTrailing) + +instance Pretty FilePathRoot where + pretty FilePathRelative = Disp.empty + pretty (FilePathRoot root) = Disp.text root + pretty FilePathHomeDir = Disp.char '~' Disp.<> Disp.char '/' + +instance Parsec FilePathRoot where + parsec = root <|> P.try home <|> P.try drive <|> pure FilePathRelative + where + root = FilePathRoot "/" <$ P.char '/' + home = FilePathHomeDir <$ P.string "~/" + drive = do + dr <- P.satisfy $ \c -> (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') + _ <- P.char ':' + _ <- P.char '/' <|> P.char '\\' + return (FilePathRoot (toUpper dr : ":\\")) + +instance Pretty FilePathGlobRel where + pretty (GlobDir glob pathglob) = + dispGlob glob + Disp.<> Disp.char '/' + Disp.<> pretty pathglob + pretty (GlobDirRecursive glob) = + Disp.text "**/" + Disp.<> dispGlob glob + pretty (GlobFile glob) = dispGlob glob + pretty GlobDirTrailing = Disp.empty + +instance Parsec FilePathGlobRel where + parsec = parsecPath + where + parsecPath :: CabalParsing m => m FilePathGlobRel + parsecPath = do + glob <- parsecGlob + dirSep *> (GlobDir glob <$> parsecPath <|> pure (GlobDir glob GlobDirTrailing)) <|> pure (GlobFile glob) + -- TODO: We could support parsing recursive directory search syntax + -- @**@ here too, rather than just in 'parseFileGlob' + + dirSep :: CabalParsing m => m () + dirSep = + () <$ P.char '/' + <|> P.try + ( do + _ <- P.char '\\' + -- check this isn't an escape code + P.notFollowedBy (P.satisfy isGlobEscapedChar) + ) + + parsecGlob :: CabalParsing m => m Glob + parsecGlob = some parsecPiece + where + parsecPiece = P.choice [literal, wildcard, union] + + wildcard = WildCard <$ P.char '*' + union = Union . toList <$> P.between (P.char '{') (P.char '}') (P.sepByNonEmpty parsecGlob (P.char ',')) + literal = Literal <$> some litchar + + litchar = normal <|> escape + + normal = P.satisfy (\c -> not (isGlobEscapedChar c) && c /= '/' && c /= '\\') + escape = P.try $ P.char '\\' >> P.satisfy isGlobEscapedChar + +-------------------------------------------------------------------------------- +-- Parse and printing utils +-------------------------------------------------------------------------------- + +dispGlob :: Glob -> Disp.Doc +dispGlob = Disp.hcat . map dispPiece + where + dispPiece WildCard = Disp.char '*' + dispPiece (Literal str) = Disp.text (escape str) + dispPiece (Union globs) = + Disp.braces + ( Disp.hcat + ( Disp.punctuate + (Disp.char ',') + (map dispGlob globs) + ) + ) + escape [] = [] + escape (c : cs) + | isGlobEscapedChar c = '\\' : c : escape cs + | otherwise = c : escape cs + +isGlobEscapedChar :: Char -> Bool +isGlobEscapedChar '*' = True +isGlobEscapedChar '{' = True +isGlobEscapedChar '}' = True +isGlobEscapedChar ',' = True +isGlobEscapedChar _ = False + +-- ** Cabal package globbing errors data GlobSyntaxError = StarInDirectory @@ -121,112 +395,33 @@ explainGlobSyntaxError filepath VersionDoesNotSupportGlob = ++ "Alternatively if you require compatibility with earlier Cabal " ++ "versions then list all the files explicitly." -data IsRecursive = Recursive | NonRecursive deriving (Eq) - -data MultiDot = MultiDotDisabled | MultiDotEnabled - -data Glob - = -- | A single subdirectory component + remainder. - GlobStem FilePath Glob - | GlobFinal GlobFinal - -data GlobFinal - = -- | First argument: Is this a @**/*.ext@ pattern? - -- Second argument: should we match against the exact extensions, or accept a suffix? - -- Third argument: the extensions to accept. - FinalMatch IsRecursive MultiDot String - | -- | Literal file name. - FinalLit IsRecursive FilePath - -reconstructGlob :: Glob -> FilePath -reconstructGlob (GlobStem dir glob) = - dir reconstructGlob glob -reconstructGlob (GlobFinal final) = case final of - FinalMatch Recursive _ exts -> "**" "*" <.> exts - FinalMatch NonRecursive _ exts -> "*" <.> exts - FinalLit Recursive path -> "**" path - FinalLit NonRecursive path -> path - --- | Returns 'Nothing' if the glob didn't match at all, or 'Just' the --- result if the glob matched (or would have matched with a higher --- cabal-version). -fileGlobMatches :: Glob -> FilePath -> Maybe (GlobResult FilePath) -fileGlobMatches pat candidate = do - match <- fileGlobMatchesSegments pat (splitDirectories candidate) - return (candidate <$ match) - -fileGlobMatchesSegments :: Glob -> [FilePath] -> Maybe (GlobResult ()) -fileGlobMatchesSegments _ [] = Nothing -fileGlobMatchesSegments pat (seg : segs) = case pat of - GlobStem dir pat' -> do - guard (dir == seg) - fileGlobMatchesSegments pat' segs - GlobFinal final -> case final of - FinalMatch Recursive multidot ext -> do - let (candidateBase, candidateExts) = splitExtensions (NE.last $ seg :| segs) - guard (not (null candidateBase)) - checkExt multidot ext candidateExts - FinalMatch NonRecursive multidot ext -> do - let (candidateBase, candidateExts) = splitExtensions seg - guard (null segs && not (null candidateBase)) - checkExt multidot ext candidateExts - FinalLit isRecursive filename -> do - guard ((isRecursive == Recursive || null segs) && filename == seg) - return (GlobMatch ()) - -checkExt - :: MultiDot - -> String - -- ^ The pattern's extension - -> String - -- ^ The candidate file's extension - -> Maybe (GlobResult ()) -checkExt multidot ext candidate - | ext == candidate = Just (GlobMatch ()) - | ext `isSuffixOf` candidate = case multidot of - MultiDotDisabled -> Just (GlobWarnMultiDot ()) - MultiDotEnabled -> Just (GlobMatch ()) - | otherwise = Nothing - -parseFileGlob :: CabalSpecVersion -> FilePath -> Either GlobSyntaxError Glob -parseFileGlob version filepath = case reverse (splitDirectories filepath) of - [] -> - Left EmptyGlob - (filename : "**" : segments) - | allowGlobStar -> do - finalSegment <- case splitExtensions filename of - ("*", ext) - | '*' `elem` ext -> Left StarInExtension - | null ext -> Left NoExtensionOnStar - | otherwise -> Right (FinalMatch Recursive multidot ext) - _ -> - if allowLiteralFilenameGlobStar - then Right (FinalLit Recursive filename) - else Left LiteralFileNameGlobStar - foldM addStem (GlobFinal finalSegment) segments - | otherwise -> Left VersionDoesNotSupportGlobStar - (filename : segments) -> do - pat <- case splitExtensions filename of - ("*", ext) - | not allowGlob -> Left VersionDoesNotSupportGlob - | '*' `elem` ext -> Left StarInExtension - | null ext -> Left NoExtensionOnStar - | otherwise -> Right (FinalMatch NonRecursive multidot ext) - (_, ext) - | '*' `elem` ext -> Left StarInExtension - | '*' `elem` filename -> Left StarInFileName - | otherwise -> Right (FinalLit NonRecursive filename) - foldM addStem (GlobFinal pat) segments - where - allowGlob = version >= CabalSpecV1_6 - allowGlobStar = version >= CabalSpecV2_4 - addStem pat seg - | '*' `elem` seg = Left StarInDirectory - | otherwise = Right (GlobStem seg pat) - multidot - | version >= CabalSpecV2_4 = MultiDotEnabled - | otherwise = MultiDotDisabled - allowLiteralFilenameGlobStar = version >= CabalSpecV3_8 +-- Note throughout that we use splitDirectories, not splitPath. On +-- Posix, this makes no difference, but, because Windows accepts both +-- slash and backslash as its path separators, if we left in the +-- separators from the glob we might not end up properly normalised. + +data GlobResult a + = -- | The glob matched the value supplied. + GlobMatch a + | -- | The glob did not match the value supplied because the + -- cabal-version is too low and the extensions on the file did + -- not precisely match the glob's extensions, but rather the + -- glob was a proper suffix of the file's extensions; i.e., if + -- not for the low cabal-version, it would have matched. + GlobWarnMultiDot a + | -- | The glob couldn't match because the directory named doesn't + -- exist. The directory will be as it appears in the glob (i.e., + -- relative to the directory passed to 'matchDirFileGlob', and, + -- for 'data-files', relative to 'data-dir'). + GlobMissingDirectory FilePath + deriving (Show, Eq, Ord, Functor) + +-- | Extract the matches from a list of 'GlobResult's. +-- +-- Note: throws away the 'GlobMissingDirectory' results; chances are +-- that you want to check for these and error out if any are present. +globMatches :: [GlobResult a] -> [a] +globMatches input = [a | GlobMatch a <- input] -- | This will 'die'' when the glob matches no files, or if the glob -- refers to a missing directory, or if the glob fails to parse. @@ -251,7 +446,7 @@ matchDirFileGlobWithDie :: Verbosity -> (Verbosity -> CabalException -> IO [File matchDirFileGlobWithDie verbosity rip version dir filepath = case parseFileGlob version filepath of Left err -> rip verbosity $ MatchDirFileGlob (explainGlobSyntaxError filepath err) Right glob -> do - results <- runDirFileGlob verbosity dir glob + results <- runDirFileGlob verbosity (Just version) dir glob let missingDirectories = [missingDir | GlobMissingDirectory missingDir <- results] matches = globMatches results @@ -283,18 +478,25 @@ matchDirFileGlobWithDie verbosity rip version dir filepath = case parseFileGlob -- The 'FilePath' argument is the directory that the glob is relative -- to. It must be a valid directory (and hence it can't be the empty -- string). The returned values will not include this prefix. -runDirFileGlob :: Verbosity -> FilePath -> Glob -> IO [GlobResult FilePath] -runDirFileGlob verbosity rawDir pat = do +runDirFileGlob + :: Verbosity + -> Maybe CabalSpecVersion + -- ^ If the glob we are running should care about the cabal spec, and warnings such as 'GlobWarnMultiDot', then this should be the version. + -- If you want to run a glob but don't care about any of the cabal-spec restrictions on globs, use 'Nothing'! + -> FilePath + -> FilePathGlobRel + -> IO [GlobResult FilePath] +runDirFileGlob verbosity mspec rawRoot pat = do -- The default data-dir is null. Our callers -should- be -- converting that to '.' themselves, but it's a certainty that -- some future call-site will forget and trigger a really -- hard-to-debug failure if we don't check for that here. - when (null rawDir) $ + when (null rawRoot) $ warn verbosity $ "Null dir passed to runDirFileGlob; interpreting it " ++ "as '.'. This is probably an internal error." - let dir = if null rawDir then "." else rawDir - debug verbosity $ "Expanding glob '" ++ reconstructGlob pat ++ "' in directory '" ++ dir ++ "'." + let root = if null rawRoot then "." else rawRoot + debug verbosity $ "Expanding glob '" ++ show (pretty pat) ++ "' in directory '" ++ root ++ "'." -- This function might be called from the project root with dir as -- ".". Walking the tree starting there involves going into .git/ -- and dist-newstyle/, which is a lot of work for no reward, so @@ -302,54 +504,114 @@ runDirFileGlob verbosity rawDir pat = do -- there, and only walk as much as we need to: recursively if **, -- the whole directory if *, and just the specific file if it's a -- literal. - let (prefixSegments, final) = splitConstantPrefix pat - joinedPrefix = joinPath prefixSegments - case final of - FinalMatch recursive multidot exts -> do - let prefix = dir joinedPrefix - directoryExists <- doesDirectoryExist prefix - if directoryExists - then do - candidates <- case recursive of - Recursive -> getDirectoryContentsRecursive prefix - NonRecursive -> filterM (doesFileExist . (prefix )) =<< getDirectoryContents prefix - let checkName candidate = do - let (candidateBase, candidateExts) = splitExtensions $ takeFileName candidate - guard (not (null candidateBase)) - match <- checkExt multidot exts candidateExts - return (joinedPrefix candidate <$ match) - return $ mapMaybe checkName candidates - else return [GlobMissingDirectory joinedPrefix] - FinalLit Recursive fn -> do - let prefix = dir joinedPrefix - directoryExists <- doesDirectoryExist prefix - if directoryExists - then do - candidates <- getDirectoryContentsRecursive prefix - let checkName candidate - | takeFileName candidate == fn = Just $ GlobMatch (joinedPrefix candidate) - | otherwise = Nothing - return $ mapMaybe checkName candidates - else return [GlobMissingDirectory joinedPrefix] - FinalLit NonRecursive fn -> do - exists <- doesFileExist (dir joinedPrefix fn) - return [GlobMatch (joinedPrefix fn) | exists] - -unfoldr' :: (a -> Either r (b, a)) -> a -> ([b], r) -unfoldr' f a = case f a of - Left r -> ([], r) - Right (b, a') -> case unfoldr' f a' of - (bs, r) -> (b : bs, r) - --- | Extract the (possibly null) constant prefix from the pattern. --- This has the property that, if @(pref, final) = splitConstantPrefix pat@, --- then @pat === foldr GlobStem (GlobFinal final) pref@. -splitConstantPrefix :: Glob -> ([FilePath], GlobFinal) -splitConstantPrefix = unfoldr' step + let + (prefixSegments, variablePattern) = splitConstantPrefix pat + joinedPrefix = joinPath prefixSegments + + -- The glob matching function depends on whether we care about the cabal version or not + doesGlobMatch :: Glob -> String -> Maybe (GlobResult ()) + doesGlobMatch glob str = case mspec of + Just spec -> checkNameMatches spec glob str + Nothing -> if matchGlob glob str then Just (GlobMatch ()) else Nothing + + go (GlobFile glob) dir = do + entries <- getDirectoryContents (root dir) + catMaybes + <$> mapM + ( \s -> do + -- When running a glob from a Cabal package description (i.e. + -- when a cabal spec version is passed as an argument), we + -- disallow matching a @GlobFile@ against a directory, preferring + -- @GlobDir dir GlobDirTrailing@ to specify a directory match. + shouldMatch <- maybe (return True) (const $ doesFileExist (root dir s)) mspec + return $ + if shouldMatch + then (dir s <$) <$> doesGlobMatch glob s + else Nothing + ) + entries + go (GlobDirRecursive glob) dir = do + entries <- getDirectoryContentsRecursive (root dir) + return $ + mapMaybe (\s -> (dir s <$) <$> doesGlobMatch glob (takeFileName s)) entries + go (GlobDir glob globPath) dir = do + entries <- getDirectoryContents (root dir) + subdirs <- + filterM + ( \subdir -> + doesDirectoryExist + (root dir subdir) + ) + $ filter (matchGlob glob) entries + concat <$> traverse (\subdir -> go globPath (dir subdir)) subdirs + go GlobDirTrailing dir = return [GlobMatch dir] + + directoryExists <- doesDirectoryExist (root joinedPrefix) + if directoryExists + then go variablePattern joinedPrefix + else return [GlobMissingDirectory joinedPrefix] where - step (GlobStem seg pat) = Right (seg, pat) - step (GlobFinal pat) = Left pat + -- \| Extract the (possibly null) constant prefix from the pattern. + -- This has the property that, if @(pref, final) = splitConstantPrefix pat@, + -- then @pat === foldr GlobDir final pref@. + splitConstantPrefix :: FilePathGlobRel -> ([FilePath], FilePathGlobRel) + splitConstantPrefix = unfoldr' step + where + step (GlobDir [Literal seg] pat') = Right (seg, pat') + step pat' = Left pat' + + unfoldr' :: (a -> Either r (b, a)) -> a -> ([b], r) + unfoldr' f a = case f a of + Left r -> ([], r) + Right (b, a') -> case unfoldr' f a' of + (bs, r) -> (b : bs, r) -isRecursiveInRoot :: Glob -> Bool -isRecursiveInRoot (GlobFinal (FinalMatch Recursive _ _)) = True +-- | Is the root of this relative glob path a directory-recursive wildcard, e.g. @**/*.txt@ ? +isRecursiveInRoot :: FilePathGlobRel -> Bool +isRecursiveInRoot (GlobDirRecursive _) = True isRecursiveInRoot _ = False + +-- | Check how the string matches the glob under this cabal version +checkNameMatches :: CabalSpecVersion -> Glob -> String -> Maybe (GlobResult ()) +checkNameMatches spec glob candidate + -- Check if glob matches in its general form + | matchGlob glob candidate = + -- if multidot is supported, then this is a clean match + if enableMultidot spec + then pure (GlobMatch ()) + else -- if not, issue a warning saying multidot is needed for the match + + let (_, candidateExts) = splitExtensions $ takeFileName candidate + extractExts :: Glob -> Maybe String + extractExts [] = Nothing + extractExts [Literal lit] + -- Any literal terminating a glob, and which does have an extension, + -- returns that extension. Otherwise, recurse until Nothing is returned. + | let ext = takeExtensions lit + , ext /= "" = + Just ext + extractExts (_ : x) = extractExts x + in case extractExts glob of + Just exts + | exts == candidateExts -> + return (GlobMatch ()) + | exts `isSuffixOf` candidateExts -> + return (GlobWarnMultiDot ()) + _ -> return (GlobMatch ()) + | otherwise = empty + +-- | How/does the glob match the given filepath, according to the cabal version? +fileGlobMatches :: CabalSpecVersion -> FilePathGlobRel -> FilePath -> Maybe (GlobResult ()) +fileGlobMatches version g path = go g (splitDirectories path) + where + go GlobDirTrailing [] = Just (GlobMatch ()) + go (GlobFile glob) [file] = checkNameMatches version glob file + go (GlobDirRecursive glob) dirs + | [] <- reverse dirs = + Nothing -- @dir/**/x.txt@ should not match @dir/hello@ + | file : _ <- reverse dirs = + checkNameMatches version glob file + go (GlobDir glob globPath) (dir : dirs) = do + _ <- checkNameMatches version glob dir -- we only care if dir segment matches + go globPath dirs + go _ _ = Nothing diff --git a/Cabal/src/Distribution/Simple/SrcDist.hs b/Cabal/src/Distribution/Simple/SrcDist.hs index 706d3b51e35..cbb4dc44c11 100644 --- a/Cabal/src/Distribution/Simple/SrcDist.hs +++ b/Cabal/src/Distribution/Simple/SrcDist.hs @@ -55,7 +55,7 @@ import Distribution.Pretty import Distribution.Simple.BuildPaths import Distribution.Simple.Configure (findDistPrefOrDefault) import Distribution.Simple.Flag -import Distribution.Simple.Glob (matchDirFileGlobWithDie) +import Distribution.Simple.Glob import Distribution.Simple.PreProcess import Distribution.Simple.Program import Distribution.Simple.Setup.SDist diff --git a/cabal-install/cabal-install.cabal b/cabal-install/cabal-install.cabal index f36c1162b13..f27c371f344 100644 --- a/cabal-install/cabal-install.cabal +++ b/cabal-install/cabal-install.cabal @@ -119,7 +119,6 @@ library Distribution.Client.GZipUtils Distribution.Client.GenBounds Distribution.Client.Get - Distribution.Client.Glob Distribution.Client.GlobalFlags Distribution.Client.Haddock Distribution.Client.HashValue diff --git a/cabal-install/src/Distribution/Client/FileMonitor.hs b/cabal-install/src/Distribution/Client/FileMonitor.hs index 5edd159496b..69a3bc27f09 100644 --- a/cabal-install/src/Distribution/Client/FileMonitor.hs +++ b/cabal-install/src/Distribution/Client/FileMonitor.hs @@ -65,9 +65,9 @@ import Control.Monad.State (StateT, mapStateT) import qualified Control.Monad.State as State import Control.Monad.Trans (MonadIO, liftIO) -import Distribution.Client.Glob import Distribution.Client.Utils (MergeResult (..), mergeBy) import Distribution.Compat.Time +import Distribution.Simple.Glob import Distribution.Simple.Utils (handleDoesNotExist, writeFileAtomic) import Distribution.Utils.Structured (Tag (..), structuredEncode) import System.Directory @@ -1050,6 +1050,7 @@ buildMonitorStateGlobRel dirEntries <- getDirectoryContents absdir dirMTime <- getModTime absdir case globPath of + GlobDirRecursive{} -> error "Monitoring directory-recursive globs (i.e. ../**/...) is currently unsupported" GlobDir glob globPath' -> do subdirs <- filterM (\subdir -> doesDirectoryExist (absdir subdir)) $ diff --git a/cabal-install/src/Distribution/Client/Glob.hs b/cabal-install/src/Distribution/Client/Glob.hs deleted file mode 100644 index 66baadf7a5d..00000000000 --- a/cabal-install/src/Distribution/Client/Glob.hs +++ /dev/null @@ -1,258 +0,0 @@ -{-# LANGUAGE DeriveGeneric #-} - --- TODO: [code cleanup] plausibly much of this module should be merged with --- similar functionality in Cabal. -module Distribution.Client.Glob - ( FilePathGlob (..) - , FilePathRoot (..) - , FilePathGlobRel (..) - , Glob - , GlobPiece (..) - , matchFileGlob - , matchFileGlobRel - , matchGlob - , isTrivialFilePathGlob - , getFilePathRootDirectory - ) where - -import Distribution.Client.Compat.Prelude -import Prelude () - -import Data.List (stripPrefix) -import System.Directory -import System.FilePath - -import qualified Distribution.Compat.CharParsing as P -import qualified Text.PrettyPrint as Disp - --- | A file path specified by globbing -data FilePathGlob = FilePathGlob FilePathRoot FilePathGlobRel - deriving (Eq, Show, Generic) - -data FilePathGlobRel - = GlobDir !Glob !FilePathGlobRel - | GlobFile !Glob - | -- | trailing dir, a glob ending in @/@ - GlobDirTrailing - deriving (Eq, Show, Generic) - --- | A single directory or file component of a globbed path -type Glob = [GlobPiece] - --- | A piece of a globbing pattern -data GlobPiece - = WildCard - | Literal String - | Union [Glob] - deriving (Eq, Show, Generic) - -data FilePathRoot - = FilePathRelative - | -- | e.g. @"/"@, @"c:\"@ or result of 'takeDrive' - FilePathRoot FilePath - | FilePathHomeDir - deriving (Eq, Show, Generic) - -instance Binary FilePathGlob -instance Binary FilePathRoot -instance Binary FilePathGlobRel -instance Binary GlobPiece - -instance Structured FilePathGlob -instance Structured FilePathRoot -instance Structured FilePathGlobRel -instance Structured GlobPiece - --- | Check if a 'FilePathGlob' doesn't actually make use of any globbing and --- is in fact equivalent to a non-glob 'FilePath'. --- --- If it is trivial in this sense then the result is the equivalent constant --- 'FilePath'. On the other hand if it is not trivial (so could in principle --- match more than one file) then the result is @Nothing@. -isTrivialFilePathGlob :: FilePathGlob -> Maybe FilePath -isTrivialFilePathGlob (FilePathGlob root pathglob) = - case root of - FilePathRelative -> go [] pathglob - FilePathRoot root' -> go [root'] pathglob - FilePathHomeDir -> Nothing - where - go paths (GlobDir [Literal path] globs) = go (path : paths) globs - go paths (GlobFile [Literal path]) = Just (joinPath (reverse (path : paths))) - go paths GlobDirTrailing = - Just - ( addTrailingPathSeparator - (joinPath (reverse paths)) - ) - go _ _ = Nothing - --- | Get the 'FilePath' corresponding to a 'FilePathRoot'. --- --- The 'FilePath' argument is required to supply the path for the --- 'FilePathRelative' case. -getFilePathRootDirectory - :: FilePathRoot - -> FilePath - -- ^ root for relative paths - -> IO FilePath -getFilePathRootDirectory FilePathRelative root = return root -getFilePathRootDirectory (FilePathRoot root) _ = return root -getFilePathRootDirectory FilePathHomeDir _ = getHomeDirectory - ------------------------------------------------------------------------------- --- Matching --- - --- | Match a 'FilePathGlob' against the file system, starting from a given --- root directory for relative paths. The results of relative globs are --- relative to the given root. Matches for absolute globs are absolute. -matchFileGlob :: FilePath -> FilePathGlob -> IO [FilePath] -matchFileGlob relroot (FilePathGlob globroot glob) = do - root <- getFilePathRootDirectory globroot relroot - matches <- matchFileGlobRel root glob - case globroot of - FilePathRelative -> return matches - _ -> return (map (root ) matches) - --- | Match a 'FilePathGlobRel' against the file system, starting from a --- given root directory. The results are all relative to the given root. -matchFileGlobRel :: FilePath -> FilePathGlobRel -> IO [FilePath] -matchFileGlobRel root glob0 = go glob0 "" - where - go (GlobFile glob) dir = do - entries <- getDirectoryContents (root dir) - let files = filter (matchGlob glob) entries - return (map (dir ) files) - go (GlobDir glob globPath) dir = do - entries <- getDirectoryContents (root dir) - subdirs <- - filterM - ( \subdir -> - doesDirectoryExist - (root dir subdir) - ) - $ filter (matchGlob glob) entries - concat <$> traverse (\subdir -> go globPath (dir subdir)) subdirs - go GlobDirTrailing dir = return [dir] - --- | Match a globbing pattern against a file path component -matchGlob :: Glob -> String -> Bool -matchGlob = goStart - where - -- From the man page, glob(7): - -- "If a filename starts with a '.', this character must be - -- matched explicitly." - - go, goStart :: [GlobPiece] -> String -> Bool - - goStart (WildCard : _) ('.' : _) = False - goStart (Union globs : rest) cs = - any - (\glob -> goStart (glob ++ rest) cs) - globs - goStart rest cs = go rest cs - - go [] "" = True - go (Literal lit : rest) cs - | Just cs' <- stripPrefix lit cs = - go rest cs' - | otherwise = False - go [WildCard] "" = True - go (WildCard : rest) (c : cs) = go rest (c : cs) || go (WildCard : rest) cs - go (Union globs : rest) cs = any (\glob -> go (glob ++ rest) cs) globs - go [] (_ : _) = False - go (_ : _) "" = False - ------------------------------------------------------------------------------- --- Parsing & printing --- - -instance Pretty FilePathGlob where - pretty (FilePathGlob root pathglob) = pretty root Disp.<> pretty pathglob - -instance Parsec FilePathGlob where - parsec = do - root <- parsec - case root of - FilePathRelative -> FilePathGlob root <$> parsec - _ -> FilePathGlob root <$> parsec <|> pure (FilePathGlob root GlobDirTrailing) - -instance Pretty FilePathRoot where - pretty FilePathRelative = Disp.empty - pretty (FilePathRoot root) = Disp.text root - pretty FilePathHomeDir = Disp.char '~' Disp.<> Disp.char '/' - -instance Parsec FilePathRoot where - parsec = root <|> P.try home <|> P.try drive <|> pure FilePathRelative - where - root = FilePathRoot "/" <$ P.char '/' - home = FilePathHomeDir <$ P.string "~/" - drive = do - dr <- P.satisfy $ \c -> (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') - _ <- P.char ':' - _ <- P.char '/' <|> P.char '\\' - return (FilePathRoot (toUpper dr : ":\\")) - -instance Pretty FilePathGlobRel where - pretty (GlobDir glob pathglob) = - dispGlob glob - Disp.<> Disp.char '/' - Disp.<> pretty pathglob - pretty (GlobFile glob) = dispGlob glob - pretty GlobDirTrailing = Disp.empty - -instance Parsec FilePathGlobRel where - parsec = parsecPath - where - parsecPath :: CabalParsing m => m FilePathGlobRel - parsecPath = do - glob <- parsecGlob - dirSep *> (GlobDir glob <$> parsecPath <|> pure (GlobDir glob GlobDirTrailing)) <|> pure (GlobFile glob) - - dirSep :: CabalParsing m => m () - dirSep = - () <$ P.char '/' - <|> P.try - ( do - _ <- P.char '\\' - -- check this isn't an escape code - P.notFollowedBy (P.satisfy isGlobEscapedChar) - ) - -dispGlob :: Glob -> Disp.Doc -dispGlob = Disp.hcat . map dispPiece - where - dispPiece WildCard = Disp.char '*' - dispPiece (Literal str) = Disp.text (escape str) - dispPiece (Union globs) = - Disp.braces - ( Disp.hcat - ( Disp.punctuate - (Disp.char ',') - (map dispGlob globs) - ) - ) - escape [] = [] - escape (c : cs) - | isGlobEscapedChar c = '\\' : c : escape cs - | otherwise = c : escape cs - -parsecGlob :: CabalParsing m => m Glob -parsecGlob = some parsecPiece - where - parsecPiece = P.choice [literal, wildcard, union] - - wildcard = WildCard <$ P.char '*' - union = Union . toList <$> P.between (P.char '{') (P.char '}') (P.sepByNonEmpty parsecGlob (P.char ',')) - literal = Literal <$> some litchar - - litchar = normal <|> escape - - normal = P.satisfy (\c -> not (isGlobEscapedChar c) && c /= '/' && c /= '\\') - escape = P.try $ P.char '\\' >> P.satisfy isGlobEscapedChar - -isGlobEscapedChar :: Char -> Bool -isGlobEscapedChar '*' = True -isGlobEscapedChar '{' = True -isGlobEscapedChar '}' = True -isGlobEscapedChar ',' = True -isGlobEscapedChar _ = False diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index b4d20e317cc..68e63d4e7f2 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -59,9 +59,6 @@ module Distribution.Client.ProjectConfig import Distribution.Client.Compat.Prelude import Prelude () -import Distribution.Client.Glob - ( isTrivialFilePathGlob - ) import Distribution.Client.ProjectConfig.Legacy import Distribution.Client.ProjectConfig.Types import Distribution.Client.RebuildMonad @@ -73,6 +70,9 @@ import Distribution.Client.VCS , syncSourceRepos , validateSourceRepos ) +import Distribution.Simple.Glob + ( isTrivialFilePathGlob + ) import Distribution.Client.BuildReports.Types ( ReportLevel (..) diff --git a/cabal-install/src/Distribution/Client/RebuildMonad.hs b/cabal-install/src/Distribution/Client/RebuildMonad.hs index 89378922d66..f3016a54a69 100644 --- a/cabal-install/src/Distribution/Client/RebuildMonad.hs +++ b/cabal-install/src/Distribution/Client/RebuildMonad.hs @@ -61,8 +61,8 @@ import Distribution.Client.Compat.Prelude import Prelude () import Distribution.Client.FileMonitor -import Distribution.Client.Glob hiding (matchFileGlob) -import qualified Distribution.Client.Glob as Glob (matchFileGlob) +import Distribution.Simple.Glob hiding (matchFileGlob) +import qualified Distribution.Simple.Glob as Glob (matchFileGlob) import Distribution.Simple.Utils (debug) diff --git a/cabal-install/tests/UnitTests/Distribution/Client/ArbitraryInstances.hs b/cabal-install/tests/UnitTests/Distribution/Client/ArbitraryInstances.hs index 13e06172f80..c3d86556dac 100644 --- a/cabal-install/tests/UnitTests/Distribution/Client/ArbitraryInstances.hs +++ b/cabal-install/tests/UnitTests/Distribution/Client/ArbitraryInstances.hs @@ -27,12 +27,12 @@ import Prelude () import Data.Char (isLetter) import Data.List ((\\)) +import Distribution.Simple.Glob (FilePathGlob (..), FilePathGlobRel (..), FilePathRoot (..), GlobPiece (..)) import Distribution.Simple.Setup import Distribution.Types.Flag (mkFlagAssignment) import Distribution.Client.BuildReports.Types (BuildReport, InstallOutcome, Outcome, ReportLevel (..)) import Distribution.Client.CmdInstall.ClientInstallFlags (InstallMethod) -import Distribution.Client.Glob (FilePathGlob (..), FilePathGlobRel (..), FilePathRoot (..), GlobPiece (..)) import Distribution.Client.IndexUtils.ActiveRepos (ActiveRepoEntry (..), ActiveRepos (..), CombineStrategy (..)) import Distribution.Client.IndexUtils.IndexState (RepoIndexState (..), TotalIndexState, makeTotalIndexState) import Distribution.Client.IndexUtils.Timestamp (Timestamp, epochTimeToTimestamp) @@ -403,6 +403,9 @@ instance Arbitrary FilePathGlobRel where : [ GlobDir (getGlobPieces glob') pathglob' | (glob', pathglob') <- shrink (GlobPieces glob, pathglob) ] + shrink (GlobDirRecursive glob) = + GlobDirTrailing + : [GlobFile (getGlobPieces glob') | glob' <- shrink (GlobPieces glob)] newtype GlobPieces = GlobPieces {getGlobPieces :: [GlobPiece]} deriving (Eq) diff --git a/cabal-install/tests/UnitTests/Distribution/Client/DescribedInstances.hs b/cabal-install/tests/UnitTests/Distribution/Client/DescribedInstances.hs index 66b9649db11..eed48f01939 100644 --- a/cabal-install/tests/UnitTests/Distribution/Client/DescribedInstances.hs +++ b/cabal-install/tests/UnitTests/Distribution/Client/DescribedInstances.hs @@ -8,12 +8,13 @@ import Distribution.Client.Compat.Prelude import Data.List ((\\)) import Distribution.Described +import Distribution.Simple.Glob (FilePathGlob) + import Distribution.Types.PackageId (PackageIdentifier) import Distribution.Types.PackageName (PackageName) import Distribution.Types.VersionRange (VersionRange) import Distribution.Client.BuildReports.Types (InstallOutcome, Outcome) -import Distribution.Client.Glob (FilePathGlob) import Distribution.Client.IndexUtils.ActiveRepos (ActiveRepoEntry, ActiveRepos, CombineStrategy) import Distribution.Client.IndexUtils.IndexState (RepoIndexState, TotalIndexState) import Distribution.Client.IndexUtils.Timestamp (Timestamp) diff --git a/cabal-install/tests/UnitTests/Distribution/Client/FileMonitor.hs b/cabal-install/tests/UnitTests/Distribution/Client/FileMonitor.hs index 0663360df42..50b5e6ea393 100644 --- a/cabal-install/tests/UnitTests/Distribution/Client/FileMonitor.hs +++ b/cabal-install/tests/UnitTests/Distribution/Client/FileMonitor.hs @@ -89,10 +89,10 @@ tests mtimeChange = _ -> id fingerprintStateGlob1, fingerprintStateGlob2, fingerprintStateFileSet1, fingerprintStateFileSet2 :: Word64 #if MIN_VERSION_base(4,19,0) - fingerprintStateGlob1 = 0xae70229aabb1ba1f - fingerprintStateGlob2 = 0xb53ed324c96f0d0d - fingerprintStateFileSet1 = 0x8e509e16f973e036 - fingerprintStateFileSet2 = 0xa23f21d8dc8a2dee + fingerprintStateGlob1 = 0xa6709a67f9d48afd + fingerprintStateGlob2 = 0x1a91e9125b065db2 + fingerprintStateFileSet1 = 0x7b19a792e1dd32cd + fingerprintStateFileSet2 = 0xd29e58469b754889 #else fingerprintStateGlob1 = 0xfd8f6be0e8258fe7 fingerprintStateGlob2 = 0xdb5fac737139bca6 diff --git a/cabal-install/tests/UnitTests/Distribution/Client/Glob.hs b/cabal-install/tests/UnitTests/Distribution/Client/Glob.hs index 8d77b6784ef..f03edbf40be 100644 --- a/cabal-install/tests/UnitTests/Distribution/Client/Glob.hs +++ b/cabal-install/tests/UnitTests/Distribution/Client/Glob.hs @@ -6,7 +6,7 @@ module UnitTests.Distribution.Client.Glob (tests) where import Distribution.Client.Compat.Prelude hiding (last) import Prelude () -import Distribution.Client.Glob +import Distribution.Simple.Glob import Distribution.Utils.Structured (structureHash) import UnitTests.Distribution.Client.ArbitraryInstances () @@ -22,9 +22,9 @@ tests = , testGroup "Structured hashes" [ testCase "GlobPiece" $ structureHash (Proxy :: Proxy GlobPiece) @?= Fingerprint 0xd5e5361866a30ea2 0x31fbfe7b58864782 - , testCase "FilePathGlobRel" $ structureHash (Proxy :: Proxy FilePathGlobRel) @?= Fingerprint 0x76fa5bcb865a8501 0xb152f68915316f98 + , testCase "FilePathGlobRel" $ structureHash (Proxy :: Proxy FilePathGlobRel) @?= Fingerprint 0xe260ba155c75dff4 0x8bbedbb21037a03b , testCase "FilePathRoot" $ structureHash (Proxy :: Proxy FilePathRoot) @?= Fingerprint 0x713373d51426ec64 0xda7376a38ecee5a5 - , testCase "FilePathGlob" $ structureHash (Proxy :: Proxy FilePathGlob) @?= Fingerprint 0x3c11c41f3f03a1f0 0x96e69d85c37d0024 + , testCase "FilePathGlob" $ structureHash (Proxy :: Proxy FilePathGlob) @?= Fingerprint 0xb0bd71fd87b02400 0x5bc1892a3bb3f21b ] ] diff --git a/cabal-testsuite/PackageTests/Check/ConfiguredPackage/Paths/AbsolutePath/cabal.out b/cabal-testsuite/PackageTests/Check/ConfiguredPackage/Paths/AbsolutePath/cabal.out index 8fa0a5d985b..562f6f4d4f7 100644 --- a/cabal-testsuite/PackageTests/Check/ConfiguredPackage/Paths/AbsolutePath/cabal.out +++ b/cabal-testsuite/PackageTests/Check/ConfiguredPackage/Paths/AbsolutePath/cabal.out @@ -1,6 +1,6 @@ # cabal check These warnings may cause trouble when distributing the package: -Warning: [no-glob-match] In 'extra-source-files': the pattern '/home/user/file' does not match any files. +Warning: [glob-missing-dir] In 'extra-source-files': the pattern '/home/user/file' attempts to match files in the directory '/home/user', but there is no directory by that name. The following errors will cause portability problems on other environments: Error: [absolute-path] 'extra-source-files: /home/user/file' specifies an absolute path, but the 'extra-source-files' field must use relative paths. Error: [malformed-relative-path] 'extra-source-files: /home/user/file' is not a good relative path: "posix absolute path"