diff --git a/Cabal/Cabal.cabal b/Cabal/Cabal.cabal index 47e078a51e5..0095b664f32 100644 --- a/Cabal/Cabal.cabal +++ b/Cabal/Cabal.cabal @@ -73,6 +73,14 @@ extra-source-files: tests/ParserTests/errors/spdx-2.errors tests/ParserTests/errors/spdx-3.cabal tests/ParserTests/errors/spdx-3.errors + tests/ParserTests/errors/version-sets-1.cabal + tests/ParserTests/errors/version-sets-1.errors + tests/ParserTests/errors/version-sets-2.cabal + tests/ParserTests/errors/version-sets-2.errors + tests/ParserTests/errors/version-sets-3.cabal + tests/ParserTests/errors/version-sets-3.errors + tests/ParserTests/errors/version-sets-4.cabal + tests/ParserTests/errors/version-sets-4.errors tests/ParserTests/ipi/Includes2.cabal tests/ParserTests/ipi/Includes2.expr tests/ParserTests/ipi/Includes2.format @@ -172,6 +180,9 @@ extra-source-files: tests/ParserTests/regressions/th-lift-instances.cabal tests/ParserTests/regressions/th-lift-instances.expr tests/ParserTests/regressions/th-lift-instances.format + tests/ParserTests/regressions/version-sets.cabal + tests/ParserTests/regressions/version-sets.expr + tests/ParserTests/regressions/version-sets.format tests/ParserTests/regressions/wl-pprint-indef.cabal tests/ParserTests/regressions/wl-pprint-indef.expr tests/ParserTests/regressions/wl-pprint-indef.format diff --git a/Cabal/ChangeLog.md b/Cabal/ChangeLog.md index d6897fb08cd..9c041e3be65 100644 --- a/Cabal/ChangeLog.md +++ b/Cabal/ChangeLog.md @@ -1,5 +1,6 @@ # 2.6.0.0 (current development version) * TODO + * Introduce set notation for `^>=` and `==` operators. * 'check' reports warnings for various ghc-\*-options fields separately ([#5342](https://github.com/haskell/cabal/issues/5432)). * `KnownExtension`: added new extension `DerivingVia`. diff --git a/Cabal/Distribution/Types/VersionRange.hs b/Cabal/Distribution/Types/VersionRange.hs index 8a0f7f38d32..9f21b5004a1 100644 --- a/Cabal/Distribution/Types/VersionRange.hs +++ b/Cabal/Distribution/Types/VersionRange.hs @@ -420,8 +420,19 @@ instance Parsec VersionRange where "==" -> do P.spaces - (wild, v) <- verOrWild - pure $ (if wild then withinVersion else thisVersion) v + (do (wild, v) <- verOrWild + pure $ (if wild then withinVersion else thisVersion) v + <|> + (verSet' thisVersion =<< verSet)) + + "^>=" -> do + P.spaces + (do (wild, v) <- verOrWild + when wild $ P.unexpected $ + "wild-card version after ^>= operator" + majorBoundVersion' v + <|> + (verSet' majorBoundVersion =<< verSet)) _ -> do P.spaces @@ -431,7 +442,6 @@ instance Parsec VersionRange where case op of ">=" -> pure $ orLaterVersion v "<" -> pure $ earlierVersion v - "^>=" -> majorBoundVersion' v "<=" -> pure $ orEarlierVersion v ">" -> pure $ laterVersion v _ -> fail $ "Unknown version operator " ++ show op @@ -469,6 +479,34 @@ instance Parsec VersionRange where (orLaterVersion u) (earlierVersion (majorUpperBound u)) embed vr = embedVersionRange vr + -- version set notation (e.g. "== { 0.0.1.0, 0.0.2.0, 0.1.0.0 }") + verSet' op vs = do + csv <- askCabalSpecVersion + if csv >= CabalSpecV3_0 + then pure $ foldr1 unionVersionRanges (map op vs) + else fail $ unwords + [ "version set syntax used." + , "To use this syntax the package needs to specify at least 'cabal-version: 3.0'." + , "Alternatively, if broader compatibility is important then use" + , "a series of single version constraints joined with the || operator:" + , prettyShow (foldr1 unionVersionRanges (map op vs)) + ] + + verSet :: CabalParsing m => m [Version] + verSet = do + _ <- P.char '{' + P.spaces + vs <- P.sepBy1 (verPlain <* P.spaces) (P.char ',' *> P.spaces) + _ <- P.char '}' + pure vs + + -- a plain version without tags or wildcards + -- note: this uses P.integral which allows redundant zeros. + -- This is not a problem because 'verPlain' is only used by + -- 'verSet' which requires cabal > 3 + verPlain :: CabalParsing m => m Version + verPlain = mkVersion <$> P.sepBy1 P.integral (P.char '.') + -- either wildcard or normal version verOrWild :: CabalParsing m => m (Bool, Version) verOrWild = do diff --git a/Cabal/doc/developing-packages.rst b/Cabal/doc/developing-packages.rst index 969b74ea2b5..b6d530466d3 100644 --- a/Cabal/doc/developing-packages.rst +++ b/Cabal/doc/developing-packages.rst @@ -2134,6 +2134,25 @@ system-dependent values for these fields. renaming in ``build-depends``; however, this support has since been removed and should not be used. + Starting with Cabal 3.0, a set notation for the ``==`` and ``^>=`` operator + is available. For instance, + + :: + + tested-with: GHC == 8.6.3, GHC == 8.4.4, GHC == 8.2.2, GHC == 8.0.2, + GHC == 7.10.3, GHC == 7.8.4, GHC == 7.6.3, GHC == 7.4.2 + + build-depends: network ^>= 2.6.3.6 || ^>= 2.7.0.2 || ^>= 2.8.0.0 || ^>= 3.0.1.0 + + can be then written in a more convenient and concise form + + :: + + tested-with: GHC == { 8.6.3, 8.4.4, 8.2.2, 8.0.2, 7.10.3, 7.8.4, 7.6.3, 7.4.2 } + + build-depends: network ^>= { 2.6.3.6, 2.7.0.2, 2.8.0.0, 3.0.1.0 } + + .. pkg-field:: other-modules: identifier list A list of modules used by the component but not exposed to users. diff --git a/Cabal/tests/ParserTests.hs b/Cabal/tests/ParserTests.hs index 1ac34662a97..b717ac770df 100644 --- a/Cabal/tests/ParserTests.hs +++ b/Cabal/tests/ParserTests.hs @@ -112,6 +112,10 @@ errorTests = testGroup "errors" , errorTest "spdx-2.cabal" , errorTest "spdx-3.cabal" , errorTest "removed-fields.cabal" + , errorTest "version-sets-1.cabal" + , errorTest "version-sets-2.cabal" + , errorTest "version-sets-3.cabal" + , errorTest "version-sets-4.cabal" ] errorTest :: FilePath -> TestTree @@ -159,6 +163,7 @@ regressionTests = testGroup "regressions" , regressionTest "spdx-3.cabal" , regressionTest "hidden-main-lib.cabal" , regressionTest "jaeger-flamegraph.cabal" + , regressionTest "version-sets.cabal" ] regressionTest :: FilePath -> TestTree diff --git a/Cabal/tests/ParserTests/errors/version-sets-1.cabal b/Cabal/tests/ParserTests/errors/version-sets-1.cabal new file mode 100644 index 00000000000..c6c73575953 --- /dev/null +++ b/Cabal/tests/ParserTests/errors/version-sets-1.cabal @@ -0,0 +1,8 @@ +cabal-version: 2.5 +name: version-sets +version: 0 +synopsis: version set notation + +library + default-language: Haskell2010 + build-depends: network == { } diff --git a/Cabal/tests/ParserTests/errors/version-sets-1.errors b/Cabal/tests/ParserTests/errors/version-sets-1.errors new file mode 100644 index 00000000000..2b498be3194 --- /dev/null +++ b/Cabal/tests/ParserTests/errors/version-sets-1.errors @@ -0,0 +1,5 @@ +VERSION: Just (mkVersion [2,5]) +version-sets-1.cabal:8:31: +unexpected "}" +expecting space or integral + diff --git a/Cabal/tests/ParserTests/errors/version-sets-2.cabal b/Cabal/tests/ParserTests/errors/version-sets-2.cabal new file mode 100644 index 00000000000..4b97a806929 --- /dev/null +++ b/Cabal/tests/ParserTests/errors/version-sets-2.cabal @@ -0,0 +1,8 @@ +cabal-version: 2.5 +name: version-sets +version: 0 +synopsis: version set notation + +library + default-language: Haskell2010 + build-depends: network >= { 2.8.0.0 } diff --git a/Cabal/tests/ParserTests/errors/version-sets-2.errors b/Cabal/tests/ParserTests/errors/version-sets-2.errors new file mode 100644 index 00000000000..726e21b9cd1 --- /dev/null +++ b/Cabal/tests/ParserTests/errors/version-sets-2.errors @@ -0,0 +1,5 @@ +VERSION: Just (mkVersion [2,5]) +version-sets-2.cabal:8:29: +unexpected "{" +expecting space or integral + diff --git a/Cabal/tests/ParserTests/errors/version-sets-3.cabal b/Cabal/tests/ParserTests/errors/version-sets-3.cabal new file mode 100644 index 00000000000..63e14da920f --- /dev/null +++ b/Cabal/tests/ParserTests/errors/version-sets-3.cabal @@ -0,0 +1,8 @@ +cabal-version: 2.5 +name: version-sets +version: 0 +synopsis: version set notation + +library + default-language: Haskell2010 + build-depends: network == { 2.8.0.0, } diff --git a/Cabal/tests/ParserTests/errors/version-sets-3.errors b/Cabal/tests/ParserTests/errors/version-sets-3.errors new file mode 100644 index 00000000000..78e65b6d255 --- /dev/null +++ b/Cabal/tests/ParserTests/errors/version-sets-3.errors @@ -0,0 +1,5 @@ +VERSION: Just (mkVersion [2,5]) +version-sets-3.cabal:8:40: +unexpected "}" +expecting space or integral + diff --git a/Cabal/tests/ParserTests/errors/version-sets-4.cabal b/Cabal/tests/ParserTests/errors/version-sets-4.cabal new file mode 100644 index 00000000000..76ce030bcb4 --- /dev/null +++ b/Cabal/tests/ParserTests/errors/version-sets-4.cabal @@ -0,0 +1,8 @@ +cabal-version: 2.5 +name: version-sets +version: 0 +synopsis: version set notation + +library + default-language: Haskell2010 + build-depends: network == { 2.8.* } diff --git a/Cabal/tests/ParserTests/errors/version-sets-4.errors b/Cabal/tests/ParserTests/errors/version-sets-4.errors new file mode 100644 index 00000000000..18263debaf9 --- /dev/null +++ b/Cabal/tests/ParserTests/errors/version-sets-4.errors @@ -0,0 +1,5 @@ +VERSION: Just (mkVersion [2,5]) +version-sets-4.cabal:8:35: +unexpected "*" +expecting integral + diff --git a/Cabal/tests/ParserTests/regressions/version-sets.cabal b/Cabal/tests/ParserTests/regressions/version-sets.cabal new file mode 100644 index 00000000000..62c6198bad0 --- /dev/null +++ b/Cabal/tests/ParserTests/regressions/version-sets.cabal @@ -0,0 +1,18 @@ +cabal-version: 2.5 +name: version-sets +version: 0 +synopsis: version set notation +tested-with: GHC == { 8.6.3, 8.4.4, 8.2.2, 8.0.2, 7.10.3, 7.8.4, 7.6.3, 7.4.2 } + +library + default-language: Haskell2010 + build-depends: network ^>= {0} + build-depends: base == {1}, base == { 1 }, base == {1,2}, base == {1.2}, base == {1.2,3.4} + build-depends: ghc == { + 8.6.3 + , + 8.4.4 ,8.2.2 , + 8.0.2,7.10.3 + , 7.8.4, 7.6.3, 7.4.2 + } + build-depends: Cabal ^>= { 2.4.1.1, 2.2.0.0 } diff --git a/Cabal/tests/ParserTests/regressions/version-sets.expr b/Cabal/tests/ParserTests/regressions/version-sets.expr new file mode 100644 index 00000000000..82b0d9a4af9 --- /dev/null +++ b/Cabal/tests/ParserTests/regressions/version-sets.expr @@ -0,0 +1,241 @@ +GenericPackageDescription + {condBenchmarks = [], + condExecutables = [], + condForeignLibs = [], + condLibrary = Just + CondNode + {condTreeComponents = [], + condTreeConstraints = [Dependency + `PackageName "network"` + (MajorBoundVersion `mkVersion [0]`) + (Set.fromList [LMainLibName]), + Dependency + `PackageName "base"` + (ThisVersion `mkVersion [1]`) + (Set.fromList [LMainLibName]), + Dependency + `PackageName "base"` + (ThisVersion `mkVersion [1]`) + (Set.fromList [LMainLibName]), + Dependency + `PackageName "base"` + (UnionVersionRanges + (ThisVersion `mkVersion [1]`) + (ThisVersion `mkVersion [2]`)) + (Set.fromList [LMainLibName]), + Dependency + `PackageName "base"` + (ThisVersion `mkVersion [1,2]`) + (Set.fromList [LMainLibName]), + Dependency + `PackageName "base"` + (UnionVersionRanges + (ThisVersion `mkVersion [1,2]`) + (ThisVersion `mkVersion [3,4]`)) + (Set.fromList [LMainLibName]), + Dependency + `PackageName "ghc"` + (UnionVersionRanges + (ThisVersion `mkVersion [8,6,3]`) + (UnionVersionRanges + (ThisVersion `mkVersion [8,4,4]`) + (UnionVersionRanges + (ThisVersion `mkVersion [8,2,2]`) + (UnionVersionRanges + (ThisVersion `mkVersion [8,0,2]`) + (UnionVersionRanges + (ThisVersion `mkVersion [7,10,3]`) + (UnionVersionRanges + (ThisVersion `mkVersion [7,8,4]`) + (UnionVersionRanges + (ThisVersion + `mkVersion [7,6,3]`) + (ThisVersion + `mkVersion [7,4,2]`)))))))) + (Set.fromList [LMainLibName]), + Dependency + `PackageName "Cabal"` + (UnionVersionRanges + (MajorBoundVersion `mkVersion [2,4,1,1]`) + (MajorBoundVersion `mkVersion [2,2,0,0]`)) + (Set.fromList [LMainLibName])], + condTreeData = Library + {exposedModules = [], + libBuildInfo = BuildInfo + {asmOptions = [], + asmSources = [], + autogenModules = [], + buildToolDepends = [], + buildTools = [], + buildable = True, + cSources = [], + ccOptions = [], + cmmOptions = [], + cmmSources = [], + cppOptions = [], + customFieldsBI = [], + cxxOptions = [], + cxxSources = [], + defaultExtensions = [], + defaultLanguage = Just Haskell2010, + extraBundledLibs = [], + extraDynLibFlavours = [], + extraFrameworkDirs = [], + extraGHCiLibs = [], + extraLibDirs = [], + extraLibFlavours = [], + extraLibs = [], + frameworks = [], + hsSourceDirs = [], + includeDirs = [], + includes = [], + installIncludes = [], + jsSources = [], + ldOptions = [], + mixins = [], + oldExtensions = [], + options = PerCompilerFlavor [] [], + otherExtensions = [], + otherLanguages = [], + otherModules = [], + pkgconfigDepends = [], + profOptions = PerCompilerFlavor [] [], + sharedOptions = PerCompilerFlavor [] [], + staticOptions = PerCompilerFlavor [] [], + targetBuildDepends = [Dependency + `PackageName "network"` + (MajorBoundVersion + `mkVersion [0]`) + (Set.fromList + [LMainLibName]), + Dependency + `PackageName "base"` + (ThisVersion + `mkVersion [1]`) + (Set.fromList + [LMainLibName]), + Dependency + `PackageName "base"` + (ThisVersion + `mkVersion [1]`) + (Set.fromList + [LMainLibName]), + Dependency + `PackageName "base"` + (UnionVersionRanges + (ThisVersion + `mkVersion [1]`) + (ThisVersion + `mkVersion [2]`)) + (Set.fromList + [LMainLibName]), + Dependency + `PackageName "base"` + (ThisVersion + `mkVersion [1,2]`) + (Set.fromList + [LMainLibName]), + Dependency + `PackageName "base"` + (UnionVersionRanges + (ThisVersion + `mkVersion [1,2]`) + (ThisVersion + `mkVersion [3,4]`)) + (Set.fromList + [LMainLibName]), + Dependency + `PackageName "ghc"` + (UnionVersionRanges + (ThisVersion + `mkVersion [8,6,3]`) + (UnionVersionRanges + (ThisVersion + `mkVersion [8,4,4]`) + (UnionVersionRanges + (ThisVersion + `mkVersion [8,2,2]`) + (UnionVersionRanges + (ThisVersion + `mkVersion [8,0,2]`) + (UnionVersionRanges + (ThisVersion + `mkVersion [7,10,3]`) + (UnionVersionRanges + (ThisVersion + `mkVersion [7,8,4]`) + (UnionVersionRanges + (ThisVersion + `mkVersion [7,6,3]`) + (ThisVersion + `mkVersion [7,4,2]`)))))))) + (Set.fromList + [LMainLibName]), + Dependency + `PackageName "Cabal"` + (UnionVersionRanges + (MajorBoundVersion + `mkVersion [2,4,1,1]`) + (MajorBoundVersion + `mkVersion [2,2,0,0]`)) + (Set.fromList + [LMainLibName])], + virtualModules = []}, + libExposed = True, + libName = LMainLibName, + libVisibility = LibraryVisibilityPublic, + reexportedModules = [], + signatures = []}}, + condSubLibraries = [], + condTestSuites = [], + genPackageFlags = [], + packageDescription = PackageDescription + {author = "", + benchmarks = [], + bugReports = "", + buildTypeRaw = Nothing, + category = "", + copyright = "", + customFieldsPD = [], + dataDir = "", + dataFiles = [], + description = "", + executables = [], + extraDocFiles = [], + extraSrcFiles = [], + extraTmpFiles = [], + foreignLibs = [], + homepage = "", + library = Nothing, + licenseFiles = [], + licenseRaw = Left NONE, + maintainer = "", + package = PackageIdentifier + {pkgName = `PackageName "version-sets"`, + pkgVersion = `mkVersion [0]`}, + pkgUrl = "", + setupBuildInfo = Nothing, + sourceRepos = [], + specVersionRaw = Left `mkVersion [2,5]`, + stability = "", + subLibraries = [], + synopsis = "version set notation", + testSuites = [], + testedWith = [_×_ + GHC + (UnionVersionRanges + (ThisVersion `mkVersion [8,6,3]`) + (UnionVersionRanges + (ThisVersion `mkVersion [8,4,4]`) + (UnionVersionRanges + (ThisVersion `mkVersion [8,2,2]`) + (UnionVersionRanges + (ThisVersion `mkVersion [8,0,2]`) + (UnionVersionRanges + (ThisVersion `mkVersion [7,10,3]`) + (UnionVersionRanges + (ThisVersion `mkVersion [7,8,4]`) + (UnionVersionRanges + (ThisVersion `mkVersion [7,6,3]`) + (ThisVersion + `mkVersion [7,4,2]`))))))))]}} diff --git a/Cabal/tests/ParserTests/regressions/version-sets.format b/Cabal/tests/ParserTests/regressions/version-sets.format new file mode 100644 index 00000000000..3296a046037 --- /dev/null +++ b/Cabal/tests/ParserTests/regressions/version-sets.format @@ -0,0 +1,18 @@ +cabal-version: 2.5 +name: version-sets +version: 0 +tested-with: + ghc ==8.6.3 || ==8.4.4 || ==8.2.2 || ==8.0.2 || ==7.10.3 || ==7.8.4 || ==7.6.3 || ==7.4.2 +synopsis: version set notation + +library + default-language: Haskell2010 + build-depends: + network ^>=0, + base ==1, + base ==1, + base ==1 || ==2, + base ==1.2, + base ==1.2 || ==3.4, + ghc ==8.6.3 || ==8.4.4 || ==8.2.2 || ==8.0.2 || ==7.10.3 || ==7.8.4 || ==7.6.3 || ==7.4.2, + Cabal ^>=2.4.1.1 || ^>=2.2.0.0