From f60c9d77beea62a101a69f02fce622f5981868e4 Mon Sep 17 00:00:00 2001 From: gbaz Date: Wed, 13 Apr 2022 18:01:27 -0400 Subject: [PATCH] allow glob matches of the form dir/**/FileNoExtension (#8005) * allow glob matches of the form dir/**/FileNoExtension * allow glob matches of the form dir/**/FileNoExtension * tests, docs, changelong Co-authored-by: Gershom Bazerman Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- Cabal-tests/tests/CheckTests.hs | 2 + .../regressions/globstar-literal.cabal | 16 +++++++ .../regressions/globstar-literal.check | 0 .../pre-3.8-globstar-literal.cabal | 16 +++++++ .../pre-3.8-globstar-literal.check | 1 + Cabal/src/Distribution/Simple/Glob.hs | 42 +++++++++++++------ changelog.d/pr-8005 | 9 ++++ doc/cabal-package.rst | 9 ++-- doc/file-format-changelog.rst | 7 +++- 9 files changed, 86 insertions(+), 16 deletions(-) create mode 100644 Cabal-tests/tests/ParserTests/regressions/globstar-literal.cabal create mode 100644 Cabal-tests/tests/ParserTests/regressions/globstar-literal.check create mode 100644 Cabal-tests/tests/ParserTests/regressions/pre-3.8-globstar-literal.cabal create mode 100644 Cabal-tests/tests/ParserTests/regressions/pre-3.8-globstar-literal.check create mode 100644 changelog.d/pr-8005 diff --git a/Cabal-tests/tests/CheckTests.hs b/Cabal-tests/tests/CheckTests.hs index 6b3f47ddbaf..b9b5d6d94f2 100644 --- a/Cabal-tests/tests/CheckTests.hs +++ b/Cabal-tests/tests/CheckTests.hs @@ -36,6 +36,8 @@ checkTests = testGroup "regressions" , checkTest "pre-1.6-glob.cabal" , checkTest "pre-2.4-globstar.cabal" , checkTest "bad-glob-syntax.cabal" + , checkTest "globstar-literal.cabal" + , checkTest "pre-3.8-globstar-literal.cabal" , checkTest "cc-options-with-optimization.cabal" , checkTest "cxx-options-with-optimization.cabal" , checkTest "ghc-option-j.cabal" diff --git a/Cabal-tests/tests/ParserTests/regressions/globstar-literal.cabal b/Cabal-tests/tests/ParserTests/regressions/globstar-literal.cabal new file mode 100644 index 00000000000..e0964a677a5 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/regressions/globstar-literal.cabal @@ -0,0 +1,16 @@ +cabal-version: 3.8 +name: globstar-literal +version: 0 +extra-source-files: + foo/**/bar + +license: BSD-3-Clause +synopsis: no +description: none +category: Test +maintainer: none + +library + default-language: Haskell2010 + exposed-modules: + Foo diff --git a/Cabal-tests/tests/ParserTests/regressions/globstar-literal.check b/Cabal-tests/tests/ParserTests/regressions/globstar-literal.check new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Cabal-tests/tests/ParserTests/regressions/pre-3.8-globstar-literal.cabal b/Cabal-tests/tests/ParserTests/regressions/pre-3.8-globstar-literal.cabal new file mode 100644 index 00000000000..5fe8691a809 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/regressions/pre-3.8-globstar-literal.cabal @@ -0,0 +1,16 @@ +cabal-version: 3.6 +name: globstar-literal +version: 0 +extra-source-files: + foo/**/bar + +license: BSD-3-Clause +synopsis: no +description: none +category: Test +maintainer: none + +library + default-language: Haskell2010 + exposed-modules: + Foo diff --git a/Cabal-tests/tests/ParserTests/regressions/pre-3.8-globstar-literal.check b/Cabal-tests/tests/ParserTests/regressions/pre-3.8-globstar-literal.check new file mode 100644 index 00000000000..73250aca7aa --- /dev/null +++ b/Cabal-tests/tests/ParserTests/regressions/pre-3.8-globstar-literal.check @@ -0,0 +1 @@ +In the 'extra-source-files' field: invalid file glob 'foo/**/bar'. Prior to 'cabal-version: 3.8' if a wildcard '**' is used as a parent directory, the file's base name must be a wildcard '*'. diff --git a/Cabal/src/Distribution/Simple/Glob.hs b/Cabal/src/Distribution/Simple/Glob.hs index 7c44c9c99a9..de14f2db7ef 100644 --- a/Cabal/src/Distribution/Simple/Glob.hs +++ b/Cabal/src/Distribution/Simple/Glob.hs @@ -96,7 +96,8 @@ explainGlobSyntaxError filepath NoExtensionOnStar = ++ "'. If a wildcard '*' is used it must be with an file extension." explainGlobSyntaxError filepath LiteralFileNameGlobStar = "invalid file glob '" ++ filepath - ++ "'. If a wildcard '**' is used as a parent directory, the" + ++ "'. Prior to 'cabal-version: 3.8'" + ++ " if a wildcard '**' is used as a parent directory, the" ++ " file's base name must be a wildcard '*'." explainGlobSyntaxError _ EmptyGlob = "invalid file glob. A glob cannot be the empty string." @@ -111,7 +112,7 @@ explainGlobSyntaxError filepath VersionDoesNotSupportGlob = ++ "Alternatively if you require compatibility with earlier Cabal " ++ "versions then list all the files explicitly." -data IsRecursive = Recursive | NonRecursive +data IsRecursive = Recursive | NonRecursive deriving Eq data MultiDot = MultiDotDisabled | MultiDotEnabled @@ -125,7 +126,7 @@ 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. - | FinalLit FilePath + | FinalLit IsRecursive FilePath -- ^ Literal file name. reconstructGlob :: Glob -> FilePath @@ -134,7 +135,8 @@ reconstructGlob (GlobStem dir glob) = reconstructGlob (GlobFinal final) = case final of FinalMatch Recursive _ exts -> "**" "*" <.> exts FinalMatch NonRecursive _ exts -> "*" <.> exts - FinalLit path -> path + 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 @@ -159,8 +161,8 @@ fileGlobMatchesSegments pat (seg : segs) = case pat of let (candidateBase, candidateExts) = splitExtensions seg guard (null segs && not (null candidateBase)) checkExt multidot ext candidateExts - FinalLit filename -> do - guard (null segs && filename == seg) + FinalLit isRecursive filename -> do + guard ((isRecursive == Recursive || null segs) && filename == seg) return (GlobMatch ()) checkExt @@ -181,12 +183,14 @@ parseFileGlob version filepath = case reverse (splitDirectories filepath) of Left EmptyGlob (filename : "**" : segments) | allowGlobStar -> do - ext <- case splitExtensions filename of + finalSegment <- case splitExtensions filename of ("*", ext) | '*' `elem` ext -> Left StarInExtension | null ext -> Left NoExtensionOnStar - | otherwise -> Right ext - _ -> Left LiteralFileNameGlobStar - foldM addStem (GlobFinal $ FinalMatch Recursive multidot ext) segments + | 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 @@ -196,7 +200,7 @@ parseFileGlob version filepath = case reverse (splitDirectories filepath) of | otherwise -> Right (FinalMatch NonRecursive multidot ext) (_, ext) | '*' `elem` ext -> Left StarInExtension | '*' `elem` filename -> Left StarInFileName - | otherwise -> Right (FinalLit filename) + | otherwise -> Right (FinalLit NonRecursive filename) foldM addStem (GlobFinal pat) segments where allowGlob = version >= CabalSpecV1_6 @@ -207,6 +211,7 @@ parseFileGlob version filepath = case reverse (splitDirectories filepath) of multidot | version >= CabalSpecV2_4 = MultiDotEnabled | otherwise = MultiDotDisabled + allowLiteralFilenameGlobStar = version >= CabalSpecV3_8 -- | 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. @@ -300,7 +305,20 @@ runDirFileGlob verbosity rawDir pat = do return $ mapMaybe checkName candidates else return [ GlobMissingDirectory joinedPrefix ] - FinalLit fn -> do + 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 ] diff --git a/changelog.d/pr-8005 b/changelog.d/pr-8005 new file mode 100644 index 00000000000..736157886d3 --- /dev/null +++ b/changelog.d/pr-8005 @@ -0,0 +1,9 @@ +synopsis: Allow glob-star matches with literal filenames (no extensions) +packages: Cabal +prs: #8005 +issues: #5883 +description: { + +- Cabal file glob syntax extended to allow matches of the form dir/**/FileNoExtension + +} diff --git a/doc/cabal-package.rst b/doc/cabal-package.rst index 49a591313db..76f52544e58 100644 --- a/doc/cabal-package.rst +++ b/doc/cabal-package.rst @@ -742,10 +742,13 @@ describe the package as a whole: - ``**`` wildcards can only appear as the final path component before the file name (e.g., ``data/**/images/*.jpg`` is not - allowed). If a ``**`` wildcard is used, then the file name must - include a ``*`` wildcard (e.g., ``data/**/README.rst`` is not allowed). + - Prior to Cabal 3.8, if a ``**`` wildcard is used, then + the file name must include a ``*`` wildcard (e.g., + ``data/**/README.rst`` was not allowed). As of ``cabal-version: + 3.8`` or greater, this restriction is lifted. + - A wildcard that does not match any files is an error. The reason for providing only a very limited form of wildcard is to @@ -2319,7 +2322,7 @@ system-dependent values for these fields. Cabal files with :pkg-field:`cabal-version` < 3.0 suffer from an infelicity in how the entries of :pkg-field:`mixins` are parsed: an entry will fail to parse if the provided renaming clause has whitespace - after the opening parenthesis. + after the opening parenthesis. See issues :issue:`5150`, :issue:`4864`, and :issue:`5293`. diff --git a/doc/file-format-changelog.rst b/doc/file-format-changelog.rst index 566d7a79ce5..78afa510ae6 100644 --- a/doc/file-format-changelog.rst +++ b/doc/file-format-changelog.rst @@ -19,7 +19,7 @@ relative to the respective preceding *published* version. versions of the ``Cabal`` library denote unreleased development branches which have no stability guarantee. -``cabal-version: 3.x`` +``cabal-version: 3.8`` ---------------------- * Added field ``code-generators`` to :pkg-section:`test-suite` stanzas. This @@ -45,6 +45,11 @@ relative to the respective preceding *published* version. When :pkg-field:`extra-lib-dirs-static` is not given, it defaults to :pkg-field:`extra-lib-dirs`. +* Wildcard matching has been slightly expanded. Matches are now + allowed of the form ``foo/**/literalFile``. Prior, double-star + wildcards required the trailing filename itself be a wildcard. + + ``cabal-version: 3.6`` ----------------------