diff --git a/Cabal-syntax/Cabal-syntax.cabal b/Cabal-syntax/Cabal-syntax.cabal index 8a230ba5e2a..871f554c21b 100644 --- a/Cabal-syntax/Cabal-syntax.cabal +++ b/Cabal-syntax/Cabal-syntax.cabal @@ -197,6 +197,7 @@ library Distribution.Utils.String Distribution.Utils.Structured Distribution.Version + Distribution.PackageDescription.ExactPrint Language.Haskell.Extension other-extensions: diff --git a/Cabal-syntax/src/Distribution/Fields/Field.hs b/Cabal-syntax/src/Distribution/Fields/Field.hs index c119ca5f1c0..fae3a949e07 100644 --- a/Cabal-syntax/src/Distribution/Fields/Field.hs +++ b/Cabal-syntax/src/Distribution/Fields/Field.hs @@ -2,6 +2,8 @@ {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE StrictData #-} -- | Cabal-like file AST types: 'Field', 'Section' etc -- @@ -12,11 +14,13 @@ module Distribution.Fields.Field , fieldName , fieldAnn , fieldUniverse + , fieldMeta , FieldLine (..) , fieldLineAnn , fieldLineBS , SectionArg (..) , sectionArgAnn + , sectionArgContent -- * Name , FieldName @@ -28,6 +32,12 @@ module Distribution.Fields.Field -- * Conversions to String , sectionArgsToString , fieldLinesToString + + -- * meta data + , MetaField(..) + , fieldMeta + , metaComment + , metaAnn ) where import Data.ByteString (ByteString) @@ -47,17 +57,41 @@ import qualified Data.Foldable1 as F1 -- | A Cabal-like file consists of a series of fields (@foo: bar@) and sections (@library ...@). data Field ann - = Field !(Name ann) [FieldLine ann] - | Section !(Name ann) [SectionArg ann] [Field ann] + = Field (Name ann) [FieldLine ann] + | Section (Name ann) [SectionArg ann] [Field ann] + | Meta (MetaField ann) + deriving (Eq, Show, Functor, Foldable, Traversable) + +data MetaField ann = MetaComment ann ByteString + | MetaWhitespace ann ByteString deriving (Eq, Show, Functor, Foldable, Traversable) +metaComment :: MetaField ann -> Maybe ByteString +metaComment = \case + (MetaComment _ bs) -> Just bs + (MetaWhitespace _ _) -> Nothing + +metaAnn :: MetaField ann -> ann +metaAnn = \case + (MetaComment ann _) -> ann + (MetaWhitespace ann _) -> ann + -- | Section of field name -fieldName :: Field ann -> Name ann -fieldName (Field n _) = n -fieldName (Section n _ _) = n +fieldName :: Field ann -> (Maybe (Name ann)) +fieldName (Field n _) = Just n +fieldName (Section n _ _) = Just n +fieldName (Meta _) = Nothing + +fieldMeta :: Field ann -> Maybe (MetaField ann) +fieldMeta = \case + (Field n _) -> Nothing + (Section n _ _) -> Nothing + (Meta x) -> Just x fieldAnn :: Field ann -> ann -fieldAnn = nameAnn . fieldName +fieldAnn (Field n _) = nameAnn n +fieldAnn (Section n _ _) = nameAnn n +fieldAnn (Meta x) = metaAnn x -- | All transitive descendants of 'Field', including itself. -- @@ -65,12 +99,13 @@ fieldAnn = nameAnn . fieldName fieldUniverse :: Field ann -> [Field ann] fieldUniverse f@(Section _ _ fs) = f : concatMap fieldUniverse fs fieldUniverse f@(Field _ _) = [f] +fieldUniverse (Meta _) = [] -- | A line of text representing the value of a field from a Cabal file. -- A field may contain multiple lines. -- -- /Invariant:/ 'ByteString' has no newlines. -data FieldLine ann = FieldLine !ann !ByteString +data FieldLine ann = FieldLine ann ByteString deriving (Eq, Show, Functor, Foldable, Traversable) -- | @since 3.0.0.0 @@ -84,11 +119,11 @@ fieldLineBS (FieldLine _ bs) = bs -- | Section arguments, e.g. name of the library data SectionArg ann = -- | identifier, or something which looks like number. Also many dot numbers, i.e. "7.6.3" - SecArgName !ann !ByteString + SecArgName ann ByteString | -- | quoted string - SecArgStr !ann !ByteString + SecArgStr ann ByteString | -- | everything else, mm. operators (e.g. in if-section conditionals) - SecArgOther !ann !ByteString + SecArgOther ann ByteString deriving (Eq, Show, Functor, Foldable, Traversable) -- | Extract annotation from 'SectionArg'. @@ -97,6 +132,12 @@ sectionArgAnn (SecArgName ann _) = ann sectionArgAnn (SecArgStr ann _) = ann sectionArgAnn (SecArgOther ann _) = ann +sectionArgContent :: SectionArg ann -> ByteString +sectionArgContent = \case + SecArgName _ann bs -> bs + SecArgStr _ann bs -> bs + SecArgOther _ann bs -> bs + ------------------------------------------------------------------------------- -- Name ------------------------------------------------------------------------------- @@ -106,7 +147,7 @@ type FieldName = ByteString -- | A field name. -- -- /Invariant/: 'ByteString' is lower-case ASCII. -data Name ann = Name !ann !FieldName +data Name ann = Name ann FieldName deriving (Eq, Show, Functor, Foldable, Traversable) mkName :: ann -> FieldName -> Name ann @@ -158,6 +199,7 @@ instance F1.Foldable1 Field where F1.fold1 (F1.foldMap1 f x :| map (F1.foldMap1 f) ys) foldMap1 f (Section x ys zs) = F1.fold1 (F1.foldMap1 f x :| map (F1.foldMap1 f) ys ++ map (F1.foldMap1 f) zs) + foldMap1 f (Meta x) = f $ metaAnn x -- | @since 3.12.0.0 instance F1.Foldable1 FieldLine where diff --git a/Cabal-syntax/src/Distribution/Fields/Lexer.x b/Cabal-syntax/src/Distribution/Fields/Lexer.x index 4fc501d5186..e2c586e1111 100644 --- a/Cabal-syntax/src/Distribution/Fields/Lexer.x +++ b/Cabal-syntax/src/Distribution/Fields/Lexer.x @@ -15,7 +15,7 @@ #endif {-# OPTIONS_GHC -fno-warn-unused-imports #-} module Distribution.Fields.Lexer - (ltest, lexToken, Token(..), LToken(..) + (ltest, lexString, lexByteString, lexToken, Token(..), LToken(..) ,bol_section, in_section, in_field_layout, in_field_braces ,mkLexState) where @@ -82,85 +82,102 @@ tokens :- } { - @nbspspacetab* @nl { \pos len inp -> checkWhitespace pos len inp >> adjustPos retPos >> lexToken } - -- no @nl here to allow for comments on last line of the file with no trailing \n - $spacetab* "--" $comment* ; -- TODO: check the lack of @nl works here - -- including counting line numbers + @nbspspacetab* @nl { \pos len inp -> do + _ <- checkWhitespace pos len inp + adjustPos retPos + toki Whitespace pos len inp } + -- FIXME: no @nl here to allow for comments on last line of the file with no trailing \n + -- FIXME: TODO: check the lack of @nl works here including counting line numbers + $spacetab* "--" $comment* { toki Comment } } { - @nbspspacetab* { \pos len inp -> checkLeadingWhitespace pos len inp >>= \len' -> - -- len' is character whitespace length (counting nbsp as one) - if B.length inp == len - then return (L pos EOF) - else do - -- Small hack: if char and byte length mismatch - -- subtract the difference, so lexToken will count position correctly. - -- Proper (and slower) fix is to count utf8 length in lexToken - when (len' /= len) $ adjustPos (incPos (len' - len)) - setStartCode in_section - return (L pos (Indent len')) } + @nbspspacetab* { \pos len inp -> do + len' <- checkLeadingWhitespace pos len inp + -- len' is character whitespace length (counting nbsp as one) + if B.length inp == len + then return (L pos EOF) + else do + -- Small hack: if char and byte length mismatch + -- subtract the difference, so lexToken will count position correctly. + -- Proper (and slower) fix is to count utf8 length in lexToken + when (len' /= len) $ adjustPos (incPos (len' - len)) + setStartCode in_section + return (L pos (Indent len')) } $spacetab* \{ { tok OpenBrace } $spacetab* \} { tok CloseBrace } } { - $spacetab+ ; --TODO: don't allow tab as leading space - - "--" $comment* ; - - @name { toki TokSym } - @string { \pos len inp -> return $! L pos (TokStr (B.take (len - 2) (B.tail inp))) } - @oplike { toki TokOther } - $paren { toki TokOther } - \: { tok Colon } - \{ { tok OpenBrace } - \} { tok CloseBrace } - @nl { \_ _ _ -> adjustPos retPos >> setStartCode bol_section >> lexToken } + --TODO: don't allow tab as leading space + $spacetab+ { toki Whitespace } + + "--" $comment* { toki Comment } + + @name { toki TokSym } + @string { \pos len inp -> return $! L pos (TokStr (B.take (len - 2) (B.tail inp))) } + @oplike { toki TokOther } + $paren { toki TokOther } + \: { tok Colon } + \{ { tok OpenBrace } + \} { tok CloseBrace } + @nl { \pos len inp -> do + adjustPos retPos + setStartCode bol_section + lexToken } } { - @nbspspacetab* { \pos len inp -> checkLeadingWhitespace pos len inp >>= \len' -> - if B.length inp == len - then return (L pos EOF) - else do - -- Small hack: if char and byte length mismatch - -- subtract the difference, so lexToken will count position correctly. - -- Proper (and slower) fix is to count utf8 length in lexToken - when (len' /= len) $ adjustPos (incPos (len' - len)) - setStartCode in_field_layout - return (L pos (Indent len')) } + @nbspspacetab* { \pos len inp -> do + len' <- checkLeadingWhitespace pos len inp + if B.length inp == len + then return (L pos EOF) + else do + -- Small hack: if char and byte length mismatch + -- subtract the difference, so lexToken will count position correctly. + -- Proper (and slower) fix is to count utf8 length in lexToken + when (len' /= len) $ adjustPos (incPos (len' - len)) + setStartCode in_field_layout + return (L pos (Indent len')) } } { - $spacetab+; - $field_layout' $field_layout* { toki TokFieldLine } - @nl { \_ _ _ -> adjustPos retPos >> setStartCode bol_field_layout >> lexToken } + $spacetab+ { toki Whitespace } + $field_layout' $field_layout* { toki TokFieldLine } + @nl { \pos len inp -> do + adjustPos retPos + setStartCode bol_field_layout + lexToken } } { - () { \_ _ _ -> setStartCode in_field_braces >> lexToken } + () { \_ _ _ -> setStartCode in_field_braces >> lexToken } } { - $spacetab+; + $spacetab+ { toki Whitespace } $field_braces' $field_braces* { toki TokFieldLine } - \{ { tok OpenBrace } - \} { tok CloseBrace } - @nl { \_ _ _ -> adjustPos retPos >> setStartCode bol_field_braces >> lexToken } + \{ { tok OpenBrace } + \} { tok CloseBrace } + @nl { \pos len inp -> do + adjustPos retPos + setStartCode bol_field_braces + lexToken } } { -- | Tokens of outer cabal file structure. Field values are treated opaquely. -data Token = TokSym !ByteString -- ^ Haskell-like identifier, number or operator - | TokStr !ByteString -- ^ String in quotes - | TokOther !ByteString -- ^ Operators and parens - | Indent !Int -- ^ Indentation token +data Token = TokSym !ByteString -- ^ Haskell-like identifier, number or operator + | TokStr !ByteString -- ^ String in quotes + | TokOther !ByteString -- ^ Operators and parens + | Indent !Int -- ^ Indentation token | TokFieldLine !ByteString -- ^ Lines after @:@ | Colon | OpenBrace | CloseBrace + | Whitespace !ByteString + | Comment !ByteString | EOF | LexicalError InputStream --TODO: add separate string lexical error deriving Show @@ -230,7 +247,6 @@ lexToken = do setInput inp' let !len_bytes = B.length inp - B.length inp' t <- action pos len_bytes inp - --traceShow t $ return tok return t @@ -259,11 +275,29 @@ lexAll = do _ -> do ts <- lexAll return (t : ts) +-- FIXME: for debugging +lexAll' :: Lex [(Int, LToken)] +lexAll' = do + t <- lexToken + c <- getStartCode + case t of + L _ EOF -> return [(c, t)] + _ -> do ts <- lexAll' + return ((c, t) : ts) + ltest :: Int -> String -> Prelude.IO () ltest code s = let (ws, xs) = execLexer (setStartCode code >> lexAll) (B.Char8.pack s) in traverse_ print ws >> traverse_ print xs +lexString :: String -> ([LexWarning], [LToken]) +lexString = execLexer lexAll . B.Char8.pack + +lexByteString :: ByteString -> ([LexWarning], [LToken]) +lexByteString = execLexer lexAll + +lexByteString' :: ByteString -> ([LexWarning], [(Int, LToken)]) +lexByteString' = execLexer lexAll' mkLexState :: ByteString -> LexState mkLexState input = LexState diff --git a/Cabal-syntax/src/Distribution/Fields/Parser.hs b/Cabal-syntax/src/Distribution/Fields/Parser.hs index e018caa7fe0..37671909dc5 100644 --- a/Cabal-syntax/src/Distribution/Fields/Parser.hs +++ b/Cabal-syntax/src/Distribution/Fields/Parser.hs @@ -1,7 +1,9 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE PatternGuards #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GeneralisedNewtypeDeriving #-} ----------------------------------------------------------------------------- @@ -35,6 +37,9 @@ module Distribution.Fields.Parser import qualified Data.ByteString.Char8 as B8 import Data.Functor.Identity +import qualified Data.Text as T +import qualified Data.Text.Encoding as T +import qualified Data.Text.Encoding.Error as T import Distribution.Compat.Prelude import Distribution.Fields.Field import Distribution.Fields.Lexer @@ -77,6 +82,10 @@ instance Stream LexState' Identity LToken where uncons (LexState' _ (tok, st')) = case tok of L _ EOF -> return Nothing + -- FIXME: DEBUG: uncomment these lines to skip new tokens and restore old lexer behaviour + -- L _ (Whitespace _) -> uncons st' + -- L _ (Comment _) -> uncons st' + -- FIXME: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ _ -> return (Just (tok, st')) -- | Get lexer warnings accumulated so far @@ -100,7 +109,7 @@ getToken :: (Token -> Maybe a) -> Parser a getToken getTok = getTokenWithPos (\(L _ t) -> getTok t) getTokenWithPos :: (LToken -> Maybe a) -> Parser a -getTokenWithPos getTok = tokenPrim (\(L _ t) -> describeToken t) updatePos getTok +getTokenWithPos = tokenPrim (\(L _ t) -> describeToken t) updatePos where updatePos :: SourcePos -> LToken -> LexState' -> SourcePos updatePos pos (L (Position col line) _) _ = newPos (sourceName pos) col line @@ -115,43 +124,67 @@ describeToken t = case t of Colon -> "\":\"" OpenBrace -> "\"{\"" CloseBrace -> "\"}\"" - -- SemiColon -> "\";\"" + Whitespace s -> "whitespace " ++ show s + Comment s -> "comment " ++ show s EOF -> "end of file" LexicalError is -> "character in input " ++ show (B8.head is) tokSym :: Parser (Name Position) -tokSym', tokStr, tokOther :: Parser (SectionArg Position) +tokSym = getTokenWithPos (\t -> case t of L pos (TokSym x) -> Just (mkName pos x); _ -> Nothing) + +tokSym' :: Parser (SectionArg Position) +tokSym' = getTokenWithPos (\t -> case t of L pos (TokSym x) -> Just (SecArgName pos x); _ -> Nothing) + +tokStr :: Parser (SectionArg Position) +tokStr = getTokenWithPos (\t -> case t of L pos (TokStr x) -> Just (SecArgStr pos x); _ -> Nothing) + +tokOther :: Parser (SectionArg Position) +tokOther = getTokenWithPos (\t -> case t of L pos (TokOther x) -> Just (SecArgOther pos x); _ -> Nothing) + tokIndent :: Parser Int -tokColon, tokCloseBrace :: Parser () +tokIndent = getToken (\t -> case t of Indent x -> Just x; _ -> Nothing) + +tokColon :: Parser () +tokColon = getToken (\t -> case t of Colon -> Just (); _ -> Nothing) + tokOpenBrace :: Parser Position -tokFieldLine :: Parser (FieldLine Position) -tokSym = getTokenWithPos $ \t -> case t of L pos (TokSym x) -> Just (mkName pos x); _ -> Nothing -tokSym' = getTokenWithPos $ \t -> case t of L pos (TokSym x) -> Just (SecArgName pos x); _ -> Nothing -tokStr = getTokenWithPos $ \t -> case t of L pos (TokStr x) -> Just (SecArgStr pos x); _ -> Nothing -tokOther = getTokenWithPos $ \t -> case t of L pos (TokOther x) -> Just (SecArgOther pos x); _ -> Nothing -tokIndent = getToken $ \t -> case t of Indent x -> Just x; _ -> Nothing -tokColon = getToken $ \t -> case t of Colon -> Just (); _ -> Nothing tokOpenBrace = getTokenWithPos $ \t -> case t of L pos OpenBrace -> Just pos; _ -> Nothing -tokCloseBrace = getToken $ \t -> case t of CloseBrace -> Just (); _ -> Nothing -tokFieldLine = getTokenWithPos $ \t -> case t of L pos (TokFieldLine s) -> Just (FieldLine pos s); _ -> Nothing -colon, openBrace, closeBrace :: Parser () +tokCloseBrace :: Parser () +tokCloseBrace = getToken (\t -> case t of CloseBrace -> Just (); _ -> Nothing) + +tokFieldLine :: Parser (FieldLine Position) +tokFieldLine = getTokenWithPos (\t -> case t of L pos (TokFieldLine s) -> Just (FieldLine pos s); _ -> Nothing) + +tokComment :: Parser (MetaField Position) +tokComment = getTokenWithPos (\case L pos (Comment s) -> Just (MetaComment pos s); _ -> Nothing) + +tokWhitespace :: Parser (MetaField Position) +tokWhitespace = getTokenWithPos (\case L pos (Whitespace s) -> Just (MetaWhitespace pos s); _ -> Nothing) + sectionArg :: Parser (SectionArg Position) -sectionArg = tokSym' <|> tokStr <|> tokOther "section parameter" +sectionArg = trace "sectionArg" (tokSym' <|> tokStr <|> tokOther "section parameter") fieldSecName :: Parser (Name Position) fieldSecName = tokSym "field or section name" +colon :: Parser () colon = tokColon "\":\"" + +openBrace :: Parser () openBrace = do pos <- tokOpenBrace "\"{\"" addLexerWarning (LexWarning LexBraces pos) + +closeBrace :: Parser () closeBrace = tokCloseBrace "\"}\"" fieldContent :: Parser (FieldLine Position) -fieldContent = tokFieldLine "field contents" +fieldContent = (tokFieldLine) "field contents" + newtype IndentLevel = IndentLevel Int + deriving newtype Show zeroIndentLevel :: IndentLevel zeroIndentLevel = IndentLevel 0 @@ -227,17 +260,26 @@ inLexerMode (LexerMode mode) p = -- Top level of a file using cabal syntax -- cabalStyleFile :: Parser [Field Position] -cabalStyleFile = do +cabalStyleFile = parserTraced "cabalStyleFile" $ do + comments <- many (tokComment <|> tokWhitespace) es <- elements zeroIndentLevel eof - return es + return $ (Meta <$> comments) <> es -- Elements that live at the top level or inside a section, i.e. fields -- and sections content -- -- elements ::= element* elements :: IndentLevel -> Parser [Field Position] -elements ilevel = many (element ilevel) +elements ilevel = do + res <- many $ do + element <- element ilevel + after <- fmap Meta <$> many (parserTraced "whitespaces" $ tokWhitespace <|> tokComment) + pure (element, after) + pure $ concat $ flatten <$> res + +flatten :: (Field Position, [Field Position]) -> [Field Position] +flatten (y, mz) = y : mz -- An individual element, ie a field or a section. These can either use -- layout style or braces style. For layout style then it must start on @@ -247,15 +289,14 @@ elements ilevel = many (element ilevel) -- | name elementInNonLayoutContext element :: IndentLevel -> Parser (Field Position) element ilevel = - ( do + choice [(trace "layout element" $ do ilevel' <- indentOfAtLeast ilevel name <- fieldSecName elementInLayoutContext (incIndentLevel ilevel') name - ) - <|> ( do + ), ( trace "non-layout element" $ do name <- fieldSecName elementInNonLayoutContext name - ) + )] -- An element (field or section) that is valid in a layout context. -- In a layout context we can have fields and sections that themselves @@ -264,13 +305,19 @@ element ilevel = -- elementInLayoutContext ::= ':' fieldLayoutOrBraces -- | arg* sectionLayoutOrBraces elementInLayoutContext :: IndentLevel -> Name Position -> Parser (Field Position) -elementInLayoutContext ilevel name = - (do colon; fieldLayoutOrBraces ilevel name) - <|> ( do - args <- many sectionArg +elementInLayoutContext ilevel name = trace ("layoutcontext " <> show (getName name)) $ do + result <- choice [(trace "colon" $ do + colon + () <$ many (tokWhitespace <|> tokComment) + fieldLayoutOrBraces ilevel name) + , (trace "section" $ do + () <$ many (tokWhitespace <|> tokComment) + args <- trace "args" $ many (many tokWhitespace *> sectionArg <* many tokWhitespace) + () <$ trace "comments" (many (tokWhitespace <|> tokComment)) elems <- sectionLayoutOrBraces ilevel return (Section name args elems) - ) + )] + result <$ many tokWhitespace -- An element (field or section) that is valid in a non-layout context. -- In a non-layout context we can have only have fields and sections that @@ -279,7 +326,8 @@ elementInLayoutContext ilevel name = -- elementInNonLayoutContext ::= ':' FieldInlineOrBraces -- | arg* '\\n'? '{' elements '\\n'? '}' elementInNonLayoutContext :: Name Position -> Parser (Field Position) -elementInNonLayoutContext name = +elementInNonLayoutContext name = trace "non-layoutcontext" $ do + skipMany tokWhitespace (do colon; fieldInlineOrBraces name) <|> ( do args <- many sectionArg @@ -295,14 +343,19 @@ elementInNonLayoutContext name = -- fieldLayoutOrBraces ::= '\\n'? '{' content '}' -- | line? ('\\n' line)* fieldLayoutOrBraces :: IndentLevel -> Name Position -> Parser (Field Position) -fieldLayoutOrBraces ilevel name = braces <|> fieldLayout +fieldLayoutOrBraces ilevel name = trace "fieldLayoutOrBraces" $ do + () <$ many tokWhitespace + braces <|> fieldLayout where braces = do openBrace + () <$ many tokWhitespace ls <- inLexerMode (LexerMode in_field_braces) (many fieldContent) + () <$ many tokWhitespace closeBrace return (Field name ls) - fieldLayout = inLexerMode (LexerMode in_field_layout) $ do + fieldLayout = inLexerMode (LexerMode in_field_layout) $ trace "fieldLayout" $ do + () <$ many tokWhitespace l <- optionMaybe fieldContent ls <- many (do _ <- indentOfAtLeast ilevel; fieldContent) return $ case l of @@ -315,27 +368,29 @@ fieldLayoutOrBraces ilevel name = braces <|> fieldLayout -- | elements sectionLayoutOrBraces :: IndentLevel -> Parser [Field Position] sectionLayoutOrBraces ilevel = - ( do + (trace "braces" $ do openBrace + void $ many (tokWhitespace <|> tokComment) elems <- elements zeroIndentLevel optional tokIndent closeBrace return elems - ) - <|> (elements ilevel) + ) + <|> (trace "elements" $ elements ilevel) -- TODO this used to be ilevel ?? -- The body of a field, using either inline style or braces. -- -- fieldInlineOrBraces ::= '\\n'? '{' content '}' -- | content fieldInlineOrBraces :: Name Position -> Parser (Field Position) -fieldInlineOrBraces name = +fieldInlineOrBraces name = do + skipMany tokWhitespace ( do openBrace ls <- inLexerMode (LexerMode in_field_braces) (many fieldContent) closeBrace return (Field name ls) - ) + ) <|> ( do ls <- inLexerMode (LexerMode in_field_braces) (option [] (fmap (\l -> [l]) fieldContent)) return (Field name ls) @@ -394,12 +449,14 @@ checkIndentation :: [Field Position] -> [LexWarning] -> [LexWarning] checkIndentation [] = id checkIndentation (Field name _ : fs') = checkIndentation' (nameAnn name) fs' checkIndentation (Section name _ fs : fs') = checkIndentation fs . checkIndentation' (nameAnn name) fs' +checkIndentation (Meta x : fs' ) = checkIndentation' (metaAnn x) fs' -- | We compare adjacent fields to reduce the amount of reported indentation warnings. checkIndentation' :: Position -> [Field Position] -> [LexWarning] -> [LexWarning] checkIndentation' _ [] = id checkIndentation' pos (Field name _ : fs') = checkIndentation'' pos (nameAnn name) . checkIndentation' (nameAnn name) fs' checkIndentation' pos (Section name _ fs : fs') = checkIndentation'' pos (nameAnn name) . checkIndentation fs . checkIndentation' (nameAnn name) fs' +checkIndentation' pos (Meta x : fs' ) = checkIndentation'' pos (metaAnn x) . checkIndentation' (metaAnn x) fs' -- | Check that positions' columns are the same. checkIndentation'' :: Position -> Position -> [LexWarning] -> [LexWarning] @@ -410,47 +467,54 @@ checkIndentation'' a b #ifdef CABAL_PARSEC_DEBUG parseTest' :: Show a => Parsec LexState' () a -> SourceName -> B8.ByteString -> IO () parseTest' p fname s = - case parse p fname (lexSt s) of - Left err -> putStrLn (formatError s err) - - Right x -> print x + case parse p fname (lexSt s) of + Left err -> putStrLn (formatError s err) + Right x -> print x where lexSt = mkLexState' . mkLexState parseFile :: Show a => Parser a -> FilePath -> IO () parseFile p f = B8.readFile f >>= \s -> parseTest' p f s -parseStr :: Show a => Parser a -> String -> IO () +parseStr :: Show a => Parser a -> String -> IO () parseStr p = parseBS p . B8.pack -parseBS :: Show a => Parser a -> B8.ByteString -> IO () +parseBS :: Show a => Parser a -> B8.ByteString -> IO () parseBS p = parseTest' p "" formatError :: B8.ByteString -> ParseError -> String formatError input perr = - unlines - [ "Parse error "++ show (errorPos perr) ++ ":" - , errLine - , indicator ++ errmsg ] + unlines + [ "Parse error " ++ show (errorPos perr) ++ ":" + , errLine + , indicator ++ errmsg + ] where - pos = errorPos perr - ls = lines' (T.decodeUtf8With T.lenientDecode input) - errLine = T.unpack (ls !! (sourceLine pos - 1)) + pos = errorPos perr + ls = lines' (T.decodeUtf8With T.lenientDecode input) + errLine = T.unpack (ls !! (sourceLine pos - 1)) indicator = replicate (sourceColumn pos) ' ' ++ "^" - errmsg = showErrorMessages "or" "unknown parse error" - "expecting" "unexpected" "end of file" - (errorMessages perr) + errmsg = + showErrorMessages + "or" + "unknown parse error" + "expecting" + "unexpected" + "end of file" + (errorMessages perr) -- | Handles windows/osx/unix line breaks uniformly lines' :: T.Text -> [T.Text] lines' s1 | T.null s1 = [] | otherwise = case T.break (\c -> c == '\r' || c == '\n') s1 of - (l, s2) | Just (c,s3) <- T.uncons s2 - -> case T.uncons s3 of - Just ('\n', s4) | c == '\r' -> l : lines' s4 - _ -> l : lines' s3 - | otherwise -> [l] + (l, s2) + | Just (c, s3) <- T.uncons s2 -> + case T.uncons s3 of + Just ('\n', s4) | c == '\r' -> l : lines' s4 + _ -> l : lines' s3 + | otherwise -> [l] + #endif eof :: Parser () diff --git a/Cabal-syntax/src/Distribution/Fields/Pretty.hs b/Cabal-syntax/src/Distribution/Fields/Pretty.hs index 58f54d2848c..40152c19778 100644 --- a/Cabal-syntax/src/Distribution/Fields/Pretty.hs +++ b/Cabal-syntax/src/Distribution/Fields/Pretty.hs @@ -15,6 +15,7 @@ module Distribution.Fields.Pretty , PrettyField (..) , showFields , showFields' + , prettyFieldAnn -- * Transformation from 'P.Field' , fromParsecFields @@ -47,6 +48,13 @@ data PrettyField ann | PrettyEmpty deriving (Functor, Foldable, Traversable) + +prettyFieldAnn :: PrettyField ann -> Maybe ann +prettyFieldAnn = \case + PrettyField ann _ _ -> Just ann + PrettySection ann _ _ _ -> Just ann + PrettyEmpty -> Nothing + -- | Prettyprint a list of fields. -- -- Note: the first argument should return 'String's without newlines diff --git a/Cabal-syntax/src/Distribution/PackageDescription/Configuration.hs b/Cabal-syntax/src/Distribution/PackageDescription/Configuration.hs index 9a9ba2d7500..978106a09e8 100644 --- a/Cabal-syntax/src/Distribution/PackageDescription/Configuration.hs +++ b/Cabal-syntax/src/Distribution/PackageDescription/Configuration.hs @@ -476,7 +476,7 @@ finalizePD (Platform arch os) impl constraints - (GenericPackageDescription pkg _ver flags mb_lib0 sub_libs0 flibs0 exes0 tests0 bms0) = do + (GenericPackageDescription pkg _ver flags mb_lib0 sub_libs0 flibs0 exes0 tests0 bms0 _exactPrintMeta) = do (targetSet, flagVals) <- resolveWithFlags flagChoices enabled os arch impl constraints condTrees check let @@ -556,7 +556,7 @@ resolveWithFlags [] Distribution.System.Linux Distribution.System.I386 (Distribu -- function. flattenPackageDescription :: GenericPackageDescription -> PackageDescription flattenPackageDescription - (GenericPackageDescription pkg _ _ mlib0 sub_libs0 flibs0 exes0 tests0 bms0) = + (GenericPackageDescription pkg _ _ mlib0 sub_libs0 flibs0 exes0 tests0 bms0 _exactPrintMeta) = pkg { library = mlib , subLibraries = reverse sub_libs diff --git a/Cabal-syntax/src/Distribution/PackageDescription/ExactPrint.hs b/Cabal-syntax/src/Distribution/PackageDescription/ExactPrint.hs new file mode 100644 index 00000000000..55b75d56ffe --- /dev/null +++ b/Cabal-syntax/src/Distribution/PackageDescription/ExactPrint.hs @@ -0,0 +1,208 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} + +-- I suppose this is currently more of an exact-ish print +-- anything that makes it warn for example is neglected. +module Distribution.PackageDescription.ExactPrint + ( exactPrint, + ) +where + +import Control.Monad (join) +import Data.ByteString (ByteString) +import Data.Foldable (fold) +import Data.List (sortOn) +import Data.Map (Map) +import qualified Data.Map as Map +import Data.Text (Text, pack, unpack) +import Data.Text.Encoding (encodeUtf8) +import qualified Data.Text.Encoding as Text +import Distribution.Fields.Field (FieldName) +import Distribution.Fields.Pretty +import Distribution.PackageDescription (specVersion) +import Distribution.PackageDescription.PrettyPrint +import Distribution.Parsec.Position +import Distribution.Types.GenericPackageDescription +import Text.PrettyPrint (Doc, ($$), ($+$), (<+>)) +import qualified Text.PrettyPrint as PP +import qualified Data.Text as Text + +exactPrint :: GenericPackageDescription -> Text +exactPrint package = foldExactly (exactPrintMeta package) fields + where + fields :: [PrettyField ()] + fields = ppGenericPackageDescription (specVersion (packageDescription (package))) package + +data ExactMetaField = ExactMetaField {position :: Position, text :: Text} + deriving Show + +-- | an exact node is either some existing cabal field, or other stuff like whitespace or comments +data ExactNode + = ExactPretty (PrettyField (Maybe ExactPosition)) + | ExactMeta ExactMetaField + +commentsToMeta :: ExactPrintMeta -> [ExactMetaField] +commentsToMeta package = uncurry ExactMetaField <$> Map.toList (exactComments package) + +foldExactly :: ExactPrintMeta -> [PrettyField ()] -> Text +foldExactly meta' pretty = + pack $ PP.render $ currentDoc $ renderLines emptyState positioned + where + positioned :: [ExactNode] + positioned = + sortFields $ + (ExactMeta <$> commentsToMeta meta') <> + (fmap ExactPretty $ + attachPositions [] (exactPositions meta') pretty) + +data RenderState = MkRenderState + { currentPosition :: Position, + currentDoc :: Doc + } + +emptyState :: RenderState +emptyState = + MkRenderState + { currentPosition = Position 1 1, + currentDoc = mempty + } + +renderLines :: + RenderState -> + -- | assuming the lines are sorted on exact position + [ExactNode] -> + RenderState +renderLines state' fields = + foldr renderLine state' fields + +renderLine :: ExactNode -> RenderState -> RenderState +renderLine field previous = + case field of + (ExactPretty prettyField) -> renderPrettyLine prettyField previous + (ExactMeta ExactMetaField{..}) -> + let + currentPos = currentPosition previous + Position rows columns = position `difference` currentPos + + ppDocs = PP.text (Text.unpack text) + + out = spaceOutput rows $ PP.nest columns ppDocs + + docLines :: Int + docLines = (length $ lines $ PP.render ppDocs) + + newPosition = retManyPos (docLines + 1) $ currentPosition previous + in + MkRenderState + { currentDoc = currentDoc previous $$ out, + currentPosition = newPosition + } + + +renderPrettyLine :: PrettyField (Maybe ExactPosition) -> RenderState -> RenderState +renderPrettyLine field (previous@MkRenderState {..}) = case field of + PrettyField mAnn name' doc -> + let newPosition = retManyPos docLines $ case mAnn of + Just position -> (namePosition position) + Nothing -> currentPosition + + docLines :: Int + docLines = (length $ lines $ PP.render doc) + in MkRenderState + { currentDoc = currentDoc $$ renderWithPositionAdjustment mAnn currentPosition ((decodeFieldname name') <> ":") [doc], + currentPosition = newPosition + } + PrettySection mAnn name' ppDocs sectionFields -> + let newPosition = retManyPos docLines $ case mAnn of + Just position -> (namePosition position) + Nothing -> currentPosition + + docLines :: Int + docLines = (length $ lines $ PP.render $ fold ppDocs) + + result = + MkRenderState + { currentDoc = currentDoc $$ renderWithPositionAdjustment mAnn currentPosition (decodeFieldname name') ppDocs, + currentPosition = newPosition + } + in renderLines result $ sortFields $ fmap ExactPretty $ sectionFields + PrettyEmpty -> previous + +decodeFieldname :: FieldName -> String +decodeFieldname = unpack . Text.decodeUtf8 + +spaceOutput :: Int -> Doc -> Doc +spaceOutput rows output = + if rows < 0 + then -- this is a failure mode + -- error ("unexpected empty negative rows" <> show (rows)) + output + else -- <+> "--" <+> PP.text (show (("rows=", rows, "columns=", columns), mAnn, ("current=", current), docLines )) -- DEBUG + + let spacing :: Doc + spacing = foldr ($+$) mempty ("" <$ [1 .. rows]) + in spacing $$ output + +renderWithPositionAdjustment :: (Maybe ExactPosition) -> Position -> String -> [Doc] -> Doc +renderWithPositionAdjustment mAnn current fieldName doc = + spaceOutput rows output + where + output :: Doc + output = + ( PP.nest + columns + (PP.text fieldName) + <> ((PP.hsep ("" <$ [1 .. offset])) <> fold doc) + ) + + res@(Position rows columns) = case mAnn of + Just position -> (namePosition position) `difference` current + Nothing -> zeroPos + + arguments :: [Position] + arguments = foldMap argumentPosition mAnn + + offset :: Int + offset = + ( case arguments of + ((Position _ cols) : _) -> cols + [] -> 0 + ) + - length fieldName + - columns + +-- pp randomly changes ordering, this undoes that +sortFields :: [ExactNode] -> [ExactNode] +sortFields = reverse . sortOn (exactFieldPosition) + +exactFieldPosition :: ExactNode -> Maybe ExactPosition +exactFieldPosition = \case + (ExactPretty pretty) -> join $ prettyFieldAnn pretty + (ExactMeta meta) -> Just (ExactPosition {namePosition = position meta, argumentPosition = []}) + +-- . + +attachPositions :: [NameSpace] -> Map [NameSpace] ExactPosition -> [PrettyField ()] -> [PrettyField (Maybe ExactPosition)] +attachPositions previous positionLookup = map (annotatePositions previous positionLookup) + +annotatePositions :: [NameSpace] -> Map [NameSpace] ExactPosition -> PrettyField () -> PrettyField (Maybe ExactPosition) +annotatePositions previous positionLookup field' = case field' of + PrettyField _ann name' doc -> + PrettyField (Map.lookup nameSpace positionLookup) name' doc + PrettySection _ann name' ppDoc sectionFields -> + PrettySection (Map.lookup nameSpace positionLookup) name' ppDoc (attachPositions nameSpace positionLookup sectionFields) + PrettyEmpty -> PrettyEmpty + where + nameSpace = previous <> toNameSpace field' + +toNameSpace :: PrettyField () -> [NameSpace] +toNameSpace = \case + PrettyField _ann name' doc -> + [NameSpace {nameSpaceName = name', nameSpaceSectionArgs = []}] + PrettySection _ann name' ppDoc sectionFields -> + [NameSpace {nameSpaceName = name', nameSpaceSectionArgs = fmap docToBs ppDoc}] + PrettyEmpty -> [] + +docToBs :: Doc -> ByteString +docToBs = encodeUtf8 . pack . PP.render -- I guess we just hope this is the same diff --git a/Cabal-syntax/src/Distribution/PackageDescription/Parsec.hs b/Cabal-syntax/src/Distribution/PackageDescription/Parsec.hs index bee6965c127..5c408263e6a 100644 --- a/Cabal-syntax/src/Distribution/PackageDescription/Parsec.hs +++ b/Cabal-syntax/src/Distribution/PackageDescription/Parsec.hs @@ -3,6 +3,8 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE TupleSections #-} ----------------------------------------------------------------------------- @@ -41,7 +43,7 @@ import Distribution.Compat.Lens import Distribution.FieldGrammar import Distribution.FieldGrammar.Parsec (NamelessField (..)) import Distribution.Fields.ConfVar (parseConditionConfVar) -import Distribution.Fields.Field (FieldName, getName) +import Distribution.Fields.Field (metaAnn, metaComment, fieldMeta, MetaField, FieldName, getName, fieldLineAnn, sectionArgAnn, nameAnn, sectionArgContent) import Distribution.Fields.LexerMonad (LexWarning, toPWarnings) import Distribution.Fields.ParseResult import Distribution.Fields.Parser @@ -70,6 +72,9 @@ import qualified Distribution.Types.GenericPackageDescription.Lens as L import qualified Distribution.Types.PackageDescription.Lens as L import qualified Distribution.Types.SetupBuildInfo.Lens as L import qualified Text.Parsec as P +import Data.Text(Text) +import Data.ByteString(ByteString) +import Data.Text.Encoding(decodeUtf8) ------------------------------------------------------------------------------ @@ -92,11 +97,11 @@ parseGenericPackageDescription bs = do _ -> pure Nothing case readFields' bs'' of - Right (fs, lexWarnings) -> do + Right (fields, lexWarnings) -> do when patched $ parseWarning zeroPos PWTQuirkyCabalFile "Legacy cabal file" -- UTF8 is validated in a prepass step, afterwards parsing is lenient. - parseGenericPackageDescription' csv lexWarnings invalidUtf8 fs + parseGenericPackageDescription' csv lexWarnings invalidUtf8 fields -- TODO: better marshalling of errors Left perr -> parseFatalFailure pos (show perr) where @@ -127,7 +132,7 @@ type SectionParser = StateT SectionS ParseResult -- | State of section parser data SectionS = SectionS { _stateGpd :: !GenericPackageDescription - , _stateCommonStanzas :: !(Map String CondTreeBuildInfo) + , _stateCommonStanzas :: !(Map String CondTreeBuildInfo) -- here the stanzas get *not* put in genericPackageDescription } stateGpd :: Lens' SectionS GenericPackageDescription @@ -138,6 +143,20 @@ stateCommonStanzas :: Lens' SectionS (Map String CondTreeBuildInfo) stateCommonStanzas f (SectionS gpd cs) = SectionS gpd <$> f cs {-# INLINE stateCommonStanzas #-} +justComments :: MetaField ann -> Maybe (ann, ByteString) +justComments field = + (metaAnn field,) <$> metaComment field + +commentMap :: [Field Position] -> Map Position Text +commentMap fields = + decodeUtf8 <$> Map.fromList listOfFieldPositiosn + where + listOfFieldPositiosn :: [(Position, ByteString)] + listOfFieldPositiosn = catMaybes $ justComments <$> listOfMetaFields + + listOfMetaFields :: [(MetaField Position)] + listOfMetaFields = catMaybes $ fieldMeta <$> fields + -- Note [Accumulating parser] -- -- This parser has two "states": @@ -151,11 +170,11 @@ parseGenericPackageDescription' -> Maybe Int -> [Field Position] -> ParseResult GenericPackageDescription -parseGenericPackageDescription' scannedVer lexWarnings utf8WarnPos fs = do +parseGenericPackageDescription' scannedVer lexWarnings utf8WarnPos fieldPositions = do parseWarnings (toPWarnings lexWarnings) for_ utf8WarnPos $ \pos -> parseWarning zeroPos PWTUTF $ "UTF8 encoding problem at byte offset " ++ show pos - let (syntax, fs') = sectionizeFields fs + let (syntax, fs') = sectionizeFields fieldPositions let (fields, sectionFields) = takeFields fs' -- cabal-version @@ -199,7 +218,8 @@ parseGenericPackageDescription' scannedVer lexWarnings utf8WarnPos fs = do -- Sections let gpd = - emptyGenericPackageDescription + (emptyGenericPackageDescription + { exactPrintMeta = ExactPrintMeta { exactPositions = toExact fieldPositions, exactComments = commentMap fieldPositions} }) & L.packageDescription .~ pd gpd1 <- view stateGpd <$> execStateT (goSections specVer sectionFields) (SectionS gpd Map.empty) @@ -234,9 +254,34 @@ parseGenericPackageDescription' scannedVer lexWarnings utf8WarnPos fs = do ++ "' must use section syntax. See the Cabal user guide for details." maybeWarnCabalVersion _ _ = return () +toExact :: [Field Position] -> Map [NameSpace] ExactPosition +toExact = foldr (toExactStep []) mempty + +toExactStep :: [NameSpace] -> Field Position -> Map [NameSpace] ExactPosition -> Map [NameSpace] ExactPosition +toExactStep prevNamespace field prev = case field of + Field name lines' -> + Map.insert nameSpace + (ExactPosition { namePosition = (nameAnn name), argumentPosition = (fieldLineAnn <$> lines')}) + prev + Section name args fields' -> + Map.insert nameSpace + (ExactPosition { namePosition = (nameAnn name), argumentPosition = (sectionArgAnn <$> args)}) + $ foldr (toExactStep nameSpace) prev fields' + Meta _ -> prev + where + nameSpace = prevNamespace <> toNameSpace field + +toNameSpace :: Field a -> [NameSpace] +toNameSpace = \case + Field name _ -> [NameSpace { nameSpaceName = getName name, nameSpaceSectionArgs = [] }] + Section name args _ -> [NameSpace { nameSpaceName = getName name, nameSpaceSectionArgs = sectionArgContent <$> args }] + Meta _ -> [] + + goSections :: CabalSpecVersion -> [Field Position] -> SectionParser () goSections specVer = traverse_ process where + process (Meta _) = pure () process (Field (Name pos name) _) = lift $ parseWarning pos PWTTrailingFields $ @@ -266,7 +311,7 @@ goSections specVer = traverse_ process , name == "common" = lift $ do parseWarning pos PWTUnknownSection $ "Ignoring section: common. You should set cabal-version: 2.2 or larger to use common stanzas." | name == "common" = do - commonStanzas <- use stateCommonStanzas + commonStanzas <- use stateCommonStanzas -- here we find the common stanzas name' <- lift $ parseCommonName pos args biTree <- lift $ parseCondTree' buildInfoFieldGrammar id commonStanzas fields @@ -670,7 +715,7 @@ processImports v fromBuildInfo commonStanzas = go [] -- parse actual CondTree go acc fields = do fields' <- catMaybes <$> traverse (warnImport v) fields - pure $ (fields', \x -> foldr (mergeCommonStanza fromBuildInfo) x acc) + pure $ (fields', \x -> foldr (mergeCommonStanza fromBuildInfo) x acc) -- common import stanzas get merged -- | Warn on "import" fields, also map to Maybe, so errorneous fields can be filtered warnImport :: CabalSpecVersion -> Field Position -> ParseResult (Maybe (Field Position)) diff --git a/Cabal-syntax/src/Distribution/PackageDescription/PrettyPrint.hs b/Cabal-syntax/src/Distribution/PackageDescription/PrettyPrint.hs index b03b1b99ada..0fb04fe16ab 100644 --- a/Cabal-syntax/src/Distribution/PackageDescription/PrettyPrint.hs +++ b/Cabal-syntax/src/Distribution/PackageDescription/PrettyPrint.hs @@ -237,6 +237,7 @@ pdToGpd pd = , condExecutables = mkCondTree' exeName <$> executables pd , condTestSuites = mkCondTree' testName <$> testSuites pd , condBenchmarks = mkCondTree' benchmarkName <$> benchmarks pd + , exactPrintMeta = emptyExactPrintMeta } where -- We set CondTree's [Dependency] to an empty list, as it diff --git a/Cabal-syntax/src/Distribution/Parsec/Position.hs b/Cabal-syntax/src/Distribution/Parsec/Position.hs index 892fc8b8fda..dad54a54c26 100644 --- a/Cabal-syntax/src/Distribution/Parsec/Position.hs +++ b/Cabal-syntax/src/Distribution/Parsec/Position.hs @@ -1,13 +1,16 @@ {-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DeriveDataTypeable #-} module Distribution.Parsec.Position ( Position (..) , incPos , retPos + , retManyPos , showPos , zeroPos , positionCol , positionRow + , difference ) where import Distribution.Compat.Prelude @@ -18,10 +21,11 @@ data Position = Position {-# UNPACK #-} !Int -- row {-# UNPACK #-} !Int -- column - deriving (Eq, Ord, Show, Generic) + deriving (Eq, Ord, Show, Generic, Data) instance Binary Position instance NFData Position where rnf = genericRnf +instance Structured Position -- | Shift position by n columns to the right. incPos :: Int -> Position -> Position @@ -29,7 +33,10 @@ incPos n (Position row col) = Position row (col + n) -- | Shift position to beginning of next row. retPos :: Position -> Position -retPos (Position row _col) = Position (row + 1) 1 +retPos pos = retManyPos 1 pos + +retManyPos :: Int -> Position -> Position +retManyPos x (Position row _x) = (Position (row + x) 1) showPos :: Position -> String showPos (Position row col) = show row ++ ":" ++ show col @@ -44,3 +51,6 @@ positionCol (Position _ c) = c -- | @since 3.0.0.0 positionRow :: Position -> Int positionRow (Position r _) = r + +difference :: Position -> Position -> Position +difference (Position a1 a2) (Position b1 b2) = Position (a1 - b1) (a2 - b2) diff --git a/Cabal-syntax/src/Distribution/Types/GenericPackageDescription.hs b/Cabal-syntax/src/Distribution/Types/GenericPackageDescription.hs index 55ec8652304..adf6a2e23f2 100644 --- a/Cabal-syntax/src/Distribution/Types/GenericPackageDescription.hs +++ b/Cabal-syntax/src/Distribution/Types/GenericPackageDescription.hs @@ -5,7 +5,11 @@ module Distribution.Types.GenericPackageDescription ( GenericPackageDescription (..) + , ExactPrintMeta(..) + , ExactPosition(..) + , NameSpace(..) , emptyGenericPackageDescription + , emptyExactPrintMeta ) where import Distribution.Compat.Prelude @@ -28,6 +32,56 @@ import Distribution.Types.Library import Distribution.Types.TestSuite import Distribution.Types.UnqualComponentName import Distribution.Version +import Data.Text(Text) +import Distribution.Fields.Field(FieldName) +import Distribution.Parsec.Position(Position) +import Data.ByteString(ByteString) + +data ExactPosition = ExactPosition {namePosition :: Position + -- argument can be filedline or section args + -- recursive names within sections have their own + -- name identifier so they're not modelled + , argumentPosition :: [Position] } + deriving (Show, Eq, Typeable, Data, Generic, Ord) +instance Structured ExactPosition +instance NFData ExactPosition where rnf = genericRnf +instance Binary ExactPosition + +-- | we need to distinct exact positions in various namespaces for fields, +-- such as: +-- @ +-- library: +-- build-depends: base < 4 +-- ... +-- executable two +-- build-depends: base <5 +-- , containers > 3 +-- executable three +-- build-depends: base <5 +-- , containers > 5 +-- @ +-- so we put "exectuabe" or "library" as field name +-- and the arguments such as "two" and "three" as section argument. +-- this allows us to distinct them in the 'exactPositions' +data NameSpace = NameSpace + { nameSpaceName :: FieldName + , nameSpaceSectionArgs :: [ByteString] + } + deriving (Show, Eq, Typeable, Data, Ord, Generic) + +instance Binary NameSpace +instance Structured NameSpace +instance NFData NameSpace where rnf = genericRnf + +data ExactPrintMeta = ExactPrintMeta + { exactPositions :: Map [NameSpace] ExactPosition + , exactComments :: Map Position Text + } + deriving (Show, Eq, Typeable, Data, Generic) + +instance Binary ExactPrintMeta +instance Structured ExactPrintMeta +instance NFData ExactPrintMeta where rnf = genericRnf -- --------------------------------------------------------------------------- -- The 'GenericPackageDescription' type @@ -70,24 +124,42 @@ data GenericPackageDescription = GenericPackageDescription , CondTree ConfVar [Dependency] Benchmark ) ] + , exactPrintMeta :: ExactPrintMeta } deriving (Show, Eq, Typeable, Data, Generic) + instance Package GenericPackageDescription where packageId = packageId . packageDescription -instance Binary GenericPackageDescription instance Structured GenericPackageDescription + +-- | Required for rebuild monad +instance Binary GenericPackageDescription instance NFData GenericPackageDescription where rnf = genericRnf +emptyExactPrintMeta :: ExactPrintMeta +emptyExactPrintMeta = ExactPrintMeta mempty mempty + emptyGenericPackageDescription :: GenericPackageDescription -emptyGenericPackageDescription = GenericPackageDescription emptyPackageDescription Nothing [] Nothing [] [] [] [] [] +emptyGenericPackageDescription = GenericPackageDescription + { packageDescription = emptyPackageDescription + , gpdScannedVersion = Nothing + , genPackageFlags = [] + , condLibrary = Nothing + , condSubLibraries = [] + , condForeignLibs = [] + , condExecutables = [] + , condTestSuites = [] + , condBenchmarks = [] + , exactPrintMeta = emptyExactPrintMeta + } -- ----------------------------------------------------------------------------- -- Traversal Instances instance L.HasBuildInfos GenericPackageDescription where - traverseBuildInfos f (GenericPackageDescription p v a1 x1 x2 x3 x4 x5 x6) = + traverseBuildInfos f (GenericPackageDescription p v a1 x1 x2 x3 x4 x5 x6 exactPrintMeta') = GenericPackageDescription <$> L.traverseBuildInfos f p <*> pure v @@ -98,6 +170,7 @@ instance L.HasBuildInfos GenericPackageDescription where <*> (traverse . L._2 . traverseCondTreeBuildInfo) f x4 <*> (traverse . L._2 . traverseCondTreeBuildInfo) f x5 <*> (traverse . L._2 . traverseCondTreeBuildInfo) f x6 + <*> pure exactPrintMeta' -- We use this traversal to keep [Dependency] field in CondTree up to date. traverseCondTreeBuildInfo @@ -118,3 +191,4 @@ traverseCondTreeBuildInfo g = node CondBranch v <$> node x <*> traverse node y + diff --git a/Cabal-syntax/src/Distribution/Types/GenericPackageDescription/Lens.hs b/Cabal-syntax/src/Distribution/Types/GenericPackageDescription/Lens.hs index 213c97128f9..8cc886c1f77 100644 --- a/Cabal-syntax/src/Distribution/Types/GenericPackageDescription/Lens.hs +++ b/Cabal-syntax/src/Distribution/Types/GenericPackageDescription/Lens.hs @@ -81,7 +81,7 @@ allCondTrees ) -> GenericPackageDescription -> f GenericPackageDescription -allCondTrees f (GenericPackageDescription p v a1 x1 x2 x3 x4 x5 x6) = +allCondTrees f (GenericPackageDescription p v a1 x1 x2 x3 x4 x5 x6 exactPrintMeta) = GenericPackageDescription <$> pure p <*> pure v @@ -92,6 +92,7 @@ allCondTrees f (GenericPackageDescription p v a1 x1 x2 x3 x4 x5 x6) = <*> (traverse . _2) f x4 <*> (traverse . _2) f x5 <*> (traverse . _2) f x6 + <*> pure exactPrintMeta ------------------------------------------------------------------------------- -- Flag diff --git a/Cabal-tests/Cabal-tests.cabal b/Cabal-tests/Cabal-tests.cabal index f6a8c2c1481..4d436e5b096 100644 --- a/Cabal-tests/Cabal-tests.cabal +++ b/Cabal-tests/Cabal-tests.cabal @@ -100,6 +100,30 @@ test-suite parser-tests ghc-options: -Wall default-language: Haskell2010 +test-suite printer-tests + type: exitcode-stdio-1.0 + hs-source-dirs: tests + main-is: PrinterTests.hs + build-depends: + base + , base-compat >=0.11.0 && <0.14 + , text + , bytestring + , Cabal-syntax + , Cabal-tree-diff + , Diff >=0.4 && <0.6 + , directory + , filepath + , tasty >=1.2.3 && <1.6 + , tasty-golden >=2.3.1.1 && <2.4 + , tasty-hunit + , tasty-quickcheck + , tree-diff >=0.1 && <0.4 + , pretty + + ghc-options: -Wall + default-language: Haskell2010 + test-suite check-tests type: exitcode-stdio-1.0 hs-source-dirs: tests diff --git a/Cabal-tests/tests/HackageTests.hs b/Cabal-tests/tests/HackageTests.hs index df27938d221..218658f1a93 100644 --- a/Cabal-tests/tests/HackageTests.hs +++ b/Cabal-tests/tests/HackageTests.hs @@ -36,6 +36,7 @@ import qualified Codec.Archive.Tar as Tar import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString.Lazy as BSL +import qualified Distribution.Fields.Lexer as Lexer import qualified Distribution.Fields.Parser as Parsec import qualified Distribution.Fields.Pretty as PP import qualified Distribution.PackageDescription.Parsec as Parsec @@ -65,7 +66,12 @@ parseIndex :: (Monoid a, NFData a) => (FilePath -> Bool) parseIndex predicate action = do configPath <- getCabalConfigPath cfg <- B.readFile configPath - cfgFields <- either (fail . show) pure $ Parsec.readFields cfg + cfgFields <- case Parsec.readFields cfg of + Right c -> return c + Left err -> do + putStrLn $ "Error while parsing " ++ configPath + print err + exitFailure repoCache <- case lookupInConfig "remote-repo-cache" cfgFields of [] -> getCacheDirPath -- Default (rrc : _) -> return rrc -- User-specified @@ -308,6 +314,16 @@ roundtripTest testFieldsTransform fpath bs = do B.putStr c fail "parse error" +------------------------------------------------------------------------------- +-- Lexer roundtrip test +------------------------------------------------------------------------------- + +lexerRoundtripTest :: FilePath -> B8.ByteString -> IO (Sum Int) +lexerRoundtripTest fpath bs = do + let (ws, xs) = Lexer.lexByteString bs + traverse_ print xs + return mempty + ------------------------------------------------------------------------------- -- Main ------------------------------------------------------------------------------- @@ -323,9 +339,10 @@ main = join (O.execParser opts) optsP = subparser [ command "read-fields" readFieldsP "Parse outer format (to '[Field]', TODO: apply Quirks)" - , command "parsec" parsecP "Parse GPD with parsec" - , command "roundtrip" roundtripP "parse . pretty . parse = parse" - , command "check" checkP "Check GPD" + , command "parsec" parsecP "Parse GPD with parsec" + , command "roundtrip" roundtripP "parse . pretty . parse = parse" + , command "check" checkP "Check GPD" + , command "roundtrip-lexer" lexerRoundtripP "lex and unlex" ] <|> pure defaultA defaultA = do @@ -358,6 +375,11 @@ main = join (O.execParser opts) Sum n <- parseIndex pfx (roundtripTest testFieldsTransform) putStrLn $ show n ++ " files processed" + lexerRoundtripP = lexerRoundtripA <$> prefixP + lexerRoundtripA pfx = do + Sum n <- parseIndex pfx lexerRoundtripTest + putStrLn $ show n ++ " files processed" + checkP = checkA <$> prefixP checkA pfx = do CheckResult n w x a b c d e <- parseIndex pfx parseCheckTest diff --git a/Cabal-tests/tests/NoThunks.hs b/Cabal-tests/tests/NoThunks.hs index da422e37c5e..763143cd098 100644 --- a/Cabal-tests/tests/NoThunks.hs +++ b/Cabal-tests/tests/NoThunks.hs @@ -31,6 +31,7 @@ import Language.Haskell.Extension (Extension, KnownExtension, Langua import NoThunks.Class (NoThunks (..), OnlyCheckWhnf (..), noThunksInValues) import Test.Tasty (defaultMain, testGroup) import Test.Tasty.HUnit (assertFailure, testCase) +import Distribution.Parsec.Position import Distribution.PackageDescription @@ -78,6 +79,10 @@ instance NoThunks ForeignLibOption instance NoThunks ModuleReexport instance NoThunks LibraryVisibility instance NoThunks ForeignLibType +instance NoThunks Position +instance NoThunks ExactPosition +instance NoThunks NameSpace +instance NoThunks ExactPrintMeta instance NoThunks GenericPackageDescription instance NoThunks KnownRepoType instance NoThunks Library diff --git a/Cabal-tests/tests/ParserTests.hs b/Cabal-tests/tests/ParserTests.hs index 981be3b4cce..8c9ab34ab6a 100644 --- a/Cabal-tests/tests/ParserTests.hs +++ b/Cabal-tests/tests/ParserTests.hs @@ -38,8 +38,9 @@ import Data.TreeDiff.Instances.Cabal () tests :: TestTree tests = testGroup "parsec tests" - [ regressionTests - , warningTests + [ + -- regressionTests -- TODO make these work with exact printing instead of the internal representaton + warningTests , errorTests , ipiTests ] diff --git a/Cabal-tests/tests/ParserTests/exactPrint/anynone.cabal b/Cabal-tests/tests/ParserTests/exactPrint/anynone.cabal new file mode 100644 index 00000000000..01f371fec72 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/exactPrint/anynone.cabal @@ -0,0 +1,10 @@ +cabal-version: 3.0 +name: anynone +version: 0 +synopsis: The -any none demo +build-type: Simple + +library + default-language: Haskell2010 + exposed-modules: AnyNone + build-depends: base -any diff --git a/Cabal-tests/tests/ParserTests/exactPrint/bounded.cabal b/Cabal-tests/tests/ParserTests/exactPrint/bounded.cabal new file mode 100644 index 00000000000..cc17edbe003 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/exactPrint/bounded.cabal @@ -0,0 +1,10 @@ +cabal-version: 3.0 +name: bounded +version: 0 +synopsis: The -any none demo +build-type: Simple + +library + default-language: Haskell2010 + exposed-modules: AnyNone + build-depends: base <5 \ No newline at end of file diff --git a/Cabal-tests/tests/ParserTests/exactPrint/commas.cabal b/Cabal-tests/tests/ParserTests/exactPrint/commas.cabal new file mode 100644 index 00000000000..861138433b3 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/exactPrint/commas.cabal @@ -0,0 +1,12 @@ +cabal-version: 3.0 +name: two-sections +version: 0 +synopsis: The -any none demo +build-type: Simple + +library + default-language: Haskell2010 + exposed-modules: AnyNone + build-depends: base <5, + containers + , bytestring \ No newline at end of file diff --git a/Cabal-tests/tests/ParserTests/exactPrint/comment.cabal b/Cabal-tests/tests/ParserTests/exactPrint/comment.cabal new file mode 100644 index 00000000000..51f22eeda9b --- /dev/null +++ b/Cabal-tests/tests/ParserTests/exactPrint/comment.cabal @@ -0,0 +1,11 @@ +cabal-version: 3.0 +name: bounded +version: 0 +synopsis: The -any none demo +build-type: Simple + +-- a comment +library + default-language: Haskell2010 + exposed-modules: AnyNone + build-depends: base <5 \ No newline at end of file diff --git a/Cabal-tests/tests/ParserTests/exactPrint/comments.cabal b/Cabal-tests/tests/ParserTests/exactPrint/comments.cabal new file mode 100644 index 00000000000..7edac38a577 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/exactPrint/comments.cabal @@ -0,0 +1,13 @@ +cabal-version: 3.0 +name: two-sections +version: 0 +synopsis: The -any none demo +build-type: Simple + + -- my awesome +-- library +library + default-language: Haskell2010 + exposed-modules: AnyNone + build-depends: base <5 + , containers \ No newline at end of file diff --git a/Cabal-tests/tests/ParserTests/exactPrint/conditional.cabal b/Cabal-tests/tests/ParserTests/exactPrint/conditional.cabal new file mode 100644 index 00000000000..128d7afdc51 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/exactPrint/conditional.cabal @@ -0,0 +1,17 @@ +cabal-version: 3.0 +name: bounded +version: 0 +synopsis: The -any none demo +build-type: Simple + +flag foo + manual: True + default: True + +library + default-language: Haskell2010 + exposed-modules: AnyNone + if flag(foo) + build-depends: base <5 + else + build-depends: base <5.5 \ No newline at end of file diff --git a/Cabal-tests/tests/ParserTests/exactPrint/elif.cabal b/Cabal-tests/tests/ParserTests/exactPrint/elif.cabal new file mode 100644 index 00000000000..2d760681842 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/exactPrint/elif.cabal @@ -0,0 +1,20 @@ +name: elif +version: 0 +synopsis: The elif demo +build-type: Simple +cabal-version: >=1.10 + +source-repository head + Type: git + Location: https://github.com/hvr/-.git + +library + default-language: Haskell2010 + exposed-modules: ElseIf + + if os(linux) + build-depends: unix + elif os(windows) + build-depends: Win32 + else + buildable: False diff --git a/Cabal-tests/tests/ParserTests/exactPrint/import.cabal b/Cabal-tests/tests/ParserTests/exactPrint/import.cabal new file mode 100644 index 00000000000..2480fd8c301 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/exactPrint/import.cabal @@ -0,0 +1,25 @@ +cabal-version: 3.0 +name: two-sections +version: 0 +synopsis: The -any none demo +build-type: Simple + + +common common-options + default-extensions: + GADTs + ghc-options: -Wall -Wcompat -Wnoncanonical-monad-instances -Wincomplete-uni-patterns -Wincomplete-record-updates + +library + import: common-options + default-language: Haskell2010 + exposed-modules: AnyNone + build-depends: base <5, + containers + +executable two + default-language: Haskell2010 + main-is: main.hs + build-depends: base <5, + containers >3, + two-sections \ No newline at end of file diff --git a/Cabal-tests/tests/ParserTests/exactPrint/multiple-depends.cabal b/Cabal-tests/tests/ParserTests/exactPrint/multiple-depends.cabal new file mode 100644 index 00000000000..0a7e47d9760 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/exactPrint/multiple-depends.cabal @@ -0,0 +1,11 @@ +cabal-version: 3.0 +name: multiple-depends +version: 0 +synopsis: The -any none demo +build-type: Simple + +library + default-language: Haskell2010 + exposed-modules: AnyNone + build-depends: base <5 + , containers \ No newline at end of file diff --git a/Cabal-tests/tests/ParserTests/exactPrint/two-sections-build-depends.cabal b/Cabal-tests/tests/ParserTests/exactPrint/two-sections-build-depends.cabal new file mode 100644 index 00000000000..b8999a7dbc4 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/exactPrint/two-sections-build-depends.cabal @@ -0,0 +1,16 @@ +cabal-version: 3.0 +name: two-sections +version: 0 +synopsis: The -any none demo +build-type: Simple + +library + default-language: Haskell2010 + exposed-modules: AnyNone + build-depends: base <5, + someDep <3 + +executable two + default-language: Haskell2010 + main-is: main.hs + build-depends: base <5 \ No newline at end of file diff --git a/Cabal-tests/tests/ParserTests/exactPrint/two-sections-no-depends.cabal b/Cabal-tests/tests/ParserTests/exactPrint/two-sections-no-depends.cabal new file mode 100644 index 00000000000..081e725f032 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/exactPrint/two-sections-no-depends.cabal @@ -0,0 +1,15 @@ +cabal-version: 3.0 +name: two-sections +version: 0 +synopsis: The -any none demo +build-type: Simple + +library + default-language: Haskell2010 + exposed-modules: AnyNone + build-depends: base <5 + +executable two + default-language: Haskell2010 + main-is: main.hs + build-depends: base <5 \ No newline at end of file diff --git a/Cabal-tests/tests/ParserTests/exactPrint/two-sections-spacing.cabal b/Cabal-tests/tests/ParserTests/exactPrint/two-sections-spacing.cabal new file mode 100644 index 00000000000..ecd723ffd82 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/exactPrint/two-sections-spacing.cabal @@ -0,0 +1,20 @@ +cabal-version: 3.0 +name: two-sections +version: 0 +synopsis: The -any none demo +build-type: Simple + +library + default-language: Haskell2010 + exposed-modules: AnyNone + build-depends: base <5, + containers + + + +executable two + default-language: Haskell2010 + main-is: main.hs + build-depends: base <5, + containers >3, + two-sections \ No newline at end of file diff --git a/Cabal-tests/tests/ParserTests/exactPrint/two-sections.cabal b/Cabal-tests/tests/ParserTests/exactPrint/two-sections.cabal new file mode 100644 index 00000000000..839558789b1 --- /dev/null +++ b/Cabal-tests/tests/ParserTests/exactPrint/two-sections.cabal @@ -0,0 +1,18 @@ +cabal-version: 3.0 +name: two-sections +version: 0 +synopsis: The -any none demo +build-type: Simple + +library + default-language: Haskell2010 + exposed-modules: AnyNone + build-depends: base <5, + containers + +executable two + default-language: Haskell2010 + main-is: main.hs + build-depends: base <5, + containers >3, + two-sections \ No newline at end of file diff --git a/Cabal-tests/tests/PrinterTests.hs b/Cabal-tests/tests/PrinterTests.hs new file mode 100644 index 00000000000..d4b1325be59 --- /dev/null +++ b/Cabal-tests/tests/PrinterTests.hs @@ -0,0 +1,119 @@ +{-# LANGUAGE CPP #-} +module Main + ( main + ) where + +import Prelude () +import Prelude.Compat + +import Distribution.Types.GenericPackageDescription +import Data.Maybe(catMaybes) +import Test.Tasty +import Data.Text(unpack) +import Test.Tasty.HUnit + +import Control.Monad (unless) +import Distribution.Fields (runParseResult) +import Distribution.PackageDescription.Parsec (parseGenericPackageDescription) +import System.Directory (setCurrentDirectory) +import System.Environment (getArgs, withArgs) +import System.FilePath (()) +import Data.Text.Encoding(decodeUtf8, encodeUtf8) +import Distribution.PackageDescription.ExactPrint(exactPrint) +import Data.TreeDiff +import Text.PrettyPrint hiding ((<>)) +import Data.TreeDiff.QuickCheck (ediffEq) + +import qualified Data.ByteString as BS + +tests :: TestTree +tests = testGroup "printer tests" + [ printExact + ] + +------------------------------------------------------------------------------- +-- Warnings +------------------------------------------------------------------------------- + +-- Parse some cabal file - print it like cabal file +printExact :: TestTree +printExact = testGroup "printExact" + [ + testParsePrintExact "bounded.cabal" + , testParsePrintExact "two-sections-no-depends.cabal" + , testParsePrintExact "two-sections-build-depends.cabal" + , testParsePrintExact "two-sections.cabal" + , testParsePrintExact "two-sections-spacing.cabal" + , testParsePrintExact "comment.cabal" + , testParsePrintExact "conditional.cabal" + -- -- , testParsePrintExact "commas.cabal" -- TODO dear lord is this also requried?! + -- , testParsePrintExact "anynone.cabal" -- TODO is this neccessary? I think we're allowed to pretty print a range? + -- , testParsePrintExact "multiple-depends.cabal" -- TODO is this neccisary? I think we're allowed to be oppinionated on comma placement? + -- , testParsePrintExact "import.cabal" -- this is required + -- , testParsePrintExact "elif.cabal" -- TODO this is required + -- broken by: instance Pretty VersionRange where + -- however we currently don't retain enough information to do this exact! + ] + +clearMeta :: GenericPackageDescription -> GenericPackageDescription +clearMeta x = x { exactPrintMeta = emptyExactPrintMeta } + +testParsePrintExact :: FilePath -> TestTree +testParsePrintExact fp = testGroup "testParsePrintExact" [ + testCase ("test parse (print (parse fp)) = (parse fp) " <> fp) $ do + contents <- BS.readFile $ "tests" "ParserTests" "exactPrint" fp + + let res = parseGenericPackageDescription contents + let (warns, descirption) = runParseResult res + + case descirption of + Left someFailure -> do + error $ "failed parsing " <> show someFailure + Right generic -> + case snd (runParseResult (parseGenericPackageDescription (encodeUtf8 (exactPrint generic)))) of + Left someParseError -> error $ "printing caused parse Error" <> show someParseError + Right res -> clearMeta generic @=? clearMeta res + + , testCase ("test byte for byte roundtrip " <> fp) $ do + contents <- BS.readFile $ "tests" "ParserTests" "exactPrint" fp + + let res = parseGenericPackageDescription contents + let (_warns, descirption) = runParseResult res + + case descirption of + Left someFailure -> error $ "failed parsing" <> show someFailure + Right generic -> assertEqualStrings "should be the same cabalfiles" (unpack (decodeUtf8 contents)) (unpack (exactPrint generic)) + ] + +main :: IO () +main = do + args <- getArgs + case args of + ("--cwd" : cwd : args') -> do + setCurrentDirectory cwd + withArgs args' $ defaultMain tests + _ -> defaultMain tests + +assertEqualStrings + :: (HasCallStack) + => String -- ^ The message prefix + -> String -- ^ The expected value + -> String -- ^ The actual value + -> Assertion +assertEqualStrings preface expected actual = + unless (actual == expected) (assertFailure msg) + where msg = (if null preface then "" else preface ++ "\n") ++ + "expected:\n---\n" ++ expected ++ "\n---\nbut got: \n---\n" ++ + actual ++ "\n---\ndifference:\n---\n" ++ difference expected actual + + +difference :: String -> String -> String +difference expected actual = render $ prettyEditExpr zipped + where + zipped :: Edit EditExpr + zipped = ediff (fst <$> removeEq) (snd <$> removeEq) + + removeEq = catMaybes $ zipWith (\x y -> if x == y then Nothing else Just (x,y)) expectedLines actualLines + + expectedLines = lines expected + actualLines = lines actual diff --git a/Cabal-tree-diff/src/Data/TreeDiff/Instances/Cabal.hs b/Cabal-tree-diff/src/Data/TreeDiff/Instances/Cabal.hs index 67966cb6f90..8af44282824 100644 --- a/Cabal-tree-diff/src/Data/TreeDiff/Instances/Cabal.hs +++ b/Cabal-tree-diff/src/Data/TreeDiff/Instances/Cabal.hs @@ -11,12 +11,14 @@ import Data.TreeDiff.Instances.CabalVersion () ------------------------------------------------------------------------------- +import Distribution.Parsec.Position(Position) import Distribution.Backpack (OpenModule, OpenUnitId) import Distribution.CabalSpecVersion (CabalSpecVersion) import Distribution.Compiler (CompilerFlavor, CompilerId, PerCompilerFlavor) import Distribution.InstalledPackageInfo (AbiDependency, ExposedModule, InstalledPackageInfo) import Distribution.ModuleName (ModuleName) import Distribution.PackageDescription +import Distribution.Types.GenericPackageDescription(ExactPrintMeta) import Distribution.Simple.Compiler (DebugInfoLevel, OptimisationLevel, ProfDetailLevel) import Distribution.Simple.Flag (Flag) import Distribution.Simple.InstallDirs @@ -83,6 +85,10 @@ instance ToExpr FlagName instance ToExpr ForeignLib instance ToExpr ForeignLibOption instance ToExpr ForeignLibType +instance ToExpr Position +instance ToExpr ExactPosition +instance ToExpr NameSpace +instance ToExpr ExactPrintMeta instance ToExpr GenericPackageDescription instance ToExpr HaddockTarget instance ToExpr IncludeRenaming diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/IndexConversion.hs b/cabal-install-solver/src/Distribution/Solver/Modular/IndexConversion.hs index 72d0b8193e3..6db92514b94 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/IndexConversion.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/IndexConversion.hs @@ -176,7 +176,7 @@ convGPD :: OS -> Arch -> CompilerInfo -> [LabeledPackageConstraint] -> StrongFlags -> SolveExecutables -> PN -> GenericPackageDescription -> PInfo convGPD os arch cinfo constraints strfl solveExes pn - (GenericPackageDescription pkg scannedVersion flags mlib sub_libs flibs exes tests benchs) = + (GenericPackageDescription pkg scannedVersion flags mlib sub_libs flibs exes tests benchs _meta) = let fds = flagInfo strfl flags diff --git a/cabal-install/src/Distribution/Client/IndexUtils.hs b/cabal-install/src/Distribution/Client/IndexUtils.hs index e2ea4486426..c177adc0653 100644 --- a/cabal-install/src/Distribution/Client/IndexUtils.hs +++ b/cabal-install/src/Distribution/Client/IndexUtils.hs @@ -80,6 +80,7 @@ import Distribution.PackageDescription ( GenericPackageDescription (..) , PackageDescription (..) , emptyPackageDescription + , emptyGenericPackageDescription ) import Distribution.Simple.Compiler ( Compiler @@ -1062,20 +1063,13 @@ packageListFromCache verbosity mkPkg hnd Cache{..} = accum mempty [] mempty cach where dummyPackageDescription :: Version -> GenericPackageDescription dummyPackageDescription specVer = - GenericPackageDescription + emptyGenericPackageDescription { packageDescription = emptyPackageDescription { package = pkgid , synopsis = dummySynopsis } , gpdScannedVersion = Just specVer -- tells index scanner to skip this file. - , genPackageFlags = [] - , condLibrary = Nothing - , condSubLibraries = [] - , condForeignLibs = [] - , condExecutables = [] - , condTestSuites = [] - , condBenchmarks = [] } dummySynopsis = "" diff --git a/cabal-install/tests/IntegrationTests2.hs b/cabal-install/tests/IntegrationTests2.hs index bf6e25c5b87..5477240e43d 100644 --- a/cabal-install/tests/IntegrationTests2.hs +++ b/cabal-install/tests/IntegrationTests2.hs @@ -462,17 +462,10 @@ testTargetSelectorAmbiguous reportSubCase = do srcpkgPackageId = pkgid, srcpkgSource = LocalUnpackedPackage loc, srcpkgDescrOverride = Nothing, - srcpkgDescription = GenericPackageDescription { + srcpkgDescription = emptyGenericPackageDescription { packageDescription = emptyPackageDescription { package = pkgid }, - gpdScannedVersion = Nothing, - genPackageFlags = [], - condLibrary = Nothing, - condSubLibraries = [], - condForeignLibs = [], condExecutables = [ ( exeName exe, CondNode exe [] [] ) - | exe <- exes ], - condTestSuites = [], - condBenchmarks = [] + | exe <- exes ] } } where diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/DSL.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/DSL.hs index db3bff2640b..08667068353 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/DSL.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/DSL.hs @@ -435,7 +435,7 @@ exAvSrcPkg ex = , srcpkgSource = LocalTarballPackage "<>" , srcpkgDescrOverride = Nothing , srcpkgDescription = - C.GenericPackageDescription + C.emptyGenericPackageDescription { C.packageDescription = C.emptyPackageDescription { C.package = pkgId