aihc-cabal-syntax-1.0.0.1: src/Aihc/Cabal/Internal/Parser.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Read package descriptions. The rules follow the package parser of
-- Cabal-syntax 3.18. The parser accepts Cabal format versions up to 3.18.
module Aihc.Cabal.Internal.Parser (parsePackage, parseHookedBuildInfo) where
import Control.Monad (foldM, guard, unless, when)
import Data.Bits (shiftL, (.&.), (.|.))
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import Data.Char (isAlphaNum)
import Data.List (find, partition)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NE
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe, isJust)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Text.Megaparsec (eof, runParser, takeWhile1P, (<|>))
import Text.Megaparsec.Char (space)
import Aihc.Cabal.Internal.Condition (parseCondition)
import Aihc.Cabal.Internal.Lexer (Field (..), SectionArg (..), readFields)
import Aihc.Cabal.Internal.Quirks (patchQuirks)
import Aihc.Cabal.Internal.Types
import Aihc.Cabal.Internal.Values
import Aihc.Cabal.Internal.Version
type Result a = Either Diagnostic a
type Fields = Map Text [FieldValue]
-- | A section inside a list of fields: position, name, arguments, and contents.
data SectionInfo = SectionInfo Position Text [SectionArg] [Field]
failAt :: Position -> Text -> Result a
failAt p = Left . Diagnostic (Just p)
-- | An error of a check on the complete package. It has no position.
failPackage :: Text -> Result a
failPackage = Left . Diagnostic Nothing
report :: Result a -> ParseResult a
report = ParseResult []
-- | Decode UTF-8 text. For invalid input, use the Cabal-syntax decoder: it
-- replaces each invalid sequence with U+FFFD and continues.
decode :: BS.ByteString -> Text
decode bytes = either (const (T.pack (lenient (BS.unpack bytes)))) id (TE.decodeUtf8' bytes)
where
lenient [] = []
lenient (c : cs)
| c <= 0x7f = toEnum (fromIntegral c) : lenient cs
| c <= 0xbf = replacement : lenient cs
| c <= 0xdf = case cs of
c1 : rest | c1 .&. 0xc0 == 0x80 ->
let d = (fromIntegral (c .&. 0x1f) `shiftL` 6) .|. fromIntegral (c1 .&. 0x3f)
in (if d >= 0x80 then toEnum d else replacement) : lenient rest
_ -> replacement : lenient cs
| c <= 0xef = more (3 :: Int) 0x800 cs (fromIntegral (c .&. 0xf))
| c <= 0xf7 = more 4 0x10000 cs (fromIntegral (c .&. 0x7))
| c <= 0xfb = more 5 0x200000 cs (fromIntegral (c .&. 0x3))
| c <= 0xfd = more 6 0x4000000 cs (fromIntegral (c .&. 0x1))
| otherwise = replacement : lenient cs
more 1 overlong cs acc
| overlong <= acc && acc <= 0x10ffff && (acc < 0xd800 || 0xdfff < acc) = toEnum acc : lenient cs
| otherwise = replacement : lenient cs
more n overlong (c : cs) acc
| c .&. 0xc0 == 0x80 = more (n - 1) overlong cs ((acc `shiftL` 6) .|. fromIntegral (c .&. 0x3f))
more _ _ cs _ = replacement : lenient cs
replacement = '\xfffd'
readInput :: BS.ByteString -> Result [Field]
readInput bytes = readFields (fromMaybe text (T.stripPrefix "\xfeff" text))
where text = decode bytes
parseField :: Parser a -> FieldValue -> Result a
parseField p fv = either (failAt (fieldPosition fv)) Right (runValue p (fieldText fv))
-- | A singular field. The last value wins. As in Cabal-syntax, the first of
-- several values is not parsed.
singular :: (FieldValue -> Result a) -> [FieldValue] -> Result (Maybe a)
singular _ [] = Right Nothing
singular f [x] = Just <$> f x
singular f (_ : xs) = Just . last <$> mapM f xs
-- | A singular field that has no value when its text is empty.
optionalField :: Parser a -> [FieldValue] -> Result (Maybe a)
optionalField p = fmap (>>= id) . singular one
where one fv = if null (fieldLines fv) then Right Nothing else Just <$> parseField p fv
monoidal :: Parser [a] -> [FieldValue] -> Result [a]
monoidal p = fmap concat . mapM (parseField p)
takeFields :: [Field] -> (Fields, [Field])
takeFields fields = (collect [(n, FieldValue p ls) | Field p n ls <- leading], rest)
where (leading, rest) = span isField fields
isField :: Field -> Bool
isField Field {} = True
isField Section {} = False
collect :: [(Text, FieldValue)] -> Fields
collect xs = Map.fromListWith (flip (++)) [(k, [v]) | (k, v) <- xs]
-- | Separate fields from sections. Consecutive sections form one group.
partitionFields :: [Field] -> (Fields, [[SectionInfo]])
partitionFields fields = (collect [(n, FieldValue p ls) | Field p n ls <- fields], groups fields)
where
groups xs = case dropWhile isField xs of
[] -> []
ys -> let (ss, rest) = break isField ys
in [SectionInfo p n a b | Section p n a b <- ss] : groups rest
-- | Fields of a section that cannot contain sections.
plainFields :: [Field] -> Result Fields
plainFields body = case sections of
SectionInfo p n _ _ : _ -> failAt p ("invalid subsection " <> T.pack (show n))
[] -> Right fs
where (fs, sections) = fmap concat (partitionFields body)
-- | The Cabal specification version for version digits, or 'Nothing' for an
-- unknown version.
knownSpec :: NonEmpty Integer -> Maybe Version
knownSpec ds = case NE.toList ds of
v | v `elem` [[3, 18], [3, 16], [3, 14], [3, 12], [3, 8], [3, 6], [3, 4], [3, 0], [2, 4], [2, 2], [2, 0]] -> Just (specVersion v)
| v >= [1, 25] -> Nothing
| otherwise -> specVersion . snd <$> find ((v >=) . fst) older
where
older =
[ ([1, 23], [1, 24]), ([1, 21], [1, 22]), ([1, 19], [1, 20]), ([1, 17], [1, 18])
, ([1, 11], [1, 12]), ([1, 9], [1, 10]), ([1, 7], [1, 8]), ([1, 5], [1, 6])
, ([1, 3], [1, 4]), ([1, 1], [1, 2]), ([], [1, 0]) ]
-- | The value of a @cabal-version@ field: a version or a range.
specVersionParser :: Version -> Parser Version
specVersionParser spec = do
v <- versionParser <|> range
maybe (fail ("Unknown cabal spec version specified: " ++ T.unpack (renderVersion v))) pure (knownSpec (versionNumbers v))
where
range = do
v <- lowestVersion <$> rangeParser spec
when (v >= specVersion [2, 1]) (fail "cabal-version higher than 2.2 cannot be specified as a range")
pure v
-- | The smallest lower bound of a range. An empty range gives version 0.
lowestVersion :: VersionRange -> Version
lowestVersion range = case [v | ((v, _), _) <- intervals range] of
[] -> zero
vs -> minimum vs
where
zero = specVersion [0]
intervals r = case r of
AnyVersion -> [((zero, True), Nothing)]
Equal v -> [((v, True), Just (v, True))]
Later v -> [((v, False), Nothing)]
AtLeast v -> [((v, True), Nothing)]
Earlier v -> nonEmpty ((zero, True), Just (v, False))
AtMost v -> [((zero, True), Just (v, True))]
MajorBound v -> intervals (Both (AtLeast v) (Earlier (majorUpper v)))
Both a b -> concat [nonEmpty (maxLower l1 l2, minUpper u1 u2) | (l1, u1) <- intervals a, (l2, u2) <- intervals b]
EitherRange a b -> intervals a ++ intervals b
nonEmpty i@((l, li), u) = case u of
Nothing -> [i]
Just (h, hi) | l < h || (l == h && li && hi) -> [i]
| otherwise -> []
maxLower a@(v, i) b@(w, j)
| v /= w = if v > w then a else b
| otherwise = (v, i && j)
minUpper Nothing u = u
minUpper u Nothing = u
minUpper (Just a@(v, i)) (Just b@(w, j))
| v /= w = Just (if v < w then a else b)
| otherwise = Just (v, i && j)
majorUpper v = case NE.toList (versionNumbers v) of
[x] -> specVersion [x, 1]
x : y : _ -> specVersion [x, y + 1]
[] -> zero
-- | Read the version on the first line, as in @cabal-version: 3.0@.
scanSpecVersion :: BS.ByteString -> Maybe Version
scanSpecVersion bytes = do
line : _ <- Just (BS8.lines bytes)
let normalized = BS.map lower (BS.filter (/= 0x20) line)
[key, text] <- Just (BS8.split ':' normalized)
guard (key == "cabal-version")
v <- either (const Nothing) Just (runParser (versionParser <* space <* eof) "" (decode text))
guard (length (versionNumbers v) `elem` [2, 3])
pure v
where
lower w = if w > 0x40 && w < 0x5b then w + 0x20 else w
-- | Change a file without sections to the section format.
sectionize :: [Field] -> [Field]
sectionize fields
| not (all isField fields) = fields
| otherwise = header ++ library ++ executables exes0
where
name (Field _ n _) = n
name (Section _ n _ _) = n
(header0, exes0) = break ((== "executable") . name) fields
(header, libraryFields0) = partition ((`notElem` libraryFieldNames) . name) header0
(deps, libraryFields) = partition ((== "build-depends") . name) libraryFields0
library = case libraryFields of
[] -> []
f : _ -> [Section (fieldPos f) "library" [] (deps ++ libraryFields)]
executables (Field p "executable" ls : rest) =
let (body, after) = break ((== "executable") . name) rest
exeName = T.dropWhile (== ' ') (T.dropWhileEnd (== ' ') (T.intercalate "\n" (map fieldLineText ls)))
in Section p "executable" [ArgName p exeName] (deps ++ body) : executables after
executables _ = []
fieldPos (Field p _ _) = p
fieldPos (Section p _ _ _) = p
-- | Fields of the build information grammar of Cabal-syntax.
buildInfoFieldNames :: [Text]
buildInfoFieldNames =
[ "buildable", "build-tools", "build-tool-depends", "cpp-options", "asm-options", "cmm-options"
, "cc-options", "cxx-options", "ld-options", "hsc2hs-options", "pkgconfig-depends", "frameworks"
, "extra-framework-dirs", "asm-sources", "cmm-sources", "c-sources", "cxx-sources", "js-sources"
, "hs-source-dirs", "hs-source-dir", "other-modules", "virtual-modules", "autogen-modules"
, "default-language", "other-languages", "default-extensions", "other-extensions", "extensions"
, "extra-libraries", "extra-libraries-static", "extra-ghci-libraries", "extra-bundled-libraries"
, "extra-library-flavours", "extra-dynamic-library-flavours", "extra-lib-dirs"
, "extra-lib-dirs-static", "include-dirs", "includes", "autogen-includes", "install-includes"
, "ghc-options", "ghcjs-options", "jhc-options", "hugs-options", "nhc98-options"
, "ghc-prof-options", "ghcjs-prof-options", "ghc-shared-options", "ghcjs-shared-options"
, "build-depends", "mixins" ]
libraryFieldNames :: [Text]
libraryFieldNames = ["exposed-modules", "reexported-modules", "signatures", "exposed"] ++ buildInfoFieldNames
data Kind = CommonKind | LibraryKind | ExecutableKind | TestKind | BenchmarkKind | ForeignKind
deriving Eq
type Commons = Map Text (Conditional BuildInfo)
mergeTree :: Conditional BuildInfo -> Conditional BuildInfo -> Conditional BuildInfo
mergeTree (Conditional a bs) (Conditional b cs) = Conditional (mergeBuildInfo a b) (bs ++ cs)
isImport :: Field -> Bool
isImport (Field _ "import" _) = True
isImport _ = False
-- | Read the imports at the start of a list of fields. Cabal-syntax ignores
-- the other imports with a warning.
imports :: Version -> Commons -> [Field] -> Result ([Conditional BuildInfo], [Field])
imports spec commons
| specAtLeast [2, 2] spec = go []
| otherwise = \fields -> Right ([], filter (not . isImport) fields)
where
go acc (Field p "import" ls : rest) = do
names <- parseField (commaList spec token) (FieldValue p ls)
trees <- mapM (\n -> maybe (failAt p ("Undefined common stanza imported: " <> n)) Right (Map.lookup n commons)) names
go (acc ++ trees) rest
go acc rest = Right (acc, filter (not . isImport) rest)
stanza :: Version -> Kind -> Commons -> [Field] -> Result (Conditional BuildInfo)
stanza spec kind commons fields = do
(imported, rest) <- imports spec commons fields
tree <- condTree spec kind commons rest
pure (foldr mergeTree tree imported)
condTree :: Version -> Kind -> Commons -> [Field] -> Result (Conditional BuildInfo)
condTree spec kind commons fields0 = do
(imported, fields) <- if specAtLeast [3, 0] spec
then imports spec commons fields0
else Right ([], filter (not . isImport) fields0)
let (fs, groups) = partitionFields fields
info <- buildInfoFields spec kind fs
branches' <- concat <$> mapM ifs groups
pure (foldr mergeTree (Conditional info branches') imported)
where
subtree = condTree spec kind commons
ifs [] = Right []
ifs (SectionInfo p "if" args body : rest) = do
c <- conditionAt p args
yes <- subtree body
(no, rest') <- elses rest
pure (Branch c yes no : rest')
ifs (_ : rest) = ifs rest
elses (SectionInfo p "else" args body : rest) = do
unless (null args) (failAt p "`else` section has section arguments")
no <- subtree body
rest' <- ifs rest
pure (Just no, rest')
elses (SectionInfo p "elif" args body : rest)
| specAtLeast [2, 2] spec = do
c <- conditionAt p args
yes <- subtree body
(no, rest') <- elses rest
pure (Just (Conditional emptyBuildInfo [Branch c yes no]), rest')
| otherwise = (,) Nothing <$> ifs rest
elses rest = (,) Nothing <$> ifs rest
conditionAt p args = maybe (failAt p "Invalid condition") Right (parseCondition args)
-- | Parse the fields of one section level. Fields that the Cabal
-- specification version does not support are ignored.
buildInfoFields :: Version -> Kind -> Fields -> Result BuildInfo
buildInfoFields spec kind fs = do
mapM_ removed [([3, 0], "hs-source-dir"), ([3, 0], "extensions"), ([3, 0], "build-tools")]
buildable' <- singular (parseField bool) (get "buildable")
dirs <- monoidal (spaceList spec filePath) (get "hs-source-dirs")
oldDirs <- monoidal (spaceList spec filePath) (get "hs-source-dir")
exposed <- if kind == LibraryKind then modules (get "exposed-modules") else pure []
other <- modules (get "other-modules")
autogen <- modules (since [2, 0] "autogen-modules")
virtual <- modules (since [2, 2] "virtual-modules")
main <- if kind `elem` [ExecutableKind, TestKind, BenchmarkKind]
then optionalField filePath (get "main-is") else pure Nothing
language <- optionalField (quoted languageName) (since [1, 10] "default-language")
otherLanguages' <- names (since [1, 10] "other-languages")
extensions' <- names (since [1, 10] "default-extensions")
otherExtensions' <- names (get "other-extensions")
legacy <- names (get "extensions")
deps <- monoidal (commaList spec (dependency spec)) (get "build-depends")
mixins' <- monoidal (commaList spec (mixin spec)) (since [2, 0] "mixins")
legacyTools <- monoidal (commaList spec (legacyExeDependency spec)) (get "build-tools")
tools <- monoidal (commaList spec (exeDependency spec)) (get "build-tool-depends")
cs <- paths (get "c-sources")
cxx <- paths (since [2, 2] "cxx-sources")
asm <- paths (since [3, 0] "asm-sources")
cmm <- paths (since [3, 0] "cmm-sources")
js <- paths (get "js-sources")
includeDirs' <- paths (get "include-dirs")
includes' <- paths (get "includes")
installIncludes' <- paths (get "install-includes")
autogenIncludes' <- paths (since [3, 0] "autogen-includes")
libDirs <- paths (get "extra-lib-dirs")
staticLibDirs <- paths (since [3, 8] "extra-lib-dirs-static")
frameworks' <- monoidal (spaceList spec token) (get "frameworks")
frameworkDirs <- paths (get "extra-framework-dirs")
cpp <- options (get "cpp-options")
cc <- options (get "cc-options")
cxxOpts <- options (since [2, 2] "cxx-options")
ghc <- options (get "ghc-options")
-- Evaluate the other fields now. Then the parsed field lines are not kept.
let !rest = Map.filterWithKey keep fs
pure BuildInfo
{ buildable = buildable', sourceDirs = map T.unpack (dirs ++ oldDirs), exposedModules = exposed
, otherModules = other, autogenModules = autogen, virtualModules = virtual
, mainIs = T.unpack <$> main, defaultLanguage = language, otherLanguages = otherLanguages'
, extensions = extensions', otherExtensions = otherExtensions', legacyExtensions = legacy
, dependencies = deps, mixins = mixins', buildTools = legacyTools ++ tools, cSources = map T.unpack cs
, cxxSources = map T.unpack cxx, asmSources = map T.unpack asm, cmmSources = map T.unpack cmm
, jsSources = map T.unpack js, includeDirs = map T.unpack includeDirs'
, includes = map T.unpack includes', installIncludes = map T.unpack installIncludes'
, autogenIncludes = map T.unpack autogenIncludes', extraLibDirs = map T.unpack libDirs
, extraLibDirsStatic = map T.unpack staticLibDirs, frameworks = frameworks'
, extraFrameworkDirs = map T.unpack frameworkDirs, cppOptions = cpp, ccOptions = cc
, cxxOptions = cxxOpts, ghcOptions = ghc, extraFields = rest
}
where
get k = Map.findWithDefault [] k fs
since v k = if specAtLeast v spec then get k else []
removed (v, k) = case get k of
fv : _ | specAtLeast v spec ->
failAt (fieldPosition fv) ("The field " <> k <> " is removed in cabal-version " <> renderVersion (specVersion v))
_ -> Right ()
modules = monoidal (spaceList spec (quoted moduleName))
names = monoidal (spaceList spec (quoted languageName))
paths = monoidal (spaceList spec filePath)
options = monoidal (optionList token')
typed = typedFields ++ ["exposed-modules" | kind == LibraryKind]
++ ["main-is" | kind `elem` [ExecutableKind, TestKind, BenchmarkKind]]
keep k _
| k `elem` typed = False
| kind == CommonKind = k `elem` buildInfoFieldNames || "x-" `T.isPrefixOf` k
| otherwise = True
typedFields :: [Text]
typedFields =
[ "buildable", "hs-source-dirs", "hs-source-dir", "other-modules", "autogen-modules"
, "virtual-modules", "default-language", "other-languages", "default-extensions"
, "other-extensions", "extensions", "build-depends", "mixins", "build-tools", "build-tool-depends"
, "c-sources", "cxx-sources", "asm-sources", "cmm-sources", "js-sources", "include-dirs"
, "includes", "install-includes", "autogen-includes", "extra-lib-dirs", "extra-lib-dirs-static"
, "frameworks", "extra-framework-dirs", "cpp-options", "cc-options", "cxx-options", "ghc-options" ]
-- | The name argument of a component, common stanza, or flag section.
sectionName :: Position -> [SectionArg] -> Result Text
sectionName p args = case args of
[ArgName _ x] -> Right x
[ArgString _ x] -> Right x
[] -> failAt p "name required"
_ -> failAt p "Invalid name"
data State = State
{ stateCommons :: Commons
, stateFlags :: [Flag]
, stateComponents :: [Component (Conditional BuildInfo)]
, stateRepositories :: [SourceRepository]
, stateSetup :: Maybe [Dependency]
}
-- | Parse a package description. First apply the Cabal-syntax patches for
-- known Hackage files. A patched file gets a warning, as in Cabal-syntax.
parsePackage :: BS.ByteString -> ParseResult Package
parsePackage input = case patchQuirks input of
(patched, bytes) -> (report (parsePatched bytes))
{ parseWarnings = [Diagnostic Nothing "Legacy cabal file" | patched] }
parsePatched :: BS.ByteString -> Result Package
parsePatched bytes = do
fields0 <- readInput bytes
let (top, sections) = takeFields (sectionize fields0)
get k = Map.findWithDefault [] k top
spec <- case scanSpecVersion bytes of
Just v -> maybe (failPackage "Unsupported cabal format version") Right (knownSpec (versionNumbers v))
Nothing -> case get "cabal-version" of
[] -> Right (specVersion [1, 0])
values -> do
v <- parseField (specVersionParser (specVersion [1, 24])) (last values)
when (v >= specVersion [2, 2]) (failAt (fieldPosition (last values))
"cabal-version should be at the beginning of the file starting with spec version 2.2")
pure v
parsedSpec <- optionalField (specVersionParser spec) (get "cabal-version")
unless (fromMaybe (specVersion [1, 0]) parsedSpec == spec)
(failPackage "Scanned and parsed cabal-versions don't match")
pkg <- required "name" componentName top
version <- required "version" versionParser top
rawBuildType <- optionalField (buildTypeValue spec) (get "build-type")
State _ flags components repositories setup <- foldM (section spec) (State Map.empty [] [] [] Nothing) sections
let bt = fromMaybe (if specAtLeast [2, 2] spec && not (isJust setup) then "Simple" else "Custom") rawBuildType
knownFlags = map flagName flags
when (bt == "Custom" && not (isJust setup) && specAtLeast [1, 24] spec)
(failPackage "Since cabal-version: 1.24 specifying custom-setup section is mandatory")
when (bt == "Hooks" && not (isJust setup))
(failPackage "Packages with build-type: Hooks require a custom-setup stanza")
mapM_ (checkFlags knownFlags . componentData) components
let libraries = [x | not (specAtLeast [3, 4] spec), Component (Library (NamedLibrary x)) _ <- components]
internal = internalDependencies pkg libraries
internalMixin m
| mixinPackage m `elem` libraries, mixinLibrary m == MainLibrary =
m {mixinPackage = pkg, mixinLibrary = if mixinPackage m == pkg then MainLibrary else NamedLibrary (mixinPackage m)}
| otherwise = m
pure Package
{ packageName = pkg, packageVersion = version, cabalVersion = spec, buildType = bt
, packageFlags = flags
, packageComponents =
[ Component k (mapTree (\bi -> bi {dependencies = internal (dependencies bi), mixins = map internalMixin (mixins bi)}) t)
| Component k t <- components ]
, packageFields = top, packageSourceRepositories = repositories
, packageSetupDependencies = internal <$> setup
}
required :: Text -> Parser a -> Fields -> Result a
required key p fields = do
value <- singular (parseField p) (Map.findWithDefault [] key fields)
maybe (failPackage (T.pack (show key) <> " field missing")) Right value
section :: Version -> State -> Field -> Result State
section _ st Field {} = Right st
section spec st (Section p name args body) = case name of
"common"
| not (specAtLeast [2, 2] spec) -> Right st
| otherwise -> do
key <- sectionName p args
tree <- stanza spec CommonKind commons body
when (Map.member key commons) (failAt p ("Duplicate common stanza: " <> key))
Right st {stateCommons = Map.insert key tree commons}
"library"
| null args -> do
when (any isMainLibrary (stateComponents st))
(failAt p "Multiple main libraries; have you forgotten to specify a name for an internal library?")
component (Library MainLibrary) LibraryKind
| otherwise -> sectionName p args >>= \n -> component (Library (NamedLibrary n)) LibraryKind
"foreign-library" -> sectionName p args >>= \n -> component (ForeignLibrary n) ForeignKind
"executable" -> sectionName p args >>= \n -> component (Executable n) ExecutableKind
"test-suite" -> sectionName p args >>= \n -> component (TestSuite n) TestKind
"benchmark" -> sectionName p args >>= \n -> component (Benchmark n) BenchmarkKind
"flag" -> do
n <- sectionName p args
key <- either (failAt p) Right (runValue flagNameValue n)
fields <- plainFields body
let get k = Map.findWithDefault [] k fields
def <- singular (parseField bool) (get "default")
manual <- singular (parseField bool) (get "manual")
let description = maybe "" (freeText spec . last) (nonEmpty (get "description"))
Right st {stateFlags = stateFlags st ++ [Flag key (fromMaybe True def) (fromMaybe False manual) description]}
"custom-setup" | null args -> do
fields <- plainFields body
deps <- monoidal (commaList spec (dependency spec)) (Map.findWithDefault [] "setup-depends" fields)
Right st {stateSetup = Just deps}
"source-repository" -> case args of
[ArgName q kind] -> do
kind' <- either (failAt q) Right (runValue (takeWhile1P Nothing (\c -> isAlphaNum c || c == '_' || c == '-')) kind)
fields <- plainFields body
Right st {stateRepositories = stateRepositories st ++ [SourceRepository kind' fields]}
[] -> failAt p "'source-repository' requires exactly one argument"
_ -> failAt p "Invalid source-repository kind"
_ -> Right st
where
commons = stateCommons st
component kind grammar = do
tree <- stanza spec grammar commons body
Right st {stateComponents = stateComponents st ++ [Component kind tree]}
isMainLibrary (Component (Library MainLibrary) _) = True
isMainLibrary _ = False
nonEmpty [] = Nothing
nonEmpty xs = Just xs
mapTree :: (BuildInfo -> BuildInfo) -> Conditional BuildInfo -> Conditional BuildInfo
mapTree f (Conditional bi bs) = Conditional (f bi) [Branch c (mapTree f t) (mapTree f <$> e) | Branch c t e <- bs]
-- | Before cabal-version 3.4, the name of an internal library refers to that
-- library of the same package. If the dependency also names other libraries,
-- Cabal-syntax 3.12 keeps the original dependency after the new one.
internalDependencies :: Text -> [Text] -> [Dependency] -> [Dependency]
internalDependencies pkg libraries = concatMap change
where
change d@(Dependency name range libs)
| name `elem` libraries, MainLibrary `elem` libs =
Dependency pkg range (NamedLibrary name :| []) : [d | any (/= MainLibrary) libs]
| otherwise = [d]
checkFlags :: [Text] -> Conditional BuildInfo -> Result ()
checkFlags known tree = mapM_ check (branches tree)
where
check (Branch c t e) = checkCondition c *> checkFlags known t *> mapM_ (checkFlags known) e
checkCondition c = case c of
FlagValue f | f `notElem` known -> failPackage ("These flags are used without having been defined: " <> f)
Not a -> checkCondition a
And a b -> checkCondition a *> checkCondition b
Or a b -> checkCondition a *> checkCondition b
_ -> Right ()
-- | Read a @.buildinfo@ file, as Cabal-syntax reads hooked build information.
parseHookedBuildInfo :: BS.ByteString -> ParseResult HookedBuildInfo
parseHookedBuildInfo bytes = report $ do
fields <- readInput bytes
let (header, rest) = break isExecutable fields
libraryFields <- plainFields header
lib <- if Map.null libraryFields then pure Nothing else Just <$> buildInfoFields latestSpec CommonKind libraryFields
exes <- groups rest
ensureUnique (map fst exes)
pure (HookedBuildInfo lib (Map.fromList exes))
where
isExecutable (Field _ "executable" _) = True
isExecutable _ = False
groups (Field p "executable" ls : rest) = do
exe <- parseField componentName (FieldValue p ls)
let (body, after) = break isExecutable rest
fs <- plainFields body
bi <- buildInfoFields latestSpec CommonKind fs
((exe, bi) :) <$> groups after
groups _ = Right []
ensureUnique names = case [n | (i, n) <- zip [0 :: Int ..] names, n `elem` take i names] of
n : _ -> failPackage ("Duplicate executable: " <> n)
[] -> Right ()