Skip to content

Commit 7479a71

Browse files
authored
Merge pull request #788 from haskell-CI/conditionals
Add cabal.project conditionals support
2 parents b6d43e3 + 845e73a commit 7479a71

File tree

11 files changed

+1010
-14
lines changed

11 files changed

+1010
-14
lines changed

cabal-install-parsers/Changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.6.4
2+
3+
- Add support for reading project files with conditionals.
4+
15
## 0.6.3
26

37
- Drop support for GHC prior 8.8.4

cabal-install-parsers/cabal-install-parsers.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cabal-version: 2.2
22
name: cabal-install-parsers
3-
version: 0.6.3
3+
version: 0.6.4
44
synopsis: Utilities to work with cabal-install files
55
description:
66
@cabal-install-parsers@ provides parsers for @cabal-install@ files:

cabal-install-parsers/src/Cabal/Index.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ indexMetadata indexFilepath mindexState = do
399399

400400
f :: Maybe TmpPackageInfo -> Maybe TmpPackageInfo
401401
f Nothing = Just TmpPackageInfo
402-
{ tmpPiVersions = Map.singleton ver TmpReleaseInfo
402+
{ tmpPiVersions = Map.singleton ver TmpReleaseInfo
403403
{ tmpRiRevision = 0
404404
, tmpRiTarOffset = offset
405405
, tmpRiCabalHash = Just digest

cabal-install-parsers/src/Cabal/Project.hs

Lines changed: 124 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
{-# LANGUAGE CPP #-}
2-
{-# LANGUAGE DeriveFoldable #-}
3-
{-# LANGUAGE DeriveFunctor #-}
4-
{-# LANGUAGE DeriveGeneric #-}
5-
{-# LANGUAGE DeriveTraversable #-}
6-
{-# LANGUAGE MultiWayIf #-}
7-
{-# LANGUAGE OverloadedStrings #-}
1+
{-# LANGUAGE CPP #-}
2+
{-# LANGUAGE DeriveFoldable #-}
3+
{-# LANGUAGE DeriveFunctor #-}
4+
{-# LANGUAGE DeriveGeneric #-}
5+
{-# LANGUAGE DeriveTraversable #-}
6+
{-# LANGUAGE MultiWayIf #-}
7+
{-# LANGUAGE OverloadedStrings #-}
8+
{-# LANGUAGE ScopedTypeVariables #-}
89
-- | License: GPL-3.0-or-later AND BSD-3-Clause
910
--
1011
module Cabal.Project (
@@ -15,6 +16,8 @@ module Cabal.Project (
1516
-- * Parse project
1617
readProject,
1718
parseProject,
19+
readProjectWithConditionals,
20+
parseProjectWithConditionals,
1821
-- * Resolve project
1922
resolveProject,
2023
ResolveError (..),
@@ -25,6 +28,7 @@ module Cabal.Project (
2528

2629
import Control.DeepSeq (NFData (..))
2730
import Control.Exception (Exception (..), throwIO)
31+
import Control.Monad (unless)
2832
import Control.Monad.IO.Class (liftIO)
2933
import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE)
3034
import Data.Bifoldable (Bifoldable (..))
@@ -55,6 +59,7 @@ import qualified Data.Map.Strict as M
5559
import qualified Distribution.CabalSpecVersion as C
5660
import qualified Distribution.FieldGrammar as C
5761
import qualified Distribution.Fields as C
62+
import qualified Distribution.Fields.ConfVar as C
5863
import qualified Distribution.PackageDescription as C
5964
import qualified Distribution.Parsec as C
6065

@@ -71,6 +76,12 @@ infixl 1 <&>
7176

7277
-- $setup
7378
-- >>> :set -XOverloadedStrings
79+
-- >>> import Data.String (fromString)
80+
-- >>> import qualified Distribution.PackageDescription as C
81+
-- >>> import Text.Show (showListWith)
82+
-- >>> import Data.Functor.Classes (liftShowsPrec)
83+
-- >>> let sB (C.CondBranch c t f) = showString "CondBranch _ " . showParen True (sT t) . showChar ' ' . liftShowsPrec (\_ -> sT) undefined 11 f; sT (C.CondNode x c xs) = showString "CondTree " . showsPrec 11 x . showString " _ " . showListWith sB xs
84+
-- >>> pp x = putStrLn (either show (flip sT "") x)
7485

7586
-- | @cabal.project@ file
7687
data Project uri opt pkg = Project
@@ -152,7 +163,7 @@ instance (NFData c, NFData b, NFData a) => NFData (Project c b a) where
152163
rnf x7 `seq` rnf x8 `seq` rnf x9 `seq`
153164
rnfList rnfPrettyField x10
154165
where
155-
rnfList :: (a -> ()) -> [a] -> ()
166+
rnfList :: (x -> ()) -> [x] -> ()
156167
rnfList _ [] = ()
157168
rnfList f (x:xs) = f x `seq` rnfList f xs
158169

@@ -179,6 +190,13 @@ readProject fp = do
179190
prj1 <- resolveProject fp prj0 >>= either throwIO return
180191
readPackagesOfProject prj1 >>= either throwIO return
181192

193+
readProjectWithConditionals :: FilePath -> IO (C.CondTree C.ConfVar () (Project URI Void (FilePath, C.GenericPackageDescription)))
194+
readProjectWithConditionals fp = do
195+
contents <- BS.readFile fp
196+
prj0 <- either throwIO return (parseProjectWithConditionals fp contents)
197+
prj1 <- traverse (\p -> resolveProject fp p >>= either throwIO return) prj0
198+
traverse (\p -> readPackagesOfProject p >>= either throwIO return) prj1
199+
182200
-- | Parse project file. Extracts only few fields.
183201
--
184202
-- >>> fmap prjPackages $ parseProject "cabal.project" "packages: foo bar/*.cabal"
@@ -207,6 +225,46 @@ parseProject = parseWith $ \fields0 -> do
207225

208226
parseSec _ = return id
209227

228+
-- | Parse project files with conditionals.
229+
--
230+
-- >>> pp $ fmap (fmap prjPackages) $ parseProjectWithConditionals "cabal.project" "packages: foo bar/*.cabal"
231+
-- CondTree ["foo","bar/*.cabal"] _ []
232+
--
233+
-- >>> pp $ fmap (fmap prjPackages) $ parseProjectWithConditionals "cabal.project" $ fromString $ unlines [ "packages: foo bar/*.cabal", "if impl(ghc >=9)", " packages: quu" ]
234+
-- CondTree ["foo","bar/*.cabal"] _ [CondBranch _ (CondTree ["quu"] _ []) Nothing]
235+
--
236+
-- >>> pp $ fmap (fmap prjPackages) $ parseProjectWithConditionals "cabal.project" $ fromString $ unlines [ "packages: foo bar/*.cabal", "if impl(ghc >=9)", " packages: quu", "if impl(ghc >=10)", " packages: zoo" ]
237+
-- CondTree ["foo","bar/*.cabal"] _ [CondBranch _ (CondTree ["quu"] _ []) Nothing,CondBranch _ (CondTree ["zoo"] _ []) Nothing]
238+
--
239+
-- >>> pp $ fmap (fmap prjPackages) $ parseProjectWithConditionals "cabal.project" $ fromString $ unlines [ "packages: foo bar/*.cabal", "if impl(ghc >=9)", " packages: quu", "else", " packages: zoo" ]
240+
-- CondTree ["foo","bar/*.cabal"] _ [CondBranch _ (CondTree ["quu"] _ []) (Just CondTree ["zoo"] _ [])]
241+
--
242+
-- >>> pp $ fmap (fmap prjPackages) $ parseProjectWithConditionals "cabal.project" $ fromString $ unlines [ "packages: foo bar/*.cabal", "if impl(ghc >=9)", " packages: quu", "elif impl(ghc >=10)", " packages: zoo", "else", " packages: yyz" ]
243+
-- CondTree ["foo","bar/*.cabal"] _ [CondBranch _ (CondTree ["quu"] _ []) (Just CondTree [] _ [CondBranch _ (CondTree ["zoo"] _ []) (Just CondTree ["yyz"] _ [])])]
244+
--
245+
parseProjectWithConditionals :: FilePath -> ByteString -> Either (ParseError NonEmpty) (C.CondTree C.ConfVar () (Project Void String String))
246+
parseProjectWithConditionals = parseWith $ \fields0 -> flip parseCondTree fields0 $ \fields1 sections -> do
247+
let fields2 = M.filterWithKey (\k _ -> k `elem` knownFields) fields1
248+
parse fields0 fields2 sections
249+
where
250+
knownFields = C.fieldGrammarKnownFieldList $ grammar []
251+
252+
parse :: [C.Field a] -> C.Fields C.Position -> [[C.Section C.Position]] -> C.ParseResult (Project Void String String)
253+
parse otherFields fields sections = do
254+
let prettyOtherFields = map void $ C.fromParsecFields $ filter otherFieldName otherFields
255+
prj <- C.parseFieldGrammar C.cabalSpecLatest fields $ grammar prettyOtherFields
256+
foldl' (&) prj <$> traverse parseSec (concat sections)
257+
258+
-- Special case for source-repository-package. If you add another such
259+
-- special case, make sure to update otherFieldName appropriately.
260+
parseSec :: C.Section C.Position -> C.ParseResult (Project Void String String -> Project Void String String)
261+
parseSec (C.MkSection (C.Name _pos name) [] fields) | name == sourceRepoSectionName = do
262+
let fields' = fst $ C.partitionFields fields
263+
repos <- C.parseFieldGrammar C.cabalSpecLatest fields' sourceRepositoryPackageGrammar
264+
return $ over prjSourceReposL (++ toList (srpFanOut repos))
265+
266+
parseSec _ = return id
267+
210268
-- | Returns 'True' if a field should be a part of 'prjOtherFields'. This
211269
-- excludes any field that is a part of 'grammar' as well as
212270
-- @source-repository-package@ (see 'parseProject', which has a special case
@@ -377,3 +435,61 @@ readPackagesOfProject :: Project uri opt FilePath -> IO (Either (ParseError NonE
377435
readPackagesOfProject prj = runExceptT $ for prj $ \fp -> do
378436
contents <- liftIO $ BS.readFile fp
379437
either throwE (\gpd -> return (fp, gpd)) (parsePackage fp contents)
438+
439+
-------------------------------------------------------------------------------
440+
-- Read package files
441+
-------------------------------------------------------------------------------
442+
443+
parseCondTree
444+
:: forall a. (C.Fields C.Position -> [[C.Section C.Position]] -> C.ParseResult a) -- ^ parse
445+
-> [C.Field C.Position]
446+
-> C.ParseResult (C.CondTree C.ConfVar () a)
447+
parseCondTree subparse = go
448+
where
449+
go fields = do
450+
let (fs, ss) = C.partitionFields fields
451+
(ss', branches) <- second concat . unzip <$> traverse (goIfs id id) ss
452+
x <- subparse fs ss'
453+
return $ C.CondNode x () branches
454+
455+
goIfs
456+
:: ([C.Section C.Position] -> [C.Section C.Position])
457+
-> ([C.CondBranch C.ConfVar () a] -> [C.CondBranch C.ConfVar () a])
458+
-> [C.Section C.Position]
459+
-> C.ParseResult ([C.Section C.Position], [C.CondBranch C.ConfVar () a])
460+
goIfs accS accB [] = do
461+
return (accS [], accB [])
462+
goIfs accS accB (C.MkSection (C.Name pos name) args fields : sections)
463+
| name == "if" = do
464+
test' <- C.parseConditionConfVar args
465+
fields' <- go fields
466+
goElse (C.CondBranch test' fields') accS accB sections
467+
| name == "else" = do
468+
C.parseFailure pos "standalone else"
469+
return ([], [])
470+
| name == "elif" = do
471+
C.parseFailure pos "standalone elif"
472+
goIfs accS accB sections
473+
goIfs accS accB (section : sections) = do
474+
goIfs (accS . (section :)) accB sections
475+
476+
goElse
477+
:: (Maybe (C.CondTree C.ConfVar () a) -> C.CondBranch C.ConfVar () a)
478+
-> ([C.Section C.Position] -> [C.Section C.Position])
479+
-> ([C.CondBranch C.ConfVar () a] -> [C.CondBranch C.ConfVar () a])
480+
-> [C.Section C.Position]
481+
-> C.ParseResult ([C.Section C.Position], [C.CondBranch C.ConfVar () a])
482+
goElse make accS accB (C.MkSection (C.Name pos name) args fields : sections)
483+
| name == "else" = do
484+
unless (null args) $ C.parseFailure pos "arguments passed to else"
485+
fields' <- go fields
486+
let condTree = make (Just fields')
487+
goIfs accS (accB . (condTree :)) sections
488+
| name == "elif" = do
489+
test' <- C.parseConditionConfVar args
490+
fields' <- go fields
491+
emptyA <- subparse mempty []
492+
goElse (make . Just . C.CondNode emptyA () . pure . C.CondBranch test' fields') accS accB sections
493+
goElse make accS accB sections = do
494+
let condTree = make Nothing
495+
goIfs accS (accB . (condTree :)) sections

fixtures/conditionals.args

Whitespace-only changes.

0 commit comments

Comments
 (0)