stack-1.5.1/doc/0000755000000000000000000000000013140560217011555 5ustar0000000000000000stack-1.5.1/src/0000755000000000000000000000000013135652051011601 5ustar0000000000000000stack-1.5.1/src/Control/0000755000000000000000000000000013135651621013223 5ustar0000000000000000stack-1.5.1/src/Control/Concurrent/0000755000000000000000000000000013135652051015343 5ustar0000000000000000stack-1.5.1/src/Data/0000755000000000000000000000000013135652051012452 5ustar0000000000000000stack-1.5.1/src/Data/Aeson/0000755000000000000000000000000013135652051013517 5ustar0000000000000000stack-1.5.1/src/Data/Attoparsec/0000755000000000000000000000000013135652051014557 5ustar0000000000000000stack-1.5.1/src/Data/IORef/0000755000000000000000000000000013135652051013416 5ustar0000000000000000stack-1.5.1/src/Data/Maybe/0000755000000000000000000000000013135652051013507 5ustar0000000000000000stack-1.5.1/src/Data/Monoid/0000755000000000000000000000000013135652051013677 5ustar0000000000000000stack-1.5.1/src/Data/Store/0000755000000000000000000000000013135652051013546 5ustar0000000000000000stack-1.5.1/src/Data/Text/0000755000000000000000000000000013135652051013376 5ustar0000000000000000stack-1.5.1/src/Distribution/0000755000000000000000000000000013135652051014260 5ustar0000000000000000stack-1.5.1/src/Distribution/Version/0000755000000000000000000000000013135652051015705 5ustar0000000000000000stack-1.5.1/src/Hackage/0000755000000000000000000000000013063526313013125 5ustar0000000000000000stack-1.5.1/src/Hackage/Security/0000755000000000000000000000000013063526313014734 5ustar0000000000000000stack-1.5.1/src/Hackage/Security/Client/0000755000000000000000000000000013063526313016152 5ustar0000000000000000stack-1.5.1/src/Hackage/Security/Client/Repository/0000755000000000000000000000000013063526313020331 5ustar0000000000000000stack-1.5.1/src/Hackage/Security/Client/Repository/HttpLib/0000755000000000000000000000000013135652051021676 5ustar0000000000000000stack-1.5.1/src/Network/0000755000000000000000000000000012546477354013252 5ustar0000000000000000stack-1.5.1/src/Network/HTTP/0000755000000000000000000000000013135652051014011 5ustar0000000000000000stack-1.5.1/src/Network/HTTP/Download/0000755000000000000000000000000013135652051015560 5ustar0000000000000000stack-1.5.1/src/Options/0000755000000000000000000000000012546477354013254 5ustar0000000000000000stack-1.5.1/src/Options/Applicative/0000755000000000000000000000000013135652051015475 5ustar0000000000000000stack-1.5.1/src/Options/Applicative/Builder/0000755000000000000000000000000013135652051017063 5ustar0000000000000000stack-1.5.1/src/Path/0000755000000000000000000000000013135652051012475 5ustar0000000000000000stack-1.5.1/src/Stack/0000755000000000000000000000000013140560217012644 5ustar0000000000000000stack-1.5.1/src/Stack/Build/0000755000000000000000000000000013135652051013705 5ustar0000000000000000stack-1.5.1/src/Stack/Config/0000755000000000000000000000000013135652051014053 5ustar0000000000000000stack-1.5.1/src/Stack/Docker/0000755000000000000000000000000013135652051014055 5ustar0000000000000000stack-1.5.1/src/Stack/Ghci/0000755000000000000000000000000013135652051013520 5ustar0000000000000000stack-1.5.1/src/Stack/Options/0000755000000000000000000000000013135652051014301 5ustar0000000000000000stack-1.5.1/src/Stack/Setup/0000755000000000000000000000000013135652051013746 5ustar0000000000000000stack-1.5.1/src/Stack/Sig/0000755000000000000000000000000013135652051013370 5ustar0000000000000000stack-1.5.1/src/Stack/Types/0000755000000000000000000000000013140560217013750 5ustar0000000000000000stack-1.5.1/src/Stack/Types/Config/0000755000000000000000000000000013135652051015157 5ustar0000000000000000stack-1.5.1/src/System/0000755000000000000000000000000012546477354013105 5ustar0000000000000000stack-1.5.1/src/System/Process/0000755000000000000000000000000013135652051014503 5ustar0000000000000000stack-1.5.1/src/Text/0000755000000000000000000000000012766643573012547 5ustar0000000000000000stack-1.5.1/src/Text/PrettyPrint/0000755000000000000000000000000012766643573015053 5ustar0000000000000000stack-1.5.1/src/Text/PrettyPrint/Leijen/0000755000000000000000000000000013135652051016237 5ustar0000000000000000stack-1.5.1/src/main/0000755000000000000000000000000013135652051012525 5ustar0000000000000000stack-1.5.1/src/setup-shim/0000755000000000000000000000000013063526313013700 5ustar0000000000000000stack-1.5.1/src/test/0000755000000000000000000000000013135652051012560 5ustar0000000000000000stack-1.5.1/src/test/Network/0000755000000000000000000000000012546477354014231 5ustar0000000000000000stack-1.5.1/src/test/Network/HTTP/0000755000000000000000000000000012546477354015010 5ustar0000000000000000stack-1.5.1/src/test/Network/HTTP/Download/0000755000000000000000000000000013135652051016537 5ustar0000000000000000stack-1.5.1/src/test/Stack/0000755000000000000000000000000013135652051013625 5ustar0000000000000000stack-1.5.1/src/test/Stack/Build/0000755000000000000000000000000013135652051014664 5ustar0000000000000000stack-1.5.1/src/test/Stack/Ghci/0000755000000000000000000000000013135652051014477 5ustar0000000000000000stack-1.5.1/src/test/Stack/Untar/0000755000000000000000000000000013140564455014724 5ustar0000000000000000stack-1.5.1/test/0000755000000000000000000000000012546477354012011 5ustar0000000000000000stack-1.5.1/test/integration/0000755000000000000000000000000013135651271014317 5ustar0000000000000000stack-1.5.1/test/integration/lib/0000755000000000000000000000000013135651621015064 5ustar0000000000000000stack-1.5.1/test/package-dump/0000755000000000000000000000000012646714302014333 5ustar0000000000000000stack-1.5.1/src/Control/Concurrent/Execute.hs0000644000000000000000000001227613135652051017311 0ustar0000000000000000{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE RecordWildCards #-} -- Concurrent execution with dependencies. Types currently hard-coded for needs -- of stack, but could be generalized easily. module Control.Concurrent.Execute ( ActionType (..) , ActionId (..) , ActionContext (..) , Action (..) , runActions ) where import Control.Applicative import Control.Concurrent.Async (Concurrently (..), async) import Control.Concurrent.STM import Control.Exception import Control.Monad (join, unless) import Data.Foldable (sequenceA_) import Data.Set (Set) import qualified Data.Set as Set import Data.Typeable (Typeable) import Prelude -- Fix AMP warning import Stack.Types.PackageIdentifier data ActionType = ATBuild | ATBuildFinal | ATFinal deriving (Show, Eq, Ord) data ActionId = ActionId !PackageIdentifier !ActionType deriving (Show, Eq, Ord) data Action = Action { actionId :: !ActionId , actionDeps :: !(Set ActionId) , actionDo :: !(ActionContext -> IO ()) } data ActionContext = ActionContext { acRemaining :: !(Set ActionId) -- ^ Does not include the current action , acDownstream :: [Action] -- ^ Actions which depend on the current action } data ExecuteState = ExecuteState { esActions :: TVar [Action] , esExceptions :: TVar [SomeException] , esInAction :: TVar (Set ActionId) , esCompleted :: TVar Int , esFinalLock :: Maybe (TMVar ()) , esKeepGoing :: Bool } data ExecuteException = InconsistentDependencies deriving Typeable instance Exception ExecuteException instance Show ExecuteException where show InconsistentDependencies = "Inconsistent dependencies were discovered while executing your build plan. This should never happen, please report it as a bug to the stack team." runActions :: Int -- ^ threads -> Bool -- ^ keep going after one task has failed -> Bool -- ^ run final actions concurrently? -> [Action] -> (TVar Int -> IO ()) -- ^ progress updated -> IO [SomeException] runActions threads keepGoing concurrentFinal actions0 withProgress = do es <- ExecuteState <$> newTVarIO actions0 <*> newTVarIO [] <*> newTVarIO Set.empty <*> newTVarIO 0 <*> (if concurrentFinal then pure Nothing else Just <$> atomically (newTMVar ())) <*> pure keepGoing _ <- async $ withProgress $ esCompleted es if threads <= 1 then runActions' es else runConcurrently $ sequenceA_ $ replicate threads $ Concurrently $ runActions' es readTVarIO $ esExceptions es runActions' :: ExecuteState -> IO () runActions' ExecuteState {..} = loop where breakOnErrs inner = do errs <- readTVar esExceptions if null errs || esKeepGoing then inner else return $ return () withActions inner = do as <- readTVar esActions if null as then return $ return () else inner as loop = join $ atomically $ breakOnErrs $ withActions $ \as -> case break (Set.null . actionDeps) as of (_, []) -> do inAction <- readTVar esInAction if Set.null inAction then do unless esKeepGoing $ modifyTVar esExceptions (toException InconsistentDependencies:) return $ return () else retry (xs, action:ys) -> do unlock <- case (actionId action, esFinalLock) of (ActionId _ ATFinal, Just lock) -> do takeTMVar lock return $ putTMVar lock () _ -> return $ return () let as' = xs ++ ys inAction <- readTVar esInAction let remaining = Set.union (Set.fromList $ map actionId as') inAction writeTVar esActions as' modifyTVar esInAction (Set.insert $ actionId action) return $ mask $ \restore -> do eres <- try $ restore $ actionDo action ActionContext { acRemaining = remaining , acDownstream = downstreamActions (actionId action) as' } atomically $ do unlock modifyTVar esInAction (Set.delete $ actionId action) modifyTVar esCompleted (+1) case eres of Left err -> modifyTVar esExceptions (err:) Right () -> let dropDep a = a { actionDeps = Set.delete (actionId action) $ actionDeps a } in modifyTVar esActions $ map dropDep restore loop downstreamActions :: ActionId -> [Action] -> [Action] downstreamActions aid = filter (\a -> aid `Set.member` actionDeps a) stack-1.5.1/src/Data/Aeson/Extended.hs0000644000000000000000000001306513135652051015620 0ustar0000000000000000{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} -- | Extensions to Aeson parsing of objects. module Data.Aeson.Extended ( module Export -- * Extended failure messages , (.:) , (.:?) -- * JSON Parser that emits warnings , JSONWarning (..) , WarningParser , WithJSONWarnings (..) , withObjectWarnings , jsonSubWarnings , jsonSubWarningsT , jsonSubWarningsTT , logJSONWarnings , noJSONWarnings , tellJSONField , unWarningParser , (..:) , (..:?) , (..!=) ) where import Control.Monad.Logger (MonadLogger, logWarn) import Control.Monad.Trans (lift) import Control.Monad.Trans.Writer.Strict (WriterT, mapWriterT, runWriterT, tell) import Data.Aeson as Export hiding ((.:), (.:?)) import qualified Data.Aeson as A import Data.Aeson.Types hiding ((.:), (.:?)) import qualified Data.HashMap.Strict as HashMap import Data.Monoid import Data.Set (Set) import qualified Data.Set as Set import Data.Text (unpack, Text) import qualified Data.Text as T import Data.Traversable import qualified Data.Traversable as Traversable import GHC.Generics (Generic) import Generics.Deriving.Monoid (mappenddefault, memptydefault) import Prelude -- Fix redundant import warnings -- | Extends @.:@ warning to include field name. (.:) :: FromJSON a => Object -> Text -> Parser a (.:) o p = modifyFailure (("failed to parse field '" <> unpack p <> "': ") <>) (o A..: p) {-# INLINE (.:) #-} -- | Extends @.:?@ warning to include field name. (.:?) :: FromJSON a => Object -> Text -> Parser (Maybe a) (.:?) o p = modifyFailure (("failed to parse field '" <> unpack p <> "': ") <>) (o A..:? p) {-# INLINE (.:?) #-} -- | 'WarningParser' version of @.:@. (..:) :: FromJSON a => Object -> Text -> WarningParser a o ..: k = tellJSONField k >> lift (o .: k) -- | 'WarningParser' version of @.:?@. (..:?) :: FromJSON a => Object -> Text -> WarningParser (Maybe a) o ..:? k = tellJSONField k >> lift (o .:? k) -- | 'WarningParser' version of @.!=@. (..!=) :: WarningParser (Maybe a) -> a -> WarningParser a wp ..!= d = flip mapWriterT wp $ \p -> do a <- fmap snd p fmap (, a) (fmap fst p .!= d) -- | Tell warning parser about an expected field, so it doesn't warn about it. tellJSONField :: Text -> WarningParser () tellJSONField key = tell (mempty { wpmExpectedFields = Set.singleton key}) -- | 'WarningParser' version of 'withObject'. withObjectWarnings :: String -> (Object -> WarningParser a) -> Value -> Parser (WithJSONWarnings a) withObjectWarnings expected f = withObject expected $ \obj -> do (a,w) <- runWriterT (f obj) let unrecognizedFields = Set.toList (Set.difference (Set.fromList (HashMap.keys obj)) (wpmExpectedFields w)) return (WithJSONWarnings a (wpmWarnings w ++ case unrecognizedFields of [] -> [] _ -> [JSONUnrecognizedFields expected unrecognizedFields])) -- | Convert a 'WarningParser' to a 'Parser'. unWarningParser :: WarningParser a -> Parser a unWarningParser wp = do (a,_) <- runWriterT wp return a -- | Log JSON warnings. logJSONWarnings :: MonadLogger m => FilePath -> [JSONWarning] -> m () logJSONWarnings fp = mapM_ (\w -> $logWarn ("Warning: " <> T.pack fp <> ": " <> T.pack (show w))) -- | Handle warnings in a sub-object. jsonSubWarnings :: WarningParser (WithJSONWarnings a) -> WarningParser a jsonSubWarnings f = do WithJSONWarnings result warnings <- f tell (mempty { wpmWarnings = warnings }) return result -- | Handle warnings in a @Traversable@ of sub-objects. jsonSubWarningsT :: Traversable t => WarningParser (t (WithJSONWarnings a)) -> WarningParser (t a) jsonSubWarningsT f = Traversable.mapM (jsonSubWarnings . return) =<< f -- | Handle warnings in a @Maybe Traversable@ of sub-objects. jsonSubWarningsTT :: (Traversable t, Traversable u) => WarningParser (u (t (WithJSONWarnings a))) -> WarningParser (u (t a)) jsonSubWarningsTT f = Traversable.mapM (jsonSubWarningsT . return) =<< f -- Parsed JSON value without any warnings noJSONWarnings :: a -> WithJSONWarnings a noJSONWarnings v = WithJSONWarnings v [] -- | JSON parser that warns about unexpected fields in objects. type WarningParser a = WriterT WarningParserMonoid Parser a -- | Monoid used by 'WarningParser' to track expected fields and warnings. data WarningParserMonoid = WarningParserMonoid { wpmExpectedFields :: !(Set Text) , wpmWarnings :: [JSONWarning] } deriving Generic instance Monoid WarningParserMonoid where mempty = memptydefault mappend = mappenddefault -- Parsed JSON value with its warnings data WithJSONWarnings a = WithJSONWarnings a [JSONWarning] deriving Generic instance Functor WithJSONWarnings where fmap f (WithJSONWarnings x w) = WithJSONWarnings (f x) w instance Monoid a => Monoid (WithJSONWarnings a) where mempty = memptydefault mappend = mappenddefault -- | Warning output from 'WarningParser'. data JSONWarning = JSONUnrecognizedFields String [Text] instance Show JSONWarning where show (JSONUnrecognizedFields obj [field]) = "Unrecognized field in " <> obj <> ": " <> T.unpack field show (JSONUnrecognizedFields obj fields) = "Unrecognized fields in " <> obj <> ": " <> T.unpack (T.intercalate ", " fields) stack-1.5.1/src/Data/Attoparsec/Args.hs0000644000000000000000000000235713135652051016016 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} -- | Parsing of stack command line arguments module Data.Attoparsec.Args ( EscapingMode(..) , argsParser , parseArgs ) where import Control.Applicative import Data.Attoparsec.Text (()) import qualified Data.Attoparsec.Text as P import Data.Text (Text) -- | Mode for parsing escape characters. data EscapingMode = Escaping | NoEscaping deriving (Show,Eq,Enum) -- | Parse arguments using 'argsParser'. parseArgs :: EscapingMode -> Text -> Either String [String] parseArgs mode = P.parseOnly (argsParser mode) -- | A basic argument parser. It supports space-separated text, and -- string quotation with identity escaping: \x -> x. argsParser :: EscapingMode -> P.Parser [String] argsParser mode = many (P.skipSpace *> (quoted <|> unquoted)) <* P.skipSpace <* (P.endOfInput "unterminated string") where unquoted = P.many1 naked quoted = P.char '"' *> string <* P.char '"' string = many (case mode of Escaping -> escaped <|> nonquote NoEscaping -> nonquote) escaped = P.char '\\' *> P.anyChar nonquote = P.satisfy (/= '"') naked = P.satisfy (not . flip elem ("\" " :: String)) stack-1.5.1/src/Data/Attoparsec/Combinators.hs0000644000000000000000000000114613135652051017375 0ustar0000000000000000-- | More readable combinators for writing parsers. module Data.Attoparsec.Combinators where import Control.Applicative import Data.Monoid -- | Concatenate two parsers. appending :: (Applicative f,Monoid a) => f a -> f a -> f a appending a b = (<>) <$> a <*> b -- | Alternative parsers. alternating :: Alternative f => f a -> f a -> f a alternating a b = a <|> b -- | Pure something. pured :: (Applicative g,Applicative f) => g a -> g (f a) pured = fmap pure -- | Concatting the result of an action. concating :: (Monoid m,Applicative f) => f [m] -> f m concating = fmap mconcat stack-1.5.1/src/Data/Attoparsec/Interpreter.hs0000644000000000000000000001344013135652051017420 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {- | This module implements parsing of additional arguments embedded in a comment when stack is invoked as a script interpreter ===Specifying arguments in script interpreter mode @/stack/@ can execute a Haskell source file using @/runghc/@ and if required it can also install and setup the compiler and any package dependencies automatically. For using a Haskell source file as an executable script on a Unix like OS, the first line of the file must specify @stack@ as the interpreter using a shebang directive e.g. > #!/usr/bin/env stack Additional arguments can be specified in a haskell comment following the @#!@ line. The contents inside the comment must be a single valid stack command line, starting with @stack@ as the command and followed by the options to use for executing this file. The comment must be on the line immediately following the @#!@ line. The comment must start in the first column of the line. When using a block style comment the command can be split on multiple lines. Here is an example of a single line comment: > #!/usr/bin/env stack > -- stack --resolver lts-3.14 --install-ghc runghc --package random Here is an example of a multi line block comment: @ #!\/usr\/bin\/env stack {\- stack --resolver lts-3.14 --install-ghc runghc --package random -\} @ When the @#!@ line is not present, the file can still be executed using @stack \@ command if the file starts with a valid stack interpreter comment. This can be used to execute the file on Windows for example. Nested block comments are not supported. -} module Data.Attoparsec.Interpreter ( interpreterArgsParser -- for unit tests , getInterpreterArgs ) where import Control.Applicative import Data.Attoparsec.Args import Data.Attoparsec.Text (()) import qualified Data.Attoparsec.Text as P import Data.Char (isSpace) import Data.Conduit import Data.Conduit.Attoparsec import qualified Data.Conduit.Binary as CB import Data.Conduit.Text (decodeUtf8) import Data.List (intercalate) import Data.Text (pack) import Stack.Constants import System.FilePath (takeExtension) import System.IO (IOMode (ReadMode), withBinaryFile, stderr, hPutStrLn) -- | Parser to extract the stack command line embedded inside a comment -- after validating the placement and formatting rules for a valid -- interpreter specification. interpreterArgsParser :: Bool -> String -> P.Parser String interpreterArgsParser isLiterate progName = P.option "" sheBangLine *> interpreterComment where sheBangLine = P.string "#!" *> P.manyTill P.anyChar P.endOfLine commentStart psr = (psr (progName ++ " options comment")) *> P.skipSpace *> (P.string (pack progName) show progName) -- Treat newlines as spaces inside the block comment anyCharNormalizeSpace = let normalizeSpace c = if isSpace c then ' ' else c in P.satisfyWith normalizeSpace $ const True comment start end = commentStart start *> ((end >> return "") <|> (P.space *> (P.manyTill anyCharNormalizeSpace end "-}"))) horizontalSpace = P.satisfy P.isHorizontalSpace lineComment = comment "--" (P.endOfLine <|> P.endOfInput) literateLineComment = comment (">" *> horizontalSpace *> "--") (P.endOfLine <|> P.endOfInput) blockComment = comment "{-" (P.string "-}") literateBlockComment = (">" *> horizontalSpace *> "{-") *> P.skipMany (("" <$ horizontalSpace) <|> (P.endOfLine *> ">")) *> (P.string (pack progName) progName) *> P.manyTill' (P.satisfy (not . P.isEndOfLine) <|> (' ' <$ (P.endOfLine *> ">" ">"))) "-}" interpreterComment = if isLiterate then literateLineComment <|> literateBlockComment else lineComment <|> blockComment -- | Extract stack arguments from a correctly placed and correctly formatted -- comment when it is being used as an interpreter getInterpreterArgs :: String -> IO [String] getInterpreterArgs file = do eArgStr <- withBinaryFile file ReadMode parseFile case eArgStr of Left err -> handleFailure $ decodeError err Right str -> parseArgStr str where parseFile h = CB.sourceHandle h =$= decodeUtf8 $$ sinkParserEither (interpreterArgsParser isLiterate stackProgName) isLiterate = takeExtension file == ".lhs" -- FIXME We should print anything only when explicit verbose mode is -- specified by the user on command line. But currently the -- implementation does not accept or parse any command line flags in -- interpreter mode. We can only invoke the interpreter as -- "stack " strictly without any options. stackWarn s = hPutStrLn stderr $ stackProgName ++ ": WARNING! " ++ s handleFailure err = do mapM_ stackWarn (lines err) stackWarn "Missing or unusable stack options specification" stackWarn "Using runghc without any additional stack options" return ["runghc"] parseArgStr str = case P.parseOnly (argsParser Escaping) (pack str) of Left err -> handleFailure ("Error parsing command specified in the \ \stack options comment: " ++ err) Right [] -> handleFailure "Empty argument list in stack options comment" Right args -> return args decodeError e = case e of ParseError ctxs _ (Position line col) -> if null ctxs then "Parse error" else ("Expecting " ++ intercalate " or " ctxs) ++ " at line " ++ show line ++ ", column " ++ show col DivergentParser -> "Divergent parser" stack-1.5.1/src/Data/IORef/RunOnce.hs0000644000000000000000000000064713135652051015332 0ustar0000000000000000module Data.IORef.RunOnce (runOnce) where import Control.Monad.IO.Class import Data.IORef runOnce :: MonadIO m => m a -> m (m a) runOnce f = do ref <- liftIO $ newIORef Nothing return $ do mval <- liftIO $ readIORef ref case mval of Just val -> return val Nothing -> do val <- f liftIO $ writeIORef ref (Just val) return val stack-1.5.1/src/Data/Maybe/Extra.hs0000644000000000000000000000134713135652051015133 0ustar0000000000000000-- | Extra Maybe utilities. module Data.Maybe.Extra where import Control.Applicative import Control.Monad import Data.Traversable hiding (mapM) import Data.Maybe import Prelude -- Silence redundant import warnings -- | Applicative 'mapMaybe'. mapMaybeA :: Applicative f => (a -> f (Maybe b)) -> [a] -> f [b] mapMaybeA f = fmap catMaybes . traverse f -- | @'forMaybeA' '==' 'flip' 'mapMaybeA'@ forMaybeA :: Applicative f => [a] -> (a -> f (Maybe b)) -> f [b] forMaybeA = flip mapMaybeA -- | Monadic 'mapMaybe'. mapMaybeM :: Monad m => (a -> m (Maybe b)) -> [a] -> m [b] mapMaybeM f = liftM catMaybes . mapM f -- | @'forMaybeM' '==' 'flip' 'mapMaybeM'@ forMaybeM :: Monad m => [a] -> (a -> m (Maybe b)) -> m [b] forMaybeM = flip mapMaybeM stack-1.5.1/src/Data/Monoid/Extra.hs0000644000000000000000000000032713135652051015320 0ustar0000000000000000-- | Extra Monoid utilities. module Data.Monoid.Extra ( fromFirst , module Data.Monoid ) where import Data.Maybe import Data.Monoid fromFirst :: a -> First a -> a fromFirst x = fromMaybe x . getFirst stack-1.5.1/src/Data/Store/VersionTagged.hs0000644000000000000000000001024513135652051016645 0ustar0000000000000000{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ViewPatterns #-} -- | Tag a Store instance with structural version info to ensure we're -- reading a compatible format. module Data.Store.VersionTagged ( versionedEncodeFile , versionedDecodeOrLoad , versionedDecodeFile , storeVersionConfig ) where import Control.Applicative import Control.Exception.Lifted (catch, IOException, assert) import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Logger import Control.Monad.Trans.Control (MonadBaseControl) import qualified Data.ByteString as BS import Data.Data (Data) import qualified Data.Map as M import Data.Monoid ((<>)) import qualified Data.Set as S import Data.Store import Data.Store.Core (unsafeEncodeWith) import Data.Store.Version import qualified Data.Text as T import Language.Haskell.TH import Path import Path.IO (ensureDir) import Prelude versionedEncodeFile :: Data a => VersionConfig a -> Q Exp versionedEncodeFile vc = [e| storeEncodeFile $(encodeWithVersionQ vc) $(decodeWithVersionQ vc) |] versionedDecodeOrLoad :: Data a => VersionConfig a -> Q Exp versionedDecodeOrLoad vc = [| versionedDecodeOrLoadImpl $(encodeWithVersionQ vc) $(decodeWithVersionQ vc) |] versionedDecodeFile :: Data a => VersionConfig a -> Q Exp versionedDecodeFile vc = [e| versionedDecodeFileImpl $(decodeWithVersionQ vc) |] -- | Write to the given file. storeEncodeFile :: (Store a, MonadIO m, MonadLogger m, Eq a) => (a -> (Int, Poke ())) -> Peek a -> Path Abs File -> a -> m () storeEncodeFile pokeFunc peekFunc fp x = do let fpt = T.pack (toFilePath fp) $logDebug $ "Encoding " <> fpt ensureDir (parent fp) let (sz, poker) = pokeFunc x encoded = unsafeEncodeWith poker sz assert (decodeExWith peekFunc encoded == x) $ liftIO $ BS.writeFile (toFilePath fp) encoded $logDebug $ "Finished writing " <> fpt -- | Read from the given file. If the read fails, run the given action and -- write that back to the file. Always starts the file off with the -- version tag. versionedDecodeOrLoadImpl :: (Store a, Eq a, MonadIO m, MonadLogger m, MonadBaseControl IO m) => (a -> (Int, Poke ())) -> Peek a -> Path Abs File -> m a -> m a versionedDecodeOrLoadImpl pokeFunc peekFunc fp mx = do let fpt = T.pack (toFilePath fp) $logDebug $ "Trying to decode " <> fpt mres <- versionedDecodeFileImpl peekFunc fp case mres of Just x -> do $logDebug $ "Success decoding " <> fpt return x _ -> do $logDebug $ "Failure decoding " <> fpt x <- mx storeEncodeFile pokeFunc peekFunc fp x return x versionedDecodeFileImpl :: (Store a, MonadIO m, MonadLogger m, MonadBaseControl IO m) => Peek a -> Path loc File -> m (Maybe a) versionedDecodeFileImpl peekFunc fp = do mbs <- liftIO (Just <$> BS.readFile (toFilePath fp)) `catch` \(err :: IOException) -> do $logDebug ("Exception ignored when attempting to load " <> T.pack (toFilePath fp) <> ": " <> T.pack (show err)) return Nothing case mbs of Nothing -> return Nothing Just bs -> liftIO (Just <$> decodeIOWith peekFunc bs) `catch` \(err :: PeekException) -> do let fpt = T.pack (toFilePath fp) $logDebug ("Error while decoding " <> fpt <> ": " <> T.pack (show err) <> " (this might not be an error, when switching between stack versions)") return Nothing storeVersionConfig :: String -> String -> VersionConfig a storeVersionConfig name hash = (namedVersionConfig name hash) { vcIgnore = S.fromList [ "Data.Vector.Unboxed.Base.Vector GHC.Types.Word" , "Data.ByteString.Internal.ByteString" ] , vcRenames = M.fromList [ ( "Data.Maybe.Maybe", "GHC.Base.Maybe") ] } stack-1.5.1/src/Data/Text/Extra.hs0000644000000000000000000000037113135652051015016 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Data.Text.Extra where import Data.Maybe (fromMaybe) import qualified Data.Text as T -- | Strip trailing carriage return from Text stripCR :: T.Text -> T.Text stripCR t = fromMaybe t (T.stripSuffix "\r" t) stack-1.5.1/src/Distribution/Version/Extra.hs0000644000000000000000000000210513135652051017322 0ustar0000000000000000-- A separate module so that we can contain all usage of deprecated identifiers here {-# OPTIONS_GHC -fno-warn-warnings-deprecations #-} module Distribution.Version.Extra ( hasUpper , hasLower ) where import Distribution.Version (VersionRange (..)) -- | Does the version range have an upper bound? hasUpper :: VersionRange -> Bool hasUpper AnyVersion = False hasUpper (ThisVersion _) = True hasUpper (LaterVersion _) = False hasUpper (EarlierVersion _) = True hasUpper (WildcardVersion _) = True hasUpper (UnionVersionRanges x y) = hasUpper x && hasUpper y hasUpper (IntersectVersionRanges x y) = hasUpper x || hasUpper y hasUpper (VersionRangeParens x) = hasUpper x -- | Does the version range have a lower bound? hasLower :: VersionRange -> Bool hasLower AnyVersion = False hasLower (ThisVersion _) = True hasLower (LaterVersion _) = True hasLower (EarlierVersion _) = False hasLower (WildcardVersion _) = True hasLower (UnionVersionRanges x y) = hasLower x && hasLower y hasLower (IntersectVersionRanges x y) = hasLower x || hasLower y hasLower (VersionRangeParens x) = hasLower x stack-1.5.1/src/Network/HTTP/Download.hs0000644000000000000000000001212013135652051016110 0ustar0000000000000000{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Network.HTTP.Download ( verifiedDownload , DownloadRequest(..) , drRetryPolicyDefault , HashCheck(..) , DownloadException(..) , CheckHexDigest(..) , LengthCheck , VerifiedDownloadException(..) , download , redownload , httpJSON , parseRequest , parseUrlThrow , setGithubHeaders ) where import Control.Exception (Exception) import Control.Exception.Safe (handleIO) import Control.Monad (void) import Control.Monad.Catch (throwM) import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Logger (MonadLogger, logDebug) import qualified Data.ByteString.Lazy as L import Data.Conduit (runConduit, runConduitRes, (.|), yield) import Data.Conduit.Binary (sourceHandle) import qualified Data.Conduit.Binary as CB import Data.Foldable (forM_) import Data.Monoid ((<>)) import Data.Text.Encoding.Error (lenientDecode) import Data.Text.Encoding (decodeUtf8With) import Data.Typeable (Typeable) import Network.HTTP.Client (Request, Response, path, checkResponse, parseUrlThrow, parseRequest) import Network.HTTP.Client.Conduit (requestHeaders) import Network.HTTP.Download.Verified import Network.HTTP.Simple (httpJSON, withResponse, getResponseBody, getResponseHeaders, getResponseStatusCode, setRequestHeader) import Path (Abs, File, Path, toFilePath) import System.Directory (createDirectoryIfMissing, removeFile) import System.FilePath (takeDirectory, (<.>)) import System.IO (IOMode (ReadMode), withBinaryFile) -- | Download the given URL to the given location. If the file already exists, -- no download is performed. Otherwise, creates the parent directory, downloads -- to a temporary file, and on file download completion moves to the -- appropriate destination. -- -- Throws an exception if things go wrong download :: (MonadIO m, MonadLogger m) => Request -> Path Abs File -- ^ destination -> m Bool -- ^ Was a downloaded performed (True) or did the file already exist (False)? download req destpath = do let downloadReq = DownloadRequest { drRequest = req , drHashChecks = [] , drLengthCheck = Nothing , drRetryPolicy = drRetryPolicyDefault } let progressHook _ = return () verifiedDownload downloadReq destpath progressHook -- | Same as 'download', but will download a file a second time if it is already present. -- -- Returns 'True' if the file was downloaded, 'False' otherwise redownload :: (MonadIO m, MonadLogger m) => Request -> Path Abs File -- ^ destination -> m Bool redownload req0 dest = do $logDebug $ "Downloading " <> decodeUtf8With lenientDecode (path req0) let destFilePath = toFilePath dest etagFilePath = destFilePath <.> "etag" metag <- liftIO $ handleIO (const $ return Nothing) $ fmap Just $ withBinaryFile etagFilePath ReadMode $ \h -> runConduit $ sourceHandle h .| CB.take 512 let req1 = case metag of Nothing -> req0 Just etag -> req0 { requestHeaders = requestHeaders req0 ++ [("If-None-Match", L.toStrict etag)] } req2 = req1 { checkResponse = \_ _ -> return () } liftIO $ recoveringHttp drRetryPolicyDefault $ withResponse req2 $ \res -> case getResponseStatusCode res of 200 -> do createDirectoryIfMissing True $ takeDirectory destFilePath -- Order here is important: first delete the etag, then write the -- file, then write the etag. That way, if any step fails, it will -- force the download to happen again. handleIO (const $ return ()) $ removeFile etagFilePath runConduitRes $ getResponseBody res .| CB.sinkFileCautious destFilePath forM_ (lookup "ETag" (getResponseHeaders res)) $ \e -> runConduitRes $ yield e .| CB.sinkFileCautious etagFilePath return True 304 -> return False _ -> throwM $ RedownloadFailed req2 dest $ void res data DownloadException = RedownloadFailed Request (Path Abs File) (Response ()) deriving (Show, Typeable) instance Exception DownloadException -- | Set the user-agent request header setGithubHeaders :: Request -> Request setGithubHeaders = setRequestHeader "User-Agent" ["The Haskell Stack"] . setRequestHeader "Accept" ["application/vnd.github.v3+json"] stack-1.5.1/src/Network/HTTP/Download/Verified.hs0000644000000000000000000002771613135652051017666 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE PackageImports #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} module Network.HTTP.Download.Verified ( verifiedDownload , recoveringHttp , DownloadRequest(..) , drRetryPolicyDefault , HashCheck(..) , CheckHexDigest(..) , LengthCheck , VerifiedDownloadException(..) ) where import qualified Data.List as List import qualified Data.ByteString as ByteString import qualified Data.ByteString.Base64 as B64 import qualified Data.Conduit.Binary as CB import qualified Data.Conduit.List as CL import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import Control.Applicative import Control.Monad import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.Logger (logDebug, MonadLogger) import Control.Retry (recovering,limitRetries,RetryPolicy,constantDelay) import Crypto.Hash import Crypto.Hash.Conduit (sinkHash) import Data.ByteArray as Mem (convert) import Data.ByteArray.Encoding as Mem (convertToBase, Base(Base16)) import Data.ByteString (ByteString) import Data.ByteString.Char8 (readInteger) import Data.Conduit import Data.Conduit.Binary (sourceHandle, sinkHandle) import Data.Foldable (traverse_,for_) import Data.Monoid import Data.String import Data.Text.Encoding (decodeUtf8With) import Data.Text.Encoding.Error (lenientDecode) import Data.Typeable (Typeable) import GHC.IO.Exception (IOException(..),IOErrorType(..)) import Network.HTTP.Client (getUri, path) import Network.HTTP.Simple (Request, HttpException, httpSink, getResponseHeaders) import Network.HTTP.Types.Header (hContentLength, hContentMD5) import Path import Prelude -- Fix AMP warning import System.Directory import System.FilePath ((<.>)) import System.IO -- | A request together with some checks to perform. data DownloadRequest = DownloadRequest { drRequest :: Request , drHashChecks :: [HashCheck] , drLengthCheck :: Maybe LengthCheck , drRetryPolicy :: RetryPolicy } -- | Default to retrying thrice with a short constant delay. drRetryPolicyDefault :: RetryPolicy drRetryPolicyDefault = limitRetries 3 <> constantDelay onehundredMilliseconds where onehundredMilliseconds = 100000 data HashCheck = forall a. (Show a, HashAlgorithm a) => HashCheck { hashCheckAlgorithm :: a , hashCheckHexDigest :: CheckHexDigest } deriving instance Show HashCheck data CheckHexDigest = CheckHexDigestString String | CheckHexDigestByteString ByteString | CheckHexDigestHeader ByteString deriving Show instance IsString CheckHexDigest where fromString = CheckHexDigestString type LengthCheck = Int -- | An exception regarding verification of a download. data VerifiedDownloadException = WrongContentLength Request Int -- expected ByteString -- actual (as listed in the header) | WrongStreamLength Request Int -- expected Int -- actual | WrongDigest Request String -- algorithm CheckHexDigest -- expected String -- actual (shown) deriving (Typeable) instance Show VerifiedDownloadException where show (WrongContentLength req expected actual) = "Download expectation failure: ContentLength header\n" ++ "Expected: " ++ show expected ++ "\n" ++ "Actual: " ++ displayByteString actual ++ "\n" ++ "For: " ++ show (getUri req) show (WrongStreamLength req expected actual) = "Download expectation failure: download size\n" ++ "Expected: " ++ show expected ++ "\n" ++ "Actual: " ++ show actual ++ "\n" ++ "For: " ++ show (getUri req) show (WrongDigest req algo expected actual) = "Download expectation failure: content hash (" ++ algo ++ ")\n" ++ "Expected: " ++ displayCheckHexDigest expected ++ "\n" ++ "Actual: " ++ show actual ++ "\n" ++ "For: " ++ show (getUri req) instance Exception VerifiedDownloadException -- This exception is always caught and never thrown outside of this module. data VerifyFileException = WrongFileSize Int -- expected Integer -- actual (as listed by hFileSize) deriving (Show, Typeable) instance Exception VerifyFileException -- Show a ByteString that is known to be UTF8 encoded. displayByteString :: ByteString -> String displayByteString = Text.unpack . Text.strip . Text.decodeUtf8 -- Show a CheckHexDigest in human-readable format. displayCheckHexDigest :: CheckHexDigest -> String displayCheckHexDigest (CheckHexDigestString s) = s ++ " (String)" displayCheckHexDigest (CheckHexDigestByteString s) = displayByteString s ++ " (ByteString)" displayCheckHexDigest (CheckHexDigestHeader h) = show (B64.decodeLenient h) ++ " (Header. unencoded: " ++ show h ++ ")" -- | Make sure that the hash digest for a finite stream of bytes -- is as expected. -- -- Throws WrongDigest (VerifiedDownloadException) sinkCheckHash :: MonadThrow m => Request -> HashCheck -> Consumer ByteString m () sinkCheckHash req HashCheck{..} = do digest <- sinkHashUsing hashCheckAlgorithm let actualDigestString = show digest let actualDigestHexByteString = Mem.convertToBase Mem.Base16 digest let actualDigestBytes = Mem.convert digest let passedCheck = case hashCheckHexDigest of CheckHexDigestString s -> s == actualDigestString CheckHexDigestByteString b -> b == actualDigestHexByteString CheckHexDigestHeader b -> B64.decodeLenient b == actualDigestHexByteString || B64.decodeLenient b == actualDigestBytes -- A hack to allow hackage tarballs to download. -- They should really base64-encode their md5 header as per rfc2616#sec14.15. -- https://github.com/commercialhaskell/stack/issues/240 || b == actualDigestHexByteString unless passedCheck $ throwM $ WrongDigest req (show hashCheckAlgorithm) hashCheckHexDigest actualDigestString assertLengthSink :: MonadThrow m => Request -> LengthCheck -> ZipSink ByteString m () assertLengthSink req expectedStreamLength = ZipSink $ do Sum actualStreamLength <- CL.foldMap (Sum . ByteString.length) when (actualStreamLength /= expectedStreamLength) $ throwM $ WrongStreamLength req expectedStreamLength actualStreamLength -- | A more explicitly type-guided sinkHash. sinkHashUsing :: (Monad m, HashAlgorithm a) => a -> Consumer ByteString m (Digest a) sinkHashUsing _ = sinkHash -- | Turns a list of hash checks into a ZipSink that checks all of them. hashChecksToZipSink :: MonadThrow m => Request -> [HashCheck] -> ZipSink ByteString m () hashChecksToZipSink req = traverse_ (ZipSink . sinkCheckHash req) -- 'Control.Retry.recovering' customized for HTTP failures recoveringHttp :: (MonadMask m, MonadIO m) => RetryPolicy -> m a -> m a recoveringHttp retryPolicy = #if MIN_VERSION_retry(0,7,0) recovering retryPolicy handlers . const #else recovering retryPolicy handlers #endif where handlers = [const $ Handler alwaysRetryHttp,const $ Handler retrySomeIO] alwaysRetryHttp :: Monad m => HttpException -> m Bool alwaysRetryHttp _ = return True retrySomeIO :: Monad m => IOException -> m Bool retrySomeIO e = return $ case ioe_type e of -- hGetBuf: resource vanished (Connection reset by peer) ResourceVanished -> True -- conservatively exclude all others _ -> False -- | Copied and extended version of Network.HTTP.Download.download. -- -- Has the following additional features: -- * Verifies that response content-length header (if present) -- matches expected length -- * Limits the download to (close to) the expected # of bytes -- * Verifies that the expected # bytes were downloaded (not too few) -- * Verifies md5 if response includes content-md5 header -- * Verifies the expected hashes -- -- Throws VerifiedDownloadException. -- Throws IOExceptions related to file system operations. -- Throws HttpException. verifiedDownload :: (MonadIO m, MonadLogger m) => DownloadRequest -> Path Abs File -- ^ destination -> (Maybe Integer -> Sink ByteString IO ()) -- ^ custom hook to observe progress -> m Bool -- ^ Whether a download was performed verifiedDownload DownloadRequest{..} destpath progressSink = do let req = drRequest whenM' (liftIO getShouldDownload) $ do $logDebug $ "Downloading " <> decodeUtf8With lenientDecode (path req) liftIO $ do createDirectoryIfMissing True dir recoveringHttp drRetryPolicy $ withBinaryFile fptmp WriteMode $ \h -> httpSink req (go h) renameFile fptmp fp where whenM' mp m = do p <- mp if p then m >> return True else return False fp = toFilePath destpath fptmp = fp <.> "tmp" dir = toFilePath $ parent destpath getShouldDownload = do fileExists <- doesFileExist fp if fileExists -- only download if file does not match expectations then not <$> fileMatchesExpectations -- or if it doesn't exist yet else return True -- precondition: file exists -- TODO: add logging fileMatchesExpectations = ((checkExpectations >> return True) `catch` \(_ :: VerifyFileException) -> return False) `catch` \(_ :: VerifiedDownloadException) -> return False checkExpectations = bracket (openFile fp ReadMode) hClose $ \h -> do for_ drLengthCheck $ checkFileSizeExpectations h sourceHandle h $$ getZipSink (hashChecksToZipSink drRequest drHashChecks) -- doesn't move the handle checkFileSizeExpectations h expectedFileSize = do fileSizeInteger <- hFileSize h when (fileSizeInteger > toInteger (maxBound :: Int)) $ throwM $ WrongFileSize expectedFileSize fileSizeInteger let fileSize = fromInteger fileSizeInteger when (fileSize /= expectedFileSize) $ throwM $ WrongFileSize expectedFileSize fileSizeInteger checkContentLengthHeader headers expectedContentLength = case List.lookup hContentLength headers of Just lengthBS -> do let lengthStr = displayByteString lengthBS when (lengthStr /= show expectedContentLength) $ throwM $ WrongContentLength drRequest expectedContentLength lengthBS _ -> return () go h res = do let headers = getResponseHeaders res mcontentLength = do hLength <- List.lookup hContentLength headers (i,_) <- readInteger hLength return i for_ drLengthCheck $ checkContentLengthHeader headers let hashChecks = (case List.lookup hContentMD5 headers of Just md5BS -> [ HashCheck { hashCheckAlgorithm = MD5 , hashCheckHexDigest = CheckHexDigestHeader md5BS } ] Nothing -> [] ) ++ drHashChecks maybe id (\len -> (CB.isolate len =$=)) drLengthCheck $ getZipSink ( hashChecksToZipSink drRequest hashChecks *> maybe (pure ()) (assertLengthSink drRequest) drLengthCheck *> ZipSink (sinkHandle h) *> ZipSink (progressSink mcontentLength)) stack-1.5.1/src/Options/Applicative/Args.hs0000644000000000000000000000276713135652051016741 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} -- | Accepting arguments to be passed through to a sub-process. module Options.Applicative.Args (argsArgument ,argsOption ,cmdOption ,parseArgsFromString) where import Data.Attoparsec.Args import qualified Data.Attoparsec.Text as P import qualified Data.Text as T import qualified Options.Applicative as O -- | An argument which accepts a list of arguments e.g. @--ghc-options="-X P.hs \"this\""@. argsArgument :: O.Mod O.ArgumentFields [String] -> O.Parser [String] argsArgument = O.argument (do string <- O.str either O.readerError return (parseArgsFromString string)) -- | An option which accepts a list of arguments e.g. @--ghc-options="-X P.hs \"this\""@. argsOption :: O.Mod O.OptionFields [String] -> O.Parser [String] argsOption = O.option (do string <- O.str either O.readerError return (parseArgsFromString string)) -- | An option which accepts a command and a list of arguments e.g. @--exec "echo hello world"@ cmdOption :: O.Mod O.OptionFields (String, [String]) -> O.Parser (String, [String]) cmdOption = O.option (do string <- O.str xs <- either O.readerError return (parseArgsFromString string) case xs of [] -> O.readerError "Must provide a command" x:xs' -> return (x, xs')) -- | Parse from a string. parseArgsFromString :: String -> Either String [String] parseArgsFromString = P.parseOnly (argsParser Escaping) . T.pack stack-1.5.1/src/Options/Applicative/Builder/Extra.hs0000644000000000000000000002463113135652051020510 0ustar0000000000000000{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} -- | Extra functions for optparse-applicative. module Options.Applicative.Builder.Extra (boolFlags ,boolFlagsNoDefault ,maybeBoolFlags ,firstBoolFlags ,enableDisableFlags ,enableDisableFlagsNoDefault ,extraHelpOption ,execExtraHelp ,textOption ,textArgument ,optionalFirst ,absFileOption ,relFileOption ,absDirOption ,relDirOption ,eitherReader' ,fileCompleter ,fileExtCompleter ,dirCompleter ,PathCompleterOpts(..) ,defaultPathCompleterOpts ,pathCompleterWith ,unescapeBashArg ) where import Control.Exception (IOException, catch) import Control.Monad (when, forM) import Data.Either.Combinators import Data.List (isPrefixOf) import Data.Maybe import Data.Monoid import Data.Text (Text) import qualified Data.Text as T import Options.Applicative import Options.Applicative.Types (readerAsk) import Path hiding (()) import System.Directory (getCurrentDirectory, getDirectoryContents, doesDirectoryExist) import System.Environment (withArgs) import System.FilePath (takeBaseName, (), splitFileName, isRelative, takeExtension) -- | Enable/disable flags for a 'Bool'. boolFlags :: Bool -- ^ Default value -> String -- ^ Flag name -> String -- ^ Help suffix -> Mod FlagFields Bool -> Parser Bool boolFlags defaultValue = enableDisableFlags defaultValue True False -- | Enable/disable flags for a 'Bool', without a default case (to allow chaining with '<|>'). boolFlagsNoDefault :: String -- ^ Flag name -> String -- ^ Help suffix -> Mod FlagFields Bool -> Parser Bool boolFlagsNoDefault = enableDisableFlagsNoDefault True False -- | Enable/disable flags for a @('Maybe' 'Bool')@. maybeBoolFlags :: String -- ^ Flag name -> String -- ^ Help suffix -> Mod FlagFields (Maybe Bool) -> Parser (Maybe Bool) maybeBoolFlags = enableDisableFlags Nothing (Just True) (Just False) -- | Like 'maybeBoolFlags', but parsing a 'First'. firstBoolFlags :: String -> String -> Mod FlagFields (Maybe Bool) -> Parser (First Bool) firstBoolFlags long0 help0 mod0 = First <$> maybeBoolFlags long0 help0 mod0 -- | Enable/disable flags for any type. enableDisableFlags :: a -- ^ Default value -> a -- ^ Enabled value -> a -- ^ Disabled value -> String -- ^ Name -> String -- ^ Help suffix -> Mod FlagFields a -> Parser a enableDisableFlags defaultValue enabledValue disabledValue name helpSuffix mods = enableDisableFlagsNoDefault enabledValue disabledValue name helpSuffix mods <|> pure defaultValue -- | Enable/disable flags for any type, without a default (to allow chaining with '<|>') enableDisableFlagsNoDefault :: a -- ^ Enabled value -> a -- ^ Disabled value -> String -- ^ Name -> String -- ^ Help suffix -> Mod FlagFields a -> Parser a enableDisableFlagsNoDefault enabledValue disabledValue name helpSuffix mods = last <$> some ((flag' enabledValue (hidden <> internal <> long name <> help helpSuffix <> mods) <|> flag' disabledValue (hidden <> internal <> long ("no-" ++ name) <> help helpSuffix <> mods)) <|> flag' disabledValue (long ("[no-]" ++ name) <> help ("Enable/disable " ++ helpSuffix) <> mods)) -- | Show an extra help option (e.g. @--docker-help@ shows help for all @--docker*@ args). -- -- To actually have that help appear, use 'execExtraHelp' before executing the main parser. extraHelpOption :: Bool -- ^ Hide from the brief description? -> String -- ^ Program name, e.g. @"stack"@ -> String -- ^ Option glob expression, e.g. @"docker*"@ -> String -- ^ Help option name, e.g. @"docker-help"@ -> Parser (a -> a) extraHelpOption hide progName fakeName helpName = infoOption (optDesc' ++ ".") (long helpName <> hidden <> internal) <*> infoOption (optDesc' ++ ".") (long fakeName <> help optDesc' <> (if hide then hidden <> internal else idm)) where optDesc' = concat ["Run '", takeBaseName progName, " --", helpName, "' for details"] -- | Display extra help if extra help option passed in arguments. -- -- Since optparse-applicative doesn't allow an arbitrary IO action for an 'abortOption', this -- was the best way I found that doesn't require manually formatting the help. execExtraHelp :: [String] -- ^ Command line arguments -> String -- ^ Extra help option name, e.g. @"docker-help"@ -> Parser a -- ^ Option parser for the relevant command -> String -- ^ Option description -> IO () execExtraHelp args helpOpt parser pd = when (args == ["--" ++ helpOpt]) $ withArgs ["--help"] $ do _ <- execParser (info (hiddenHelper <*> ((,) <$> parser <*> some (strArgument (metavar "OTHER ARGUMENTS")))) (fullDesc <> progDesc pd)) return () where hiddenHelper = abortOption ShowHelpText (long "help" <> hidden <> internal) -- | 'option', specialized to 'Text'. textOption :: Mod OptionFields Text -> Parser Text textOption = option (T.pack <$> readerAsk) -- | 'argument', specialized to 'Text'. textArgument :: Mod ArgumentFields Text -> Parser Text textArgument = argument (T.pack <$> readerAsk) -- | Like 'optional', but returning a 'First'. optionalFirst :: Alternative f => f a -> f (First a) optionalFirst = fmap First . optional absFileOption :: Mod OptionFields (Path Abs File) -> Parser (Path Abs File) absFileOption mods = option (eitherReader' parseAbsFile) $ completer (pathCompleterWith defaultPathCompleterOpts { pcoRelative = False }) <> mods relFileOption :: Mod OptionFields (Path Rel File) -> Parser (Path Rel File) relFileOption mods = option (eitherReader' parseRelFile) $ completer (pathCompleterWith defaultPathCompleterOpts { pcoAbsolute = False }) <> mods absDirOption :: Mod OptionFields (Path Abs Dir) -> Parser (Path Abs Dir) absDirOption mods = option (eitherReader' parseAbsDir) $ completer (pathCompleterWith defaultPathCompleterOpts { pcoRelative = False, pcoFileFilter = const False }) <> mods relDirOption :: Mod OptionFields (Path Rel Dir) -> Parser (Path Rel Dir) relDirOption mods = option (eitherReader' parseRelDir) $ completer (pathCompleterWith defaultPathCompleterOpts { pcoAbsolute = False, pcoFileFilter = const False }) <> mods -- | Like 'eitherReader', but accepting any @'Show' e@ on the 'Left'. eitherReader' :: Show e => (String -> Either e a) -> ReadM a eitherReader' f = eitherReader (mapLeft show . f) data PathCompleterOpts = PathCompleterOpts { pcoAbsolute :: Bool , pcoRelative :: Bool , pcoRootDir :: Maybe FilePath , pcoFileFilter :: FilePath -> Bool , pcoDirFilter :: FilePath -> Bool } defaultPathCompleterOpts :: PathCompleterOpts defaultPathCompleterOpts = PathCompleterOpts { pcoAbsolute = True , pcoRelative = True , pcoRootDir = Nothing , pcoFileFilter = const True , pcoDirFilter = const True } fileCompleter :: Completer fileCompleter = pathCompleterWith defaultPathCompleterOpts fileExtCompleter :: [String] -> Completer fileExtCompleter exts = pathCompleterWith defaultPathCompleterOpts { pcoFileFilter = (`elem` exts) . takeExtension } dirCompleter :: Completer dirCompleter = pathCompleterWith defaultPathCompleterOpts { pcoFileFilter = const False } pathCompleterWith :: PathCompleterOpts -> Completer pathCompleterWith PathCompleterOpts {..} = mkCompleter $ \inputRaw -> do -- Unescape input, to handle single and double quotes. Note that the -- results do not need to be re-escaped, due to some fiddly bash -- magic. let input = unescapeBashArg inputRaw let (inputSearchDir0, searchPrefix) = splitFileName input inputSearchDir = if inputSearchDir0 == "./" then "" else inputSearchDir0 msearchDir <- case (isRelative inputSearchDir, pcoAbsolute, pcoRelative) of (True, _, True) -> do rootDir <- maybe getCurrentDirectory return pcoRootDir return $ Just (rootDir inputSearchDir) (False, True, _) -> return $ Just inputSearchDir _ -> return Nothing case msearchDir of Nothing | input == "" && pcoAbsolute -> return ["/"] | otherwise -> return [] Just searchDir -> do entries <- getDirectoryContents searchDir `catch` \(_ :: IOException) -> return [] fmap catMaybes $ forM entries $ \entry -> -- Skip . and .. unless user is typing . or .. if entry `elem` ["..", "."] && searchPrefix `notElem` ["..", "."] then return Nothing else if searchPrefix `isPrefixOf` entry then do let path = searchDir entry case (pcoFileFilter path, pcoDirFilter path) of (True, True) -> return $ Just (inputSearchDir entry) (fileAllowed, dirAllowed) -> do isDir <- doesDirectoryExist path if (if isDir then dirAllowed else fileAllowed) then return $ Just (inputSearchDir entry) else return Nothing else return Nothing unescapeBashArg :: String -> String unescapeBashArg ('\'' : rest) = rest unescapeBashArg ('\"' : rest) = go rest where go [] = [] go ('\\' : x : xs) | x `elem` "$`\"\\\n" = x : xs | otherwise = '\\' : x : go xs go (x : xs) = x : go xs unescapeBashArg input = go input where go [] = [] go ('\\' : x : xs) = x : go xs go (x : xs) = x : go xs stack-1.5.1/src/Options/Applicative/Complicated.hs0000644000000000000000000001342213135652051020257 0ustar0000000000000000-- | Simple interface to complicated program arguments. -- -- This is a "fork" of the @optparse-simple@ package that has some workarounds for -- optparse-applicative issues that become problematic with programs that have many options and -- subcommands. Because it makes the interface more complex, these workarounds are not suitable for -- pushing upstream to optparse-applicative. module Options.Applicative.Complicated ( addCommand , addSubCommands , complicatedOptions , complicatedParser ) where import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Either import Control.Monad.Trans.Writer import Data.Monoid import Data.Version import Options.Applicative import Options.Applicative.Types import Options.Applicative.Builder.Internal import System.Environment -- | Generate and execute a complicated options parser. complicatedOptions :: Monoid a => Version -- ^ numeric version -> Maybe String -- ^ version string -> String -- ^ hpack numeric version, as string -> String -- ^ header -> String -- ^ program description (displayed between usage and options listing in the help output) -> String -- ^ footer -> Parser a -- ^ common settings -> Maybe (ParserFailure ParserHelp -> [String] -> IO (a,(b,a))) -- ^ optional handler for parser failure; 'handleParseResult' is called by -- default -> EitherT b (Writer (Mod CommandFields (b,a))) () -- ^ commands (use 'addCommand') -> IO (a,b) complicatedOptions numericVersion versionString numericHpackVersion h pd footerStr commonParser mOnFailure commandParser = do args <- getArgs (a,(b,c)) <- case execParserPure (prefs noBacktrack) parser args of Failure _ | null args -> withArgs ["--help"] (execParser parser) -- call onFailure handler if it's present and parsing options failed Failure f | Just onFailure <- mOnFailure -> onFailure f args parseResult -> handleParseResult parseResult return (mappend c a,b) where parser = info (helpOption <*> versionOptions <*> complicatedParser "COMMAND|FILE" commonParser commandParser) desc desc = fullDesc <> header h <> progDesc pd <> footer footerStr versionOptions = case versionString of Nothing -> versionOption (showVersion numericVersion) Just s -> versionOption s <*> numericVersionOption <*> numericHpackVersionOption versionOption s = infoOption s (long "version" <> help "Show version") numericVersionOption = infoOption (showVersion numericVersion) (long "numeric-version" <> help "Show only version number") numericHpackVersionOption = infoOption numericHpackVersion (long "hpack-numeric-version" <> help "Show only hpack's version number") -- | Add a command to the options dispatcher. addCommand :: String -- ^ command string -> String -- ^ title of command -> String -- ^ footer of command help -> (a -> b) -- ^ constructor to wrap up command in common data type -> Parser c -- ^ common parser -> Parser a -- ^ command parser -> EitherT b (Writer (Mod CommandFields (b,c))) () addCommand cmd title footerStr constr = addCommand' cmd title footerStr (\a c -> (constr a,c)) -- | Add a command that takes sub-commands to the options dispatcher. addSubCommands :: Monoid c => String -- ^ command string -> String -- ^ title of command -> String -- ^ footer of command help -> Parser c -- ^ common parser -> EitherT b (Writer (Mod CommandFields (b,c))) () -- ^ sub-commands (use 'addCommand') -> EitherT b (Writer (Mod CommandFields (b,c))) () addSubCommands cmd title footerStr commonParser commandParser = addCommand' cmd title footerStr (\(c1,(a,c2)) c3 -> (a,mconcat [c3, c2, c1])) commonParser (complicatedParser "COMMAND" commonParser commandParser) -- | Add a command to the options dispatcher. addCommand' :: String -- ^ command string -> String -- ^ title of command -> String -- ^ footer of command help -> (a -> c -> (b,c)) -- ^ constructor to wrap up command in common data type -> Parser c -- ^ common parser -> Parser a -- ^ command parser -> EitherT b (Writer (Mod CommandFields (b,c))) () addCommand' cmd title footerStr constr commonParser inner = lift (tell (command cmd (info (constr <$> inner <*> commonParser) (progDesc title <> footer footerStr)))) -- | Generate a complicated options parser. complicatedParser :: Monoid a => String -- ^ metavar for the sub-command -> Parser a -- ^ common settings -> EitherT b (Writer (Mod CommandFields (b,a))) () -- ^ commands (use 'addCommand') -> Parser (a,(b,a)) complicatedParser commandMetavar commonParser commandParser = (,) <$> commonParser <*> case runWriter (runEitherT commandParser) of (Right (),d) -> hsubparser' commandMetavar d (Left b,_) -> pure (b,mempty) -- | Subparser with @--help@ argument. Borrowed with slight modification -- from Options.Applicative.Extra. hsubparser' :: String -> Mod CommandFields a -> Parser a hsubparser' commandMetavar m = mkParser d g rdr where Mod _ d g = metavar commandMetavar `mappend` m (groupName, cmds, subs) = mkCommand m rdr = CmdReader groupName cmds (fmap add_helper . subs) add_helper pinfo = pinfo { infoParser = infoParser pinfo <**> helpOption } -- | Non-hidden help option. helpOption :: Parser (a -> a) helpOption = abortOption ShowHelpText $ long "help" <> help "Show this help text" stack-1.5.1/src/Path/CheckInstall.hs0000644000000000000000000000516013135652051015377 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} module Path.CheckInstall where import Control.Monad (unless) import Control.Monad.Extra (anyM, (&&^)) import Control.Monad.IO.Class import Control.Monad.Logger import Data.Foldable (forM_) import Data.Text (Text) import qualified Data.Text as T import qualified System.Directory as D import qualified System.FilePath as FP -- | Checks if the installed executable will be available on the user's -- PATH. This doesn't use @envSearchPath menv@ because it includes paths -- only visible when running in the stack environment. warnInstallSearchPathIssues :: (MonadIO m, MonadLogger m) => FilePath -> [Text] -> m () warnInstallSearchPathIssues destDir installed = do searchPath <- liftIO FP.getSearchPath destDirIsInPATH <- liftIO $ anyM (\dir -> D.doesDirectoryExist dir &&^ fmap (FP.equalFilePath destDir) (D.canonicalizePath dir)) searchPath if destDirIsInPATH then forM_ installed $ \exe -> do mexePath <- (liftIO . D.findExecutable . T.unpack) exe case mexePath of Just exePath -> do exeDir <- (liftIO . fmap FP.takeDirectory . D.canonicalizePath) exePath unless (exeDir `FP.equalFilePath` destDir) $ do $logWarn "" $logWarn $ T.concat [ "WARNING: The \"" , exe , "\" executable found on the PATH environment variable is " , T.pack exePath , ", and not the version that was just installed." ] $logWarn $ T.concat [ "This means that \"" , exe , "\" calls on the command line will not use this version." ] Nothing -> do $logWarn "" $logWarn $ T.concat [ "WARNING: Installation path " , T.pack destDir , " is on the PATH but the \"" , exe , "\" executable that was just installed could not be found on the PATH." ] else do $logWarn "" $logWarn $ T.concat [ "WARNING: Installation path " , T.pack destDir , " not found on the PATH environment variable" ] stack-1.5.1/src/Path/Extra.hs0000644000000000000000000001054613135652051014122 0ustar0000000000000000{-# LANGUAGE ViewPatterns #-} -- | Extra Path utilities. module Path.Extra (toFilePathNoTrailingSep ,dropRoot ,parseCollapsedAbsDir ,parseCollapsedAbsFile ,concatAndColapseAbsDir ,rejectMissingFile ,rejectMissingDir ,pathToByteString ,pathToLazyByteString ,pathToText ) where import qualified Data.ByteString.Lazy.Char8 as BSL import qualified Data.ByteString.Char8 as BS import qualified Data.Text as T import qualified Data.Text.Encoding as T import Control.Monad (liftM) import Control.Monad.Catch import Control.Monad.IO.Class import Data.Bool (bool) import Path import Path.IO import Path.Internal (Path(..)) import qualified System.FilePath as FP -- | Convert to FilePath but don't add a trailing slash. toFilePathNoTrailingSep :: Path loc Dir -> FilePath toFilePathNoTrailingSep = FP.dropTrailingPathSeparator . toFilePath -- | Collapse intermediate "." and ".." directories from path, then parse -- it with 'parseAbsDir'. -- (probably should be moved to the Path module) parseCollapsedAbsDir :: MonadThrow m => FilePath -> m (Path Abs Dir) parseCollapsedAbsDir = parseAbsDir . collapseFilePath -- | Collapse intermediate "." and ".." directories from path, then parse -- it with 'parseAbsFile'. -- (probably should be moved to the Path module) parseCollapsedAbsFile :: MonadThrow m => FilePath -> m (Path Abs File) parseCollapsedAbsFile = parseAbsFile . collapseFilePath -- | Add a relative FilePath to the end of a Path -- We can't parse the FilePath first because we need to account for ".." -- in the FilePath (#2895) concatAndColapseAbsDir :: MonadThrow m => Path Abs Dir -> FilePath -> m (Path Abs Dir) concatAndColapseAbsDir base rel = parseCollapsedAbsDir (toFilePath base FP. rel) -- | Collapse intermediate "." and ".." directories from a path. -- -- > collapseFilePath "./foo" == "foo" -- > collapseFilePath "/bar/../baz" == "/baz" -- > collapseFilePath "/../baz" == "/../baz" -- > collapseFilePath "parent/foo/baz/../bar" == "parent/foo/bar" -- > collapseFilePath "parent/foo/baz/../../bar" == "parent/bar" -- > collapseFilePath "parent/foo/.." == "parent" -- > collapseFilePath "/parent/foo/../../bar" == "/bar" -- -- (adapted from @Text.Pandoc.Shared@) collapseFilePath :: FilePath -> FilePath collapseFilePath = FP.joinPath . reverse . foldl go [] . FP.splitDirectories where go rs "." = rs go r@(p:rs) ".." = case p of ".." -> "..":r (checkPathSeparator -> True) -> "..":r _ -> rs go _ (checkPathSeparator -> True) = [[FP.pathSeparator]] go rs x = x:rs checkPathSeparator [x] = FP.isPathSeparator x checkPathSeparator _ = False -- | Drop the root (either @\/@ on POSIX or @C:\\@, @D:\\@, etc. on -- Windows). dropRoot :: Path Abs t -> Path Rel t dropRoot (Path l) = Path (FP.dropDrive l) -- | If given file in 'Maybe' does not exist, ensure we have 'Nothing'. This -- is to be used in conjunction with 'forgivingAbsence' and -- 'resolveFile'. -- -- Previously the idiom @forgivingAbsence (relsoveFile …)@ alone was used, -- which relied on 'canonicalizePath' throwing 'isDoesNotExistError' when -- path does not exist. As it turns out, this behavior is actually not -- intentional and unreliable, see -- . This was “fixed” in -- version @1.2.3.0@ of @directory@ package (now it never throws). To make -- it work with all versions, we need to use the following idiom: -- -- > forgivingAbsence (resolveFile …) >>= rejectMissingFile rejectMissingFile :: MonadIO m => Maybe (Path Abs File) -> m (Maybe (Path Abs File)) rejectMissingFile Nothing = return Nothing rejectMissingFile (Just p) = bool Nothing (Just p) `liftM` doesFileExist p -- | See 'rejectMissingFile'. rejectMissingDir :: MonadIO m => Maybe (Path Abs Dir) -> m (Maybe (Path Abs Dir)) rejectMissingDir Nothing = return Nothing rejectMissingDir (Just p) = bool Nothing (Just p) `liftM` doesDirExist p -- | Convert to a lazy ByteString using toFilePath and UTF8. pathToLazyByteString :: Path b t -> BSL.ByteString pathToLazyByteString = BSL.fromStrict . pathToByteString -- | Convert to a ByteString using toFilePath and UTF8. pathToByteString :: Path b t -> BS.ByteString pathToByteString = T.encodeUtf8 . pathToText pathToText :: Path b t -> T.Text pathToText = T.pack . toFilePath stack-1.5.1/src/Path/Find.hs0000644000000000000000000000755313135652051013723 0ustar0000000000000000{-# LANGUAGE DataKinds #-} -- | Finding files. module Path.Find (findFileUp ,findDirUp ,findFiles ,findInParents) where import Control.Exception (evaluate) import Control.DeepSeq (force) import Control.Monad import Control.Monad.Catch import Control.Monad.IO.Class import System.IO.Error (isPermissionError) import Data.List import Path import Path.IO hiding (findFiles) import System.PosixCompat.Files (getSymbolicLinkStatus, isSymbolicLink) -- | Find the location of a file matching the given predicate. findFileUp :: (MonadIO m,MonadThrow m) => Path Abs Dir -- ^ Start here. -> (Path Abs File -> Bool) -- ^ Predicate to match the file. -> Maybe (Path Abs Dir) -- ^ Do not ascend above this directory. -> m (Maybe (Path Abs File)) -- ^ Absolute file path. findFileUp = findPathUp snd -- | Find the location of a directory matching the given predicate. findDirUp :: (MonadIO m,MonadThrow m) => Path Abs Dir -- ^ Start here. -> (Path Abs Dir -> Bool) -- ^ Predicate to match the directory. -> Maybe (Path Abs Dir) -- ^ Do not ascend above this directory. -> m (Maybe (Path Abs Dir)) -- ^ Absolute directory path. findDirUp = findPathUp fst -- | Find the location of a path matching the given predicate. findPathUp :: (MonadIO m,MonadThrow m) => (([Path Abs Dir],[Path Abs File]) -> [Path Abs t]) -- ^ Choose path type from pair. -> Path Abs Dir -- ^ Start here. -> (Path Abs t -> Bool) -- ^ Predicate to match the path. -> Maybe (Path Abs Dir) -- ^ Do not ascend above this directory. -> m (Maybe (Path Abs t)) -- ^ Absolute path. findPathUp pathType dir p upperBound = do entries <- listDir dir case find p (pathType entries) of Just path -> return (Just path) Nothing | Just dir == upperBound -> return Nothing | parent dir == dir -> return Nothing | otherwise -> findPathUp pathType (parent dir) p upperBound -- | Find files matching predicate below a root directory. -- -- NOTE: this skips symbolic directory links, to avoid loops. This may -- not make sense for all uses of file finding. -- -- TODO: write one of these that traverses symbolic links but -- efficiently ignores loops. findFiles :: Path Abs Dir -- ^ Root directory to begin with. -> (Path Abs File -> Bool) -- ^ Predicate to match files. -> (Path Abs Dir -> Bool) -- ^ Predicate for which directories to traverse. -> IO [Path Abs File] -- ^ List of matching files. findFiles dir p traversep = do (dirs,files) <- catchJust (\ e -> if isPermissionError e then Just () else Nothing) (listDir dir) (\ _ -> return ([], [])) filteredFiles <- evaluate $ force (filter p files) filteredDirs <- filterM (fmap not . isSymLink) dirs subResults <- forM filteredDirs (\entry -> if traversep entry then findFiles entry p traversep else return []) return (concat (filteredFiles : subResults)) isSymLink :: Path Abs t -> IO Bool isSymLink = fmap isSymbolicLink . getSymbolicLinkStatus . toFilePath -- | @findInParents f path@ applies @f@ to @path@ and its 'parent's until -- it finds a 'Just' or reaches the root directory. findInParents :: MonadIO m => (Path Abs Dir -> m (Maybe a)) -> Path Abs Dir -> m (Maybe a) findInParents f path = do mres <- f path case mres of Just res -> return (Just res) Nothing -> do let next = parent path if next == path then return Nothing else findInParents f next stack-1.5.1/src/Stack/Build.hs0000644000000000000000000004156413140560217014251 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE ViewPatterns #-} -- | Build the project. module Stack.Build (build ,withLoadPackage ,mkBaseConfigOpts ,queryBuildInfo ,splitObjsWarning ,CabalVersionException(..)) where import Control.Exception (Exception) import Control.Monad import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.Reader (MonadReader) import Control.Monad.Trans.Resource import Control.Monad.Trans.Unlift (MonadBaseUnlift) import Data.Aeson (Value (Object, Array), (.=), object) import Data.Function import qualified Data.HashMap.Strict as HM import Data.List ((\\)) import Data.List.Extra (groupSort) import Data.List.NonEmpty (NonEmpty(..)) import qualified Data.List.NonEmpty as NE import qualified Data.Map as Map import Data.Map.Strict (Map) import Data.Maybe (catMaybes) import Data.Monoid import Data.Set (Set) import qualified Data.Set as Set import Data.String import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeUtf8) import qualified Data.Text.IO as TIO import Data.Text.Read (decimal) import Data.Typeable (Typeable) import qualified Data.Vector as V import qualified Data.Yaml as Yaml import Path import Prelude hiding (FilePath, writeFile) import Stack.Build.ConstructPlan import Stack.Build.Execute import Stack.Build.Haddock import Stack.Build.Installed import Stack.Build.Source import Stack.Build.Target import Stack.Fetch as Fetch import Stack.Package import Stack.PackageIndex import Stack.PrettyPrint import Stack.Types.Build import Stack.Types.Config import Stack.Types.FlagName import Stack.Types.Package import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.StackT import Stack.Types.StringError import Stack.Types.Version #ifdef WINDOWS import Stack.Types.Compiler #endif import System.FileLock (FileLock, unlockFile) #ifdef WINDOWS import System.Win32.Console (setConsoleCP, setConsoleOutputCP, getConsoleCP, getConsoleOutputCP) import qualified Control.Monad.Catch as Catch #endif -- | Build. -- -- If a buildLock is passed there is an important contract here. That lock must -- protect the snapshot, and it must be safe to unlock it if there are no further -- modifications to the snapshot to be performed by this build. build :: (StackM env m, HasEnvConfig env, MonadBaseUnlift IO m) => (Set (Path Abs File) -> IO ()) -- ^ callback after discovering all local files -> Maybe FileLock -> BuildOptsCLI -> m () build setLocalFiles mbuildLk boptsCli = fixCodePage $ do bopts <- view buildOptsL let profiling = boptsLibProfile bopts || boptsExeProfile bopts let symbols = not (boptsLibStrip bopts || boptsExeStrip bopts) menv <- getMinimalEnvOverride (targets, mbp, locals, extraToBuild, extraDeps, sourceMap) <- loadSourceMapFull NeedTargets boptsCli -- Set local files, necessary for file watching stackYaml <- view stackYamlL liftIO $ setLocalFiles $ Set.insert stackYaml $ Set.unions $ map lpFiles locals (installedMap, globalDumpPkgs, snapshotDumpPkgs, localDumpPkgs) <- getInstalled menv GetInstalledOpts { getInstalledProfiling = profiling , getInstalledHaddock = shouldHaddockDeps bopts , getInstalledSymbols = symbols } sourceMap warnMissingExtraDeps installedMap extraDeps baseConfigOpts <- mkBaseConfigOpts boptsCli plan <- withLoadPackage $ \loadPackage -> constructPlan mbp baseConfigOpts locals extraToBuild localDumpPkgs loadPackage sourceMap installedMap (boptsCLIInitialBuildSteps boptsCli) allowLocals <- view $ configL.to configAllowLocals unless allowLocals $ case justLocals plan of [] -> return () localsIdents -> throwM $ LocalPackagesPresent localsIdents -- If our work to do is all local, let someone else have a turn with the snapshot. -- They won't damage what's already in there. case (mbuildLk, allLocal plan) of -- NOTE: This policy is too conservative. In the future we should be able to -- schedule unlocking as an Action that happens after all non-local actions are -- complete. (Just lk,True) -> do $logDebug "All installs are local; releasing snapshot lock early." liftIO $ unlockFile lk _ -> return () checkCabalVersion warnAboutSplitObjs bopts warnIfExecutablesWithSameNameCouldBeOverwritten locals plan when (boptsPreFetch bopts) $ preFetch plan if boptsCLIDryrun boptsCli then printPlan plan else executePlan menv boptsCli baseConfigOpts locals globalDumpPkgs snapshotDumpPkgs localDumpPkgs installedMap targets plan -- | If all the tasks are local, they don't mutate anything outside of our local directory. allLocal :: Plan -> Bool allLocal = all (== Local) . map taskLocation . Map.elems . planTasks justLocals :: Plan -> [PackageIdentifier] justLocals = map taskProvides . filter ((== Local) . taskLocation) . Map.elems . planTasks checkCabalVersion :: (StackM env m, HasEnvConfig env) => m () checkCabalVersion = do allowNewer <- view $ configL.to configAllowNewer cabalVer <- view cabalVersionL -- https://github.com/haskell/cabal/issues/2023 when (allowNewer && cabalVer < $(mkVersion "1.22")) $ throwM $ CabalVersionException $ "Error: --allow-newer requires at least Cabal version 1.22, but version " ++ versionString cabalVer ++ " was found." newtype CabalVersionException = CabalVersionException { unCabalVersionException :: String } deriving (Typeable) instance Show CabalVersionException where show = unCabalVersionException instance Exception CabalVersionException warnMissingExtraDeps :: (StackM env m, HasConfig env) => InstalledMap -> Map PackageName Version -> m () warnMissingExtraDeps installed extraDeps = do missingExtraDeps <- fmap catMaybes $ forM (Map.toList extraDeps) $ \(n, v) -> if Map.member n installed then return Nothing else do vs <- getPackageVersions n if Set.null vs then return $ Just $ fromString (packageNameString n ++ "-" ++ versionString v) else return Nothing unless (null missingExtraDeps) $ $prettyWarn $ "Some extra-deps are neither installed nor in the index:" <> line <> indent 4 (bulletedList missingExtraDeps) -- | See https://github.com/commercialhaskell/stack/issues/1198. warnIfExecutablesWithSameNameCouldBeOverwritten :: MonadLogger m => [LocalPackage] -> Plan -> m () warnIfExecutablesWithSameNameCouldBeOverwritten locals plan = do $logDebug "Checking if we are going to build multiple executables with the same name" forM_ (Map.toList warnings) $ \(exe,(toBuild,otherLocals)) -> do let exe_s | length toBuild > 1 = "several executables with the same name:" | otherwise = "executable" exesText pkgs = T.intercalate ", " ["'" <> packageNameText p <> ":" <> exe <> "'" | p <- pkgs] ($logWarn . T.unlines . concat) [ [ "Building " <> exe_s <> " " <> exesText toBuild <> "." ] , [ "Only one of them will be available via 'stack exec' or locally installed." | length toBuild > 1 ] , [ "Other executables with the same name might be overwritten: " <> exesText otherLocals <> "." | not (null otherLocals) ] ] where -- Cases of several local packages having executables with the same name. -- The Map entries have the following form: -- -- executable name: ( package names for executables that are being built -- , package names for other local packages that have an -- executable with the same name -- ) warnings :: Map Text ([PackageName],[PackageName]) warnings = Map.mapMaybe (\(pkgsToBuild,localPkgs) -> case (pkgsToBuild,NE.toList localPkgs \\ NE.toList pkgsToBuild) of (_ :| [],[]) -> -- We want to build the executable of single local package -- and there are no other local packages with an executable of -- the same name. Nothing to warn about, ignore. Nothing (_,otherLocals) -> -- We could be here for two reasons (or their combination): -- 1) We are building two or more executables with the same -- name that will end up overwriting each other. -- 2) In addition to the executable(s) that we want to build -- there are other local packages with an executable of the -- same name that might get overwritten. -- Both cases warrant a warning. Just (NE.toList pkgsToBuild,otherLocals)) (Map.intersectionWith (,) exesToBuild localExes) exesToBuild :: Map Text (NonEmpty PackageName) exesToBuild = collect [ (exe,pkgName) | (pkgName,task) <- Map.toList (planTasks plan) , isLocal task , exe <- (Set.toList . exeComponents . lpComponents . taskLP) task ] where isLocal Task{taskType = (TTLocal _)} = True isLocal _ = False taskLP Task{taskType = (TTLocal lp)} = lp taskLP _ = error "warnIfExecutablesWithSameNameCouldBeOverwritten/taskLP: task isn't local" localExes :: Map Text (NonEmpty PackageName) localExes = collect [ (exe,packageName pkg) | pkg <- map lpPackage locals , exe <- Set.toList (packageExes pkg) ] collect :: Ord k => [(k,v)] -> Map k (NonEmpty v) collect = Map.map NE.fromList . Map.fromDistinctAscList . groupSort warnAboutSplitObjs :: MonadLogger m => BuildOpts -> m () warnAboutSplitObjs bopts | boptsSplitObjs bopts = do $logWarn $ "Building with --split-objs is enabled. " <> T.pack splitObjsWarning warnAboutSplitObjs _ = return () splitObjsWarning :: String splitObjsWarning = unwords [ "Note that this feature is EXPERIMENTAL, and its behavior may be changed and improved." , "You will need to clean your workdirs before use. If you want to compile all dependencies" , "with split-objs, you will need to delete the snapshot (and all snapshots that could" , "reference that snapshot)." ] -- | Get the @BaseConfigOpts@ necessary for constructing configure options mkBaseConfigOpts :: (MonadIO m, MonadReader env m, HasEnvConfig env, MonadThrow m) => BuildOptsCLI -> m BaseConfigOpts mkBaseConfigOpts boptsCli = do bopts <- view buildOptsL snapDBPath <- packageDatabaseDeps localDBPath <- packageDatabaseLocal snapInstallRoot <- installationRootDeps localInstallRoot <- installationRootLocal packageExtraDBs <- packageDatabaseExtra return BaseConfigOpts { bcoSnapDB = snapDBPath , bcoLocalDB = localDBPath , bcoSnapInstallRoot = snapInstallRoot , bcoLocalInstallRoot = localInstallRoot , bcoBuildOpts = bopts , bcoBuildOptsCLI = boptsCli , bcoExtraDBs = packageExtraDBs } -- | Provide a function for loading package information from the package index withLoadPackage :: (StackM env m, HasEnvConfig env, MonadBaseUnlift IO m) => ((PackageName -> Version -> Map FlagName Bool -> [Text] -> IO Package) -> m a) -> m a withLoadPackage inner = do econfig <- view envConfigL withCabalLoader $ \cabalLoader -> inner $ \name version flags ghcOptions -> do bs <- cabalLoader $ PackageIdentifier name version -- Intentionally ignore warnings, as it's not really -- appropriate to print a bunch of warnings out while -- resolving the package index. (_warnings,pkg) <- readPackageBS (depPackageConfig econfig flags ghcOptions) (PackageIdentifier name version) bs return pkg where -- | Package config to be used for dependencies depPackageConfig :: EnvConfig -> Map FlagName Bool -> [Text] -> PackageConfig depPackageConfig econfig flags ghcOptions = PackageConfig { packageConfigEnableTests = False , packageConfigEnableBenchmarks = False , packageConfigFlags = flags , packageConfigGhcOptions = ghcOptions , packageConfigCompilerVersion = view actualCompilerVersionL econfig , packageConfigPlatform = view platformL econfig } -- | Set the code page for this process as necessary. Only applies to Windows. -- See: https://github.com/commercialhaskell/stack/issues/738 #ifdef WINDOWS fixCodePage :: (StackM env m, HasBuildConfig env, HasEnvConfig env) => m a -> m a fixCodePage inner = do mcp <- view $ configL.to configModifyCodePage ghcVersion <- view $ actualCompilerVersionL.to getGhcVersion if mcp && ghcVersion < $(mkVersion "7.10.3") then fixCodePage' -- GHC >=7.10.3 doesn't need this code page hack. else inner where fixCodePage' = do origCPI <- liftIO getConsoleCP origCPO <- liftIO getConsoleOutputCP let setInput = origCPI /= expected setOutput = origCPO /= expected fixInput | setInput = Catch.bracket_ (liftIO $ do setConsoleCP expected) (liftIO $ setConsoleCP origCPI) | otherwise = id fixOutput | setOutput = Catch.bracket_ (liftIO $ do setConsoleOutputCP expected) (liftIO $ setConsoleOutputCP origCPO) | otherwise = id case (setInput, setOutput) of (False, False) -> return () (True, True) -> warn "" (True, False) -> warn " input" (False, True) -> warn " output" fixInput $ fixOutput inner expected = 65001 -- UTF-8 warn typ = $logInfo $ T.concat [ "Setting" , typ , " codepage to UTF-8 (65001) to ensure correct output from GHC" ] #else fixCodePage :: a -> a fixCodePage = id #endif -- | Query information about the build and print the result to stdout in YAML format. queryBuildInfo :: (StackM env m, HasEnvConfig env) => [Text] -- ^ selectors -> m () queryBuildInfo selectors0 = rawBuildInfo >>= select id selectors0 >>= liftIO . TIO.putStrLn . decodeUtf8 . Yaml.encode where select _ [] value = return value select front (sel:sels) value = case value of Object o -> case HM.lookup sel o of Nothing -> err "Selector not found" Just value' -> cont value' Array v -> case decimal sel of Right (i, "") | i >= 0 && i < V.length v -> cont $ v V.! i | otherwise -> err "Index out of range" _ -> err "Encountered array and needed numeric selector" _ -> err $ "Cannot apply selector to " ++ show value where cont = select (front . (sel:)) sels err msg = errorString $ msg ++ ": " ++ show (front [sel]) -- | Get the raw build information object rawBuildInfo :: (StackM env m, HasEnvConfig env) => m Value rawBuildInfo = do (locals, _sourceMap) <- loadSourceMap NeedTargets defaultBuildOptsCLI return $ object [ "locals" .= Object (HM.fromList $ map localToPair locals) ] where localToPair lp = (T.pack $ packageNameString $ packageName p, value) where p = lpPackage lp value = object [ "version" .= packageVersion p , "path" .= toFilePath (lpDir lp) ] stack-1.5.1/src/Stack/Build/Cache.hs0000644000000000000000000003641313135652051015253 0ustar0000000000000000{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE FlexibleContexts #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- | Cache information about previous builds module Stack.Build.Cache ( tryGetBuildCache , tryGetConfigCache , tryGetCabalMod , getInstalledExes , tryGetFlagCache , deleteCaches , markExeInstalled , markExeNotInstalled , writeFlagCache , writeBuildCache , writeConfigCache , writeCabalMod , setTestSuccess , unsetTestSuccess , checkTestSuccess , writePrecompiledCache , readPrecompiledCache -- Exported for testing , BuildCache(..) ) where import Control.Applicative import Control.DeepSeq (NFData) import Control.Exception.Safe (handleIO, tryAnyDeep) import Control.Monad (liftM) import Control.Monad.Catch (MonadThrow, MonadCatch) import Control.Monad.IO.Class import Control.Monad.Logger (MonadLogger) import Control.Monad.Reader (MonadReader) import Control.Monad.Trans.Control (MonadBaseControl) import Crypto.Hash (hashWith, SHA256(..)) import Data.Binary (Binary (..)) import qualified Data.Binary as Binary import Data.Binary.Tagged (HasStructuralInfo, HasSemanticVersion) import qualified Data.Binary.Tagged as BinaryTagged import qualified Data.ByteArray as Mem (convert) import qualified Data.ByteArray.Encoding as Mem (convertToBase, Base(Base16)) import qualified Data.ByteString.Base64.URL as B64URL import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Lazy as LBS import Data.Foldable (forM_) import Data.Map (Map) import qualified Data.Map as M import Data.Maybe (fromMaybe, mapMaybe) import Data.Set (Set) import qualified Data.Set as Set import qualified Data.Store as Store import Data.Store.VersionTagged import Data.Text (Text) import qualified Data.Text as T import Data.Traversable (forM) import Path import Path.IO import Prelude -- Fix redundant import warnings import Stack.Constants import Stack.Types.Build import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.GhcPkgId import Stack.Types.Package import Stack.Types.PackageIdentifier import Stack.Types.Version import qualified System.FilePath as FilePath -- | Directory containing files to mark an executable as installed exeInstalledDir :: (MonadReader env m, HasEnvConfig env, MonadThrow m) => InstallLocation -> m (Path Abs Dir) exeInstalledDir Snap = ( $(mkRelDir "installed-packages")) `liftM` installationRootDeps exeInstalledDir Local = ( $(mkRelDir "installed-packages")) `liftM` installationRootLocal -- | Get all of the installed executables getInstalledExes :: (MonadReader env m, HasEnvConfig env, MonadIO m, MonadThrow m) => InstallLocation -> m [PackageIdentifier] getInstalledExes loc = do dir <- exeInstalledDir loc (_, files) <- liftIO $ handleIO (const $ return ([], [])) $ listDir dir return $ concat $ M.elems $ -- If there are multiple install records (from a stack version -- before https://github.com/commercialhaskell/stack/issues/2373 -- was fixed), then we don't know which is correct - ignore them. M.fromListWith (\_ _ -> []) $ map (\x -> (packageIdentifierName x, [x])) $ mapMaybe (parsePackageIdentifierFromString . toFilePath . filename) files -- | Mark the given executable as installed markExeInstalled :: (MonadReader env m, HasEnvConfig env, MonadIO m, MonadCatch m) => InstallLocation -> PackageIdentifier -> m () markExeInstalled loc ident = do dir <- exeInstalledDir loc ensureDir dir ident' <- parseRelFile $ packageIdentifierString ident let fp = toFilePath $ dir ident' -- Remove old install records for this package. -- TODO: This is a bit in-efficient. Put all this metadata into one file? installed <- getInstalledExes loc forM_ (filter (\x -> packageIdentifierName ident == packageIdentifierName x) installed) (markExeNotInstalled loc) -- TODO consideration for the future: list all of the executables -- installed, and invalidate this file in getInstalledExes if they no -- longer exist liftIO $ writeFile fp "Installed" -- | Mark the given executable as not installed markExeNotInstalled :: (MonadReader env m, HasEnvConfig env, MonadIO m, MonadCatch m) => InstallLocation -> PackageIdentifier -> m () markExeNotInstalled loc ident = do dir <- exeInstalledDir loc ident' <- parseRelFile $ packageIdentifierString ident ignoringAbsence (removeFile $ dir ident') -- | Try to read the dirtiness cache for the given package directory. tryGetBuildCache :: (MonadIO m, MonadReader env m, MonadThrow m, MonadLogger m, HasEnvConfig env, MonadBaseControl IO m) => Path Abs Dir -> m (Maybe (Map FilePath FileCacheInfo)) tryGetBuildCache dir = liftM (fmap buildCacheTimes) . $(versionedDecodeFile buildCacheVC) =<< buildCacheFile dir -- | Try to read the dirtiness cache for the given package directory. tryGetConfigCache :: (MonadIO m, MonadReader env m, MonadThrow m, HasEnvConfig env, MonadBaseControl IO m, MonadLogger m) => Path Abs Dir -> m (Maybe ConfigCache) tryGetConfigCache dir = $(versionedDecodeFile configCacheVC) =<< configCacheFile dir -- | Try to read the mod time of the cabal file from the last build tryGetCabalMod :: (MonadIO m, MonadReader env m, MonadThrow m, HasEnvConfig env, MonadBaseControl IO m, MonadLogger m) => Path Abs Dir -> m (Maybe ModTime) tryGetCabalMod dir = $(versionedDecodeFile modTimeVC) =<< configCabalMod dir -- | Write the dirtiness cache for this package's files. writeBuildCache :: (MonadIO m, MonadReader env m, MonadThrow m, HasEnvConfig env, MonadLogger m) => Path Abs Dir -> Map FilePath FileCacheInfo -> m () writeBuildCache dir times = do fp <- buildCacheFile dir $(versionedEncodeFile buildCacheVC) fp BuildCache { buildCacheTimes = times } -- | Write the dirtiness cache for this package's configuration. writeConfigCache :: (MonadIO m, MonadReader env m, MonadThrow m, HasEnvConfig env, MonadLogger m) => Path Abs Dir -> ConfigCache -> m () writeConfigCache dir x = do fp <- configCacheFile dir $(versionedEncodeFile configCacheVC) fp x -- | See 'tryGetCabalMod' writeCabalMod :: (MonadIO m, MonadReader env m, MonadThrow m, HasEnvConfig env, MonadLogger m) => Path Abs Dir -> ModTime -> m () writeCabalMod dir x = do fp <- configCabalMod dir $(versionedEncodeFile modTimeVC) fp x -- | Delete the caches for the project. deleteCaches :: (MonadIO m, MonadReader env m, MonadCatch m, HasEnvConfig env) => Path Abs Dir -> m () deleteCaches dir = do {- FIXME confirm that this is acceptable to remove bfp <- buildCacheFile dir removeFileIfExists bfp -} cfp <- configCacheFile dir ignoringAbsence (removeFile cfp) flagCacheFile :: (MonadIO m, MonadThrow m, MonadReader env m, HasEnvConfig env) => Installed -> m (Path Abs File) flagCacheFile installed = do rel <- parseRelFile $ case installed of Library _ gid -> ghcPkgIdString gid Executable ident -> packageIdentifierString ident dir <- flagCacheLocal return $ dir rel -- | Loads the flag cache for the given installed extra-deps tryGetFlagCache :: (MonadIO m, MonadThrow m, MonadReader env m, HasEnvConfig env, MonadBaseControl IO m, MonadLogger m) => Installed -> m (Maybe ConfigCache) tryGetFlagCache gid = do fp <- flagCacheFile gid $(versionedDecodeFile configCacheVC) fp writeFlagCache :: (MonadIO m, MonadReader env m, HasEnvConfig env, MonadThrow m, MonadLogger m) => Installed -> ConfigCache -> m () writeFlagCache gid cache = do file <- flagCacheFile gid ensureDir (parent file) $(versionedEncodeFile configCacheVC) file cache -- | Mark a test suite as having succeeded setTestSuccess :: (MonadIO m, MonadThrow m, MonadReader env m, HasEnvConfig env, MonadLogger m) => Path Abs Dir -> m () setTestSuccess dir = do fp <- testSuccessFile dir $(versionedEncodeFile testSuccessVC) fp True -- | Mark a test suite as not having succeeded unsetTestSuccess :: (MonadIO m, MonadThrow m, MonadReader env m, HasEnvConfig env, MonadLogger m) => Path Abs Dir -> m () unsetTestSuccess dir = do fp <- testSuccessFile dir $(versionedEncodeFile testSuccessVC) fp False -- | Check if the test suite already passed checkTestSuccess :: (MonadIO m, MonadThrow m, MonadReader env m, HasEnvConfig env, MonadBaseControl IO m, MonadLogger m) => Path Abs Dir -> m Bool checkTestSuccess dir = liftM (fromMaybe False) ($(versionedDecodeFile testSuccessVC) =<< testSuccessFile dir) -------------------------------------- -- Precompiled Cache -- -- Idea is simple: cache information about packages built in other snapshots, -- and then for identical matches (same flags, config options, dependencies) -- just copy over the executables and reregister the libraries. -------------------------------------- -- | The file containing information on the given package/configuration -- combination. The filename contains a hash of the non-directory configure -- options for quick lookup if there's a match. -- -- It also returns an action yielding the location of the precompiled -- path based on the old binary encoding. -- -- We only pay attention to non-directory options. We don't want to avoid a -- cache hit just because it was installed in a different directory. precompiledCacheFile :: (MonadThrow m, MonadReader env m, HasEnvConfig env, MonadLogger m) => PackageIdentifier -> ConfigureOpts -> Set GhcPkgId -- ^ dependencies -> m (Path Abs File, m (Path Abs File)) precompiledCacheFile pkgident copts installedPackageIDs = do ec <- view envConfigL compiler <- view actualCompilerVersionL >>= parseRelDir . compilerVersionString cabal <- view cabalVersionL >>= parseRelDir . versionString pkg <- parseRelDir $ packageIdentifierString pkgident platformRelDir <- platformGhcRelDir let input = (coNoDirs copts, installedPackageIDs) -- In Cabal versions 1.22 and later, the configure options contain the -- installed package IDs, which is what we need for a unique hash. -- Unfortunately, earlier Cabals don't have the information, so we must -- supplement it with the installed package IDs directly. -- See issue: https://github.com/commercialhaskell/stack/issues/1103 let oldHash = Mem.convertToBase Mem.Base16 $ hashWith SHA256 $ LBS.toStrict $ if view cabalVersionL ec >= $(mkVersion "1.22") then Binary.encode (coNoDirs copts) else Binary.encode input hashToPath hash = do hashPath <- parseRelFile $ S8.unpack hash return $ view stackRootL ec $(mkRelDir "precompiled") platformRelDir compiler cabal pkg hashPath newPath <- hashToPath $ B64URL.encode $ Mem.convert $ hashWith SHA256 $ Store.encode input return (newPath, hashToPath oldHash) -- | Write out information about a newly built package writePrecompiledCache :: (MonadThrow m, MonadReader env m, HasEnvConfig env, MonadIO m, MonadLogger m) => BaseConfigOpts -> PackageIdentifier -> ConfigureOpts -> Set GhcPkgId -- ^ dependencies -> Installed -- ^ library -> Set Text -- ^ executables -> m () writePrecompiledCache baseConfigOpts pkgident copts depIDs mghcPkgId exes = do (file, _) <- precompiledCacheFile pkgident copts depIDs ensureDir (parent file) ec <- view envConfigL let stackRootRelative = makeRelative (view stackRootL ec) mlibpath <- case mghcPkgId of Executable _ -> return Nothing Library _ ipid -> liftM Just $ do ipid' <- parseRelFile $ ghcPkgIdString ipid ++ ".conf" relPath <- stackRootRelative $ bcoSnapDB baseConfigOpts ipid' return $ toFilePath relPath exes' <- forM (Set.toList exes) $ \exe -> do name <- parseRelFile $ T.unpack exe relPath <- stackRootRelative $ bcoSnapInstallRoot baseConfigOpts bindirSuffix name return $ toFilePath relPath $(versionedEncodeFile precompiledCacheVC) file PrecompiledCache { pcLibrary = mlibpath , pcExes = exes' } -- | Check the cache for a precompiled package matching the given -- configuration. readPrecompiledCache :: (MonadThrow m, MonadReader env m, HasEnvConfig env, MonadIO m, MonadLogger m, MonadBaseControl IO m) => PackageIdentifier -- ^ target package -> ConfigureOpts -> Set GhcPkgId -- ^ dependencies -> m (Maybe PrecompiledCache) readPrecompiledCache pkgident copts depIDs = do ec <- view envConfigL let toAbsPath path = do if FilePath.isAbsolute path then path -- Only older version store absolute path else toFilePath (view stackRootL ec) FilePath. path let toAbsPC pc = PrecompiledCache { pcLibrary = fmap toAbsPath (pcLibrary pc) , pcExes = map toAbsPath (pcExes pc) } (file, getOldFile) <- precompiledCacheFile pkgident copts depIDs mres <- $(versionedDecodeFile precompiledCacheVC) file case mres of Just res -> return (Just $ toAbsPC res) Nothing -> do -- Fallback on trying the old binary format. oldFile <- getOldFile mpc <- fmap toAbsPC <$> binaryDecodeFileOrFailDeep oldFile -- Write out file in new format. Keep old file around for -- the benefit of older stack versions. forM_ mpc ($(versionedEncodeFile precompiledCacheVC) file) return mpc -- | Ensure that there are no lurking exceptions deep inside the parsed -- value... because that happens unfortunately. See -- https://github.com/commercialhaskell/stack/issues/554 binaryDecodeFileOrFailDeep :: (BinarySchema a, MonadIO m) => Path loc File -> m (Maybe a) binaryDecodeFileOrFailDeep fp = liftIO $ fmap (either (const Nothing) id) $ tryAnyDeep $ do eres <- BinaryTagged.taggedDecodeFileOrFail (toFilePath fp) case eres of Left _ -> return Nothing Right x -> return (Just x) type BinarySchema a = (Binary a, NFData a, HasStructuralInfo a, HasSemanticVersion a) stack-1.5.1/src/Stack/Build/ConstructPlan.hs0000644000000000000000000013663613135652051017057 0ustar0000000000000000{-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE StandaloneDeriving #-} -- | Construct a @Plan@ for how to build module Stack.Build.ConstructPlan ( constructPlan ) where import Control.Exception.Lifted import Control.Monad import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.RWS.Strict import Control.Monad.State.Strict (execState) import Control.Monad.Trans.Resource import Data.Either import Data.Function import qualified Data.HashSet as HashSet import Data.List import Data.List.Extra (nubOrd) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import qualified Data.Map.Strict as Map import Data.Maybe import Data.Set (Set) import qualified Data.Set as Set import Data.String (fromString) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeUtf8With) import Data.Text.Encoding.Error (lenientDecode) import Data.Typeable import qualified Distribution.Package as Cabal import qualified Distribution.Text as Cabal import qualified Distribution.Version as Cabal import GHC.Generics (Generic) import Generics.Deriving.Monoid (memptydefault, mappenddefault) import Lens.Micro (lens) import Path import Prelude hiding (pi, writeFile) import Stack.Build.Cache import Stack.Build.Haddock import Stack.Build.Installed import Stack.Build.Source import Stack.BuildPlan import Stack.Constants import Stack.Package import Stack.PackageDump import Stack.PackageIndex import Stack.PrettyPrint import Stack.Types.Build import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.FlagName import Stack.Types.GhcPkgId import Stack.Types.Package import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.StackT (StackM) import Stack.Types.Version import System.Process.Read (findExecutable) data PackageInfo = -- | This indicates that the package is already installed, and -- that we shouldn't build it from source. This is always the case -- for snapshot packages. PIOnlyInstalled InstallLocation Installed -- | This indicates that the package isn't installed, and we know -- where to find its source (either a hackage package or a local -- directory). | PIOnlySource PackageSource -- | This indicates that the package is installed and we know -- where to find its source. We may want to reinstall from source. | PIBoth PackageSource Installed deriving (Show) combineSourceInstalled :: PackageSource -> (InstallLocation, Installed) -> PackageInfo combineSourceInstalled ps (location, installed) = assert (piiVersion ps == installedVersion installed) $ assert (piiLocation ps == location) $ case location of -- Always trust something in the snapshot Snap -> PIOnlyInstalled location installed Local -> PIBoth ps installed type CombinedMap = Map PackageName PackageInfo combineMap :: SourceMap -> InstalledMap -> CombinedMap combineMap = Map.mergeWithKey (\_ s i -> Just $ combineSourceInstalled s i) (fmap PIOnlySource) (fmap (uncurry PIOnlyInstalled)) data AddDepRes = ADRToInstall Task | ADRFound InstallLocation Installed deriving Show type ParentMap = MonoidMap PackageName (First Version, [(PackageIdentifier, VersionRange)]) data W = W { wFinals :: !(Map PackageName (Either ConstructPlanException Task)) , wInstall :: !(Map Text InstallLocation) -- ^ executable to be installed, and location where the binary is placed , wDirty :: !(Map PackageName Text) -- ^ why a local package is considered dirty , wDeps :: !(Set PackageName) -- ^ Packages which count as dependencies , wWarnings :: !([Text] -> [Text]) -- ^ Warnings , wParents :: !ParentMap -- ^ Which packages a given package depends on, along with the package's version } deriving Generic instance Monoid W where mempty = memptydefault mappend = mappenddefault type M = RWST Ctx W (Map PackageName (Either ConstructPlanException AddDepRes)) IO data Ctx = Ctx { mbp :: !MiniBuildPlan , baseConfigOpts :: !BaseConfigOpts , loadPackage :: !(PackageName -> Version -> Map FlagName Bool -> [Text] -> IO Package) , combinedMap :: !CombinedMap , toolToPackages :: !(Cabal.Dependency -> Map PackageName VersionRange) , ctxEnvConfig :: !EnvConfig , callStack :: ![PackageName] , extraToBuild :: !(Set PackageName) , getVersions :: !(PackageName -> IO (Set Version)) , wanted :: !(Set PackageName) , localNames :: !(Set PackageName) , logFunc :: Loc -> LogSource -> LogLevel -> LogStr -> IO () } instance HasPlatform Ctx instance HasGHCVariant Ctx instance HasConfig Ctx instance HasBuildConfig Ctx instance HasEnvConfig Ctx where envConfigL = lens ctxEnvConfig (\x y -> x { ctxEnvConfig = y }) -- | Computes a build plan. This means figuring out which build 'Task's -- to take, and the interdependencies among the build 'Task's. In -- particular: -- -- 1) It determines which packages need to be built, based on the -- transitive deps of the current targets. For local packages, this is -- indicated by the 'lpWanted' boolean. For extra packages to build, -- this comes from the @extraToBuild0@ argument of type @Set -- PackageName@. These are usually packages that have been specified on -- the commandline. -- -- 2) It will only rebuild an upstream package if it isn't present in -- the 'InstalledMap', or if some of its dependencies have changed. -- -- 3) It will only rebuild a local package if its files are dirty or -- some of its dependencies have changed. constructPlan :: forall env m. (StackM env m, HasEnvConfig env) => MiniBuildPlan -> BaseConfigOpts -> [LocalPackage] -> Set PackageName -- ^ additional packages that must be built -> [DumpPackage () () ()] -- ^ locally registered -> (PackageName -> Version -> Map FlagName Bool -> [Text] -> IO Package) -- ^ load upstream package -> SourceMap -> InstalledMap -> Bool -> m Plan constructPlan mbp0 baseConfigOpts0 locals extraToBuild0 localDumpPkgs loadPackage0 sourceMap installedMap initialBuildSteps = do $logDebug "Constructing the build plan" getVersions0 <- getPackageVersionsIO econfig <- view envConfigL let onWanted = void . addDep False . packageName . lpPackage let inner = do mapM_ onWanted $ filter lpWanted locals mapM_ (addDep False) $ Set.toList extraToBuild0 lf <- askLoggerIO ((), m, W efinals installExes dirtyReason deps warnings parents) <- liftIO $ runRWST inner (ctx econfig getVersions0 lf) M.empty mapM_ $logWarn (warnings []) let toEither (_, Left e) = Left e toEither (k, Right v) = Right (k, v) (errlibs, adrs) = partitionEithers $ map toEither $ M.toList m (errfinals, finals) = partitionEithers $ map toEither $ M.toList efinals errs = errlibs ++ errfinals if null errs then do let toTask (_, ADRFound _ _) = Nothing toTask (name, ADRToInstall task) = Just (name, task) tasks = M.fromList $ mapMaybe toTask adrs takeSubset = case boptsCLIBuildSubset $ bcoBuildOptsCLI baseConfigOpts0 of BSAll -> id BSOnlySnapshot -> stripLocals BSOnlyDependencies -> stripNonDeps deps return $ takeSubset Plan { planTasks = tasks , planFinals = M.fromList finals , planUnregisterLocal = mkUnregisterLocal tasks dirtyReason localDumpPkgs sourceMap initialBuildSteps , planInstallExes = if boptsInstallExes $ bcoBuildOpts baseConfigOpts0 then installExes else Map.empty } else do planDebug $ show errs stackYaml <- view stackYamlL $prettyError $ pprintExceptions errs stackYaml parents (wantedLocalPackages locals) throwM $ ConstructPlanFailed "Plan construction failed." where ctx econfig getVersions0 lf = Ctx { mbp = mbp0 , baseConfigOpts = baseConfigOpts0 , loadPackage = loadPackage0 , combinedMap = combineMap sourceMap installedMap , toolToPackages = \(Cabal.Dependency name _) -> maybe Map.empty (Map.fromSet (const Cabal.anyVersion)) $ Map.lookup (T.pack . packageNameString . fromCabalPackageName $ name) toolMap , ctxEnvConfig = econfig , callStack = [] , extraToBuild = extraToBuild0 , getVersions = getVersions0 , wanted = wantedLocalPackages locals <> extraToBuild0 , localNames = Set.fromList $ map (packageName . lpPackage) locals , logFunc = lf } -- TODO Currently, this will only consider and install tools from the -- snapshot. It will not automatically install build tools from extra-deps -- or local packages. toolMap = getToolMap mbp0 -- | State to be maintained during the calculation of local packages -- to unregister. data UnregisterState = UnregisterState { usToUnregister :: !(Map GhcPkgId (PackageIdentifier, Text)) , usKeep :: ![DumpPackage () () ()] , usAnyAdded :: !Bool } -- | Determine which packages to unregister based on the given tasks and -- already registered local packages mkUnregisterLocal :: Map PackageName Task -- ^ Tasks -> Map PackageName Text -- ^ Reasons why packages are dirty and must be rebuilt -> [DumpPackage () () ()] -- ^ Local package database dump -> SourceMap -> Bool -- ^ If true, we're doing a special initialBuildSteps -- build - don't unregister target packages. -> Map GhcPkgId (PackageIdentifier, Text) mkUnregisterLocal tasks dirtyReason localDumpPkgs sourceMap initialBuildSteps = -- We'll take multiple passes through the local packages. This -- will allow us to detect that a package should be unregistered, -- as well as all packages directly or transitively depending on -- it. loop Map.empty localDumpPkgs where loop toUnregister keep -- If any new packages were added to the unregister Map, we -- need to loop through the remaining packages again to detect -- if a transitive dependency is being unregistered. | usAnyAdded us = loop (usToUnregister us) (usKeep us) -- Nothing added, so we've already caught them all. Return the -- Map we've already calculated. | otherwise = usToUnregister us where -- Run the unregister checking function on all packages we -- currently think we'll be keeping. us = execState (mapM_ go keep) UnregisterState { usToUnregister = toUnregister , usKeep = [] , usAnyAdded = False } go dp = do us <- get case go' (usToUnregister us) ident deps of -- Not unregistering, add it to the keep list Nothing -> put us { usKeep = dp : usKeep us } -- Unregistering, add it to the unregister Map and -- indicate that a package was in fact added to the -- unregister Map so we loop again. Just reason -> put us { usToUnregister = Map.insert gid (ident, reason) (usToUnregister us) , usAnyAdded = True } where gid = dpGhcPkgId dp ident = dpPackageIdent dp deps = dpDepends dp go' toUnregister ident deps -- If we're planning on running a task on it, then it must be -- unregistered, unless it's a target and an initial-build-steps -- build is being done. | Just task <- Map.lookup name tasks = if initialBuildSteps && taskIsTarget task && taskProvides task == ident then Nothing else Just $ fromMaybe "" $ Map.lookup name dirtyReason -- Check if we're no longer using the local version | Just (PSUpstream _ Snap _ _ _) <- Map.lookup name sourceMap = Just "Switching to snapshot installed package" -- Check if a dependency is going to be unregistered | (dep, _):_ <- mapMaybe (`Map.lookup` toUnregister) deps = Just $ "Dependency being unregistered: " <> packageIdentifierText dep -- None of the above, keep it! | otherwise = Nothing where name = packageIdentifierName ident -- | Given a 'LocalPackage' and its 'lpTestBench', adds a 'Task' for -- running its tests and benchmarks. -- -- If @isAllInOne@ is 'True', then this means that the build step will -- also build the tests. Otherwise, this indicates that there's a cyclic -- dependency and an additional build step needs to be done. -- -- This will also add all the deps needed to build the tests / -- benchmarks. If @isAllInOne@ is 'True' (the common case), then all of -- these should have already been taken care of as part of the build -- step. addFinal :: LocalPackage -> Package -> Bool -> M () addFinal lp package isAllInOne = do depsRes <- addPackageDeps package res <- case depsRes of Left e -> return $ Left e Right (missing, present, _minLoc) -> do ctx <- ask return $ Right Task { taskProvides = PackageIdentifier (packageName package) (packageVersion package) , taskConfigOpts = TaskConfigOpts missing $ \missing' -> let allDeps = Map.union present missing' in configureOpts (view envConfigL ctx) (baseConfigOpts ctx) allDeps True -- local Local package , taskPresent = present , taskType = TTLocal lp , taskAllInOne = isAllInOne , taskCachePkgSrc = CacheSrcLocal (toFilePath (lpDir lp)) } tell mempty { wFinals = Map.singleton (packageName package) res } -- | Given a 'PackageName', adds all of the build tasks to build the -- package, if needed. -- -- 'constructPlan' invokes this on all the target packages, setting -- @treatAsDep'@ to False, because those packages are direct build -- targets. 'addPackageDeps' invokes this while recursing into the -- dependencies of a package. As such, it sets @treatAsDep'@ to True, -- forcing this package to be marked as a dependency, even if it is -- directly wanted. This makes sense - if we left out packages that are -- deps, it would break the --only-dependencies build plan. addDep :: Bool -- ^ is this being used by a dependency? -> PackageName -> M (Either ConstructPlanException AddDepRes) addDep treatAsDep' name = do ctx <- ask let treatAsDep = treatAsDep' || name `Set.notMember` wanted ctx when treatAsDep $ markAsDep name m <- get case Map.lookup name m of Just res -> do planDebug $ "addDep: Using cached result for " ++ show name ++ ": " ++ show res return res Nothing -> do res <- if name `elem` callStack ctx then do planDebug $ "addDep: Detected cycle " ++ show name ++ ": " ++ show (callStack ctx) return $ Left $ DependencyCycleDetected $ name : callStack ctx else local (\ctx' -> ctx' { callStack = name : callStack ctx' }) $ do let mpackageInfo = Map.lookup name $ combinedMap ctx planDebug $ "addDep: Package info for " ++ show name ++ ": " ++ show mpackageInfo case mpackageInfo of -- TODO look up in the package index and see if there's a -- recommendation available Nothing -> return $ Left $ UnknownPackage name Just (PIOnlyInstalled loc installed) -> do -- slightly hacky, no flags since they likely won't affect executable names tellExecutablesUpstream name (installedVersion installed) loc Map.empty return $ Right $ ADRFound loc installed Just (PIOnlySource ps) -> do tellExecutables name ps installPackage name ps Nothing Just (PIBoth ps installed) -> do tellExecutables name ps installPackage name ps (Just installed) updateLibMap name res return res tellExecutables :: PackageName -> PackageSource -> M () tellExecutables _ (PSLocal lp) | lpWanted lp = tellExecutablesPackage Local $ lpPackage lp | otherwise = return () -- Ignores ghcOptions because they don't matter for enumerating -- executables. tellExecutables name (PSUpstream version loc flags _ghcOptions _gitSha) = tellExecutablesUpstream name version loc flags tellExecutablesUpstream :: PackageName -> Version -> InstallLocation -> Map FlagName Bool -> M () tellExecutablesUpstream name version loc flags = do ctx <- ask when (name `Set.member` extraToBuild ctx) $ do p <- liftIO $ loadPackage ctx name version flags [] tellExecutablesPackage loc p tellExecutablesPackage :: InstallLocation -> Package -> M () tellExecutablesPackage loc p = do cm <- asks combinedMap -- Determine which components are enabled so we know which ones to copy let myComps = case Map.lookup (packageName p) cm of Nothing -> assert False Set.empty Just (PIOnlyInstalled _ _) -> Set.empty Just (PIOnlySource ps) -> goSource ps Just (PIBoth ps _) -> goSource ps goSource (PSLocal lp) | lpWanted lp = exeComponents (lpComponents lp) | otherwise = Set.empty goSource PSUpstream{} = Set.empty tell mempty { wInstall = Map.fromList $ map (, loc) $ Set.toList $ filterComps myComps $ packageExes p } where filterComps myComps x | Set.null myComps = x | otherwise = Set.intersection x myComps -- | Given a 'PackageSource' and perhaps an 'Installed' value, adds -- build 'Task's for the package and its dependencies. installPackage :: PackageName -> PackageSource -> Maybe Installed -> M (Either ConstructPlanException AddDepRes) installPackage name ps minstalled = do ctx <- ask case ps of PSUpstream version _ flags ghcOptions _ -> do planDebug $ "installPackage: Doing all-in-one build for upstream package " ++ show name package <- liftIO $ loadPackage ctx name version flags ghcOptions resolveDepsAndInstall True ps package minstalled PSLocal lp -> case lpTestBench lp of Nothing -> do planDebug $ "installPackage: No test / bench component for " ++ show name ++ " so doing an all-in-one build." resolveDepsAndInstall True ps (lpPackage lp) minstalled Just tb -> do -- Attempt to find a plan which performs an all-in-one -- build. Ignore the writer action + reset the state if -- it fails. s <- get res <- pass $ do res <- addPackageDeps tb let writerFunc w = case res of Left _ -> mempty _ -> w return (res, writerFunc) case res of Right deps -> do planDebug $ "installPackage: For " ++ show name ++ ", successfully added package deps" adr <- installPackageGivenDeps True ps tb minstalled deps -- FIXME: this redundantly adds the deps (but -- they'll all just get looked up in the map) addFinal lp tb True return $ Right adr Left _ -> do -- Reset the state to how it was before -- attempting to find an all-in-one build -- plan. planDebug $ "installPackage: Before trying cyclic plan, resetting lib result map to " ++ show s put s -- Otherwise, fall back on building the -- tests / benchmarks in a separate step. res' <- resolveDepsAndInstall False ps (lpPackage lp) minstalled when (isRight res') $ do -- Insert it into the map so that it's -- available for addFinal. updateLibMap name res' addFinal lp tb False return res' resolveDepsAndInstall :: Bool -> PackageSource -> Package -> Maybe Installed -> M (Either ConstructPlanException AddDepRes) resolveDepsAndInstall isAllInOne ps package minstalled = do res <- addPackageDeps package case res of Left err -> return $ Left err Right deps -> liftM Right $ installPackageGivenDeps isAllInOne ps package minstalled deps -- | Checks if we need to install the given 'Package', given the results -- of 'addPackageDeps'. If dependencies are missing, the package is -- dirty, or it's not installed, then it needs to be installed. installPackageGivenDeps :: Bool -> PackageSource -> Package -> Maybe Installed -> ( Set PackageIdentifier , Map PackageIdentifier GhcPkgId , InstallLocation ) -> M AddDepRes installPackageGivenDeps isAllInOne ps package minstalled (missing, present, minLoc) = do let name = packageName package ctx <- ask mRightVersionInstalled <- case (minstalled, Set.null missing) of (Just installed, True) -> do shouldInstall <- checkDirtiness ps installed package present (wanted ctx) return $ if shouldInstall then Nothing else Just installed (Just _, False) -> do let t = T.intercalate ", " $ map (T.pack . packageNameString . packageIdentifierName) (Set.toList missing) tell mempty { wDirty = Map.singleton name $ "missing dependencies: " <> addEllipsis t } return Nothing (Nothing, _) -> return Nothing return $ case mRightVersionInstalled of Just installed -> ADRFound (piiLocation ps) installed Nothing -> ADRToInstall Task { taskProvides = PackageIdentifier (packageName package) (packageVersion package) , taskConfigOpts = TaskConfigOpts missing $ \missing' -> let allDeps = Map.union present missing' destLoc = piiLocation ps <> minLoc in configureOpts (view envConfigL ctx) (baseConfigOpts ctx) allDeps (psLocal ps) -- An assertion to check for a recurrence of -- https://github.com/commercialhaskell/stack/issues/345 (assert (destLoc == piiLocation ps) destLoc) package , taskPresent = present , taskType = case ps of PSLocal lp -> TTLocal lp PSUpstream _ loc _ _ sha -> TTUpstream package (loc <> minLoc) sha , taskAllInOne = isAllInOne , taskCachePkgSrc = toCachePkgSrc ps } -- Update response in the lib map. If it is an error, and there's -- already an error about cyclic dependencies, prefer the cyclic error. updateLibMap :: PackageName -> Either ConstructPlanException AddDepRes -> M () updateLibMap name val = modify $ \mp -> case (M.lookup name mp, val) of (Just (Left DependencyCycleDetected{}), Left _) -> mp _ -> M.insert name val mp addEllipsis :: Text -> Text addEllipsis t | T.length t < 100 = t | otherwise = T.take 97 t <> "..." -- | Given a package, recurses into all of its dependencies. The results -- indicate which packages are missing, meaning that their 'GhcPkgId's -- will be figured out during the build, after they've been built. The -- 2nd part of the tuple result indicates the packages that are already -- installed which will be used. -- -- The 3rd part of the tuple is an 'InstallLocation'. If it is 'Local', -- then the parent package must be installed locally. Otherwise, if it -- is 'Snap', then it can either be installed locally or in the -- snapshot. addPackageDeps :: Package -> M (Either ConstructPlanException (Set PackageIdentifier, Map PackageIdentifier GhcPkgId, InstallLocation)) addPackageDeps package = do ctx <- ask deps' <- packageDepsWithTools package deps <- forM (Map.toList deps') $ \(depname, range) -> do eres <- addDep True depname let getLatestApplicable = do vs <- liftIO $ getVersions ctx depname return (latestApplicableVersion range vs) case eres of Left e -> do addParent depname range Nothing let bd = case e of UnknownPackage name -> assert (name == depname) NotInBuildPlan _ -> Couldn'tResolveItsDependencies (packageVersion package) mlatestApplicable <- getLatestApplicable return $ Left (depname, (range, mlatestApplicable, bd)) Right adr -> do addParent depname range Nothing inRange <- if adrVersion adr `withinRange` range then return True else do let warn_ reason = tell mempty { wWarnings = (msg:) } where msg = T.concat [ "WARNING: Ignoring out of range dependency" , reason , ": " , T.pack $ packageIdentifierString $ PackageIdentifier depname (adrVersion adr) , ". " , T.pack $ packageNameString $ packageName package , " requires: " , versionRangeText range ] allowNewer <- view $ configL.to configAllowNewer if allowNewer then do warn_ " (allow-newer enabled)" return True else do x <- inSnapshot (packageName package) (packageVersion package) y <- inSnapshot depname (adrVersion adr) if x && y then do warn_ " (trusting snapshot over Hackage revisions)" return True else return False if inRange then case adr of ADRToInstall task -> return $ Right (Set.singleton $ taskProvides task, Map.empty, taskLocation task) ADRFound loc (Executable _) -> return $ Right (Set.empty, Map.empty, loc) ADRFound loc (Library ident gid) -> return $ Right (Set.empty, Map.singleton ident gid, loc) else do mlatestApplicable <- getLatestApplicable return $ Left (depname, (range, mlatestApplicable, DependencyMismatch $ adrVersion adr)) case partitionEithers deps of -- Note that the Monoid for 'InstallLocation' means that if any -- is 'Local', the result is 'Local', indicating that the parent -- package must be installed locally. Otherwise the result is -- 'Snap', indicating that the parent can either be installed -- locally or in the snapshot. ([], pairs) -> return $ Right $ mconcat pairs (errs, _) -> return $ Left $ DependencyPlanFailures package (Map.fromList errs) where adrVersion (ADRToInstall task) = packageIdentifierVersion $ taskProvides task adrVersion (ADRFound _ installed) = installedVersion installed -- Update the parents map, for later use in plan construction errors -- - see 'getShortestDepsPath'. addParent depname range mversion = tell mempty { wParents = MonoidMap $ M.singleton depname val } where val = (First mversion, [(packageIdentifier package, range)]) checkDirtiness :: PackageSource -> Installed -> Package -> Map PackageIdentifier GhcPkgId -> Set PackageName -> M Bool checkDirtiness ps installed package present wanted = do ctx <- ask moldOpts <- flip runLoggingT (logFunc ctx) $ tryGetFlagCache installed let configOpts = configureOpts (view envConfigL ctx) (baseConfigOpts ctx) present (psLocal ps) (piiLocation ps) -- should be Local always package buildOpts = bcoBuildOpts (baseConfigOpts ctx) wantConfigCache = ConfigCache { configCacheOpts = configOpts , configCacheDeps = Set.fromList $ Map.elems present , configCacheComponents = case ps of PSLocal lp -> Set.map renderComponent $ lpComponents lp PSUpstream{} -> Set.empty , configCacheHaddock = shouldHaddockPackage buildOpts wanted (packageName package) || -- Disabling haddocks when old config had haddocks doesn't make dirty. maybe False configCacheHaddock moldOpts , configCachePkgSrc = toCachePkgSrc ps } let mreason = case moldOpts of Nothing -> Just "old configure information not found" Just oldOpts | Just reason <- describeConfigDiff config oldOpts wantConfigCache -> Just reason | True <- psForceDirty ps -> Just "--force-dirty specified" | Just files <- psDirty ps -> Just $ "local file changes: " <> addEllipsis (T.pack $ unwords $ Set.toList files) | otherwise -> Nothing config = view configL ctx case mreason of Nothing -> return False Just reason -> do tell mempty { wDirty = Map.singleton (packageName package) reason } return True describeConfigDiff :: Config -> ConfigCache -> ConfigCache -> Maybe Text describeConfigDiff config old new | configCachePkgSrc old /= configCachePkgSrc new = Just $ "switching from " <> pkgSrcName (configCachePkgSrc old) <> " to " <> pkgSrcName (configCachePkgSrc new) | not (configCacheDeps new `Set.isSubsetOf` configCacheDeps old) = Just "dependencies changed" | not $ Set.null newComponents = Just $ "components added: " `T.append` T.intercalate ", " (map (decodeUtf8With lenientDecode) (Set.toList newComponents)) | not (configCacheHaddock old) && configCacheHaddock new = Just "rebuilding with haddocks" | oldOpts /= newOpts = Just $ T.pack $ concat [ "flags changed from " , show oldOpts , " to " , show newOpts ] | otherwise = Nothing where stripGhcOptions = go where go [] = [] go ("--ghc-option":x:xs) = go' Ghc x xs go ("--ghc-options":x:xs) = go' Ghc x xs go ((T.stripPrefix "--ghc-option=" -> Just x):xs) = go' Ghc x xs go ((T.stripPrefix "--ghc-options=" -> Just x):xs) = go' Ghc x xs go ("--ghcjs-option":x:xs) = go' Ghcjs x xs go ("--ghcjs-options":x:xs) = go' Ghcjs x xs go ((T.stripPrefix "--ghcjs-option=" -> Just x):xs) = go' Ghcjs x xs go ((T.stripPrefix "--ghcjs-options=" -> Just x):xs) = go' Ghcjs x xs go (x:xs) = x : go xs go' wc x xs = checkKeepers wc x $ go xs checkKeepers wc x xs = case filter isKeeper $ T.words x of [] -> xs keepers -> T.pack (compilerOptionsCabalFlag wc) : T.unwords keepers : xs -- GHC options which affect build results and therefore should always -- force a rebuild -- -- For the most part, we only care about options generated by Stack -- itself isKeeper = (== "-fhpc") -- more to be added later userOpts = filter (not . isStackOpt) . (if configRebuildGhcOptions config then id else stripGhcOptions) . map T.pack . (\(ConfigureOpts x y) -> x ++ y) . configCacheOpts (oldOpts, newOpts) = removeMatching (userOpts old) (userOpts new) removeMatching (x:xs) (y:ys) | x == y = removeMatching xs ys removeMatching xs ys = (xs, ys) newComponents = configCacheComponents new `Set.difference` configCacheComponents old pkgSrcName (CacheSrcLocal fp) = T.pack fp pkgSrcName CacheSrcUpstream = "upstream source" psForceDirty :: PackageSource -> Bool psForceDirty (PSLocal lp) = lpForceDirty lp psForceDirty PSUpstream{} = False psDirty :: PackageSource -> Maybe (Set FilePath) psDirty (PSLocal lp) = lpDirtyFiles lp psDirty PSUpstream{} = Nothing -- files never change in an upstream package psLocal :: PackageSource -> Bool psLocal (PSLocal _) = True psLocal PSUpstream{} = False -- | Get all of the dependencies for a given package, including guessed build -- tool dependencies. packageDepsWithTools :: Package -> M (Map PackageName VersionRange) packageDepsWithTools p = do ctx <- ask -- TODO: it would be cool to defer these warnings until there's an -- actual issue building the package. let toEither (Cabal.Dependency (Cabal.PackageName name) _) mp = case Map.toList mp of [] -> Left (NoToolFound name (packageName p)) [_] -> Right mp xs -> Left (AmbiguousToolsFound name (packageName p) (map fst xs)) (warnings0, toolDeps) = partitionEithers $ map (\dep -> toEither dep (toolToPackages ctx dep)) (packageTools p) -- Check whether the tool is on the PATH before warning about it. warnings <- fmap catMaybes $ forM warnings0 $ \warning -> do let toolName = case warning of NoToolFound tool _ -> tool AmbiguousToolsFound tool _ _ -> tool config <- view configL menv <- liftIO $ configEnvOverride config minimalEnvSettings { esIncludeLocals = True } mfound <- findExecutable menv toolName case mfound of Nothing -> return (Just warning) Just _ -> return Nothing tell mempty { wWarnings = (map toolWarningText warnings ++) } when (any isNoToolFound warnings) $ do let msg = T.unlines [ "Missing build-tools may be caused by dependencies of the build-tool being overridden by extra-deps." , "This should be fixed soon - see this issue https://github.com/commercialhaskell/stack/issues/595" ] tell mempty { wWarnings = (msg:) } return $ Map.unionsWith intersectVersionRanges $ packageDeps p : toolDeps data ToolWarning = NoToolFound String PackageName | AmbiguousToolsFound String PackageName [PackageName] isNoToolFound :: ToolWarning -> Bool isNoToolFound NoToolFound{} = True isNoToolFound _ = False toolWarningText :: ToolWarning -> Text toolWarningText (NoToolFound toolName pkgName) = "No packages found in snapshot which provide a " <> T.pack (show toolName) <> " executable, which is a build-tool dependency of " <> T.pack (show (packageNameString pkgName)) toolWarningText (AmbiguousToolsFound toolName pkgName options) = "Multiple packages found in snapshot which provide a " <> T.pack (show toolName) <> " exeuctable, which is a build-tool dependency of " <> T.pack (show (packageNameString pkgName)) <> ", so none will be installed.\n" <> "Here's the list of packages which provide it: " <> T.intercalate ", " (map packageNameText options) <> "\nSince there's no good way to choose, you may need to install it manually." -- | Strip out anything from the @Plan@ intended for the local database stripLocals :: Plan -> Plan stripLocals plan = plan { planTasks = Map.filter checkTask $ planTasks plan , planFinals = Map.empty , planUnregisterLocal = Map.empty , planInstallExes = Map.filter (/= Local) $ planInstallExes plan } where checkTask task = case taskType task of TTLocal _ -> False TTUpstream _ Local _ -> False TTUpstream _ Snap _ -> True stripNonDeps :: Set PackageName -> Plan -> Plan stripNonDeps deps plan = plan { planTasks = Map.filter checkTask $ planTasks plan , planFinals = Map.empty , planInstallExes = Map.empty -- TODO maybe don't disable this? } where checkTask task = packageIdentifierName (taskProvides task) `Set.member` deps markAsDep :: PackageName -> M () markAsDep name = tell mempty { wDeps = Set.singleton name } -- | Is the given package/version combo defined in the snapshot? inSnapshot :: PackageName -> Version -> M Bool inSnapshot name version = do p <- asks mbp ls <- asks localNames return $ fromMaybe False $ do guard $ not $ name `Set.member` ls mpi <- Map.lookup name (mbpPackages p) return $ mpiVersion mpi == version data ConstructPlanException = DependencyCycleDetected [PackageName] | DependencyPlanFailures Package (Map PackageName (VersionRange, LatestApplicableVersion, BadDependency)) | UnknownPackage PackageName -- TODO perhaps this constructor will be removed, and BadDependency will handle it all -- ^ Recommend adding to extra-deps, give a helpful version number? deriving (Typeable, Eq, Ord, Show) deriving instance Ord VersionRange -- | For display purposes only, Nothing if package not found type LatestApplicableVersion = Maybe Version -- | Reason why a dependency was not used data BadDependency = NotInBuildPlan | Couldn'tResolveItsDependencies Version | DependencyMismatch Version deriving (Typeable, Eq, Ord, Show) -- TODO: Consider intersecting version ranges for multiple deps on a -- package. This is why VersionRange is in the parent map. pprintExceptions :: [ConstructPlanException] -> Path Abs File -> ParentMap -> Set PackageName -> AnsiDoc pprintExceptions exceptions stackYaml parentMap wanted = "While constructing the build plan, the following exceptions were encountered:" <> line <> line <> mconcat (intersperse (line <> line) (mapMaybe pprintException exceptions')) <> line <> if Map.null extras then "" else line <> "Recommended action: try adding the following to your extra-deps in" <+> toAnsiDoc (display stackYaml) <> ":" <> line <> vsep (map pprintExtra (Map.toList extras)) <> line <> line <> "You may also want to try the 'stack solver' command" where exceptions' = nubOrd exceptions extras = Map.unions $ map getExtras exceptions' getExtras (DependencyCycleDetected _) = Map.empty getExtras (UnknownPackage _) = Map.empty getExtras (DependencyPlanFailures _ m) = Map.unions $ map go $ Map.toList m where go (name, (_range, Just version, NotInBuildPlan)) = Map.singleton name version go _ = Map.empty pprintExtra (name, version) = fromString (concat ["- ", packageNameString name, "-", versionString version]) allNotInBuildPlan = Set.fromList $ concatMap toNotInBuildPlan exceptions' toNotInBuildPlan (DependencyPlanFailures _ pDeps) = map fst $ filter (\(_, (_, _, badDep)) -> badDep == NotInBuildPlan) $ Map.toList pDeps toNotInBuildPlan _ = [] pprintException (DependencyCycleDetected pNames) = Just $ "Dependency cycle detected in packages:" <> line <> indent 4 (encloseSep "[" "]" "," (map (errorRed . fromString . packageNameString) pNames)) pprintException (DependencyPlanFailures pkg pDeps) = case mapMaybe pprintDep (Map.toList pDeps) of [] -> Nothing depErrors -> Just $ "In the dependencies for" <+> pkgIdent <> pprintFlags (packageFlags pkg) <> ":" <> line <> indent 4 (vsep depErrors) <> case getShortestDepsPath parentMap wanted (packageName pkg) of Nothing -> line <> "needed for unknown reason - stack invariant violated." Just [] -> line <> "needed since" <+> pkgIdent <+> "is a build target." Just (target:path) -> line <> "needed due to " <> encloseSep "" "" " -> " pathElems where pathElems = [displayTargetPkgId target] ++ map display path ++ [pkgIdent] where pkgIdent = displayCurrentPkgId (packageIdentifier pkg) -- Skip these when they are redundant with 'NotInBuildPlan' info. pprintException (UnknownPackage name) | name `Set.member` allNotInBuildPlan = Nothing | name `HashSet.member` wiredInPackages = Just $ "Can't build a package with same name as a wired-in-package:" <+> displayCurrentPkgName name | otherwise = Just $ "Unknown package:" <+> displayCurrentPkgName name pprintFlags flags | Map.null flags = "" | otherwise = parens $ sep $ map pprintFlag $ Map.toList flags pprintFlag (name, True) = "+" <> fromString (show name) pprintFlag (name, False) = "-" <> fromString (show name) pprintDep (name, (range, mlatestApplicable, badDep)) = case badDep of NotInBuildPlan -> Just $ errorRed (display name) <+> align ("must match" <+> goodRange <> "," <> softline <> "but the stack configuration has no specified version" <> latestApplicable Nothing) -- TODO: For local packages, suggest editing constraints DependencyMismatch version -> Just $ displayErrorPkgId (PackageIdentifier name version) <+> align ("must match" <+> goodRange <> latestApplicable (Just version)) -- I think the main useful info is these explain why missing -- packages are needed. Instead lets give the user the shortest -- path from a target to the package. Couldn'tResolveItsDependencies _version -> Nothing where goodRange = goodGreen (fromString (Cabal.display range)) latestApplicable mversion = case mlatestApplicable of Nothing -> "" Just la | mlatestApplicable == mversion -> softline <> "(latest applicable is specified)" | otherwise -> softline <> "(latest applicable is " <> goodGreen (display la) <> ")" -- | Get the shortest reason for the package to be in the build plan. In -- other words, trace the parent dependencies back to a 'wanted' -- package. getShortestDepsPath :: ParentMap -> Set PackageName -> PackageName -> Maybe [PackageIdentifier] getShortestDepsPath (MonoidMap parentsMap) wanted name = if Set.member name wanted then Just [] else case M.lookup name parentsMap of Nothing -> Nothing Just (_, parents) -> Just $ findShortest 256 paths0 where paths0 = M.fromList $ map (\(ident, _) -> (packageIdentifierName ident, startDepsPath ident)) parents where -- The 'paths' map is a map from PackageName to the shortest path -- found to get there. It is the frontier of our breadth-first -- search of dependencies. findShortest :: Int -> Map PackageName DepsPath -> [PackageIdentifier] findShortest fuel _ | fuel <= 0 = [PackageIdentifier $(mkPackageName "stack-ran-out-of-jet-fuel") $(mkVersion "0")] findShortest _ paths | M.null paths = [] findShortest fuel paths = case targets of [] -> findShortest (fuel - 1) $ M.fromListWith chooseBest $ concatMap extendPath recurses _ -> let (DepsPath _ _ path) = minimum (map snd targets) in path where (targets, recurses) = partition (\(n, _) -> n `Set.member` wanted) (M.toList paths) chooseBest :: DepsPath -> DepsPath -> DepsPath chooseBest x y = if x > y then x else y -- Extend a path to all its parents. extendPath :: (PackageName, DepsPath) -> [(PackageName, DepsPath)] extendPath (n, dp) = case M.lookup n parentsMap of Nothing -> [] Just (_, parents) -> map (\(pkgId, _) -> (packageIdentifierName pkgId, extendDepsPath pkgId dp)) parents data DepsPath = DepsPath { dpLength :: Int -- ^ Length of dpPath , dpNameLength :: Int -- ^ Length of package names combined , dpPath :: [PackageIdentifier] -- ^ A path where the packages later -- in the list depend on those that -- come earlier } deriving (Eq, Ord, Show) startDepsPath :: PackageIdentifier -> DepsPath startDepsPath ident = DepsPath { dpLength = 1 , dpNameLength = T.length (packageNameText (packageIdentifierName ident)) , dpPath = [ident] } extendDepsPath :: PackageIdentifier -> DepsPath -> DepsPath extendDepsPath ident dp = DepsPath { dpLength = dpLength dp + 1 , dpNameLength = dpNameLength dp + T.length (packageNameText (packageIdentifierName ident)) , dpPath = [ident] } -- Utility newtype wrapper to make make Map's Monoid also use the -- element's Monoid. newtype MonoidMap k a = MonoidMap (Map k a) deriving (Eq, Ord, Read, Show, Generic, Functor) instance (Ord k, Monoid a) => Monoid (MonoidMap k a) where mappend (MonoidMap mp1) (MonoidMap mp2) = MonoidMap (M.unionWith mappend mp1 mp2) mempty = MonoidMap mempty -- Switch this to 'True' to enable some debugging putStrLn in this module planDebug :: MonadIO m => String -> m () planDebug = if False then liftIO . putStrLn else \_ -> return () stack-1.5.1/src/Stack/Build/Execute.hs0000644000000000000000000024626713135652051015664 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ScopedTypeVariables #-} -- | Perform a build module Stack.Build.Execute ( printPlan , preFetch , executePlan -- * Running Setup.hs , ExecuteEnv , withExecuteEnv , withSingleContext , ExcludeTHLoading(..) ) where import Control.Applicative import Control.Arrow ((&&&), second) import Control.Concurrent.Execute import Control.Concurrent.MVar.Lifted import Control.Concurrent.STM import Control.Exception.Safe (catchIO) import Control.Exception.Lifted import Control.Monad (liftM, when, unless, void) import Control.Monad.Catch (MonadCatch) import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.Trans.Control (liftBaseWith) import Control.Monad.Trans.Resource import Crypto.Hash import Data.Attoparsec.Text hiding (try) import qualified Data.ByteArray as Mem (convert) import qualified Data.ByteString as S import qualified Data.ByteString.Base64.URL as B64URL import Data.Char (isSpace) import Data.Conduit import qualified Data.Conduit.Binary as CB import qualified Data.Conduit.List as CL import qualified Data.Conduit.Text as CT import Data.Either (isRight) import Data.FileEmbed (embedFile, makeRelativeToProject) import Data.Foldable (forM_, any) import Data.Function import Data.IORef import Data.IORef.RunOnce (runOnce) import Data.List hiding (any) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Maybe import Data.Maybe.Extra (forMaybeM) import Data.Monoid import Data.Set (Set) import qualified Data.Set as Set import Data.Streaming.Process hiding (callProcess, env) import Data.String import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeUtf8, encodeUtf8) import Data.Text.Extra (stripCR) import Data.Time.Clock (getCurrentTime) import Data.Traversable (forM) import Data.Tuple import qualified Distribution.PackageDescription as C import qualified Distribution.Simple.Build.Macros as C import Distribution.System (OS (Windows), Platform (Platform)) import qualified Distribution.Text as C import Language.Haskell.TH as TH (location) import Path import Path.CheckInstall import Path.Extra (toFilePathNoTrailingSep, rejectMissingFile) import Path.IO hiding (findExecutable, makeAbsolute) import Prelude hiding (FilePath, writeFile, any) import Stack.Build.Cache import Stack.Build.Haddock import Stack.Build.Installed import Stack.Build.Source import Stack.Build.Target import Stack.Config import Stack.Constants import Stack.Coverage import Stack.Fetch as Fetch import Stack.GhcPkg import Stack.Package import Stack.PackageDump import Stack.PrettyPrint import Stack.Types.Build import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.GhcPkgId import Stack.Types.Internal import Stack.Types.Package import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.StackT import Stack.Types.Version import qualified System.Directory as D import System.Environment (getExecutablePath) import System.Exit (ExitCode (ExitSuccess)) import qualified System.FilePath as FP import System.IO import System.PosixCompat.Files (createLink) import System.Process.Log (showProcessArgDebug, withProcessTimeLog) import System.Process.Read import System.Process.Run #if !MIN_VERSION_process(1,2,1) import System.Process.Internals (createProcess_) #endif -- | Fetch the packages necessary for a build, for example in combination with a dry run. preFetch :: (StackM env m, HasEnvConfig env) => Plan -> m () preFetch plan | Set.null idents = $logDebug "Nothing to fetch" | otherwise = do $logDebug $ T.pack $ "Prefetching: " ++ intercalate ", " (map packageIdentifierString $ Set.toList idents) fetchPackages idents where idents = Set.unions $ map toIdent $ Map.toList $ planTasks plan toIdent (name, task) = case taskType task of TTLocal _ -> Set.empty TTUpstream package _ _ -> Set.singleton $ PackageIdentifier name (packageVersion package) -- | Print a description of build plan for human consumption. printPlan :: (StackM env m) => Plan -> m () printPlan plan = do case Map.elems $ planUnregisterLocal plan of [] -> $logInfo "No packages would be unregistered." xs -> do $logInfo "Would unregister locally:" forM_ xs $ \(ident, reason) -> $logInfo $ T.concat [ T.pack $ packageIdentifierString ident , if T.null reason then "" else T.concat [ " (" , reason , ")" ] ] $logInfo "" case Map.elems $ planTasks plan of [] -> $logInfo "Nothing to build." xs -> do $logInfo "Would build:" mapM_ ($logInfo . displayTask) xs let hasTests = not . Set.null . testComponents . taskComponents hasBenches = not . Set.null . benchComponents . taskComponents tests = Map.elems $ Map.filter hasTests $ planFinals plan benches = Map.elems $ Map.filter hasBenches $ planFinals plan unless (null tests) $ do $logInfo "" $logInfo "Would test:" mapM_ ($logInfo . displayTask) tests unless (null benches) $ do $logInfo "" $logInfo "Would benchmark:" mapM_ ($logInfo . displayTask) benches $logInfo "" case Map.toList $ planInstallExes plan of [] -> $logInfo "No executables to be installed." xs -> do $logInfo "Would install executables:" forM_ xs $ \(name, loc) -> $logInfo $ T.concat [ name , " from " , case loc of Snap -> "snapshot" Local -> "local" , " database" ] -- | For a dry run displayTask :: Task -> Text displayTask task = T.pack $ concat [ packageIdentifierString $ taskProvides task , ": database=" , case taskLocation task of Snap -> "snapshot" Local -> "local" , ", source=" , case taskType task of TTLocal lp -> toFilePath $ lpDir lp TTUpstream{} -> "package index" , if Set.null missing then "" else ", after: " ++ intercalate "," (map packageIdentifierString $ Set.toList missing) ] where missing = tcoMissing $ taskConfigOpts task data ExecuteEnv m = ExecuteEnv { eeEnvOverride :: !EnvOverride , eeConfigureLock :: !(MVar ()) , eeInstallLock :: !(MVar ()) , eeBuildOpts :: !BuildOpts , eeBuildOptsCLI :: !BuildOptsCLI , eeBaseConfigOpts :: !BaseConfigOpts , eeGhcPkgIds :: !(TVar (Map PackageIdentifier Installed)) , eeTempDir :: !(Path Abs Dir) , eeSetupHs :: !(Path Abs File) -- ^ Temporary Setup.hs for simple builds , eeSetupShimHs :: !(Path Abs File) -- ^ Temporary SetupShim.hs, to provide access to initial-build-steps , eeSetupExe :: !(Maybe (Path Abs File)) -- ^ Compiled version of eeSetupHs , eeCabalPkgVer :: !Version , eeTotalWanted :: !Int , eeWanted :: !(Set PackageName) , eeLocals :: ![LocalPackage] , eeGlobalDB :: !(Path Abs Dir) , eeGlobalDumpPkgs :: !(Map GhcPkgId (DumpPackage () () ())) , eeSnapshotDumpPkgs :: !(TVar (Map GhcPkgId (DumpPackage () () ()))) , eeLocalDumpPkgs :: !(TVar (Map GhcPkgId (DumpPackage () () ()))) , eeLogFiles :: !(TChan (Path Abs Dir, Path Abs File)) , eeGetGhcPath :: !(m (Path Abs File)) , eeGetGhcjsPath :: !(m (Path Abs File)) , eeCustomBuilt :: !(IORef (Set PackageName)) -- ^ Stores which packages with custom-setup have already had their -- Setup.hs built. } buildSetupArgs :: [String] buildSetupArgs = [ "-rtsopts" , "-threaded" , "-clear-package-db" , "-global-package-db" , "-hide-all-packages" , "-package" , "base" , "-main-is" , "StackSetupShim.mainOverride" ] setupGhciShimCode :: S.ByteString setupGhciShimCode = $(do path <- makeRelativeToProject "src/setup-shim/StackSetupShim.hs" embedFile path) simpleSetupCode :: S.ByteString simpleSetupCode = "import Distribution.Simple\nmain = defaultMain" simpleSetupHash :: String simpleSetupHash = T.unpack $ decodeUtf8 $ S.take 8 $ B64URL.encode $ Mem.convert $ hashWith SHA256 $ encodeUtf8 (T.pack (unwords buildSetupArgs)) <> setupGhciShimCode <> simpleSetupCode -- | Get a compiled Setup exe getSetupExe :: (StackM env m, HasEnvConfig env) => Path Abs File -- ^ Setup.hs input file -> Path Abs File -- ^ SetupShim.hs input file -> Path Abs Dir -- ^ temporary directory -> m (Maybe (Path Abs File)) getSetupExe setupHs setupShimHs tmpdir = do wc <- view $ actualCompilerVersionL.whichCompilerL platformDir <- platformGhcRelDir config <- view configL cabalVersionString <- view $ cabalVersionL.to versionString actualCompilerVersionString <- view $ actualCompilerVersionL.to compilerVersionString platform <- view platformL let baseNameS = concat [ "Cabal-simple_" , simpleSetupHash , "_" , cabalVersionString , "_" , actualCompilerVersionString ] exeNameS = baseNameS ++ case platform of Platform _ Windows -> ".exe" _ -> "" outputNameS = case wc of Ghc -> exeNameS Ghcjs -> baseNameS ++ ".jsexe" jsExeNameS = baseNameS ++ ".jsexe" setupDir = configStackRoot config $(mkRelDir "setup-exe-cache") platformDir exePath <- (setupDir ) <$> parseRelFile exeNameS jsExePath <- (setupDir ) <$> parseRelDir jsExeNameS exists <- liftIO $ D.doesFileExist $ toFilePath exePath if exists then return $ Just exePath else do tmpExePath <- fmap (setupDir ) $ parseRelFile $ "tmp-" ++ exeNameS tmpOutputPath <- fmap (setupDir ) $ parseRelFile $ "tmp-" ++ outputNameS tmpJsExePath <- fmap (setupDir ) $ parseRelDir $ "tmp-" ++ jsExeNameS ensureDir setupDir menv <- getMinimalEnvOverride let args = buildSetupArgs ++ [ "-package" , "Cabal-" ++ cabalVersionString , toFilePath setupHs , toFilePath setupShimHs , "-o" , toFilePath tmpOutputPath ] ++ ["-build-runner" | wc == Ghcjs] runCmd' (\cp -> cp { std_out = UseHandle stderr }) (Cmd (Just tmpdir) (compilerExeName wc) menv args) Nothing when (wc == Ghcjs) $ renameDir tmpJsExePath jsExePath renameFile tmpExePath exePath return $ Just exePath -- | Execute a function that takes an 'ExecuteEnv'. withExecuteEnv :: forall env m a. (StackM env m, HasEnvConfig env) => EnvOverride -> BuildOpts -> BuildOptsCLI -> BaseConfigOpts -> [LocalPackage] -> [DumpPackage () () ()] -- ^ global packages -> [DumpPackage () () ()] -- ^ snapshot packages -> [DumpPackage () () ()] -- ^ local packages -> (ExecuteEnv m -> m a) -> m a withExecuteEnv menv bopts boptsCli baseConfigOpts locals globalPackages snapshotPackages localPackages inner = do withSystemTempDir stackProgName $ \tmpdir -> do configLock <- newMVar () installLock <- newMVar () idMap <- liftIO $ newTVarIO Map.empty config <- view configL getGhcPath <- runOnce $ getCompilerPath Ghc getGhcjsPath <- runOnce $ getCompilerPath Ghcjs customBuiltRef <- liftIO $ newIORef Set.empty -- Create files for simple setup and setup shim, if necessary let setupSrcDir = configStackRoot config $(mkRelDir "setup-exe-src") ensureDir setupSrcDir setupFileName <- parseRelFile ("setup-" ++ simpleSetupHash ++ ".hs") let setupHs = setupSrcDir setupFileName setupHsExists <- doesFileExist setupHs unless setupHsExists $ liftIO $ S.writeFile (toFilePath setupHs) simpleSetupCode setupShimFileName <- parseRelFile ("setup-shim-" ++ simpleSetupHash ++ ".hs") let setupShimHs = setupSrcDir setupShimFileName setupShimHsExists <- doesFileExist setupShimHs unless setupShimHsExists $ liftIO $ S.writeFile (toFilePath setupShimHs) setupGhciShimCode setupExe <- getSetupExe setupHs setupShimHs tmpdir cabalPkgVer <- view cabalVersionL globalDB <- getGlobalDB menv =<< view (actualCompilerVersionL.whichCompilerL) snapshotPackagesTVar <- liftIO $ newTVarIO (toDumpPackagesByGhcPkgId snapshotPackages) localPackagesTVar <- liftIO $ newTVarIO (toDumpPackagesByGhcPkgId localPackages) logFilesTChan <- liftIO $ atomically newTChan let totalWanted = length $ filter lpWanted locals inner ExecuteEnv { eeEnvOverride = menv , eeBuildOpts = bopts , eeBuildOptsCLI = boptsCli -- Uncertain as to why we cannot run configures in parallel. This appears -- to be a Cabal library bug. Original issue: -- https://github.com/fpco/stack/issues/84. Ideally we'd be able to remove -- this. , eeConfigureLock = configLock , eeInstallLock = installLock , eeBaseConfigOpts = baseConfigOpts , eeGhcPkgIds = idMap , eeTempDir = tmpdir , eeSetupHs = setupHs , eeSetupShimHs = setupShimHs , eeSetupExe = setupExe , eeCabalPkgVer = cabalPkgVer , eeTotalWanted = totalWanted , eeWanted = wantedLocalPackages locals , eeLocals = locals , eeGlobalDB = globalDB , eeGlobalDumpPkgs = toDumpPackagesByGhcPkgId globalPackages , eeSnapshotDumpPkgs = snapshotPackagesTVar , eeLocalDumpPkgs = localPackagesTVar , eeLogFiles = logFilesTChan , eeGetGhcPath = getGhcPath , eeGetGhcjsPath = getGhcjsPath , eeCustomBuilt = customBuiltRef } `finally` dumpLogs logFilesTChan totalWanted where toDumpPackagesByGhcPkgId = Map.fromList . map (\dp -> (dpGhcPkgId dp, dp)) dumpLogs :: TChan (Path Abs Dir, Path Abs File) -> Int -> m () dumpLogs chan totalWanted = do allLogs <- fmap reverse $ liftIO $ atomically drainChan case allLogs of -- No log files generated, nothing to dump [] -> return () firstLog:_ -> do toDump <- view $ configL.to configDumpLogs case toDump of DumpAllLogs -> mapM_ (dumpLog "") allLogs DumpWarningLogs -> mapM_ dumpLogIfWarning allLogs DumpNoLogs | totalWanted > 1 -> $logInfo $ T.concat [ "Build output has been captured to log files, use " , "--dump-logs to see it on the console" ] | otherwise -> return () $logInfo $ T.pack $ "Log files have been written to: " ++ toFilePath (parent (snd firstLog)) where drainChan :: STM [(Path Abs Dir, Path Abs File)] drainChan = do mx <- tryReadTChan chan case mx of Nothing -> return [] Just x -> do xs <- drainChan return $ x:xs dumpLogIfWarning :: (Path Abs Dir, Path Abs File) -> m () dumpLogIfWarning (pkgDir, filepath) = do firstWarning <- runResourceT $ CB.sourceFile (toFilePath filepath) $$ CT.decodeUtf8Lenient =$ CT.lines =$ CL.map stripCR =$ CL.filter isWarning =$ CL.take 1 unless (null firstWarning) $ dumpLog " due to warnings" (pkgDir, filepath) isWarning :: Text -> Bool isWarning t = ": Warning:" `T.isSuffixOf` t -- prior to GHC 8 || ": warning:" `T.isInfixOf` t -- GHC 8 is slightly different dumpLog :: String -> (Path Abs Dir, Path Abs File) -> m () dumpLog msgSuffix (pkgDir, filepath) = do $logInfo $ T.pack $ concat ["\n-- Dumping log file", msgSuffix, ": ", toFilePath filepath, "\n"] compilerVer <- view actualCompilerVersionL runResourceT $ CB.sourceFile (toFilePath filepath) $$ CT.decodeUtf8Lenient =$ mungeBuildOutput ExcludeTHLoading ConvertPathsToAbsolute pkgDir compilerVer =$ CL.mapM_ $logInfo $logInfo $ T.pack $ "\n-- End of log file: " ++ toFilePath filepath ++ "\n" -- | Perform the actual plan executePlan :: (StackM env m, HasEnvConfig env) => EnvOverride -> BuildOptsCLI -> BaseConfigOpts -> [LocalPackage] -> [DumpPackage () () ()] -- ^ global packages -> [DumpPackage () () ()] -- ^ snapshot packages -> [DumpPackage () () ()] -- ^ local packages -> InstalledMap -> Map PackageName SimpleTarget -> Plan -> m () executePlan menv boptsCli baseConfigOpts locals globalPackages snapshotPackages localPackages installedMap targets plan = do $logDebug "Executing the build plan" bopts <- view buildOptsL withExecuteEnv menv bopts boptsCli baseConfigOpts locals globalPackages snapshotPackages localPackages (executePlan' installedMap targets plan) copyExecutables (planInstallExes plan) config <- view configL menv' <- liftIO $ configEnvOverride config EnvSettings { esIncludeLocals = True , esIncludeGhcPackagePath = True , esStackExe = True , esLocaleUtf8 = False } forM_ (boptsCLIExec boptsCli) $ \(cmd, args) -> $withProcessTimeLog cmd args $ callProcess (Cmd Nothing cmd menv' args) copyExecutables :: (StackM env m, HasEnvConfig env) => Map Text InstallLocation -> m () copyExecutables exes | Map.null exes = return () copyExecutables exes = do snapBin <- ( bindirSuffix) `liftM` installationRootDeps localBin <- ( bindirSuffix) `liftM` installationRootLocal destDir <- view $ configL.to configLocalBin ensureDir destDir destDir' <- liftIO . D.canonicalizePath . toFilePath $ destDir platform <- view platformL let ext = case platform of Platform _ Windows -> ".exe" _ -> "" currExe <- liftIO getExecutablePath -- needed for windows, see below installed <- forMaybeM (Map.toList exes) $ \(name, loc) -> do let bindir = case loc of Snap -> snapBin Local -> localBin mfp <- forgivingAbsence (resolveFile bindir $ T.unpack name ++ ext) >>= rejectMissingFile case mfp of Nothing -> do $logWarn $ T.concat [ "Couldn't find executable " , name , " in directory " , T.pack $ toFilePath bindir ] return Nothing Just file -> do let destFile = destDir' FP. T.unpack name ++ ext $logInfo $ T.concat [ "Copying from " , T.pack $ toFilePath file , " to " , T.pack destFile ] liftIO $ case platform of Platform _ Windows | FP.equalFilePath destFile currExe -> windowsRenameCopy (toFilePath file) destFile _ -> D.copyFile (toFilePath file) destFile return $ Just (name <> T.pack ext) unless (null installed) $ do $logInfo "" $logInfo $ T.concat [ "Copied executables to " , T.pack destDir' , ":"] forM_ installed $ \exe -> $logInfo ("- " <> exe) warnInstallSearchPathIssues destDir' installed -- | Windows can't write over the current executable. Instead, we rename the -- current executable to something else and then do the copy. windowsRenameCopy :: FilePath -> FilePath -> IO () windowsRenameCopy src dest = do D.copyFile src new D.renameFile dest old D.renameFile new dest where new = dest ++ ".new" old = dest ++ ".old" -- | Perform the actual plan (internal) executePlan' :: (StackM env m, HasEnvConfig env) => InstalledMap -> Map PackageName SimpleTarget -> Plan -> ExecuteEnv m -> m () executePlan' installedMap0 targets plan ee@ExecuteEnv {..} = do when (toCoverage $ boptsTestOpts eeBuildOpts) deleteHpcReports cv <- view actualCompilerVersionL let wc = view whichCompilerL cv case Map.toList $ planUnregisterLocal plan of [] -> return () ids -> do localDB <- packageDatabaseLocal forM_ ids $ \(id', (ident, reason)) -> do $logInfo $ T.concat [ T.pack $ packageIdentifierString ident , ": unregistering" , if T.null reason then "" else T.concat [ " (" , reason , ")" ] ] unregisterGhcPkgId eeEnvOverride wc cv localDB id' ident liftIO $ atomically $ modifyTVar' eeLocalDumpPkgs $ \initMap -> foldl' (flip Map.delete) initMap $ Map.keys (planUnregisterLocal plan) -- Yes, we're explicitly discarding result values, which in general would -- be bad. monad-unlift does this all properly at the type system level, -- but I don't want to pull it in for this one use case, when we know that -- stack always using transformer stacks that are safe for this use case. runInBase <- liftBaseWith $ \run -> return (void . run) let actions = concatMap (toActions installedMap' runInBase ee) $ Map.elems $ Map.mergeWithKey (\_ b f -> Just (Just b, Just f)) (fmap (\b -> (Just b, Nothing))) (fmap (\f -> (Nothing, Just f))) (planTasks plan) (planFinals plan) threads <- view $ configL.to configJobs concurrentTests <- view $ configL.to configConcurrentTests let keepGoing = fromMaybe (boptsTests eeBuildOpts || boptsBenchmarks eeBuildOpts) (boptsKeepGoing eeBuildOpts) concurrentFinal = -- TODO it probably makes more sense to use a lock for test suites -- and just have the execution blocked. Turning off all concurrency -- on finals based on the --test option doesn't fit in well. if boptsTests eeBuildOpts then concurrentTests else True terminal <- view terminalL errs <- liftIO $ runActions threads keepGoing concurrentFinal actions $ \doneVar -> do let total = length actions loop prev | prev == total = runInBase $ $logStickyDone ("Completed " <> T.pack (show total) <> " action(s).") | otherwise = do when terminal $ runInBase $ $logSticky ("Progress: " <> T.pack (show prev) <> "/" <> T.pack (show total)) done <- atomically $ do done <- readTVar doneVar check $ done /= prev return done loop done when (total > 1) $ loop 0 when (toCoverage $ boptsTestOpts eeBuildOpts) $ do generateHpcUnifiedReport generateHpcMarkupIndex unless (null errs) $ throwM $ ExecutionFailure errs when (boptsHaddock eeBuildOpts) $ do snapshotDumpPkgs <- liftIO (readTVarIO eeSnapshotDumpPkgs) localDumpPkgs <- liftIO (readTVarIO eeLocalDumpPkgs) generateLocalHaddockIndex eeEnvOverride wc eeBaseConfigOpts localDumpPkgs eeLocals generateDepsHaddockIndex eeEnvOverride wc eeBaseConfigOpts eeGlobalDumpPkgs snapshotDumpPkgs localDumpPkgs eeLocals generateSnapHaddockIndex eeEnvOverride wc eeBaseConfigOpts eeGlobalDumpPkgs snapshotDumpPkgs when (boptsOpenHaddocks eeBuildOpts) $ do let planPkgs, localPkgs, installedPkgs, availablePkgs :: Map PackageName (PackageIdentifier, InstallLocation) planPkgs = Map.map (taskProvides &&& taskLocation) (planTasks plan) localPkgs = Map.fromList [(packageName p, (packageIdentifier p, Local)) | p <- map lpPackage eeLocals] installedPkgs = Map.map (swap . second installedPackageIdentifier) installedMap' availablePkgs = Map.unions [planPkgs, localPkgs, installedPkgs] openHaddocksInBrowser eeBaseConfigOpts availablePkgs (Map.keysSet targets) where installedMap' = Map.difference installedMap0 $ Map.fromList $ map (\(ident, _) -> (packageIdentifierName ident, ())) $ Map.elems $ planUnregisterLocal plan toActions :: (StackM env m, HasEnvConfig env) => InstalledMap -> (m () -> IO ()) -> ExecuteEnv m -> (Maybe Task, Maybe Task) -- build and final -> [Action] toActions installedMap runInBase ee (mbuild, mfinal) = abuild ++ afinal where abuild = case mbuild of Nothing -> [] Just task@Task {..} -> [ Action { actionId = ActionId taskProvides ATBuild , actionDeps = Set.map (\ident -> ActionId ident ATBuild) (tcoMissing taskConfigOpts) , actionDo = \ac -> runInBase $ singleBuild runInBase ac ee task installedMap False } ] afinal = case mfinal of Nothing -> [] Just task@Task {..} -> (if taskAllInOne then [] else [Action { actionId = ActionId taskProvides ATBuildFinal , actionDeps = addBuild (Set.map (\ident -> ActionId ident ATBuild) (tcoMissing taskConfigOpts)) , actionDo = \ac -> runInBase $ singleBuild runInBase ac ee task installedMap True }]) ++ [ Action { actionId = ActionId taskProvides ATFinal , actionDeps = if taskAllInOne then addBuild mempty else Set.singleton (ActionId taskProvides ATBuildFinal) , actionDo = \ac -> runInBase $ do let comps = taskComponents task tests = testComponents comps benches = benchComponents comps unless (Set.null tests) $ do singleTest runInBase topts (Set.toList tests) ac ee task installedMap unless (Set.null benches) $ do singleBench runInBase beopts (Set.toList benches) ac ee task installedMap } ] where addBuild = case mbuild of Nothing -> id Just _ -> Set.insert $ ActionId taskProvides ATBuild bopts = eeBuildOpts ee topts = boptsTestOpts bopts beopts = boptsBenchmarkOpts bopts -- | Generate the ConfigCache getConfigCache :: (StackM env m, HasEnvConfig env) => ExecuteEnv m -> Task -> InstalledMap -> Bool -> Bool -> m (Map PackageIdentifier GhcPkgId, ConfigCache) getConfigCache ExecuteEnv {..} task@Task {..} installedMap enableTest enableBench = do useExactConf <- view $ configL.to configAllowNewer let extra = -- We enable tests if the test suite dependencies are already -- installed, so that we avoid unnecessary recompilation based on -- cabal_macros.h changes when switching between 'stack build' and -- 'stack test'. See: -- https://github.com/commercialhaskell/stack/issues/805 case taskType of TTLocal lp -> -- FIXME: make this work with exact-configuration. -- Not sure how to plumb the info atm. See -- https://github.com/commercialhaskell/stack/issues/2049 [ "--enable-tests" | enableTest || (not useExactConf && depsPresent installedMap (lpTestDeps lp))] ++ [ "--enable-benchmarks" | enableBench || (not useExactConf && depsPresent installedMap (lpBenchDeps lp))] _ -> [] idMap <- liftIO $ readTVarIO eeGhcPkgIds let getMissing ident = case Map.lookup ident idMap of Nothing -- Expect to instead find it in installedMap if it's -- an initialBuildSteps target. | boptsCLIInitialBuildSteps eeBuildOptsCLI && taskIsTarget task, Just (_, installed) <- Map.lookup (packageIdentifierName ident) installedMap -> installedToGhcPkgId ident installed Just installed -> installedToGhcPkgId ident installed _ -> error "singleBuild: invariant violated, missing package ID missing" installedToGhcPkgId ident (Library ident' x) = assert (ident == ident') $ Just (ident, x) installedToGhcPkgId _ (Executable _) = Nothing missing' = Map.fromList $ mapMaybe getMissing $ Set.toList missing TaskConfigOpts missing mkOpts = taskConfigOpts opts = mkOpts missing' allDeps = Set.fromList $ Map.elems missing' ++ Map.elems taskPresent cache = ConfigCache { configCacheOpts = opts { coNoDirs = coNoDirs opts ++ map T.unpack extra } , configCacheDeps = allDeps , configCacheComponents = case taskType of TTLocal lp -> Set.map renderComponent $ lpComponents lp TTUpstream{} -> Set.empty , configCacheHaddock = shouldHaddockPackage eeBuildOpts eeWanted (packageIdentifierName taskProvides) , configCachePkgSrc = taskCachePkgSrc } allDepsMap = Map.union missing' taskPresent return (allDepsMap, cache) -- | Ensure that the configuration for the package matches what is given ensureConfig :: (StackM env m, HasEnvConfig env) => ConfigCache -- ^ newConfigCache -> Path Abs Dir -- ^ package directory -> ExecuteEnv m -> m () -- ^ announce -> (ExcludeTHLoading -> [String] -> m ()) -- ^ cabal -> Path Abs File -- ^ .cabal file -> m Bool ensureConfig newConfigCache pkgDir ExecuteEnv {..} announce cabal cabalfp = do newCabalMod <- liftIO (fmap modTime (D.getModificationTime (toFilePath cabalfp))) needConfig <- if boptsReconfigure eeBuildOpts then return True else do -- We can ignore the components portion of the config -- cache, because it's just used to inform 'construct -- plan that we need to plan to build additional -- components. These components don't affect the actual -- package configuration. let ignoreComponents cc = cc { configCacheComponents = Set.empty } -- Determine the old and new configuration in the local directory, to -- determine if we need to reconfigure. mOldConfigCache <- tryGetConfigCache pkgDir mOldCabalMod <- tryGetCabalMod pkgDir return $ fmap ignoreComponents mOldConfigCache /= Just (ignoreComponents newConfigCache) || mOldCabalMod /= Just newCabalMod let ConfigureOpts dirs nodirs = configCacheOpts newConfigCache when needConfig $ withMVar eeConfigureLock $ \_ -> do deleteCaches pkgDir announce menv <- getMinimalEnvOverride let programNames = if eeCabalPkgVer < $(mkVersion "1.22") then ["ghc", "ghc-pkg"] else ["ghc", "ghc-pkg", "ghcjs", "ghcjs-pkg"] exes <- forM programNames $ \name -> do mpath <- findExecutable menv name return $ case mpath of Nothing -> [] Just x -> return $ concat ["--with-", name, "=", toFilePath x] -- Configure cabal with arguments determined by -- Stack.Types.Build.configureOpts cabal KeepTHLoading $ "configure" : concat [ concat exes , dirs , nodirs ] writeConfigCache pkgDir newConfigCache writeCabalMod pkgDir newCabalMod return needConfig announceTask :: MonadLogger m => Task -> Text -> m () announceTask task x = $logInfo $ T.concat [ T.pack $ packageIdentifierString $ taskProvides task , ": " , x ] -- | This sets up a context for executing build steps which need to run -- Cabal (via a compiled Setup.hs). In particular it does the following: -- -- * Ensures the package exists in the file system, downloading if necessary. -- -- * Opens a log file if the built output shouldn't go to stderr. -- -- * Ensures that either a simple Setup.hs is built, or the package's -- custom setup is built. -- -- * Provides the user a function with which run the Cabal process. withSingleContext :: forall env m a. (StackM env m, HasEnvConfig env) => (m () -> IO ()) -> ActionContext -> ExecuteEnv m -> Task -> Maybe (Map PackageIdentifier GhcPkgId) -- ^ All dependencies' package ids to provide to Setup.hs. If -- Nothing, just provide global and snapshot package -- databases. -> Maybe String -> ( Package -- Package info -> Path Abs File -- Cabal file path -> Path Abs Dir -- Package root directory file path -> (ExcludeTHLoading -> [String] -> m ()) -- Function to run Cabal with args -> (Text -> m ()) -- An 'announce' function, for different build phases -> Bool -- Whether output should be directed to the console -> Maybe (Path Abs File, Handle) -- Log file -> m a) -> m a withSingleContext runInBase ActionContext {..} ExecuteEnv {..} task@Task {..} mdeps msuffix inner0 = withPackage $ \package cabalfp pkgDir -> withLogFile pkgDir package $ \mlogFile -> withCabal package pkgDir mlogFile $ \cabal -> inner0 package cabalfp pkgDir cabal announce console mlogFile where announce = announceTask task wanted = case taskType of TTLocal lp -> lpWanted lp TTUpstream{} -> False console = wanted && all (\(ActionId ident _) -> ident == taskProvides) (Set.toList acRemaining) && eeTotalWanted == 1 withPackage inner = case taskType of TTLocal lp -> inner (lpPackage lp) (lpCabalFile lp) (lpDir lp) TTUpstream package _ gitSHA1 -> do mdist <- liftM Just distRelativeDir m <- unpackPackageIdents eeTempDir mdist $ Map.singleton taskProvides gitSHA1 case Map.toList m of [(ident, dir)] | ident == taskProvides -> do let name = packageIdentifierName taskProvides cabalfpRel <- parseRelFile $ packageNameString name ++ ".cabal" let cabalfp = dir cabalfpRel inner package cabalfp dir _ -> error $ "withPackage: invariant violated: " ++ show m withLogFile pkgDir package inner | console = inner Nothing | otherwise = do logPath <- buildLogPath package msuffix ensureDir (parent logPath) let fp = toFilePath logPath -- We only want to dump logs for local non-dependency packages case taskType of TTLocal lp | lpWanted lp -> liftIO $ atomically $ writeTChan eeLogFiles (pkgDir, logPath) _ -> return () bracket (liftIO $ openBinaryFile fp WriteMode) (liftIO . hClose) $ \h -> inner (Just (logPath, h)) withCabal :: Package -> Path Abs Dir -> Maybe (Path Abs File, Handle) -> ((ExcludeTHLoading -> [String] -> m ()) -> m a) -> m a withCabal package pkgDir mlogFile inner = do config <- view configL unless (configAllowDifferentUser config) $ checkOwnership (pkgDir configWorkDir config) let envSettings = EnvSettings { esIncludeLocals = taskLocation task == Local , esIncludeGhcPackagePath = False , esStackExe = False , esLocaleUtf8 = True } menv <- liftIO $ configEnvOverride config envSettings distRelativeDir' <- distRelativeDir esetupexehs <- -- Avoid broken Setup.hs files causing problems for simple build -- types, see: -- https://github.com/commercialhaskell/stack/issues/370 case (packageBuildType package, eeSetupExe) of (Just C.Simple, Just setupExe) -> return $ Left setupExe _ -> liftIO $ Right <$> getSetupHs pkgDir inner $ \stripTHLoading args -> do let cabalPackageArg -- Omit cabal package dependency when building -- Cabal. See -- https://github.com/commercialhaskell/stack/issues/1356 | packageName package == $(mkPackageName "Cabal") = [] | otherwise = ["-package=" ++ packageIdentifierString (PackageIdentifier cabalPackageName eeCabalPkgVer)] packageDBArgs = ( "-clear-package-db" : "-global-package-db" : map (("-package-db=" ++) . toFilePathNoTrailingSep) (bcoExtraDBs eeBaseConfigOpts) ) ++ ( ("-package-db=" ++ toFilePathNoTrailingSep (bcoSnapDB eeBaseConfigOpts)) : ("-package-db=" ++ toFilePathNoTrailingSep (bcoLocalDB eeBaseConfigOpts)) : ["-hide-all-packages"] ) warnCustomNoDeps :: m () warnCustomNoDeps = case (taskType, packageBuildType package) of (TTLocal{}, Just C.Custom) -> do $logWarn $ T.pack $ concat [ "Package " , packageNameString $ packageName package , " uses a custom Cabal build, but does not use a custom-setup stanza" ] $logWarn "Using the explicit setup deps approach based on configuration" $logWarn "Strongly recommend fixing the package's cabal file" _ -> return () getPackageArgs :: Path Abs Dir -> m [String] getPackageArgs setupDir = case (packageSetupDeps package, mdeps) of -- The package is using the Cabal custom-setup -- configuration introduced in Cabal 1.24. In -- this case, the package is providing an -- explicit list of dependencies, and we -- should simply use all of them. (Just customSetupDeps, _) -> do allDeps <- case mdeps of Just x -> return x Nothing -> do $logWarn "In getPackageArgs: custom-setup in use, but no dependency map present" return Map.empty matchedDeps <- forM (Map.toList customSetupDeps) $ \(name, range) -> do let matches (PackageIdentifier name' version) = name == name' && version `withinRange` range case filter (matches . fst) (Map.toList allDeps) of x:xs -> do unless (null xs) ($logWarn (T.pack ("Found multiple installed packages for custom-setup dep: " ++ packageNameString name))) return ("-package-id=" ++ ghcPkgIdString (snd x), Just (toCabalPackageIdentifier (fst x))) [] -> do $logWarn (T.pack ("Could not find custom-setup dep: " ++ packageNameString name)) return ("-package=" ++ packageNameString name, Nothing) let depsArgs = map fst matchedDeps -- Generate setup_macros.h and provide it to ghc let macroDeps = mapMaybe snd matchedDeps cppMacrosFile = toFilePath $ setupDir $(mkRelFile "setup_macros.h") cppArgs = ["-optP-include", "-optP" ++ cppMacrosFile] liftIO $ S.writeFile cppMacrosFile (encodeUtf8 (T.pack (C.generatePackageVersionMacros macroDeps))) return (packageDBArgs ++ depsArgs ++ cppArgs) -- This branch is taken when -- 'explicit-setup-deps' is requested in your -- stack.yaml file. (Nothing, Just deps) | explicitSetupDeps (packageName package) config -> do warnCustomNoDeps -- Stack always builds with the global Cabal for various -- reproducibility issues. let depsMinusCabal = map ghcPkgIdString $ Set.toList $ addGlobalPackages deps (Map.elems eeGlobalDumpPkgs) return ( packageDBArgs ++ cabalPackageArg ++ map ("-package-id=" ++) depsMinusCabal) -- This branch is usually taken for builds, and -- is always taken for `stack sdist`. -- -- This approach is debatable. It adds access to the -- snapshot package database for Cabal. There are two -- possible objections: -- -- 1. This doesn't isolate the build enough; arbitrary -- other packages available could cause the build to -- succeed or fail. -- -- 2. This doesn't provide enough packages: we should also -- include the local database when building local packages. -- -- Currently, this branch is only taken via `stack -- sdist` or when explicitly requested in the -- stack.yaml file. (Nothing, _) -> do warnCustomNoDeps return $ cabalPackageArg ++ -- NOTE: This is different from -- packageDBArgs above in that it does not -- include the local database and does not -- pass in the -hide-all-packages argument ("-clear-package-db" : "-global-package-db" : map (("-package-db=" ++) . toFilePathNoTrailingSep) (bcoExtraDBs eeBaseConfigOpts) ++ ["-package-db=" ++ toFilePathNoTrailingSep (bcoSnapDB eeBaseConfigOpts)]) setupArgs = ("--builddir=" ++ toFilePathNoTrailingSep distRelativeDir') : args runExe :: Path Abs File -> [String] -> m () runExe exeName fullArgs = do compilerVer <- view actualCompilerVersionL runAndOutput compilerVer `catch` \(ProcessExitedUnsuccessfully _ ec) -> do bss <- case mlogFile of Nothing -> return [] Just (logFile, h) -> do liftIO $ hClose h runResourceT $ CB.sourceFile (toFilePath logFile) =$= CT.decodeUtf8Lenient $$ mungeBuildOutput stripTHLoading makeAbsolute pkgDir compilerVer =$ CL.consume throwM $ CabalExitedUnsuccessfully ec taskProvides exeName fullArgs (fmap fst mlogFile) bss where runAndOutput :: CompilerVersion -> m () runAndOutput compilerVer = case mlogFile of Just (_, h) -> sinkProcessStderrStdoutHandle (Just pkgDir) menv (toFilePath exeName) fullArgs h h Nothing -> void $ sinkProcessStderrStdout (Just pkgDir) menv (toFilePath exeName) fullArgs (outputSink KeepTHLoading LevelWarn compilerVer) (outputSink stripTHLoading LevelInfo compilerVer) outputSink :: ExcludeTHLoading -> LogLevel -> CompilerVersion -> Sink S.ByteString IO () outputSink excludeTH level compilerVer = CT.decodeUtf8Lenient =$ mungeBuildOutput excludeTH makeAbsolute pkgDir compilerVer =$ CL.mapM_ (runInBase . monadLoggerLog $(TH.location >>= liftLoc) "" level) -- If users want control, we should add a config option for this makeAbsolute :: ConvertPathsToAbsolute makeAbsolute = case stripTHLoading of ExcludeTHLoading -> ConvertPathsToAbsolute KeepTHLoading -> KeepPathsAsIs wc <- view $ actualCompilerVersionL.whichCompilerL exeName <- case (esetupexehs, wc) of (Left setupExe, _) -> return setupExe (Right setuphs, compiler) -> do distDir <- distDirFromDir pkgDir let setupDir = distDir $(mkRelDir "setup") outputFile = setupDir $(mkRelFile "setup") customBuilt <- liftIO $ readIORef eeCustomBuilt if Set.member (packageName package) customBuilt then return outputFile else do ensureDir setupDir compilerPath <- case compiler of Ghc -> eeGetGhcPath Ghcjs -> eeGetGhcjsPath packageArgs <- getPackageArgs setupDir runExe compilerPath $ [ "--make" , "-odir", toFilePathNoTrailingSep setupDir , "-hidir", toFilePathNoTrailingSep setupDir , "-i", "-i." ] ++ packageArgs ++ [ toFilePath setuphs , toFilePath eeSetupShimHs , "-main-is" , "StackSetupShim.mainOverride" , "-o", toFilePath outputFile , "-threaded" ] ++ (case compiler of Ghc -> [] Ghcjs -> ["-build-runner"]) liftIO $ atomicModifyIORef' eeCustomBuilt $ \oldCustomBuilt -> (Set.insert (packageName package) oldCustomBuilt, ()) return outputFile runExe exeName $ (if boptsCabalVerbose eeBuildOpts then ("--verbose":) else id) setupArgs -- Implements running a package's build, used to implement 'ATBuild' and -- 'ATBuildFinal' tasks. In particular this does the following: -- -- * Checks if the package exists in the precompiled cache, and if so, -- add it to the database instead of performing the build. -- -- * Runs the configure step if needed ('ensureConfig') -- -- * Runs the build step -- -- * Generates haddocks -- -- * Registers the library and copiesthe built executables into the -- local install directory. Note that this is literally invoking Cabal -- with @copy@, and not the copying done by @stack install@ - that is -- handled by 'copyExecutables'. singleBuild :: forall env m. (StackM env m, HasEnvConfig env) => (m () -> IO ()) -> ActionContext -> ExecuteEnv m -> Task -> InstalledMap -> Bool -- ^ Is this a final build? -> m () singleBuild runInBase ac@ActionContext {..} ee@ExecuteEnv {..} task@Task {..} installedMap isFinalBuild = do (allDepsMap, cache) <- getConfigCache ee task installedMap enableTests enableBenchmarks mprecompiled <- getPrecompiled cache minstalled <- case mprecompiled of Just precompiled -> copyPreCompiled precompiled Nothing -> realConfigAndBuild cache allDepsMap case minstalled of Nothing -> return () Just installed -> do writeFlagCache installed cache liftIO $ atomically $ modifyTVar eeGhcPkgIds $ Map.insert taskProvides installed where pname = packageIdentifierName taskProvides shouldHaddockPackage' = shouldHaddockPackage eeBuildOpts eeWanted pname doHaddock package = shouldHaddockPackage' && not isFinalBuild && -- Works around haddock failing on bytestring-builder since it has no modules -- when bytestring is new enough. packageHasExposedModules package buildingFinals = isFinalBuild || taskAllInOne enableTests = buildingFinals && any isCTest (taskComponents task) enableBenchmarks = buildingFinals && any isCBench (taskComponents task) annSuffix = if result == "" then "" else " (" <> result <> ")" where result = T.intercalate " + " $ concat [ ["lib" | taskAllInOne && hasLib] , ["exe" | taskAllInOne && hasExe] , ["test" | enableTests] , ["bench" | enableBenchmarks] ] (hasLib, hasExe) = case taskType of TTLocal lp -> (packageHasLibrary (lpPackage lp), not (Set.null (exesToBuild lp))) -- This isn't true, but we don't want to have this info for -- upstream deps. TTUpstream{} -> (False, False) getPrecompiled cache = case taskLocation task of Snap | not shouldHaddockPackage' -> do mpc <- readPrecompiledCache taskProvides (configCacheOpts cache) (configCacheDeps cache) case mpc of Nothing -> return Nothing -- Only pay attention to precompiled caches that refer to packages within -- the snapshot. Just pc | maybe False (bcoSnapInstallRoot eeBaseConfigOpts `isParentOf`) (parseAbsFile =<< pcLibrary pc) -> return Nothing -- If old precompiled cache files are left around but snapshots are deleted, -- it is possible for the precompiled file to refer to the very library -- we're building, and if flags are changed it may try to copy the library -- to itself. This check prevents that from happening. Just pc -> do let allM _ [] = return True allM f (x:xs) = do b <- f x if b then allM f xs else return False b <- liftIO $ allM D.doesFileExist $ maybe id (:) (pcLibrary pc) $ pcExes pc return $ if b then Just pc else Nothing _ -> return Nothing copyPreCompiled (PrecompiledCache mlib exes) = do wc <- view $ actualCompilerVersionL.whichCompilerL announceTask task "using precompiled package" forM_ mlib $ \libpath -> do menv <- getMinimalEnvOverride withMVar eeInstallLock $ \() -> do -- We want to ignore the global and user databases. -- Unfortunately, ghc-pkg doesn't take such arguments on the -- command line. Instead, we'll set GHC_PACKAGE_PATH. See: -- https://github.com/commercialhaskell/stack/issues/1146 menv' <- modifyEnvOverride menv $ Map.insert "GHC_PACKAGE_PATH" (T.pack $ toFilePathNoTrailingSep $ bcoSnapDB eeBaseConfigOpts) -- In case a build of the library with different flags already exists, unregister it -- before copying. let ghcPkgExe = ghcPkgExeName wc catch (readProcessNull Nothing menv' ghcPkgExe [ "unregister" , "--force" , packageIdentifierString taskProvides ]) (\ex -> case ex of ProcessFailed{} -> return () _ -> throwM ex) readProcessNull Nothing menv' ghcPkgExe [ "register" , "--force" , libpath ] liftIO $ forM_ exes $ \exe -> do D.createDirectoryIfMissing True bindir let dst = bindir FP. FP.takeFileName exe createLink exe dst `catchIO` \_ -> D.copyFile exe dst case (mlib, exes) of (Nothing, _:_) -> markExeInstalled (taskLocation task) taskProvides _ -> return () -- Find the package in the database let pkgDbs = [bcoSnapDB eeBaseConfigOpts] case mlib of Nothing -> return $ Just $ Executable taskProvides Just _ -> do mpkgid <- loadInstalledPkg eeEnvOverride wc pkgDbs eeSnapshotDumpPkgs pname return $ Just $ case mpkgid of Nothing -> assert False $ Executable taskProvides Just pkgid -> Library taskProvides pkgid where bindir = toFilePath $ bcoSnapInstallRoot eeBaseConfigOpts bindirSuffix realConfigAndBuild cache allDepsMap = withSingleContext runInBase ac ee task (Just allDepsMap) Nothing $ \package cabalfp pkgDir cabal announce _console _mlogFile -> do _neededConfig <- ensureConfig cache pkgDir ee (announce ("configure" <> annSuffix)) cabal cabalfp let installedMapHasThisPkg :: Bool installedMapHasThisPkg = case Map.lookup (packageName package) installedMap of Just (_, Library ident _) -> ident == taskProvides Just (_, Executable _) -> True _ -> False case ( boptsCLIOnlyConfigure eeBuildOptsCLI , boptsCLIInitialBuildSteps eeBuildOptsCLI && taskIsTarget task) of -- A full build is done if there are downstream actions, -- because their configure step will require that this -- package is built. See -- https://github.com/commercialhaskell/stack/issues/2787 (True, _) | null acDownstream -> return Nothing (_, True) | null acDownstream || installedMapHasThisPkg -> do initialBuildSteps cabal announce return Nothing _ -> liftM Just $ realBuild cache package pkgDir cabal announce initialBuildSteps cabal announce = do () <- announce ("initial-build-steps" <> annSuffix) cabal KeepTHLoading ["repl", "stack-initial-build-steps"] realBuild :: ConfigCache -> Package -> Path Abs Dir -> (ExcludeTHLoading -> [String] -> m ()) -> (Text -> m ()) -> m Installed realBuild cache package pkgDir cabal announce = do wc <- view $ actualCompilerVersionL.whichCompilerL markExeNotInstalled (taskLocation task) taskProvides case taskType of TTLocal lp -> do when enableTests $ unsetTestSuccess pkgDir writeBuildCache pkgDir $ lpNewBuildCache lp TTUpstream{} -> return () -- FIXME: only output these if they're in the build plan. preBuildTime <- modTime <$> liftIO getCurrentTime let postBuildCheck _succeeded = do mlocalWarnings <- case taskType of TTLocal lp -> do warnings <- checkForUnlistedFiles taskType preBuildTime pkgDir return (Just (lpCabalFile lp, warnings)) _ -> return Nothing -- NOTE: once -- https://github.com/commercialhaskell/stack/issues/2649 -- is resolved, we will want to partition the warnings -- based on variety, and output in different lists. let showModuleWarning (UnlistedModulesWarning mcomp modules) = "- In" <+> maybe "the library component" (\c -> fromString c <+> "component") mcomp <> ":" <> line <> indent 4 (mconcat $ intersperse line $ map (goodGreen . fromString . C.display) modules) forM_ mlocalWarnings $ \(cabalfp, warnings) -> do unless (null warnings) $ $prettyWarn $ "The following modules should be added to exposed-modules or other-modules in" <+> display cabalfp <> ":" <> line <> indent 4 (mconcat $ map showModuleWarning warnings) <> line <> line <> "Missing modules in the cabal file are likely to cause undefined reference errors from the linker, along with other problems." () <- announce ("build" <> annSuffix) config <- view configL extraOpts <- extraBuildOptions wc eeBuildOpts let stripTHLoading | configHideTHLoading config = ExcludeTHLoading | otherwise = KeepTHLoading cabal stripTHLoading (("build" :) $ (++ extraOpts) $ case (taskType, taskAllInOne, isFinalBuild) of (_, True, True) -> error "Invariant violated: cannot have an all-in-one build that also has a final build step." (TTLocal lp, False, False) -> primaryComponentOptions lp (TTLocal lp, False, True) -> finalComponentOptions lp (TTLocal lp, True, False) -> primaryComponentOptions lp ++ finalComponentOptions lp (TTUpstream{}, _, _) -> []) `catch` \ex -> case ex of CabalExitedUnsuccessfully{} -> postBuildCheck False >> throwM ex _ -> throwM ex postBuildCheck True when (doHaddock package) $ do announce "haddock" sourceFlag <- if not (boptsHaddockHyperlinkSource eeBuildOpts) then return [] else do -- See #2429 for why the temp dir is used hyped <- tryProcessStdout (Just eeTempDir) eeEnvOverride "haddock" ["--hyperlinked-source"] case hyped of -- Fancy crosslinked source Right _ -> do return ["--haddock-option=--hyperlinked-source"] -- Older hscolour colouring Left _ -> do hscolourExists <- doesExecutableExist eeEnvOverride "HsColour" unless hscolourExists $ $logWarn ("Warning: haddock not generating hyperlinked sources because 'HsColour' not\n" <> "found on PATH (use 'stack install hscolour' to install).") return ["--hyperlink-source" | hscolourExists] cabal KeepTHLoading $ concat [ ["haddock", "--html", "--html-location=../$pkg-$version/"] , sourceFlag , ["--internal" | boptsHaddockInternal eeBuildOpts] , [ "--haddock-option=" <> opt | opt <- hoAdditionalArgs (boptsHaddockOpts eeBuildOpts) ] ] let shouldCopy = not isFinalBuild && (packageHasLibrary package || not (Set.null (packageExes package))) when shouldCopy $ withMVar eeInstallLock $ \() -> do announce "copy/register" eres <- try $ cabal KeepTHLoading ["copy"] case eres of Left err@CabalExitedUnsuccessfully{} -> throwM $ CabalCopyFailed (packageBuildType package == Just C.Simple) (show err) _ -> return () when (packageHasLibrary package) $ cabal KeepTHLoading ["register"] let (installedPkgDb, installedDumpPkgsTVar) = case taskLocation task of Snap -> ( bcoSnapDB eeBaseConfigOpts , eeSnapshotDumpPkgs ) Local -> ( bcoLocalDB eeBaseConfigOpts , eeLocalDumpPkgs ) let ident = PackageIdentifier (packageName package) (packageVersion package) mpkgid <- if packageHasLibrary package then do mpkgid <- loadInstalledPkg eeEnvOverride wc [installedPkgDb] installedDumpPkgsTVar (packageName package) case mpkgid of Nothing -> throwM $ Couldn'tFindPkgId $ packageName package Just pkgid -> return $ Library ident pkgid else do markExeInstalled (taskLocation task) taskProvides -- TODO unify somehow with writeFlagCache? return $ Executable ident case taskLocation task of Snap -> writePrecompiledCache eeBaseConfigOpts taskProvides (configCacheOpts cache) (configCacheDeps cache) mpkgid (packageExes package) Local -> return () case taskType of -- For upstream packages, pkgDir is in the tmp directory. We -- eagerly delete it if no other tasks require it, to reduce -- space usage in tmp (#3018). TTUpstream{} -> do let remaining = filter (\(ActionId x _) -> x == taskProvides) (Set.toList acRemaining) when (null remaining) $ removeDirRecur pkgDir _ -> return () return mpkgid loadInstalledPkg menv wc pkgDbs tvar name = do dps <- ghcPkgDescribe name menv wc pkgDbs $ conduitDumpPackage =$ CL.consume case dps of [] -> return Nothing [dp] -> do liftIO $ atomically $ modifyTVar' tvar (Map.insert (dpGhcPkgId dp) dp) return $ Just (dpGhcPkgId dp) _ -> error "singleBuild: invariant violated: multiple results when describing installed package" -- | Check if any unlisted files have been found, and add them to the build cache. checkForUnlistedFiles :: (StackM env m, HasEnvConfig env) => TaskType -> ModTime -> Path Abs Dir -> m [PackageWarning] checkForUnlistedFiles (TTLocal lp) preBuildTime pkgDir = do (addBuildCache,warnings) <- addUnlistedToBuildCache preBuildTime (lpPackage lp) (lpCabalFile lp) (lpNewBuildCache lp) unless (null addBuildCache) $ writeBuildCache pkgDir $ Map.unions (lpNewBuildCache lp : addBuildCache) return warnings checkForUnlistedFiles TTUpstream{} _ _ = return [] -- | Determine if all of the dependencies given are installed depsPresent :: InstalledMap -> Map PackageName VersionRange -> Bool depsPresent installedMap deps = all (\(name, range) -> case Map.lookup name installedMap of Just (_, installed) -> installedVersion installed `withinRange` range Nothing -> False) (Map.toList deps) -- | Implements running a package's tests. Also handles producing -- coverage reports if coverage is enabled. singleTest :: (StackM env m, HasEnvConfig env) => (m () -> IO ()) -> TestOpts -> [Text] -> ActionContext -> ExecuteEnv m -> Task -> InstalledMap -> m () singleTest runInBase topts testsToRun ac ee task installedMap = do -- FIXME: Since this doesn't use cabal, we should be able to avoid using a -- fullblown 'withSingleContext'. (allDepsMap, _cache) <- getConfigCache ee task installedMap True False withSingleContext runInBase ac ee task (Just allDepsMap) (Just "test") $ \package _cabalfp pkgDir _cabal announce _console mlogFile -> do config <- view configL let needHpc = toCoverage topts toRun <- if toDisableRun topts then do announce "Test running disabled by --no-run-tests flag." return False else if toRerunTests topts then return True else do success <- checkTestSuccess pkgDir if success then do unless (null testsToRun) $ announce "skipping already passed test" return False else return True when toRun $ do buildDir <- distDirFromDir pkgDir hpcDir <- hpcDirFromDir pkgDir when needHpc (ensureDir hpcDir) let suitesToRun = [ testSuitePair | testSuitePair <- Map.toList $ packageTests package , let testName = fst testSuitePair , testName `elem` testsToRun ] errs <- liftM Map.unions $ forM suitesToRun $ \(testName, suiteInterface) -> do let stestName = T.unpack testName (testName', isTestTypeLib) <- case suiteInterface of C.TestSuiteLibV09{} -> return (stestName ++ "Stub", True) C.TestSuiteExeV10{} -> return (stestName, False) interface -> throwM (TestSuiteTypeUnsupported interface) let exeName = testName' ++ case configPlatform config of Platform _ Windows -> ".exe" _ -> "" tixPath <- liftM (pkgDir ) $ parseRelFile $ exeName ++ ".tix" exePath <- liftM (buildDir ) $ parseRelFile $ "build/" ++ testName' ++ "/" ++ exeName exists <- doesFileExist exePath menv <- liftIO $ configEnvOverride config EnvSettings { esIncludeLocals = taskLocation task == Local , esIncludeGhcPackagePath = True , esStackExe = True , esLocaleUtf8 = False } if exists then do -- We clear out the .tix files before doing a run. when needHpc $ do tixexists <- doesFileExist tixPath when tixexists $ $logWarn ("Removing HPC file " <> T.pack (toFilePath tixPath)) ignoringAbsence (removeFile tixPath) let args = toAdditionalArgs topts argsDisplay = case args of [] -> "" _ -> ", args: " <> T.intercalate " " (map showProcessArgDebug args) announce $ "test (suite: " <> testName <> argsDisplay <> ")" -- Clear "Progress: ..." message before -- redirecting output. when (isNothing mlogFile) $ do $logStickyDone "" liftIO $ hFlush stdout liftIO $ hFlush stderr let output = case mlogFile of Nothing -> Inherit Just (_, h) -> UseHandle h -- Use createProcess_ to avoid the log file being closed afterwards (Just inH, Nothing, Nothing, ph) <- createProcess' stestName (\cp -> cp { std_in = CreatePipe, std_out = output, std_err = output }) (Cmd (Just pkgDir) (toFilePath exePath) menv args) when isTestTypeLib $ do logPath <- buildLogPath package (Just stestName) ensureDir (parent logPath) liftIO $ hPutStr inH $ show (logPath, testName) liftIO $ hClose inH ec <- liftIO $ waitForProcess ph -- Add a trailing newline, incase the test -- output didn't finish with a newline. when (isNothing mlogFile) ($logInfo "") -- Move the .tix file out of the package -- directory into the hpc work dir, for -- tidiness. when needHpc $ updateTixFile (packageName package) tixPath testName' return $ case ec of ExitSuccess -> Map.empty _ -> Map.singleton testName $ Just ec else do $logError $ T.pack $ show $ TestSuiteExeMissing (packageBuildType package == Just C.Simple) exeName (packageNameString (packageName package)) (T.unpack testName) return $ Map.singleton testName Nothing when needHpc $ do let testsToRun' = map f testsToRun f tName = case Map.lookup tName (packageTests package) of Just C.TestSuiteLibV09{} -> tName <> "Stub" _ -> tName generateHpcReport pkgDir package testsToRun' bs <- liftIO $ case mlogFile of Nothing -> return "" Just (logFile, h) -> do hClose h S.readFile $ toFilePath logFile unless (Map.null errs) $ throwM $ TestSuiteFailure (taskProvides task) errs (fmap fst mlogFile) bs -- | Implements running a package's benchmarks. singleBench :: (StackM env m, HasEnvConfig env) => (m () -> IO ()) -> BenchmarkOpts -> [Text] -> ActionContext -> ExecuteEnv m -> Task -> InstalledMap -> m () singleBench runInBase beopts benchesToRun ac ee task installedMap = do (allDepsMap, _cache) <- getConfigCache ee task installedMap False True withSingleContext runInBase ac ee task (Just allDepsMap) (Just "bench") $ \_package _cabalfp _pkgDir cabal announce _console _mlogFile -> do let args = map T.unpack benchesToRun <> maybe [] ((:[]) . ("--benchmark-options=" <>)) (beoAdditionalArgs beopts) toRun <- if beoDisableRun beopts then do announce "Benchmark running disabled by --no-run-benchmarks flag." return False else do return True when toRun $ do announce "benchmarks" cabal KeepTHLoading ("bench" : args) data ExcludeTHLoading = ExcludeTHLoading | KeepTHLoading data ConvertPathsToAbsolute = ConvertPathsToAbsolute | KeepPathsAsIs -- | Strip Template Haskell "Loading package" lines and making paths absolute. mungeBuildOutput :: forall m. (MonadIO m, MonadCatch m, MonadBaseControl IO m) => ExcludeTHLoading -- ^ exclude TH loading? -> ConvertPathsToAbsolute -- ^ convert paths to absolute? -> Path Abs Dir -- ^ package's root directory -> CompilerVersion -- ^ compiler we're building with -> ConduitM Text Text m () mungeBuildOutput excludeTHLoading makeAbsolute pkgDir compilerVer = void $ CT.lines =$ CL.map stripCR =$ CL.filter (not . isTHLoading) =$ filterLinkerWarnings =$ toAbsolute where -- | Is this line a Template Haskell "Loading package" line -- ByteString isTHLoading :: Text -> Bool isTHLoading = case excludeTHLoading of KeepTHLoading -> const False ExcludeTHLoading -> \bs -> "Loading package " `T.isPrefixOf` bs && ("done." `T.isSuffixOf` bs || "done.\r" `T.isSuffixOf` bs) filterLinkerWarnings :: ConduitM Text Text m () filterLinkerWarnings -- Check for ghc 7.8 since it's the only one prone to producing -- linker warnings on Windows x64 | getGhcVersion compilerVer >= $(mkVersion "7.8") = doNothing | otherwise = CL.filter (not . isLinkerWarning) isLinkerWarning :: Text -> Bool isLinkerWarning str = ("ghc.exe: warning:" `T.isPrefixOf` str || "ghc.EXE: warning:" `T.isPrefixOf` str) && "is linked instead of __imp_" `T.isInfixOf` str -- | Convert GHC error lines with file paths to have absolute file paths toAbsolute :: ConduitM Text Text m () toAbsolute = case makeAbsolute of KeepPathsAsIs -> doNothing ConvertPathsToAbsolute -> CL.mapM toAbsolutePath toAbsolutePath :: Text -> m Text toAbsolutePath bs = do let (x, y) = T.break (== ':') bs mabs <- if isValidSuffix y then liftM (fmap ((T.takeWhile isSpace x <>) . T.pack . toFilePath)) $ forgivingAbsence (resolveFile pkgDir (T.unpack $ T.dropWhile isSpace x)) `catch` \(_ :: PathParseException) -> return Nothing else return Nothing case mabs of Nothing -> return bs Just fp -> return $ fp `T.append` y doNothing :: ConduitM Text Text m () doNothing = awaitForever yield -- | Match the error location format at the end of lines isValidSuffix = isRight . parseOnly lineCol lineCol = char ':' >> choice [ num >> char ':' >> num >> optional (char '-' >> num) >> return () , char '(' >> num >> char ',' >> num >> string ")-(" >> num >> char ',' >> num >> char ')' >> return () ] >> char ':' >> return () where num = some digit -- | Find the Setup.hs or Setup.lhs in the given directory. If none exists, -- throw an exception. getSetupHs :: Path Abs Dir -- ^ project directory -> IO (Path Abs File) getSetupHs dir = do exists1 <- doesFileExist fp1 if exists1 then return fp1 else do exists2 <- doesFileExist fp2 if exists2 then return fp2 else throwM $ NoSetupHsFound dir where fp1 = dir $(mkRelFile "Setup.hs") fp2 = dir $(mkRelFile "Setup.lhs") -- Do not pass `-hpcdir` as GHC option if the coverage is not enabled. -- This helps running stack-compiled programs with dynamic interpreters like `hint`. -- Cfr: https://github.com/commercialhaskell/stack/issues/997 extraBuildOptions :: (StackM env m, HasEnvConfig env) => WhichCompiler -> BuildOpts -> m [String] extraBuildOptions wc bopts = do let ddumpOpts = " -ddump-hi -ddump-to-file" optsFlag = compilerOptionsCabalFlag wc if toCoverage (boptsTestOpts bopts) then do hpcIndexDir <- toFilePathNoTrailingSep <$> hpcRelativeDir return [optsFlag, "-hpcdir " ++ hpcIndexDir ++ ddumpOpts] else return [optsFlag, ddumpOpts] -- Library and executable build components. primaryComponentOptions :: LocalPackage -> [String] primaryComponentOptions lp = ["lib:" ++ packageNameString (packageName (lpPackage lp)) -- TODO: get this information from target parsing instead, -- which will allow users to turn off library building if -- desired | packageHasLibrary (lpPackage lp)] ++ map (T.unpack . T.append "exe:") (Set.toList $ exesToBuild lp) exesToBuild :: LocalPackage -> Set Text exesToBuild lp = packageExes (lpPackage lp) -- NOTE: Ideally we'd do something like the following code, allowing -- the user to control which executables get built. However, due to -- https://github.com/haskell/cabal/issues/2780 we must build all -- exes... -- -- if lpWanted lp -- then exeComponents (lpComponents lp) -- -- Build all executables in the event that no -- -- specific list is provided (as happens with -- -- extra-deps). -- else packageExes (lpPackage lp) -- Test-suite and benchmark build components. finalComponentOptions :: LocalPackage -> [String] finalComponentOptions lp = map (T.unpack . decodeUtf8 . renderComponent) $ Set.toList $ Set.filter (\c -> isCTest c || isCBench c) (lpComponents lp) taskComponents :: Task -> Set NamedComponent taskComponents task = case taskType task of TTLocal lp -> lpComponents lp TTUpstream{} -> Set.empty -- | Take the given list of package dependencies and the contents of the global -- package database, and construct a set of installed package IDs that: -- -- * Excludes the Cabal library (it's added later) -- -- * Includes all packages depended on by this package -- -- * Includes all global packages, unless: (1) it's hidden, (2) it's shadowed -- by a depended-on package, or (3) one of its dependencies is not met. -- -- See: -- -- * https://github.com/commercialhaskell/stack/issues/941 -- -- * https://github.com/commercialhaskell/stack/issues/944 -- -- * https://github.com/commercialhaskell/stack/issues/949 addGlobalPackages :: Map PackageIdentifier GhcPkgId -- ^ dependencies of the package -> [DumpPackage () () ()] -- ^ global packages -> Set GhcPkgId addGlobalPackages deps globals0 = res where -- Initial set of packages: the installed IDs of all dependencies res0 = Map.elems $ Map.filterWithKey (\ident _ -> not $ isCabal ident) deps -- First check on globals: it's not shadowed by a dep, it's not Cabal, and -- it's exposed goodGlobal1 dp = not (isDep dp) && not (isCabal $ dpPackageIdent dp) && dpIsExposed dp globals1 = filter goodGlobal1 globals0 -- Create a Map of unique package names in the global database globals2 = Map.fromListWith chooseBest $ map (packageIdentifierName . dpPackageIdent &&& id) globals1 -- Final result: add in globals that have their dependencies met res = loop id (Map.elems globals2) $ Set.fromList res0 ---------------------------------- -- Some auxiliary helper functions ---------------------------------- -- Is the given package identifier for any version of Cabal isCabal (PackageIdentifier name _) = name == $(mkPackageName "Cabal") -- Is the given package name provided by the package dependencies? isDep dp = packageIdentifierName (dpPackageIdent dp) `Set.member` depNames depNames = Set.map packageIdentifierName $ Map.keysSet deps -- Choose the best of two competing global packages (the newest version) chooseBest dp1 dp2 | getVer dp1 < getVer dp2 = dp2 | otherwise = dp1 where getVer = packageIdentifierVersion . dpPackageIdent -- Are all dependencies of the given package met by the given Set of -- installed packages depsMet dp gids = all (`Set.member` gids) (dpDepends dp) -- Find all globals that have all of their dependencies met loop front (dp:dps) gids -- This package has its deps met. Add it to the list of dependencies -- and then traverse the list from the beginning (this package may have -- been a dependency of an earlier one). | depsMet dp gids = loop id (front dps) (Set.insert (dpGhcPkgId dp) gids) -- Deps are not met, keep going | otherwise = loop (front . (dp:)) dps gids -- None of the packages we checked can be added, therefore drop them all -- and return our results loop _ [] gids = gids stack-1.5.1/src/Stack/Build/Haddock.hs0000644000000000000000000003305713135652051015606 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} -- | Generate haddocks module Stack.Build.Haddock ( generateLocalHaddockIndex , generateDepsHaddockIndex , generateSnapHaddockIndex , openHaddocksInBrowser , shouldHaddockPackage , shouldHaddockDeps ) where import Control.Exception (tryJust, onException) import Control.Monad import Control.Monad.Catch (MonadCatch) import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.Trans.Resource import qualified Data.Foldable as F import Data.Function import qualified Data.HashSet as HS import Data.List import Data.List.Extra (nubOrd) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Maybe import Data.Maybe.Extra (mapMaybeM) import Data.Monoid ((<>)) import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as T import Data.Time (UTCTime) import Path import Path.Extra import Path.IO import Prelude import Stack.PackageDump import Stack.PrettyPrint import Stack.Types.Build import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.GhcPkgId import Stack.Types.Package import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.StackT (StackM) import Stack.Types.StringError import qualified System.FilePath as FP import System.IO.Error (isDoesNotExistError) import System.Process.Read import Web.Browser (openBrowser) openHaddocksInBrowser :: StackM env m => BaseConfigOpts -> Map PackageName (PackageIdentifier, InstallLocation) -- ^ Available packages and their locations for the current project -> Set PackageName -- ^ Build targets as determined by 'Stack.Build.Source.loadSourceMap' -> m () openHaddocksInBrowser bco pkgLocations buildTargets = do let cliTargets = (boptsCLITargets . bcoBuildOptsCLI) bco getDocIndex = do let localDocs = haddockIndexFile (localDepsDocDir bco) localExists <- doesFileExist localDocs if localExists then return localDocs else do let snapDocs = haddockIndexFile (snapDocDir bco) snapExists <- doesFileExist snapDocs if snapExists then return snapDocs else throwString "No local or snapshot doc index found to open." docFile <- case (cliTargets, map (`Map.lookup` pkgLocations) (Set.toList buildTargets)) of ([_], [Just (pkgId, iloc)]) -> do pkgRelDir <- (parseRelDir . packageIdentifierString) pkgId let docLocation = case iloc of Snap -> snapDocDir bco Local -> localDocDir bco let docFile = haddockIndexFile (docLocation pkgRelDir) exists <- doesFileExist docFile if exists then return docFile else do $logWarn $ "Expected to find documentation at " <> T.pack (toFilePath docFile) <> ", but that file is missing. Opening doc index instead." getDocIndex _ -> getDocIndex $prettyInfo $ "Opening" <+> display docFile <+> "in the browser." _ <- liftIO $ openBrowser (toFilePath docFile) return () -- | Determine whether we should haddock for a package. shouldHaddockPackage :: BuildOpts -> Set PackageName -- ^ Packages that we want to generate haddocks for -- in any case (whether or not we are going to generate -- haddocks for dependencies) -> PackageName -> Bool shouldHaddockPackage bopts wanted name = if Set.member name wanted then boptsHaddock bopts else shouldHaddockDeps bopts -- | Determine whether to build haddocks for dependencies. shouldHaddockDeps :: BuildOpts -> Bool shouldHaddockDeps bopts = fromMaybe (boptsHaddock bopts) (boptsHaddockDeps bopts) -- | Generate Haddock index and contents for local packages. generateLocalHaddockIndex :: (MonadIO m, MonadCatch m, MonadLogger m, MonadBaseControl IO m) => EnvOverride -> WhichCompiler -> BaseConfigOpts -> Map GhcPkgId (DumpPackage () () ()) -- ^ Local package dump -> [LocalPackage] -> m () generateLocalHaddockIndex envOverride wc bco localDumpPkgs locals = do let dumpPackages = mapMaybe (\LocalPackage{lpPackage = Package{..}} -> F.find (\dp -> dpPackageIdent dp == PackageIdentifier packageName packageVersion) localDumpPkgs) locals generateHaddockIndex "local packages" envOverride wc (boptsHaddockOpts (bcoBuildOpts bco)) dumpPackages "." (localDocDir bco) -- | Generate Haddock index and contents for local packages and their dependencies. generateDepsHaddockIndex :: (MonadIO m, MonadCatch m, MonadLogger m, MonadBaseControl IO m) => EnvOverride -> WhichCompiler -> BaseConfigOpts -> Map GhcPkgId (DumpPackage () () ()) -- ^ Global dump information -> Map GhcPkgId (DumpPackage () () ()) -- ^ Snapshot dump information -> Map GhcPkgId (DumpPackage () () ()) -- ^ Local dump information -> [LocalPackage] -> m () generateDepsHaddockIndex envOverride wc bco globalDumpPkgs snapshotDumpPkgs localDumpPkgs locals = do let deps = (mapMaybe (`lookupDumpPackage` allDumpPkgs) . nubOrd . findTransitiveDepends . mapMaybe getGhcPkgId) locals depDocDir = localDepsDocDir bco generateHaddockIndex "local packages and dependencies" envOverride wc (boptsHaddockOpts (bcoBuildOpts bco)) deps ".." depDocDir where getGhcPkgId :: LocalPackage -> Maybe GhcPkgId getGhcPkgId LocalPackage{lpPackage = Package{..}} = let pkgId = PackageIdentifier packageName packageVersion mdpPkg = F.find (\dp -> dpPackageIdent dp == pkgId) localDumpPkgs in fmap dpGhcPkgId mdpPkg findTransitiveDepends :: [GhcPkgId] -> [GhcPkgId] findTransitiveDepends = (`go` HS.empty) . HS.fromList where go todo checked = case HS.toList todo of [] -> HS.toList checked (ghcPkgId:_) -> let deps = case lookupDumpPackage ghcPkgId allDumpPkgs of Nothing -> HS.empty Just pkgDP -> HS.fromList (dpDepends pkgDP) deps' = deps `HS.difference` checked todo' = HS.delete ghcPkgId (deps' `HS.union` todo) checked' = HS.insert ghcPkgId checked in go todo' checked' allDumpPkgs = [localDumpPkgs, snapshotDumpPkgs, globalDumpPkgs] -- | Generate Haddock index and contents for all snapshot packages. generateSnapHaddockIndex :: (MonadIO m, MonadCatch m, MonadLogger m, MonadBaseControl IO m) => EnvOverride -> WhichCompiler -> BaseConfigOpts -> Map GhcPkgId (DumpPackage () () ()) -- ^ Global package dump -> Map GhcPkgId (DumpPackage () () ()) -- ^ Snapshot package dump -> m () generateSnapHaddockIndex envOverride wc bco globalDumpPkgs snapshotDumpPkgs = generateHaddockIndex "snapshot packages" envOverride wc (boptsHaddockOpts (bcoBuildOpts bco)) (Map.elems snapshotDumpPkgs ++ Map.elems globalDumpPkgs) "." (snapDocDir bco) -- | Generate Haddock index and contents for specified packages. generateHaddockIndex :: (MonadIO m, MonadCatch m, MonadLogger m, MonadBaseControl IO m) => Text -> EnvOverride -> WhichCompiler -> HaddockOpts -> [DumpPackage () () ()] -> FilePath -> Path Abs Dir -> m () generateHaddockIndex descr envOverride wc hdopts dumpPackages docRelFP destDir = do ensureDir destDir interfaceOpts <- (liftIO . fmap nubOrd . mapMaybeM toInterfaceOpt) dumpPackages unless (null interfaceOpts) $ do let destIndexFile = haddockIndexFile destDir eindexModTime <- liftIO (tryGetModificationTime destIndexFile) let needUpdate = case eindexModTime of Left _ -> True Right indexModTime -> or [mt > indexModTime | (_,mt,_,_) <- interfaceOpts] if needUpdate then do $logInfo (T.concat ["Updating Haddock index for ", descr, " in\n", T.pack (toFilePath destIndexFile)]) liftIO (mapM_ copyPkgDocs interfaceOpts) readProcessNull (Just destDir) envOverride (haddockExeName wc) (hoAdditionalArgs hdopts ++ ["--gen-contents", "--gen-index"] ++ [x | (xs,_,_,_) <- interfaceOpts, x <- xs]) else $logInfo (T.concat ["Haddock index for ", descr, " already up to date at:\n", T.pack (toFilePath destIndexFile)]) where toInterfaceOpt :: DumpPackage a b c -> IO (Maybe ([String], UTCTime, Path Abs File, Path Abs File)) toInterfaceOpt DumpPackage {..} = case dpHaddockInterfaces of [] -> return Nothing srcInterfaceFP:_ -> do srcInterfaceAbsFile <- parseCollapsedAbsFile srcInterfaceFP let (PackageIdentifier name _) = dpPackageIdent destInterfaceRelFP = docRelFP FP. packageIdentifierString dpPackageIdent FP. (packageNameString name FP.<.> "haddock") destInterfaceAbsFile <- parseCollapsedAbsFile (toFilePath destDir FP. destInterfaceRelFP) esrcInterfaceModTime <- tryGetModificationTime srcInterfaceAbsFile return $ case esrcInterfaceModTime of Left _ -> Nothing Right srcInterfaceModTime -> Just ( [ "-i" , concat [ docRelFP FP. packageIdentifierString dpPackageIdent , "," , destInterfaceRelFP ]] , srcInterfaceModTime , srcInterfaceAbsFile , destInterfaceAbsFile ) tryGetModificationTime :: Path Abs File -> IO (Either () UTCTime) tryGetModificationTime = tryJust (guard . isDoesNotExistError) . getModificationTime copyPkgDocs :: (a, UTCTime, Path Abs File, Path Abs File) -> IO () copyPkgDocs (_,srcInterfaceModTime,srcInterfaceAbsFile,destInterfaceAbsFile) = do -- Copy dependencies' haddocks to documentation directory. This way, relative @../$pkg-$ver@ -- links work and it's easy to upload docs to a web server or otherwise view them in a -- non-local-filesystem context. We copy instead of symlink for two reasons: (1) symlinks -- aren't reliably supported on Windows, and (2) the filesystem containing dependencies' -- docs may not be available where viewing the docs (e.g. if building in a Docker -- container). edestInterfaceModTime <- tryGetModificationTime destInterfaceAbsFile case edestInterfaceModTime of Left _ -> doCopy Right destInterfaceModTime | destInterfaceModTime < srcInterfaceModTime -> doCopy | otherwise -> return () where doCopy = do ignoringAbsence (removeDirRecur destHtmlAbsDir) ensureDir destHtmlAbsDir onException (copyDirRecur' (parent srcInterfaceAbsFile) destHtmlAbsDir) (ignoringAbsence (removeDirRecur destHtmlAbsDir)) destHtmlAbsDir = parent destInterfaceAbsFile -- | Find first DumpPackage matching the GhcPkgId lookupDumpPackage :: GhcPkgId -> [Map GhcPkgId (DumpPackage () () ())] -> Maybe (DumpPackage () () ()) lookupDumpPackage ghcPkgId dumpPkgs = listToMaybe $ mapMaybe (Map.lookup ghcPkgId) dumpPkgs -- | Path of haddock index file. haddockIndexFile :: Path Abs Dir -> Path Abs File haddockIndexFile destDir = destDir $(mkRelFile "index.html") -- | Path of local packages documentation directory. localDocDir :: BaseConfigOpts -> Path Abs Dir localDocDir bco = bcoLocalInstallRoot bco docDirSuffix -- | Path of documentation directory for the dependencies of local packages localDepsDocDir :: BaseConfigOpts -> Path Abs Dir localDepsDocDir bco = localDocDir bco $(mkRelDir "all") -- | Path of snapshot packages documentation directory. snapDocDir :: BaseConfigOpts -> Path Abs Dir snapDocDir bco = bcoSnapInstallRoot bco docDirSuffix stack-1.5.1/src/Stack/Build/Installed.hs0000644000000000000000000003016313135652051016163 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} -- Determine which packages are already installed module Stack.Build.Installed ( InstalledMap , Installed (..) , GetInstalledOpts (..) , getInstalled ) where import Control.Applicative import Control.Arrow import Control.Monad import Control.Monad.Logger import Data.Conduit import qualified Data.Conduit.List as CL import qualified Data.Foldable as F import Data.Function import qualified Data.HashSet as HashSet import Data.List import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import qualified Data.Map.Strict as Map import Data.Maybe import Data.Maybe.Extra (mapMaybeM) import Data.Monoid import qualified Data.Text as T import Path import Prelude hiding (FilePath, writeFile) import Stack.Build.Cache import Stack.Constants import Stack.GhcPkg import Stack.PackageDump import Stack.Types.Build import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.GhcPkgId import Stack.Types.Package import Stack.Types.PackageDump import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.StackT import Stack.Types.Version -- | Options for 'getInstalled'. data GetInstalledOpts = GetInstalledOpts { getInstalledProfiling :: !Bool -- ^ Require profiling libraries? , getInstalledHaddock :: !Bool -- ^ Require haddocks? , getInstalledSymbols :: !Bool -- ^ Require debugging symbols? } -- | Returns the new InstalledMap and all of the locally registered packages. getInstalled :: (StackM env m, HasEnvConfig env, PackageInstallInfo pii) => EnvOverride -> GetInstalledOpts -> Map PackageName pii -- ^ does not contain any installed information -> m ( InstalledMap , [DumpPackage () () ()] -- globally installed , [DumpPackage () () ()] -- snapshot installed , [DumpPackage () () ()] -- locally installed ) getInstalled menv opts sourceMap = do $logDebug "Finding out which packages are already installed" snapDBPath <- packageDatabaseDeps localDBPath <- packageDatabaseLocal extraDBPaths <- packageDatabaseExtra mcache <- if getInstalledProfiling opts || getInstalledHaddock opts then configInstalledCache >>= liftM Just . loadInstalledCache else return Nothing let loadDatabase' = loadDatabase menv opts mcache sourceMap (installedLibs0, globalDumpPkgs) <- loadDatabase' Nothing [] (installedLibs1, _extraInstalled) <- foldM (\lhs' pkgdb -> loadDatabase' (Just (ExtraGlobal, pkgdb)) (fst lhs') ) (installedLibs0, globalDumpPkgs) extraDBPaths (installedLibs2, snapshotDumpPkgs) <- loadDatabase' (Just (InstalledTo Snap, snapDBPath)) installedLibs1 (installedLibs3, localDumpPkgs) <- loadDatabase' (Just (InstalledTo Local, localDBPath)) installedLibs2 let installedLibs = M.fromList $ map lhPair installedLibs3 F.forM_ mcache $ \cache -> do icache <- configInstalledCache saveInstalledCache icache cache -- Add in the executables that are installed, making sure to only trust a -- listed installation under the right circumstances (see below) let exesToSM loc = Map.unions . map (exeToSM loc) exeToSM loc (PackageIdentifier name version) = case Map.lookup name sourceMap of -- Doesn't conflict with anything, so that's OK Nothing -> m Just pii -- Not the version we want, ignore it | version /= piiVersion pii || loc /= piiLocation pii -> Map.empty | otherwise -> m where m = Map.singleton name (loc, Executable $ PackageIdentifier name version) exesSnap <- getInstalledExes Snap exesLocal <- getInstalledExes Local let installedMap = Map.unions [ exesToSM Local exesLocal , exesToSM Snap exesSnap , installedLibs ] return ( installedMap , globalDumpPkgs , snapshotDumpPkgs , localDumpPkgs ) -- | Outputs both the modified InstalledMap and the Set of all installed packages in this database -- -- The goal is to ascertain that the dependencies for a package are present, -- that it has profiling if necessary, and that it matches the version and -- location needed by the SourceMap loadDatabase :: (StackM env m, HasEnvConfig env, PackageInstallInfo pii) => EnvOverride -> GetInstalledOpts -> Maybe InstalledCache -- ^ if Just, profiling or haddock is required -> Map PackageName pii -- ^ to determine which installed things we should include -> Maybe (InstalledPackageLocation, Path Abs Dir) -- ^ package database, Nothing for global -> [LoadHelper] -- ^ from parent databases -> m ([LoadHelper], [DumpPackage () () ()]) loadDatabase menv opts mcache sourceMap mdb lhs0 = do wc <- view $ actualCompilerVersionL.to whichCompiler (lhs1', dps) <- ghcPkgDump menv wc (fmap snd (maybeToList mdb)) $ conduitDumpPackage =$ sink let ghcjsHack = wc == Ghcjs && isNothing mdb lhs1 <- mapMaybeM (processLoadResult mdb ghcjsHack) lhs1' let lhs = pruneDeps id lhId lhDeps const (lhs0 ++ lhs1) return (map (\lh -> lh { lhDeps = [] }) $ Map.elems lhs, dps) where conduitProfilingCache = case mcache of Just cache | getInstalledProfiling opts -> addProfiling cache -- Just an optimization to avoid calculating the profiling -- values when they aren't necessary _ -> CL.map (\dp -> dp { dpProfiling = False }) conduitHaddockCache = case mcache of Just cache | getInstalledHaddock opts -> addHaddock cache -- Just an optimization to avoid calculating the haddock -- values when they aren't necessary _ -> CL.map (\dp -> dp { dpHaddock = False }) conduitSymbolsCache = case mcache of Just cache | getInstalledSymbols opts -> addSymbols cache -- Just an optimization to avoid calculating the debugging -- symbol values when they aren't necessary _ -> CL.map (\dp -> dp { dpSymbols = False }) mloc = fmap fst mdb sinkDP = conduitProfilingCache =$ conduitHaddockCache =$ conduitSymbolsCache =$ CL.map (isAllowed opts mcache sourceMap mloc &&& toLoadHelper mloc) =$ CL.consume sink = getZipSink $ (,) <$> ZipSink sinkDP <*> ZipSink CL.consume processLoadResult :: MonadLogger m => Maybe (InstalledPackageLocation, Path Abs Dir) -> Bool -> (Allowed, LoadHelper) -> m (Maybe LoadHelper) processLoadResult _ _ (Allowed, lh) = return (Just lh) processLoadResult _ True (WrongVersion actual wanted, lh) -- Allow some packages in the ghcjs global DB to have the wrong -- versions. Treat them as wired-ins by setting deps to []. | fst (lhPair lh) `HashSet.member` ghcjsBootPackages = do $logWarn $ T.concat [ "Ignoring that the GHCJS boot package \"" , packageNameText (fst (lhPair lh)) , "\" has a different version, " , versionText actual , ", than the resolver's wanted version, " , versionText wanted ] return (Just lh) processLoadResult mdb _ (reason, lh) = do $logDebug $ T.concat $ [ "Ignoring package " , packageNameText (fst (lhPair lh)) ] ++ maybe [] (\db -> [", from ", T.pack (show db), ","]) mdb ++ [ " due to" , case reason of Allowed -> " the impossible?!?!" NeedsProfiling -> " it needing profiling." NeedsHaddock -> " it needing haddocks." NeedsSymbols -> " it needing debugging symbols." UnknownPkg -> " it being unknown to the resolver / extra-deps." WrongLocation mloc loc -> " wrong location: " <> T.pack (show (mloc, loc)) WrongVersion actual wanted -> T.concat [ " wanting version " , versionText wanted , " instead of " , versionText actual ] ] return Nothing data Allowed = Allowed | NeedsProfiling | NeedsHaddock | NeedsSymbols | UnknownPkg | WrongLocation (Maybe InstalledPackageLocation) InstallLocation | WrongVersion Version Version deriving (Eq, Show) -- | Check if a can be included in the set of installed packages or not, based -- on the package selections made by the user. This does not perform any -- dirtiness or flag change checks. isAllowed :: PackageInstallInfo pii => GetInstalledOpts -> Maybe InstalledCache -> Map PackageName pii -> Maybe InstalledPackageLocation -> DumpPackage Bool Bool Bool -> Allowed isAllowed opts mcache sourceMap mloc dp -- Check that it can do profiling if necessary | getInstalledProfiling opts && isJust mcache && not (dpProfiling dp) = NeedsProfiling -- Check that it has haddocks if necessary | getInstalledHaddock opts && isJust mcache && not (dpHaddock dp) = NeedsHaddock -- Check that it has haddocks if necessary | getInstalledSymbols opts && isJust mcache && not (dpSymbols dp) = NeedsSymbols | otherwise = case Map.lookup name sourceMap of Nothing -> case mloc of -- The sourceMap has nothing to say about this global -- package, so we can use it Nothing -> Allowed Just ExtraGlobal -> Allowed -- For non-global packages, don't include unknown packages. -- See: -- https://github.com/commercialhaskell/stack/issues/292 Just _ -> UnknownPkg Just pii | not (checkLocation (piiLocation pii)) -> WrongLocation mloc (piiLocation pii) | version /= piiVersion pii -> WrongVersion version (piiVersion pii) | otherwise -> Allowed where PackageIdentifier name version = dpPackageIdent dp -- Ensure that the installed location matches where the sourceMap says it -- should be installed checkLocation Snap = mloc /= Just (InstalledTo Local) -- we can allow either global or snap checkLocation Local = mloc == Just (InstalledTo Local) || mloc == Just ExtraGlobal -- 'locally' installed snapshot packages can come from extra dbs data LoadHelper = LoadHelper { lhId :: !GhcPkgId , lhDeps :: ![GhcPkgId] , lhPair :: !(PackageName, (InstallLocation, Installed)) } deriving Show toLoadHelper :: Maybe InstalledPackageLocation -> DumpPackage Bool Bool Bool -> LoadHelper toLoadHelper mloc dp = LoadHelper { lhId = gid , lhDeps = -- We always want to consider the wired in packages as having all -- of their dependencies installed, since we have no ability to -- reinstall them. This is especially important for using different -- minor versions of GHC, where the dependencies of wired-in -- packages may change slightly and therefore not match the -- snapshot. if name `HashSet.member` wiredInPackages then [] else dpDepends dp , lhPair = (name, (toPackageLocation mloc, Library ident gid)) } where gid = dpGhcPkgId dp ident@(PackageIdentifier name _) = dpPackageIdent dp toPackageLocation :: Maybe InstalledPackageLocation -> InstallLocation toPackageLocation Nothing = Snap toPackageLocation (Just ExtraGlobal) = Snap toPackageLocation (Just (InstalledTo loc)) = loc stack-1.5.1/src/Stack/Build/Source.hs0000644000000000000000000007151413135652051015511 0ustar0000000000000000{-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PackageImports #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ConstraintKinds #-} -- Load information on package sources module Stack.Build.Source ( loadSourceMap , loadSourceMapFull , SourceMap , PackageSource (..) , getLocalFlags , getGhcOptions , getLocalPackageViews , parseTargetsFromBuildOpts , parseTargetsFromBuildOptsWith , addUnlistedToBuildCache , getDefaultPackageConfig , getPackageConfig ) where import Control.Applicative import Control.Arrow ((&&&)) import Control.Exception (assert, catch) import Control.Monad hiding (sequence) import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.Reader (MonadReader) import Control.Monad.Trans.Resource import Crypto.Hash (Digest, SHA256(..)) import Crypto.Hash.Conduit (sinkHash) import qualified Data.ByteArray as Mem (convert) import qualified Data.ByteString as S import Data.Conduit (($$), ZipSink (..)) import qualified Data.Conduit.Binary as CB import qualified Data.Conduit.List as CL import Data.Either import Data.Function import qualified Data.HashSet as HashSet import Data.List import qualified Data.Map as Map import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe import Data.Monoid import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as T import Data.Traversable (sequence) import Distribution.Package (pkgName, pkgVersion) import Distribution.PackageDescription (GenericPackageDescription, package, packageDescription) import qualified Distribution.PackageDescription as C import Path import Path.IO import Prelude hiding (sequence) import Stack.Build.Cache import Stack.Build.Target import Stack.BuildPlan (shadowMiniBuildPlan) import Stack.Config (getLocalPackages) import Stack.Constants (wiredInPackages) import Stack.Package import Stack.PackageIndex (getPackageVersions) import Stack.Types.Build import Stack.Types.BuildPlan import Stack.Types.Config import Stack.Types.FlagName import Stack.Types.Package import Stack.Types.PackageName import Stack.Types.Resolver import Stack.Types.StackT import Stack.Types.Version import qualified System.Directory as D import System.FilePath (takeFileName) import System.IO (withBinaryFile, IOMode (ReadMode)) import System.IO.Error (isDoesNotExistError) -- | Like 'loadSourceMapFull', but doesn't return values that aren't as -- commonly needed. loadSourceMap :: (StackM env m, HasEnvConfig env) => NeedTargets -> BuildOptsCLI -> m ( [LocalPackage] , SourceMap ) loadSourceMap needTargets boptsCli = do (_, _, locals, _, _, sourceMap) <- loadSourceMapFull needTargets boptsCli return (locals, sourceMap) -- | Given the build commandline options, does the following: -- -- * Parses the build targets. -- -- * Loads the 'MiniBuildPlan' from the resolver, with extra-deps -- shadowing any packages that should be built locally. -- -- * Loads up the 'LocalPackage' info. -- -- * Builds a 'SourceMap', which contains info for all the packages that -- will be involved in the build. loadSourceMapFull :: (StackM env m, HasEnvConfig env) => NeedTargets -> BuildOptsCLI -> m ( Map PackageName SimpleTarget , MiniBuildPlan , [LocalPackage] , Set PackageName -- non-local targets , Map PackageName Version -- extra-deps from configuration and cli , SourceMap ) loadSourceMapFull needTargets boptsCli = do bconfig <- view buildConfigL rawLocals <- getLocalPackageViews (mbp0, cliExtraDeps, targets) <- parseTargetsFromBuildOptsWith rawLocals needTargets boptsCli -- Extend extra-deps to encompass targets requested on the command line -- that are not in the snapshot. extraDeps0 <- extendExtraDeps (bcExtraDeps bconfig) cliExtraDeps (Map.keysSet $ Map.filter (== STUnknown) targets) locals <- mapM (loadLocalPackage boptsCli targets) $ Map.toList rawLocals checkFlagsUsed boptsCli locals extraDeps0 (mbpPackages mbp0) checkComponentsBuildable locals let -- loadLocals returns PackageName (foo) and PackageIdentifier (bar-1.2.3) targets separately; -- here we combine them into nonLocalTargets. This is one of the -- return values of this function. nonLocalTargets :: Set PackageName nonLocalTargets = Map.keysSet $ Map.filter (not . isLocal) targets where isLocal (STLocalComps _) = True isLocal STLocalAll = True isLocal STUnknown = False isLocal STNonLocal = False shadowed = Map.keysSet rawLocals <> Map.keysSet extraDeps0 -- Ignores all packages in the MiniBuildPlan that depend on any -- local packages or extra-deps. All packages that have -- transitive dependenceis on these packages are treated as -- extra-deps (extraDeps1). (mbp, extraDeps1) = shadowMiniBuildPlan mbp0 shadowed -- Combine the extra-deps with the ones implicitly shadowed. extraDeps2 = Map.union (Map.map (\v -> (v, Map.empty, [])) extraDeps0) (Map.map (\mpi -> (mpiVersion mpi, mpiFlags mpi, mpiGhcOptions mpi)) extraDeps1) -- Add flag and ghc-option settings from the config file / cli extraDeps3 = Map.mapWithKey (\n (v, flags0, ghcOptions0) -> let flags = case ( Map.lookup (Just n) $ boptsCLIFlags boptsCli , Map.lookup Nothing $ boptsCLIFlags boptsCli , Map.lookup n $ unPackageFlags $ bcFlags bconfig ) of -- Didn't have any flag overrides, fall back to the flags -- defined in the snapshot. (Nothing, Nothing, Nothing) -> flags0 -- Either command line flag for this package, general -- command line flag, or flag in stack.yaml is defined. -- Take all of those and ignore the snapshot flags. (x, y, z) -> Map.unions [ fromMaybe Map.empty x , fromMaybe Map.empty y , fromMaybe Map.empty z ] ghcOptions = ghcOptions0 ++ getGhcOptions bconfig boptsCli n False False -- currently have no ability for extra-deps to specify their -- cabal file hashes in PSUpstream v Local flags ghcOptions Nothing) extraDeps2 -- Combine the local packages, extra-deps, and MiniBuildPlan into -- one unified source map. let sourceMap = Map.unions [ Map.fromList $ flip map locals $ \lp -> let p = lpPackage lp in (packageName p, PSLocal lp) , extraDeps3 , flip Map.mapWithKey (mbpPackages mbp) $ \n mpi -> let configOpts = getGhcOptions bconfig boptsCli n False False in PSUpstream (mpiVersion mpi) Snap (mpiFlags mpi) (mpiGhcOptions mpi ++ configOpts) (mpiGitSHA1 mpi) ] `Map.difference` Map.fromList (map (, ()) (HashSet.toList wiredInPackages)) return (targets, mbp, locals, nonLocalTargets, extraDeps0, sourceMap) -- | All flags for a local package. getLocalFlags :: BuildConfig -> BuildOptsCLI -> PackageName -> Map FlagName Bool getLocalFlags bconfig boptsCli name = Map.unions [ Map.findWithDefault Map.empty (Just name) cliFlags , Map.findWithDefault Map.empty Nothing cliFlags , Map.findWithDefault Map.empty name (unPackageFlags (bcFlags bconfig)) ] where cliFlags = boptsCLIFlags boptsCli -- | Get the configured options to pass from GHC, based on the build -- configuration and commandline. getGhcOptions :: BuildConfig -> BuildOptsCLI -> PackageName -> Bool -> Bool -> [Text] getGhcOptions bconfig boptsCli name isTarget isLocal = concat [ ghcOptionsFor name (configGhcOptions config) , concat [["-fhpc"] | isLocal && toCoverage (boptsTestOpts bopts)] , if boptsLibProfile bopts || boptsExeProfile bopts then ["-auto-all","-caf-all"] else [] , if not $ boptsLibStrip bopts || boptsExeStrip bopts then ["-g"] else [] , if includeExtraOptions then boptsCLIGhcOptions boptsCli else [] ] where bopts = configBuild config config = view configL bconfig includeExtraOptions = case configApplyGhcOptions config of AGOTargets -> isTarget AGOLocals -> isLocal AGOEverything -> True -- | Use the build options and environment to parse targets. -- -- If the local packages views are already known, use 'parseTargetsFromBuildOptsWith' -- instead. -- -- Along with the 'Map' of targets, this yields the loaded -- 'MiniBuildPlan' for the resolver, as well as a Map of extra-deps -- derived from the commandline. These extra-deps targets come from when -- the user specifies a particular package version on the commonadline, -- or when a flag is specified for a snapshot package. parseTargetsFromBuildOpts :: (StackM env m, HasEnvConfig env) => NeedTargets -> BuildOptsCLI -> m (MiniBuildPlan, M.Map PackageName Version, M.Map PackageName SimpleTarget) parseTargetsFromBuildOpts needTargets boptscli = do rawLocals <- getLocalPackageViews parseTargetsFromBuildOptsWith rawLocals needTargets boptscli parseTargetsFromBuildOptsWith :: (StackM env m, HasEnvConfig env) => Map PackageName (LocalPackageView, GenericPackageDescription) -- ^ Local package views -> NeedTargets -> BuildOptsCLI -> m (MiniBuildPlan, M.Map PackageName Version, M.Map PackageName SimpleTarget) parseTargetsFromBuildOptsWith rawLocals needTargets boptscli = do $logDebug "Parsing the targets" bconfig <- view buildConfigL mbp0 <- case bcResolver bconfig of ResolverCompiler _ -> do -- We ignore the resolver version, as it might be -- GhcMajorVersion, and we want the exact version -- we're using. version <- view actualCompilerVersionL return MiniBuildPlan { mbpCompilerVersion = version , mbpPackages = Map.empty } _ -> return (bcWantedMiniBuildPlan bconfig) workingDir <- getCurrentDir let snapshot = mpiVersion <$> mbpPackages mbp0 flagExtraDeps <- convertSnapshotToExtra snapshot (bcExtraDeps bconfig) rawLocals (catMaybes $ Map.keys $ boptsCLIFlags boptscli) (cliExtraDeps, targets) <- parseTargets needTargets (bcImplicitGlobal bconfig) snapshot (flagExtraDeps <> bcExtraDeps bconfig) (fst <$> rawLocals) workingDir (boptsCLITargets boptscli) return (mbp0, cliExtraDeps <> flagExtraDeps, targets) -- | For every package in the snapshot which is referenced by a flag, give the -- user a warning and then add it to extra-deps. convertSnapshotToExtra :: MonadLogger m => Map PackageName Version -- ^ snapshot -> Map PackageName Version -- ^ extra-deps -> Map PackageName a -- ^ locals -> [PackageName] -- ^ packages referenced by a flag -> m (Map PackageName Version) convertSnapshotToExtra snapshot extra0 locals = go Map.empty where go !extra [] = return extra go extra (flag:flags) | Just _ <- Map.lookup flag extra0 = go extra flags | flag `Map.member` locals = go extra flags | otherwise = case Map.lookup flag snapshot of Nothing -> go extra flags Just version -> do $logWarn $ T.concat [ "- Implicitly adding " , T.pack $ packageNameString flag , " to extra-deps based on command line flag" ] go (Map.insert flag version extra) flags -- | Parse out the local package views for the current project getLocalPackageViews :: (StackM env m, HasEnvConfig env) => m (Map PackageName (LocalPackageView, GenericPackageDescription)) getLocalPackageViews = do $logDebug "Parsing the cabal files of the local packages" packages <- getLocalPackages locals <- forM (Map.toList packages) $ \(dir, treatLikeExtraDep) -> do cabalfp <- findOrGenerateCabalFile dir (warnings,gpkg) <- readPackageUnresolved cabalfp mapM_ (printCabalFileWarning cabalfp) warnings let cabalID = package $ packageDescription gpkg name = fromCabalPackageName $ pkgName cabalID checkCabalFileName name cabalfp let lpv = LocalPackageView { lpvVersion = fromCabalVersion $ pkgVersion cabalID , lpvRoot = dir , lpvCabalFP = cabalfp , lpvExtraDep = treatLikeExtraDep , lpvComponents = getNamedComponents gpkg } return (name, (lpv, gpkg)) checkDuplicateNames locals return $ Map.fromList locals where getNamedComponents gpkg = Set.fromList $ concat [ maybe [] (const [CLib]) (C.condLibrary gpkg) , go CExe C.condExecutables , go CTest C.condTestSuites , go CBench C.condBenchmarks ] where go wrapper f = map (wrapper . T.pack . fst) $ f gpkg -- | Check if there are any duplicate package names and, if so, throw an -- exception. checkDuplicateNames :: MonadThrow m => [(PackageName, (LocalPackageView, gpd))] -> m () checkDuplicateNames locals = case filter hasMultiples $ Map.toList $ Map.fromListWith (++) $ map toPair locals of [] -> return () x -> throwM $ DuplicateLocalPackageNames x where toPair (pn, (lpv, _)) = (pn, [lpvRoot lpv]) hasMultiples (_, _:_:_) = True hasMultiples _ = False splitComponents :: [NamedComponent] -> (Set Text, Set Text, Set Text) splitComponents = go id id id where go a b c [] = (Set.fromList $ a [], Set.fromList $ b [], Set.fromList $ c []) go a b c (CLib:xs) = go a b c xs go a b c (CExe x:xs) = go (a . (x:)) b c xs go a b c (CTest x:xs) = go a (b . (x:)) c xs go a b c (CBench x:xs) = go a b (c . (x:)) xs -- | Upgrade the initial local package info to a full-blown @LocalPackage@ -- based on the selected components loadLocalPackage :: forall m env. (StackM env m, HasEnvConfig env) => BuildOptsCLI -> Map PackageName SimpleTarget -> (PackageName, (LocalPackageView, GenericPackageDescription)) -> m LocalPackage loadLocalPackage boptsCli targets (name, (lpv, gpkg)) = do let mtarget = Map.lookup name targets config <- getPackageConfig boptsCli name (isJust mtarget) True bopts <- view buildOptsL let (exes, tests, benches) = case mtarget of Just (STLocalComps comps) -> splitComponents $ Set.toList comps Just STLocalAll -> ( packageExes pkg , if boptsTests bopts && not (lpvExtraDep lpv) then Map.keysSet (packageTests pkg) else Set.empty , if boptsBenchmarks bopts && not (lpvExtraDep lpv) then packageBenchmarks pkg else Set.empty ) Just STNonLocal -> assert False mempty Just STUnknown -> assert False mempty Nothing -> mempty toComponents e t b = Set.unions [ Set.map CExe e , Set.map CTest t , Set.map CBench b ] btconfig = config { packageConfigEnableTests = not $ Set.null tests , packageConfigEnableBenchmarks = not $ Set.null benches } testconfig = config { packageConfigEnableTests = True , packageConfigEnableBenchmarks = False } benchconfig = config { packageConfigEnableTests = False , packageConfigEnableBenchmarks = True } -- We resolve the package in 4 different configurations: -- -- - pkg doesn't have tests or benchmarks enabled. -- -- - btpkg has them enabled if they are present. -- -- - testpkg has tests enabled, but not benchmarks. -- -- - benchpkg has benchmarks enablde, but not tests. -- -- The latter two configurations are used to compute the deps -- when --enable-benchmarks or --enable-tests are configured. -- This allows us to do an optimization where these are passed -- if the deps are present. This can avoid doing later -- unnecessary reconfigures. pkg = resolvePackage config gpkg btpkg | Set.null tests && Set.null benches = Nothing | otherwise = Just (resolvePackage btconfig gpkg) testpkg = resolvePackage testconfig gpkg benchpkg = resolvePackage benchconfig gpkg mbuildCache <- tryGetBuildCache $ lpvRoot lpv (files,_) <- getPackageFilesSimple pkg (lpvCabalFP lpv) (dirtyFiles, newBuildCache) <- checkBuildCache (fromMaybe Map.empty mbuildCache) (Set.toList files) return LocalPackage { lpPackage = pkg , lpTestDeps = packageDeps testpkg , lpBenchDeps = packageDeps benchpkg , lpTestBench = btpkg , lpFiles = files , lpForceDirty = boptsForceDirty bopts , lpDirtyFiles = if not (Set.null dirtyFiles) then let tryStripPrefix y = fromMaybe y (stripPrefix (toFilePath $ lpvRoot lpv) y) in Just $ Set.map tryStripPrefix dirtyFiles else Nothing , lpNewBuildCache = newBuildCache , lpCabalFile = lpvCabalFP lpv , lpDir = lpvRoot lpv , lpWanted = isJust mtarget , lpComponents = toComponents exes tests benches -- TODO: refactor this so that it's easier to be sure that these -- components are indeed unbuildable. -- -- The reasoning here is that if the STLocalComps specification -- made it through component parsing, but the components aren't -- present, then they must not be buildable. , lpUnbuildable = toComponents (exes `Set.difference` packageExes pkg) (tests `Set.difference` Map.keysSet (packageTests pkg)) (benches `Set.difference` packageBenchmarks pkg) } -- | Ensure that the flags specified in the stack.yaml file and on the command -- line are used. checkFlagsUsed :: (MonadThrow m, MonadReader env m, HasBuildConfig env) => BuildOptsCLI -> [LocalPackage] -> Map PackageName extraDeps -- ^ extra deps -> Map PackageName snapshot -- ^ snapshot, for error messages -> m () checkFlagsUsed boptsCli lps extraDeps snapshot = do bconfig <- view buildConfigL -- Check if flags specified in stack.yaml and the command line are -- used, see https://github.com/commercialhaskell/stack/issues/617 let flags = map (, FSCommandLine) [(k, v) | (Just k, v) <- Map.toList $ boptsCLIFlags boptsCli] ++ map (, FSStackYaml) (Map.toList $ unPackageFlags $ bcFlags bconfig) localNameMap = Map.fromList $ map (packageName . lpPackage &&& lpPackage) lps checkFlagUsed ((name, userFlags), source) = case Map.lookup name localNameMap of -- Package is not available locally Nothing -> case Map.lookup name extraDeps of -- Also not in extra-deps, it's an error Nothing -> case Map.lookup name snapshot of Nothing -> Just $ UFNoPackage source name Just _ -> Just $ UFSnapshot name -- We don't check for flag presence for extra deps Just _ -> Nothing -- Package exists locally, let's check if the flags are defined Just pkg -> let unused = Set.difference (Map.keysSet userFlags) (packageDefinedFlags pkg) in if Set.null unused -- All flags are defined, nothing to do then Nothing -- Error about the undefined flags else Just $ UFFlagsNotDefined source pkg unused unusedFlags = mapMaybe checkFlagUsed flags unless (null unusedFlags) $ throwM $ InvalidFlagSpecification $ Set.fromList unusedFlags -- | Add in necessary packages to extra dependencies -- -- Originally part of https://github.com/commercialhaskell/stack/issues/272, -- this was then superseded by -- https://github.com/commercialhaskell/stack/issues/651 extendExtraDeps :: (StackM env m, HasBuildConfig env) => Map PackageName Version -- ^ original extra deps -> Map PackageName Version -- ^ package identifiers from the command line -> Set PackageName -- ^ all packages added on the command line -> m (Map PackageName Version) -- ^ new extradeps extendExtraDeps extraDeps0 cliExtraDeps unknowns = do (errs, unknowns') <- fmap partitionEithers $ mapM addUnknown $ Set.toList unknowns case errs of [] -> return $ Map.unions $ extraDeps1 : unknowns' _ -> do bconfig <- view buildConfigL throwM $ UnknownTargets (Set.fromList errs) Map.empty -- TODO check the cliExtraDeps for presence in index (bcStackYaml bconfig) where extraDeps1 = Map.union extraDeps0 cliExtraDeps addUnknown pn = do case Map.lookup pn extraDeps1 of Just _ -> return (Right Map.empty) Nothing -> do mlatestVersion <- getLatestVersion pn case mlatestVersion of Just v -> return (Right $ Map.singleton pn v) Nothing -> return (Left pn) getLatestVersion pn = do vs <- getPackageVersions pn return (fmap fst (Set.maxView vs)) -- | Compare the current filesystem state to the cached information, and -- determine (1) if the files are dirty, and (2) the new cache values. checkBuildCache :: forall m. (MonadIO m) => Map FilePath FileCacheInfo -- ^ old cache -> [Path Abs File] -- ^ files in package -> m (Set FilePath, Map FilePath FileCacheInfo) checkBuildCache oldCache files = do fileTimes <- liftM Map.fromList $ forM files $ \fp -> do mmodTime <- liftIO (getModTimeMaybe (toFilePath fp)) return (toFilePath fp, mmodTime) liftM (mconcat . Map.elems) $ sequence $ Map.mergeWithKey (\fp mmodTime fci -> Just (go fp mmodTime (Just fci))) (Map.mapWithKey (\fp mmodTime -> go fp mmodTime Nothing)) (Map.mapWithKey (\fp fci -> go fp Nothing (Just fci))) fileTimes oldCache where go :: FilePath -> Maybe ModTime -> Maybe FileCacheInfo -> m (Set FilePath, Map FilePath FileCacheInfo) -- Filter out the cabal_macros file to avoid spurious recompilations go fp _ _ | takeFileName fp == "cabal_macros.h" = return (Set.empty, Map.empty) -- Common case where it's in the cache and on the filesystem. go fp (Just modTime') (Just fci) | fciModTime fci == modTime' = return (Set.empty, Map.empty) | otherwise = do newFci <- calcFci modTime' fp let isDirty = fciSize fci /= fciSize newFci || fciHash fci /= fciHash newFci newDirty = if isDirty then Set.singleton fp else Set.empty return (newDirty, Map.singleton fp newFci) -- Missing file. Add it to dirty files, but no FileCacheInfo. go fp Nothing _ = return (Set.singleton fp, Map.empty) -- Missing cache. Add it to dirty files and compute FileCacheInfo. go fp (Just modTime') Nothing = do newFci <- calcFci modTime' fp return (Set.singleton fp, Map.singleton fp newFci) -- | Returns entries to add to the build cache for any newly found unlisted modules addUnlistedToBuildCache :: (StackM env m, HasEnvConfig env) => ModTime -> Package -> Path Abs File -> Map FilePath a -> m ([Map FilePath FileCacheInfo], [PackageWarning]) addUnlistedToBuildCache preBuildTime pkg cabalFP buildCache = do (files,warnings) <- getPackageFilesSimple pkg cabalFP let newFiles = Set.toList $ Set.map toFilePath files `Set.difference` Map.keysSet buildCache addBuildCache <- mapM addFileToCache newFiles return (addBuildCache, warnings) where addFileToCache fp = do mmodTime <- getModTimeMaybe fp case mmodTime of Nothing -> return Map.empty Just modTime' -> if modTime' < preBuildTime then do newFci <- calcFci modTime' fp return (Map.singleton fp newFci) else return Map.empty -- | Gets list of Paths for files in a package getPackageFilesSimple :: (StackM env m, HasEnvConfig env) => Package -> Path Abs File -> m (Set (Path Abs File), [PackageWarning]) getPackageFilesSimple pkg cabalFP = do (_,compFiles,cabalFiles,warnings) <- getPackageFiles (packageFiles pkg) cabalFP return ( Set.map dotCabalGetPath (mconcat (M.elems compFiles)) <> cabalFiles , warnings) -- | Get file modification time, if it exists. getModTimeMaybe :: MonadIO m => FilePath -> m (Maybe ModTime) getModTimeMaybe fp = liftIO (catch (liftM (Just . modTime) (D.getModificationTime fp)) (\e -> if isDoesNotExistError e then return Nothing else throwM e)) -- | Create FileCacheInfo for a file. calcFci :: MonadIO m => ModTime -> FilePath -> m FileCacheInfo calcFci modTime' fp = liftIO $ withBinaryFile fp ReadMode $ \h -> do (size, digest) <- CB.sourceHandle h $$ getZipSink ((,) <$> ZipSink (CL.fold (\x y -> x + fromIntegral (S.length y)) 0) <*> ZipSink sinkHash) return FileCacheInfo { fciModTime = modTime' , fciSize = size , fciHash = Mem.convert (digest :: Digest SHA256) } checkComponentsBuildable :: MonadThrow m => [LocalPackage] -> m () checkComponentsBuildable lps = unless (null unbuildable) $ throwM $ SomeTargetsNotBuildable unbuildable where unbuildable = [ (packageName (lpPackage lp), c) | lp <- lps , c <- Set.toList (lpUnbuildable lp) ] getDefaultPackageConfig :: (MonadIO m, MonadReader env m, HasEnvConfig env) => m PackageConfig getDefaultPackageConfig = do platform <- view platformL compilerVersion <- view actualCompilerVersionL return PackageConfig { packageConfigEnableTests = False , packageConfigEnableBenchmarks = False , packageConfigFlags = M.empty , packageConfigGhcOptions = [] , packageConfigCompilerVersion = compilerVersion , packageConfigPlatform = platform } -- | Get 'PackageConfig' for package given its name. getPackageConfig :: (MonadIO m, MonadReader env m, HasEnvConfig env) => BuildOptsCLI -> PackageName -> Bool -> Bool -> m PackageConfig getPackageConfig boptsCli name isTarget isLocal = do bconfig <- view buildConfigL platform <- view platformL compilerVersion <- view actualCompilerVersionL return PackageConfig { packageConfigEnableTests = False , packageConfigEnableBenchmarks = False , packageConfigFlags = getLocalFlags bconfig boptsCli name , packageConfigGhcOptions = getGhcOptions bconfig boptsCli name isTarget isLocal , packageConfigCompilerVersion = compilerVersion , packageConfigPlatform = platform } stack-1.5.1/src/Stack/Build/Target.hs0000644000000000000000000003347413135652051015502 0ustar0000000000000000{-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE ViewPatterns #-} -- | Parsing command line targets module Stack.Build.Target ( -- * Types ComponentName , UnresolvedComponent (..) , RawTarget (..) , LocalPackageView (..) , SimpleTarget (..) , NeedTargets (..) -- * Parsers , parseRawTarget , parseTargets ) where import Control.Applicative import Control.Arrow (second) import Control.Monad.Catch (MonadCatch, throwM) import Control.Monad.IO.Class import Data.Either (partitionEithers) import Data.Foldable import Data.List.Extra (groupSort) import Data.List.NonEmpty (NonEmpty((:|))) import qualified Data.List.NonEmpty as NonEmpty import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe (mapMaybe) import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as T import Path import Path.Extra (rejectMissingDir) import Path.IO import Prelude hiding (concat, concatMap) -- Fix redundant import warnings import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.Version import Stack.Types.Config import Stack.Types.Build import Stack.Types.Package -- | The name of a component, which applies to executables, test suites, and benchmarks type ComponentName = Text newtype RawInput = RawInput { unRawInput :: Text } -- | Either a fully resolved component, or a component name that could be -- either an executable, test, or benchmark data UnresolvedComponent = ResolvedComponent !NamedComponent | UnresolvedComponent !ComponentName deriving (Show, Eq, Ord) -- | Raw command line input, without checking against any databases or list of -- locals. Does not deal with directories data RawTarget (a :: RawTargetType) where RTPackageComponent :: !PackageName -> !UnresolvedComponent -> RawTarget a RTComponent :: !ComponentName -> RawTarget a RTPackage :: !PackageName -> RawTarget a RTPackageIdentifier :: !PackageIdentifier -> RawTarget 'HasIdents deriving instance Show (RawTarget a) deriving instance Eq (RawTarget a) deriving instance Ord (RawTarget a) data RawTargetType = HasIdents | NoIdents -- | If this function returns @Nothing@, the input should be treated as a -- directory. parseRawTarget :: Text -> Maybe (RawTarget 'HasIdents) parseRawTarget t = (RTPackageIdentifier <$> parsePackageIdentifierFromString s) <|> (RTPackage <$> parsePackageNameFromString s) <|> (RTComponent <$> T.stripPrefix ":" t) <|> parsePackageComponent where s = T.unpack t parsePackageComponent = case T.splitOn ":" t of [pname, "lib"] | Just pname' <- parsePackageNameFromString (T.unpack pname) -> Just $ RTPackageComponent pname' $ ResolvedComponent CLib [pname, cname] | Just pname' <- parsePackageNameFromString (T.unpack pname) -> Just $ RTPackageComponent pname' $ UnresolvedComponent cname [pname, typ, cname] | Just pname' <- parsePackageNameFromString (T.unpack pname) , Just wrapper <- parseCompType typ -> Just $ RTPackageComponent pname' $ ResolvedComponent $ wrapper cname _ -> Nothing parseCompType t' = case t' of "exe" -> Just CExe "test" -> Just CTest "bench" -> Just CBench _ -> Nothing -- | A view of a local package needed for resolving components data LocalPackageView = LocalPackageView { lpvVersion :: !Version , lpvRoot :: !(Path Abs Dir) , lpvCabalFP :: !(Path Abs File) , lpvComponents :: !(Set NamedComponent) , lpvExtraDep :: !TreatLikeExtraDep } -- | Same as @parseRawTarget@, but also takes directories into account. parseRawTargetDirs :: (MonadIO m, MonadCatch m) => Path Abs Dir -- ^ current directory -> Map PackageName LocalPackageView -> Text -> m (Either Text [(RawInput, RawTarget 'HasIdents)]) parseRawTargetDirs root locals t = case parseRawTarget t of Just rt -> return $ Right [(ri, rt)] Nothing -> do mdir <- forgivingAbsence (resolveDir root (T.unpack t)) >>= rejectMissingDir case mdir of Nothing -> return $ Left $ "Directory not found: " `T.append` t Just dir -> case mapMaybe (childOf dir) $ Map.toList locals of [] -> return $ Left $ "No local directories found as children of " `T.append` t names -> return $ Right $ map ((ri, ) . RTPackage) names where ri = RawInput t childOf dir (name, lpv) = if (dir == lpvRoot lpv || isParentOf dir (lpvRoot lpv)) && not (lpvExtraDep lpv) then Just name else Nothing data SimpleTarget = STUnknown | STNonLocal | STLocalComps !(Set NamedComponent) | STLocalAll deriving (Show, Eq, Ord) resolveIdents :: Map PackageName Version -- ^ snapshot -> Map PackageName Version -- ^ extra deps -> Map PackageName LocalPackageView -> (RawInput, RawTarget 'HasIdents) -> Either Text ((RawInput, RawTarget 'NoIdents), Map PackageName Version) resolveIdents _ _ _ (ri, RTPackageComponent x y) = Right ((ri, RTPackageComponent x y), Map.empty) resolveIdents _ _ _ (ri, RTComponent x) = Right ((ri, RTComponent x), Map.empty) resolveIdents _ _ _ (ri, RTPackage x) = Right ((ri, RTPackage x), Map.empty) resolveIdents snap extras locals (ri, RTPackageIdentifier (PackageIdentifier name version)) = fmap ((ri, RTPackage name), ) newExtras where newExtras = case (Map.lookup name locals, mfound) of -- Error if it matches a local package, pkg idents not -- supported for local. (Just _, _) -> Left $ T.concat [ packageNameText name , " target has a specific version number, but it is a local package." , "\nTo avoid confusion, we will not install the specified version or build the local one." , "\nTo build the local package, specify the target without an explicit version." ] -- If the found version matches, no need for an extra-dep. (_, Just foundVersion) | foundVersion == version -> Right Map.empty -- Otherwise, if there is no specified version or a -- mismatch, add an extra-dep. _ -> Right $ Map.singleton name version mfound = asum (map (Map.lookup name) [extras, snap]) resolveRawTarget :: Map PackageName Version -- ^ snapshot -> Map PackageName Version -- ^ extra deps -> Map PackageName LocalPackageView -> (RawInput, RawTarget 'NoIdents) -> Either Text (PackageName, (RawInput, SimpleTarget)) resolveRawTarget snap extras locals (ri, rt) = go rt where go (RTPackageComponent name ucomp) = case Map.lookup name locals of Nothing -> Left $ T.pack $ "Unknown local package: " ++ packageNameString name Just lpv -> case ucomp of ResolvedComponent comp | comp `Set.member` lpvComponents lpv -> Right (name, (ri, STLocalComps $ Set.singleton comp)) | otherwise -> Left $ T.pack $ concat [ "Component " , show comp , " does not exist in package " , packageNameString name ] UnresolvedComponent comp -> case filter (isCompNamed comp) $ Set.toList $ lpvComponents lpv of [] -> Left $ T.concat [ "Component " , comp , " does not exist in package " , T.pack $ packageNameString name ] [x] -> Right (name, (ri, STLocalComps $ Set.singleton x)) matches -> Left $ T.concat [ "Ambiguous component name " , comp , " for package " , T.pack $ packageNameString name , ": " , T.pack $ show matches ] go (RTComponent cname) = let allPairs = concatMap (\(name, lpv) -> map (name,) $ Set.toList $ lpvComponents lpv) (Map.toList locals) in case filter (isCompNamed cname . snd) allPairs of [] -> Left $ cname `T.append` " doesn't seem to be a local target. Run 'stack ide targets' for a list of available targets" [(name, comp)] -> Right (name, (ri, STLocalComps $ Set.singleton comp)) matches -> Left $ T.concat [ "Ambiugous component name " , cname , ", matches: " , T.pack $ show matches ] go (RTPackage name) = case Map.lookup name locals of Just _lpv -> Right (name, (ri, STLocalAll)) Nothing -> case Map.lookup name extras of Just _ -> Right (name, (ri, STNonLocal)) Nothing -> case Map.lookup name snap of Just _ -> Right (name, (ri, STNonLocal)) Nothing -> Right (name, (ri, STUnknown)) isCompNamed :: Text -> NamedComponent -> Bool isCompNamed _ CLib = False isCompNamed t1 (CExe t2) = t1 == t2 isCompNamed t1 (CTest t2) = t1 == t2 isCompNamed t1 (CBench t2) = t1 == t2 simplifyTargets :: [(PackageName, (RawInput, SimpleTarget))] -> ([Text], Map PackageName SimpleTarget) simplifyTargets = foldMap go . collect where go :: (PackageName, NonEmpty (RawInput, SimpleTarget)) -> ([Text], Map PackageName SimpleTarget) go (name, (_, st) :| []) = ([], Map.singleton name st) go (name, pairs) = case partitionEithers $ map (getLocalComp . snd) (NonEmpty.toList pairs) of ([], comps) -> ([], Map.singleton name $ STLocalComps $ Set.unions comps) _ -> let err = T.pack $ concat [ "Overlapping targets provided for package " , packageNameString name , ": " , show $ map (unRawInput . fst) (NonEmpty.toList pairs) ] in ([err], Map.empty) collect :: Ord a => [(a, b)] -> [(a, NonEmpty b)] collect = map (second NonEmpty.fromList) . groupSort getLocalComp (STLocalComps comps) = Right comps getLocalComp _ = Left () -- | Need targets, e.g. `stack build` or allow none? data NeedTargets = NeedTargets | AllowNoTargets parseTargets :: (MonadCatch m, MonadIO m) => NeedTargets -- ^ need at least one target -> Bool -- ^ using implicit global project? -> Map PackageName Version -- ^ snapshot -> Map PackageName Version -- ^ extra deps -> Map PackageName LocalPackageView -> Path Abs Dir -- ^ current directory -> [Text] -- ^ command line targets -> m (Map PackageName Version, Map PackageName SimpleTarget) parseTargets needTargets implicitGlobal snap extras locals currDir textTargets' = do let nonExtraDeps = Map.keys $ Map.filter (not . lpvExtraDep) locals textTargets = if null textTargets' then map (T.pack . packageNameString) nonExtraDeps else textTargets' erawTargets <- mapM (parseRawTargetDirs currDir locals) textTargets let (errs1, rawTargets) = partitionEithers erawTargets -- When specific package identifiers are provided, treat these -- as extra-deps. (errs2, unzip -> (rawTargets', newExtras)) = partitionEithers $ map (resolveIdents snap extras locals) $ concat rawTargets -- Find targets that specify components in the local packages, -- otherwise find package targets in snap and extra-deps. (errs3, targetTypes) = partitionEithers $ map (resolveRawTarget snap extras locals) rawTargets' (errs4, targets) = simplifyTargets targetTypes errs = concat [errs1, errs2, errs3, errs4] if null errs then if Map.null targets then case needTargets of AllowNoTargets -> return (Map.empty, Map.empty) NeedTargets | null textTargets' && implicitGlobal -> throwM $ TargetParseException ["The specified targets matched no packages.\nPerhaps you need to run 'stack init'?"] | null textTargets' && null nonExtraDeps -> throwM $ TargetParseException ["The project contains no local packages (packages not marked with 'extra-dep')"] | otherwise -> throwM $ TargetParseException ["The specified targets matched no packages"] else return (Map.unions newExtras, targets) else throwM $ TargetParseException errs stack-1.5.1/src/Stack/BuildPlan.hs0000644000000000000000000013535413140560217015065 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE EmptyDataDecls #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} -- | Resolving a build plan for a set of packages in a given Stackage -- snapshot. module Stack.BuildPlan ( BuildPlanException (..) , BuildPlanCheck (..) , checkSnapBuildPlan , DepError(..) , DepErrors , gpdPackageDeps , gpdPackages , gpdPackageName , MiniBuildPlan(..) , MiniPackageInfo(..) , loadResolver , loadMiniBuildPlan , removeSrcPkgDefaultFlags , resolveBuildPlan , selectBestSnapshot , getToolMap , shadowMiniBuildPlan , showItems , showPackageFlags , parseCustomMiniBuildPlan , loadBuildPlan ) where import Control.Applicative import Control.Exception (assert) import Control.Monad (liftM, forM, unless) import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.Reader (MonadReader) import Control.Monad.State.Strict (State, execState, get, modify, put) import Crypto.Hash (hashWith, SHA256(..)) import Data.Aeson.Extended (WithJSONWarnings(..), logJSONWarnings) import Data.Store.VersionTagged import qualified Data.ByteArray as Mem (convert) import qualified Data.ByteString as S import qualified Data.ByteString.Base64.URL as B64URL import qualified Data.ByteString.Char8 as S8 import Data.Either (partitionEithers) import qualified Data.Foldable as F import qualified Data.HashSet as HashSet import Data.List (intercalate) import Data.List.NonEmpty (NonEmpty(..)) import qualified Data.List.NonEmpty as NonEmpty import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe (fromMaybe, mapMaybe, isNothing) import Data.Monoid import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import qualified Data.Traversable as Tr import Data.Typeable (Typeable) import Data.Yaml (decodeEither', decodeFileEither) import qualified Distribution.Package as C import Distribution.PackageDescription (GenericPackageDescription, flagDefault, flagManual, flagName, genPackageFlags, executables, exeName, library, libBuildInfo, buildable) import qualified Distribution.PackageDescription as C import Distribution.System (Platform) import Distribution.Text (display) import qualified Distribution.Version as C import Network.HTTP.Download import Path import Path.IO import Prelude -- Fix AMP warning import Stack.Constants import Stack.Fetch import Stack.Package import Stack.PackageIndex import Stack.Types.BuildPlan import Stack.Types.FlagName import Stack.Types.PackageIdentifier import Stack.Types.PackageIndex import Stack.Types.PackageName import Stack.Types.Version import Stack.Types.Config import Stack.Types.Urls import Stack.Types.Compiler import Stack.Types.Resolver import Stack.Types.StackT data BuildPlanException = UnknownPackages (Path Abs File) -- stack.yaml file (Map PackageName (Maybe Version, Set PackageName)) -- truly unknown (Map PackageName (Set PackageIdentifier)) -- shadowed | SnapshotNotFound SnapName | FilepathInDownloadedSnapshot T.Text | NeitherCompilerOrResolverSpecified T.Text deriving (Typeable) instance Exception BuildPlanException instance Show BuildPlanException where show (SnapshotNotFound snapName) = unlines [ "SnapshotNotFound " ++ snapName' , "Non existing resolver: " ++ snapName' ++ "." , "For a complete list of available snapshots see https://www.stackage.org/snapshots" ] where snapName' = show $ renderSnapName snapName show (UnknownPackages stackYaml unknown shadowed) = unlines $ unknown' ++ shadowed' where unknown' :: [String] unknown' | Map.null unknown = [] | otherwise = concat [ ["The following packages do not exist in the build plan:"] , map go (Map.toList unknown) , case mapMaybe goRecommend $ Map.toList unknown of [] -> [] rec -> ("Recommended action: modify the extra-deps field of " ++ toFilePath stackYaml ++ " to include the following:") : (rec ++ ["Note: further dependencies may need to be added"]) , case mapMaybe getNoKnown $ Map.toList unknown of [] -> [] noKnown -> [ "There are no known versions of the following packages:" , intercalate ", " $ map packageNameString noKnown ] ] where go (dep, (_, users)) | Set.null users = packageNameString dep go (dep, (_, users)) = concat [ packageNameString dep , " (used by " , intercalate ", " $ map packageNameString $ Set.toList users , ")" ] goRecommend (name, (Just version, _)) = Just $ "- " ++ packageIdentifierString (PackageIdentifier name version) goRecommend (_, (Nothing, _)) = Nothing getNoKnown (name, (Nothing, _)) = Just name getNoKnown (_, (Just _, _)) = Nothing shadowed' :: [String] shadowed' | Map.null shadowed = [] | otherwise = concat [ ["The following packages are shadowed by local packages:"] , map go (Map.toList shadowed) , ["Recommended action: modify the extra-deps field of " ++ toFilePath stackYaml ++ " to include the following:"] , extraDeps , ["Note: further dependencies may need to be added"] ] where go (dep, users) | Set.null users = packageNameString dep ++ " (internal stack error: this should never be null)" go (dep, users) = concat [ packageNameString dep , " (used by " , intercalate ", " $ map (packageNameString . packageIdentifierName) $ Set.toList users , ")" ] extraDeps = map (\ident -> "- " ++ packageIdentifierString ident) $ Set.toList $ Set.unions $ Map.elems shadowed show (FilepathInDownloadedSnapshot url) = unlines [ "Downloaded snapshot specified a 'resolver: { location: filepath }' " , "field, but filepaths are not allowed in downloaded snapshots.\n" , "Filepath specified: " ++ T.unpack url ] show (NeitherCompilerOrResolverSpecified url) = "Failed to load custom snapshot at " ++ T.unpack url ++ ", because no 'compiler' or 'resolver' is specified." -- | Determine the necessary packages to install to have the given set of -- packages available. -- -- This function will not provide test suite and benchmark dependencies. -- -- This may fail if a target package is not present in the @BuildPlan@. resolveBuildPlan :: (StackMiniM env m, HasBuildConfig env) => MiniBuildPlan -> (PackageName -> Bool) -- ^ is it shadowed by a local package? -> Map PackageName (Set PackageName) -- ^ required packages, and users of it -> m ( Map PackageName (Version, Map FlagName Bool) , Map PackageName (Set PackageName) ) resolveBuildPlan mbp isShadowed packages | Map.null (rsUnknown rs) && Map.null (rsShadowed rs) = return (rsToInstall rs, rsUsedBy rs) | otherwise = do bconfig <- view buildConfigL (caches, _gitShaCaches) <- getPackageCaches let maxVer = Map.fromListWith max $ map toTuple $ Map.keys caches unknown = flip Map.mapWithKey (rsUnknown rs) $ \ident x -> (Map.lookup ident maxVer, x) throwM $ UnknownPackages (bcStackYaml bconfig) unknown (rsShadowed rs) where rs = getDeps mbp isShadowed packages data ResolveState = ResolveState { rsVisited :: Map PackageName (Set PackageName) -- ^ set of shadowed dependencies , rsUnknown :: Map PackageName (Set PackageName) , rsShadowed :: Map PackageName (Set PackageIdentifier) , rsToInstall :: Map PackageName (Version, Map FlagName Bool) , rsUsedBy :: Map PackageName (Set PackageName) } toMiniBuildPlan :: (StackMiniM env m, HasConfig env) => CompilerVersion -- ^ Compiler version -> Map PackageName Version -- ^ cores -> Map PackageName (Version, Map FlagName Bool, [Text], Maybe GitSHA1) -- ^ non-core packages -> m MiniBuildPlan toMiniBuildPlan compilerVersion corePackages packages = do -- Determine the dependencies of all of the packages in the build plan. We -- handle core packages specially, because some of them will not be in the -- package index. For those, we allow missing packages to exist, and then -- remove those from the list of dependencies, since there's no way we'll -- ever reinstall them anyway. (cores, missingCores) <- addDeps True compilerVersion $ fmap (, Map.empty, [], Nothing) corePackages (extras, missing) <- addDeps False compilerVersion packages assert (Set.null missing) $ return MiniBuildPlan { mbpCompilerVersion = compilerVersion , mbpPackages = Map.unions [ fmap (removeMissingDeps (Map.keysSet cores)) cores , extras , Map.fromList $ map goCore $ Set.toList missingCores ] } where goCore (PackageIdentifier name version) = (name, MiniPackageInfo { mpiVersion = version , mpiFlags = Map.empty , mpiGhcOptions = [] , mpiPackageDeps = Set.empty , mpiToolDeps = Set.empty , mpiExes = Set.empty , mpiHasLibrary = True , mpiGitSHA1 = Nothing }) removeMissingDeps cores mpi = mpi { mpiPackageDeps = Set.intersection cores (mpiPackageDeps mpi) } -- | Add in the resolved dependencies from the package index addDeps :: (StackMiniM env m, HasConfig env) => Bool -- ^ allow missing -> CompilerVersion -- ^ Compiler version -> Map PackageName (Version, Map FlagName Bool, [Text], Maybe GitSHA1) -> m (Map PackageName MiniPackageInfo, Set PackageIdentifier) addDeps allowMissing compilerVersion toCalc = do platform <- view platformL (resolvedMap, missingIdents) <- if allowMissing then do (missingNames, missingIdents, m) <- resolvePackagesAllowMissing Nothing shaMap Set.empty assert (Set.null missingNames) $ return (m, missingIdents) else do m <- resolvePackages Nothing shaMap Set.empty return (m, Set.empty) let byIndex = Map.fromListWith (++) $ flip map resolvedMap $ \rp -> let (cache, ghcOptions, sha) = case Map.lookup (packageIdentifierName (rpIdent rp)) toCalc of Nothing -> (Map.empty, [], Nothing) Just (_, x, y, z) -> (x, y, z) in (indexName $ rpIndex rp, [(rp, (cache, ghcOptions, sha))]) res <- forM (Map.toList byIndex) $ \(indexName', pkgs) -> fmap Map.unions $ withCabalFiles indexName' pkgs $ \ident (flags, ghcOptions, mgitSha) cabalBS -> case readPackageUnresolvedBS (Right ident) cabalBS of Left e | allowedToSkip ident -> return Map.empty | otherwise -> throwM e Right (_warnings, gpd) -> do let packageConfig = PackageConfig { packageConfigEnableTests = False , packageConfigEnableBenchmarks = False , packageConfigFlags = flags , packageConfigGhcOptions = ghcOptions , packageConfigCompilerVersion = compilerVersion , packageConfigPlatform = platform } name = packageIdentifierName ident pd = resolvePackageDescription packageConfig gpd exes = Set.fromList $ map (ExeName . T.pack . exeName) $ executables pd notMe = Set.filter (/= name) . Map.keysSet return $ Map.singleton name MiniPackageInfo { mpiVersion = packageIdentifierVersion ident , mpiFlags = flags , mpiGhcOptions = ghcOptions , mpiPackageDeps = notMe $ packageDependencies pd , mpiToolDeps = Map.keysSet $ packageToolDependencies pd , mpiExes = exes , mpiHasLibrary = maybe False (buildable . libBuildInfo) (library pd) , mpiGitSHA1 = mgitSha } return (Map.unions res, missingIdents) where shaMap = Map.fromList $ map (\(n, (v, _f, _ghcOptions, gitsha)) -> (PackageIdentifier n v, gitsha)) $ Map.toList toCalc -- Michael Snoyman, 2017-07-31: -- -- This is a stop-gap measure to address a specific concern around -- the GHC 8.2.1 release. The current Stack version (1.5.0) will -- eagerly parse all cabal files mentioned in a snapshot, -- including global packages. Additionally, for the first time -- (AFAICT), GHC 8.2.1 is providing a package on Hackage with a -- ghc.cabal file, which requires the (not yet supported) Cabal -- 2.0 file format. To work around this, we're adding a special -- dispensation to ignore parse failures for this package. -- -- Master already does better by simply ignoring global -- information and looking things up in the database. We may want -- to consider going a step further and simply ignoring _all_ -- parse failures, or turning them into warnings, though I haven't -- considered the repercussions of that. allowedToSkip (PackageIdentifier name _) = name == $(mkPackageName "ghc") -- | Resolve all packages necessary to install for the needed packages. getDeps :: MiniBuildPlan -> (PackageName -> Bool) -- ^ is it shadowed by a local package? -> Map PackageName (Set PackageName) -> ResolveState getDeps mbp isShadowed packages = execState (mapM_ (uncurry goName) $ Map.toList packages) ResolveState { rsVisited = Map.empty , rsUnknown = Map.empty , rsShadowed = Map.empty , rsToInstall = Map.empty , rsUsedBy = Map.empty } where toolMap = getToolMap mbp -- | Returns a set of shadowed packages we depend on. goName :: PackageName -> Set PackageName -> State ResolveState (Set PackageName) goName name users = do -- Even though we could check rsVisited first and short-circuit things -- earlier, lookup in mbpPackages first so that we can produce more -- usable error information on missing dependencies rs <- get put rs { rsUsedBy = Map.insertWith Set.union name users $ rsUsedBy rs } case Map.lookup name $ mbpPackages mbp of Nothing -> do modify $ \rs' -> rs' { rsUnknown = Map.insertWith Set.union name users $ rsUnknown rs' } return Set.empty Just mpi -> case Map.lookup name (rsVisited rs) of Just shadowed -> return shadowed Nothing -> do put rs { rsVisited = Map.insert name Set.empty $ rsVisited rs } let depsForTools = Set.unions $ mapMaybe (flip Map.lookup toolMap) (Set.toList $ mpiToolDeps mpi) let deps = Set.filter (/= name) (mpiPackageDeps mpi <> depsForTools) shadowed <- fmap F.fold $ Tr.forM (Set.toList deps) $ \dep -> if isShadowed dep then do modify $ \rs' -> rs' { rsShadowed = Map.insertWith Set.union dep (Set.singleton $ PackageIdentifier name (mpiVersion mpi)) (rsShadowed rs') } return $ Set.singleton dep else do shadowed <- goName dep (Set.singleton name) let m = Map.fromSet (\_ -> Set.singleton $ PackageIdentifier name (mpiVersion mpi)) shadowed modify $ \rs' -> rs' { rsShadowed = Map.unionWith Set.union m $ rsShadowed rs' } return shadowed modify $ \rs' -> rs' { rsToInstall = Map.insert name (mpiVersion mpi, mpiFlags mpi) $ rsToInstall rs' , rsVisited = Map.insert name shadowed $ rsVisited rs' } return shadowed -- | Map from tool name to package providing it getToolMap :: MiniBuildPlan -> Map Text (Set PackageName) getToolMap mbp = Map.unionsWith Set.union {- We no longer do this, following discussion at: https://github.com/commercialhaskell/stack/issues/308#issuecomment-112076704 -- First grab all of the package names, for times where a build tool is -- identified by package name $ Map.fromList (map (packageNameByteString &&& Set.singleton) (Map.keys ps)) -} -- And then get all of the explicit executable names $ concatMap goPair (Map.toList ps) where ps = mbpPackages mbp goPair (pname, mpi) = map (flip Map.singleton (Set.singleton pname) . unExeName) $ Set.toList $ mpiExes mpi loadResolver :: (StackMiniM env m, HasConfig env, HasGHCVariant env) => Maybe (Path Abs File) -> Resolver -> m (MiniBuildPlan, LoadedResolver) loadResolver mconfigPath resolver = case resolver of ResolverSnapshot snap -> liftM (, ResolverSnapshot snap) $ loadMiniBuildPlan snap -- TODO(mgsloan): Not sure what this FIXME means -- FIXME instead of passing the stackYaml dir we should maintain -- the file URL in the custom resolver always relative to stackYaml. ResolverCustom name url -> do (mbp, hash) <- parseCustomMiniBuildPlan mconfigPath url return (mbp, ResolverCustomLoaded name url hash) ResolverCompiler compiler -> return ( MiniBuildPlan { mbpCompilerVersion = compiler , mbpPackages = mempty } , ResolverCompiler compiler ) -- | Load up a 'MiniBuildPlan', preferably from cache loadMiniBuildPlan :: (StackMiniM env m, HasConfig env, HasGHCVariant env) => SnapName -> m MiniBuildPlan loadMiniBuildPlan name = do path <- configMiniBuildPlanCache name $(versionedDecodeOrLoad miniBuildPlanVC) path $ liftM buildPlanFixes $ do bp <- loadBuildPlan name toMiniBuildPlan (siCompilerVersion $ bpSystemInfo bp) (siCorePackages $ bpSystemInfo bp) (goPP <$> bpPackages bp) where goPP pp = ( ppVersion pp , pcFlagOverrides $ ppConstraints pp -- TODO: store ghc options in BuildPlan? , [] , ppCabalFileInfo pp >>= fmap (GitSHA1 . encodeUtf8) . Map.lookup "GitSHA1" . cfiHashes ) -- | Some hard-coded fixes for build plans, hopefully to be irrelevant over -- time. buildPlanFixes :: MiniBuildPlan -> MiniBuildPlan buildPlanFixes mbp = mbp { mbpPackages = Map.fromList $ map go $ Map.toList $ mbpPackages mbp } where go (name, mpi) = (name, mpi { mpiFlags = goF (packageNameString name) (mpiFlags mpi) }) goF "persistent-sqlite" = Map.insert $(mkFlagName "systemlib") False goF "yaml" = Map.insert $(mkFlagName "system-libyaml") False goF _ = id -- | Load the 'BuildPlan' for the given snapshot. Will load from a local copy -- if available, otherwise downloading from Github. loadBuildPlan :: (StackMiniM env m, HasConfig env) => SnapName -> m BuildPlan loadBuildPlan name = do stackage <- view stackRootL file' <- parseRelFile $ T.unpack file let fp = buildPlanDir stackage file' $logDebug $ "Decoding build plan from: " <> T.pack (toFilePath fp) eres <- liftIO $ decodeFileEither $ toFilePath fp case eres of Right bp -> return bp Left e -> do $logDebug $ "Decoding build plan from file failed: " <> T.pack (show e) ensureDir (parent fp) url <- buildBuildPlanUrl name file req <- parseRequest $ T.unpack url $logSticky $ "Downloading " <> renderSnapName name <> " build plan ..." $logDebug $ "Downloading build plan from: " <> url _ <- redownload req fp $logStickyDone $ "Downloaded " <> renderSnapName name <> " build plan." liftIO (decodeFileEither $ toFilePath fp) >>= either throwM return where file = renderSnapName name <> ".yaml" buildBuildPlanUrl :: (MonadReader env m, HasConfig env) => SnapName -> Text -> m Text buildBuildPlanUrl name file = do urls <- view $ configL.to configUrls return $ case name of LTS _ _ -> urlsLtsBuildPlans urls <> "/" <> file Nightly _ -> urlsNightlyBuildPlans urls <> "/" <> file gpdPackages :: [GenericPackageDescription] -> Map PackageName Version gpdPackages gpds = Map.fromList $ map (fromCabalIdent . C.package . C.packageDescription) gpds where fromCabalIdent (C.PackageIdentifier name version) = (fromCabalPackageName name, fromCabalVersion version) gpdPackageName :: GenericPackageDescription -> PackageName gpdPackageName = fromCabalPackageName . C.pkgName . C.package . C.packageDescription gpdPackageDeps :: GenericPackageDescription -> CompilerVersion -> Platform -> Map FlagName Bool -> Map PackageName VersionRange gpdPackageDeps gpd cv platform flags = Map.filterWithKey (const . (/= name)) (packageDependencies pkgDesc) where name = gpdPackageName gpd pkgDesc = resolvePackageDescription pkgConfig gpd pkgConfig = PackageConfig { packageConfigEnableTests = True , packageConfigEnableBenchmarks = True , packageConfigFlags = flags , packageConfigGhcOptions = [] , packageConfigCompilerVersion = cv , packageConfigPlatform = platform } -- Remove any src package flags having default values -- Remove any package entries with no flags set removeSrcPkgDefaultFlags :: [C.GenericPackageDescription] -> Map PackageName (Map FlagName Bool) -> Map PackageName (Map FlagName Bool) removeSrcPkgDefaultFlags gpds flags = let defaults = Map.unions (map gpdDefaultFlags gpds) flags' = Map.differenceWith removeSame flags defaults in Map.filter (not . Map.null) flags' where removeSame f1 f2 = let diff v v' = if v == v' then Nothing else Just v in Just $ Map.differenceWith diff f1 f2 gpdDefaultFlags gpd = let tuples = map getDefault (C.genPackageFlags gpd) in Map.singleton (gpdPackageName gpd) (Map.fromList tuples) flagName' = fromCabalFlagName . C.flagName getDefault f | C.flagDefault f = (flagName' f, True) | otherwise = (flagName' f, False) -- | Find the set of @FlagName@s necessary to get the given -- @GenericPackageDescription@ to compile against the given @BuildPlan@. Will -- only modify non-manual flags, and will prefer default values for flags. -- Returns the plan which produces least number of dep errors selectPackageBuildPlan :: Platform -> CompilerVersion -> Map PackageName Version -> GenericPackageDescription -> (Map PackageName (Map FlagName Bool), DepErrors) selectPackageBuildPlan platform compiler pool gpd = (selectPlan . limitSearchSpace . NonEmpty.map makePlan) flagCombinations where selectPlan :: NonEmpty (a, DepErrors) -> (a, DepErrors) selectPlan = F.foldr1 fewerErrors where fewerErrors p1 p2 | nErrors p1 == 0 = p1 | nErrors p1 <= nErrors p2 = p1 | otherwise = p2 where nErrors = Map.size . snd -- Avoid exponential complexity in flag combinations making us sad pandas. -- See: https://github.com/commercialhaskell/stack/issues/543 limitSearchSpace :: NonEmpty a -> NonEmpty a limitSearchSpace (x :| xs) = x :| take (maxFlagCombinations - 1) xs where maxFlagCombinations = 128 makePlan :: [(FlagName, Bool)] -> (Map PackageName (Map FlagName Bool), DepErrors) makePlan flags = checkPackageBuildPlan platform compiler pool (Map.fromList flags) gpd flagCombinations :: NonEmpty [(FlagName, Bool)] flagCombinations = mapM getOptions (genPackageFlags gpd) where getOptions :: C.Flag -> NonEmpty (FlagName, Bool) getOptions f | flagManual f = (fname, flagDefault f) :| [] | flagDefault f = (fname, True) :| [(fname, False)] | otherwise = (fname, False) :| [(fname, True)] where fname = (fromCabalFlagName . flagName) f -- | Check whether with the given set of flags a package's dependency -- constraints can be satisfied against a given build plan or pool of packages. checkPackageBuildPlan :: Platform -> CompilerVersion -> Map PackageName Version -> Map FlagName Bool -> GenericPackageDescription -> (Map PackageName (Map FlagName Bool), DepErrors) checkPackageBuildPlan platform compiler pool flags gpd = (Map.singleton pkg flags, errs) where pkg = gpdPackageName gpd errs = checkPackageDeps pkg constraints pool constraints = gpdPackageDeps gpd compiler platform flags -- | Checks if the given package dependencies can be satisfied by the given set -- of packages. Will fail if a package is either missing or has a version -- outside of the version range. checkPackageDeps :: PackageName -- ^ package using dependencies, for constructing DepErrors -> Map PackageName VersionRange -- ^ dependency constraints -> Map PackageName Version -- ^ Available package pool or index -> DepErrors checkPackageDeps myName deps packages = Map.unionsWith combineDepError $ map go $ Map.toList deps where go :: (PackageName, VersionRange) -> DepErrors go (name, range) = case Map.lookup name packages of Nothing -> Map.singleton name DepError { deVersion = Nothing , deNeededBy = Map.singleton myName range } Just v | withinRange v range -> Map.empty | otherwise -> Map.singleton name DepError { deVersion = Just v , deNeededBy = Map.singleton myName range } type DepErrors = Map PackageName DepError data DepError = DepError { deVersion :: !(Maybe Version) , deNeededBy :: !(Map PackageName VersionRange) } deriving Show -- | Combine two 'DepError's for the same 'Version'. combineDepError :: DepError -> DepError -> DepError combineDepError (DepError a x) (DepError b y) = assert (a == b) $ DepError a (Map.unionWith C.intersectVersionRanges x y) -- | Given a bundle of packages (a list of @GenericPackageDescriptions@'s) to -- build and an available package pool (snapshot) check whether the bundle's -- dependencies can be satisfied. If flags is passed as Nothing flag settings -- will be chosen automatically. checkBundleBuildPlan :: Platform -> CompilerVersion -> Map PackageName Version -> Maybe (Map PackageName (Map FlagName Bool)) -> [GenericPackageDescription] -> (Map PackageName (Map FlagName Bool), DepErrors) checkBundleBuildPlan platform compiler pool flags gpds = (Map.unionsWith dupError (map fst plans) , Map.unionsWith combineDepError (map snd plans)) where plans = map (pkgPlan flags) gpds pkgPlan Nothing gpd = selectPackageBuildPlan platform compiler pool' gpd pkgPlan (Just f) gpd = checkPackageBuildPlan platform compiler pool' (flags' f gpd) gpd flags' f gpd = fromMaybe Map.empty (Map.lookup (gpdPackageName gpd) f) pool' = Map.union (gpdPackages gpds) pool dupError _ _ = error "Bug: Duplicate packages are not expected here" data BuildPlanCheck = BuildPlanCheckOk (Map PackageName (Map FlagName Bool)) | BuildPlanCheckPartial (Map PackageName (Map FlagName Bool)) DepErrors | BuildPlanCheckFail (Map PackageName (Map FlagName Bool)) DepErrors CompilerVersion -- | Compare 'BuildPlanCheck', where GT means a better plan. compareBuildPlanCheck :: BuildPlanCheck -> BuildPlanCheck -> Ordering compareBuildPlanCheck (BuildPlanCheckPartial _ e1) (BuildPlanCheckPartial _ e2) = -- Note: order of comparison flipped, since it's better to have fewer errors. compare (Map.size e2) (Map.size e1) compareBuildPlanCheck (BuildPlanCheckFail _ e1 _) (BuildPlanCheckFail _ e2 _) = let numUserPkgs e = Map.size $ Map.unions (Map.elems (fmap deNeededBy e)) in compare (numUserPkgs e2) (numUserPkgs e1) compareBuildPlanCheck BuildPlanCheckOk{} BuildPlanCheckOk{} = EQ compareBuildPlanCheck BuildPlanCheckOk{} BuildPlanCheckPartial{} = GT compareBuildPlanCheck BuildPlanCheckOk{} BuildPlanCheckFail{} = GT compareBuildPlanCheck BuildPlanCheckPartial{} BuildPlanCheckFail{} = GT compareBuildPlanCheck _ _ = LT instance Show BuildPlanCheck where show BuildPlanCheckOk {} = "" show (BuildPlanCheckPartial f e) = T.unpack $ showDepErrors f e show (BuildPlanCheckFail f e c) = T.unpack $ showCompilerErrors f e c -- | Check a set of 'GenericPackageDescription's and a set of flags against a -- given snapshot. Returns how well the snapshot satisfies the dependencies of -- the packages. checkSnapBuildPlan :: (StackM env m, HasConfig env, HasGHCVariant env) => [GenericPackageDescription] -> Maybe (Map PackageName (Map FlagName Bool)) -> SnapName -> m BuildPlanCheck checkSnapBuildPlan gpds flags snap = do platform <- view platformL mbp <- loadMiniBuildPlan snap let compiler = mbpCompilerVersion mbp snapPkgs = mpiVersion <$> mbpPackages mbp (f, errs) = checkBundleBuildPlan platform compiler snapPkgs flags gpds cerrs = compilerErrors compiler errs if Map.null errs then return $ BuildPlanCheckOk f else if Map.null cerrs then do return $ BuildPlanCheckPartial f errs else return $ BuildPlanCheckFail f cerrs compiler where compilerErrors compiler errs | whichCompiler compiler == Ghc = ghcErrors errs -- FIXME not sure how to handle ghcjs boot packages | otherwise = Map.empty isGhcWiredIn p _ = p `HashSet.member` wiredInPackages ghcErrors = Map.filterWithKey isGhcWiredIn -- | Find a snapshot and set of flags that is compatible with and matches as -- best as possible with the given 'GenericPackageDescription's. selectBestSnapshot :: (StackM env m, HasConfig env, HasGHCVariant env) => [GenericPackageDescription] -> NonEmpty SnapName -> m (SnapName, BuildPlanCheck) selectBestSnapshot gpds snaps = do $logInfo $ "Selecting the best among " <> T.pack (show (NonEmpty.length snaps)) <> " snapshots...\n" F.foldr1 go (NonEmpty.map getResult snaps) where go mold mnew = do old@(_snap, bpc) <- mold case bpc of BuildPlanCheckOk {} -> return old _ -> fmap (betterSnap old) mnew getResult snap = do result <- checkSnapBuildPlan gpds Nothing snap reportResult result snap return (snap, result) betterSnap (s1, r1) (s2, r2) | compareBuildPlanCheck r1 r2 /= LT = (s1, r1) | otherwise = (s2, r2) reportResult BuildPlanCheckOk {} snap = do $logInfo $ "* Matches " <> renderSnapName snap $logInfo "" reportResult r@BuildPlanCheckPartial {} snap = do $logWarn $ "* Partially matches " <> renderSnapName snap $logWarn $ indent $ T.pack $ show r reportResult r@BuildPlanCheckFail {} snap = do $logWarn $ "* Rejected " <> renderSnapName snap $logWarn $ indent $ T.pack $ show r indent t = T.unlines $ fmap (" " <>) (T.lines t) showItems :: Show a => [a] -> Text showItems items = T.concat (map formatItem items) where formatItem item = T.concat [ " - " , T.pack $ show item , "\n" ] showPackageFlags :: PackageName -> Map FlagName Bool -> Text showPackageFlags pkg fl = if not $ Map.null fl then T.concat [ " - " , T.pack $ packageNameString pkg , ": " , T.pack $ intercalate ", " $ map formatFlags (Map.toList fl) , "\n" ] else "" where formatFlags (f, v) = show f ++ " = " ++ show v showMapPackages :: Map PackageName a -> Text showMapPackages mp = showItems $ Map.keys mp showCompilerErrors :: Map PackageName (Map FlagName Bool) -> DepErrors -> CompilerVersion -> Text showCompilerErrors flags errs compiler = T.concat [ compilerVersionText compiler , " cannot be used for these packages:\n" , showMapPackages $ Map.unions (Map.elems (fmap deNeededBy errs)) , showDepErrors flags errs -- TODO only in debug mode ] showDepErrors :: Map PackageName (Map FlagName Bool) -> DepErrors -> Text showDepErrors flags errs = T.concat [ T.concat $ map formatError (Map.toList errs) , if T.null flagVals then "" else "Using package flags:\n" <> flagVals ] where formatError (depName, DepError mversion neededBy) = T.concat [ showDepVersion depName mversion , T.concat (map showRequirement (Map.toList neededBy)) ] showDepVersion depName mversion = T.concat [ T.pack $ packageNameString depName , case mversion of Nothing -> " not found" Just version -> T.concat [ " version " , T.pack $ versionString version , " found" ] , "\n" ] showRequirement (user, range) = T.concat [ " - " , T.pack $ packageNameString user , " requires " , T.pack $ display range , "\n" ] flagVals = T.concat (map showFlags userPkgs) userPkgs = Map.keys $ Map.unions (Map.elems (fmap deNeededBy errs)) showFlags pkg = maybe "" (showPackageFlags pkg) (Map.lookup pkg flags) -- | Given a set of packages to shadow, this removes them, and any -- packages that transitively depend on them, from the 'MiniBuildPlan'. -- The 'Map' result yields all of the packages that were downstream of -- the shadowed packages. It does not include the shadowed packages. shadowMiniBuildPlan :: MiniBuildPlan -> Set PackageName -> (MiniBuildPlan, Map PackageName MiniPackageInfo) shadowMiniBuildPlan (MiniBuildPlan cv pkgs0) shadowed = (MiniBuildPlan cv (Map.fromList met), Map.fromList unmet) where pkgs1 = Map.difference pkgs0 $ Map.fromSet (const ()) shadowed depsMet = flip execState Map.empty $ mapM_ (check Set.empty) (Map.keys pkgs1) check visited name | name `Set.member` visited = error $ "shadowMiniBuildPlan: cycle detected, your MiniBuildPlan is broken: " ++ show (visited, name) | otherwise = do m <- get case Map.lookup name m of Just x -> return x Nothing -> case Map.lookup name pkgs1 of Nothing | name `Set.member` shadowed -> return False -- In this case, we have to assume that we're -- constructing a build plan on a different OS or -- architecture, and therefore different packages -- are being chosen. The common example of this is -- the Win32 package. | otherwise -> return True Just mpi -> do let visited' = Set.insert name visited ress <- mapM (check visited') (Set.toList $ mpiPackageDeps mpi) let res = and ress modify $ \m' -> Map.insert name res m' return res (met, unmet) = partitionEithers $ map toEither $ Map.toList pkgs1 toEither pair@(name, _) = wrapper pair where wrapper = case Map.lookup name depsMet of Just True -> Left Just False -> Right Nothing -> assert False Right -- This works differently for snapshots fetched from URL and those -- fetched from file: -- -- 1) If downloading the snapshot from a URL, assume the fetched data is -- immutable. Hash the URL in order to determine the location of the -- cached download. The file contents of the snapshot determines the -- hash for looking up cached MBP. -- -- 2) If loading the snapshot from a file, load all of the involved -- snapshot files. The hash used to determine the cached MBP is the hash -- of the concatenation of the parent's hash with the snapshot contents. -- -- Why this difference? We want to make it easy to simply edit snapshots -- in the filesystem, but we want caching for remote snapshots. In order -- to avoid reparsing / reloading all the yaml for remote snapshots, we -- need a different hash system. -- TODO: This could probably be more efficient if it first merged the -- custom snapshots, and then applied them to the MBP. It is nice to -- apply directly, because then we have the guarantee that it's -- semantically identical to snapshot extension. If this optimization is -- implemented, note that the direct Monoid for CustomSnapshot is not -- correct. Crucially, if a package is present in the snapshot, its -- flags and ghc-options are not based on settings from prior snapshots. -- TODO: This semantics should be discussed / documented more. -- TODO: allow a hash check in the resolver. This adds safety / -- correctness, allowing you to ensure that you are indeed getting the -- right custom snapshot. -- TODO: Allow custom plan to specify a name. parseCustomMiniBuildPlan :: (StackMiniM env m, HasConfig env, HasGHCVariant env) => Maybe (Path Abs File) -- ^ Root directory for when url is a filepath -> T.Text -> m (MiniBuildPlan, SnapshotHash) parseCustomMiniBuildPlan mconfigPath0 url0 = do $logDebug $ "Loading " <> url0 <> " build plan" case parseUrlThrow $ T.unpack url0 of Just req -> downloadCustom url0 req Nothing -> case mconfigPath0 of Nothing -> throwM $ FilepathInDownloadedSnapshot url0 Just configPath -> do (getMbp, hash) <- readCustom configPath url0 mbp <- getMbp -- NOTE: We make the choice of only writing a cache -- file for the full MBP, not the intermediate ones. -- This isn't necessarily the best choice if we want -- to share work extended snapshots. I think only -- writing this one is more efficient for common -- cases. binaryPath <- getBinaryPath hash alreadyCached <- doesFileExist binaryPath unless alreadyCached $ $(versionedEncodeFile miniBuildPlanVC) binaryPath mbp return (mbp, hash) where downloadCustom url req = do let urlHash = S8.unpack $ trimmedSnapshotHash $ doHash $ encodeUtf8 url hashFP <- parseRelFile $ urlHash ++ ".yaml" customPlanDir <- getCustomPlanDir let cacheFP = customPlanDir $(mkRelDir "yaml") hashFP _ <- download req cacheFP yamlBS <- liftIO $ S.readFile $ toFilePath cacheFP let yamlHash = doHash yamlBS binaryPath <- getBinaryPath yamlHash liftM (, yamlHash) $ $(versionedDecodeOrLoad miniBuildPlanVC) binaryPath $ do (cs, mresolver) <- decodeYaml yamlBS parentMbp <- case (csCompilerVersion cs, mresolver) of (Nothing, Nothing) -> throwM (NeitherCompilerOrResolverSpecified url) (Just cv, Nothing) -> return (compilerBuildPlan cv) -- NOTE: ignoring the parent's hash, even though -- there could be one. URL snapshot's hash are -- determined just from their contents. (_, Just resolver) -> liftM fst (loadResolver Nothing resolver) applyCustomSnapshot cs parentMbp readCustom configPath path = do yamlFP <- resolveFile (parent configPath) (T.unpack $ fromMaybe path $ T.stripPrefix "file://" path <|> T.stripPrefix "file:" path) yamlBS <- liftIO $ S.readFile $ toFilePath yamlFP (cs, mresolver) <- decodeYaml yamlBS (getMbp, hash) <- case mresolver of Just (ResolverCustom _ url ) -> case parseUrlThrow $ T.unpack url of Just req -> do let getMbp = do -- Ignore custom hash, under the -- assumption that the URL is sufficient -- for identity. (mbp, _) <- downloadCustom url req return mbp return (getMbp, doHash yamlBS) Nothing -> do (getMbp0, SnapshotHash hash0) <- readCustom yamlFP url let hash = doHash (hash0 <> yamlBS) getMbp = do binaryPath <- getBinaryPath hash -- Idea here is to not waste time -- writing out intermediate cache files, -- but check for them. exists <- doesFileExist binaryPath if exists then do eres <- $(versionedDecodeFile miniBuildPlanVC) binaryPath case eres of Just mbp -> return mbp -- Invalid format cache file, remove. Nothing -> do removeFile binaryPath getMbp0 else getMbp0 return (getMbp, hash) Just resolver -> do -- NOTE: in the cases where we don't have a hash, the -- normal resolver name is enough. Since this name is -- part of the yaml file, it ends up in our hash. let hash = doHash yamlBS getMbp = do (mbp, resolver') <- loadResolver (Just configPath) resolver let mhash = customResolverHash resolver' assert (isNothing mhash) (return mbp) return (getMbp, hash) Nothing -> do case csCompilerVersion cs of Nothing -> throwM (NeitherCompilerOrResolverSpecified path) Just cv -> do let hash = doHash yamlBS getMbp = return (compilerBuildPlan cv) return (getMbp, hash) return (applyCustomSnapshot cs =<< getMbp, hash) getBinaryPath hash = do binaryFilename <- parseRelFile $ S8.unpack (trimmedSnapshotHash hash) ++ ".bin" customPlanDir <- getCustomPlanDir return $ customPlanDir $(mkRelDir "bin") binaryFilename decodeYaml yamlBS = do WithJSONWarnings res warnings <- either (throwM . ParseCustomSnapshotException url0) return $ decodeEither' yamlBS logJSONWarnings (T.unpack url0) warnings return res compilerBuildPlan cv = MiniBuildPlan { mbpCompilerVersion = cv , mbpPackages = mempty } getCustomPlanDir = do root <- view stackRootL return $ root $(mkRelDir "custom-plan") doHash = SnapshotHash . B64URL.encode . Mem.convert . hashWith SHA256 applyCustomSnapshot :: (StackMiniM env m, HasConfig env) => CustomSnapshot -> MiniBuildPlan -> m MiniBuildPlan applyCustomSnapshot cs mbp0 = do let CustomSnapshot mcompilerVersion packages dropPackages (PackageFlags flags) ghcOptions = cs addFlagsAndOpts :: PackageIdentifier -> (PackageName, (Version, Map FlagName Bool, [Text], Maybe GitSHA1)) addFlagsAndOpts (PackageIdentifier name ver) = ( name , ( ver , Map.findWithDefault Map.empty name flags -- NOTE: similar to 'allGhcOptions' in Stack.Types.Build , ghcOptionsFor name ghcOptions -- we add a Nothing since we don't yet collect Git SHAs for custom snapshots , Nothing ) ) packageMap = Map.fromList $ map addFlagsAndOpts $ Set.toList packages cv = fromMaybe (mbpCompilerVersion mbp0) mcompilerVersion packages0 = mbpPackages mbp0 `Map.difference` Map.fromSet (const ()) dropPackages mbp1 <- toMiniBuildPlan cv mempty packageMap return MiniBuildPlan { mbpCompilerVersion = cv , mbpPackages = Map.union (mbpPackages mbp1) packages0 } stack-1.5.1/src/Stack/Clean.hs0000644000000000000000000000544313135652051014232 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleContexts #-} -- | Clean a project. module Stack.Clean (clean ,CleanOpts(..) ,StackCleanException(..) ) where import Control.Exception (Exception) import Control.Monad.Catch (throwM) import Data.Foldable (forM_) import Data.List ((\\),intercalate) import qualified Data.Map.Strict as Map import Data.Maybe (mapMaybe) import Data.Typeable (Typeable) import Path (Path, Abs, Dir) import Path.IO (ignoringAbsence, removeDirRecur) import Stack.Build.Source (getLocalPackageViews) import Stack.Build.Target (LocalPackageView(..)) import Stack.Config (getLocalPackages) import Stack.Constants (distDirFromDir, workDirFromDir) import Stack.Types.PackageName import Stack.Types.Config import Stack.Types.StackT -- | Deletes build artifacts in the current project. -- -- Throws 'StackCleanException'. clean :: (StackM env m, HasEnvConfig env) => CleanOpts -> m () clean cleanOpts = do dirs <- dirsToDelete cleanOpts forM_ dirs (ignoringAbsence . removeDirRecur) dirsToDelete :: (StackM env m, HasEnvConfig env) => CleanOpts -> m [Path Abs Dir] dirsToDelete cleanOpts = do packages <- getLocalPackages case cleanOpts of CleanShallow [] -> -- Filter out packages listed as extra-deps mapM distDirFromDir . Map.keys . Map.filter (== False) $ packages CleanShallow targets -> do localPkgViews <- getLocalPackageViews let localPkgNames = Map.keys localPkgViews getPkgDir pkgName = fmap (lpvRoot . fst) (Map.lookup pkgName localPkgViews) case targets \\ localPkgNames of [] -> mapM distDirFromDir (mapMaybe getPkgDir targets) xs -> throwM (NonLocalPackages xs) CleanFull -> do pkgWorkDirs <- mapM workDirFromDir (Map.keys packages) projectWorkDir <- getProjectWorkDir return (projectWorkDir : pkgWorkDirs) -- | Options for @stack clean@. data CleanOpts = CleanShallow [PackageName] -- ^ Delete the "dist directories" as defined in 'Stack.Constants.distRelativeDir' -- for the given local packages. If no packages are given, all project packages -- should be cleaned. | CleanFull -- ^ Delete all work directories in the project. -- | Exceptions during cleanup. newtype StackCleanException = NonLocalPackages [PackageName] deriving (Typeable) instance Show StackCleanException where show (NonLocalPackages pkgs) = "The following packages are not part of this project: " ++ intercalate ", " (map show pkgs) instance Exception StackCleanException stack-1.5.1/src/Stack/Config.hs0000644000000000000000000013353413135652051014420 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE TupleSections #-} -- | The general Stack configuration that starts everything off. This should -- be smart to falback if there is no stack.yaml, instead relying on -- whatever files are available. -- -- If there is no stack.yaml, and there is a cabal.config, we -- read in those constraints, and if there's a cabal.sandbox.config, -- we read any constraints from there and also find the package -- database from there, etc. And if there's nothing, we should -- probably default to behaving like cabal, possibly with spitting out -- a warning that "you should run `stk init` to make things better". module Stack.Config (MiniConfig ,loadConfig ,loadConfigMaybeProject ,loadMiniConfig ,loadConfigYaml ,packagesParser ,getLocalPackages ,resolvePackageEntry ,getImplicitGlobalProjectDir ,getStackYaml ,getSnapshots ,makeConcreteResolver ,checkOwnership ,getInContainer ,getInNixShell ,defaultConfigYaml ,getProjectConfig ,LocalConfigStatus(..) ,removePathFromPackageEntry ) where import qualified Codec.Archive.Tar as Tar import qualified Codec.Archive.Zip as Zip import qualified Codec.Compression.GZip as GZip import Control.Applicative import Control.Arrow ((***)) import Control.Exception (assert) import Control.Monad (liftM, unless, when, filterM) import Control.Monad.Catch (MonadThrow, MonadCatch, catchAll, throwM, catch) import Control.Monad.Extra (firstJustM) import Control.Monad.IO.Class import Control.Monad.Logger hiding (Loc) import Control.Monad.Reader (ask, runReaderT) import Crypto.Hash (hashWith, SHA256(..)) import Data.Aeson.Extended import qualified Data.ByteArray as Mem (convert) import qualified Data.ByteString as S import qualified Data.ByteString.Base64.URL as B64URL import qualified Data.ByteString.Lazy as L import Data.Foldable (forM_) import Data.IORef import qualified Data.IntMap as IntMap import qualified Data.Map as Map import Data.Maybe import Data.Monoid.Extra import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8, decodeUtf8) import qualified Data.Yaml as Yaml import Distribution.System (OS (..), Platform (..), buildPlatform, Arch(OtherArch)) import qualified Distribution.Text import Distribution.Version (simplifyVersionRange) import GHC.Conc (getNumProcessors) import Lens.Micro (lens) import Network.HTTP.Client (parseUrlThrow) import Network.HTTP.Download (download) import Network.HTTP.Simple (httpJSON, getResponseBody) import Options.Applicative (Parser, strOption, long, help) import Path import Path.Extra (toFilePathNoTrailingSep) import Path.Find (findInParents) import Path.IO import qualified Paths_stack as Meta import Stack.BuildPlan import Stack.Config.Build import Stack.Config.Docker import Stack.Config.Nix import Stack.Config.Urls import Stack.Constants import qualified Stack.Image as Image import Stack.Types.BuildPlan import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.Docker import Stack.Types.Internal import Stack.Types.Nix import Stack.Types.PackageIndex (IndexType (ITHackageSecurity), HackageSecurity (..)) import Stack.Types.Resolver import Stack.Types.StackT import Stack.Types.StringError import Stack.Types.Urls import Stack.Types.Version import System.Environment import System.IO import System.PosixCompat.Files (fileOwner, getFileStatus) import System.PosixCompat.User (getEffectiveUserID) import System.Process.Read import System.Process.Run -- | If deprecated path exists, use it and print a warning. -- Otherwise, return the new path. tryDeprecatedPath :: (MonadIO m, MonadLogger m) => Maybe T.Text -- ^ Description of file for warning (if Nothing, no deprecation warning is displayed) -> (Path Abs a -> m Bool) -- ^ Test for existence -> Path Abs a -- ^ New path -> Path Abs a -- ^ Deprecated path -> m (Path Abs a, Bool) -- ^ (Path to use, whether it already exists) tryDeprecatedPath mWarningDesc exists new old = do newExists <- exists new if newExists then return (new, True) else do oldExists <- exists old if oldExists then do case mWarningDesc of Nothing -> return () Just desc -> $logWarn $ T.concat [ "Warning: Location of ", desc, " at '" , T.pack (toFilePath old) , "' is deprecated; rename it to '" , T.pack (toFilePath new) , "' instead" ] return (old, True) else return (new, False) -- | Get the location of the implicit global project directory. -- If the directory already exists at the deprecated location, its location is returned. -- Otherwise, the new location is returned. getImplicitGlobalProjectDir :: (MonadIO m, MonadLogger m) => Config -> m (Path Abs Dir) getImplicitGlobalProjectDir config = --TEST no warning printed liftM fst $ tryDeprecatedPath Nothing doesDirExist (implicitGlobalProjectDir stackRoot) (implicitGlobalProjectDirDeprecated stackRoot) where stackRoot = configStackRoot config -- | This is slightly more expensive than @'asks' ('bcStackYaml' '.' 'getBuildConfig')@ -- and should only be used when no 'BuildConfig' is at hand. getStackYaml :: (StackMiniM env m, HasConfig env) => m (Path Abs File) getStackYaml = do config <- view configL case configMaybeProject config of Just (_project, stackYaml) -> return stackYaml Nothing -> liftM ( stackDotYaml) (getImplicitGlobalProjectDir config) -- | Download the 'Snapshots' value from stackage.org. getSnapshots :: (StackMiniM env m, HasConfig env) => m Snapshots getSnapshots = do latestUrlText <- askLatestSnapshotUrl latestUrl <- parseUrlThrow (T.unpack latestUrlText) $logDebug $ "Downloading snapshot versions file from " <> latestUrlText result <- httpJSON latestUrl $logDebug $ "Done downloading and parsing snapshot versions file" return $ getResponseBody result -- | Turn an 'AbstractResolver' into a 'Resolver'. makeConcreteResolver :: (StackMiniM env m, HasConfig env) => AbstractResolver -> m Resolver makeConcreteResolver (ARResolver r) = return r makeConcreteResolver ar = do snapshots <- getSnapshots r <- case ar of ARResolver r -> assert False $ return r ARGlobal -> do config <- view configL implicitGlobalDir <- getImplicitGlobalProjectDir config let fp = implicitGlobalDir stackDotYaml ProjectAndConfigMonoid project _ <- loadConfigYaml (parseProjectAndConfigMonoid (parent fp)) fp return $ projectResolver project ARLatestNightly -> return $ ResolverSnapshot $ Nightly $ snapshotsNightly snapshots ARLatestLTSMajor x -> case IntMap.lookup x $ snapshotsLts snapshots of Nothing -> errorString $ "No LTS release found with major version " ++ show x Just y -> return $ ResolverSnapshot $ LTS x y ARLatestLTS | IntMap.null $ snapshotsLts snapshots -> errorString "No LTS releases found" | otherwise -> let (x, y) = IntMap.findMax $ snapshotsLts snapshots in return $ ResolverSnapshot $ LTS x y $logInfo $ "Selected resolver: " <> resolverName r return r -- | Get the latest snapshot resolver available. getLatestResolver :: (StackMiniM env m, HasConfig env) => m Resolver getLatestResolver = do snapshots <- getSnapshots let mlts = do (x,y) <- listToMaybe (reverse (IntMap.toList (snapshotsLts snapshots))) return (LTS x y) snap = fromMaybe (Nightly (snapshotsNightly snapshots)) mlts return (ResolverSnapshot snap) -- | Create a 'Config' value when we're not using any local -- configuration files (e.g., the script command) configNoLocalConfig :: (MonadLogger m, MonadIO m, MonadCatch m) => Path Abs Dir -- ^ stack root -> Maybe AbstractResolver -> ConfigMonoid -> m Config configNoLocalConfig _ Nothing _ = throwM NoResolverWhenUsingNoLocalConfig configNoLocalConfig stackRoot (Just resolver) configMonoid = do userConfigPath <- getFakeConfigPath stackRoot resolver configFromConfigMonoid stackRoot userConfigPath False (Just resolver) Nothing -- project configMonoid -- Interprets ConfigMonoid options. configFromConfigMonoid :: (MonadLogger m, MonadIO m, MonadCatch m) => Path Abs Dir -- ^ stack root, e.g. ~/.stack -> Path Abs File -- ^ user config file path, e.g. ~/.stack/config.yaml -> Bool -- ^ allow locals? -> Maybe AbstractResolver -> Maybe (Project, Path Abs File) -> ConfigMonoid -> m Config configFromConfigMonoid configStackRoot configUserConfigPath configAllowLocals mresolver mproject ConfigMonoid{..} = do -- If --stack-work is passed, prefer it. Otherwise, if STACK_WORK -- is set, use that. If neither, use the default ".stack-work" mstackWorkEnv <- liftIO $ lookupEnv stackWorkEnvVar configWorkDir0 <- maybe (return $(mkRelDir ".stack-work")) parseRelDir mstackWorkEnv let configWorkDir = fromFirst configWorkDir0 configMonoidWorkDir -- This code is to handle the deprecation of latest-snapshot-url configUrls <- case (getFirst configMonoidLatestSnapshotUrl, getFirst (urlsMonoidLatestSnapshot configMonoidUrls)) of (Just url, Nothing) -> do $logWarn "The latest-snapshot-url field is deprecated in favor of 'urls' configuration" return (urlsFromMonoid configMonoidUrls) { urlsLatestSnapshot = url } _ -> return (urlsFromMonoid configMonoidUrls) let configConnectionCount = fromFirst 8 configMonoidConnectionCount configHideTHLoading = fromFirst True configMonoidHideTHLoading configPackageIndices = fromFirst [PackageIndex { indexName = IndexName "Hackage" , indexLocation = "https://s3.amazonaws.com/hackage.fpcomplete.com/" , indexType = ITHackageSecurity HackageSecurity { hsKeyIds = [ "0a5c7ea47cd1b15f01f5f51a33adda7e655bc0f0b0615baa8e271f4c3351e21d" , "1ea9ba32c526d1cc91ab5e5bd364ec5e9e8cb67179a471872f6e26f0ae773d42" , "280b10153a522681163658cb49f632cde3f38d768b736ddbc901d99a1a772833" , "2a96b1889dc221c17296fcc2bb34b908ca9734376f0f361660200935916ef201" , "2c6c3627bd6c982990239487f1abd02e08a02e6cf16edb105a8012d444d870c3" , "51f0161b906011b52c6613376b1ae937670da69322113a246a09f807c62f6921" , "772e9f4c7db33d251d5c6e357199c819e569d130857dc225549b40845ff0890d" , "aa315286e6ad281ad61182235533c41e806e5a787e0b6d1e7eef3f09d137d2e9" , "fe331502606802feac15e514d9b9ea83fee8b6ffef71335479a2e68d84adc6b0" ] , hsKeyThreshold = 3 } , indexDownloadPrefix = "https://s3.amazonaws.com/hackage.fpcomplete.com/package/" , indexRequireHashes = False }] configMonoidPackageIndices configGHCVariant0 = getFirst configMonoidGHCVariant configGHCBuild = getFirst configMonoidGHCBuild configInstallGHC = fromFirst False configMonoidInstallGHC configSkipGHCCheck = fromFirst False configMonoidSkipGHCCheck configSkipMsys = fromFirst False configMonoidSkipMsys configExtraIncludeDirs = configMonoidExtraIncludeDirs configExtraLibDirs = configMonoidExtraLibDirs configOverrideGccPath = getFirst configMonoidOverrideGccPath -- Only place in the codebase where platform is hard-coded. In theory -- in the future, allow it to be configured. (Platform defArch defOS) = buildPlatform arch = fromMaybe defArch $ getFirst configMonoidArch >>= Distribution.Text.simpleParse os = defOS configPlatform = Platform arch os configRequireStackVersion = simplifyVersionRange (getIntersectingVersionRange configMonoidRequireStackVersion) configImage = Image.imgOptsFromMonoid configMonoidImageOpts configCompilerCheck = fromFirst MatchMinor configMonoidCompilerCheck case arch of OtherArch unk -> $logWarn $ "Warning: Unknown value for architecture setting: " <> T.pack (show unk) _ -> return () configPlatformVariant <- liftIO $ maybe PlatformVariantNone PlatformVariant <$> lookupEnv platformVariantEnvVar let configBuild = buildOptsFromMonoid configMonoidBuildOpts configDocker <- dockerOptsFromMonoid (fmap fst mproject) configStackRoot mresolver configMonoidDockerOpts configNix <- nixOptsFromMonoid configMonoidNixOpts os configSystemGHC <- case (getFirst configMonoidSystemGHC, nixEnable configNix) of (Just False, True) -> throwM NixRequiresSystemGhc _ -> return (fromFirst (dockerEnable configDocker || nixEnable configNix) configMonoidSystemGHC) when (isJust configGHCVariant0 && configSystemGHC) $ throwM ManualGHCVariantSettingsAreIncompatibleWithSystemGHC rawEnv <- liftIO getEnvironment pathsEnv <- augmentPathMap configMonoidExtraPath (Map.fromList (map (T.pack *** T.pack) rawEnv)) origEnv <- mkEnvOverride configPlatform pathsEnv let configEnvOverride _ = return origEnv configLocalProgramsBase <- case getFirst configMonoidLocalProgramsBase of Nothing -> getDefaultLocalProgramsBase configStackRoot configPlatform origEnv Just path -> return path platformOnlyDir <- runReaderT platformOnlyRelDir (configPlatform, configPlatformVariant) let configLocalPrograms = configLocalProgramsBase platformOnlyDir configLocalBin <- case getFirst configMonoidLocalBinPath of Nothing -> do localDir <- getAppUserDataDir "local" return $ localDir $(mkRelDir "bin") Just userPath -> (case mproject of -- Not in a project Nothing -> resolveDir' userPath -- Resolves to the project dir and appends the user path if it is relative Just (_, configYaml) -> resolveDir (parent configYaml) userPath) -- TODO: Either catch specific exceptions or add a -- parseRelAsAbsDirMaybe utility and use it along with -- resolveDirMaybe. `catchAll` const (throwM (NoSuchDirectory userPath)) configJobs <- case getFirst configMonoidJobs of Nothing -> liftIO getNumProcessors Just i -> return i let configConcurrentTests = fromFirst True configMonoidConcurrentTests let configTemplateParams = configMonoidTemplateParameters configScmInit = getFirst configMonoidScmInit configGhcOptions = configMonoidGhcOptions configSetupInfoLocations = configMonoidSetupInfoLocations configPvpBounds = fromFirst (PvpBounds PvpBoundsNone False) configMonoidPvpBounds configModifyCodePage = fromFirst True configMonoidModifyCodePage configExplicitSetupDeps = configMonoidExplicitSetupDeps configRebuildGhcOptions = fromFirst False configMonoidRebuildGhcOptions configApplyGhcOptions = fromFirst AGOLocals configMonoidApplyGhcOptions configAllowNewer = fromFirst False configMonoidAllowNewer configDefaultTemplate = getFirst configMonoidDefaultTemplate configDumpLogs = fromFirst DumpWarningLogs configMonoidDumpLogs configSaveHackageCreds = fromFirst True configMonoidSaveHackageCreds configAllowDifferentUser <- case getFirst configMonoidAllowDifferentUser of Just True -> return True _ -> getInContainer configPackageCaches <- liftIO $ newIORef Nothing let configMaybeProject = mproject return Config {..} -- | Get the default location of the local programs directory. getDefaultLocalProgramsBase :: MonadThrow m => Path Abs Dir -> Platform -> EnvOverride -> m (Path Abs Dir) getDefaultLocalProgramsBase configStackRoot configPlatform override = let defaultBase = configStackRoot $(mkRelDir "programs") in case configPlatform of -- For historical reasons, on Windows a subdirectory of LOCALAPPDATA is -- used instead of a subdirectory of STACK_ROOT. Unifying the defaults would -- mean that Windows users would manually have to move data from the old -- location to the new one, which is undesirable. Platform _ Windows -> case Map.lookup "LOCALAPPDATA" $ unEnvOverride override of Just t -> case parseAbsDir $ T.unpack t of Nothing -> throwString ("Failed to parse LOCALAPPDATA environment variable (expected absolute directory): " ++ show t) Just lad -> return $ lad $(mkRelDir "Programs") $(mkRelDir stackProgName) Nothing -> return defaultBase _ -> return defaultBase -- | An environment with a subset of BuildConfig used for setup. data MiniConfig = MiniConfig { mcGHCVariant :: !GHCVariant , mcConfig :: !Config } instance HasConfig MiniConfig where configL = lens mcConfig (\x y -> x { mcConfig = y }) instance HasPlatform MiniConfig instance HasGHCVariant MiniConfig where ghcVariantL = lens mcGHCVariant (\x y -> x { mcGHCVariant = y }) -- | Load the 'MiniConfig'. loadMiniConfig :: Config -> MiniConfig loadMiniConfig config = let ghcVariant = fromMaybe GHCStandard (configGHCVariant0 config) in MiniConfig ghcVariant config -- Load the configuration, using environment variables, and defaults as -- necessary. loadConfigMaybeProject :: StackM env m => ConfigMonoid -- ^ Config monoid from parsed command-line arguments -> Maybe AbstractResolver -- ^ Override resolver -> LocalConfigStatus (Project, Path Abs File, ConfigMonoid) -- ^ Project config to use, if any -> m (LoadConfig m) loadConfigMaybeProject configArgs mresolver mproject = do (stackRoot, userOwnsStackRoot) <- determineStackRootAndOwnership configArgs let loadHelper mproject' = do userConfigPath <- getDefaultUserConfigPath stackRoot extraConfigs0 <- getExtraConfigs userConfigPath >>= mapM (\file -> loadConfigYaml (parseConfigMonoid (parent file)) file) let extraConfigs = -- non-project config files' existence of a docker section should never default docker -- to enabled, so make it look like they didn't exist map (\c -> c {configMonoidDockerOpts = (configMonoidDockerOpts c) {dockerMonoidDefaultEnable = Any False}}) extraConfigs0 configFromConfigMonoid stackRoot userConfigPath True -- allow locals mresolver (fmap (\(x, y, _) -> (x, y)) mproject') $ mconcat $ configArgs : maybe id (\(_, _, projectConfig) -> (projectConfig:)) mproject' extraConfigs config <- case mproject of LCSNoConfig -> configNoLocalConfig stackRoot mresolver configArgs LCSProject project -> loadHelper $ Just project LCSNoProject -> loadHelper Nothing unless (fromCabalVersion Meta.version `withinRange` configRequireStackVersion config) (throwM (BadStackVersionException (configRequireStackVersion config))) let mprojectRoot = fmap (\(_, fp, _) -> parent fp) mproject unless (configAllowDifferentUser config) $ do unless userOwnsStackRoot $ throwM (UserDoesn'tOwnDirectory stackRoot) forM_ mprojectRoot $ \dir -> checkOwnership (dir configWorkDir config) return LoadConfig { lcConfig = config , lcLoadBuildConfig = loadBuildConfig mproject config mresolver , lcProjectRoot = case mprojectRoot of LCSProject fp -> Just fp LCSNoProject -> Nothing LCSNoConfig -> Nothing } -- | Load the configuration, using current directory, environment variables, -- and defaults as necessary. The passed @Maybe (Path Abs File)@ is an -- override for the location of the project's stack.yaml. loadConfig :: StackM env m => ConfigMonoid -- ^ Config monoid from parsed command-line arguments -> Maybe AbstractResolver -- ^ Override resolver -> StackYamlLoc (Path Abs File) -- ^ Override stack.yaml -> m (LoadConfig m) loadConfig configArgs mresolver mstackYaml = loadProjectConfig mstackYaml >>= loadConfigMaybeProject configArgs mresolver -- | Load the build configuration, adds build-specific values to config loaded by @loadConfig@. -- values. loadBuildConfig :: StackM env m => LocalConfigStatus (Project, Path Abs File, ConfigMonoid) -> Config -> Maybe AbstractResolver -- override resolver -> Maybe CompilerVersion -- override compiler -> m BuildConfig loadBuildConfig mproject config mresolver mcompiler = do env <- ask (project', stackYamlFP) <- case mproject of LCSProject (project, fp, _) -> do forM_ (projectUserMsg project) ($logWarn . T.pack) return (project, fp) LCSNoConfig -> do p <- getEmptyProject return (p, configUserConfigPath config) LCSNoProject -> do $logDebug "Run from outside a project, using implicit global project config" destDir <- getImplicitGlobalProjectDir config let dest :: Path Abs File dest = destDir stackDotYaml dest' :: FilePath dest' = toFilePath dest ensureDir destDir exists <- doesFileExist dest if exists then do ProjectAndConfigMonoid project _ <- loadConfigYaml (parseProjectAndConfigMonoid destDir) dest when (view terminalL env) $ case mresolver of Nothing -> $logDebug ("Using resolver: " <> resolverName (projectResolver project) <> " from implicit global project's config file: " <> T.pack dest') Just aresolver -> do let name = case aresolver of ARResolver resolver -> resolverName resolver ARLatestNightly -> "nightly" ARLatestLTS -> "lts" ARLatestLTSMajor x -> T.pack $ "lts-" ++ show x ARGlobal -> "global" $logDebug ("Using resolver: " <> name <> " specified on command line") return (project, dest) else do $logInfo ("Writing implicit global project config file to: " <> T.pack dest') $logInfo "Note: You can change the snapshot via the resolver field there." p <- getEmptyProject liftIO $ do S.writeFile dest' $ S.concat [ "# This is the implicit global project's config file, which is only used when\n" , "# 'stack' is run outside of a real project. Settings here do _not_ act as\n" , "# defaults for all projects. To change stack's default settings, edit\n" , "# '", encodeUtf8 (T.pack $ toFilePath $ configUserConfigPath config), "' instead.\n" , "#\n" , "# For more information about stack's configuration, see\n" , "# http://docs.haskellstack.org/en/stable/yaml_configuration/\n" , "#\n" , Yaml.encode p] S.writeFile (toFilePath $ parent dest $(mkRelFile "README.txt")) $ S.concat [ "This is the implicit global project, which is used only when 'stack' is run\n" , "outside of a real project.\n" ] return (p, dest) resolver <- case mresolver of Nothing -> return $ projectResolver project' Just aresolver -> runReaderT (makeConcreteResolver aresolver) miniConfig let project = project' { projectResolver = resolver , projectCompiler = mcompiler <|> projectCompiler project' } (mbp0, loadedResolver) <- flip runReaderT miniConfig $ loadResolver (Just stackYamlFP) (projectResolver project) let mbp = case projectCompiler project of Just compiler -> mbp0 { mbpCompilerVersion = compiler } Nothing -> mbp0 extraPackageDBs <- mapM resolveDir' (projectExtraPackageDBs project) return BuildConfig { bcConfig = config , bcResolver = loadedResolver , bcWantedMiniBuildPlan = mbp , bcGHCVariant = view ghcVariantL miniConfig , bcPackageEntries = projectPackages project , bcExtraDeps = projectExtraDeps project , bcExtraPackageDBs = extraPackageDBs , bcStackYaml = stackYamlFP , bcFlags = projectFlags project , bcImplicitGlobal = case mproject of LCSNoProject -> True LCSProject _ -> False LCSNoConfig -> False } where miniConfig = loadMiniConfig config getEmptyProject = do r <- case mresolver of Just aresolver -> do r' <- runReaderT (makeConcreteResolver aresolver) miniConfig $logInfo ("Using resolver: " <> resolverName r' <> " specified on command line") return r' Nothing -> do r'' <- runReaderT getLatestResolver miniConfig $logInfo ("Using latest snapshot resolver: " <> resolverName r'') return r'' return Project { projectUserMsg = Nothing , projectPackages = mempty , projectExtraDeps = mempty , projectFlags = mempty , projectResolver = r , projectCompiler = Nothing , projectExtraPackageDBs = [] } -- | Get packages from EnvConfig, downloading and cloning as necessary. -- If the packages have already been downloaded, this uses a cached value ( getLocalPackages :: (StackMiniM env m, HasEnvConfig env) => m (Map.Map (Path Abs Dir) TreatLikeExtraDep) getLocalPackages = do cacheRef <- view $ envConfigL.to envConfigPackagesRef mcached <- liftIO $ readIORef cacheRef case mcached of Just cached -> return cached Nothing -> do menv <- getMinimalEnvOverride root <- view projectRootL entries <- view $ buildConfigL.to bcPackageEntries liftM (Map.fromList . concat) $ mapM (resolvePackageEntry menv root) entries -- | Resolve a PackageEntry into a list of paths, downloading and cloning as -- necessary. resolvePackageEntry :: (StackMiniM env m, HasConfig env) => EnvOverride -> Path Abs Dir -- ^ project root -> PackageEntry -> m [(Path Abs Dir, TreatLikeExtraDep)] resolvePackageEntry menv projRoot pe = do entryRoot <- resolvePackageLocation menv projRoot (peLocation pe) paths <- case peSubdirs pe of [] -> return [entryRoot] subs -> mapM (resolveDir entryRoot) subs extraDep <- case peExtraDepMaybe pe of Just e -> return e Nothing -> case peLocation pe of PLFilePath _ -> -- we don't give a warning on missing explicit -- value here, user intent is almost always -- the default for a local directory return False PLRemote url _ -> do $logWarn $ mconcat [ "No extra-dep setting found for package at URL:\n\n" , url , "\n\n" , "This is usually a mistake, external packages " , "should typically\nbe treated as extra-deps to avoid " , "spurious test case failures." ] return False return $ map (, extraDep) paths -- | Resolve a PackageLocation into a path, downloading and cloning as -- necessary. resolvePackageLocation :: (StackMiniM env m, HasConfig env) => EnvOverride -> Path Abs Dir -- ^ project root -> PackageLocation -> m (Path Abs Dir) resolvePackageLocation _ projRoot (PLFilePath fp) = resolveDir projRoot fp resolvePackageLocation menv projRoot (PLRemote url remotePackageType) = do workDir <- view workDirL let nameBeforeHashing = case remotePackageType of RPTHttp{} -> url RPTGit commit -> T.unwords [url, commit] RPTHg commit -> T.unwords [url, commit, "hg"] -- TODO: dedupe with code for snapshot hash? name = T.unpack $ decodeUtf8 $ S.take 12 $ B64URL.encode $ Mem.convert $ hashWith SHA256 $ encodeUtf8 nameBeforeHashing root = projRoot workDir $(mkRelDir "downloaded") fileExtension' = case remotePackageType of RPTHttp -> ".http-archive" _ -> ".unused" fileRel <- parseRelFile $ name ++ fileExtension' dirRel <- parseRelDir name dirRelTmp <- parseRelDir $ name ++ ".tmp" let file = root fileRel dir = root dirRel exists <- doesDirExist dir unless exists $ do ignoringAbsence (removeDirRecur dir) let cloneAndExtract commandName cloneArgs resetCommand commit = do ensureDir root callProcessInheritStderrStdout Cmd { cmdDirectoryToRunIn = Just root , cmdCommandToRun = commandName , cmdEnvOverride = menv , cmdCommandLineArguments = "clone" : cloneArgs ++ [ T.unpack url , toFilePathNoTrailingSep dir ] } created <- doesDirExist dir unless created $ throwM $ FailedToCloneRepo commandName readProcessNull (Just dir) menv commandName (resetCommand ++ [T.unpack commit, "--"]) `catch` \case ex@ProcessFailed{} -> do $logInfo $ "Please ensure that commit " <> commit <> " exists within " <> url throwM ex ex -> throwM ex case remotePackageType of RPTHttp -> do let dirTmp = root dirRelTmp ignoringAbsence (removeDirRecur dirTmp) let fp = toFilePath file req <- parseUrlThrow $ T.unpack url _ <- download req file let tryTar = do $logDebug $ "Trying to untar " <> T.pack fp liftIO $ withBinaryFile fp ReadMode $ \h -> do lbs <- L.hGetContents h let entries = Tar.read $ GZip.decompress lbs Tar.unpack (toFilePath dirTmp) entries tryZip = do $logDebug $ "Trying to unzip " <> T.pack fp archive <- fmap Zip.toArchive $ liftIO $ L.readFile fp liftIO $ Zip.extractFilesFromArchive [Zip.OptDestination (toFilePath dirTmp)] archive err = throwM $ UnableToExtractArchive url file catchAllLog goodpath handler = catchAll goodpath $ \e -> do $logDebug $ "Got exception: " <> T.pack (show e) handler tryTar `catchAllLog` tryZip `catchAllLog` err renameDir dirTmp dir -- Passes in --git-dir to git and --repository to hg, in order -- to avoid the update commands being applied to the user's -- repo. See https://github.com/commercialhaskell/stack/issues/2748 RPTGit commit -> cloneAndExtract "git" ["--recursive"] ["--git-dir=.git", "reset", "--hard"] commit RPTHg commit -> cloneAndExtract "hg" [] ["--repository", ".", "update", "-C"] commit case remotePackageType of RPTHttp -> do x <- listDir dir case x of ([dir'], []) -> return dir' (dirs, files) -> do ignoringAbsence (removeFile file) ignoringAbsence (removeDirRecur dir) throwM $ UnexpectedArchiveContents dirs files _ -> return dir -- | Remove path from package entry. If the package entry contains subdirs, then it removes -- the subdir. If the package entry points to the path to remove, this function returns -- Nothing. If the package entry doesn't mention the path to remove, it is returned unchanged removePathFromPackageEntry :: (StackMiniM env m, HasConfig env) => EnvOverride -> Path Abs Dir -- ^ project root -> Path Abs Dir -- ^ path to remove -> PackageEntry -> m (Maybe PackageEntry) -- ^ Nothing if the whole package entry should be removed, otherwise -- it returns the updated PackageEntry removePathFromPackageEntry menv projectRoot pathToRemove packageEntry = do locationPath <- resolvePackageLocation menv projectRoot (peLocation packageEntry) case peSubdirs packageEntry of [] -> if locationPath == pathToRemove then return Nothing else return (Just packageEntry) subdirPaths -> do let shouldKeepSubdir path = do resolvedPath <- resolveDir locationPath path return (pathToRemove /= resolvedPath) filteredSubdirs <- filterM shouldKeepSubdir subdirPaths if null filteredSubdirs then return Nothing else return (Just packageEntry {peSubdirs = filteredSubdirs}) -- | Get the stack root, e.g. @~/.stack@, and determine whether the user owns it. -- -- On Windows, the second value is always 'True'. determineStackRootAndOwnership :: (MonadIO m, MonadCatch m) => ConfigMonoid -- ^ Parsed command-line arguments -> m (Path Abs Dir, Bool) determineStackRootAndOwnership clArgs = do stackRoot <- do case getFirst (configMonoidStackRoot clArgs) of Just x -> return x Nothing -> do mstackRoot <- liftIO $ lookupEnv stackRootEnvVar case mstackRoot of Nothing -> getAppUserDataDir stackProgName Just x -> case parseAbsDir x of Nothing -> throwString ("Failed to parse STACK_ROOT environment variable (expected absolute directory): " ++ show x) Just parsed -> return parsed (existingStackRootOrParentDir, userOwnsIt) <- do mdirAndOwnership <- findInParents getDirAndOwnership stackRoot case mdirAndOwnership of Just x -> return x Nothing -> throwM (BadStackRoot stackRoot) when (existingStackRootOrParentDir /= stackRoot) $ if userOwnsIt then liftIO $ ensureDir stackRoot else throwM $ Won'tCreateStackRootInDirectoryOwnedByDifferentUser stackRoot existingStackRootOrParentDir stackRoot' <- canonicalizePath stackRoot return (stackRoot', userOwnsIt) -- | @'checkOwnership' dir@ throws 'UserDoesn'tOwnDirectory' if @dir@ -- isn't owned by the current user. -- -- If @dir@ doesn't exist, its parent directory is checked instead. -- If the parent directory doesn't exist either, @'NoSuchDirectory' ('parent' dir)@ -- is thrown. checkOwnership :: (MonadIO m, MonadCatch m) => Path Abs Dir -> m () checkOwnership dir = do mdirAndOwnership <- firstJustM getDirAndOwnership [dir, parent dir] case mdirAndOwnership of Just (_, True) -> return () Just (dir', False) -> throwM (UserDoesn'tOwnDirectory dir') Nothing -> (throwM . NoSuchDirectory) $ (toFilePathNoTrailingSep . parent) dir -- | @'getDirAndOwnership' dir@ returns @'Just' (dir, 'True')@ when @dir@ -- exists and the current user owns it in the sense of 'isOwnedByUser'. getDirAndOwnership :: (MonadIO m, MonadCatch m) => Path Abs Dir -> m (Maybe (Path Abs Dir, Bool)) getDirAndOwnership dir = forgivingAbsence $ do ownership <- isOwnedByUser dir return (dir, ownership) -- | Check whether the current user (determined with 'getEffectiveUserId') is -- the owner for the given path. -- -- Will always return 'True' on Windows. isOwnedByUser :: MonadIO m => Path Abs t -> m Bool isOwnedByUser path = liftIO $ do if osIsWindows then return True else do fileStatus <- getFileStatus (toFilePath path) user <- getEffectiveUserID return (user == fileOwner fileStatus) where #ifdef WINDOWS osIsWindows = True #else osIsWindows = False #endif -- | 'True' if we are currently running inside a Docker container. getInContainer :: (MonadIO m) => m Bool getInContainer = liftIO (isJust <$> lookupEnv inContainerEnvVar) -- | 'True' if we are currently running inside a Nix. getInNixShell :: (MonadIO m) => m Bool getInNixShell = liftIO (isJust <$> lookupEnv inNixShellEnvVar) -- | Determine the extra config file locations which exist. -- -- Returns most local first getExtraConfigs :: (MonadIO m, MonadLogger m) => Path Abs File -- ^ use config path -> m [Path Abs File] getExtraConfigs userConfigPath = do defaultStackGlobalConfigPath <- getDefaultGlobalConfigPath liftIO $ do env <- getEnvironment mstackConfig <- maybe (return Nothing) (fmap Just . parseAbsFile) $ lookup "STACK_CONFIG" env mstackGlobalConfig <- maybe (return Nothing) (fmap Just . parseAbsFile) $ lookup "STACK_GLOBAL_CONFIG" env filterM doesFileExist $ fromMaybe userConfigPath mstackConfig : maybe [] return (mstackGlobalConfig <|> defaultStackGlobalConfigPath) -- | Load and parse YAML from the given config file. Throws -- 'ParseConfigFileException' when there's a decoding error. loadConfigYaml :: (MonadIO m, MonadLogger m) => (Value -> Yaml.Parser (WithJSONWarnings a)) -> Path Abs File -> m a loadConfigYaml parser path = do eres <- loadYaml parser path case eres of Left err -> liftIO $ throwM (ParseConfigFileException path err) Right res -> return res -- | Load and parse YAML from the given file. loadYaml :: (MonadIO m, MonadLogger m) => (Value -> Yaml.Parser (WithJSONWarnings a)) -> Path Abs File -> m (Either Yaml.ParseException a) loadYaml parser path = do eres <- liftIO $ Yaml.decodeFileEither (toFilePath path) case eres of Left err -> return (Left err) Right val -> case Yaml.parseEither parser val of Left err -> return (Left (Yaml.AesonException err)) Right (WithJSONWarnings res warnings) -> do logJSONWarnings (toFilePath path) warnings return (Right res) -- | Get the location of the project config file, if it exists. getProjectConfig :: (MonadIO m, MonadThrow m, MonadLogger m) => StackYamlLoc (Path Abs File) -- ^ Override stack.yaml -> m (LocalConfigStatus (Path Abs File)) getProjectConfig (SYLOverride stackYaml) = return $ LCSProject stackYaml getProjectConfig SYLDefault = do env <- liftIO getEnvironment case lookup "STACK_YAML" env of Just fp -> do $logInfo "Getting project config file from STACK_YAML environment" liftM LCSProject $ resolveFile' fp Nothing -> do currDir <- getCurrentDir maybe LCSNoProject LCSProject <$> findInParents getStackDotYaml currDir where getStackDotYaml dir = do let fp = dir stackDotYaml fp' = toFilePath fp $logDebug $ "Checking for project config at: " <> T.pack fp' exists <- doesFileExist fp if exists then return $ Just fp else return Nothing getProjectConfig SYLNoConfig = return LCSNoConfig data LocalConfigStatus a = LCSNoProject | LCSProject a | LCSNoConfig deriving (Show,Functor,Foldable,Traversable) -- | Find the project config file location, respecting environment variables -- and otherwise traversing parents. If no config is found, we supply a default -- based on current directory. loadProjectConfig :: (MonadIO m, MonadThrow m, MonadLogger m) => StackYamlLoc (Path Abs File) -- ^ Override stack.yaml -> m (LocalConfigStatus (Project, Path Abs File, ConfigMonoid)) loadProjectConfig mstackYaml = do mfp <- getProjectConfig mstackYaml case mfp of LCSProject fp -> do currDir <- getCurrentDir $logDebug $ "Loading project config file " <> T.pack (maybe (toFilePath fp) toFilePath (stripDir currDir fp)) LCSProject <$> load fp LCSNoProject -> do $logDebug $ "No project config file found, using defaults." return LCSNoProject LCSNoConfig -> do $logDebug "Ignoring config files" return LCSNoConfig where load fp = do ProjectAndConfigMonoid project config <- loadConfigYaml (parseProjectAndConfigMonoid (parent fp)) fp return (project, fp, config) -- | Get the location of the default stack configuration file. -- If a file already exists at the deprecated location, its location is returned. -- Otherwise, the new location is returned. getDefaultGlobalConfigPath :: (MonadIO m, MonadLogger m) => m (Maybe (Path Abs File)) getDefaultGlobalConfigPath = case (defaultGlobalConfigPath, defaultGlobalConfigPathDeprecated) of (Just new,Just old) -> liftM (Just . fst ) $ tryDeprecatedPath (Just "non-project global configuration file") doesFileExist new old (Just new,Nothing) -> return (Just new) _ -> return Nothing -- | Get the location of the default user configuration file. -- If a file already exists at the deprecated location, its location is returned. -- Otherwise, the new location is returned. getDefaultUserConfigPath :: (MonadIO m, MonadLogger m) => Path Abs Dir -> m (Path Abs File) getDefaultUserConfigPath stackRoot = do (path, exists) <- tryDeprecatedPath (Just "non-project configuration file") doesFileExist (defaultUserConfigPath stackRoot) (defaultUserConfigPathDeprecated stackRoot) unless exists $ do ensureDir (parent path) liftIO $ S.writeFile (toFilePath path) defaultConfigYaml return path -- | Get a fake configuration file location, used when doing a "no -- config" run (the script command). getFakeConfigPath :: (MonadIO m, MonadThrow m) => Path Abs Dir -- ^ stack root -> AbstractResolver -> m (Path Abs File) getFakeConfigPath stackRoot ar = do asString <- case ar of ARResolver r -> return $ T.unpack $ resolverName r _ -> throwM $ InvalidResolverForNoLocalConfig $ show ar asDir <- parseRelDir asString let full = stackRoot $(mkRelDir "script") asDir $(mkRelFile "config.yaml") ensureDir (parent full) return full packagesParser :: Parser [String] packagesParser = many (strOption (long "package" <> help "Additional packages that must be installed")) defaultConfigYaml :: S.ByteString defaultConfigYaml = S.intercalate "\n" [ "# This file contains default non-project-specific settings for 'stack', used" , "# in all projects. For more information about stack's configuration, see" , "# http://docs.haskellstack.org/en/stable/yaml_configuration/" , "" , "# The following parameters are used by \"stack new\" to automatically fill fields" , "# in the cabal config. We recommend uncommenting them and filling them out if" , "# you intend to use 'stack new'." , "# See https://docs.haskellstack.org/en/stable/yaml_configuration/#templates" , "templates:" , " params:" , "# author-name:" , "# author-email:" , "# copyright:" , "# github-username:" ] stack-1.5.1/src/Stack/Config/Build.hs0000644000000000000000000001050313135652051015445 0ustar0000000000000000{-# LANGUAGE RecordWildCards #-} -- | Build configuration module Stack.Config.Build where import Data.Maybe import Data.Monoid.Extra import Stack.Types.Config -- | Interprets BuildOptsMonoid options. buildOptsFromMonoid :: BuildOptsMonoid -> BuildOpts buildOptsFromMonoid BuildOptsMonoid{..} = BuildOpts { boptsLibProfile = fromFirst (boptsLibProfile defaultBuildOpts) (buildMonoidLibProfile <> First (if tracing || profiling then Just True else Nothing)) , boptsExeProfile = fromFirst (boptsExeProfile defaultBuildOpts) (buildMonoidExeProfile <> First (if tracing || profiling then Just True else Nothing)) , boptsLibStrip = fromFirst (boptsLibStrip defaultBuildOpts) (buildMonoidLibStrip <> First (if noStripping then Just False else Nothing)) , boptsExeStrip = fromFirst (boptsExeStrip defaultBuildOpts) (buildMonoidExeStrip <> First (if noStripping then Just False else Nothing)) , boptsHaddock = fromFirst (boptsHaddock defaultBuildOpts) buildMonoidHaddock , boptsHaddockOpts = haddockOptsFromMonoid buildMonoidHaddockOpts , boptsOpenHaddocks = fromFirst (boptsOpenHaddocks defaultBuildOpts) buildMonoidOpenHaddocks , boptsHaddockDeps = getFirst buildMonoidHaddockDeps , boptsHaddockInternal = fromFirst (boptsHaddockInternal defaultBuildOpts) buildMonoidHaddockInternal , boptsHaddockHyperlinkSource = fromFirst (boptsHaddockHyperlinkSource defaultBuildOpts) buildMonoidHaddockHyperlinkSource , boptsInstallExes = fromFirst (boptsInstallExes defaultBuildOpts) buildMonoidInstallExes , boptsPreFetch = fromFirst (boptsPreFetch defaultBuildOpts) buildMonoidPreFetch , boptsKeepGoing = getFirst buildMonoidKeepGoing , boptsForceDirty = fromFirst (boptsForceDirty defaultBuildOpts) buildMonoidForceDirty , boptsTests = fromFirst (boptsTests defaultBuildOpts) buildMonoidTests , boptsTestOpts = testOptsFromMonoid buildMonoidTestOpts additionalArgs , boptsBenchmarks = fromFirst (boptsBenchmarks defaultBuildOpts) buildMonoidBenchmarks , boptsBenchmarkOpts = benchmarkOptsFromMonoid buildMonoidBenchmarkOpts additionalArgs , boptsReconfigure = fromFirst (boptsReconfigure defaultBuildOpts) buildMonoidReconfigure , boptsCabalVerbose = fromFirst (boptsCabalVerbose defaultBuildOpts) buildMonoidCabalVerbose , boptsSplitObjs = fromFirst (boptsSplitObjs defaultBuildOpts) buildMonoidSplitObjs } where -- These options are not directly used in bopts, instead they -- transform other options. tracing = getAny buildMonoidTrace profiling = getAny buildMonoidProfile noStripping = getAny buildMonoidNoStrip -- Additional args for tracing / profiling additionalArgs = if tracing || profiling then Just $ "+RTS" : catMaybes [trac, prof, Just "-RTS"] else Nothing trac = if tracing then Just "-xc" else Nothing prof = if profiling then Just "-p" else Nothing haddockOptsFromMonoid :: HaddockOptsMonoid -> HaddockOpts haddockOptsFromMonoid HaddockOptsMonoid{..} = defaultHaddockOpts {hoAdditionalArgs = hoMonoidAdditionalArgs} testOptsFromMonoid :: TestOptsMonoid -> Maybe [String] -> TestOpts testOptsFromMonoid TestOptsMonoid{..} madditional = defaultTestOpts { toRerunTests = fromFirst (toRerunTests defaultTestOpts) toMonoidRerunTests , toAdditionalArgs = fromMaybe [] madditional <> toMonoidAdditionalArgs , toCoverage = fromFirst (toCoverage defaultTestOpts) toMonoidCoverage , toDisableRun = fromFirst (toDisableRun defaultTestOpts) toMonoidDisableRun } benchmarkOptsFromMonoid :: BenchmarkOptsMonoid -> Maybe [String] -> BenchmarkOpts benchmarkOptsFromMonoid BenchmarkOptsMonoid{..} madditional = defaultBenchmarkOpts { beoAdditionalArgs = fmap (\args -> unwords args <> " ") madditional <> getFirst beoMonoidAdditionalArgs , beoDisableRun = fromFirst (beoDisableRun defaultBenchmarkOpts) beoMonoidDisableRun } stack-1.5.1/src/Stack/Config/Urls.hs0000644000000000000000000000141513135652051015335 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Stack.Config.Urls (urlsFromMonoid) where import Stack.Types.Urls import Data.Monoid.Extra urlsFromMonoid :: UrlsMonoid -> Urls urlsFromMonoid monoid = Urls (fromFirst defaultLatestSnapshot $ urlsMonoidLatestSnapshot monoid) (fromFirst defaultLtsBuildPlans $ urlsMonoidLtsBuildPlans monoid) (fromFirst defaultNightlyBuildPlans $ urlsMonoidNightlyBuildPlans monoid) where defaultLatestSnapshot = "https://s3.amazonaws.com/haddock.stackage.org/snapshots.json" defaultLtsBuildPlans = "https://raw.githubusercontent.com/fpco/lts-haskell/master/" defaultNightlyBuildPlans = "https://raw.githubusercontent.com/fpco/stackage-nightly/master/" stack-1.5.1/src/Stack/Config/Docker.hs0000644000000000000000000001114713135652051015622 0ustar0000000000000000{-# LANGUAGE CPP, DeriveDataTypeable, RecordWildCards, TemplateHaskell #-} -- | Docker configuration module Stack.Config.Docker where import Control.Exception.Lifted import Control.Monad.Catch (MonadThrow) import Data.List (find) import Data.Maybe import Data.Monoid.Extra import qualified Data.Text as T import Data.Typeable (Typeable) import Distribution.Version (simplifyVersionRange) import Path import Stack.Types.BuildPlan import Stack.Types.Version import Stack.Types.Config import Stack.Types.Docker import Stack.Types.Resolver -- | Interprets DockerOptsMonoid options. dockerOptsFromMonoid :: MonadThrow m => Maybe Project -> Path Abs Dir -> Maybe AbstractResolver -> DockerOptsMonoid -> m DockerOpts dockerOptsFromMonoid mproject stackRoot maresolver DockerOptsMonoid{..} = do let dockerEnable = fromFirst (getAny dockerMonoidDefaultEnable) dockerMonoidEnable dockerImage = let mresolver = case maresolver of Just (ARResolver resolver) -> Just resolver Just aresolver -> throw (ResolverNotSupportedException $ show aresolver) Nothing -> fmap projectResolver mproject defaultTag = case mresolver of Nothing -> "" Just resolver -> case resolver of ResolverSnapshot n@(LTS _ _) -> ":" ++ T.unpack (renderSnapName n) _ -> throw (ResolverNotSupportedException $ show resolver) in case getFirst dockerMonoidRepoOrImage of Nothing -> "fpco/stack-build" ++ defaultTag Just (DockerMonoidImage image) -> image Just (DockerMonoidRepo repo) -> case find (`elem` (":@" :: String)) repo of Just _ -- Repo already specified a tag or digest, so don't append default -> repo Nothing -> repo ++ defaultTag dockerRegistryLogin = fromFirst (isJust (emptyToNothing (getFirst dockerMonoidRegistryUsername))) dockerMonoidRegistryLogin dockerRegistryUsername = emptyToNothing (getFirst dockerMonoidRegistryUsername) dockerRegistryPassword = emptyToNothing (getFirst dockerMonoidRegistryPassword) dockerAutoPull = fromFirst False dockerMonoidAutoPull dockerDetach = fromFirst False dockerMonoidDetach dockerPersist = fromFirst False dockerMonoidPersist dockerContainerName = emptyToNothing (getFirst dockerMonoidContainerName) dockerRunArgs = dockerMonoidRunArgs dockerMount = dockerMonoidMount dockerEnv = dockerMonoidEnv dockerSetUser = getFirst dockerMonoidSetUser dockerRequireDockerVersion = simplifyVersionRange (getIntersectingVersionRange dockerMonoidRequireDockerVersion) dockerDatabasePath = fromFirst (stackRoot $(mkRelFile "docker.db")) dockerMonoidDatabasePath dockerStackExe = getFirst dockerMonoidStackExe return DockerOpts{..} where emptyToNothing Nothing = Nothing emptyToNothing (Just s) | null s = Nothing | otherwise = Just s -- | Exceptions thrown by Stack.Docker.Config. data StackDockerConfigException = ResolverNotSupportedException String -- ^ Only LTS resolvers are supported for default image tag. | InvalidDatabasePathException SomeException -- ^ Invalid global database path. deriving (Typeable) -- | Exception instance for StackDockerConfigException. instance Exception StackDockerConfigException -- | Show instance for StackDockerConfigException. instance Show StackDockerConfigException where show (ResolverNotSupportedException resolver) = concat [ "Resolver not supported for Docker images:\n " , resolver , "\nUse an LTS resolver, or set the '" , T.unpack dockerImageArgName , "' explicitly, in your configuration file."] show (InvalidDatabasePathException ex) = "Invalid database path: " ++ show ex stack-1.5.1/src/Stack/Config/Nix.hs0000644000000000000000000000526613135652051015156 0ustar0000000000000000{-# LANGUAGE RecordWildCards, DeriveDataTypeable, OverloadedStrings #-} -- | Nix configuration module Stack.Config.Nix (nixOptsFromMonoid ,nixCompiler ,StackNixException(..) ) where import Control.Monad (when) import Data.Maybe import Data.Monoid.Extra import qualified Data.Text as T import Data.Typeable import Distribution.System (OS (..)) import Stack.Types.Version import Stack.Types.Nix import Stack.Types.Compiler import Stack.Types.StringError import Control.Exception.Lifted import Control.Monad.Catch (throwM,MonadCatch) import Prelude -- | Interprets NixOptsMonoid options. nixOptsFromMonoid :: (Monad m, MonadCatch m) => NixOptsMonoid -> OS -> m NixOpts nixOptsFromMonoid NixOptsMonoid{..} os = do let nixEnable = fromFirst (getAny nixMonoidDefaultEnable) nixMonoidEnable defaultPure = case os of OSX -> False _ -> True nixPureShell = fromFirst defaultPure nixMonoidPureShell nixPackages = fromFirst [] nixMonoidPackages nixInitFile = getFirst nixMonoidInitFile nixShellOptions = fromFirst [] nixMonoidShellOptions ++ prefixAll (T.pack "-I") (fromFirst [] nixMonoidPath) nixAddGCRoots = fromFirst False nixMonoidAddGCRoots when (not (null nixPackages) && isJust nixInitFile) $ throwM NixCannotUseShellFileAndPackagesException return NixOpts{..} where prefixAll p (x:xs) = p : x : prefixAll p xs prefixAll _ _ = [] nixCompiler :: CompilerVersion -> T.Text nixCompiler compilerVersion = let -- These are the latest minor versions for each respective major version available in nixpkgs fixMinor "8.0" = "8.0.1" fixMinor "7.10" = "7.10.3" fixMinor "7.8" = "7.8.4" fixMinor "7.6" = "7.6.3" fixMinor "7.4" = "7.4.2" fixMinor "7.2" = "7.2.2" fixMinor "6.12" = "6.12.3" fixMinor "6.10" = "6.10.4" fixMinor v = v nixCompilerFromVersion v = T.append (T.pack "haskell.compiler.ghc") (T.filter (/= '.') (fixMinor (versionText v))) in case compilerVersion of GhcVersion v -> nixCompilerFromVersion v _ -> errorString "Only GHC is supported by stack --nix" -- Exceptions thown specifically by Stack.Nix data StackNixException = NixCannotUseShellFileAndPackagesException -- ^ Nix can't be given packages and a shell file at the same time deriving (Typeable) instance Exception StackNixException instance Show StackNixException where show NixCannotUseShellFileAndPackagesException = "You cannot have packages and a shell-file filled at the same time in your nix-shell configuration." stack-1.5.1/src/Stack/ConfigCmd.hs0000644000000000000000000001452313135652051015040 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE GADTs #-} -- | Make changes to project or global configuration. module Stack.ConfigCmd (ConfigCmdSet(..) ,configCmdSetParser ,cfgCmdSet ,cfgCmdSetName ,cfgCmdName) where import Control.Applicative import Control.Monad import Control.Monad.Catch (throwM) import Control.Monad.IO.Class import Control.Monad.Logger import qualified Data.ByteString as S import qualified Data.HashMap.Strict as HMap import Data.Monoid import Data.Text (Text) import qualified Data.Text as T import qualified Data.Yaml as Yaml import qualified Options.Applicative as OA import qualified Options.Applicative.Types as OA import Path import Path.IO import Prelude -- Silence redundant import warnings import Stack.BuildPlan import Stack.Config (makeConcreteResolver, getProjectConfig, getImplicitGlobalProjectDir, LocalConfigStatus(..)) import Stack.Constants import Stack.Types.Config import Stack.Types.Resolver import Stack.Types.StringError data ConfigCmdSet = ConfigCmdSetResolver AbstractResolver | ConfigCmdSetSystemGhc CommandScope Bool | ConfigCmdSetInstallGhc CommandScope Bool data CommandScope = CommandScopeGlobal -- ^ Apply changes to the global configuration, -- typically at @~/.stack/config.yaml@. | CommandScopeProject -- ^ Apply changes to the project @stack.yaml@. configCmdSetScope :: ConfigCmdSet -> CommandScope configCmdSetScope (ConfigCmdSetResolver _) = CommandScopeProject configCmdSetScope (ConfigCmdSetSystemGhc scope _) = scope configCmdSetScope (ConfigCmdSetInstallGhc scope _) = scope cfgCmdSet :: (StackMiniM env m, HasConfig env, HasGHCVariant env) => GlobalOpts -> ConfigCmdSet -> m () cfgCmdSet go cmd = do conf <- view configL configFilePath <- liftM toFilePath (case configCmdSetScope cmd of CommandScopeProject -> do mstackYamlOption <- forM (globalStackYaml go) resolveFile' mstackYaml <- getProjectConfig mstackYamlOption case mstackYaml of LCSProject stackYaml -> return stackYaml LCSNoProject -> liftM ( stackDotYaml) (getImplicitGlobalProjectDir conf) LCSNoConfig -> errorString "config command used when no local configuration available" CommandScopeGlobal -> return (configUserConfigPath conf)) -- We don't need to worry about checking for a valid yaml here (config :: Yaml.Object) <- liftIO (Yaml.decodeFileEither configFilePath) >>= either throwM return newValue <- cfgCmdSetValue cmd let cmdKey = cfgCmdSetOptionName cmd config' = HMap.insert cmdKey newValue config if config' == config then $logInfo (T.pack configFilePath <> " already contained the intended configuration and remains unchanged.") else do liftIO (S.writeFile configFilePath (Yaml.encode config')) $logInfo (T.pack configFilePath <> " has been updated.") cfgCmdSetValue :: (StackMiniM env m, HasConfig env, HasGHCVariant env) => ConfigCmdSet -> m Yaml.Value cfgCmdSetValue (ConfigCmdSetResolver newResolver) = do concreteResolver <- makeConcreteResolver newResolver case concreteResolver of -- Check that the snapshot actually exists ResolverSnapshot snapName -> void $ loadMiniBuildPlan snapName ResolverCompiler _ -> return () -- TODO: custom snapshot support? Would need a way to specify on CLI ResolverCustom _ _ -> errorString "'stack config set resolver' does not support custom resolvers" return (Yaml.String (resolverName concreteResolver)) cfgCmdSetValue (ConfigCmdSetSystemGhc _ bool) = return (Yaml.Bool bool) cfgCmdSetValue (ConfigCmdSetInstallGhc _ bool) = return (Yaml.Bool bool) cfgCmdSetOptionName :: ConfigCmdSet -> Text cfgCmdSetOptionName (ConfigCmdSetResolver _) = "resolver" cfgCmdSetOptionName (ConfigCmdSetSystemGhc _ _) = configMonoidSystemGHCName cfgCmdSetOptionName (ConfigCmdSetInstallGhc _ _) = configMonoidInstallGHCName cfgCmdName :: String cfgCmdName = "config" cfgCmdSetName :: String cfgCmdSetName = "set" configCmdSetParser :: OA.Parser ConfigCmdSet configCmdSetParser = OA.hsubparser $ mconcat [ OA.command "resolver" (OA.info (ConfigCmdSetResolver <$> OA.argument readAbstractResolver (OA.metavar "RESOLVER" <> OA.help "E.g. \"nightly\" or \"lts-7.2\"")) (OA.progDesc "Change the resolver of the current project. See https://docs.haskellstack.org/en/stable/yaml_configuration/#resolver for more info.")) , OA.command (T.unpack configMonoidSystemGHCName) (OA.info (ConfigCmdSetSystemGhc <$> scopeFlag <*> boolArgument) (OA.progDesc "Configure whether stack should use a system GHC installation or not.")) , OA.command (T.unpack configMonoidInstallGHCName) (OA.info (ConfigCmdSetInstallGhc <$> scopeFlag <*> boolArgument) (OA.progDesc "Configure whether stack should automatically install GHC when necessary.")) ] scopeFlag :: OA.Parser CommandScope scopeFlag = OA.flag CommandScopeProject CommandScopeGlobal (OA.long "global" <> OA.help "Modify the global configuration (typically at \"~/.stack/config.yaml\") instead of the project stack.yaml.") readBool :: OA.ReadM Bool readBool = do s <- OA.readerAsk case s of "true" -> return True "false" -> return False _ -> OA.readerError ("Invalid value " ++ show s ++ ": Expected \"true\" or \"false\"") boolArgument :: OA.Parser Bool boolArgument = OA.argument readBool (OA.metavar "true|false" <> OA.completeWith ["true", "false"]) stack-1.5.1/src/Stack/Constants.hs0000644000000000000000000002675113135652051015171 0ustar0000000000000000{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} -- | Constants used throughout the project. module Stack.Constants (buildPlanDir ,distDirFromDir ,workDirFromDir ,distRelativeDir ,haskellModuleExts ,imageStagingDir ,projectDockerSandboxDir ,stackDotYaml ,stackWorkEnvVar ,stackRootEnvVar ,stackRootOptionName ,deprecatedStackRootOptionName ,inContainerEnvVar ,inNixShellEnvVar ,configCacheFile ,configCabalMod ,buildCacheFile ,testSuccessFile ,testBuiltFile ,stackProgName ,stackProgNameUpper ,wiredInPackages ,ghcjsBootPackages ,cabalPackageName ,implicitGlobalProjectDirDeprecated ,implicitGlobalProjectDir ,hpcRelativeDir ,hpcDirFromDir ,objectInterfaceDirL ,templatesDir ,defaultUserConfigPathDeprecated ,defaultUserConfigPath ,defaultGlobalConfigPathDeprecated ,defaultGlobalConfigPath ,platformVariantEnvVar ,compilerOptionsCabalFlag ) where import Control.Monad.Catch (MonadThrow) import Control.Monad.Reader import Data.Char (toUpper) import Data.HashSet (HashSet) import qualified Data.HashSet as HashSet import Data.Text (Text) import Lens.Micro (Getting) import Path as FL import Prelude import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.PackageIdentifier import Stack.Types.PackageName -- | Extensions for anything that can be a Haskell module. haskellModuleExts :: [Text] haskellModuleExts = haskellFileExts ++ haskellPreprocessorExts -- | Extensions used for Haskell modules. Excludes preprocessor ones. haskellFileExts :: [Text] haskellFileExts = ["hs", "hsc", "lhs"] -- | Extensions for modules that are preprocessed by common preprocessors. haskellPreprocessorExts :: [Text] haskellPreprocessorExts = ["gc", "chs", "hsc", "x", "y", "ly", "cpphs"] -- | Output .o/.hi directory. objectInterfaceDirL :: HasBuildConfig env => Getting r env (Path Abs Dir) objectInterfaceDirL = to $ \env -> -- FIXME is this idomatic lens code? let workDir = view workDirL env root = view projectRootL env in root workDir $(mkRelDir "odir/") -- | The filename used for dirtiness check of source files. buildCacheFile :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => Path Abs Dir -- ^ Package directory. -> m (Path Abs File) buildCacheFile dir = liftM ( $(mkRelFile "stack-build-cache")) (distDirFromDir dir) -- | The filename used to mark tests as having succeeded testSuccessFile :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => Path Abs Dir -- ^ Package directory -> m (Path Abs File) testSuccessFile dir = liftM ( $(mkRelFile "stack-test-success")) (distDirFromDir dir) -- | The filename used to mark tests as having built testBuiltFile :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => Path Abs Dir -- ^ Package directory -> m (Path Abs File) testBuiltFile dir = liftM ( $(mkRelFile "stack-test-built")) (distDirFromDir dir) -- | The filename used for dirtiness check of config. configCacheFile :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => Path Abs Dir -- ^ Package directory. -> m (Path Abs File) configCacheFile dir = liftM ( $(mkRelFile "stack-config-cache")) (distDirFromDir dir) -- | The filename used for modification check of .cabal configCabalMod :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => Path Abs Dir -- ^ Package directory. -> m (Path Abs File) configCabalMod dir = liftM ( $(mkRelFile "stack-cabal-mod")) (distDirFromDir dir) -- | Directory for HPC work. hpcDirFromDir :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => Path Abs Dir -- ^ Package directory. -> m (Path Abs Dir) hpcDirFromDir fp = liftM (fp ) hpcRelativeDir -- | Relative location of directory for HPC work. hpcRelativeDir :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Path Rel Dir) hpcRelativeDir = liftM ( $(mkRelDir "hpc")) distRelativeDir -- | Package's build artifacts directory. distDirFromDir :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => Path Abs Dir -> m (Path Abs Dir) distDirFromDir fp = liftM (fp ) distRelativeDir -- | Package's working directory. workDirFromDir :: (MonadReader env m, HasEnvConfig env) => Path Abs Dir -> m (Path Abs Dir) workDirFromDir fp = view $ workDirL.to (fp ) -- | Directory for project templates. templatesDir :: Config -> Path Abs Dir templatesDir config = configStackRoot config $(mkRelDir "templates") -- | Relative location of build artifacts. distRelativeDir :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Path Rel Dir) distRelativeDir = do cabalPkgVer <- view cabalVersionL platform <- platformGhcRelDir wc <- view $ actualCompilerVersionL.to whichCompiler -- Cabal version, suffixed with "_ghcjs" if we're using GHCJS. envDir <- parseRelDir $ (if wc == Ghcjs then (++ "_ghcjs") else id) $ packageIdentifierString $ PackageIdentifier cabalPackageName cabalPkgVer platformAndCabal <- useShaPathOnWindows (platform envDir) workDir <- view workDirL return $ workDir $(mkRelDir "dist") platformAndCabal -- | Docker sandbox from project root. projectDockerSandboxDir :: (MonadReader env m, HasConfig env) => Path Abs Dir -- ^ Project root -> m (Path Abs Dir) -- ^ Docker sandbox projectDockerSandboxDir projectRoot = do workDir <- view workDirL return $ projectRoot workDir $(mkRelDir "docker/") -- | Image staging dir from project root. imageStagingDir :: (MonadReader env m, HasConfig env, MonadThrow m) => Path Abs Dir -- ^ Project root -> Int -- ^ Index of image -> m (Path Abs Dir) -- ^ Docker sandbox imageStagingDir projectRoot imageIdx = do workDir <- view workDirL idxRelDir <- parseRelDir (show imageIdx) return $ projectRoot workDir $(mkRelDir "image") idxRelDir -- | Name of the 'stack' program, uppercased stackProgNameUpper :: String stackProgNameUpper = map toUpper stackProgName -- | Name of the 'stack' program. stackProgName :: String stackProgName = "stack" -- | The filename used for the stack config file. stackDotYaml :: Path Rel File stackDotYaml = $(mkRelFile "stack.yaml") -- | Environment variable used to override the '.stack-work' relative dir. stackWorkEnvVar :: String stackWorkEnvVar = "STACK_WORK" -- | Environment variable used to override the '~/.stack' location. stackRootEnvVar :: String stackRootEnvVar = "STACK_ROOT" -- | Option name for the global stack root. stackRootOptionName :: String stackRootOptionName = "stack-root" -- | Deprecated option name for the global stack root. -- -- Deprecated since stack-1.1.0. -- -- TODO: Remove occurences of this variable and use 'stackRootOptionName' only -- after an appropriate deprecation period. deprecatedStackRootOptionName :: String deprecatedStackRootOptionName = "global-stack-root" -- | Environment variable used to indicate stack is running in container. inContainerEnvVar :: String inContainerEnvVar = stackProgNameUpper ++ "_IN_CONTAINER" -- | Environment variable used to indicate stack is running in container. -- although we already have STACK_IN_NIX_EXTRA_ARGS that is set in the same conditions, -- it can happen that STACK_IN_NIX_EXTRA_ARGS is set to empty. inNixShellEnvVar :: String inNixShellEnvVar = map toUpper stackProgName ++ "_IN_NIXSHELL" -- See https://downloads.haskell.org/~ghc/7.10.1/docs/html/libraries/ghc/src/Module.html#integerPackageKey wiredInPackages :: HashSet PackageName wiredInPackages = maybe (error "Parse error in wiredInPackages") HashSet.fromList mparsed where mparsed = mapM parsePackageName [ "ghc-prim" , "integer-gmp" , "integer-simple" , "base" , "rts" , "template-haskell" , "dph-seq" , "dph-par" , "ghc" , "interactive" ] -- TODO: Get this unwieldy list out of here and into a datafile -- generated by GHCJS! See https://github.com/ghcjs/ghcjs/issues/434 ghcjsBootPackages :: HashSet PackageName ghcjsBootPackages = maybe (error "Parse error in ghcjsBootPackages") HashSet.fromList mparsed where mparsed = mapM parsePackageName -- stage1a [ "array" , "base" , "binary" , "bytestring" , "containers" , "deepseq" , "integer-gmp" , "pretty" , "primitive" , "integer-gmp" , "pretty" , "primitive" , "template-haskell" , "transformers" -- stage1b , "directory" , "filepath" , "old-locale" , "process" , "time" -- stage2 , "async" , "aeson" , "attoparsec" , "case-insensitive" , "dlist" , "extensible-exceptions" , "hashable" , "mtl" , "old-time" , "parallel" , "scientific" , "stm" , "syb" , "text" , "unordered-containers" , "vector" ] -- | Just to avoid repetition and magic strings. cabalPackageName :: PackageName cabalPackageName = $(mkPackageName "Cabal") -- | Deprecated implicit global project directory used when outside of a project. implicitGlobalProjectDirDeprecated :: Path Abs Dir -- ^ Stack root. -> Path Abs Dir implicitGlobalProjectDirDeprecated p = p $(mkRelDir "global") -- | Implicit global project directory used when outside of a project. -- Normally, @getImplicitGlobalProjectDir@ should be used instead. implicitGlobalProjectDir :: Path Abs Dir -- ^ Stack root. -> Path Abs Dir implicitGlobalProjectDir p = p $(mkRelDir "global-project") -- | Deprecated default global config path. defaultUserConfigPathDeprecated :: Path Abs Dir -> Path Abs File defaultUserConfigPathDeprecated = ( $(mkRelFile "stack.yaml")) -- | Default global config path. -- Normally, @getDefaultUserConfigPath@ should be used instead. defaultUserConfigPath :: Path Abs Dir -> Path Abs File defaultUserConfigPath = ( $(mkRelFile "config.yaml")) -- | Deprecated default global config path. -- Note that this will be @Nothing@ on Windows, which is by design. defaultGlobalConfigPathDeprecated :: Maybe (Path Abs File) defaultGlobalConfigPathDeprecated = parseAbsFile "/etc/stack/config" -- | Default global config path. -- Normally, @getDefaultGlobalConfigPath@ should be used instead. -- Note that this will be @Nothing@ on Windows, which is by design. defaultGlobalConfigPath :: Maybe (Path Abs File) defaultGlobalConfigPath = parseAbsFile "/etc/stack/config.yaml" -- | Path where build plans are stored. buildPlanDir :: Path Abs Dir -- ^ Stack root -> Path Abs Dir buildPlanDir = ( $(mkRelDir "build-plan")) -- | Environment variable that stores a variant to append to platform-specific directory -- names. Used to ensure incompatible binaries aren't shared between Docker builds and host platformVariantEnvVar :: String platformVariantEnvVar = stackProgNameUpper ++ "_PLATFORM_VARIANT" -- | Provides --ghc-options for 'Ghc', and similarly, --ghcjs-options -- for 'Ghcjs'. compilerOptionsCabalFlag :: WhichCompiler -> String compilerOptionsCabalFlag Ghc = "--ghc-options" compilerOptionsCabalFlag Ghcjs = "--ghcjs-options" stack-1.5.1/src/Stack/Coverage.hs0000644000000000000000000005466113135652051014751 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-} -- | Generate HPC (Haskell Program Coverage) reports module Stack.Coverage ( deleteHpcReports , updateTixFile , generateHpcReport , HpcReportOpts(..) , generateHpcReportForTargets , generateHpcUnifiedReport , generateHpcMarkupIndex ) where import Control.Exception.Safe (handleIO) import Control.Exception.Lifted import Control.Monad (liftM, when, unless, void, (<=<)) import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.Trans.Resource import qualified Data.ByteString.Char8 as S8 import Data.Foldable (forM_, asum, toList) import Data.Function import Data.List import qualified Data.Map.Strict as Map import Data.Maybe import Data.Maybe.Extra (mapMaybeM) import Data.Monoid ((<>)) import Data.String import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.IO as T import qualified Data.Text.Lazy as LT import Data.Traversable (forM) import Path import Path.Extra (toFilePathNoTrailingSep) import Path.IO import Prelude hiding (FilePath, writeFile) import Stack.Build.Source (parseTargetsFromBuildOpts) import Stack.Build.Target import Stack.Config (getLocalPackages) import Stack.Constants import Stack.Package import Stack.PrettyPrint import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.Package import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.StackT (StackM) import Stack.Types.StringError import Stack.Types.Version import System.FilePath (isPathSeparator) import System.Process.Read import Text.Hastache (htmlEscape) import Trace.Hpc.Tix import Web.Browser (openBrowser) -- | Invoked at the beginning of running with "--coverage" deleteHpcReports :: (StackM env m, HasEnvConfig env) => m () deleteHpcReports = do hpcDir <- hpcReportDir ignoringAbsence (removeDirRecur hpcDir) -- | Move a tix file into a sub-directory of the hpc report directory. Deletes the old one if one is -- present. updateTixFile :: (StackM env m, HasEnvConfig env) => PackageName -> Path Abs File -> String -> m () updateTixFile pkgName tixSrc testName = do exists <- doesFileExist tixSrc when exists $ do tixDest <- tixFilePath pkgName testName ignoringAbsence (removeFile tixDest) ensureDir (parent tixDest) -- Remove exe modules because they are problematic. This could be revisited if there's a GHC -- version that fixes https://ghc.haskell.org/trac/ghc/ticket/1853 mtix <- readTixOrLog tixSrc case mtix of Nothing -> $logError $ "Failed to read " <> T.pack (toFilePath tixSrc) Just tix -> do liftIO $ writeTix (toFilePath tixDest) (removeExeModules tix) -- TODO: ideally we'd do a file move, but IIRC this can -- have problems. Something about moving between drives -- on windows? copyFile tixSrc =<< parseAbsFile (toFilePath tixDest ++ ".premunging") ignoringAbsence (removeFile tixSrc) -- | Get the directory used for hpc reports for the given pkgId. hpcPkgPath :: (StackM env m, HasEnvConfig env) => PackageName -> m (Path Abs Dir) hpcPkgPath pkgName = do outputDir <- hpcReportDir pkgNameRel <- parseRelDir (packageNameString pkgName) return (outputDir pkgNameRel) -- | Get the tix file location, given the name of the file (without extension), and the package -- identifier string. tixFilePath :: (StackM env m, HasEnvConfig env) => PackageName -> String -> m (Path Abs File) tixFilePath pkgName testName = do pkgPath <- hpcPkgPath pkgName tixRel <- parseRelFile (testName ++ "/" ++ testName ++ ".tix") return (pkgPath tixRel) -- | Generates the HTML coverage report and shows a textual coverage summary for a package. generateHpcReport :: (StackM env m, HasEnvConfig env) => Path Abs Dir -> Package -> [Text] -> m () generateHpcReport pkgDir package tests = do compilerVersion <- view actualCompilerVersionL -- If we're using > GHC 7.10, the hpc 'include' parameter must specify a ghc package key. See -- https://github.com/commercialhaskell/stack/issues/785 let pkgName = packageNameText (packageName package) pkgId = packageIdentifierString (packageIdentifier package) ghcVersion = getGhcVersion compilerVersion eincludeName <- -- Pre-7.8 uses plain PKG-version in tix files. if ghcVersion < $(mkVersion "7.10") then return $ Right $ Just pkgId -- We don't expect to find a package key if there is no library. else if not (packageHasLibrary package) then return $ Right Nothing -- Look in the inplace DB for the package key. -- See https://github.com/commercialhaskell/stack/issues/1181#issuecomment-148968986 else do -- GHC 8.0 uses package id instead of package key. -- See https://github.com/commercialhaskell/stack/issues/2424 let hpcNameField = if ghcVersion >= $(mkVersion "8.0") then "id" else "key" eincludeName <- findPackageFieldForBuiltPackage pkgDir (packageIdentifier package) hpcNameField case eincludeName of Left err -> do $logError err return $ Left err Right includeName -> return $ Right $ Just $ T.unpack includeName forM_ tests $ \testName -> do tixSrc <- tixFilePath (packageName package) (T.unpack testName) let report = "coverage report for " <> pkgName <> "'s test-suite \"" <> testName <> "\"" reportDir = parent tixSrc case eincludeName of Left err -> generateHpcErrorReport reportDir (sanitize (T.unpack err)) -- Restrict to just the current library code, if there is a library in the package (see -- #634 - this will likely be customizable in the future) Right mincludeName -> do let extraArgs = case mincludeName of Just includeName -> ["--include", includeName ++ ":"] Nothing -> [] mreportPath <- generateHpcReportInternal tixSrc reportDir report extraArgs extraArgs forM_ mreportPath (displayReportPath report) generateHpcReportInternal :: (StackM env m, HasEnvConfig env) => Path Abs File -> Path Abs Dir -> Text -> [String] -> [String] -> m (Maybe (Path Abs File)) generateHpcReportInternal tixSrc reportDir report extraMarkupArgs extraReportArgs = do -- If a .tix file exists, move it to the HPC output directory and generate a report for it. tixFileExists <- doesFileExist tixSrc if not tixFileExists then do $logError $ T.concat [ "Didn't find .tix for " , report , " - expected to find it at " , T.pack (toFilePath tixSrc) , "." ] return Nothing else (`catch` \err -> do let msg = show (err :: ReadProcessException) $logError (T.pack msg) generateHpcErrorReport reportDir $ sanitize msg return Nothing) $ (`onException` $logError ("Error occurred while producing " <> report)) $ do -- Directories for .mix files. hpcRelDir <- hpcRelativeDir -- Compute arguments used for both "hpc markup" and "hpc report". pkgDirs <- liftM Map.keys getLocalPackages let args = -- Use index files from all packages (allows cross-package coverage results). concatMap (\x -> ["--srcdir", toFilePathNoTrailingSep x]) pkgDirs ++ -- Look for index files in the correct dir (relative to each pkgdir). ["--hpcdir", toFilePathNoTrailingSep hpcRelDir, "--reset-hpcdirs"] menv <- getMinimalEnvOverride $logInfo $ "Generating " <> report outputLines <- liftM (map (S8.filter (/= '\r')) . S8.lines) $ readProcessStdout Nothing menv "hpc" ( "report" : toFilePath tixSrc : (args ++ extraReportArgs) ) if all ("(0/0)" `S8.isSuffixOf`) outputLines then do let msg html = T.concat [ "Error: The " , report , " did not consider any code. One possible cause of this is" , " if your test-suite builds the library code (see stack " , if html then "" else "" , "issue #1008" , if html then "" else "" , "). It may also indicate a bug in stack or" , " the hpc program. Please report this issue if you think" , " your coverage report should have meaningful results." ] $logError (msg False) generateHpcErrorReport reportDir (msg True) return Nothing else do let reportPath = reportDir $(mkRelFile "hpc_index.html") -- Print output, stripping @\r@ characters because Windows. forM_ outputLines ($logInfo . T.decodeUtf8) -- Generate the markup. void $ readProcessStdout Nothing menv "hpc" ( "markup" : toFilePath tixSrc : ("--destdir=" ++ toFilePathNoTrailingSep reportDir) : (args ++ extraMarkupArgs) ) return (Just reportPath) data HpcReportOpts = HpcReportOpts { hroptsInputs :: [Text] , hroptsAll :: Bool , hroptsDestDir :: Maybe String , hroptsOpenBrowser :: Bool } deriving (Show) generateHpcReportForTargets :: (StackM env m, HasEnvConfig env) => HpcReportOpts -> m () generateHpcReportForTargets opts = do let (tixFiles, targetNames) = partition (".tix" `T.isSuffixOf`) (hroptsInputs opts) targetTixFiles <- -- When there aren't any package component arguments, and --all -- isn't passed, default to not considering any targets. if not (hroptsAll opts) && null targetNames then return [] else do when (hroptsAll opts && not (null targetNames)) $ $logWarn $ "Since --all is used, it is redundant to specify these targets: " <> T.pack (show targetNames) (_,_,targets) <- parseTargetsFromBuildOpts AllowNoTargets defaultBuildOptsCLI { boptsCLITargets = if hroptsAll opts then [] else targetNames } liftM concat $ forM (Map.toList targets) $ \(name, target) -> case target of STUnknown -> throwString $ "Error: " ++ packageNameString name ++ " isn't a known local page" STNonLocal -> throwString $ "Error: Expected a local package, but " ++ packageNameString name ++ " is either an extra-dep or in the snapshot." STLocalComps comps -> do pkgPath <- hpcPkgPath name forM (toList comps) $ \nc -> case nc of CTest testName -> liftM (pkgPath ) $ parseRelFile (T.unpack testName ++ "/" ++ T.unpack testName ++ ".tix") _ -> fail $ "Can't specify anything except test-suites as hpc report targets (" ++ packageNameString name ++ " is used with a non test-suite target)" STLocalAll -> do pkgPath <- hpcPkgPath name exists <- doesDirExist pkgPath if exists then do (dirs, _) <- listDir pkgPath liftM concat $ forM dirs $ \dir -> do (_, files) <- listDir dir return (filter ((".tix" `isSuffixOf`) . toFilePath) files) else return [] tixPaths <- liftM (\xs -> xs ++ targetTixFiles) $ mapM (resolveFile' . T.unpack) tixFiles when (null tixPaths) $ throwString "Not generating combined report, because no targets or tix files are specified." outputDir <- hpcReportDir reportDir <- case hroptsDestDir opts of Nothing -> return (outputDir $(mkRelDir "combined/custom")) Just destDir -> do dest <- resolveDir' destDir ensureDir dest return dest let report = "combined report" mreportPath <- generateUnionReport report reportDir tixPaths forM_ mreportPath $ \reportPath -> if hroptsOpenBrowser opts then do $prettyInfo $ "Opening" <+> display reportPath <+> "in the browser." void $ liftIO $ openBrowser (toFilePath reportPath) else displayReportPath report reportPath generateHpcUnifiedReport :: (StackM env m, HasEnvConfig env) => m () generateHpcUnifiedReport = do outputDir <- hpcReportDir ensureDir outputDir (dirs, _) <- listDir outputDir tixFiles0 <- liftM (concat . concat) $ forM (filter (("combined" /=) . dirnameString) dirs) $ \dir -> do (dirs', _) <- listDir dir forM dirs' $ \dir' -> do (_, files) <- listDir dir' return (filter ((".tix" `isSuffixOf`) . toFilePath) files) extraTixFiles <- findExtraTixFiles let tixFiles = tixFiles0 ++ extraTixFiles reportDir = outputDir $(mkRelDir "combined/all") if length tixFiles < 2 then $logInfo $ T.concat [ if null tixFiles then "No tix files" else "Only one tix file" , " found in " , T.pack (toFilePath outputDir) , ", so not generating a unified coverage report." ] else do let report = "unified report" mreportPath <- generateUnionReport report reportDir tixFiles forM_ mreportPath (displayReportPath report) generateUnionReport :: (StackM env m, HasEnvConfig env) => Text -> Path Abs Dir -> [Path Abs File] -> m (Maybe (Path Abs File)) generateUnionReport report reportDir tixFiles = do (errs, tix) <- fmap (unionTixes . map removeExeModules) (mapMaybeM readTixOrLog tixFiles) $logDebug $ "Using the following tix files: " <> T.pack (show tixFiles) unless (null errs) $ $logWarn $ T.concat $ "The following modules are left out of the " : report : " due to version mismatches: " : intersperse ", " (map T.pack errs) tixDest <- liftM (reportDir ) $ parseRelFile (dirnameString reportDir ++ ".tix") ensureDir (parent tixDest) liftIO $ writeTix (toFilePath tixDest) tix generateHpcReportInternal tixDest reportDir report [] [] readTixOrLog :: (MonadLogger m, MonadIO m, MonadBaseControl IO m) => Path b File -> m (Maybe Tix) readTixOrLog path = do mtix <- liftIO (readTix (toFilePath path)) `catch` \errorCall -> do $logError $ "Error while reading tix: " <> T.pack (show (errorCall :: ErrorCall)) return Nothing when (isNothing mtix) $ $logError $ "Failed to read tix file " <> T.pack (toFilePath path) return mtix -- | Module names which contain '/' have a package name, and so they weren't built into the -- executable. removeExeModules :: Tix -> Tix removeExeModules (Tix ms) = Tix (filter (\(TixModule name _ _ _) -> '/' `elem` name) ms) unionTixes :: [Tix] -> ([String], Tix) unionTixes tixes = (Map.keys errs, Tix (Map.elems outputs)) where (errs, outputs) = Map.mapEither id $ Map.unionsWith merge $ map toMap tixes toMap (Tix ms) = Map.fromList (map (\x@(TixModule k _ _ _) -> (k, Right x)) ms) merge (Right (TixModule k hash1 len1 tix1)) (Right (TixModule _ hash2 len2 tix2)) | hash1 == hash2 && len1 == len2 = Right (TixModule k hash1 len1 (zipWith (+) tix1 tix2)) merge _ _ = Left () generateHpcMarkupIndex :: (StackM env m, HasEnvConfig env) => m () generateHpcMarkupIndex = do outputDir <- hpcReportDir let outputFile = outputDir $(mkRelFile "index.html") ensureDir outputDir (dirs, _) <- listDir outputDir rows <- liftM (catMaybes . concat) $ forM dirs $ \dir -> do (subdirs, _) <- listDir dir forM subdirs $ \subdir -> do let indexPath = subdir $(mkRelFile "hpc_index.html") exists' <- doesFileExist indexPath if not exists' then return Nothing else do relPath <- stripDir outputDir indexPath let package = dirname dir testsuite = dirname subdir return $ Just $ T.concat [ "" , pathToHtml package , "" , pathToHtml testsuite , "" ] liftIO $ T.writeFile (toFilePath outputFile) $ T.concat $ [ "" -- Part of the css from HPC's output HTML , "" , "" , "" ] ++ (if null rows then [ "No hpc_index.html files found in \"" , pathToHtml outputDir , "\"." ] else [ "" , "

NOTE: This is merely a listing of the html files found in the coverage reports directory. Some of these reports may be old.

" , "" ] ++ rows ++ ["
PackageTestSuiteModification Time
"]) ++ [""] unless (null rows) $ $logInfo $ "\nAn index of the generated HTML coverage reports is available at " <> T.pack (toFilePath outputFile) generateHpcErrorReport :: MonadIO m => Path Abs Dir -> Text -> m () generateHpcErrorReport dir err = do ensureDir dir liftIO $ T.writeFile (toFilePath (dir $(mkRelFile "hpc_index.html"))) $ T.concat [ "" , "

HPC Report Generation Error

" , "

" , err , "

" , "" ] pathToHtml :: Path b t -> Text pathToHtml = T.dropWhileEnd (=='/') . sanitize . toFilePath sanitize :: String -> Text sanitize = LT.toStrict . htmlEscape . LT.pack dirnameString :: Path r Dir -> String dirnameString = dropWhileEnd isPathSeparator . toFilePath . dirname findPackageFieldForBuiltPackage :: (StackM env m, HasEnvConfig env) => Path Abs Dir -> PackageIdentifier -> Text -> m (Either Text Text) findPackageFieldForBuiltPackage pkgDir pkgId field = do distDir <- distDirFromDir pkgDir let inplaceDir = distDir $(mkRelDir "package.conf.inplace") pkgIdStr = packageIdentifierString pkgId notFoundErr = return $ Left $ "Failed to find package key for " <> T.pack pkgIdStr extractField path = do contents <- liftIO $ T.readFile (toFilePath path) case asum (map (T.stripPrefix (field <> ": ")) (T.lines contents)) of Just result -> return $ Right result Nothing -> notFoundErr cabalVer <- view cabalVersionL if cabalVer < $(mkVersion "1.24") then do path <- liftM (inplaceDir ) $ parseRelFile (pkgIdStr ++ "-inplace.conf") $logDebug $ "Parsing config in Cabal < 1.24 location: " <> T.pack (toFilePath path) exists <- doesFileExist path if exists then extractField path else notFoundErr else do -- With Cabal-1.24, it's in a different location. $logDebug $ "Scanning " <> T.pack (toFilePath inplaceDir) <> " for files matching " <> T.pack pkgIdStr (_, files) <- handleIO (const $ return ([], [])) $ listDir inplaceDir $logDebug $ T.pack (show files) case mapMaybe (\file -> fmap (const file) . (T.stripSuffix ".conf" <=< T.stripPrefix (T.pack (pkgIdStr ++ "-"))) . T.pack . toFilePath . filename $ file) files of [] -> notFoundErr [path] -> extractField path _ -> return $ Left $ "Multiple files matching " <> T.pack (pkgIdStr ++ "-*.conf") <> " found in " <> T.pack (toFilePath inplaceDir) <> ". Maybe try 'stack clean' on this package?" displayReportPath :: (StackM env m, HasAnsiAnn (Ann a), Display a) => Text -> a -> m () displayReportPath report reportPath = $prettyInfo $ "The" <+> fromString (T.unpack report) <+> "is available at" <+> display reportPath findExtraTixFiles :: (StackM env m , HasEnvConfig env) => m [Path Abs File] findExtraTixFiles = do outputDir <- hpcReportDir let dir = outputDir $(mkRelDir "extra-tix-files") dirExists <- doesDirExist dir if dirExists then do (_, files) <- listDir dir return $ filter ((".tix" `isSuffixOf`) . toFilePath) files else return [] stack-1.5.1/src/Stack/Docker.hs0000644000000000000000000012412313135652051014414 0ustar0000000000000000{-# LANGUAGE CPP, ConstraintKinds, DeriveDataTypeable, FlexibleContexts, MultiWayIf, NamedFieldPuns, OverloadedStrings, PackageImports, RankNTypes, RecordWildCards, ScopedTypeVariables, TemplateHaskell, TupleSections #-} -- | Run commands in Docker containers module Stack.Docker (cleanup ,CleanupOpts(..) ,CleanupAction(..) ,dockerCleanupCmdName ,dockerCmdName ,dockerHelpOptName ,dockerPullCmdName ,entrypoint ,preventInContainer ,pull ,reexecWithOptionalContainer ,reset ,reExecArgName ,StackDockerException(..) ) where import Control.Applicative import Control.Concurrent.MVar.Lifted (MVar,modifyMVar_,newMVar) import Control.Exception.Lifted import Control.Monad import Control.Monad.Catch (MonadThrow,throwM,MonadCatch) import Control.Monad.IO.Class (MonadIO,liftIO) import Control.Monad.Logger (MonadLogger,logError,logInfo,logWarn) import Control.Monad.Reader (MonadReader,runReaderT) import Control.Monad.Trans.Control (MonadBaseControl) import Control.Monad.Writer (execWriter,runWriter,tell) import qualified Crypto.Hash as Hash (Digest, MD5, hash) import Data.Aeson.Extended (FromJSON(..),(.:),(.:?),(.!=),eitherDecode) import Data.ByteString.Builder (stringUtf8,charUtf8,toLazyByteString) import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Lazy.Char8 as LBS import Data.Char (isSpace,toUpper,isAscii,isDigit) import Data.Conduit.List (sinkNull) import Data.List (dropWhileEnd,intercalate,isPrefixOf,isInfixOf,foldl') import Data.List.Extra (trim, nubOrd) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Maybe import Data.Ord (Down(..)) import Data.Streaming.Process (ProcessExitedUnsuccessfully(..)) import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T import Data.Time (UTCTime,LocalTime(..),diffDays,utcToLocalTime,getZonedTime,ZonedTime(..)) import Data.Version (showVersion) import GHC.Exts (sortWith) import Path import Path.Extra (toFilePathNoTrailingSep) import Path.IO hiding (canonicalizePath) import qualified Paths_stack as Meta import Prelude -- Fix redundant import warnings import Stack.Config (getInContainer) import Stack.Constants import Stack.Docker.GlobalDB import Stack.Types.PackageIndex import Stack.Types.Version import Stack.Types.Config import Stack.Types.Docker import Stack.Types.Internal import Stack.Types.StackT import Stack.Setup (ensureDockerStackExe) import System.Directory (canonicalizePath,getHomeDirectory) import System.Environment (getEnv,getEnvironment,getProgName,getArgs,getExecutablePath) import System.Exit (exitSuccess, exitWith, ExitCode(..)) import qualified System.FilePath as FP import System.IO (stderr,stdin,stdout,hIsTerminalDevice, hClose) import System.IO.Error (isDoesNotExistError) import System.IO.Unsafe (unsafePerformIO) import qualified System.PosixCompat.User as User import qualified System.PosixCompat.Files as Files import System.Process (CreateProcess(..), StdStream(..), waitForProcess) import System.Process.PagerEditor (editByteString) import System.Process.Read import System.Process.Run import Text.Printf (printf) #ifndef WINDOWS import Control.Concurrent (threadDelay) import qualified Control.Monad.Trans.Control as Control import System.Posix.Signals import qualified System.Posix.User as PosixUser #endif -- | If Docker is enabled, re-runs the currently running OS command in a Docker container. -- Otherwise, runs the inner action. -- -- This takes an optional release action which should be taken IFF control is -- transfering away from the current process to the intra-container one. The main use -- for this is releasing a lock. After launching reexecution, the host process becomes -- nothing but an manager for the call into docker and thus may not hold the lock. reexecWithOptionalContainer :: (StackM env m, HasConfig env) => Maybe (Path Abs Dir) -> Maybe (m ()) -> IO () -> Maybe (m ()) -> Maybe (m ()) -> m () reexecWithOptionalContainer mprojectRoot = execWithOptionalContainer mprojectRoot getCmdArgs where getCmdArgs docker envOverride imageInfo isRemoteDocker = do config <- view configL deUser <- if fromMaybe (not isRemoteDocker) (dockerSetUser docker) then liftIO $ do duUid <- User.getEffectiveUserID duGid <- User.getEffectiveGroupID duGroups <- nubOrd <$> User.getGroups duUmask <- Files.setFileCreationMask 0o022 -- Only way to get old umask seems to be to change it, so set it back afterward _ <- Files.setFileCreationMask duUmask return (Just DockerUser{..}) else return Nothing args <- fmap (["--" ++ reExecArgName ++ "=" ++ showVersion Meta.version ,"--" ++ dockerEntrypointArgName ,show DockerEntrypoint{..}] ++) (liftIO getArgs) case dockerStackExe (configDocker config) of Just DockerStackExeHost | configPlatform config == dockerContainerPlatform -> do exePath <- liftIO getExecutablePath cmdArgs args exePath | otherwise -> throwM UnsupportedStackExeHostPlatformException Just DockerStackExeImage -> do progName <- liftIO getProgName return (FP.takeBaseName progName, args, [], []) Just (DockerStackExePath path) -> do exePath <- liftIO $ canonicalizePath (toFilePath path) cmdArgs args exePath Just DockerStackExeDownload -> exeDownload args Nothing | configPlatform config == dockerContainerPlatform -> do (exePath,exeTimestamp,misCompatible) <- liftIO $ do exePath <- liftIO getExecutablePath exeTimestamp <- resolveFile' exePath >>= getModificationTime isKnown <- liftIO $ getDockerImageExe config (iiId imageInfo) exePath exeTimestamp return (exePath, exeTimestamp, isKnown) case misCompatible of Just True -> cmdArgs args exePath Just False -> exeDownload args Nothing -> do e <- try $ sinkProcessStderrStdout Nothing envOverride "docker" [ "run" , "-v" , exePath ++ ":" ++ "/tmp/stack" , iiId imageInfo , "/tmp/stack" , "--version"] sinkNull sinkNull let compatible = case e of Left (ProcessExitedUnsuccessfully _ _) -> False Right _ -> True liftIO $ setDockerImageExe config (iiId imageInfo) exePath exeTimestamp compatible if compatible then cmdArgs args exePath else exeDownload args Nothing -> exeDownload args exeDownload args = do exePath <- ensureDockerStackExe dockerContainerPlatform cmdArgs args (toFilePath exePath) cmdArgs args exePath = do let mountPath = hostBinDir FP. FP.takeBaseName exePath return (mountPath, args, [], [Mount exePath mountPath]) -- | If Docker is enabled, re-runs the OS command returned by the second argument in a -- Docker container. Otherwise, runs the inner action. -- -- This takes an optional release action just like `reexecWithOptionalContainer`. execWithOptionalContainer :: (StackM env m, HasConfig env) => Maybe (Path Abs Dir) -> GetCmdArgs env m -> Maybe (m ()) -> IO () -> Maybe (m ()) -> Maybe (m ()) -> m () execWithOptionalContainer mprojectRoot getCmdArgs mbefore inner mafter mrelease = do config <- view configL inContainer <- getInContainer isReExec <- view reExecL if | inContainer && not isReExec && (isJust mbefore || isJust mafter) -> throwM OnlyOnHostException | inContainer -> liftIO (do inner exitSuccess) | not (dockerEnable (configDocker config)) -> do fromMaybeAction mbefore liftIO inner fromMaybeAction mafter liftIO exitSuccess | otherwise -> do fromMaybeAction mrelease runContainerAndExit getCmdArgs mprojectRoot (fromMaybeAction mbefore) (fromMaybeAction mafter) where fromMaybeAction Nothing = return () fromMaybeAction (Just hook) = hook -- | Error if running in a container. preventInContainer :: (MonadIO m,MonadThrow m) => m () -> m () preventInContainer inner = do inContainer <- getInContainer if inContainer then throwM OnlyOnHostException else inner -- | Run a command in a new Docker container, then exit the process. runContainerAndExit :: (StackM env m, HasConfig env) => GetCmdArgs env m -> Maybe (Path Abs Dir) -- ^ Project root (maybe) -> m () -- ^ Action to run before -> m () -- ^ Action to run after -> m () runContainerAndExit getCmdArgs mprojectRoot before after = do config <- view configL let docker = configDocker config envOverride <- getEnvOverride (configPlatform config) checkDockerVersion envOverride docker (env,isStdinTerminal,isStderrTerminal,homeDir) <- liftIO $ (,,,) <$> getEnvironment <*> hIsTerminalDevice stdin <*> hIsTerminalDevice stderr <*> (parseAbsDir =<< getHomeDirectory) isStdoutTerminal <- view terminalL let dockerHost = lookup "DOCKER_HOST" env dockerCertPath = lookup "DOCKER_CERT_PATH" env bamboo = lookup "bamboo_buildKey" env jenkins = lookup "JENKINS_HOME" env msshAuthSock = lookup "SSH_AUTH_SOCK" env muserEnv = lookup "USER" env isRemoteDocker = maybe False (isPrefixOf "tcp://") dockerHost image = dockerImage docker when (isRemoteDocker && maybe False (isInfixOf "boot2docker") dockerCertPath) ($logWarn "Warning: Using boot2docker is NOT supported, and not likely to perform well.") maybeImageInfo <- inspect envOverride image imageInfo@Inspect{..} <- case maybeImageInfo of Just ii -> return ii Nothing | dockerAutoPull docker -> do pullImage envOverride docker image mii2 <- inspect envOverride image case mii2 of Just ii2 -> return ii2 Nothing -> throwM (InspectFailedException image) | otherwise -> throwM (NotPulledException image) sandboxDir <- projectDockerSandboxDir projectRoot let ImageConfig {..} = iiConfig imageEnvVars = map (break (== '=')) icEnv platformVariant = show $ hashRepoName image stackRoot = configStackRoot config sandboxHomeDir = sandboxDir homeDirName isTerm = not (dockerDetach docker) && isStdinTerminal && isStdoutTerminal && isStderrTerminal keepStdinOpen = not (dockerDetach docker) && -- Workaround for https://github.com/docker/docker/issues/12319 -- This is fixed in Docker 1.9.1, but will leave the workaround -- in place for now, for users who haven't upgraded yet. (isTerm || (isNothing bamboo && isNothing jenkins)) hostBinDirPath <- parseAbsDir hostBinDir newPathEnv <- augmentPath [ hostBinDirPath , sandboxHomeDir $(mkRelDir ".local/bin")] (T.pack <$> lookupImageEnv "PATH" imageEnvVars) (cmnd,args,envVars,extraMount) <- getCmdArgs docker envOverride imageInfo isRemoteDocker pwd <- getCurrentDir liftIO (do updateDockerImageLastUsed config iiId (toFilePath projectRoot) mapM_ ensureDir [sandboxHomeDir, stackRoot]) -- Since $HOME is now mounted in the same place in the container we can -- just symlink $HOME/.ssh to the right place for the stack docker user let sshDir = homeDir sshRelDir sshDirExists <- doesDirExist sshDir sshSandboxDirExists <- liftIO (Files.fileExist (toFilePathNoTrailingSep (sandboxHomeDir sshRelDir))) when (sshDirExists && not sshSandboxDirExists) (liftIO (Files.createSymbolicLink (toFilePathNoTrailingSep sshDir) (toFilePathNoTrailingSep (sandboxHomeDir sshRelDir)))) containerID <- (trim . decodeUtf8) <$> readDockerProcess envOverride (Just projectRoot) (concat [["create" ,"--net=host" ,"-e",inContainerEnvVar ++ "=1" ,"-e",stackRootEnvVar ++ "=" ++ toFilePathNoTrailingSep stackRoot ,"-e",platformVariantEnvVar ++ "=dk" ++ platformVariant ,"-e","HOME=" ++ toFilePathNoTrailingSep sandboxHomeDir ,"-e","PATH=" ++ T.unpack newPathEnv ,"-e","PWD=" ++ toFilePathNoTrailingSep pwd ,"-v",toFilePathNoTrailingSep homeDir ++ ":" ++ toFilePathNoTrailingSep homeDir ,"-v",toFilePathNoTrailingSep stackRoot ++ ":" ++ toFilePathNoTrailingSep stackRoot ,"-v",toFilePathNoTrailingSep projectRoot ++ ":" ++ toFilePathNoTrailingSep projectRoot ,"-v",toFilePathNoTrailingSep sandboxHomeDir ++ ":" ++ toFilePathNoTrailingSep sandboxHomeDir ,"-w",toFilePathNoTrailingSep pwd] ,case muserEnv of Nothing -> [] Just userEnv -> ["-e","USER=" ++ userEnv] ,case msshAuthSock of Nothing -> [] Just sshAuthSock -> ["-e","SSH_AUTH_SOCK=" ++ sshAuthSock ,"-v",sshAuthSock ++ ":" ++ sshAuthSock] -- Disable the deprecated entrypoint in FP Complete-generated images ,["--entrypoint=/usr/bin/env" | isJust (lookupImageEnv oldSandboxIdEnvVar imageEnvVars) && (icEntrypoint == ["/usr/local/sbin/docker-entrypoint"] || icEntrypoint == ["/root/entrypoint.sh"])] ,concatMap (\(k,v) -> ["-e", k ++ "=" ++ v]) envVars ,concatMap mountArg (extraMount ++ dockerMount docker) ,concatMap (\nv -> ["-e", nv]) (dockerEnv docker) ,case dockerContainerName docker of Just name -> ["--name=" ++ name] Nothing -> [] ,["-t" | isTerm] ,["-i" | keepStdinOpen] ,dockerRunArgs docker ,[image] ,[cmnd] ,args]) before #ifndef WINDOWS runInBase <- Control.liftBaseWith $ \run -> return (void . run) oldHandlers <- forM [sigINT,sigABRT,sigHUP,sigPIPE,sigTERM,sigUSR1,sigUSR2] $ \sig -> do let sigHandler = runInBase $ do readProcessNull Nothing envOverride "docker" ["kill","--signal=" ++ show sig,containerID] when (sig `elem` [sigTERM,sigABRT]) $ do -- Give the container 30 seconds to exit gracefully, then send a sigKILL to force it liftIO $ threadDelay 30000000 readProcessNull Nothing envOverride "docker" ["kill",containerID] oldHandler <- liftIO $ installHandler sig (Catch sigHandler) Nothing return (sig, oldHandler) #endif let cmd = Cmd Nothing "docker" envOverride (concat [["start"] ,["-a" | not (dockerDetach docker)] ,["-i" | keepStdinOpen] ,[containerID]]) e <- finally (try $ callProcess' (\cp -> cp { delegate_ctlc = False }) cmd) (do unless (dockerPersist docker || dockerDetach docker) $ catch (readProcessNull Nothing envOverride "docker" ["rm","-f",containerID]) (\(_::ReadProcessException) -> return ()) #ifndef WINDOWS forM_ oldHandlers $ \(sig,oldHandler) -> liftIO $ installHandler sig oldHandler Nothing #endif ) case e of Left (ProcessExitedUnsuccessfully _ ec) -> liftIO (exitWith ec) Right () -> do after liftIO exitSuccess where -- This is using a hash of the Docker repository (without tag or digest) to ensure -- binaries/libraries aren't shared between Docker and host (or incompatible Docker images) hashRepoName :: String -> Hash.Digest Hash.MD5 hashRepoName = Hash.hash . BS.pack . takeWhile (\c -> c /= ':' && c /= '@') lookupImageEnv name vars = case lookup name vars of Just ('=':val) -> Just val _ -> Nothing mountArg (Mount host container) = ["-v",host ++ ":" ++ container] projectRoot = fromMaybeProjectRoot mprojectRoot sshRelDir = $(mkRelDir ".ssh/") -- | Clean-up old docker images and containers. cleanup :: (StackM env m, HasConfig env) => CleanupOpts -> m () cleanup opts = do config <- view configL let docker = configDocker config envOverride <- getEnvOverride (configPlatform config) checkDockerVersion envOverride docker let runDocker = readDockerProcess envOverride Nothing imagesOut <- runDocker ["images","--no-trunc","-f","dangling=false"] danglingImagesOut <- runDocker ["images","--no-trunc","-f","dangling=true"] runningContainersOut <- runDocker ["ps","-a","--no-trunc","-f","status=running"] restartingContainersOut <- runDocker ["ps","-a","--no-trunc","-f","status=restarting"] exitedContainersOut <- runDocker ["ps","-a","--no-trunc","-f","status=exited"] pausedContainersOut <- runDocker ["ps","-a","--no-trunc","-f","status=paused"] let imageRepos = parseImagesOut imagesOut danglingImageHashes = Map.keys (parseImagesOut danglingImagesOut) runningContainers = parseContainersOut runningContainersOut ++ parseContainersOut restartingContainersOut stoppedContainers = parseContainersOut exitedContainersOut ++ parseContainersOut pausedContainersOut inspectMap <- inspects envOverride (Map.keys imageRepos ++ danglingImageHashes ++ map fst stoppedContainers ++ map fst runningContainers) (imagesLastUsed,curTime) <- liftIO ((,) <$> getDockerImagesLastUsed config <*> getZonedTime) let planWriter = buildPlan curTime imagesLastUsed imageRepos danglingImageHashes stoppedContainers runningContainers inspectMap plan = toLazyByteString (execWriter planWriter) plan' <- case dcAction opts of CleanupInteractive -> liftIO (editByteString (intercalate "-" [stackProgName ,dockerCmdName ,dockerCleanupCmdName ,"plan"]) plan) CleanupImmediate -> return plan CleanupDryRun -> do liftIO (LBS.hPut stdout plan) return LBS.empty mapM_ (performPlanLine envOverride) (reverse (filter filterPlanLine (lines (LBS.unpack plan')))) allImageHashesOut <- runDocker ["images","-aq","--no-trunc"] liftIO (pruneDockerImagesLastUsed config (lines (decodeUtf8 allImageHashesOut))) where filterPlanLine line = case line of c:_ | isSpace c -> False _ -> True performPlanLine envOverride line = case filter (not . null) (words (takeWhile (/= '#') line)) of [] -> return () (c:_):t:v:_ -> do args <- if | toUpper c == 'R' && t == imageStr -> do $logInfo (concatT ["Removing image: '",v,"'"]) return ["rmi",v] | toUpper c == 'R' && t == containerStr -> do $logInfo (concatT ["Removing container: '",v,"'"]) return ["rm","-f",v] | otherwise -> throwM (InvalidCleanupCommandException line) e <- try (readDockerProcess envOverride Nothing args) case e of Left ex@ProcessFailed{} -> $logError (concatT ["Could not remove: '",v,"': ", show ex]) Left e' -> throwM e' Right _ -> return () _ -> throwM (InvalidCleanupCommandException line) parseImagesOut = Map.fromListWith (++) . map parseImageRepo . drop 1 . lines . decodeUtf8 where parseImageRepo :: String -> (String, [String]) parseImageRepo line = case words line of repo:tag:hash:_ | repo == "" -> (hash,[]) | tag == "" -> (hash,[repo]) | otherwise -> (hash,[repo ++ ":" ++ tag]) _ -> throw (InvalidImagesOutputException line) parseContainersOut = map parseContainer . drop 1 . lines . decodeUtf8 where parseContainer line = case words line of hash:image:rest -> (hash,(image,last rest)) _ -> throw (InvalidPSOutputException line) buildPlan curTime imagesLastUsed imageRepos danglingImageHashes stoppedContainers runningContainers inspectMap = do case dcAction opts of CleanupInteractive -> do buildStrLn (concat ["# STACK DOCKER CLEANUP PLAN" ,"\n#" ,"\n# When you leave the editor, the lines in this plan will be processed." ,"\n#" ,"\n# Lines that begin with 'R' denote an image or container that will be." ,"\n# removed. You may change the first character to/from 'R' to remove/keep" ,"\n# and image or container that would otherwise be kept/removed." ,"\n#" ,"\n# To cancel the cleanup, delete all lines in this file." ,"\n#" ,"\n# By default, the following images/containers will be removed:" ,"\n#"]) buildDefault dcRemoveKnownImagesLastUsedDaysAgo "Known images last used" buildDefault dcRemoveUnknownImagesCreatedDaysAgo "Unknown images created" buildDefault dcRemoveDanglingImagesCreatedDaysAgo "Dangling images created" buildDefault dcRemoveStoppedContainersCreatedDaysAgo "Stopped containers created" buildDefault dcRemoveRunningContainersCreatedDaysAgo "Running containers created" buildStrLn (concat ["#" ,"\n# The default plan can be adjusted using command-line arguments." ,"\n# Run '" ++ unwords [stackProgName, dockerCmdName, dockerCleanupCmdName] ++ " --help' for details." ,"\n#"]) _ -> buildStrLn (unlines ["# Lines that begin with 'R' denote an image or container that will be." ,"# removed."]) buildSection "KNOWN IMAGES (pulled/used by stack)" imagesLastUsed buildKnownImage buildSection "UNKNOWN IMAGES (not managed by stack)" (sortCreated (Map.toList (foldl' (\m (h,_) -> Map.delete h m) imageRepos imagesLastUsed))) buildUnknownImage buildSection "DANGLING IMAGES (no named references and not depended on by other images)" (sortCreated (map (,()) danglingImageHashes)) buildDanglingImage buildSection "STOPPED CONTAINERS" (sortCreated stoppedContainers) (buildContainer (dcRemoveStoppedContainersCreatedDaysAgo opts)) buildSection "RUNNING CONTAINERS" (sortCreated runningContainers) (buildContainer (dcRemoveRunningContainersCreatedDaysAgo opts)) where buildDefault accessor description = case accessor opts of Just days -> buildStrLn ("# - " ++ description ++ " at least " ++ showDays days ++ ".") Nothing -> return () sortCreated = sortWith (\(_,_,x) -> Down x) . mapMaybe (\(h,r) -> case Map.lookup h inspectMap of Nothing -> Nothing Just ii -> Just (h,r,iiCreated ii)) buildSection sectionHead items itemBuilder = do let (anyWrote,b) = runWriter (forM items itemBuilder) when (or anyWrote) $ do buildSectionHead sectionHead tell b buildKnownImage (imageHash,lastUsedProjects) = case Map.lookup imageHash imageRepos of Just repos@(_:_) -> do case lastUsedProjects of (l,_):_ -> forM_ repos (buildImageTime (dcRemoveKnownImagesLastUsedDaysAgo opts) l) _ -> forM_ repos buildKeepImage forM_ lastUsedProjects buildProject buildInspect imageHash return True _ -> return False buildUnknownImage (hash, repos, created) = case repos of [] -> return False _ -> do forM_ repos (buildImageTime (dcRemoveUnknownImagesCreatedDaysAgo opts) created) buildInspect hash return True buildDanglingImage (hash, (), created) = do buildImageTime (dcRemoveDanglingImagesCreatedDaysAgo opts) created hash buildInspect hash return True buildContainer removeAge (hash,(image,name),created) = do let disp = name ++ " (image: " ++ image ++ ")" buildTime containerStr removeAge created disp buildInspect hash return True buildProject (lastUsedTime, projectPath) = buildInfo ("Last used " ++ showDaysAgo lastUsedTime ++ " in " ++ projectPath) buildInspect hash = case Map.lookup hash inspectMap of Just Inspect{iiCreated,iiVirtualSize} -> buildInfo ("Created " ++ showDaysAgo iiCreated ++ maybe "" (\s -> " (size: " ++ printf "%g" (fromIntegral s / 1024.0 / 1024.0 :: Float) ++ "M)") iiVirtualSize) Nothing -> return () showDays days = case days of 0 -> "today" 1 -> "yesterday" n -> show n ++ " days ago" showDaysAgo oldTime = showDays (daysAgo oldTime) daysAgo oldTime = let ZonedTime (LocalTime today _) zone = curTime LocalTime oldDay _ = utcToLocalTime zone oldTime in diffDays today oldDay buildImageTime = buildTime imageStr buildTime t removeAge time disp = case removeAge of Just d | daysAgo time >= d -> buildStrLn ("R " ++ t ++ " " ++ disp) _ -> buildKeep t disp buildKeep t d = buildStrLn (" " ++ t ++ " " ++ d) buildKeepImage = buildKeep imageStr buildSectionHead s = buildStrLn ("\n#\n# " ++ s ++ "\n#\n") buildInfo = buildStrLn . (" # " ++) buildStrLn l = do buildStr l tell (charUtf8 '\n') buildStr = tell . stringUtf8 imageStr = "image" containerStr = "container" -- | Inspect Docker image or container. inspect :: (MonadIO m,MonadLogger m,MonadBaseControl IO m,MonadCatch m) => EnvOverride -> String -> m (Maybe Inspect) inspect envOverride image = do results <- inspects envOverride [image] case Map.toList results of [] -> return Nothing [(_,i)] -> return (Just i) _ -> throwM (InvalidInspectOutputException "expect a single result") -- | Inspect multiple Docker images and/or containers. inspects :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => EnvOverride -> [String] -> m (Map String Inspect) inspects _ [] = return Map.empty inspects envOverride images = do maybeInspectOut <- try (readDockerProcess envOverride Nothing ("inspect" : images)) case maybeInspectOut of Right inspectOut -> -- filtering with 'isAscii' to workaround @docker inspect@ output containing invalid UTF-8 case eitherDecode (LBS.pack (filter isAscii (decodeUtf8 inspectOut))) of Left msg -> throwM (InvalidInspectOutputException msg) Right results -> return (Map.fromList (map (\r -> (iiId r,r)) results)) Left (ProcessFailed _ _ _ err) | "Error: No such image" `LBS.isPrefixOf` err -> return Map.empty Left e -> throwM e -- | Pull latest version of configured Docker image from registry. pull :: (StackM env m, HasConfig env) => m () pull = do config <- view configL let docker = configDocker config envOverride <- getEnvOverride (configPlatform config) checkDockerVersion envOverride docker pullImage envOverride docker (dockerImage docker) -- | Pull Docker image from registry. pullImage :: (MonadLogger m,MonadIO m,MonadThrow m) => EnvOverride -> DockerOpts -> String -> m () pullImage envOverride docker image = do $logInfo (concatT ["Pulling image from registry: '",image,"'"]) when (dockerRegistryLogin docker) (do $logInfo "You may need to log in." callProcess $ Cmd Nothing "docker" envOverride (concat [["login"] ,maybe [] (\n -> ["--username=" ++ n]) (dockerRegistryUsername docker) ,maybe [] (\p -> ["--password=" ++ p]) (dockerRegistryPassword docker) ,[takeWhile (/= '/') image]])) -- We redirect the stdout of the process to stderr so that the output -- of @docker pull@ will not interfere with the output of other -- commands when using --auto-docker-pull. See issue #2733. let stdoutToStderr cp = cp { std_out = UseHandle stderr , std_err = UseHandle stderr , std_in = CreatePipe } (Just hin, _, _, ph) <- createProcess' "pullImage" stdoutToStderr $ Cmd Nothing "docker" envOverride ["pull",image] liftIO (hClose hin) ec <- liftIO (waitForProcess ph) case ec of ExitSuccess -> return () ExitFailure _ -> throwM (PullFailedException image) -- | Check docker version (throws exception if incorrect) checkDockerVersion :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => EnvOverride -> DockerOpts -> m () checkDockerVersion envOverride docker = do dockerExists <- doesExecutableExist envOverride "docker" unless dockerExists (throwM DockerNotInstalledException) dockerVersionOut <- readDockerProcess envOverride Nothing ["--version"] case words (decodeUtf8 dockerVersionOut) of (_:_:v:_) -> case parseVersionFromString (stripVersion v) of Just v' | v' < minimumDockerVersion -> throwM (DockerTooOldException minimumDockerVersion v') | v' `elem` prohibitedDockerVersions -> throwM (DockerVersionProhibitedException prohibitedDockerVersions v') | not (v' `withinRange` dockerRequireDockerVersion docker) -> throwM (BadDockerVersionException (dockerRequireDockerVersion docker) v') | otherwise -> return () _ -> throwM InvalidVersionOutputException _ -> throwM InvalidVersionOutputException where minimumDockerVersion = $(mkVersion "1.6.0") prohibitedDockerVersions = [] stripVersion v = takeWhile (/= '-') (dropWhileEnd (not . isDigit) v) -- | Remove the project's Docker sandbox. reset :: (MonadIO m, MonadReader env m, HasConfig env) => Maybe (Path Abs Dir) -> Bool -> m () reset maybeProjectRoot keepHome = do dockerSandboxDir <- projectDockerSandboxDir projectRoot liftIO (removeDirectoryContents dockerSandboxDir [homeDirName | keepHome] []) where projectRoot = fromMaybeProjectRoot maybeProjectRoot -- | The Docker container "entrypoint": special actions performed when first entering -- a container, such as switching the UID/GID to the "outside-Docker" user's. entrypoint :: (MonadIO m, MonadBaseControl IO m, MonadCatch m, MonadLogger m) => Config -> DockerEntrypoint -> m () entrypoint config@Config{..} DockerEntrypoint{..} = modifyMVar_ entrypointMVar $ \alreadyRan -> do -- Only run the entrypoint once unless alreadyRan $ do envOverride <- getEnvOverride configPlatform homeDir <- parseAbsDir =<< liftIO (getEnv "HOME") -- Get the UserEntry for the 'stack' user in the image, if it exists estackUserEntry0 <- liftIO $ tryJust (guard . isDoesNotExistError) $ User.getUserEntryForName stackUserName -- Switch UID/GID if needed, and update user's home directory case deUser of Nothing -> return () Just (DockerUser 0 _ _ _) -> return () Just du -> updateOrCreateStackUser envOverride estackUserEntry0 homeDir du case estackUserEntry0 of Left _ -> return () Right ue -> do -- If the 'stack' user exists in the image, copy any build plans and package indices from -- its original home directory to the host's stack root, to avoid needing to download them origStackHomeDir <- parseAbsDir (User.homeDirectory ue) let origStackRoot = origStackHomeDir $(mkRelDir ("." ++ stackProgName)) buildPlanDirExists <- doesDirExist (buildPlanDir origStackRoot) when buildPlanDirExists $ do (_, buildPlans) <- listDir (buildPlanDir origStackRoot) forM_ buildPlans $ \srcBuildPlan -> do let destBuildPlan = buildPlanDir configStackRoot filename srcBuildPlan exists <- doesFileExist destBuildPlan unless exists $ do ensureDir (parent destBuildPlan) copyFile srcBuildPlan destBuildPlan forM_ configPackageIndices $ \pkgIdx -> do msrcIndex <- flip runReaderT (config{configStackRoot = origStackRoot}) $ do srcIndex <- configPackageIndex (indexName pkgIdx) exists <- doesFileExist srcIndex return $ if exists then Just srcIndex else Nothing case msrcIndex of Nothing -> return () Just srcIndex -> do flip runReaderT config $ do destIndex <- configPackageIndex (indexName pkgIdx) exists <- doesFileExist destIndex unless exists $ do ensureDir (parent destIndex) copyFile srcIndex destIndex return True where updateOrCreateStackUser envOverride estackUserEntry homeDir DockerUser{..} = do case estackUserEntry of Left _ -> do -- If no 'stack' user in image, create one with correct UID/GID and home directory readProcessNull Nothing envOverride "groupadd" ["-o" ,"--gid",show duGid ,stackUserName] readProcessNull Nothing envOverride "useradd" ["-oN" ,"--uid",show duUid ,"--gid",show duGid ,"--home",toFilePathNoTrailingSep homeDir ,stackUserName] Right _ -> do -- If there is already a 'stack' user in the image, adjust its UID/GID and home directory readProcessNull Nothing envOverride "usermod" ["-o" ,"--uid",show duUid ,"--home",toFilePathNoTrailingSep homeDir ,stackUserName] readProcessNull Nothing envOverride "groupmod" ["-o" ,"--gid",show duGid ,stackUserName] forM_ duGroups $ \gid -> do readProcessNull Nothing envOverride "groupadd" ["-o" ,"--gid",show gid ,"group" ++ show gid] -- 'setuid' to the wanted UID and GID liftIO $ do User.setGroupID duGid #ifndef WINDOWS PosixUser.setGroups duGroups #endif User.setUserID duUid _ <- Files.setFileCreationMask duUmask return () stackUserName = "stack"::String -- | MVar used to ensure the Docker entrypoint is performed exactly once entrypointMVar :: MVar Bool {-# NOINLINE entrypointMVar #-} entrypointMVar = unsafePerformIO (newMVar False) -- | Remove the contents of a directory, without removing the directory itself. -- This is used instead of 'FS.removeTree' to clear bind-mounted directories, since -- removing the root of the bind-mount won't work. removeDirectoryContents :: Path Abs Dir -- ^ Directory to remove contents of -> [Path Rel Dir] -- ^ Top-level directory names to exclude from removal -> [Path Rel File] -- ^ Top-level file names to exclude from removal -> IO () removeDirectoryContents path excludeDirs excludeFiles = do isRootDir <- doesDirExist path when isRootDir (do (lsd,lsf) <- listDir path forM_ lsd (\d -> unless (dirname d `elem` excludeDirs) (removeDirRecur d)) forM_ lsf (\f -> unless (filename f `elem` excludeFiles) (removeFile f))) -- | Produce a strict 'S.ByteString' from the stdout of a -- process. Throws a 'ReadProcessException' exception if the -- process fails. Logs process's stderr using @$logError@. readDockerProcess :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => EnvOverride -> Maybe (Path Abs Dir) -> [String] -> m BS.ByteString readDockerProcess envOverride mpwd = readProcessStdout mpwd envOverride "docker" -- | Name of home directory within docker sandbox. homeDirName :: Path Rel Dir homeDirName = $(mkRelDir "_home/") -- | Directory where 'stack' executable is bind-mounted in Docker container hostBinDir :: FilePath hostBinDir = "/opt/host/bin" -- | Convenience function to decode ByteString to String. decodeUtf8 :: BS.ByteString -> String decodeUtf8 bs = T.unpack (T.decodeUtf8 bs) -- | Convenience function constructing message for @$log*@. concatT :: [String] -> Text concatT = T.pack . concat -- | Fail with friendly error if project root not set. fromMaybeProjectRoot :: Maybe (Path Abs Dir) -> Path Abs Dir fromMaybeProjectRoot = fromMaybe (throw CannotDetermineProjectRootException) -- | Environment variable that contained the old sandbox ID. -- | Use of this variable is deprecated, and only used to detect old images. oldSandboxIdEnvVar :: String oldSandboxIdEnvVar = "DOCKER_SANDBOX_ID" -- | Options for 'cleanup'. data CleanupOpts = CleanupOpts { dcAction :: !CleanupAction , dcRemoveKnownImagesLastUsedDaysAgo :: !(Maybe Integer) , dcRemoveUnknownImagesCreatedDaysAgo :: !(Maybe Integer) , dcRemoveDanglingImagesCreatedDaysAgo :: !(Maybe Integer) , dcRemoveStoppedContainersCreatedDaysAgo :: !(Maybe Integer) , dcRemoveRunningContainersCreatedDaysAgo :: !(Maybe Integer) } deriving (Show) -- | Cleanup action. data CleanupAction = CleanupInteractive | CleanupImmediate | CleanupDryRun deriving (Show) -- | Parsed result of @docker inspect@. data Inspect = Inspect {iiConfig :: ImageConfig ,iiCreated :: UTCTime ,iiId :: String ,iiVirtualSize :: Maybe Integer} deriving (Show) -- | Parse @docker inspect@ output. instance FromJSON Inspect where parseJSON v = do o <- parseJSON v Inspect <$> o .: "Config" <*> o .: "Created" <*> o .: "Id" <*> o .:? "VirtualSize" -- | Parsed @Config@ section of @docker inspect@ output. data ImageConfig = ImageConfig {icEnv :: [String] ,icEntrypoint :: [String]} deriving (Show) -- | Parse @Config@ section of @docker inspect@ output. instance FromJSON ImageConfig where parseJSON v = do o <- parseJSON v ImageConfig <$> fmap join (o .:? "Env") .!= [] <*> fmap join (o .:? "Entrypoint") .!= [] -- | Function to get command and arguments to run in Docker container type GetCmdArgs env m = (StackM env m, HasConfig env) => DockerOpts -> EnvOverride -> Inspect -> Bool -> m (FilePath,[String],[(String,String)],[Mount]) stack-1.5.1/src/Stack/Docker/GlobalDB.hs0000644000000000000000000001202713135652051016021 0ustar0000000000000000{-# LANGUAGE QuasiQuotes, TemplateHaskell, TypeFamilies, OverloadedStrings, GADTs, FlexibleContexts, MultiParamTypeClasses, GeneralizedNewtypeDeriving, RankNTypes, NamedFieldPuns #-} -- | Global sqlite database shared by all projects. -- Warning: this is currently only accessible from __outside__ a Docker container. module Stack.Docker.GlobalDB (updateDockerImageLastUsed ,getDockerImagesLastUsed ,pruneDockerImagesLastUsed ,DockerImageLastUsed ,DockerImageProjectId ,getDockerImageExe ,setDockerImageExe ,DockerImageExeId) where import Control.Exception (IOException,catch,throwIO) import Control.Monad (forM_, when) import Control.Monad.Logger (NoLoggingT) import Control.Monad.Trans.Resource (ResourceT) import Data.List (sortBy, isInfixOf, stripPrefix) import Data.List.Extra (stripSuffix) import qualified Data.Map.Strict as Map import Data.Maybe (fromMaybe) import qualified Data.Text as T import Data.Time.Clock (UTCTime,getCurrentTime) import Database.Persist import Database.Persist.Sqlite import Database.Persist.TH import Path (toFilePath, parent) import Path.IO (ensureDir) import Stack.Types.Config import Stack.Types.Docker import Stack.Types.StringError share [mkPersist sqlSettings, mkMigrate "migrateTables"] [persistLowerCase| DockerImageProject imageHash String projectPath FilePath lastUsedTime UTCTime DockerImageProjectPathKey imageHash projectPath deriving Show DockerImageExe imageHash String exePath FilePath exeTimestamp UTCTime compatible Bool DockerImageExeUnique imageHash exePath exeTimestamp deriving Show |] -- | Update last used time and project for a Docker image hash. updateDockerImageLastUsed :: Config -> String -> FilePath -> IO () updateDockerImageLastUsed config imageId projectPath = do curTime <- getCurrentTime _ <- withGlobalDB config (upsert (DockerImageProject imageId projectPath curTime) []) return () -- | Get a list of Docker image hashes and when they were last used. getDockerImagesLastUsed :: Config -> IO [DockerImageLastUsed] getDockerImagesLastUsed config = do imageProjects <- withGlobalDB config (selectList [] [Asc DockerImageProjectLastUsedTime]) return (sortBy (flip sortImage) (Map.toDescList (Map.fromListWith (++) (map mapImageProject imageProjects)))) where mapImageProject (Entity _ imageProject) = (dockerImageProjectImageHash imageProject ,[(dockerImageProjectLastUsedTime imageProject ,dockerImageProjectProjectPath imageProject)]) sortImage (_,(a,_):_) (_,(b,_):_) = compare a b sortImage _ _ = EQ -- | Given a list of all existing Docker images, remove any that no longer exist from -- the database. pruneDockerImagesLastUsed :: Config -> [String] -> IO () pruneDockerImagesLastUsed config existingHashes = withGlobalDB config go where go = do l <- selectList [] [] forM_ l (\(Entity k DockerImageProject{dockerImageProjectImageHash = h}) -> when (h `notElem` existingHashes) $ delete k) -- | Get the record of whether an executable is compatible with a Docker image getDockerImageExe :: Config -> String -> FilePath -> UTCTime -> IO (Maybe Bool) getDockerImageExe config imageId exePath exeTimestamp = withGlobalDB config $ do mentity <- getBy (DockerImageExeUnique imageId exePath exeTimestamp) return (fmap (dockerImageExeCompatible . entityVal) mentity) -- | Seet the record of whether an executable is compatible with a Docker image setDockerImageExe :: Config -> String -> FilePath -> UTCTime -> Bool -> IO () setDockerImageExe config imageId exePath exeTimestamp compatible = withGlobalDB config $ do _ <- upsert (DockerImageExe imageId exePath exeTimestamp compatible) [] return () -- | Run an action with the global database. This performs any needed migrations as well. withGlobalDB :: forall a. Config -> SqlPersistT (NoLoggingT (ResourceT IO)) a -> IO a withGlobalDB config action = do let db = dockerDatabasePath (configDocker config) ensureDir (parent db) runSqlite (T.pack (toFilePath db)) (do _ <- runMigrationSilent migrateTables action) `catch` \ex -> do let str = show ex str' = fromMaybe str $ stripPrefix "user error (" $ fromMaybe str $ stripSuffix ")" str if "ErrorReadOnly" `isInfixOf` str then throwString $ str' ++ " This likely indicates that your DB file, " ++ toFilePath db ++ ", has incorrect permissions or ownership." else throwIO (ex :: IOException) -- | Date and project path where Docker image hash last used. type DockerImageLastUsed = (String, [(UTCTime, FilePath)]) stack-1.5.1/src/Stack/Dot.hs0000644000000000000000000003446613135652051013745 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Stack.Dot (dot ,listDependencies ,DotOpts(..) ,DotPayload(..) ,ListDepsOpts(..) ,resolveDependencies ,printGraph ,pruneGraph ) where import Control.Applicative import Control.Arrow ((&&&)) import Control.Monad (liftM, void) import Control.Monad.IO.Class import Control.Monad.Trans.Unlift (MonadBaseUnlift) import qualified Data.Foldable as F import qualified Data.HashSet as HashSet import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe import Data.Monoid ((<>)) import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Text.IO as Text import qualified Data.Traversable as T import Distribution.License (License(BSD3)) import Prelude -- Fix redundant import warnings import Stack.Build (withLoadPackage) import Stack.Build.Installed (getInstalled, GetInstalledOpts(..)) import Stack.Build.Source import Stack.Build.Target import Stack.Constants import Stack.Package import Stack.PackageDump (DumpPackage(..)) import Stack.Types.Build import Stack.Types.Config import Stack.Types.FlagName import Stack.Types.GhcPkgId import Stack.Types.Package import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.StackT import Stack.Types.Version -- | Options record for @stack dot@ data DotOpts = DotOpts { dotIncludeExternal :: !Bool -- ^ Include external dependencies , dotIncludeBase :: !Bool -- ^ Include dependencies on base , dotDependencyDepth :: !(Maybe Int) -- ^ Limit the depth of dependency resolution to (Just n) or continue until fixpoint , dotPrune :: !(Set String) -- ^ Package names to prune from the graph , dotTargets :: [Text] -- ^ stack TARGETs to trace dependencies for , dotFlags :: !(Map (Maybe PackageName) (Map FlagName Bool)) -- ^ Flags to apply when calculating dependencies , dotTestTargets :: Bool -- ^ Like the "--test" flag for build, affects the meaning of 'dotTargets'. , dotBenchTargets :: Bool -- ^ Like the "--bench" flag for build, affects the meaning of 'dotTargets'. } data ListDepsOpts = ListDepsOpts { listDepsDotOpts :: !DotOpts -- ^ The normal dot options. , listDepsSep :: !Text -- ^ Separator between the package name and details. , listDepsLicense :: !Bool -- ^ Print dependency licenses instead of versions. } -- | Visualize the project's dependencies as a graphviz graph dot :: (StackM env m, HasEnvConfig env, MonadBaseUnlift IO m) => DotOpts -> m () dot dotOpts = do (localNames, prunedGraph) <- createPrunedDependencyGraph dotOpts printGraph dotOpts localNames prunedGraph -- | Information about a package in the dependency graph, when available. data DotPayload = DotPayload { payloadVersion :: Maybe Version -- ^ The package version. , payloadLicense :: Maybe License -- ^ The license the package was released under. } deriving (Eq, Show) -- | Create the dependency graph and also prune it as specified in the dot -- options. Returns a set of local names and and a map from package names to -- dependencies. createPrunedDependencyGraph :: (StackM env m, HasEnvConfig env, MonadBaseUnlift IO m) => DotOpts -> m (Set PackageName, Map PackageName (Set PackageName, DotPayload)) createPrunedDependencyGraph dotOpts = do localNames <- liftM Map.keysSet getLocalPackageViews resultGraph <- createDependencyGraph dotOpts let pkgsToPrune = if dotIncludeBase dotOpts then dotPrune dotOpts else Set.insert "base" (dotPrune dotOpts) prunedGraph = pruneGraph localNames pkgsToPrune resultGraph return (localNames, prunedGraph) -- | Create the dependency graph, the result is a map from a package -- name to a tuple of dependencies and payload if available. This -- function mainly gathers the required arguments for -- @resolveDependencies@. createDependencyGraph :: (StackM env m, HasEnvConfig env, MonadBaseUnlift IO m) => DotOpts -> m (Map PackageName (Set PackageName, DotPayload)) createDependencyGraph dotOpts = do (_, _, locals, _, _, sourceMap) <- loadSourceMapFull NeedTargets defaultBuildOptsCLI { boptsCLITargets = dotTargets dotOpts , boptsCLIFlags = dotFlags dotOpts } let graph = Map.fromList (localDependencies dotOpts (filter lpWanted locals)) menv <- getMinimalEnvOverride (installedMap, globalDump, _, _) <- getInstalled menv (GetInstalledOpts False False False) sourceMap -- TODO: Can there be multiple entries for wired-in-packages? If so, -- this will choose one arbitrarily.. let globalDumpMap = Map.fromList $ map (\dp -> (packageIdentifierName (dpPackageIdent dp), dp)) globalDump globalIdMap = Map.fromList $ map (\dp -> (dpGhcPkgId dp, dpPackageIdent dp)) globalDump withLoadPackage (\loader -> do let depLoader = createDepLoader sourceMap installedMap globalDumpMap globalIdMap loadPackageDeps loadPackageDeps name version flags ghcOptions -- Skip packages that can't be loaded - see -- https://github.com/commercialhaskell/stack/issues/2967 | name `elem` [$(mkPackageName "rts"), $(mkPackageName "ghc")] = return (Set.empty, DotPayload (Just version) (Just BSD3)) | otherwise = fmap (packageAllDeps &&& makePayload) (loader name version flags ghcOptions) liftIO $ resolveDependencies (dotDependencyDepth dotOpts) graph depLoader) where makePayload pkg = DotPayload (Just $ packageVersion pkg) (Just $ packageLicense pkg) listDependencies :: (StackM env m, HasEnvConfig env, MonadBaseUnlift IO m) => ListDepsOpts -> m () listDependencies opts = do let dotOpts = listDepsDotOpts opts (_, resultGraph) <- createPrunedDependencyGraph dotOpts void (Map.traverseWithKey go (snd <$> resultGraph)) where go name payload = let payloadText = if listDepsLicense opts then maybe "" (Text.pack . show) (payloadLicense payload) else maybe "" (Text.pack . show) (payloadVersion payload) line = packageNameText name <> listDepsSep opts <> payloadText in liftIO $ Text.putStrLn line -- | @pruneGraph dontPrune toPrune graph@ prunes all packages in -- @graph@ with a name in @toPrune@ and removes resulting orphans -- unless they are in @dontPrune@ pruneGraph :: (F.Foldable f, F.Foldable g, Eq a) => f PackageName -> g String -> Map PackageName (Set PackageName, a) -> Map PackageName (Set PackageName, a) pruneGraph dontPrune names = pruneUnreachable dontPrune . Map.mapMaybeWithKey (\pkg (pkgDeps,x) -> if show pkg `F.elem` names then Nothing else let filtered = Set.filter (\n -> show n `F.notElem` names) pkgDeps in if Set.null filtered && not (Set.null pkgDeps) then Nothing else Just (filtered,x)) -- | Make sure that all unreachable nodes (orphans) are pruned pruneUnreachable :: (Eq a, F.Foldable f) => f PackageName -> Map PackageName (Set PackageName, a) -> Map PackageName (Set PackageName, a) pruneUnreachable dontPrune = fixpoint prune where fixpoint :: Eq a => (a -> a) -> a -> a fixpoint f v = if f v == v then v else fixpoint f (f v) prune graph' = Map.filterWithKey (\k _ -> reachable k) graph' where reachable k = k `F.elem` dontPrune || k `Set.member` reachables reachables = F.fold (fst <$> graph') -- | Resolve the dependency graph up to (Just depth) or until fixpoint is reached resolveDependencies :: (Applicative m, Monad m) => Maybe Int -> Map PackageName (Set PackageName, DotPayload) -> (PackageName -> m (Set PackageName, DotPayload)) -> m (Map PackageName (Set PackageName, DotPayload)) resolveDependencies (Just 0) graph _ = return graph resolveDependencies limit graph loadPackageDeps = do let values = Set.unions (fst <$> Map.elems graph) keys = Map.keysSet graph next = Set.difference values keys if Set.null next then return graph else do x <- T.traverse (\name -> (name,) <$> loadPackageDeps name) (F.toList next) resolveDependencies (subtract 1 <$> limit) (Map.unionWith unifier graph (Map.fromList x)) loadPackageDeps where unifier (pkgs1,v1) (pkgs2,_) = (Set.union pkgs1 pkgs2, v1) -- | Given a SourceMap and a dependency loader, load the set of dependencies for a package createDepLoader :: Applicative m => Map PackageName PackageSource -> Map PackageName (InstallLocation, Installed) -> Map PackageName (DumpPackage () () ()) -> Map GhcPkgId PackageIdentifier -> (PackageName -> Version -> Map FlagName Bool -> [Text] -> m (Set PackageName, DotPayload)) -> PackageName -> m (Set PackageName, DotPayload) createDepLoader sourceMap installed globalDumpMap globalIdMap loadPackageDeps pkgName = if not (pkgName `HashSet.member` wiredInPackages) then case Map.lookup pkgName sourceMap of Just (PSLocal lp) -> pure (packageAllDeps pkg, payloadFromLocal pkg) where pkg = localPackageToPackage lp Just (PSUpstream version _ flags ghcOptions _) -> loadPackageDeps pkgName version flags ghcOptions Nothing -> pure (Set.empty, payloadFromInstalled (Map.lookup pkgName installed)) -- For wired-in-packages, use information from ghc-pkg (see #3084) else case Map.lookup pkgName globalDumpMap of Nothing -> error ("Invariant violated: Expected to find wired-in-package " ++ packageNameString pkgName ++ " in global DB") Just dp -> pure (Set.fromList deps, payloadFromDump dp) where deps = map (\depId -> maybe (error ("Invariant violated: Expected to find " ++ ghcPkgIdString depId ++ " in global DB")) packageIdentifierName (Map.lookup depId globalIdMap)) (dpDepends dp) where payloadFromLocal pkg = DotPayload (Just $ packageVersion pkg) (Just $ packageLicense pkg) payloadFromInstalled maybePkg = DotPayload (fmap (installedVersion . snd) maybePkg) Nothing payloadFromDump dp = DotPayload (Just $ packageIdentifierVersion $ dpPackageIdent dp) (dpLicense dp) -- | Resolve the direct (depth 0) external dependencies of the given local packages localDependencies :: DotOpts -> [LocalPackage] -> [(PackageName, (Set PackageName, DotPayload))] localDependencies dotOpts locals = map (\lp -> let pkg = localPackageToPackage lp in (packageName pkg, (deps pkg, lpPayload pkg))) locals where deps pkg = if dotIncludeExternal dotOpts then Set.delete (packageName pkg) (packageAllDeps pkg) else Set.intersection localNames (packageAllDeps pkg) localNames = Set.fromList $ map (packageName . lpPackage) locals lpPayload pkg = DotPayload (Just $ packageVersion pkg) (Just $ packageLicense pkg) -- | Print a graphviz graph of the edges in the Map and highlight the given local packages printGraph :: (Applicative m, MonadIO m) => DotOpts -> Set PackageName -- ^ all locals -> Map PackageName (Set PackageName, DotPayload) -> m () printGraph dotOpts locals graph = do liftIO $ Text.putStrLn "strict digraph deps {" printLocalNodes dotOpts filteredLocals printLeaves graph void (Map.traverseWithKey printEdges (fst <$> graph)) liftIO $ Text.putStrLn "}" where filteredLocals = Set.filter (\local -> packageNameString local `Set.notMember` dotPrune dotOpts) locals -- | Print the local nodes with a different style depending on options printLocalNodes :: (F.Foldable t, MonadIO m) => DotOpts -> t PackageName -> m () printLocalNodes dotOpts locals = liftIO $ Text.putStrLn (Text.intercalate "\n" lpNodes) where applyStyle :: Text -> Text applyStyle n = if dotIncludeExternal dotOpts then n <> " [style=dashed];" else n <> " [style=solid];" lpNodes :: [Text] lpNodes = map (applyStyle . nodeName) (F.toList locals) -- | Print nodes without dependencies printLeaves :: MonadIO m => Map PackageName (Set PackageName, DotPayload) -> m () printLeaves = F.mapM_ printLeaf . Map.keysSet . Map.filter Set.null . fmap fst -- | @printDedges p ps@ prints an edge from p to every ps printEdges :: MonadIO m => PackageName -> Set PackageName -> m () printEdges package deps = F.forM_ deps (printEdge package) -- | Print an edge between the two package names printEdge :: MonadIO m => PackageName -> PackageName -> m () printEdge from to' = liftIO $ Text.putStrLn (Text.concat [ nodeName from, " -> ", nodeName to', ";"]) -- | Convert a package name to a graph node name. nodeName :: PackageName -> Text nodeName name = "\"" <> packageNameText name <> "\"" -- | Print a node with no dependencies printLeaf :: MonadIO m => PackageName -> m () printLeaf package = liftIO . Text.putStrLn . Text.concat $ if isWiredIn package then ["{rank=max; ", nodeName package, " [shape=box]; };"] else ["{rank=max; ", nodeName package, "; };"] -- | Check if the package is wired in (shipped with) ghc isWiredIn :: PackageName -> Bool isWiredIn = (`HashSet.member` wiredInPackages) localPackageToPackage :: LocalPackage -> Package localPackageToPackage lp = fromMaybe (lpPackage lp) (lpTestBench lp) stack-1.5.1/src/Stack/Exec.hs0000644000000000000000000000566113135652051014076 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} #if __GLASGOW_HASKELL__ >= 800 {-# OPTIONS_GHC -Wno-redundant-constraints #-} #endif -- | Execute commands within the properly configured Stack -- environment. module Stack.Exec where import Control.Monad.Reader import Control.Monad.Logger import Control.Monad.Trans.Control (MonadBaseControl) import Stack.Types.Config import System.Process.Log import Control.Exception.Lifted import Data.Streaming.Process (ProcessExitedUnsuccessfully(..)) import System.Exit import System.Process.Run (callProcess, callProcessObserveStdout, Cmd(..)) #ifdef WINDOWS import System.Process.Read (EnvOverride) #else import qualified System.Process.PID1 as PID1 import System.Process.Read (EnvOverride, envHelper, preProcess) #endif -- | Default @EnvSettings@ which includes locals and GHC_PACKAGE_PATH defaultEnvSettings :: EnvSettings defaultEnvSettings = EnvSettings { esIncludeLocals = True , esIncludeGhcPackagePath = True , esStackExe = True , esLocaleUtf8 = False } -- | Environment settings which do not embellish the environment plainEnvSettings :: EnvSettings plainEnvSettings = EnvSettings { esIncludeLocals = False , esIncludeGhcPackagePath = False , esStackExe = False , esLocaleUtf8 = False } -- | Execute a process within the Stack configured environment. -- -- Execution will not return, because either: -- -- 1) On non-windows, execution is taken over by execv of the -- sub-process. This allows signals to be propagated (#527) -- -- 2) On windows, an 'ExitCode' exception will be thrown. exec :: (MonadIO m, MonadLogger m, MonadBaseControl IO m) => EnvOverride -> String -> [String] -> m b #ifdef WINDOWS exec = execSpawn #else exec menv cmd0 args = do cmd <- preProcess Nothing menv cmd0 $withProcessTimeLog cmd args $ liftIO $ PID1.run cmd args (envHelper menv) #endif -- | Like 'exec', but does not use 'execv' on non-windows. This way, there -- is a sub-process, which is helpful in some cases (#1306) -- -- This function only exits by throwing 'ExitCode'. execSpawn :: (MonadIO m, MonadLogger m, MonadBaseControl IO m) => EnvOverride -> String -> [String] -> m b execSpawn menv cmd0 args = do e <- $withProcessTimeLog cmd0 args $ try (callProcess (Cmd Nothing cmd0 menv args)) liftIO $ case e of Left (ProcessExitedUnsuccessfully _ ec) -> exitWith ec Right () -> exitSuccess execObserve :: (MonadIO m, MonadLogger m, MonadBaseControl IO m) => EnvOverride -> String -> [String] -> m String execObserve menv cmd0 args = do e <- $withProcessTimeLog cmd0 args $ try (callProcessObserveStdout (Cmd Nothing cmd0 menv args)) case e of Left (ProcessExitedUnsuccessfully _ ec) -> liftIO $ exitWith ec Right s -> return s stack-1.5.1/src/Stack/Fetch.hs0000644000000000000000000007057013135652051014244 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE PackageImports #-} {-# LANGUAGE ViewPatterns #-} -- | Functionality for downloading packages securely for cabal's usage. module Stack.Fetch ( unpackPackages , unpackPackageIdents , fetchPackages , untar , resolvePackages , resolvePackagesAllowMissing , ResolvedPackage (..) , withCabalFiles , withCabalLoader ) where import qualified Codec.Archive.Tar as Tar import qualified Codec.Archive.Tar.Check as Tar import qualified Codec.Archive.Tar.Entry as Tar import Codec.Compression.GZip (decompress) import Control.Applicative import Control.Concurrent.Async (Concurrently (..)) import Control.Concurrent.MVar.Lifted (modifyMVar, newMVar) import Control.Concurrent.STM import Control.Exception (assert) import Control.Monad (join, liftM, unless, void, when) import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.Reader (ask, runReaderT) import Control.Monad.Trans.Control import Control.Monad.Trans.Unlift (MonadBaseUnlift, askRunBase) import Crypto.Hash (SHA256 (..)) import Data.ByteString (ByteString) import qualified Data.ByteString as S import qualified Data.ByteString.Lazy as L import Data.Either (partitionEithers) import qualified Data.Foldable as F import Data.Function (fix) import qualified Data.HashMap.Strict as HashMap import Data.List (intercalate) import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NE import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe (maybeToList, catMaybes, isJust) import Data.Monoid import Data.Set (Set) import qualified Data.Set as Set import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8, decodeUtf8) import Data.Text.Metrics import Data.Typeable (Typeable) import Data.Word (Word64) import Network.HTTP.Download import Path import Path.Extra (toFilePathNoTrailingSep) import Path.IO import Prelude -- Fix AMP warning import Stack.PackageIndex import Stack.Types.BuildPlan import Stack.Types.Config import Stack.Types.PackageIdentifier import Stack.Types.PackageIndex import Stack.Types.PackageName import Stack.Types.Version import System.FilePath ((<.>)) import qualified System.FilePath as FP import System.IO import System.PosixCompat (setFileMode) type PackageCaches = Map PackageIdentifier (PackageIndex, PackageCache) data FetchException = Couldn'tReadIndexTarball FilePath Tar.FormatError | Couldn'tReadPackageTarball FilePath SomeException | UnpackDirectoryAlreadyExists (Set FilePath) | CouldNotParsePackageSelectors [String] | UnknownPackageNames (Set PackageName) | UnknownPackageIdentifiers (Set PackageIdentifier) String deriving Typeable instance Exception FetchException instance Show FetchException where show (Couldn'tReadIndexTarball fp err) = concat [ "There was an error reading the index tarball " , fp , ": " , show err ] show (Couldn'tReadPackageTarball fp err) = concat [ "There was an error reading the package tarball " , fp , ": " , show err ] show (UnpackDirectoryAlreadyExists dirs) = unlines $ "Unable to unpack due to already present directories:" : map (" " ++) (Set.toList dirs) show (CouldNotParsePackageSelectors strs) = "The following package selectors are not valid package names or identifiers: " ++ intercalate ", " strs show (UnknownPackageNames names) = "The following packages were not found in your indices: " ++ intercalate ", " (map packageNameString $ Set.toList names) show (UnknownPackageIdentifiers idents suggestions) = "The following package identifiers were not found in your indices: " ++ intercalate ", " (map packageIdentifierString $ Set.toList idents) ++ (if null suggestions then "" else "\n" ++ suggestions) -- | Fetch packages into the cache without unpacking fetchPackages :: (StackMiniM env m, HasConfig env) => Set PackageIdentifier -> m () fetchPackages idents' = do resolved <- resolvePackages Nothing idents Set.empty ToFetchResult toFetch alreadyUnpacked <- getToFetch Nothing resolved assert (Map.null alreadyUnpacked) (return ()) nowUnpacked <- fetchPackages' Nothing toFetch assert (Map.null nowUnpacked) (return ()) where -- Since we're just fetching tarballs and not unpacking cabal files, we can -- always provide a Nothing Git SHA idents = Map.fromList $ map (, Nothing) $ Set.toList idents' -- | Intended to work for the command line command. unpackPackages :: (StackMiniM env m, HasConfig env) => Maybe MiniBuildPlan -- ^ when looking up by name, take from this build plan -> FilePath -- ^ destination -> [String] -- ^ names or identifiers -> m () unpackPackages mMiniBuildPlan dest input = do dest' <- resolveDir' dest (names, idents) <- case partitionEithers $ map parse input of ([], x) -> return $ partitionEithers x (errs, _) -> throwM $ CouldNotParsePackageSelectors errs resolved <- resolvePackages mMiniBuildPlan (Map.fromList idents) (Set.fromList names) ToFetchResult toFetch alreadyUnpacked <- getToFetch (Just dest') resolved unless (Map.null alreadyUnpacked) $ throwM $ UnpackDirectoryAlreadyExists $ Set.fromList $ map toFilePath $ Map.elems alreadyUnpacked unpacked <- fetchPackages' Nothing toFetch F.forM_ (Map.toList unpacked) $ \(ident, dest'') -> $logInfo $ T.pack $ concat [ "Unpacked " , packageIdentifierString ident , " to " , toFilePath dest'' ] where -- Possible future enhancement: parse names as name + version range parse s = case parsePackageNameFromString s of Right x -> Right $ Left x Left _ -> case parsePackageIdentifierFromString s of Right x -> Right $ Right (x, Nothing) Left _ -> maybe (Left s) (Right . Right) $ do (identS, '@':revisionS) <- return $ break (== '@') s Right ident <- return $ parsePackageIdentifierFromString identS hash <- T.stripPrefix "gitsha1:" $ T.pack revisionS Just (ident, Just $ GitSHA1 $ encodeUtf8 hash) -- | Ensure that all of the given package idents are unpacked into the build -- unpack directory, and return the paths to all of the subdirectories. unpackPackageIdents :: (StackMiniM env m, HasConfig env) => Path Abs Dir -- ^ unpack directory -> Maybe (Path Rel Dir) -- ^ the dist rename directory, see: https://github.com/fpco/stack/issues/157 -> Map PackageIdentifier (Maybe GitSHA1) -> m (Map PackageIdentifier (Path Abs Dir)) unpackPackageIdents unpackDir mdistDir idents = do resolved <- resolvePackages Nothing idents Set.empty ToFetchResult toFetch alreadyUnpacked <- getToFetch (Just unpackDir) resolved nowUnpacked <- fetchPackages' mdistDir toFetch return $ alreadyUnpacked <> nowUnpacked data ResolvedPackage = ResolvedPackage { rpIdent :: !PackageIdentifier , rpCache :: !PackageCache , rpIndex :: !PackageIndex } deriving Show -- | Resolve a set of package names and identifiers into @FetchPackage@ values. resolvePackages :: (StackMiniM env m, HasConfig env) => Maybe MiniBuildPlan -- ^ when looking up by name, take from this build plan -> Map PackageIdentifier (Maybe GitSHA1) -> Set PackageName -> m [ResolvedPackage] resolvePackages mMiniBuildPlan idents0 names0 = do eres <- go case eres of Left _ -> do updateAllIndices go >>= either throwM return Right x -> return x where go = r <$> resolvePackagesAllowMissing mMiniBuildPlan idents0 names0 r (missingNames, missingIdents, idents) | not $ Set.null missingNames = Left $ UnknownPackageNames missingNames | not $ Set.null missingIdents = Left $ UnknownPackageIdentifiers missingIdents "" | otherwise = Right idents resolvePackagesAllowMissing :: (StackMiniM env m, HasConfig env) => Maybe MiniBuildPlan -- ^ when looking up by name, take from this build plan -> Map PackageIdentifier (Maybe GitSHA1) -> Set PackageName -> m (Set PackageName, Set PackageIdentifier, [ResolvedPackage]) resolvePackagesAllowMissing mMiniBuildPlan idents0 names0 = do (res1, res2, resolved) <- inner if any (isJust . snd) resolved then do $logInfo "Missing some cabal revision files, updating indices" updateAllIndices (res1', res2', resolved') <- inner -- Print an error message if any SHAs are still missing. F.forM_ resolved' $ \(rp, missing) -> F.forM_ missing $ \(GitSHA1 sha) -> $logWarn $ mconcat [ "Did not find .cabal file for " , T.pack $ packageIdentifierString $ rpIdent rp , " with SHA of " , decodeUtf8 sha , " in tarball-based cache" ] return (res1', res2', map fst resolved') else return (res1, res2, map fst resolved) where inner = do (caches, shaCaches) <- getPackageCaches let versions = Map.fromListWith max $ map toTuple $ Map.keys caches getNamed :: PackageName -> Maybe (PackageIdentifier, Maybe GitSHA1) getNamed = case mMiniBuildPlan of Nothing -> getNamedFromIndex Just mbp -> getNamedFromBuildPlan mbp getNamedFromBuildPlan mbp name = do mpi <- Map.lookup name $ mbpPackages mbp Just (PackageIdentifier name (mpiVersion mpi), mpiGitSHA1 mpi) getNamedFromIndex name = fmap (\ver -> (PackageIdentifier name ver, Nothing)) (Map.lookup name versions) (missingNames, idents1) = partitionEithers $ map (\name -> maybe (Left name) Right (getNamed name)) (Set.toList names0) let (missingIdents, resolved) = partitionEithers $ map (goIdent caches shaCaches) $ Map.toList $ idents0 <> Map.fromList idents1 return (Set.fromList missingNames, Set.fromList missingIdents, resolved) goIdent caches shaCaches (ident, mgitsha) = case Map.lookup ident caches of Nothing -> Left ident Just (index, cache) -> let (index', cache', missingGitSHA) = case mgitsha of Nothing -> (index, cache, mgitsha) Just gitsha -> case HashMap.lookup gitsha shaCaches of Just (index'', offsetSize) -> ( index'' , cache { pcOffsetSize = offsetSize } -- we already got the info -- about this SHA, don't do -- any lookups later , Nothing ) -- Index using HTTP, so we're missing the Git SHA Nothing -> (index, cache, mgitsha) in Right (ResolvedPackage { rpIdent = ident , rpCache = cache' , rpIndex = index' }, missingGitSHA) data ToFetch = ToFetch { tfTarball :: !(Path Abs File) , tfDestDir :: !(Maybe (Path Abs Dir)) , tfUrl :: !T.Text , tfSize :: !(Maybe Word64) , tfSHA256 :: !(Maybe ByteString) , tfCabal :: !ByteString -- ^ Contents of the .cabal file } data ToFetchResult = ToFetchResult { tfrToFetch :: !(Map PackageIdentifier ToFetch) , tfrAlreadyUnpacked :: !(Map PackageIdentifier (Path Abs Dir)) } -- | Add the cabal files to a list of idents with their caches. withCabalFiles :: (StackMiniM env m, HasConfig env) => IndexName -> [(ResolvedPackage, a)] -> (PackageIdentifier -> a -> ByteString -> IO b) -> m [b] withCabalFiles name pkgs f = do indexPath <- configPackageIndex name bracket (liftIO $ openBinaryFile (toFilePath indexPath) ReadMode) (liftIO . hClose) $ \h -> mapM (goPkg h) pkgs where goPkg h (ResolvedPackage ident pc _index, tf) = do -- Did not find warning for tarballs is handled above let OffsetSize offset size = pcOffsetSize pc liftIO $ do hSeek h AbsoluteSeek $ fromIntegral offset cabalBS <- S.hGet h $ fromIntegral size f ident tf cabalBS -- | Provide a function which will load up a cabal @ByteString@ from the -- package indices. withCabalLoader :: (StackMiniM env m, HasConfig env, MonadBaseUnlift IO m) => ((PackageIdentifier -> IO ByteString) -> m a) -> m a withCabalLoader inner = do env <- ask -- Want to try updating the index once during a single run for missing -- package identifiers. We also want to ensure we only update once at a -- time -- -- TODO: probably makes sense to move this concern into getPackageCaches updateRef <- liftIO $ newMVar True loadCaches <- getPackageCachesIO runInBase <- liftBaseWith $ \run -> return (void . run) unlift <- askRunBase -- TODO in the future, keep all of the necessary @Handle@s open let doLookup :: PackageIdentifier -> IO ByteString doLookup ident = do (caches, _gitSHACaches) <- loadCaches eres <- unlift $ lookupPackageIdentifierExact ident env caches case eres of Just bs -> return bs -- Update the cache and try again Nothing -> do let fuzzy = fuzzyLookupCandidates ident caches suggestions = case fuzzy of Nothing -> case typoCorrectionCandidates ident caches of Nothing -> "" Just cs -> "Perhaps you meant " <> orSeparated cs <> "?" Just cs -> "Possible candidates: " <> commaSeparated (NE.map packageIdentifierText cs) <> "." join $ modifyMVar updateRef $ \toUpdate -> if toUpdate then do runInBase $ do $logInfo $ T.concat [ "Didn't see " , T.pack $ packageIdentifierString ident , " in your package indices.\n" , "Updating and trying again." ] updateAllIndices _ <- getPackageCaches return () return (False, doLookup ident) else return (toUpdate, throwM $ UnknownPackageIdentifiers (Set.singleton ident) (T.unpack suggestions)) inner doLookup lookupPackageIdentifierExact :: (StackMiniM env m, HasConfig env) => PackageIdentifier -> env -> PackageCaches -> m (Maybe ByteString) lookupPackageIdentifierExact ident env caches = case Map.lookup ident caches of Nothing -> return Nothing Just (index, cache) -> do [bs] <- flip runReaderT env $ withCabalFiles (indexName index) [(ResolvedPackage { rpIdent = ident , rpCache = cache , rpIndex = index }, ())] $ \_ _ bs -> return bs return $ Just bs -- | Given package identifier and package caches, return list of packages -- with the same name and the same two first version number components found -- in the caches. fuzzyLookupCandidates :: PackageIdentifier -> PackageCaches -> Maybe (NonEmpty PackageIdentifier) fuzzyLookupCandidates (PackageIdentifier name ver) caches = let (_, zero, bigger) = Map.splitLookup zeroIdent caches zeroIdent = PackageIdentifier name $(mkVersion "0.0") sameName (PackageIdentifier n _) = n == name sameMajor (PackageIdentifier _ v) = toMajorVersion v == toMajorVersion ver in NE.nonEmpty . filter sameMajor $ maybe [] (pure . const zeroIdent) zero <> takeWhile sameName (Map.keys bigger) -- | Try to come up with typo corrections for given package identifier using -- package caches. This should be called before giving up, i.e. when -- 'fuzzyLookupCandidates' cannot return anything. typoCorrectionCandidates :: PackageIdentifier -> PackageCaches -> Maybe (NonEmpty T.Text) typoCorrectionCandidates ident = let getName = packageNameText . packageIdentifierName name = getName ident in NE.nonEmpty . take 10 . map snd . filter (\(distance, _) -> distance < 4) . map (\(k, _) -> (damerauLevenshtein name (getName k), getName k)) . Map.toList -- | Figure out where to fetch from. getToFetch :: (StackMiniM env m, HasConfig env) => Maybe (Path Abs Dir) -- ^ directory to unpack into, @Nothing@ means no unpack -> [ResolvedPackage] -> m ToFetchResult getToFetch mdest resolvedAll = do (toFetch0, unpacked) <- liftM partitionEithers $ mapM checkUnpacked resolvedAll toFetch1 <- mapM goIndex $ Map.toList $ Map.fromListWith (++) toFetch0 return ToFetchResult { tfrToFetch = Map.unions toFetch1 , tfrAlreadyUnpacked = Map.fromList unpacked } where checkUnpacked resolved = do let ident = rpIdent resolved dirRel <- parseRelDir $ packageIdentifierString ident let mdestDir = ( dirRel) <$> mdest mexists <- case mdestDir of Nothing -> return Nothing Just destDir -> do exists <- doesDirExist destDir return $ if exists then Just destDir else Nothing case mexists of Just destDir -> return $ Right (ident, destDir) Nothing -> do let index = rpIndex resolved d = pcDownload $ rpCache resolved targz = T.pack $ packageIdentifierString ident ++ ".tar.gz" tarball <- configPackageTarball (indexName index) ident return $ Left (indexName index, [(resolved, ToFetch { tfTarball = tarball , tfDestDir = mdestDir , tfUrl = case fmap pdUrl d of Just url | not (S.null url) -> decodeUtf8 url _ -> indexDownloadPrefix index <> targz , tfSize = fmap pdSize d , tfSHA256 = fmap pdSHA256 d , tfCabal = S.empty -- filled in by goIndex })]) goIndex (name, pkgs) = liftM Map.fromList $ withCabalFiles name pkgs $ \ident tf cabalBS -> return (ident, tf { tfCabal = cabalBS }) -- | Download the given name,version pairs into the directory expected by cabal. -- -- For each package it downloads, it will optionally unpack it to the given -- @Path@ (if present). Note that unpacking is not simply a matter of -- untarring, but also of grabbing the cabal file from the package index. The -- destinations should not include package identifiers. -- -- Returns the list of paths unpacked, including package identifiers. E.g.: -- -- @ -- fetchPackages [("foo-1.2.3", Just "/some/dest")] ==> ["/some/dest/foo-1.2.3"] -- @ -- -- Since 0.1.0.0 fetchPackages' :: (StackMiniM env m, HasConfig env) => Maybe (Path Rel Dir) -- ^ the dist rename directory, see: https://github.com/fpco/stack/issues/157 -> Map PackageIdentifier ToFetch -> m (Map PackageIdentifier (Path Abs Dir)) fetchPackages' mdistDir toFetchAll = do connCount <- view $ configL.to configConnectionCount outputVar <- liftIO $ newTVarIO Map.empty runInBase <- liftBaseWith $ \run -> return (void . run) parMapM_ connCount (go outputVar runInBase) (Map.toList toFetchAll) liftIO $ readTVarIO outputVar where go :: (MonadIO m,MonadThrow m,MonadLogger m) => TVar (Map PackageIdentifier (Path Abs Dir)) -> (m () -> IO ()) -> (PackageIdentifier, ToFetch) -> m () go outputVar runInBase (ident, toFetch) = do req <- parseUrlThrow $ T.unpack $ tfUrl toFetch let destpath = tfTarball toFetch let toHashCheck bs = HashCheck SHA256 (CheckHexDigestByteString bs) let downloadReq = DownloadRequest { drRequest = req , drHashChecks = map toHashCheck $ maybeToList (tfSHA256 toFetch) , drLengthCheck = fromIntegral <$> tfSize toFetch , drRetryPolicy = drRetryPolicyDefault } let progressSink _ = liftIO $ runInBase $ $logInfo $ packageIdentifierText ident <> ": download" _ <- verifiedDownload downloadReq destpath progressSink identStrP <- parseRelDir $ packageIdentifierString ident F.forM_ (tfDestDir toFetch) $ \destDir -> do let innerDest = toFilePath destDir unexpectedEntries <- liftIO $ untar destpath identStrP (parent destDir) liftIO $ do case mdistDir of Nothing -> return () -- See: https://github.com/fpco/stack/issues/157 Just distDir -> do let inner = parent destDir identStrP oldDist = inner $(mkRelDir "dist") newDist = inner distDir exists <- doesDirExist oldDist when exists $ do -- Previously used takeDirectory, but that got confused -- by trailing slashes, see: -- https://github.com/commercialhaskell/stack/issues/216 -- -- Instead, use Path which is a bit more resilient ensureDir $ parent newDist renameDir oldDist newDist let cabalFP = innerDest FP. packageNameString (packageIdentifierName ident) <.> "cabal" S.writeFile cabalFP $ tfCabal toFetch atomically $ modifyTVar outputVar $ Map.insert ident destDir F.forM_ unexpectedEntries $ \(path, entryType) -> $logWarn $ "Unexpected entry type " <> entryType <> " for entry " <> T.pack path -- | Internal function used to unpack tarball. -- -- Takes a path to a .tar.gz file, the name of the directory it should contain, -- and a destination folder to extract the tarball into. Returns unexpected -- entries, as pairs of paths and descriptions. untar :: forall b1 b2. Path b1 File -> Path Rel Dir -> Path b2 Dir -> IO [(FilePath, T.Text)] untar tarPath expectedTarFolder destDirParent = do ensureDir destDirParent withBinaryFile (toFilePath tarPath) ReadMode $ \h -> do -- Avoid using L.readFile, which is more likely to leak -- resources lbs <- L.hGetContents h let rawEntries = fmap (either wrap wrap) $ Tar.checkTarbomb (toFilePathNoTrailingSep expectedTarFolder) $ Tar.read $ decompress lbs filterEntries :: Monoid w => (Tar.Entry -> (Bool, w)) -> Tar.Entries b -> (Tar.Entries b, w) -- Allow collecting warnings, Writer-monad style. filterEntries f = Tar.foldEntries (\e -> let (res, w) = f e in \(rest, wOld) -> ((if res then Tar.Next e else id) rest, wOld <> w)) (Tar.Done, mempty) (\err -> (Tar.Fail err, mempty)) extractableEntry e = case Tar.entryContent e of Tar.NormalFile _ _ -> (True, []) Tar.Directory -> (True, []) Tar.SymbolicLink _ -> (True, []) Tar.HardLink _ -> (True, []) Tar.OtherEntryType 'g' _ _ -> (False, []) Tar.OtherEntryType 'x' _ _ -> (False, []) Tar.CharacterDevice _ _ -> (False, [(path, "character device")]) Tar.BlockDevice _ _ -> (False, [(path, "block device")]) Tar.NamedPipe -> (False, [(path, "named pipe")]) Tar.OtherEntryType code _ _ -> (False, [(path, "other entry type with code " <> T.pack (show code))]) where path = Tar.fromTarPath $ Tar.entryTarPath e (entries, unexpectedEntries) = filterEntries extractableEntry rawEntries wrap :: Exception e => e -> FetchException wrap = Couldn'tReadPackageTarball (toFilePath tarPath) . toException getPerms :: Tar.Entry -> (FilePath, Tar.Permissions) getPerms e = (toFilePath destDirParent FP. Tar.fromTarPath (Tar.entryTarPath e), Tar.entryPermissions e) filePerms :: [(FilePath, Tar.Permissions)] filePerms = catMaybes $ Tar.foldEntries (\e -> (:) (Just $ getPerms e)) [] (const []) entries Tar.unpack (toFilePath destDirParent) entries -- Reset file permissions as they were in the tarball, but only -- for extracted entries (whence filterEntries extractableEntry above). -- See https://github.com/commercialhaskell/stack/issues/2361 mapM_ (\(fp, perm) -> setFileMode (FP.dropTrailingPathSeparator fp) perm) filePerms return unexpectedEntries parMapM_ :: (F.Foldable f,MonadIO m,MonadBaseControl IO m) => Int -> (a -> m ()) -> f a -> m () parMapM_ (max 1 -> 1) f xs = F.mapM_ f xs parMapM_ cnt f xs0 = do var <- liftIO (newTVarIO $ F.toList xs0) -- See comment on similar line in Stack.Build runInBase <- liftBaseWith $ \run -> return (void . run) let worker = fix $ \loop -> join $ atomically $ do xs <- readTVar var case xs of [] -> return $ return () x:xs' -> do writeTVar var xs' return $ do runInBase $ f x loop workers 1 = Concurrently worker workers i = Concurrently worker *> workers (i - 1) liftIO $ runConcurrently $ workers cnt orSeparated :: NonEmpty T.Text -> T.Text orSeparated xs | NE.length xs == 1 = NE.head xs | NE.length xs == 2 = NE.head xs <> " or " <> NE.last xs | otherwise = T.intercalate ", " (NE.init xs) <> ", or " <> NE.last xs commaSeparated :: NonEmpty T.Text -> T.Text commaSeparated = F.fold . NE.intersperse ", " stack-1.5.1/src/Stack/FileWatch.hs0000644000000000000000000001326513135652051015057 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-} module Stack.FileWatch ( fileWatch , fileWatchPoll , printExceptionStderr ) where import Blaze.ByteString.Builder (toLazyByteString, copyByteString) import Blaze.ByteString.Builder.Char.Utf8 (fromShow) import Control.Concurrent.Async (race_) import Control.Concurrent.STM import Control.Exception (Exception, fromException, catch, throwIO) import Control.Exception.Safe (tryAny) import Control.Monad (forever, unless, when) import qualified Data.ByteString.Lazy as L import qualified Data.Map.Strict as Map import Data.Monoid ((<>)) import Data.Set (Set) import qualified Data.Set as Set import Data.String (fromString) import Data.Traversable (forM) import GHC.IO.Exception import GHC.IO.Handle (hIsTerminalDevice) import Path import System.Console.ANSI import System.FSNotify import System.IO (Handle, stdout, stderr, hPutStrLn) -- | Print an exception to stderr printExceptionStderr :: Exception e => e -> IO () printExceptionStderr e = L.hPut stderr $ toLazyByteString $ fromShow e <> copyByteString "\n" fileWatch :: Handle -> ((Set (Path Abs File) -> IO ()) -> IO ()) -> IO () fileWatch = fileWatchConf defaultConfig fileWatchPoll :: Handle -> ((Set (Path Abs File) -> IO ()) -> IO ()) -> IO () fileWatchPoll = fileWatchConf $ defaultConfig { confUsePolling = True } -- | Run an action, watching for file changes -- -- The action provided takes a callback that is used to set the files to be -- watched. When any of those files are changed, we rerun the action again. fileWatchConf :: WatchConfig -> Handle -> ((Set (Path Abs File) -> IO ()) -> IO ()) -> IO () fileWatchConf cfg out inner = withManagerConf cfg $ \manager -> do let putLn = hPutStrLn out let withColor color action = do outputIsTerminal <- hIsTerminalDevice stdout if outputIsTerminal then do setSGR [SetColor Foreground Dull color] action setSGR [Reset] else action allFiles <- newTVarIO Set.empty dirtyVar <- newTVarIO True watchVar <- newTVarIO Map.empty let onChange event = atomically $ do files <- readTVar allFiles when (eventPath event `Set.member` files) (writeTVar dirtyVar True) setWatched :: Set (Path Abs File) -> IO () setWatched files = do atomically $ writeTVar allFiles $ Set.map toFilePath files watch0 <- readTVarIO watchVar let actions = Map.mergeWithKey keepListening stopListening startListening watch0 newDirs watch1 <- forM (Map.toList actions) $ \(k, mmv) -> do mv <- mmv return $ case mv of Nothing -> Map.empty Just v -> Map.singleton k v atomically $ writeTVar watchVar $ Map.unions watch1 where newDirs = Map.fromList $ map (, ()) $ Set.toList $ Set.map parent files keepListening _dir listen () = Just $ return $ Just listen stopListening = Map.map $ \f -> do () <- f `catch` \ioe -> -- Ignore invalid argument error - it can happen if -- the directory is removed. case ioe_type ioe of InvalidArgument -> return () _ -> throwIO ioe return Nothing startListening = Map.mapWithKey $ \dir () -> do let dir' = fromString $ toFilePath dir listen <- watchDir manager dir' (const True) onChange return $ Just listen let watchInput = do line <- getLine unless (line == "quit") $ do case line of "help" -> do putLn "" putLn "help: display this help" putLn "quit: exit" putLn "build: force a rebuild" putLn "watched: display watched files" "build" -> atomically $ writeTVar dirtyVar True "watched" -> do watch <- readTVarIO allFiles mapM_ putLn (Set.toList watch) "" -> atomically $ writeTVar dirtyVar True _ -> putLn $ concat [ "Unknown command: " , show line , ". Try 'help'" ] watchInput race_ watchInput $ forever $ do atomically $ do dirty <- readTVar dirtyVar check dirty eres <- tryAny $ inner setWatched -- Clear dirtiness flag after the build to avoid an infinite -- loop caused by the build itself triggering dirtiness. This -- could be viewed as a bug, since files changed during the -- build will not trigger an extra rebuild, but overall seems -- like better behavior. See -- https://github.com/commercialhaskell/stack/issues/822 atomically $ writeTVar dirtyVar False case eres of Left e -> do let color = case fromException e of Just ExitSuccess -> Green _ -> Red withColor color $ printExceptionStderr e _ -> withColor Green $ putLn "Success! Waiting for next file change." putLn "Type help for available commands. Press enter to force a rebuild." stack-1.5.1/src/Stack/GhcPkg.hs0000644000000000000000000001706313135652051014354 0ustar0000000000000000-- FIXME See how much of this module can be deleted. {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# OPTIONS -fno-warn-unused-do-bind #-} -- | Functions for the GHC package database. module Stack.GhcPkg (getGlobalDB ,EnvOverride ,envHelper ,findGhcPkgField ,createDatabase ,unregisterGhcPkgId ,getCabalPkgVer ,ghcPkgExeName ,mkGhcPackagePath) where import Control.Monad import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.Trans.Control import qualified Data.ByteString.Char8 as S8 import Data.Either import Data.List import Data.Maybe import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T import Data.Text.Extra (stripCR) import Path (Path, Abs, Dir, toFilePath, parent, mkRelFile, ()) import Path.Extra (toFilePathNoTrailingSep) import Path.IO import Prelude hiding (FilePath) import Stack.Constants import Stack.Types.Build import Stack.Types.GhcPkgId import Stack.Types.PackageIdentifier import Stack.Types.Compiler import Stack.Types.PackageName import Stack.Types.Version import System.FilePath (searchPathSeparator) import System.Process.Read -- | Get the global package database getGlobalDB :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => EnvOverride -> WhichCompiler -> m (Path Abs Dir) getGlobalDB menv wc = do $logDebug "Getting global package database location" -- This seems like a strange way to get the global package database -- location, but I don't know of a better one bs <- ghcPkg menv wc [] ["list", "--global"] >>= either throwM return let fp = S8.unpack $ stripTrailingColon $ firstLine bs resolveDir' fp where stripTrailingColon bs | S8.null bs = bs | S8.last bs == ':' = S8.init bs | otherwise = bs firstLine = S8.takeWhile (\c -> c /= '\r' && c /= '\n') -- | Run the ghc-pkg executable ghcPkg :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => EnvOverride -> WhichCompiler -> [Path Abs Dir] -> [String] -> m (Either ReadProcessException S8.ByteString) ghcPkg menv wc pkgDbs args = do eres <- go case eres of Left _ -> do mapM_ (createDatabase menv wc) pkgDbs go Right _ -> return eres where go = tryProcessStdout Nothing menv (ghcPkgExeName wc) args' args' = packageDbFlags pkgDbs ++ args -- | Create a package database in the given directory, if it doesn't exist. createDatabase :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => EnvOverride -> WhichCompiler -> Path Abs Dir -> m () createDatabase menv wc db = do exists <- doesFileExist (db $(mkRelFile "package.cache")) unless exists $ do -- ghc-pkg requires that the database directory does not exist -- yet. If the directory exists but the package.cache file -- does, we're in a corrupted state. Check for that state. dirExists <- doesDirExist db args <- if dirExists then do $logWarn $ T.pack $ concat [ "The package database located at " , toFilePath db , " is corrupted (missing its package.cache file)." ] $logWarn "Proceeding with a recache" return ["--package-db", toFilePath db, "recache"] else do -- Creating the parent doesn't seem necessary, as ghc-pkg -- seems to be sufficiently smart. But I don't feel like -- finding out it isn't the hard way ensureDir (parent db) return ["init", toFilePath db] eres <- tryProcessStdout Nothing menv (ghcPkgExeName wc) args case eres of Left e -> do $logError $ T.pack $ "Unable to create package database at " ++ toFilePath db throwM e Right _ -> return () -- | Get the name to use for "ghc-pkg", given the compiler version. ghcPkgExeName :: WhichCompiler -> String ghcPkgExeName Ghc = "ghc-pkg" ghcPkgExeName Ghcjs = "ghcjs-pkg" -- | Get the necessary ghc-pkg flags for setting up the given package database packageDbFlags :: [Path Abs Dir] -> [String] packageDbFlags pkgDbs = "--no-user-package-db" : map (\x -> "--package-db=" ++ toFilePath x) pkgDbs -- | Get the value of a field of the package. findGhcPkgField :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => EnvOverride -> WhichCompiler -> [Path Abs Dir] -- ^ package databases -> String -- ^ package identifier, or GhcPkgId -> Text -> m (Maybe Text) findGhcPkgField menv wc pkgDbs name field = do result <- ghcPkg menv wc pkgDbs ["field", "--simple-output", name, T.unpack field] return $ case result of Left{} -> Nothing Right lbs -> fmap (stripCR . T.decodeUtf8) $ listToMaybe $ S8.lines lbs -- | Get the version of the package findGhcPkgVersion :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => EnvOverride -> WhichCompiler -> [Path Abs Dir] -- ^ package databases -> PackageName -> m (Maybe Version) findGhcPkgVersion menv wc pkgDbs name = do mv <- findGhcPkgField menv wc pkgDbs (packageNameString name) "version" case mv of Just !v -> return (parseVersion v) _ -> return Nothing unregisterGhcPkgId :: (MonadIO m, MonadLogger m, MonadCatch m, MonadBaseControl IO m) => EnvOverride -> WhichCompiler -> CompilerVersion -> Path Abs Dir -- ^ package database -> GhcPkgId -> PackageIdentifier -> m () unregisterGhcPkgId menv wc cv pkgDb gid ident = do eres <- ghcPkg menv wc [pkgDb] args case eres of Left e -> $logWarn $ T.pack $ show e Right _ -> return () where -- TODO ideally we'd tell ghc-pkg a GhcPkgId instead args = "unregister" : "--user" : "--force" : (case cv of GhcVersion v | v < $(mkVersion "7.9") -> [packageIdentifierString ident] _ -> ["--ipid", ghcPkgIdString gid]) -- | Get the version of Cabal from the global package database. getCabalPkgVer :: (MonadThrow m, MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => EnvOverride -> WhichCompiler -> m Version getCabalPkgVer menv wc = do $logDebug "Getting Cabal package version" mres <- findGhcPkgVersion menv wc [] -- global DB cabalPackageName maybe (throwM $ Couldn'tFindPkgId cabalPackageName) return mres -- | Get the value for GHC_PACKAGE_PATH mkGhcPackagePath :: Bool -> Path Abs Dir -> Path Abs Dir -> [Path Abs Dir] -> Path Abs Dir -> Text mkGhcPackagePath locals localdb deps extras globaldb = T.pack $ intercalate [searchPathSeparator] $ concat [ [toFilePathNoTrailingSep localdb | locals] , [toFilePathNoTrailingSep deps] , [toFilePathNoTrailingSep db | db <- reverse extras] , [toFilePathNoTrailingSep globaldb] ] stack-1.5.1/src/Stack/Ghci.hs0000644000000000000000000011060713135652051014061 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} -- | Run a GHCi configured with the user's package(s). module Stack.Ghci ( GhciOpts(..) , GhciPkgInfo(..) , GhciException(..) , ghci -- TODO: Address what should and should not be exported. , renderScriptGhci , renderScriptIntero ) where import Control.Applicative import Control.Arrow (second) import Control.Exception.Safe (tryAny) import Control.Monad hiding (forM) import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.State.Strict (State, execState, get, modify) import Control.Monad.Trans.Unlift (MonadBaseUnlift) import qualified Data.ByteString.Char8 as S8 import Data.Either import Data.Function import Data.List import Data.List.Extra (nubOrd) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe import Data.Maybe.Extra (forMaybeM) import Data.Monoid import Data.Set (Set) import qualified Data.Set as S import Data.String import Data.Text (Text) import qualified Data.Text as T import Data.Traversable (forM) import Data.Typeable (Typeable) import qualified Distribution.PackageDescription as C import qualified Distribution.Text as C import Path import Path.Extra (toFilePathNoTrailingSep) import Path.IO import Prelude import Stack.Build import Stack.Build.Installed import Stack.Build.Source import Stack.Build.Target import Stack.Config (getLocalPackages) import Stack.Constants import Stack.Exec import Stack.Ghci.Script import Stack.Package import Stack.PrettyPrint import Stack.Types.Build import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.FlagName import Stack.Types.Package import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.StackT import Text.Read (readMaybe) #ifndef WINDOWS import qualified System.Posix.Files as Posix #endif -- | Command-line options for GHC. data GhciOpts = GhciOpts { ghciTargets :: ![Text] , ghciArgs :: ![String] , ghciGhcOptions :: ![Text] , ghciFlags :: !(Map (Maybe PackageName) (Map FlagName Bool)) , ghciGhcCommand :: !(Maybe FilePath) , ghciNoLoadModules :: !Bool , ghciAdditionalPackages :: ![String] , ghciMainIs :: !(Maybe Text) , ghciLoadLocalDeps :: !Bool , ghciSkipIntermediate :: !Bool , ghciHidePackages :: !Bool , ghciNoBuild :: !Bool } deriving Show -- | Necessary information to load a package or its components. data GhciPkgInfo = GhciPkgInfo { ghciPkgName :: !PackageName , ghciPkgOpts :: ![(NamedComponent, BuildInfoOpts)] , ghciPkgDir :: !(Path Abs Dir) , ghciPkgModules :: !(Set ModuleName) , ghciPkgModFiles :: !(Set (Path Abs File)) -- ^ Module file paths. , ghciPkgCFiles :: !(Set (Path Abs File)) -- ^ C files. , ghciPkgMainIs :: !(Map NamedComponent (Set (Path Abs File))) , ghciPkgTargetFiles :: !(Maybe (Set (Path Abs File))) , ghciPkgPackage :: !Package } deriving Show data GhciException = InvalidPackageOption String | LoadingDuplicateModules | MissingFileTarget String | Can'tSpecifyFilesAndTargets | Can'tSpecifyFilesAndMainIs deriving (Typeable) instance Exception GhciException instance Show GhciException where show (InvalidPackageOption name) = "Failed to parse --package option " ++ name show LoadingDuplicateModules = unlines [ "Not attempting to start ghci due to these duplicate modules." , "Use --no-load to try to start it anyway, without loading any modules (but these are still likely to cause errors)" ] show (MissingFileTarget name) = "Cannot find file target " ++ name show Can'tSpecifyFilesAndTargets = "Cannot use 'stack ghci' with both file targets and build targets" show Can'tSpecifyFilesAndMainIs = "Cannot use 'stack ghci' with both file targets and --main-is flag" -- | Launch a GHCi session for the given local package targets with the -- given options and configure it with the load paths and extensions -- of those targets. ghci :: (StackM r m, HasEnvConfig r, MonadBaseUnlift IO m) => GhciOpts -> m () ghci opts@GhciOpts{..} = do let buildOptsCLI = defaultBuildOptsCLI { boptsCLITargets = [] , boptsCLIFlags = ghciFlags } -- Load source map, without explicit targets, to collect all info. (locals, sourceMap) <- loadSourceMap AllowNoTargets buildOptsCLI -- Parse --main-is argument. mainIsTargets <- parseMainIsTargets buildOptsCLI ghciMainIs -- Parse to either file targets or build targets etargets <- preprocessTargets ghciTargets (inputTargets, mfileTargets) <- case etargets of Left rawFileTargets -> do case mainIsTargets of Nothing -> return () Just _ -> throwM Can'tSpecifyFilesAndMainIs -- Figure out targets based on filepath targets (targetMap, fileInfo, extraFiles) <- findFileTargets locals rawFileTargets return (targetMap, Just (fileInfo, extraFiles)) Right rawTargets -> do (_,_,normalTargets) <- parseTargetsFromBuildOpts AllowNoTargets buildOptsCLI { boptsCLITargets = rawTargets } return (normalTargets, Nothing) -- Make sure the targets are known. checkTargets inputTargets -- Get a list of all the local target packages. localTargets <- getAllLocalTargets opts inputTargets mainIsTargets sourceMap -- Check if additional package arguments are sensible. addPkgs <- checkAdditionalPackages ghciAdditionalPackages -- Build required dependencies and setup local packages. buildDepsAndInitialSteps opts (map (packageNameText . fst) localTargets) -- Load the list of modules _after_ building, to catch changes in unlisted dependencies (#1180) pkgs <- getGhciPkgInfos buildOptsCLI sourceMap addPkgs (fmap fst mfileTargets) localTargets checkForIssues pkgs -- Finally, do the invocation of ghci runGhci opts localTargets mainIsTargets pkgs (maybe [] snd mfileTargets) preprocessTargets :: (StackM r m) => [Text] -> m (Either [Path Abs File] [Text]) preprocessTargets rawTargets = do let (fileTargetsRaw, normalTargets) = partition (\t -> ".hs" `T.isSuffixOf` t || ".lhs" `T.isSuffixOf` t) rawTargets fileTargets <- forM fileTargetsRaw $ \fp0 -> do let fp = T.unpack fp0 mpath <- forgivingAbsence (resolveFile' fp) case mpath of Nothing -> throwM (MissingFileTarget fp) Just path -> return path case (null fileTargets, null normalTargets) of (False, False) -> throwM Can'tSpecifyFilesAndTargets (False, _) -> return (Left fileTargets) _ -> return (Right normalTargets) parseMainIsTargets :: (StackM r m, HasEnvConfig r) => BuildOptsCLI -> Maybe Text -> m (Maybe (Map PackageName SimpleTarget)) parseMainIsTargets buildOptsCLI mtarget = forM mtarget $ \target -> do (_,_,targets) <- parseTargetsFromBuildOpts AllowNoTargets buildOptsCLI { boptsCLITargets = [target] } return targets findFileTargets :: (StackM r m, HasEnvConfig r) => [LocalPackage] -> [Path Abs File] -> m (Map PackageName SimpleTarget, Map PackageName (Set (Path Abs File)), [Path Abs File]) findFileTargets locals fileTargets = do filePackages <- forM locals $ \lp -> do (_,compFiles,_,_) <- getPackageFiles (packageFiles (lpPackage lp)) (lpCabalFile lp) return (lp, M.map (S.map dotCabalGetPath) compFiles) let foundFileTargetComponents :: [(Path Abs File, [(PackageName, NamedComponent)])] foundFileTargetComponents = map (\fp -> (fp, ) $ sort $ concatMap (\(lp, files) -> map ((packageName (lpPackage lp), ) . fst) (filter (S.member fp . snd) (M.toList files)) ) filePackages ) fileTargets results <- forM foundFileTargetComponents $ \(fp, xs) -> case xs of [] -> do $prettyWarn $ "Couldn't find a component for file target" <+> display fp <> ". Attempting to load anyway." return $ Left fp [x] -> do $prettyInfo $ "Using configuration for" <+> display x <+> "to load" <+> display fp return $ Right (fp, x) (x:_) -> do $prettyWarn $ "Multiple components contain file target" <+> display fp <> ":" <+> mconcat (intersperse ", " (map display xs)) <> line <> "Guessing the first one," <+> display x <> "." return $ Right (fp, x) let (extraFiles, associatedFiles) = partitionEithers results targetMap = foldl unionSimpleTargets M.empty $ map (\(_, (name, comp)) -> M.singleton name (STLocalComps (S.singleton comp))) associatedFiles infoMap = foldl (M.unionWith S.union) M.empty $ map (\(fp, (name, _)) -> M.singleton name (S.singleton fp)) associatedFiles return (targetMap, infoMap, extraFiles) checkTargets :: (StackM r m, HasEnvConfig r) => Map PackageName SimpleTarget -> m () checkTargets mp = do let filtered = M.filter (== STUnknown) mp unless (M.null filtered) $ do bconfig <- view buildConfigL throwM $ UnknownTargets (M.keysSet filtered) M.empty (bcStackYaml bconfig) getAllLocalTargets :: (StackM r m, HasEnvConfig r) => GhciOpts -> Map PackageName SimpleTarget -> Maybe (Map PackageName SimpleTarget) -> SourceMap -> m [(PackageName, (Path Abs File, SimpleTarget))] getAllLocalTargets GhciOpts{..} targets0 mainIsTargets sourceMap = do -- Use the 'mainIsTargets' as normal targets, for CLI concision. See -- #1845. This is a little subtle - we need to do the target parsing -- independently in order to handle the case where no targets are -- specified. let targets = maybe targets0 (unionSimpleTargets targets0) mainIsTargets packages <- getLocalPackages -- Find all of the packages that are directly demanded by the -- targets. directlyWanted <- forMaybeM (M.toList packages) $ \(dir,treatLikeExtraDep) -> do cabalfp <- findOrGenerateCabalFile dir name <- parsePackageNameFromFilePath cabalfp if treatLikeExtraDep then return Nothing else case M.lookup name targets of Just simpleTargets -> return (Just (name, (cabalfp, simpleTargets))) Nothing -> return Nothing -- Figure out let extraLoadDeps = getExtraLoadDeps ghciLoadLocalDeps sourceMap directlyWanted if (ghciSkipIntermediate && not ghciLoadLocalDeps) || null extraLoadDeps then return directlyWanted else do let extraList = T.intercalate ", " (map (packageNameText . fst) extraLoadDeps) if ghciLoadLocalDeps then $logInfo $ T.concat [ "The following libraries will also be loaded into GHCi because " , "they are local dependencies of your targets, and you specified --load-local-deps:\n " , extraList ] else $logInfo $ T.concat [ "The following libraries will also be loaded into GHCi because " , "they are intermediate dependencies of your targets:\n " , extraList , "\n(Use --skip-intermediate-deps to omit these)" ] return (directlyWanted ++ extraLoadDeps) buildDepsAndInitialSteps :: (StackM r m, HasEnvConfig r, MonadBaseUnlift IO m) => GhciOpts -> [Text] -> m () buildDepsAndInitialSteps GhciOpts{..} targets0 = do let targets = targets0 ++ map T.pack ghciAdditionalPackages -- If necessary, do the build, for local packagee targets, only do -- 'initialBuildSteps'. when (not ghciNoBuild && not (null targets)) $ do eres <- tryAny $ build (const (return ())) Nothing defaultBuildOptsCLI { boptsCLITargets = targets , boptsCLIInitialBuildSteps = True , boptsCLIFlags = ghciFlags , boptsCLIGhcOptions = ghciGhcOptions } case eres of Right () -> return () Left err -> do $prettyError $ fromString (show err) $prettyWarn "Build failed, but optimistically launching GHCi anyway" checkAdditionalPackages :: MonadThrow m => [String] -> m [PackageName] checkAdditionalPackages pkgs = forM pkgs $ \name -> do let mres = (packageIdentifierName <$> parsePackageIdentifierFromString name) <|> parsePackageNameFromString name maybe (throwM $ InvalidPackageOption name) return mres runGhci :: (StackM r m, HasEnvConfig r) => GhciOpts -> [(PackageName, (Path Abs File, SimpleTarget))] -> Maybe (Map PackageName SimpleTarget) -> [GhciPkgInfo] -> [Path Abs File] -> m () runGhci GhciOpts{..} targets mainIsTargets pkgs extraFiles = do config <- view configL wc <- view $ actualCompilerVersionL.whichCompilerL let pkgopts = hidePkgOpt ++ genOpts ++ ghcOpts hidePkgOpt = if null pkgs || not ghciHidePackages then [] else ["-hide-all-packages"] oneWordOpts bio | ghciHidePackages = bioOneWordOpts bio ++ bioPackageFlags bio | otherwise = bioOneWordOpts bio genOpts = nubOrd (concatMap (concatMap (oneWordOpts . snd) . ghciPkgOpts) pkgs) (omittedOpts, ghcOpts) = partition badForGhci $ concatMap (concatMap (bioOpts . snd) . ghciPkgOpts) pkgs ++ getUserOptions Nothing ++ concatMap (getUserOptions . Just . ghciPkgName) pkgs getUserOptions mpkg = map T.unpack (M.findWithDefault [] mpkg (unGhcOptions (configGhcOptions config))) badForGhci x = isPrefixOf "-O" x || elem x (words "-debug -threaded -ticky -static -Werror") unless (null omittedOpts) $ $logWarn ("The following GHC options are incompatible with GHCi and have not been passed to it: " <> T.unwords (map T.pack (nubOrd omittedOpts))) oiDir <- view objectInterfaceDirL let odir = [ "-odir=" <> toFilePathNoTrailingSep oiDir , "-hidir=" <> toFilePathNoTrailingSep oiDir ] $logInfo ("Configuring GHCi with the following packages: " <> T.intercalate ", " (map (packageNameText . ghciPkgName) pkgs)) let execGhci extras = do menv <- liftIO $ configEnvOverride config defaultEnvSettings execSpawn menv (fromMaybe (compilerExeName wc) ghciGhcCommand) (("--interactive" : ) $ -- This initial "-i" resets the include directories to -- not include CWD. If there aren't any packages, CWD -- is included. (if null pkgs then id else ("-i" : )) $ odir <> pkgopts <> map T.unpack ghciGhcOptions <> ghciArgs <> extras) interrogateExeForRenderFunction = do menv <- liftIO $ configEnvOverride config defaultEnvSettings output <- execObserve menv (fromMaybe (compilerExeName wc) ghciGhcCommand) ["--version"] if "Intero" `isPrefixOf` output then return renderScriptIntero else return renderScriptGhci withSystemTempDir "ghci" $ \tmpDirectory -> do macrosOptions <- writeMacrosFile tmpDirectory pkgs if ghciNoLoadModules then execGhci macrosOptions else do checkForDuplicateModules pkgs renderFn <- interrogateExeForRenderFunction bopts <- view buildOptsL mainFile <- figureOutMainFile bopts mainIsTargets targets pkgs scriptPath <- writeGhciScript tmpDirectory (renderFn pkgs mainFile extraFiles) execGhci (macrosOptions ++ ["-ghci-script=" <> toFilePath scriptPath]) writeMacrosFile :: (MonadIO m) => Path Abs Dir -> [GhciPkgInfo] -> m [String] writeMacrosFile tmpDirectory packages = do preprocessCabalMacros packages macrosFile where macrosFile = tmpDirectory $(mkRelFile "cabal_macros.h") writeGhciScript :: (MonadIO m) => Path Abs Dir -> GhciScript -> m (Path Abs File) writeGhciScript tmpDirectory script = do liftIO $ scriptToFile scriptPath script setScriptPerms scriptFilePath return scriptPath where scriptPath = tmpDirectory $(mkRelFile "ghci-script") scriptFilePath = toFilePath scriptPath findOwningPackageForMain :: [GhciPkgInfo] -> Path Abs File -> Maybe GhciPkgInfo findOwningPackageForMain pkgs mainFile = find (\pkg -> toFilePath (ghciPkgDir pkg) `isPrefixOf` toFilePath mainFile) pkgs renderScriptGhci :: [GhciPkgInfo] -> Maybe (Path Abs File) -> [Path Abs File] -> GhciScript renderScriptGhci pkgs mainFile extraFiles = let addPhase = mconcat $ fmap renderPkg pkgs mainPhase = case mainFile of Just path -> cmdAddFile path Nothing -> mempty modulePhase = cmdModule $ foldl' S.union S.empty (fmap ghciPkgModules pkgs) in case getFileTargets pkgs <> extraFiles of [] -> addPhase <> mainPhase <> modulePhase fileTargets -> mconcat $ map cmdAddFile fileTargets where renderPkg pkg = cmdAdd (ghciPkgModules pkg) renderScriptIntero :: [GhciPkgInfo] -> Maybe (Path Abs File) -> [Path Abs File] -> GhciScript renderScriptIntero pkgs mainFile extraFiles = let addPhase = mconcat $ fmap renderPkg pkgs mainPhase = case mainFile of Just path -> case findOwningPackageForMain pkgs path of Just mainPkg -> cmdCdGhc (ghciPkgDir mainPkg) <> cmdAddFile path Nothing -> cmdAddFile path Nothing -> mempty modulePhase = cmdModule $ foldl' S.union S.empty (fmap ghciPkgModules pkgs) in case getFileTargets pkgs <> extraFiles of [] -> addPhase <> mainPhase <> modulePhase fileTargets -> mconcat $ map cmdAddFile fileTargets where renderPkg pkg = cmdCdGhc (ghciPkgDir pkg) <> cmdAdd (ghciPkgModules pkg) -- Hacky check if module / main phase should be omitted. This should be -- improved if / when we have a better per-component load. getFileTargets :: [GhciPkgInfo] -> [Path Abs File] getFileTargets = concatMap (concatMap S.toList . maybeToList . ghciPkgTargetFiles) -- | Figure out the main-is file to load based on the targets. Sometimes there -- is none, sometimes it's unambiguous, sometimes it's -- ambiguous. Warns and returns nothing if it's ambiguous. figureOutMainFile :: (StackM r m) => BuildOpts -> Maybe (Map PackageName SimpleTarget) -> [(PackageName, (Path Abs File, SimpleTarget))] -> [GhciPkgInfo] -> m (Maybe (Path Abs File)) figureOutMainFile bopts mainIsTargets targets0 packages = do case candidates of [] -> return Nothing [c@(_,_,fp)] -> do $logInfo ("Using main module: " <> renderCandidate c) return (Just fp) candidate:_ -> do borderedWarning $ do $logWarn "The main module to load is ambiguous. Candidates are: " forM_ (map renderCandidate candidates) $logWarn $logWarn "You can specify which one to pick by: " $logWarn (" * Specifying targets to stack ghci e.g. stack ghci " <> sampleTargetArg candidate) $logWarn (" * Specifying what the main is e.g. stack ghci " <> sampleMainIsArg candidate) $logWarn (" * Choosing from the candidate above [1.." <> T.pack (show $ length candidates) <> "]") liftIO userOption where targets = fromMaybe (M.fromList $ map (\(k, (_, x)) -> (k, x)) targets0) mainIsTargets candidates = do pkg <- packages case M.lookup (ghciPkgName pkg) targets of Nothing -> [] Just target -> do (component,mains) <- M.toList $ M.filterWithKey (\k _ -> k `S.member` wantedComponents) (ghciPkgMainIs pkg) main <- S.toList mains return (ghciPkgName pkg, component, main) where wantedComponents = wantedPackageComponents bopts target (ghciPkgPackage pkg) renderCandidate c@(pkgName,namedComponent,mainIs) = let candidateIndex = T.pack . show . (+1) . fromMaybe 0 . elemIndex c in candidateIndex candidates <> ". Package `" <> packageNameText pkgName <> "' component " <> renderComp namedComponent <> " with main-is file: " <> T.pack (toFilePath mainIs) candidateIndices = take (length candidates) [1 :: Int ..] userOption = do putStr "Specify main module to use (press enter to load none): " option <- getLine let selected = fromMaybe ((+1) $ length candidateIndices) (readMaybe option :: Maybe Int) case elemIndex selected candidateIndices of Nothing -> do putStrLn "Not loading any main modules, as no valid module selected" putStrLn "" return Nothing Just op -> do let (_,_,fp) = candidates !! op putStrLn ("Loading main module from candidate " <> show (op + 1) <> ", --main-is " <> toFilePath fp) putStrLn "" return $ Just fp renderComp c = case c of CLib -> "lib" CExe name -> "exe:" <> name CTest name -> "test:" <> name CBench name -> "bench:" <> name sampleTargetArg (pkg,comp,_) = packageNameText pkg <> ":" <> renderComp comp sampleMainIsArg (pkg,comp,_) = "--main-is " <> packageNameText pkg <> ":" <> renderComp comp getGhciPkgInfos :: (StackM r m, HasEnvConfig r) => BuildOptsCLI -> SourceMap -> [PackageName] -> Maybe (Map PackageName (Set (Path Abs File))) -> [(PackageName, (Path Abs File, SimpleTarget))] -> m [GhciPkgInfo] getGhciPkgInfos buildOptsCLI sourceMap addPkgs mfileTargets localTargets = do menv <- getMinimalEnvOverride (installedMap, _, _, _) <- getInstalled menv GetInstalledOpts { getInstalledProfiling = False , getInstalledHaddock = False , getInstalledSymbols = False } sourceMap let localLibs = [name | (name, (_, target)) <- localTargets, hasLocalComp isCLib target] forM localTargets $ \(name, (cabalfp, target)) -> makeGhciPkgInfo buildOptsCLI sourceMap installedMap localLibs addPkgs mfileTargets name cabalfp target -- | Make information necessary to load the given package in GHCi. makeGhciPkgInfo :: (StackM r m, HasEnvConfig r) => BuildOptsCLI -> SourceMap -> InstalledMap -> [PackageName] -> [PackageName] -> Maybe (Map PackageName (Set (Path Abs File))) -> PackageName -> Path Abs File -> SimpleTarget -> m GhciPkgInfo makeGhciPkgInfo buildOptsCLI sourceMap installedMap locals addPkgs mfileTargets name cabalfp target = do bopts <- view buildOptsL econfig <- view envConfigL bconfig <- view buildConfigL compilerVersion <- view actualCompilerVersionL let config = PackageConfig { packageConfigEnableTests = True , packageConfigEnableBenchmarks = True , packageConfigFlags = getLocalFlags bconfig buildOptsCLI name , packageConfigGhcOptions = getGhcOptions bconfig buildOptsCLI name True True , packageConfigCompilerVersion = compilerVersion , packageConfigPlatform = view platformL econfig } (warnings,gpkgdesc) <- readPackageUnresolved cabalfp -- Source the package's *.buildinfo file created by configure if any. See -- https://www.haskell.org/cabal/users-guide/developing-packages.html#system-dependent-parameters buildinfofp <- parseRelFile (T.unpack (packageNameText name) ++ ".buildinfo") hasDotBuildinfo <- doesFileExist (parent cabalfp buildinfofp) let mbuildinfofp | hasDotBuildinfo = Just (parent cabalfp buildinfofp) | otherwise = Nothing mbuildinfo <- forM mbuildinfofp readDotBuildinfo let pkg = packageFromPackageDescription config (C.genPackageFlags gpkgdesc) $ maybe id C.updatePackageDescription mbuildinfo $ resolvePackageDescription config gpkgdesc mapM_ (printCabalFileWarning cabalfp) warnings (mods,files,opts) <- getPackageOpts (packageOpts pkg) sourceMap installedMap locals addPkgs cabalfp let filteredOpts = filterWanted opts filterWanted = M.filterWithKey (\k _ -> k `S.member` allWanted) allWanted = wantedPackageComponents bopts target pkg setMapMaybe f = S.fromList . mapMaybe f . S.toList return GhciPkgInfo { ghciPkgName = packageName pkg , ghciPkgOpts = M.toList filteredOpts , ghciPkgDir = parent cabalfp , ghciPkgModules = mconcat (M.elems (filterWanted mods)) , ghciPkgModFiles = mconcat (M.elems (filterWanted (M.map (setMapMaybe dotCabalModulePath) files))) , ghciPkgMainIs = M.map (setMapMaybe dotCabalMainPath) files , ghciPkgCFiles = mconcat (M.elems (filterWanted (M.map (setMapMaybe dotCabalCFilePath) files))) , ghciPkgTargetFiles = mfileTargets >>= M.lookup name , ghciPkgPackage = pkg } -- NOTE: this should make the same choices as the components code in -- 'loadLocalPackage'. Unfortunately for now we reiterate this logic -- (differently). wantedPackageComponents :: BuildOpts -> SimpleTarget -> Package -> Set NamedComponent wantedPackageComponents _ (STLocalComps cs) _ = cs wantedPackageComponents bopts STLocalAll pkg = S.fromList $ (if packageHasLibrary pkg then [CLib] else []) ++ map CExe (S.toList (packageExes pkg)) <> (if boptsTests bopts then map CTest (M.keys (packageTests pkg)) else []) <> (if boptsBenchmarks bopts then map CBench (S.toList (packageBenchmarks pkg)) else []) wantedPackageComponents _ _ _ = S.empty checkForIssues :: (MonadThrow m, MonadLogger m) => [GhciPkgInfo] -> m () checkForIssues pkgs = do unless (null issues) $ borderedWarning $ do $logWarn "Warning: There are cabal settings for this project which may prevent GHCi from loading your code properly." $logWarn "In some cases it can also load some projects which would otherwise fail to build." $logWarn "" mapM_ $logWarn $ intercalate [""] issues $logWarn "" $logWarn "To resolve, remove the flag(s) from the cabal file(s) and instead put them at the top of the haskell files." $logWarn "" $logWarn "It isn't yet possible to load multiple packages into GHCi in all cases - see" $logWarn "https://ghc.haskell.org/trac/ghc/ticket/10827" where issues = concat [ mixedFlag "-XNoImplicitPrelude" [ "-XNoImplicitPrelude will be used, but GHCi will likely fail to build things which depend on the implicit prelude." ] , mixedFlag "-XCPP" [ "-XCPP will be used, but it can cause issues with multiline strings." , "See https://downloads.haskell.org/~ghc/7.10.2/docs/html/users_guide/options-phases.html#cpp-string-gaps" ] , mixedFlag "-XNoTraditionalRecordSyntax" [ "-XNoTraditionalRecordSyntax will be used, but it break modules which use record syntax." ] , mixedFlag "-XTemplateHaskell" [ "-XTemplateHaskell will be used, but it may cause compilation issues due to different parsing of '$' when there's no space after it." ] , mixedFlag "-XQuasiQuotes" [ "-XQuasiQuotes will be used, but it may cause parse failures due to a different meaning for list comprehension syntax like [x| ... ]" ] , mixedFlag "-XSafe" [ "-XSafe will be used, but it will fail to compile unsafe modules." ] , mixedFlag "-XArrows" [ "-XArrows will be used, but it will cause non-arrow usages of proc, (-<), (-<<) to fail" ] , mixedFlag "-XOverloadedStrings" [ "-XOverloadedStrings will be used, but it can cause type ambiguity in code not usually compiled with it." ] , mixedFlag "-XOverloadedLists" [ "-XOverloadedLists will be used, but it can cause type ambiguity in code not usually compiled with it." ] , mixedFlag "-XMonoLocalBinds" [ "-XMonoLocalBinds will be used, but it can cause type errors in code which expects generalized local bindings." ] , mixedFlag "-XTypeFamilies" [ "-XTypeFamilies will be used, but it implies -XMonoLocalBinds, and so can cause type errors in code which expects generalized local bindings." ] , mixedFlag "-XGADTs" [ "-XGADTs will be used, but it implies -XMonoLocalBinds, and so can cause type errors in code which expects generalized local bindings." ] , mixedFlag "-XNewQualifiedOperators" [ "-XNewQualifiedOperators will be used, but this will break usages of the old qualified operator syntax." ] ] mixedFlag flag msgs = let x = partitionComps (== flag) in [ msgs ++ showWhich x | mixedSettings x ] mixedSettings (xs, ys) = xs /= [] && ys /= [] showWhich (haveIt, don'tHaveIt) = [ "It is specified for:" , " " <> renderPkgComponents haveIt , "But not for: " , " " <> renderPkgComponents don'tHaveIt ] partitionComps f = (map fst xs, map fst ys) where (xs, ys) = partition (any f . snd) compsWithOpts compsWithOpts = map (\(k, bio) -> (k, bioOneWordOpts bio ++ bioOpts bio)) compsWithBios compsWithBios = [ ((ghciPkgName pkg, c), bio) | pkg <- pkgs , (c, bio) <- ghciPkgOpts pkg ] borderedWarning :: MonadLogger m => m a -> m a borderedWarning f = do $logWarn "" $logWarn "* * * * * * * *" x <- f $logWarn "* * * * * * * *" $logWarn "" return x checkForDuplicateModules :: (MonadThrow m, MonadLogger m) => [GhciPkgInfo] -> m () checkForDuplicateModules pkgs = do unless (null duplicates) $ do borderedWarning $ do $logWarn "The following modules are present in multiple packages:" forM_ duplicates $ \(mn, pns) -> do $logWarn (" * " <> T.pack mn <> " (in " <> T.intercalate ", " (map packageNameText pns) <> ")") throwM LoadingDuplicateModules where duplicates, allModules :: [(String, [PackageName])] duplicates = filter (not . null . tail . snd) allModules allModules = M.toList $ M.fromListWith (++) $ concatMap (\pkg -> map ((, [ghciPkgName pkg]) . C.display) (S.toList (ghciPkgModules pkg))) pkgs -- Adds in intermediate dependencies between ghci targets. Note that it -- will return a Lib component for these intermediate dependencies even -- if they don't have a library (but that's fine for the usage within -- this module). -- -- If 'True' is passed for loadAllDeps, this loads all local deps, even -- if they aren't intermediate. getExtraLoadDeps :: Bool -> SourceMap -> [(PackageName, (Path Abs File, SimpleTarget))] -> [(PackageName, (Path Abs File, SimpleTarget))] getExtraLoadDeps loadAllDeps sourceMap targets = M.toList $ (\mp -> foldl' (flip M.delete) mp (map fst targets)) $ M.mapMaybe id $ execState (mapM_ (mapM_ go . getDeps . fst) targets) (M.fromList (map (second Just) targets)) where getDeps :: PackageName -> [PackageName] getDeps name = case M.lookup name sourceMap of Just (PSLocal lp) -> M.keys (packageDeps (lpPackage lp)) _ -> [] go :: PackageName -> State (Map PackageName (Maybe (Path Abs File, SimpleTarget))) Bool go name = do cache <- get case (M.lookup name cache, M.lookup name sourceMap) of (Just (Just _), _) -> return True (Just Nothing, _) | not loadAllDeps -> return False (_, Just (PSLocal lp)) -> do let deps = M.keys (packageDeps (lpPackage lp)) shouldLoad <- liftM or $ mapM go deps if shouldLoad then do modify (M.insert name (Just (lpCabalFile lp, STLocalComps (S.singleton CLib)))) return True else do modify (M.insert name Nothing) return False (_, Just PSUpstream{}) -> return loadAllDeps (_, _) -> return False preprocessCabalMacros :: MonadIO m => [GhciPkgInfo] -> Path Abs File -> m [String] preprocessCabalMacros pkgs out = liftIO $ do let fps = nubOrd (concatMap (mapMaybe (bioCabalMacros . snd) . ghciPkgOpts) pkgs) files <- mapM (S8.readFile . toFilePath) fps if null files then return [] else do S8.writeFile (toFilePath out) $ S8.concat $ map (<> "\n#undef CURRENT_PACKAGE_KEY\n#undef CURRENT_COMPONENT_ID\n") files return ["-optP-include", "-optP" <> toFilePath out] setScriptPerms :: MonadIO m => FilePath -> m () #ifdef WINDOWS setScriptPerms _ = do return () #else setScriptPerms fp = do liftIO $ Posix.setFileMode fp $ foldl1 Posix.unionFileModes [ Posix.ownerReadMode , Posix.ownerWriteMode , Posix.groupReadMode , Posix.otherReadMode ] #endif unionSimpleTargets :: Ord k => Map k SimpleTarget -> Map k SimpleTarget -> Map k SimpleTarget unionSimpleTargets = M.unionWith $ \l r -> case (l, r) of (STUnknown, _) -> r (STNonLocal, _) -> r (STLocalComps sl, STLocalComps sr) -> STLocalComps (S.union sl sr) (STLocalComps _, STLocalAll) -> STLocalAll (STLocalComps _, _) -> l (STLocalAll, _) -> STLocalAll hasLocalComp :: (NamedComponent -> Bool) -> SimpleTarget -> Bool hasLocalComp p t = case t of STLocalComps s -> any p (S.toList s) STLocalAll -> True _ -> False {- Copied from Stack.Ide, may be useful in the future -- | Get options and target files for the given package info. getPackageOptsAndTargetFiles :: (MonadThrow m, MonadIO m, MonadReader env m, HasEnvConfig env) => Path Abs Dir -> GhciPkgInfo -> m ([FilePath], [FilePath]) getPackageOptsAndTargetFiles pwd pkg = do dist <- distDirFromDir (ghciPkgDir pkg) let autogen = autogenDir dist paths_foo <- liftM (autogen ) (parseRelFile ("Paths_" ++ packageNameString (ghciPkgName pkg) ++ ".hs")) paths_foo_exists <- doesFileExist paths_foo let ghcOptions bio = bioOneWordOpts bio ++ bioOpts bio ++ bioPackageFlags bio ++ maybe [] (\cabalMacros -> ["-optP-include", "-optP" <> toFilePath cabalMacros]) (bioCabalMacros bio) return ( ("--dist-dir=" <> toFilePathNoTrailingSep dist) : -- FIXME: use compilerOptionsCabalFlag map ("--ghc-option=" ++) (concatMap (ghcOptions . snd) (ghciPkgOpts pkg)) , mapMaybe (fmap toFilePath . stripDir pwd) (S.toList (ghciPkgCFiles pkg) <> S.toList (ghciPkgModFiles pkg) <> [paths_foo | paths_foo_exists])) -- | List load targets for a package target. targetsCmd :: Text -> GlobalOpts -> IO () targetsCmd target go@GlobalOpts{..} = withBuildConfig go $ do let boptsCli = defaultBuildOptsCLI { boptsCLITargets = [target] } (_realTargets,_,pkgs) <- ghciSetup (ideGhciOpts boptsCli) pwd <- getCurrentDir targets <- fmap (concat . snd . unzip) (mapM (getPackageOptsAndTargetFiles pwd) pkgs) forM_ targets (liftIO . putStrLn) -} stack-1.5.1/src/Stack/Ghci/Script.hs0000644000000000000000000000600413135652051015320 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Stack.Ghci.Script ( GhciScript , ModuleName , cmdAdd , cmdAddFile , cmdCdGhc , cmdModule , scriptToLazyByteString , scriptToBuilder , scriptToFile ) where import Control.Applicative import Data.ByteString.Lazy (ByteString) import Data.ByteString.Builder import Data.Monoid import Data.List import Data.Set (Set) import qualified Data.Set as S import Data.Text (Text) import Data.Text.Encoding (encodeUtf8Builder) import Path import Prelude -- Fix redundant imports warnings import System.IO import Distribution.ModuleName hiding (toFilePath) newtype GhciScript = GhciScript { unGhciScript :: [GhciCommand] } instance Monoid GhciScript where mempty = GhciScript [] (GhciScript xs) `mappend` (GhciScript ys) = GhciScript (ys <> xs) data GhciCommand = Add (Set ModuleName) | AddFile (Path Abs File) | CdGhc (Path Abs Dir) | Module (Set ModuleName) deriving (Show) cmdAdd :: Set ModuleName -> GhciScript cmdAdd = GhciScript . (:[]) . Add cmdAddFile :: Path Abs File -> GhciScript cmdAddFile = GhciScript . (:[]) . AddFile cmdCdGhc :: Path Abs Dir -> GhciScript cmdCdGhc = GhciScript . (:[]) . CdGhc cmdModule :: Set ModuleName -> GhciScript cmdModule = GhciScript . (:[]) . Module scriptToLazyByteString :: GhciScript -> ByteString scriptToLazyByteString = toLazyByteString . scriptToBuilder scriptToBuilder :: GhciScript -> Builder scriptToBuilder backwardScript = mconcat $ fmap commandToBuilder script where script = reverse $ unGhciScript backwardScript scriptToFile :: Path Abs File -> GhciScript -> IO () scriptToFile path script = withFile filepath WriteMode $ \hdl -> do hSetBuffering hdl (BlockBuffering Nothing) hSetBinaryMode hdl True hPutBuilder hdl (scriptToBuilder script) where filepath = toFilePath path -- Command conversion fromText :: Text -> Builder fromText = encodeUtf8Builder commandToBuilder :: GhciCommand -> Builder commandToBuilder (Add modules) | S.null modules = mempty | otherwise = fromText ":add " <> mconcat (intersperse (fromText " ") $ (stringUtf8 . quoteFileName . mconcat . intersperse "." . components) <$> S.toAscList modules) <> fromText "\n" commandToBuilder (AddFile path) = fromText ":add " <> stringUtf8 (quoteFileName (toFilePath path)) <> fromText "\n" commandToBuilder (CdGhc path) = fromText ":cd-ghc " <> stringUtf8 (quoteFileName (toFilePath path)) <> fromText "\n" commandToBuilder (Module modules) | S.null modules = fromText ":module +\n" | otherwise = fromText ":module + " <> mconcat (intersperse (fromText " ") $ (stringUtf8 . quoteFileName . mconcat . intersperse "." . components) <$> S.toAscList modules) <> fromText "\n" -- | Make sure that a filename with spaces in it gets the proper quotes. quoteFileName :: String -> String quoteFileName x = if ' ' `elem` x then show x else x stack-1.5.1/src/Stack/Hoogle.hs0000644000000000000000000001665613135652051014435 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} -- | A wrapper around hoogle. module Stack.Hoogle ( hoogleCmd ) where import Control.Exception import Control.Monad.IO.Class import Control.Monad.Logger import qualified Data.ByteString.Char8 as S8 import Data.List (find) import Data.Monoid import qualified Data.Set as Set import Lens.Micro import Path import Path.IO import qualified Stack.Build import Stack.Fetch import Stack.Runners import Stack.Types.Config import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.StackT import Stack.Types.Version import System.Exit import System.Process.Read (resetExeCache, tryProcessStdout) import System.Process.Run -- | Hoogle command. hoogleCmd :: ([String],Bool,Bool) -> GlobalOpts -> IO () hoogleCmd (args,setup,rebuild) go = withBuildConfig go pathToHaddocks where pathToHaddocks :: StackT EnvConfig IO () pathToHaddocks = do hoogleIsInPath <- checkHoogleInPath if hoogleIsInPath then haddocksToDb else do if setup then do $logWarn "Hoogle isn't installed or is too old. Automatically installing (use --no-setup to disable) ..." installHoogle haddocksToDb else do $logError "Hoogle isn't installed or is too old. Not installing it due to --no-setup." bail haddocksToDb :: StackT EnvConfig IO () haddocksToDb = do databaseExists <- checkDatabaseExists if databaseExists && not rebuild then runHoogle args else if setup || rebuild then do $logWarn (if rebuild then "Rebuilding database ..." else "No Hoogle database yet. Automatically building haddocks and hoogle database (use --no-setup to disable) ...") buildHaddocks $logInfo "Built docs." generateDb $logInfo "Generated DB." runHoogle args else do $logError "No Hoogle database. Not building one due to --no-setup" bail generateDb :: StackT EnvConfig IO () generateDb = do do dir <- hoogleRoot createDirIfMissing True dir runHoogle ["generate", "--local"] buildHaddocks :: StackT EnvConfig IO () buildHaddocks = liftIO (catch (withBuildConfigAndLock (set (globalOptsBuildOptsMonoidL . buildOptsMonoidHaddockL) (Just True) go) (\lk -> Stack.Build.build (const (return ())) lk defaultBuildOptsCLI)) (\(_ :: ExitCode) -> return ())) installHoogle :: StackT EnvConfig IO () installHoogle = do let hooglePackageName = $(mkPackageName "hoogle") hoogleMinVersion = $(mkVersion "5.0") hoogleMinIdent = PackageIdentifier hooglePackageName hoogleMinVersion hooglePackageIdentifier <- do (_,_,resolved) <- resolvePackagesAllowMissing -- FIXME this Nothing means "do not follow any -- specific snapshot", which matches old -- behavior. However, since introducing the -- logic to pin a name to a package in a -- snapshot, we may arguably want to ensure -- that we're grabbing the version of Hoogle -- present in the snapshot currently being -- used. Nothing mempty (Set.fromList [hooglePackageName]) return (case find ((== hooglePackageName) . packageIdentifierName) (map rpIdent resolved) of Just ident@(PackageIdentifier _ ver) | ver >= hoogleMinVersion -> Right ident _ -> Left hoogleMinIdent) case hooglePackageIdentifier of Left{} -> $logInfo ("Minimum " <> packageIdentifierText hoogleMinIdent <> " is not in your index. Installing the minimum version.") Right ident -> $logInfo ("Minimum version is " <> packageIdentifierText hoogleMinIdent <> ". Found acceptable " <> packageIdentifierText ident <> " in your index, installing it.") config <- view configL menv <- liftIO $ configEnvOverride config envSettings liftIO (catch (withBuildConfigAndLock go (\lk -> Stack.Build.build (const (return ())) lk defaultBuildOptsCLI { boptsCLITargets = [ packageIdentifierText (either id id hooglePackageIdentifier)] })) (\(e :: ExitCode) -> case e of ExitSuccess -> resetExeCache menv _ -> throwIO e)) runHoogle :: [String] -> StackT EnvConfig IO () runHoogle hoogleArgs = do config <- view configL menv <- liftIO $ configEnvOverride config envSettings dbpath <- hoogleDatabasePath let databaseArg = ["--database=" ++ toFilePath dbpath] runCmd Cmd { cmdDirectoryToRunIn = Nothing , cmdCommandToRun = "hoogle" , cmdEnvOverride = menv , cmdCommandLineArguments = hoogleArgs ++ databaseArg } Nothing bail :: StackT EnvConfig IO () bail = liftIO (exitWith (ExitFailure (-1))) checkDatabaseExists = do path <- hoogleDatabasePath liftIO (doesFileExist path) checkHoogleInPath = do config <- view configL menv <- liftIO $ configEnvOverride config envSettings result <- tryProcessStdout Nothing menv "hoogle" ["--numeric-version"] case fmap (reads . S8.unpack) result of Right [(ver :: Double,_)] -> return (ver >= 5.0) _ -> return False envSettings = EnvSettings { esIncludeLocals = True , esIncludeGhcPackagePath = True , esStackExe = True , esLocaleUtf8 = False } stack-1.5.1/src/Stack/IDE.hs0000644000000000000000000000356413135652051013613 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} -- | Functions for IDEs. module Stack.IDE ( listPackages , listTargets ) where import Control.Monad.Logger import Control.Monad.Reader import qualified Data.Map as Map import qualified Data.Set as Set import qualified Data.Text as T import Stack.Build.Source (getLocalPackageViews) import Stack.Build.Target (LocalPackageView(..)) import Stack.Config (getLocalPackages) import Stack.Package (findOrGenerateCabalFile) import Stack.Types.Config import Stack.Types.Package import Stack.Types.PackageName import Stack.Types.StackT -- | List the packages inside the current project. listPackages :: (StackM env m, HasEnvConfig env) => m () listPackages = do -- TODO: Instead of setting up an entire EnvConfig only to look up the package directories, -- make do with a Config (and the Project inside) and use resolvePackageEntry to get -- the directory. packageDirs <- liftM Map.keys getLocalPackages forM_ packageDirs $ \dir -> do cabalfp <- findOrGenerateCabalFile dir pkgName <- parsePackageNameFromFilePath cabalfp ($logInfo . packageNameText) pkgName -- | List the targets in the current project. listTargets :: (StackM env m, HasEnvConfig env) => m () listTargets = do rawLocals <- getLocalPackageViews $logInfo (T.intercalate "\n" (map renderPkgComponent (concatMap toNameAndComponent (Map.toList (Map.map fst rawLocals))))) where toNameAndComponent (pkgName,view') = map (pkgName, ) (Set.toList (lpvComponents view')) stack-1.5.1/src/Stack/Image.hs0000644000000000000000000002142713135652051014232 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} -- | This module builds Docker (OpenContainer) images. module Stack.Image (stageContainerImageArtifacts, createContainerImageFromStage, imgCmdName, imgDockerCmdName, imgOptsFromMonoid) where import Control.Exception.Lifted hiding (finally) import Control.Monad import Control.Monad.Catch hiding (bracket) import Control.Monad.IO.Class import Control.Monad.Logger import Data.Char (toLower) import qualified Data.Map.Strict as Map import Data.Maybe import Data.Typeable import Data.Text (Text) import qualified Data.Text as T import Path import Path.Extra import Path.IO import Stack.Constants import Stack.PrettyPrint import Stack.Types.Config import Stack.Types.Image import Stack.Types.StackT import System.Process.Run -- | Stages the executables & additional content in a staging -- directory under '.stack-work' stageContainerImageArtifacts :: (StackM env m, HasEnvConfig env) => Maybe (Path Abs Dir) -> [Text] -> m () stageContainerImageArtifacts mProjectRoot imageNames = do config <- view configL forM_ (zip [0 ..] (filterImages (map T.unpack imageNames) (imgDockers $ configImage config))) (\(idx,opts) -> do imageDir <- imageStagingDir (fromMaybeProjectRoot mProjectRoot) idx ignoringAbsence (removeDirRecur imageDir) ensureDir imageDir stageExesInDir opts imageDir syncAddContentToDir opts imageDir) -- | Builds a Docker (OpenContainer) image extending the `base` image -- specified in the project's stack.yaml. Then new image will be -- extended with an ENTRYPOINT specified for each `entrypoint` listed -- in the config file. createContainerImageFromStage :: (StackM env m, HasConfig env) => Maybe (Path Abs Dir) -> [Text] -> m () createContainerImageFromStage mProjectRoot imageNames = do config <- view configL forM_ (zip [0 ..] (filterImages (map T.unpack imageNames) (imgDockers $ configImage config))) (\(idx,opts) -> do imageDir <- imageStagingDir (fromMaybeProjectRoot mProjectRoot) idx createDockerImage opts imageDir extendDockerImageWithEntrypoint opts imageDir) filterImages :: [String] -> [ImageDockerOpts] -> [ImageDockerOpts] filterImages [] = id -- all: no filter filterImages names = filter (imageNameFound . imgDockerImageName) where imageNameFound (Just name) = name `elem` names imageNameFound _ = False -- | Stage all the Package executables in the usr/local/bin -- subdirectory of a temp directory. stageExesInDir :: (StackM env m, HasEnvConfig env) => ImageDockerOpts -> Path Abs Dir -> m () stageExesInDir opts dir = do srcBinPath <- fmap ( $(mkRelDir "bin")) installationRootLocal let destBinPath = dir $(mkRelDir "usr/local/bin") ensureDir destBinPath case imgDockerExecutables opts of Nothing -> do $logInfo "" $logInfo "Note: 'executables' not specified for a image container, so every executable in the project's local bin dir will be used." mcontents <- forgivingAbsence $ listDir srcBinPath case mcontents of Just (files, dirs) | not (null files) || not (null dirs) -> copyDirRecur srcBinPath destBinPath _ -> $prettyWarn "The project's local bin dir contains no files, so no executables will be added to the docker image." $logInfo "" Just exes -> forM_ exes (\exe -> copyFile (srcBinPath exe) (destBinPath exe)) -- | Add any additional files into the temp directory, respecting the -- (Source, Destination) mapping. syncAddContentToDir :: (StackM env m, HasEnvConfig env) => ImageDockerOpts -> Path Abs Dir -> m () syncAddContentToDir opts dir = do root <- view projectRootL let imgAdd = imgDockerAdd opts forM_ (Map.toList imgAdd) (\(source,destPath) -> do sourcePath <- resolveDir root source let destFullPath = dir dropRoot destPath ensureDir destFullPath copyDirRecur sourcePath destFullPath) -- | Derive an image name from the project directory. imageName :: Path Abs Dir -> String imageName = map toLower . toFilePathNoTrailingSep . dirname -- | Create a general purpose docker image from the temporary -- directory of executables & static content. createDockerImage :: (StackM env m, HasConfig env) => ImageDockerOpts -> Path Abs Dir -> m () createDockerImage dockerConfig dir = do menv <- getMinimalEnvOverride case imgDockerBase dockerConfig of Nothing -> throwM StackImageDockerBaseUnspecifiedException Just base -> do liftIO (writeFile (toFilePath (dir $(mkRelFile "Dockerfile"))) (unlines ["FROM " ++ base, "ADD ./ /"])) let args = [ "build" , "-t" , fromMaybe (imageName (parent . parent . parent $ dir)) (imgDockerImageName dockerConfig) , toFilePathNoTrailingSep dir] callProcess (Cmd Nothing "docker" menv args) -- | Extend the general purpose docker image with entrypoints (if specified). extendDockerImageWithEntrypoint :: (StackM env m, HasConfig env) => ImageDockerOpts -> Path Abs Dir -> m () extendDockerImageWithEntrypoint dockerConfig dir = do menv <- getMinimalEnvOverride let dockerImageName = fromMaybe (imageName (parent . parent . parent $ dir)) (imgDockerImageName dockerConfig) let imgEntrypoints = imgDockerEntrypoints dockerConfig case imgEntrypoints of Nothing -> return () Just eps -> forM_ eps (\ep -> do liftIO (writeFile (toFilePath (dir $(mkRelFile "Dockerfile"))) (unlines [ "FROM " ++ dockerImageName , "ENTRYPOINT [\"/usr/local/bin/" ++ ep ++ "\"]" , "CMD []"])) callProcess (Cmd Nothing "docker" menv [ "build" , "-t" , dockerImageName ++ "-" ++ ep , toFilePathNoTrailingSep dir])) -- | Fail with friendly error if project root not set. fromMaybeProjectRoot :: Maybe (Path Abs Dir) -> Path Abs Dir fromMaybeProjectRoot = fromMaybe (throw StackImageCannotDetermineProjectRootException) -- | The command name for dealing with images. imgCmdName :: String imgCmdName = "image" -- | The command name for building a docker container. imgDockerCmdName :: String imgDockerCmdName = "container" -- | Convert image opts monoid to image options. imgOptsFromMonoid :: ImageOptsMonoid -> ImageOpts imgOptsFromMonoid ImageOptsMonoid{..} = ImageOpts { imgDockers = imgMonoidDockers } -- | Stack image exceptions. data StackImageException = StackImageDockerBaseUnspecifiedException -- ^ Unspecified parent docker -- container makes building -- impossible | StackImageCannotDetermineProjectRootException -- ^ Can't determine the -- project root (where to -- put image sandbox). deriving (Typeable) instance Exception StackImageException instance Show StackImageException where show StackImageDockerBaseUnspecifiedException = "You must specify a base docker image on which to place your haskell executables." show StackImageCannotDetermineProjectRootException = "Stack was unable to determine the project root in order to build a container." stack-1.5.1/src/Stack/Init.hs0000644000000000000000000005377413135652051014125 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Stack.Init ( initProject , InitOpts (..) ) where import Control.Exception (assert) import Control.Exception.Safe (catchAny) import Control.Monad import Control.Monad.Catch (throwM) import Control.Monad.IO.Class import Control.Monad.Logger import qualified Data.ByteString.Builder as B import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Lazy as L import qualified Data.Foldable as F import Data.Function (on) import qualified Data.HashMap.Strict as HM import qualified Data.IntMap as IntMap import Data.List (intercalate, intersect, maximumBy) import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as NonEmpty import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe import Data.Monoid import qualified Data.Text as T import qualified Data.Yaml as Yaml import qualified Distribution.PackageDescription as C import qualified Distribution.Text as C import qualified Distribution.Version as C import Path import Path.Extra (toFilePathNoTrailingSep) import Path.IO import qualified Paths_stack as Meta import Stack.BuildPlan import Stack.Config (getSnapshots, makeConcreteResolver) import Stack.Constants import Stack.Solver import Stack.Types.Build import Stack.Types.BuildPlan import Stack.Types.Config import Stack.Types.FlagName import Stack.Types.PackageName import Stack.Types.Resolver import Stack.Types.StackT (StackM) import Stack.Types.StringError import Stack.Types.Version import qualified System.FilePath as FP -- | Generate stack.yaml initProject :: (StackM env m, HasConfig env, HasGHCVariant env) => WhichSolverCmd -> Path Abs Dir -> InitOpts -> Maybe AbstractResolver -> m () initProject whichCmd currDir initOpts mresolver = do let dest = currDir stackDotYaml reldest <- toFilePath `liftM` makeRelativeToCurrentDir dest exists <- doesFileExist dest when (not (forceOverwrite initOpts) && exists) $ throwString ("Error: Stack configuration file " <> reldest <> " exists, use 'stack solver' to fix the existing config file or \ \'--force' to overwrite it.") dirs <- mapM (resolveDir' . T.unpack) (searchDirs initOpts) let noPkgMsg = "In order to init, you should have an existing .cabal \ \file. Please try \"stack new\" instead." find = findCabalFiles (includeSubDirs initOpts) dirs' = if null dirs then [currDir] else dirs $logInfo "Looking for .cabal or package.yaml files to use to init the project." cabalfps <- liftM concat $ mapM find dirs' (bundle, dupPkgs) <- cabalPackagesCheck cabalfps noPkgMsg Nothing (r, flags, extraDeps, rbundle) <- getDefaultResolver whichCmd dest initOpts mresolver bundle let ignored = Map.difference bundle rbundle dupPkgMsg | dupPkgs /= [] = "Warning (added by new or init): Some packages were found to \ \have names conflicting with others and have been commented \ \out in the packages section.\n" | otherwise = "" missingPkgMsg | Map.size ignored > 0 = "Warning (added by new or init): Some packages were found to \ \be incompatible with the resolver and have been left commented \ \out in the packages section.\n" | otherwise = "" extraDepMsg | Map.size extraDeps > 0 = "Warning (added by new or init): Specified resolver could not \ \satisfy all dependencies. Some external packages have been \ \added as dependencies.\n" | otherwise = "" makeUserMsg msgs = let msg = concat msgs in if msg /= "" then msg <> "You can suppress this message by removing it from \ \stack.yaml\n" else "" userMsg = makeUserMsg [dupPkgMsg, missingPkgMsg, extraDepMsg] gpds = Map.elems $ fmap snd rbundle p = Project { projectUserMsg = if userMsg == "" then Nothing else Just userMsg , projectPackages = pkgs , projectExtraDeps = extraDeps , projectFlags = PackageFlags (removeSrcPkgDefaultFlags gpds flags) , projectResolver = r , projectCompiler = Nothing , projectExtraPackageDBs = [] } makeRelDir dir = case stripDir currDir dir of Nothing | currDir == dir -> "." | otherwise -> assert False $ toFilePathNoTrailingSep dir Just rel -> toFilePathNoTrailingSep rel makeRel = fmap toFilePath . makeRelativeToCurrentDir pkgs = map toPkg $ Map.elems (fmap (parent . fst) rbundle) toPkg dir = PackageEntry { peExtraDepMaybe = Nothing , peLocation = PLFilePath $ makeRelDir dir , peSubdirs = [] } indent t = T.unlines $ fmap (" " <>) (T.lines t) $logInfo $ "Initialising configuration using resolver: " <> resolverName r $logInfo $ "Total number of user packages considered: " <> T.pack (show (Map.size bundle + length dupPkgs)) when (dupPkgs /= []) $ do $logWarn $ "Warning! Ignoring " <> T.pack (show $ length dupPkgs) <> " duplicate packages:" rels <- mapM makeRel dupPkgs $logWarn $ indent $ showItems rels when (Map.size ignored > 0) $ do $logWarn $ "Warning! Ignoring " <> T.pack (show $ Map.size ignored) <> " packages due to dependency conflicts:" rels <- mapM makeRel (Map.elems (fmap fst ignored)) $logWarn $ indent $ showItems rels when (Map.size extraDeps > 0) $ do $logWarn $ "Warning! " <> T.pack (show $ Map.size extraDeps) <> " external dependencies were added." $logInfo $ (if exists then "Overwriting existing configuration file: " else "Writing configuration to file: ") <> T.pack reldest liftIO $ L.writeFile (toFilePath dest) $ B.toLazyByteString $ renderStackYaml p (Map.elems $ fmap (makeRelDir . parent . fst) ignored) (map (makeRelDir . parent) dupPkgs) $logInfo "All done." -- | Render a stack.yaml file with comments, see: -- https://github.com/commercialhaskell/stack/issues/226 renderStackYaml :: Project -> [FilePath] -> [FilePath] -> B.Builder renderStackYaml p ignoredPackages dupPackages = case Yaml.toJSON p of Yaml.Object o -> renderObject o _ -> assert False $ B.byteString $ Yaml.encode p where renderObject o = B.byteString headerHelp <> B.byteString "\n\n" <> F.foldMap (goComment o) comments <> goOthers (o `HM.difference` HM.fromList comments) <> B.byteString footerHelp goComment o (name, comment) = case HM.lookup name o of Nothing -> assert (name == "user-message") mempty Just v -> B.byteString comment <> B.byteString "\n" <> B.byteString (Yaml.encode $ Yaml.object [(name, v)]) <> if name == "packages" then commentedPackages else "" <> B.byteString "\n" commentLine l | null l = "#" | otherwise = "# " ++ l commentHelp = BC.pack . intercalate "\n" . map commentLine commentedPackages = let ignoredComment = commentHelp [ "The following packages have been ignored due to incompatibility with the" , "resolver compiler, dependency conflicts with other packages" , "or unsatisfied dependencies." ] dupComment = commentHelp [ "The following packages have been ignored due to package name conflict " , "with other packages." ] in commentPackages ignoredComment ignoredPackages <> commentPackages dupComment dupPackages commentPackages comment pkgs | pkgs /= [] = B.byteString comment <> B.byteString "\n" <> B.byteString (BC.pack $ concat $ map (\x -> "#- " ++ x ++ "\n") pkgs ++ ["\n"]) | otherwise = "" goOthers o | HM.null o = mempty | otherwise = assert False $ B.byteString $ Yaml.encode o -- Per Section Help comments = [ ("user-message" , userMsgHelp) , ("resolver" , resolverHelp) , ("packages" , packageHelp) , ("extra-deps" , "# Dependency packages to be pulled from upstream that are not in the resolver\n# (e.g., acme-missiles-0.3)") , ("flags" , "# Override default flag values for local packages and extra-deps") , ("extra-package-dbs", "# Extra package databases containing global packages") ] -- Help strings headerHelp = commentHelp [ "This file was automatically generated by 'stack init'" , "" , "Some commonly used options have been documented as comments in this file." , "For advanced use and comprehensive documentation of the format, please see:" , "https://docs.haskellstack.org/en/stable/yaml_configuration/" ] resolverHelp = commentHelp [ "Resolver to choose a 'specific' stackage snapshot or a compiler version." , "A snapshot resolver dictates the compiler version and the set of packages" , "to be used for project dependencies. For example:" , "" , "resolver: lts-3.5" , "resolver: nightly-2015-09-21" , "resolver: ghc-7.10.2" , "resolver: ghcjs-0.1.0_ghc-7.10.2" , "resolver:" , " name: custom-snapshot" , " location: \"./custom-snapshot.yaml\"" ] userMsgHelp = commentHelp [ "A warning or info to be displayed to the user on config load." ] packageHelp = commentHelp [ "User packages to be built." , "Various formats can be used as shown in the example below." , "" , "packages:" , "- some-directory" , "- https://example.com/foo/bar/baz-0.0.2.tar.gz" , "- location:" , " git: https://github.com/commercialhaskell/stack.git" , " commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a" , "- location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a" , " extra-dep: true" , " subdirs:" , " - auto-update" , " - wai" , "" , "A package marked 'extra-dep: true' will only be built if demanded by a" , "non-dependency (i.e. a user package), and its test suites and benchmarks" , "will not be run. This is useful for tweaking upstream packages." ] footerHelp = let major = toCabalVersion $ toMajorVersion $ fromCabalVersion Meta.version in commentHelp [ "Control whether we use the GHC we find on the path" , "system-ghc: true" , "" , "Require a specific version of stack, using version ranges" , "require-stack-version: -any # Default" , "require-stack-version: \"" ++ C.display (C.orLaterVersion major) ++ "\"" , "" , "Override the architecture used by stack, especially useful on Windows" , "arch: i386" , "arch: x86_64" , "" , "Extra directories used by stack for building" , "extra-include-dirs: [/path/to/dir]" , "extra-lib-dirs: [/path/to/dir]" , "" , "Allow a newer minor version of GHC than the snapshot specifies" , "compiler-check: newer-minor" ] getSnapshots' :: (StackM env m, HasConfig env) => m Snapshots getSnapshots' = do getSnapshots `catchAny` \e -> do $logError $ "Unable to download snapshot list, and therefore could " <> "not generate a stack.yaml file automatically" $logError $ "This sometimes happens due to missing Certificate Authorities " <> "on your system. For more information, see:" $logError "" $logError " https://github.com/commercialhaskell/stack/issues/234" $logError "" $logError "You can try again, or create your stack.yaml file by hand. See:" $logError "" $logError " http://docs.haskellstack.org/en/stable/yaml_configuration/" $logError "" $logError $ "Exception was: " <> T.pack (show e) errorString "" -- | Get the default resolver value getDefaultResolver :: (StackM env m, HasConfig env, HasGHCVariant env) => WhichSolverCmd -> Path Abs File -- ^ stack.yaml -> InitOpts -> Maybe AbstractResolver -> Map PackageName (Path Abs File, C.GenericPackageDescription) -- ^ Src package name: cabal dir, cabal package description -> m ( Resolver , Map PackageName (Map FlagName Bool) , Map PackageName Version , Map PackageName (Path Abs File, C.GenericPackageDescription)) -- ^ ( Resolver -- , Flags for src packages and extra deps -- , Extra dependencies -- , Src packages actually considered) getDefaultResolver whichCmd stackYaml initOpts mresolver bundle = maybe selectSnapResolver makeConcreteResolver mresolver >>= getWorkingResolverPlan whichCmd stackYaml initOpts bundle where -- TODO support selecting best across regular and custom snapshots selectSnapResolver = do let gpds = Map.elems (fmap snd bundle) snaps <- fmap getRecommendedSnapshots getSnapshots' (s, r) <- selectBestSnapshot gpds snaps case r of BuildPlanCheckFail {} | not (omitPackages initOpts) -> throwM (NoMatchingSnapshot whichCmd snaps) _ -> return $ ResolverSnapshot s getWorkingResolverPlan :: (StackM env m, HasConfig env, HasGHCVariant env) => WhichSolverCmd -> Path Abs File -- ^ stack.yaml -> InitOpts -> Map PackageName (Path Abs File, C.GenericPackageDescription) -- ^ Src package name: cabal dir, cabal package description -> Resolver -> m ( Resolver , Map PackageName (Map FlagName Bool) , Map PackageName Version , Map PackageName (Path Abs File, C.GenericPackageDescription)) -- ^ ( Resolver -- , Flags for src packages and extra deps -- , Extra dependencies -- , Src packages actually considered) getWorkingResolverPlan whichCmd stackYaml initOpts bundle resolver = do $logInfo $ "Selected resolver: " <> resolverName resolver go bundle where go info = do eres <- checkBundleResolver whichCmd stackYaml initOpts info resolver -- if some packages failed try again using the rest case eres of Right (f, edeps)-> return (resolver, f, edeps, info) Left ignored | Map.null available -> do $logWarn "*** Could not find a working plan for any of \ \the user packages.\nProceeding to create a \ \config anyway." return (resolver, Map.empty, Map.empty, Map.empty) | otherwise -> do when (Map.size available == Map.size info) $ error "Bug: No packages to ignore" if length ignored > 1 then do $logWarn "*** Ignoring packages:" $logWarn $ indent $ showItems ignored else $logWarn $ "*** Ignoring package: " <> T.pack (packageNameString (head ignored)) go available where indent t = T.unlines $ fmap (" " <>) (T.lines t) isAvailable k _ = k `notElem` ignored available = Map.filterWithKey isAvailable info checkBundleResolver :: (StackM env m, HasConfig env, HasGHCVariant env) => WhichSolverCmd -> Path Abs File -- ^ stack.yaml -> InitOpts -> Map PackageName (Path Abs File, C.GenericPackageDescription) -- ^ Src package name: cabal dir, cabal package description -> Resolver -> m (Either [PackageName] ( Map PackageName (Map FlagName Bool) , Map PackageName Version)) checkBundleResolver whichCmd stackYaml initOpts bundle resolver = do result <- checkResolverSpec gpds Nothing resolver case result of BuildPlanCheckOk f -> return $ Right (f, Map.empty) BuildPlanCheckPartial f e | needSolver resolver initOpts -> do warnPartial result solve f | omitPackages initOpts -> do warnPartial result $logWarn "*** Omitting packages with unsatisfied dependencies" return $ Left $ failedUserPkgs e | otherwise -> throwM $ ResolverPartial whichCmd resolver (show result) BuildPlanCheckFail _ e _ | omitPackages initOpts -> do $logWarn $ "*** Resolver compiler mismatch: " <> resolverName resolver $logWarn $ indent $ T.pack $ show result return $ Left $ failedUserPkgs e | otherwise -> throwM $ ResolverMismatch whichCmd resolver (show result) where indent t = T.unlines $ fmap (" " <>) (T.lines t) warnPartial res = do $logWarn $ "*** Resolver " <> resolverName resolver <> " will need external packages: " $logWarn $ indent $ T.pack $ show res failedUserPkgs e = Map.keys $ Map.unions (Map.elems (fmap deNeededBy e)) gpds = Map.elems (fmap snd bundle) solve flags = do let cabalDirs = map parent (Map.elems (fmap fst bundle)) srcConstraints = mergeConstraints (gpdPackages gpds) flags eresult <- solveResolverSpec stackYaml cabalDirs (resolver, srcConstraints, Map.empty) case eresult of Right (src, ext) -> return $ Right (fmap snd (Map.union src ext), fmap fst ext) Left packages | omitPackages initOpts, srcpkgs /= []-> do pkg <- findOneIndependent srcpkgs flags return $ Left [pkg] | otherwise -> throwM (SolverGiveUp giveUpMsg) where srcpkgs = Map.keys bundle `intersect` packages -- among a list of packages find one on which none among the rest of the -- packages depend. This package is a good candidate to be removed from -- the list of packages when there is conflict in dependencies among this -- set of packages. findOneIndependent packages flags = do platform <- view platformL (compiler, _) <- getResolverConstraints stackYaml resolver let getGpd pkg = snd (fromJust (Map.lookup pkg bundle)) getFlags pkg = fromJust (Map.lookup pkg flags) deps pkg = gpdPackageDeps (getGpd pkg) compiler platform (getFlags pkg) allDeps = concatMap (Map.keys . deps) packages isIndependent pkg = pkg `notElem` allDeps -- prefer to reject packages in deeper directories path pkg = fst (fromJust (Map.lookup pkg bundle)) pathlen = length . FP.splitPath . toFilePath . path maxPathlen = maximumBy (compare `on` pathlen) return $ maxPathlen (filter isIndependent packages) giveUpMsg = concat [ " - Use '--omit-packages to exclude conflicting package(s).\n" , " - Tweak the generated " , toFilePath stackDotYaml <> " and then run 'stack solver':\n" , " - Add any missing remote packages.\n" , " - Add extra dependencies to guide solver.\n" , " - Update external packages with 'stack update' and try again.\n" ] needSolver _ InitOpts {useSolver = True} = True needSolver (ResolverCompiler _) _ = True needSolver _ _ = False getRecommendedSnapshots :: Snapshots -> NonEmpty SnapName getRecommendedSnapshots snapshots = -- in order - Latest LTS, Latest Nightly, all LTS most recent first case NonEmpty.nonEmpty ltss of Just (mostRecent :| older) -> mostRecent :| (nightly : older) Nothing -> nightly :| [] where ltss = map (uncurry LTS) (IntMap.toDescList $ snapshotsLts snapshots) nightly = Nightly (snapshotsNightly snapshots) data InitOpts = InitOpts { searchDirs :: ![T.Text] -- ^ List of sub directories to search for .cabal files , useSolver :: Bool -- ^ Use solver to determine required external dependencies , omitPackages :: Bool -- ^ Exclude conflicting or incompatible user packages , forceOverwrite :: Bool -- ^ Overwrite existing stack.yaml , includeSubDirs :: Bool -- ^ If True, include all .cabal files found in any sub directories } stack-1.5.1/src/Stack/New.hs0000644000000000000000000004316213135652051013741 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} -- | Create new a new project directory populated with a basic working -- project. module Stack.New ( new , NewOpts(..) , defaultTemplateName , templateNameArgument , getTemplates , TemplateName , listTemplates) where import Control.Monad import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.Trans.Writer.Strict import Data.Aeson import Data.Aeson.Types import qualified Data.ByteString as SB import qualified Data.ByteString.Lazy as LB import Data.Conduit import Data.Foldable (asum) import qualified Data.HashMap.Strict as HM import Data.List import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe (fromMaybe) import Data.Maybe.Extra (mapMaybeM) import Data.Monoid import Data.Set (Set) import qualified Data.Set as S import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.Encoding.Error as T (lenientDecode) import qualified Data.Text.IO as T import qualified Data.Text.Lazy as LT import Data.Time.Calendar import Data.Time.Clock import Data.Typeable import qualified Data.Yaml as Yaml import Network.HTTP.Download import Network.HTTP.Simple import Path import Path.IO import Prelude import Stack.Constants import Stack.Types.Config import Stack.Types.PackageName import Stack.Types.StackT import Stack.Types.TemplateName import System.Process.Run import Text.Hastache import Text.Hastache.Context import Text.Printf import Text.ProjectTemplate -------------------------------------------------------------------------------- -- Main project creation -- | Options for creating a new project. data NewOpts = NewOpts { newOptsProjectName :: PackageName -- ^ Name of the project to create. , newOptsCreateBare :: Bool -- ^ Whether to create the project without a directory. , newOptsTemplate :: Maybe TemplateName -- ^ Name of the template to use. , newOptsNonceParams :: Map Text Text -- ^ Nonce parameters specified just for this invocation. } -- | Create a new project with the given options. new :: (StackM env m, HasConfig env) => NewOpts -> Bool -> m (Path Abs Dir) new opts forceOverwrite = do when (newOptsProjectName opts `elem` wiredInPackages) $ throwM $ Can'tUseWiredInName (newOptsProjectName opts) pwd <- getCurrentDir absDir <- if bare then return pwd else do relDir <- parseRelDir (packageNameString project) liftM (pwd ) (return relDir) exists <- doesDirExist absDir configTemplate <- view $ configL.to configDefaultTemplate let template = fromMaybe defaultTemplateName $ asum [ cliOptionTemplate , configTemplate ] if exists && not bare then throwM (AlreadyExists absDir) else do templateText <- loadTemplate template (logUsing absDir template) files <- applyTemplate project template (newOptsNonceParams opts) absDir templateText when (not forceOverwrite && bare) $ checkForOverwrite (M.keys files) writeTemplateFiles files runTemplateInits absDir return absDir where cliOptionTemplate = newOptsTemplate opts project = newOptsProjectName opts bare = newOptsCreateBare opts logUsing absDir template templateFrom = let loading = case templateFrom of LocalTemp -> "Loading local" RemoteTemp -> "Downloading" in $logInfo (loading <> " template \"" <> templateName template <> "\" to create project \"" <> packageNameText project <> "\" in " <> if bare then "the current directory" else T.pack (toFilePath (dirname absDir)) <> " ...") data TemplateFrom = LocalTemp | RemoteTemp -- | Download and read in a template's text content. loadTemplate :: forall env m. (StackM env m, HasConfig env) => TemplateName -> (TemplateFrom -> m ()) -> m Text loadTemplate name logIt = do templateDir <- view $ configL.to templatesDir case templatePath name of AbsPath absFile -> logIt LocalTemp >> loadLocalFile absFile UrlPath s -> do req <- parseRequest s let rel = fromMaybe backupUrlRelPath (parseRelFile s) downloadTemplate req (templateDir rel) RelPath relFile -> catch (do f <- loadLocalFile relFile logIt LocalTemp return f) (\(e :: NewException) -> case relRequest relFile of Just req -> downloadTemplate req (templateDir relFile) Nothing -> throwM e ) where loadLocalFile :: Path b File -> m Text loadLocalFile path = do $logDebug ("Opening local template: \"" <> T.pack (toFilePath path) <> "\"") exists <- doesFileExist path if exists then liftIO (fmap (T.decodeUtf8With T.lenientDecode) (SB.readFile (toFilePath path))) else throwM (FailedToLoadTemplate name (toFilePath path)) relRequest :: MonadThrow n => Path Rel File -> n Request relRequest rel = parseRequest (defaultTemplateUrl <> "/" <> toFilePath rel) downloadTemplate :: Request -> Path Abs File -> m Text downloadTemplate req path = do logIt RemoteTemp _ <- catch (redownload req path) (throwM . FailedToDownloadTemplate name) loadLocalFile path backupUrlRelPath = $(mkRelFile "downloaded.template.file.hsfiles") -- | Apply and unpack a template into a directory. applyTemplate :: (StackM env m, HasConfig env) => PackageName -> TemplateName -> Map Text Text -> Path Abs Dir -> Text -> m (Map (Path Abs File) LB.ByteString) applyTemplate project template nonceParams dir templateText = do config <- view configL currentYear <- do now <- liftIO getCurrentTime (year, _, _) <- return $ toGregorian . utctDay $ now return $ T.pack . show $ year let context = M.union (M.union nonceParams extraParams) configParams where nameAsVarId = T.replace "-" "_" $ packageNameText project nameAsModule = T.filter (/= '-') $ T.toTitle $ packageNameText project extraParams = M.fromList [ ("name", packageNameText project) , ("name-as-varid", nameAsVarId) , ("name-as-module", nameAsModule) , ("year", currentYear) ] configParams = configTemplateParams config (applied,missingKeys) <- runWriterT (hastacheStr defaultConfig { muEscapeFunc = id } templateText (mkStrContextM (contextFunction context))) unless (S.null missingKeys) ($logInfo ("\n" <> T.pack (show (MissingParameters project template missingKeys (configUserConfigPath config))) <> "\n")) files :: Map FilePath LB.ByteString <- catch (execWriterT $ yield (T.encodeUtf8 (LT.toStrict applied)) $$ unpackTemplate receiveMem id ) (\(e :: ProjectTemplateException) -> throwM (InvalidTemplate template (show e))) when (M.null files) $ throwM (InvalidTemplate template "Template does not contain any files") let isPkgSpec f = ".cabal" `isSuffixOf` f || f == "package.yaml" unless (any isPkgSpec . M.keys $ files) $ throwM (InvalidTemplate template "Template does not contain a .cabal \ \or package.yaml file") liftM M.fromList (mapM (\(fp,bytes) -> do path <- parseRelFile fp return (dir path, bytes)) (M.toList files)) where -- | Does a lookup in the context and returns a moustache value, -- on the side, writes out a set of keys that were requested but -- not found. contextFunction :: Monad m => Map Text Text -> String -> WriterT (Set String) m (MuType (WriterT (Set String) m)) contextFunction context key = case M.lookup (T.pack key) context of Nothing -> do tell (S.singleton key) return MuNothing Just value -> return (MuVariable value) -- | Check if we're going to overwrite any existing files. checkForOverwrite :: (MonadIO m, MonadThrow m) => [Path Abs File] -> m () checkForOverwrite files = do overwrites <- filterM doesFileExist files unless (null overwrites) $ throwM (AttemptedOverwrites overwrites) -- | Write files to the new project directory. writeTemplateFiles :: MonadIO m => Map (Path Abs File) LB.ByteString -> m () writeTemplateFiles files = forM_ (M.toList files) (\(fp,bytes) -> do ensureDir (parent fp) liftIO (LB.writeFile (toFilePath fp) bytes)) -- | Run any initialization functions, such as Git. runTemplateInits :: (StackM env m, HasConfig env) => Path Abs Dir -> m () runTemplateInits dir = do menv <- getMinimalEnvOverride config <- view configL case configScmInit config of Nothing -> return () Just Git -> catch (callProcess $ Cmd (Just dir) "git" menv ["init"]) (\(_ :: ProcessExitedUnsuccessfully) -> $logInfo "git init failed to run, ignoring ...") -- | Display the set of templates accompanied with description if available. listTemplates :: StackM env m => m () listTemplates = do templates <- getTemplates templateInfo <- getTemplateInfo if not . M.null $ templateInfo then do let keySizes = map (T.length . templateName) $ S.toList templates padWidth = show $ maximum keySizes outputfmt = "%-" <> padWidth <> "s %s\n" headerfmt = "%-" <> padWidth <> "s %s\n" liftIO $ printf headerfmt ("Template"::String) ("Description"::String) forM_ (S.toList templates) (\x -> do let name = templateName x desc = fromMaybe "" $ liftM (mappend "- ") (M.lookup name templateInfo >>= description) liftIO $ printf outputfmt (T.unpack name) (T.unpack desc)) else mapM_ (liftIO . T.putStrLn . templateName) (S.toList templates) -- | Get the set of templates. getTemplates :: StackM env m => m (Set TemplateName) getTemplates = do req <- liftM setGithubHeaders (parseUrlThrow defaultTemplatesList) resp <- catch (httpJSON req) (throwM . FailedToDownloadTemplates) case getResponseStatusCode resp of 200 -> return $ unTemplateSet $ getResponseBody resp code -> throwM (BadTemplatesResponse code) getTemplateInfo :: StackM env m => m (Map Text TemplateInfo) getTemplateInfo = do req <- liftM setGithubHeaders (parseUrlThrow defaultTemplateInfoUrl) resp <- catch (liftM Right $ httpLbs req) (\(ex :: HttpException) -> return . Left $ "Failed to download template info. The HTTP error was: " <> show ex) case resp >>= is200 of Left err -> do liftIO . putStrLn $ err return M.empty Right resp' -> case Yaml.decodeEither (LB.toStrict $ getResponseBody resp') :: Either String Object of Left err -> throwM $ BadTemplateInfo err Right o -> return (M.mapMaybe (Yaml.parseMaybe Yaml.parseJSON) (M.fromList . HM.toList $ o) :: Map Text TemplateInfo) where is200 resp = case getResponseStatusCode resp of 200 -> return resp code -> Left $ "Unexpected status code while retrieving templates info: " <> show code newtype TemplateSet = TemplateSet { unTemplateSet :: Set TemplateName } instance FromJSON TemplateSet where parseJSON = fmap TemplateSet . parseTemplateSet -- | Parser the set of templates from the JSON. parseTemplateSet :: Value -> Parser (Set TemplateName) parseTemplateSet a = do xs <- parseJSON a fmap S.fromList (mapMaybeM parseTemplate xs) where parseTemplate v = do o <- parseJSON v name <- o .: "name" if ".hsfiles" `isSuffixOf` name then case parseTemplateNameFromString name of Left{} -> fail ("Unable to parse template name from " <> name) Right template -> return (Just template) else return Nothing -------------------------------------------------------------------------------- -- Defaults -- | The default template name you can use if you don't have one. defaultTemplateName :: TemplateName defaultTemplateName = $(mkTemplateName "new-template") -- | Default web root URL to download from. defaultTemplateUrl :: String defaultTemplateUrl = "https://raw.githubusercontent.com/commercialhaskell/stack-templates/master" -- | Default web URL to get a yaml file containing template metadata. defaultTemplateInfoUrl :: String defaultTemplateInfoUrl = "https://raw.githubusercontent.com/commercialhaskell/stack-templates/master/template-info.yaml" -- | Default web URL to list the repo contents. defaultTemplatesList :: String defaultTemplatesList = "https://api.github.com/repos/commercialhaskell/stack-templates/contents/" -------------------------------------------------------------------------------- -- Exceptions -- | Exception that might occur when making a new project. data NewException = FailedToLoadTemplate !TemplateName !FilePath | FailedToDownloadTemplate !TemplateName !DownloadException | FailedToDownloadTemplates !HttpException | BadTemplatesResponse !Int | AlreadyExists !(Path Abs Dir) | MissingParameters !PackageName !TemplateName !(Set String) !(Path Abs File) | InvalidTemplate !TemplateName !String | AttemptedOverwrites [Path Abs File] | FailedToDownloadTemplateInfo !HttpException | BadTemplateInfo !String | BadTemplateInfoResponse !Int | Can'tUseWiredInName !PackageName deriving (Typeable) instance Exception NewException instance Show NewException where show (FailedToLoadTemplate name path) = "Failed to load download template " <> T.unpack (templateName name) <> " from " <> path show (FailedToDownloadTemplate name (RedownloadFailed _ _ resp)) = case getResponseStatusCode resp of 404 -> "That template doesn't exist. Run `stack templates' to see a list of available templates." code -> "Failed to download template " <> T.unpack (templateName name) <> ": unknown reason, status code was: " <> show code show (AlreadyExists path) = "Directory " <> toFilePath path <> " already exists. Aborting." show (FailedToDownloadTemplates ex) = "Failed to download templates. The HTTP error was: " <> show ex show (BadTemplatesResponse code) = "Unexpected status code while retrieving templates list: " <> show code show (MissingParameters name template missingKeys userConfigPath) = intercalate "\n" [ "The following parameters were needed by the template but not provided: " <> intercalate ", " (S.toList missingKeys) , "You can provide them in " <> toFilePath userConfigPath <> ", like this:" , "templates:" , " params:" , intercalate "\n" (map (\key -> " " <> key <> ": value") (S.toList missingKeys)) , "Or you can pass each one as parameters like this:" , "stack new " <> packageNameString name <> " " <> T.unpack (templateName template) <> " " <> unwords (map (\key -> "-p \"" <> key <> ":value\"") (S.toList missingKeys))] show (InvalidTemplate name why) = "The template \"" <> T.unpack (templateName name) <> "\" is invalid and could not be used. " <> "The error was: \"" <> why <> "\"" show (AttemptedOverwrites fps) = "The template would create the following files, but they already exist:\n" <> unlines (map ((" " ++) . toFilePath) fps) <> "Use --force to ignore this, and overwite these files." show (FailedToDownloadTemplateInfo ex) = "Failed to download templates info. The HTTP error was: " <> show ex show (BadTemplateInfo err) = "Template info couldn't be parsed: " <> err show (BadTemplateInfoResponse code) = "Unexpected status code while retrieving templates info: " <> show code show (Can'tUseWiredInName name) = "The name \"" <> packageNameString name <> "\" is used by GHC wired-in packages, and so shouldn't be used as a package name" stack-1.5.1/src/Stack/Nix.hs0000644000000000000000000001605313135652051013745 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} -- | Run commands in a nix-shell module Stack.Nix (reexecWithOptionalShell ,nixCmdName ,nixHelpOptName ) where import Control.Arrow ((***)) import Control.Exception (Exception,throw) import Control.Monad hiding (mapM) import Control.Monad.IO.Class (liftIO) import Control.Monad.Logger (logDebug) import Data.Maybe import Data.Monoid import qualified Data.Text as T import Data.Traversable import Data.Typeable (Typeable) import Data.Version (showVersion) import Path import Path.IO import qualified Paths_stack as Meta import Prelude hiding (mapM) -- Fix redundant import warnings import Stack.Config (getInNixShell, getInContainer) import Stack.Config.Nix (nixCompiler) import Stack.Constants (platformVariantEnvVar,inNixShellEnvVar,inContainerEnvVar) import Stack.Exec (exec) import Stack.Types.Config import Stack.Types.Docker import Stack.Types.Nix import Stack.Types.Compiler import Stack.Types.Internal import Stack.Types.StackT import System.Environment (getArgs,getExecutablePath,lookupEnv) import qualified System.FilePath as F import System.Process.Read (getEnvOverride) -- | If Nix is enabled, re-runs the currently running OS command in a Nix container. -- Otherwise, runs the inner action. reexecWithOptionalShell :: (StackM env m, HasConfig env) => Maybe (Path Abs Dir) -> IO CompilerVersion -> IO () -> m () reexecWithOptionalShell mprojectRoot getCompilerVersion inner = do config <- view configL inShell <- getInNixShell inContainer <- getInContainer isReExec <- view reExecL let getCmdArgs = do origArgs <- liftIO getArgs let args | inContainer = origArgs -- internal-re-exec version already passed -- first stack when restarting in the container | otherwise = ("--" ++ reExecArgName ++ "=" ++ showVersion Meta.version) : origArgs exePath <- liftIO getExecutablePath return (exePath, args) if nixEnable (configNix config) && not inShell && (not isReExec || inContainer) then runShellAndExit mprojectRoot getCompilerVersion getCmdArgs else liftIO inner runShellAndExit :: (StackM env m, HasConfig env) => Maybe (Path Abs Dir) -> IO CompilerVersion -> m (String, [String]) -> m () runShellAndExit mprojectRoot getCompilerVersion getCmdArgs = do config <- view configL envOverride <- getEnvOverride (configPlatform config) (cmnd,args) <- fmap (escape *** map escape) getCmdArgs mshellFile <- traverse (resolveFile (fromMaybeProjectRoot mprojectRoot)) $ nixInitFile (configNix config) compilerVersion <- liftIO getCompilerVersion inContainer <- getInContainer let pkgsInConfig = nixPackages (configNix config) ghc = nixCompiler compilerVersion pkgs = pkgsInConfig ++ [ghc, "git"] pkgsStr = "[" <> T.intercalate " " pkgs <> "]" pureShell = nixPureShell (configNix config) addGCRoots = nixAddGCRoots (configNix config) nixopts = case mshellFile of Just fp -> [toFilePath fp, "--arg", "ghc" ,"with (import {}); " ++ T.unpack ghc] Nothing -> ["-E", T.unpack $ T.concat ["with (import {}); " ,"let inputs = ",pkgsStr,"; " , "libPath = lib.makeLibraryPath inputs; " , "stackExtraArgs = lib.concatMap (pkg: " , "[ ''--extra-lib-dirs=${lib.getLib pkg}/lib'' " , " ''--extra-include-dirs=${lib.getDev pkg}/include'' ]" , ") inputs; in " ,"runCommand ''myEnv'' { " ,"buildInputs = lib.optional stdenv.isLinux glibcLocales ++ inputs; " ,T.pack platformVariantEnvVar <> "=''nix''; " ,T.pack inNixShellEnvVar <> "=1; " ,if inContainer -- If shell is pure, this env var would not -- be seen by stack inside nix then T.pack inContainerEnvVar <> "=1; " else "" ,"LD_LIBRARY_PATH = libPath;" -- LD_LIBRARY_PATH is set because for now it's -- needed by builds using Template Haskell ,"STACK_IN_NIX_EXTRA_ARGS = stackExtraArgs; " ,"} \"\""]] -- glibcLocales is necessary on Linux to avoid warnings about GHC being incapable to set the locale. fullArgs = concat [if pureShell then ["--pure"] else [] ,if addGCRoots then ["--indirect", "--add-root" ,toFilePath (configWorkDir config) F. "nix-gc-symlinks" F. "gc-root"] else [] ,map T.unpack (nixShellOptions (configNix config)) ,nixopts ,["--run", unwords (cmnd:"$STACK_IN_NIX_EXTRA_ARGS":args)] ] -- Using --run instead of --command so we cannot -- end up in the nix-shell if stack build is Ctrl-C'd pathVar <- liftIO $ lookupEnv "PATH" $logDebug $ "PATH is: " <> T.pack (show pathVar) $logDebug $ "Using a nix-shell environment " <> (case mshellFile of Just path -> "from file: " <> T.pack (toFilePath path) Nothing -> "with nix packages: " <> T.intercalate ", " pkgs) exec envOverride "nix-shell" fullArgs -- | Shell-escape quotes inside the string and enclose it in quotes. escape :: String -> String escape str = "'" ++ foldr (\c -> if c == '\'' then ("'\"'\"'"++) else (c:)) "" str ++ "'" -- | Fail with friendly error if project root not set. fromMaybeProjectRoot :: Maybe (Path Abs Dir) -> Path Abs Dir fromMaybeProjectRoot = fromMaybe (throw CannotDetermineProjectRoot) -- | Command-line argument for "nix" nixCmdName :: String nixCmdName = "nix" nixHelpOptName :: String nixHelpOptName = nixCmdName ++ "-help" -- | Exceptions thrown by "Stack.Nix". data StackNixException = CannotDetermineProjectRoot -- ^ Can't determine the project root (location of the shell file if any). deriving (Typeable) instance Exception StackNixException instance Show StackNixException where show CannotDetermineProjectRoot = "Cannot determine project root directory." stack-1.5.1/src/Stack/Options/BenchParser.hs0000644000000000000000000000202613135652051017031 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Stack.Options.BenchParser where import Data.Monoid.Extra import Options.Applicative import Options.Applicative.Builder.Extra import Stack.Options.Utils import Stack.Types.Config -- | Parser for bench arguments. -- FIXME hiding options benchOptsParser :: Bool -> Parser BenchmarkOptsMonoid benchOptsParser hide0 = BenchmarkOptsMonoid <$> optionalFirst (strOption (long "benchmark-arguments" <> long "ba" <> metavar "BENCH_ARGS" <> help ("Forward BENCH_ARGS to the benchmark suite. " <> "Supports templates from `cabal bench`") <> hide)) <*> optionalFirst (switch (long "no-run-benchmarks" <> help "Disable running of benchmarks. (Benchmarks will still be built.)" <> hide)) where hide = hideMods hide0 stack-1.5.1/src/Stack/Options/BuildMonoidParser.hs0000644000000000000000000001250413135652051020221 0ustar0000000000000000module Stack.Options.BuildMonoidParser where import Data.Monoid.Extra import Options.Applicative import Options.Applicative.Builder.Extra import Stack.Build (splitObjsWarning) import Stack.Options.BenchParser import Stack.Options.TestParser import Stack.Options.HaddockParser import Stack.Options.Utils import Stack.Types.Config.Build buildOptsMonoidParser :: GlobalOptsContext -> Parser BuildOptsMonoid buildOptsMonoidParser hide0 = BuildOptsMonoid <$> trace <*> profile <*> noStrip <*> libProfiling <*> exeProfiling <*> libStripping <*> exeStripping <*> haddock <*> haddockOptsParser hideBool <*> openHaddocks <*> haddockDeps <*> haddockInternal <*> haddockHyperlinkSource <*> copyBins <*> preFetch <*> keepGoing <*> forceDirty <*> tests <*> testOptsParser hideBool <*> benches <*> benchOptsParser hideBool <*> reconfigure <*> cabalVerbose <*> splitObjs where hideBool = hide0 /= BuildCmdGlobalOpts hide = hideMods hideBool hideExceptGhci = hideMods (hide0 `notElem` [BuildCmdGlobalOpts, GhciCmdGlobalOpts]) -- These use 'Any' because they are not settable in stack.yaml, so -- there is no need for options like --no-profile. trace = Any <$> flag False True (long "trace" <> help "Enable profiling in libraries, executables, etc. \ \for all expressions and generate a backtrace on \ \exception" <> hideExceptGhci) profile = Any <$> flag False True (long "profile" <> help "profiling in libraries, executables, etc. \ \for all expressions and generate a profiling report\ \ in tests or benchmarks" <> hideExceptGhci) noStrip = Any <$> flag False True (long "no-strip" <> help "Disable DWARF debugging symbol stripping in libraries, \ \executables, etc. for all expressions, producing \ \larger executables but allowing the use of standard \ \debuggers/profiling tools/other utilities that use \ \debugging symbols." <> hideExceptGhci) libProfiling = firstBoolFlags "library-profiling" "library profiling for TARGETs and all its dependencies" hide exeProfiling = firstBoolFlags "executable-profiling" "executable profiling for TARGETs and all its dependencies" hide libStripping = firstBoolFlags "library-stripping" "library stripping for TARGETs and all its dependencies" hide exeStripping = firstBoolFlags "executable-stripping" "executable stripping for TARGETs and all its dependencies" hide haddock = firstBoolFlags "haddock" "generating Haddocks the package(s) in this directory/configuration" hide openHaddocks = firstBoolFlags "open" "opening the local Haddock documentation in the browser" hide haddockDeps = firstBoolFlags "haddock-deps" "building Haddocks for dependencies" hide haddockInternal = firstBoolFlags "haddock-internal" "building Haddocks for internal modules (like cabal haddock --internal)" hide haddockHyperlinkSource = firstBoolFlags "haddock-hyperlink-source" "building hyperlinked source for Haddock (like haddock --hyperlinked-source)" hide copyBins = firstBoolFlags "copy-bins" "copying binaries to the local-bin-path (see 'stack path')" hide keepGoing = firstBoolFlags "keep-going" "continue running after a step fails (default: false for build, true for test/bench)" hide preFetch = firstBoolFlags "prefetch" "Fetch packages necessary for the build immediately, useful with --dry-run" hide forceDirty = firstBoolFlags "force-dirty" "Force treating all local packages as having dirty files (useful for cases where stack can't detect a file change" hide tests = firstBoolFlags "test" "testing the package(s) in this directory/configuration" hideExceptGhci benches = firstBoolFlags "bench" "benchmarking the package(s) in this directory/configuration" hideExceptGhci reconfigure = firstBoolFlags "reconfigure" "Perform the configure step even if unnecessary. Useful in some corner cases with custom Setup.hs files" hide cabalVerbose = firstBoolFlags "cabal-verbose" "Ask Cabal to be verbose in its output" hide splitObjs = firstBoolFlags "split-objs" ("Enable split-objs, to reduce output size (at the cost of build time). " ++ splitObjsWarning) hide stack-1.5.1/src/Stack/Options/BuildParser.hs0000644000000000000000000000716513135652051017062 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Stack.Options.BuildParser where import qualified Data.Map as Map import Data.Monoid.Extra import Data.Text (Text) import Data.Version (showVersion) import Options.Applicative import Options.Applicative.Args import Options.Applicative.Builder.Extra import Paths_stack as Meta import Stack.Options.Completion import Stack.Options.PackageParser (readFlag) import Stack.Types.Config import Stack.Types.FlagName import Stack.Types.PackageName -- | Parser for CLI-only build arguments buildOptsParser :: BuildCommand -> Parser BuildOptsCLI buildOptsParser cmd = BuildOptsCLI <$> targetsParser <*> switch (long "dry-run" <> help "Don't build anything, just prepare to") <*> ((\x y z -> concat [x, y, z]) <$> flag [] ["-Wall", "-Werror"] (long "pedantic" <> help "Turn on -Wall and -Werror") <*> flag [] ["-O0"] (long "fast" <> help "Turn off optimizations (-O0)") <*> many (textOption (long "ghc-options" <> metavar "OPTIONS" <> completer ghcOptsCompleter <> help "Additional options passed to GHC"))) <*> flagsParser <*> (flag' BSOnlyDependencies (long "dependencies-only" <> help "A synonym for --only-dependencies") <|> flag' BSOnlySnapshot (long "only-snapshot" <> help "Only build packages for the snapshot database, not the local database") <|> flag' BSOnlyDependencies (long "only-dependencies" <> help "Only build packages that are dependencies of targets on the command line") <|> pure BSAll) <*> (flag' FileWatch (long "file-watch" <> help "Watch for changes in local files and automatically rebuild. Ignores files in VCS boring/ignore file") <|> flag' FileWatchPoll (long "file-watch-poll" <> help "Like --file-watch, but polling the filesystem instead of using events") <|> pure NoFileWatch) <*> many (cmdOption (long "exec" <> metavar "CMD [ARGS]" <> help "Command and arguments to run after a successful build")) <*> switch (long "only-configure" <> help "Only perform the configure step, not any builds. Intended for tool usage, may break when used on multiple packages at once!") <*> pure cmd <*> switch (long "initial-build-steps" <> help "For target packages, only run initial build steps needed for GHCi" <> internal) targetsParser :: Parser [Text] targetsParser = many (textArgument (metavar "TARGET" <> completer targetCompleter <> help ("If none specified, use all local packages. " <> "See https://docs.haskellstack.org/en/v" <> showVersion Meta.version <> "/build_command/#target-syntax for details."))) flagsParser :: Parser (Map.Map (Maybe PackageName) (Map.Map FlagName Bool)) flagsParser = Map.unionsWith Map.union <$> many (option readFlag (long "flag" <> completer flagCompleter <> metavar "PACKAGE:[-]FLAG" <> help ("Override flags set in stack.yaml " <> "(applies to local packages and extra-deps)"))) stack-1.5.1/src/Stack/Options/CleanParser.hs0000644000000000000000000000131413135652051017033 0ustar0000000000000000module Stack.Options.CleanParser where import Data.Monoid.Extra import Options.Applicative import Stack.Clean (CleanOpts (..)) import Stack.Types.PackageName -- | Command-line parser for the clean command. cleanOptsParser :: Parser CleanOpts cleanOptsParser = CleanShallow <$> packages <|> doFullClean where packages = many (packageNameArgument (metavar "PACKAGE" <> help "If none specified, clean all local packages")) doFullClean = flag' CleanFull (long "full" <> help "Delete all work directories (.stack-work by default) in the project") stack-1.5.1/src/Stack/Options/ConfigParser.hs0000644000000000000000000001351113135652051017220 0ustar0000000000000000module Stack.Options.ConfigParser where import Data.Char import Data.Either.Combinators import Data.Monoid.Extra import qualified Data.Set as Set import Options.Applicative import Options.Applicative.Builder.Extra import Path import Stack.Constants import Stack.Options.BuildMonoidParser import Stack.Options.DockerParser import Stack.Options.GhcBuildParser import Stack.Options.GhcVariantParser import Stack.Options.NixParser import Stack.Options.Utils import Stack.Types.Config import qualified System.FilePath as FilePath -- | Command-line arguments parser for configuration. configOptsParser :: FilePath -> GlobalOptsContext -> Parser ConfigMonoid configOptsParser currentDir hide0 = (\stackRoot workDir buildOpts dockerOpts nixOpts systemGHC installGHC arch ghcVariant ghcBuild jobs includes libs overrideGccPath skipGHCCheck skipMsys localBin modifyCodePage allowDifferentUser dumpLogs -> mempty { configMonoidStackRoot = stackRoot , configMonoidWorkDir = workDir , configMonoidBuildOpts = buildOpts , configMonoidDockerOpts = dockerOpts , configMonoidNixOpts = nixOpts , configMonoidSystemGHC = systemGHC , configMonoidInstallGHC = installGHC , configMonoidSkipGHCCheck = skipGHCCheck , configMonoidArch = arch , configMonoidGHCVariant = ghcVariant , configMonoidGHCBuild = ghcBuild , configMonoidJobs = jobs , configMonoidExtraIncludeDirs = includes , configMonoidExtraLibDirs = libs , configMonoidOverrideGccPath = overrideGccPath , configMonoidSkipMsys = skipMsys , configMonoidLocalBinPath = localBin , configMonoidModifyCodePage = modifyCodePage , configMonoidAllowDifferentUser = allowDifferentUser , configMonoidDumpLogs = dumpLogs }) <$> optionalFirst (absDirOption ( long stackRootOptionName <> metavar (map toUpper stackRootOptionName) <> help ("Absolute path to the global stack root directory " ++ "(Overrides any STACK_ROOT environment variable)") <> hide )) <*> optionalFirst (option (eitherReader (mapLeft showWorkDirError . parseRelDir)) ( long "work-dir" <> metavar "WORK-DIR" <> completer (pathCompleterWith (defaultPathCompleterOpts { pcoAbsolute = False, pcoFileFilter = const False })) <> help ("Relative path of work directory " ++ "(Overrides any STACK_WORK environment variable, default is '.stack-work')") <> hide )) <*> buildOptsMonoidParser hide0 <*> dockerOptsParser True <*> nixOptsParser True <*> firstBoolFlags "system-ghc" "using the system installed GHC (on the PATH) if available and a matching version. Disabled by default." hide <*> firstBoolFlags "install-ghc" "downloading and installing GHC if necessary (can be done manually with stack setup)" hide <*> optionalFirst (strOption ( long "arch" <> metavar "ARCH" <> help "System architecture, e.g. i386, x86_64" <> hide )) <*> optionalFirst (ghcVariantParser (hide0 /= OuterGlobalOpts)) <*> optionalFirst (ghcBuildParser (hide0 /= OuterGlobalOpts)) <*> optionalFirst (option auto ( long "jobs" <> short 'j' <> metavar "JOBS" <> help "Number of concurrent jobs to run" <> hide )) <*> fmap Set.fromList (many ((currentDir FilePath.) <$> strOption ( long "extra-include-dirs" <> metavar "DIR" <> completer dirCompleter <> help "Extra directories to check for C header files" <> hide ))) <*> fmap Set.fromList (many ((currentDir FilePath.) <$> strOption ( long "extra-lib-dirs" <> metavar "DIR" <> completer dirCompleter <> help "Extra directories to check for libraries" <> hide ))) <*> optionalFirst (absFileOption ( long "with-gcc" <> metavar "PATH-TO-GCC" <> help "Use gcc found at PATH-TO-GCC" <> hide )) <*> firstBoolFlags "skip-ghc-check" "skipping the GHC version and architecture check" hide <*> firstBoolFlags "skip-msys" "skipping the local MSYS installation (Windows only)" hide <*> optionalFirst (strOption ( long "local-bin-path" <> metavar "DIR" <> completer dirCompleter <> help "Install binaries to DIR" <> hide )) <*> firstBoolFlags "modify-code-page" "setting the codepage to support UTF-8 (Windows only)" hide <*> firstBoolFlags "allow-different-user" ("permission for users other than the owner of the stack root " ++ "directory to use a stack installation (POSIX only)") hide <*> fmap toDumpLogs (firstBoolFlags "dump-logs" "dump the build output logs for local packages to the console" hide) where hide = hideMods (hide0 /= OuterGlobalOpts) toDumpLogs (First (Just True)) = First (Just DumpAllLogs) toDumpLogs (First (Just False)) = First (Just DumpNoLogs) toDumpLogs (First Nothing) = First Nothing showWorkDirError err = show err ++ "\nNote that --work-dir must be a relative child directory, because work-dirs outside of the package are not supported by Cabal." ++ "\nSee https://github.com/commercialhaskell/stack/issues/2954" stack-1.5.1/src/Stack/Options/Completion.hs0000644000000000000000000001055613135652051016755 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} module Stack.Options.Completion ( ghcOptsCompleter , targetCompleter , flagCompleter , projectExeCompleter ) where import Control.Monad.Logger (LogLevel (LevelOther)) import Data.Char (isSpace) import Data.List (isPrefixOf) import Data.List.Extra (nubOrd) import qualified Data.Map as Map import Data.Maybe import qualified Data.Set as Set import qualified Data.Text as T import qualified Distribution.PackageDescription as C import Options.Applicative import Options.Applicative.Builder.Extra import Stack.Build.Target (LocalPackageView(..)) import Stack.Build.Source (getLocalPackageViews) import Stack.Options.GlobalParser (globalOptsFromMonoid) import Stack.Runners (loadConfigWithOpts) import Stack.Setup import Stack.Types.Config import Stack.Types.FlagName import Stack.Types.Package import Stack.Types.PackageName import Stack.Types.StackT import System.Process (readProcess) import Language.Haskell.TH.Syntax (runIO, lift) ghcOptsCompleter :: Completer ghcOptsCompleter = mkCompleter $ \inputRaw -> return $ let input = unescapeBashArg inputRaw (curArgReversed, otherArgsReversed) = break isSpace (reverse input) curArg = reverse curArgReversed otherArgs = reverse otherArgsReversed in if null curArg then [] else map (otherArgs ++) $ filter (curArg `isPrefixOf`) -- Technically, we should be consulting the user's current ghc, -- but that would require loading up a BuildConfig. $(runIO (readProcess "ghc" ["--show-options"] "") >>= lift . lines) -- TODO: Ideally this would pay attention to --stack-yaml, may require -- changes to optparse-applicative. buildConfigCompleter :: (String -> StackT EnvConfig IO [String]) -> Completer buildConfigCompleter inner = mkCompleter $ \inputRaw -> do let input = unescapeBashArg inputRaw case input of -- If it looks like a flag, skip this more costly completion. ('-': _) -> return [] _ -> do let go = (globalOptsFromMonoid False mempty) { globalLogLevel = LevelOther "silent" } lc <- loadConfigWithOpts go bconfig <- runStackTGlobal () go $ lcLoadBuildConfig lc (globalCompiler go) envConfig <- runStackTGlobal bconfig go (setupEnv Nothing) runStackTGlobal envConfig go (inner input) targetCompleter :: Completer targetCompleter = buildConfigCompleter $ \input -> do lpvs <- getLocalPackageViews return $ filter (input `isPrefixOf`) $ concatMap allComponentNames (Map.toList lpvs) where allComponentNames (name, (lpv, _)) = map (T.unpack . renderPkgComponent . (name,)) (Set.toList (lpvComponents lpv)) flagCompleter :: Completer flagCompleter = buildConfigCompleter $ \input -> do lpvs <- getLocalPackageViews bconfig <- view buildConfigL let wildcardFlags = nubOrd $ concatMap (\(name, (_, gpd)) -> map (\fl -> "*:" ++ flagString name fl) (C.genPackageFlags gpd)) $ Map.toList lpvs normalFlags = concatMap (\(name, (_, gpd)) -> map (\fl -> packageNameString name ++ ":" ++ flagString name fl) (C.genPackageFlags gpd)) $ Map.toList lpvs flagString name fl = case C.flagName fl of C.FlagName flname -> (if flagEnabled name fl then "-" else "") ++ flname flagEnabled name fl = fromMaybe (C.flagDefault fl) $ Map.lookup (fromCabalFlagName (C.flagName fl)) $ Map.findWithDefault Map.empty name (unPackageFlags (bcFlags bconfig)) return $ filter (input `isPrefixOf`) $ case input of ('*' : ':' : _) -> wildcardFlags ('*' : _) -> wildcardFlags _ -> normalFlags projectExeCompleter :: Completer projectExeCompleter = buildConfigCompleter $ \input -> do lpvs <- getLocalPackageViews return $ filter (input `isPrefixOf`) $ nubOrd $ concatMap (\(_, (_, gpd)) -> map fst (C.condExecutables gpd)) $ Map.toList lpvs stack-1.5.1/src/Stack/Options/DockerParser.hs0000644000000000000000000001525413135652051017230 0ustar0000000000000000module Stack.Options.DockerParser where import Data.Char import Data.List (intercalate) import Data.Monoid.Extra import qualified Data.Text as T import Distribution.Version (anyVersion) import Options.Applicative import Options.Applicative.Args import Options.Applicative.Builder.Extra import Stack.Constants import Stack.Docker import qualified Stack.Docker as Docker import Stack.Options.Utils import Stack.Types.Version import Stack.Types.Docker -- | Options parser configuration for Docker. dockerOptsParser :: Bool -> Parser DockerOptsMonoid dockerOptsParser hide0 = DockerOptsMonoid <$> pure (Any False) <*> firstBoolFlags dockerCmdName "using a Docker container. Implies 'system-ghc: true'" hide <*> fmap First ((Just . DockerMonoidRepo) <$> option str (long (dockerOptName dockerRepoArgName) <> hide <> metavar "NAME" <> help "Docker repository name") <|> (Just . DockerMonoidImage) <$> option str (long (dockerOptName dockerImageArgName) <> hide <> metavar "IMAGE" <> help "Exact Docker image ID (overrides docker-repo)") <|> pure Nothing) <*> firstBoolFlags (dockerOptName dockerRegistryLoginArgName) "registry requires login" hide <*> firstStrOption (long (dockerOptName dockerRegistryUsernameArgName) <> hide <> metavar "USERNAME" <> help "Docker registry username") <*> firstStrOption (long (dockerOptName dockerRegistryPasswordArgName) <> hide <> metavar "PASSWORD" <> help "Docker registry password") <*> firstBoolFlags (dockerOptName dockerAutoPullArgName) "automatic pulling latest version of image" hide <*> firstBoolFlags (dockerOptName dockerDetachArgName) "running a detached Docker container" hide <*> firstBoolFlags (dockerOptName dockerPersistArgName) "not deleting container after it exits" hide <*> firstStrOption (long (dockerOptName dockerContainerNameArgName) <> hide <> metavar "NAME" <> help "Docker container name") <*> argsOption (long (dockerOptName dockerRunArgsArgName) <> hide <> value [] <> metavar "'ARG1 [ARG2 ...]'" <> help "Additional options to pass to 'docker run'") <*> many (option auto (long (dockerOptName dockerMountArgName) <> hide <> metavar "(PATH | HOST-PATH:CONTAINER-PATH)" <> completer dirCompleter <> help ("Mount volumes from host in container " ++ "(may specify multiple times)"))) <*> many (option str (long (dockerOptName dockerEnvArgName) <> hide <> metavar "NAME=VALUE" <> help ("Set environment variable in container " ++ "(may specify multiple times)"))) <*> optionalFirst (absFileOption (long (dockerOptName dockerDatabasePathArgName) <> hide <> metavar "PATH" <> help "Location of image usage tracking database")) <*> optionalFirst (option (eitherReader' parseDockerStackExe) (let specialOpts = [ dockerStackExeDownloadVal , dockerStackExeHostVal , dockerStackExeImageVal ] in long(dockerOptName dockerStackExeArgName) <> hide <> metavar (intercalate "|" (specialOpts ++ ["PATH"])) <> completer (listCompleter specialOpts <> fileCompleter) <> help (concat [ "Location of " , stackProgName , " executable used in container" ]))) <*> firstBoolFlags (dockerOptName dockerSetUserArgName) "setting user in container to match host" hide <*> pure (IntersectingVersionRange anyVersion) where dockerOptName optName = dockerCmdName ++ "-" ++ T.unpack optName firstStrOption = optionalFirst . option str hide = hideMods hide0 -- | Parser for docker cleanup arguments. dockerCleanupOptsParser :: Parser Docker.CleanupOpts dockerCleanupOptsParser = Docker.CleanupOpts <$> (flag' Docker.CleanupInteractive (short 'i' <> long "interactive" <> help "Show cleanup plan in editor and allow changes (default)") <|> flag' Docker.CleanupImmediate (short 'y' <> long "immediate" <> help "Immediately execute cleanup plan") <|> flag' Docker.CleanupDryRun (short 'n' <> long "dry-run" <> help "Display cleanup plan but do not execute") <|> pure Docker.CleanupInteractive) <*> opt (Just 14) "known-images" "LAST-USED" <*> opt Nothing "unknown-images" "CREATED" <*> opt (Just 0) "dangling-images" "CREATED" <*> opt Nothing "stopped-containers" "CREATED" <*> opt Nothing "running-containers" "CREATED" where opt def' name mv = fmap Just (option auto (long name <> metavar (mv ++ "-DAYS-AGO") <> help ("Remove " ++ toDescr name ++ " " ++ map toLower (toDescr mv) ++ " N days ago" ++ case def' of Just n -> " (default " ++ show n ++ ")" Nothing -> ""))) <|> flag' Nothing (long ("no-" ++ name) <> help ("Do not remove " ++ toDescr name ++ case def' of Just _ -> "" Nothing -> " (default)")) <|> pure def' toDescr = map (\c -> if c == '-' then ' ' else c) stack-1.5.1/src/Stack/Options/DotParser.hs0000644000000000000000000000604313135652051016543 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Stack.Options.DotParser where import Data.Char (isSpace) import Data.List.Split (splitOn) import Data.Monoid.Extra import qualified Data.Set as Set import qualified Data.Text as T import Options.Applicative import Options.Applicative.Builder.Extra import Stack.Dot import Stack.Options.BuildParser -- | Parser for arguments to `stack dot` dotOptsParser :: Bool -> Parser DotOpts dotOptsParser externalDefault = DotOpts <$> includeExternal <*> includeBase <*> depthLimit <*> fmap (maybe Set.empty Set.fromList . fmap splitNames) prunedPkgs <*> targetsParser <*> flagsParser <*> testTargets <*> benchTargets where includeExternal = boolFlags externalDefault "external" "inclusion of external dependencies" idm includeBase = boolFlags True "include-base" "inclusion of dependencies on base" idm depthLimit = optional (option auto (long "depth" <> metavar "DEPTH" <> help ("Limit the depth of dependency resolution " <> "(Default: No limit)"))) prunedPkgs = optional (strOption (long "prune" <> metavar "PACKAGES" <> help ("Prune each package name " <> "from the comma separated list " <> "of package names PACKAGES"))) testTargets = switch (long "test" <> help "Consider dependencies of test components") benchTargets = switch (long "bench" <> help "Consider dependencies of benchmark components") splitNames :: String -> [String] splitNames = map (takeWhile (not . isSpace) . dropWhile isSpace) . splitOn "," -- | Parser for arguments to `stack list-dependencies`. listDepsOptsParser :: Parser ListDepsOpts listDepsOptsParser = ListDepsOpts <$> dotOptsParser True -- Default for --external is True. <*> fmap escapeSep (textOption (long "separator" <> metavar "SEP" <> help ("Separator between package name " <> "and package version.") <> value " " <> showDefault)) <*> boolFlags False "license" "printing of dependency licenses instead of versions" idm where escapeSep sep = T.replace "\\t" "\t" (T.replace "\\n" "\n" sep) stack-1.5.1/src/Stack/Options/ExecParser.hs0000644000000000000000000000440713135652051016703 0ustar0000000000000000module Stack.Options.ExecParser where import Data.Monoid.Extra import Options.Applicative import Options.Applicative.Builder.Extra import Options.Applicative.Args import Stack.Options.Completion import Stack.Types.Config -- | Parser for exec command execOptsParser :: Maybe SpecialExecCmd -> Parser ExecOpts execOptsParser mcmd = ExecOpts <$> maybe eoCmdParser pure mcmd <*> eoArgsParser <*> execOptsExtraParser where eoCmdParser = ExecCmd <$> strArgument (metavar "CMD" <> completer projectExeCompleter) eoArgsParser = many (strArgument (metavar "-- ARGS (e.g. stack ghc -- X.hs -o x)")) evalOptsParser :: String -- ^ metavar -> Parser EvalOpts evalOptsParser meta = EvalOpts <$> eoArgsParser <*> execOptsExtraParser where eoArgsParser :: Parser String eoArgsParser = strArgument (metavar meta) -- | Parser for extra options to exec command execOptsExtraParser :: Parser ExecOptsExtra execOptsExtraParser = eoPlainParser <|> ExecOptsEmbellished <$> eoEnvSettingsParser <*> eoPackagesParser <*> eoRtsOptionsParser where eoEnvSettingsParser :: Parser EnvSettings eoEnvSettingsParser = EnvSettings <$> pure True <*> boolFlags True "ghc-package-path" "setting the GHC_PACKAGE_PATH variable for the subprocess" idm <*> boolFlags True "stack-exe" "setting the STACK_EXE environment variable to the path for the stack executable" idm <*> pure False eoPackagesParser :: Parser [String] eoPackagesParser = many (strOption (long "package" <> help "Additional packages that must be installed")) eoRtsOptionsParser :: Parser [String] eoRtsOptionsParser = concat <$> many (argsOption ( long "rts-options" <> help "Explicit RTS options to pass to application" <> metavar "RTSFLAG")) eoPlainParser :: Parser ExecOptsExtra eoPlainParser = flag' ExecOptsPlain (long "plain" <> help "Use an unmodified environment (only useful with Docker)") stack-1.5.1/src/Stack/Options/GhcBuildParser.hs0000644000000000000000000000146713135652051017503 0ustar0000000000000000module Stack.Options.GhcBuildParser where import Data.Monoid.Extra import Options.Applicative import Options.Applicative.Types import Stack.Options.Utils import Stack.Types.CompilerBuild -- | GHC build parser ghcBuildParser :: Bool -> Parser CompilerBuild ghcBuildParser hide = option readGHCBuild (long "ghc-build" <> metavar "BUILD" <> completeWith ["standard", "gmp4", "nopie", "tinfo6", "tinfo6-nopie", "ncurses6", "integersimple"] <> help "Specialized GHC build, e.g. 'gmp4' or 'standard' (usually auto-detected)" <> hideMods hide ) where readGHCBuild = do s <- readerAsk case parseCompilerBuild s of Left e -> readerError (show e) Right v -> return v stack-1.5.1/src/Stack/Options/GhciParser.hs0000644000000000000000000000574013135652051016672 0ustar0000000000000000module Stack.Options.GhciParser where import Data.Monoid.Extra import Data.Version (showVersion) import Options.Applicative import Options.Applicative.Args import Options.Applicative.Builder.Extra import Paths_stack as Meta import Stack.Config (packagesParser) import Stack.Ghci (GhciOpts (..)) import Stack.Options.BuildParser (flagsParser) import Stack.Options.Completion -- | Parser for GHCI options ghciOptsParser :: Parser GhciOpts ghciOptsParser = GhciOpts <$> many (textArgument (metavar "TARGET/FILE" <> completer (targetCompleter <> fileExtCompleter [".hs", ".lhs"]) <> help ("If none specified, use all local packages. " <> "See https://docs.haskellstack.org/en/v" <> showVersion Meta.version <> "/build_command/#target-syntax for details. " <> "If a path to a .hs or .lhs file is specified, it will be loaded."))) <*> fmap concat (many (argsOption (long "ghci-options" <> metavar "OPTIONS" <> completer ghcOptsCompleter <> help "Additional options passed to GHCi"))) <*> many (textOption (long "ghc-options" <> metavar "OPTIONS" <> completer ghcOptsCompleter <> help "Additional options passed to both GHC and GHCi")) <*> flagsParser <*> optional (strOption (long "with-ghc" <> metavar "GHC" <> help "Use this GHC to run GHCi")) <*> (not <$> boolFlags True "load" "load modules on start-up" idm) <*> packagesParser <*> optional (textOption (long "main-is" <> metavar "TARGET" <> completer targetCompleter <> help "Specify which target should contain the main \ \module to load, such as for an executable for \ \test suite or benchmark.")) <*> switch (long "load-local-deps" <> help "Load all local dependencies of your targets") -- TODO: deprecate this? probably useless. <*> switch (long "skip-intermediate-deps" <> help "Skip loading intermediate target dependencies" <> internal) <*> boolFlags True "package-hiding" "package hiding" idm <*> switch (long "no-build" <> help "Don't build before launching GHCi" <> internal) stack-1.5.1/src/Stack/Options/GhcVariantParser.hs0000644000000000000000000000134413135652051020042 0ustar0000000000000000module Stack.Options.GhcVariantParser where import Data.Monoid.Extra import Options.Applicative import Options.Applicative.Types (readerAsk) import Stack.Options.Utils import Stack.Types.Config -- | GHC variant parser ghcVariantParser :: Bool -> Parser GHCVariant ghcVariantParser hide = option readGHCVariant (long "ghc-variant" <> metavar "VARIANT" <> help "Specialized GHC variant, e.g. integersimple (incompatible with --system-ghc)" <> hideMods hide ) where readGHCVariant = do s <- readerAsk case parseGHCVariant s of Left e -> readerError (show e) Right v -> return v stack-1.5.1/src/Stack/Options/GlobalParser.hs0000644000000000000000000000734313135652051017221 0ustar0000000000000000{-# LANGUAGE RecordWildCards #-} module Stack.Options.GlobalParser where import Control.Monad.Logger (LogLevel (..)) import Data.Monoid.Extra import Options.Applicative import Options.Applicative.Builder.Extra import qualified Stack.Docker as Docker import Stack.Init import Stack.Options.ConfigParser import Stack.Options.LogLevelParser import Stack.Options.ResolverParser import Stack.Options.Utils import Stack.Types.Config import Stack.Types.Docker -- | Parser for global command-line options. globalOptsParser :: FilePath -> GlobalOptsContext -> Maybe LogLevel -> Parser GlobalOptsMonoid globalOptsParser currentDir kind defLogLevel = GlobalOptsMonoid <$> optionalFirst (strOption (long Docker.reExecArgName <> hidden <> internal)) <*> optionalFirst (option auto (long dockerEntrypointArgName <> hidden <> internal)) <*> (First <$> logLevelOptsParser hide0 defLogLevel) <*> firstBoolFlags "time-in-log" "inclusion of timings in logs, for the purposes of using diff with logs" hide <*> configOptsParser currentDir kind <*> optionalFirst (abstractResolverOptsParser hide0) <*> optionalFirst (compilerOptsParser hide0) <*> firstBoolFlags "terminal" "overriding terminal detection in the case of running in a false terminal" hide <*> optionalFirst (option readColorWhen (long "color" <> metavar "WHEN" <> completeWith ["always", "never", "auto"] <> help "Specify when to use color in output; WHEN is 'always', 'never', or 'auto'" <> hide)) <*> optionalFirst (strOption (long "stack-yaml" <> metavar "STACK-YAML" <> completer (fileExtCompleter [".yaml"]) <> help ("Override project stack.yaml file " <> "(overrides any STACK_YAML environment variable)") <> hide)) where hide = hideMods hide0 hide0 = kind /= OuterGlobalOpts -- | Create GlobalOpts from GlobalOptsMonoid. globalOptsFromMonoid :: Bool -> GlobalOptsMonoid -> GlobalOpts globalOptsFromMonoid defaultTerminal GlobalOptsMonoid{..} = GlobalOpts { globalReExecVersion = getFirst globalMonoidReExecVersion , globalDockerEntrypoint = getFirst globalMonoidDockerEntrypoint , globalLogLevel = fromFirst defaultLogLevel globalMonoidLogLevel , globalTimeInLog = fromFirst True globalMonoidTimeInLog , globalConfigMonoid = globalMonoidConfigMonoid , globalResolver = getFirst globalMonoidResolver , globalCompiler = getFirst globalMonoidCompiler , globalTerminal = fromFirst defaultTerminal globalMonoidTerminal , globalColorWhen = fromFirst ColorAuto globalMonoidColorWhen , globalStackYaml = maybe SYLDefault SYLOverride $ getFirst globalMonoidStackYaml } initOptsParser :: Parser InitOpts initOptsParser = InitOpts <$> searchDirs <*> solver <*> omitPackages <*> overwrite <*> fmap not ignoreSubDirs where searchDirs = many (textArgument (metavar "DIR" <> completer dirCompleter <> help "Directories to include, default is current directory.")) ignoreSubDirs = switch (long "ignore-subdirs" <> help "Do not search for .cabal files in sub directories") overwrite = switch (long "force" <> help "Force overwriting an existing stack.yaml") omitPackages = switch (long "omit-packages" <> help "Exclude conflicting or incompatible user packages") solver = switch (long "solver" <> help "Use a dependency solver to determine extra dependencies") stack-1.5.1/src/Stack/Options/HaddockParser.hs0000644000000000000000000000140013135652051017342 0ustar0000000000000000module Stack.Options.HaddockParser where import Data.Maybe import Data.Monoid.Extra import Options.Applicative import Options.Applicative.Args import Stack.Options.Utils import Stack.Types.Config -- | Parser for haddock arguments. haddockOptsParser :: Bool -> Parser HaddockOptsMonoid haddockOptsParser hide0 = HaddockOptsMonoid <$> fmap (fromMaybe []) (optional (argsOption (long "haddock-arguments" <> metavar "HADDOCK_ARGS" <> help "Arguments passed to the haddock program" <> hide))) where hide = hideMods hide0 stack-1.5.1/src/Stack/Options/HpcReportParser.hs0000644000000000000000000000316013135652051017720 0ustar0000000000000000module Stack.Options.HpcReportParser where import Data.Monoid.Extra import qualified Data.Text as T import Options.Applicative import Options.Applicative.Builder.Extra import Options.Applicative.Types (readerAsk) import Stack.Coverage (HpcReportOpts (..)) import Stack.Options.Completion (targetCompleter) import Stack.Types.Config -- | Parser for @stack hpc report@. hpcReportOptsParser :: Parser HpcReportOpts hpcReportOptsParser = HpcReportOpts <$> many (textArgument $ metavar "TARGET_OR_TIX" <> completer (targetCompleter <> fileExtCompleter [".tix"])) <*> switch (long "all" <> help "Use results from all packages and components involved in previous --coverage run") <*> optional (strOption (long "destdir" <> metavar "DIR" <> completer dirCompleter <> help "Output directory for HTML report")) <*> switch (long "open" <> help "Open the report in the browser") pvpBoundsOption :: Parser PvpBounds pvpBoundsOption = option readPvpBounds (long "pvp-bounds" <> metavar "PVP-BOUNDS" <> completeWith ["none", "lower", "upper", "both"] <> help "How PVP version bounds should be added to .cabal file: none, lower, upper, both") where readPvpBounds = do s <- readerAsk case parsePvpBounds $ T.pack s of Left e -> readerError e Right v -> return v stack-1.5.1/src/Stack/Options/LogLevelParser.hs0000644000000000000000000000313213135652051017522 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Stack.Options.LogLevelParser where import Control.Monad.Logger (LogLevel (..)) import Data.Monoid.Extra import qualified Data.Text as T import Options.Applicative import Stack.Options.Utils -- | Parser for a logging level. logLevelOptsParser :: Bool -> Maybe LogLevel -> Parser (Maybe LogLevel) logLevelOptsParser hide defLogLevel = fmap (Just . parse) (strOption (long "verbosity" <> metavar "VERBOSITY" <> completeWith ["silent", "error", "warn", "info", "debug"] <> help "Verbosity: silent, error, warn, info, debug" <> hideMods hide)) <|> flag' (Just verboseLevel) (short 'v' <> long "verbose" <> help ("Enable verbose mode: verbosity level \"" <> showLevel verboseLevel <> "\"") <> hideMods hide) <|> flag' (Just silentLevel) (long "silent" <> help ("Enable silent mode: verbosity level \"" <> showLevel silentLevel <> "\"") <> hideMods hide) <|> pure defLogLevel where verboseLevel = LevelDebug silentLevel = LevelOther "silent" showLevel l = case l of LevelDebug -> "debug" LevelInfo -> "info" LevelWarn -> "warn" LevelError -> "error" LevelOther x -> T.unpack x parse s = case s of "debug" -> LevelDebug "info" -> LevelInfo "warn" -> LevelWarn "error" -> LevelError _ -> LevelOther (T.pack s) stack-1.5.1/src/Stack/Options/NewParser.hs0000644000000000000000000000241213135652051016542 0ustar0000000000000000module Stack.Options.NewParser where import qualified Data.Map.Strict as M import Data.Monoid.Extra import Options.Applicative import Stack.Init import Stack.New import Stack.Options.GlobalParser import Stack.Types.PackageName import Stack.Types.TemplateName -- | Parser for @stack new@. newOptsParser :: Parser (NewOpts,InitOpts) newOptsParser = (,) <$> newOpts <*> initOptsParser where newOpts = NewOpts <$> packageNameArgument (metavar "PACKAGE_NAME" <> help "A valid package name.") <*> switch (long "bare" <> help "Do not create a subdirectory for the project") <*> optional (templateNameArgument (metavar "TEMPLATE_NAME" <> help "Name of a template or a local template in a file or a URL.\ \ For example: foo or foo.hsfiles or ~/foo or\ \ https://example.com/foo.hsfiles")) <*> fmap M.fromList (many (templateParamArgument (short 'p' <> long "param" <> metavar "KEY:VALUE" <> help "Parameter for the template in the format key:value"))) stack-1.5.1/src/Stack/Options/NixParser.hs0000644000000000000000000000421413135652051016551 0ustar0000000000000000module Stack.Options.NixParser where import Data.Monoid.Extra import qualified Data.Text as T import Options.Applicative import Options.Applicative.Args import Options.Applicative.Builder.Extra import Stack.Nix import Stack.Options.Utils import Stack.Types.Nix nixOptsParser :: Bool -> Parser NixOptsMonoid nixOptsParser hide0 = overrideActivation <$> (NixOptsMonoid <$> pure (Any False) <*> firstBoolFlags nixCmdName "use of a Nix-shell. Implies 'system-ghc: true'" hide <*> firstBoolFlags "nix-pure" "use of a pure Nix-shell. Implies '--nix' and 'system-ghc: true'" hide <*> optionalFirst (textArgsOption (long "nix-packages" <> metavar "NAMES" <> help "List of packages that should be available in the nix-shell (space separated)" <> hide)) <*> optionalFirst (option str (long "nix-shell-file" <> metavar "FILE" <> completer (fileExtCompleter [".nix"]) <> help "Nix file to be used to launch a nix-shell (for regular Nix users)" <> hide)) <*> optionalFirst (textArgsOption (long "nix-shell-options" <> metavar "OPTIONS" <> help "Additional options passed to nix-shell" <> hide)) <*> optionalFirst (textArgsOption (long "nix-path" <> metavar "PATH_OPTIONS" <> help "Additional options to override NIX_PATH parts (notably 'nixpkgs')" <> hide)) <*> firstBoolFlags "nix-add-gc-roots" "addition of packages to the nix GC roots so nix-collect-garbage doesn't remove them" hide ) where hide = hideMods hide0 overrideActivation m = if fromFirst False (nixMonoidPureShell m) then m { nixMonoidEnable = (First . Just . fromFirst True) (nixMonoidEnable m) } else m textArgsOption = fmap (map T.pack) . argsOption stack-1.5.1/src/Stack/Options/PackageParser.hs0000644000000000000000000000235213135652051017347 0ustar0000000000000000module Stack.Options.PackageParser where import qualified Data.Map as Map import Data.Map.Strict (Map) import Options.Applicative import Options.Applicative.Types (readerAsk) import Stack.Types.FlagName import Stack.Types.PackageName -- | Parser for package:[-]flag readFlag :: ReadM (Map (Maybe PackageName) (Map FlagName Bool)) readFlag = do s <- readerAsk case break (== ':') s of (pn, ':':mflag) -> do pn' <- case parsePackageNameFromString pn of Nothing | pn == "*" -> return Nothing | otherwise -> readerError $ "Invalid package name: " ++ pn Just x -> return $ Just x let (b, flagS) = case mflag of '-':x -> (False, x) _ -> (True, mflag) flagN <- case parseFlagNameFromString flagS of Nothing -> readerError $ "Invalid flag name: " ++ flagS Just x -> return x return $ Map.singleton pn' $ Map.singleton flagN b _ -> readerError "Must have a colon" stack-1.5.1/src/Stack/Options/ResolverParser.hs0000644000000000000000000000212513135652051017613 0ustar0000000000000000module Stack.Options.ResolverParser where import Data.Monoid.Extra import qualified Data.Text as T import Options.Applicative import Options.Applicative.Types (readerAsk) import Stack.Options.Utils import Stack.Types.Compiler import Stack.Types.Resolver -- | Parser for the resolver abstractResolverOptsParser :: Bool -> Parser AbstractResolver abstractResolverOptsParser hide = option readAbstractResolver (long "resolver" <> metavar "RESOLVER" <> help "Override resolver in project file" <> hideMods hide) compilerOptsParser :: Bool -> Parser CompilerVersion compilerOptsParser hide = option readCompilerVersion (long "compiler" <> metavar "COMPILER" <> help "Use the specified compiler" <> hideMods hide) readCompilerVersion :: ReadM CompilerVersion readCompilerVersion = do s <- readerAsk case parseCompilerVersion (T.pack s) of Nothing -> readerError $ "Failed to parse compiler: " ++ s Just x -> return x stack-1.5.1/src/Stack/Options/ScriptParser.hs0000644000000000000000000000205413135652051017257 0ustar0000000000000000module Stack.Options.ScriptParser where import Data.Monoid ((<>)) import Options.Applicative import Options.Applicative.Builder.Extra data ScriptOpts = ScriptOpts { soPackages :: ![String] , soFile :: !FilePath , soArgs :: ![String] , soCompile :: !ScriptExecute } deriving Show data ScriptExecute = SEInterpret | SECompile | SEOptimize deriving Show scriptOptsParser :: Parser ScriptOpts scriptOptsParser = ScriptOpts <$> many (strOption (long "package" <> help "Additional packages that must be installed")) <*> strArgument (metavar "FILE" <> completer (fileExtCompleter [".hs", ".lhs"])) <*> many (strArgument (metavar "-- ARGS (e.g. stack ghc -- X.hs -o x)")) <*> (flag' SECompile ( long "compile" <> help "Compile the script without optimization and run the executable" ) <|> flag' SEOptimize ( long "optimize" <> help "Compile the script with optimization and run the executable" ) <|> pure SEInterpret) stack-1.5.1/src/Stack/Options/SDistParser.hs0000644000000000000000000000223013135652051017035 0ustar0000000000000000module Stack.Options.SDistParser where import Data.Monoid import Options.Applicative import Options.Applicative.Builder.Extra import Stack.SDist import Stack.Options.HpcReportParser (pvpBoundsOption) -- | Parser for arguments to `stack sdist` and `stack upload` sdistOptsParser :: Bool -- ^ Whether to sign by default `stack upload` does, `stack sdist` doesn't -> Parser SDistOpts sdistOptsParser signDefault = SDistOpts <$> many (strArgument $ metavar "DIR" <> completer dirCompleter) <*> optional pvpBoundsOption <*> ignoreCheckSwitch <*> (if signDefault then switch (long "no-signature" <> help "Do not sign & upload signatures") else switch (long "sign" <> help "Sign & upload signatures")) <*> strOption (long "sig-server" <> metavar "URL" <> showDefault <> value "https://sig.commercialhaskell.org" <> help "URL") <*> buildPackageOption where ignoreCheckSwitch = switch (long "ignore-check" <> help "Do not check package for common mistakes") buildPackageOption = boolFlags False "test-tarball" "building of the resulting tarball" idm stack-1.5.1/src/Stack/Options/SolverParser.hs0000644000000000000000000000050513135652051017264 0ustar0000000000000000module Stack.Options.SolverParser where import Options.Applicative import Options.Applicative.Builder.Extra -- | Parser for @solverCmd@ solverOptsParser :: Parser Bool solverOptsParser = boolFlags False "update-config" "Automatically update stack.yaml with the solver's recommendations" idm stack-1.5.1/src/Stack/Options/TestParser.hs0000644000000000000000000000251013135652051016727 0ustar0000000000000000module Stack.Options.TestParser where import Data.Maybe import Data.Monoid.Extra import Options.Applicative import Options.Applicative.Args import Options.Applicative.Builder.Extra import Stack.Options.Utils import Stack.Types.Config -- | Parser for test arguments. -- FIXME hide args testOptsParser :: Bool -> Parser TestOptsMonoid testOptsParser hide0 = TestOptsMonoid <$> firstBoolFlags "rerun-tests" "running already successful tests" hide <*> fmap (fromMaybe []) (optional (argsOption (long "test-arguments" <> long "ta" <> metavar "TEST_ARGS" <> help "Arguments passed in to the test suite program" <> hide))) <*> optionalFirst (switch (long "coverage" <> help "Generate a code coverage report" <> hide)) <*> optionalFirst (switch (long "no-run-tests" <> help "Disable running of tests. (Tests will still be built.)" <> hide)) where hide = hideMods hide0 stack-1.5.1/src/Stack/Options/Utils.hs0000644000000000000000000000146313135652051015741 0ustar0000000000000000module Stack.Options.Utils where import Data.Monoid.Extra import Options.Applicative -- | If argument is True, hides the option from usage and help hideMods :: Bool -> Mod f a hideMods hide = if hide then internal <> hidden else idm -- | Allows adjust global options depending on their context -- Note: This was being used to remove ambibuity between the local and global -- implementation of stack init --resolver option. Now that stack init has no -- local --resolver this is not being used anymore but the code is kept for any -- similar future use cases. data GlobalOptsContext = OuterGlobalOpts -- ^ Global options before subcommand name | OtherCmdGlobalOpts -- ^ Global options following any other subcommand | BuildCmdGlobalOpts | GhciCmdGlobalOpts deriving (Show, Eq) stack-1.5.1/src/Stack/Package.hs0000644000000000000000000014677613140560217014560 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE RecordWildCards #-} -- | Dealing with Cabal. module Stack.Package (readPackage ,readPackageBS ,readPackageDescriptionDir ,readDotBuildinfo ,readPackageUnresolved ,readPackageUnresolvedBS ,resolvePackage ,packageFromPackageDescription ,findOrGenerateCabalFile ,hpack ,Package(..) ,GetPackageFiles(..) ,GetPackageOpts(..) ,PackageConfig(..) ,buildLogPath ,PackageException (..) ,resolvePackageDescription ,packageToolDependencies ,packageDependencies ,autogenDir ,checkCabalFileName ,printCabalFileWarning ,cabalFilePackageId) where import Prelude () import Prelude.Compat import Control.Arrow ((&&&)) import Control.Exception hiding (try,catch) import Control.Monad (liftM, liftM2, (<=<), when, forM, forM_) import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.Reader (MonadReader,runReaderT,ask,asks) import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as C8 import Data.List.Compat import Data.List.Extra (nubOrd) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe import Data.Maybe.Extra import Data.Monoid import Data.Set (Set) import qualified Data.Set as S import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeUtf8, decodeUtf8With) import Data.Text.Encoding.Error (lenientDecode) import Data.Version (showVersion) import Distribution.Compiler import Distribution.ModuleName (ModuleName) import qualified Distribution.ModuleName as Cabal import qualified Distribution.Package as D import Distribution.Package hiding (Package,PackageName,packageName,packageVersion,PackageIdentifier) import qualified Distribution.PackageDescription as D import Distribution.PackageDescription hiding (FlagName) import Distribution.PackageDescription.Parse import qualified Distribution.PackageDescription.Parse as D import Distribution.ParseUtils import Distribution.Simple.Utils import Distribution.System (OS (..), Arch, Platform (..)) import qualified Distribution.Text as D import qualified Distribution.Verbosity as D import qualified Hpack import qualified Hpack.Config as Hpack import Path as FL import Path.Extra import Path.Find import Path.IO hiding (findFiles) import Safe (headDef, tailSafe) import Stack.Build.Installed import Stack.Constants import Stack.PrettyPrint import Stack.Types.Build import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.FlagName import Stack.Types.GhcPkgId import Stack.Types.Package import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.Version import qualified System.Directory as D import System.FilePath (splitExtensions, replaceExtension) import qualified System.FilePath as FilePath import System.IO.Error -- | Read the raw, unresolved package information. readPackageUnresolved :: (MonadIO m, MonadThrow m) => Path Abs File -> m ([PWarning],GenericPackageDescription) readPackageUnresolved cabalfp = liftIO (BS.readFile (FL.toFilePath cabalfp)) >>= readPackageUnresolvedBS (Left cabalfp) -- | Read the raw, unresolved package information from a ByteString. readPackageUnresolvedBS :: (MonadThrow m) => Either (Path Abs File) PackageIdentifier -> BS.ByteString -> m ([PWarning],GenericPackageDescription) readPackageUnresolvedBS source bs = case parsePackageDescription chars of ParseFailed per -> throwM (PackageInvalidCabalFile source per) ParseOk warnings gpkg -> return (warnings,gpkg) where chars = T.unpack (dropBOM (decodeUtf8With lenientDecode bs)) -- https://github.com/haskell/hackage-server/issues/351 dropBOM t = fromMaybe t $ T.stripPrefix "\xFEFF" t -- | Reads and exposes the package information readPackage :: (MonadLogger m, MonadIO m, MonadCatch m) => PackageConfig -> Path Abs File -> m ([PWarning],Package) readPackage packageConfig cabalfp = do (warnings,gpkg) <- readPackageUnresolved cabalfp return (warnings,resolvePackage packageConfig gpkg) -- | Reads and exposes the package information, from a ByteString readPackageBS :: (MonadThrow m) => PackageConfig -> PackageIdentifier -> BS.ByteString -> m ([PWarning],Package) readPackageBS packageConfig ident bs = do (warnings,gpkg) <- readPackageUnresolvedBS (Right ident) bs return (warnings,resolvePackage packageConfig gpkg) -- | Get 'GenericPackageDescription' and 'PackageDescription' reading info -- from given directory. readPackageDescriptionDir :: (MonadLogger m, MonadIO m, MonadCatch m) => PackageConfig -> Path Abs Dir -> m (GenericPackageDescription, PackageDescription) readPackageDescriptionDir config pkgDir = do cabalfp <- findOrGenerateCabalFile pkgDir gdesc <- liftM snd (readPackageUnresolved cabalfp) return (gdesc, resolvePackageDescription config gdesc) -- | Read @.buildinfo@ ancillary files produced by some Setup.hs hooks. -- The file includes Cabal file syntax to be merged into the package description -- derived from the package's .cabal file. -- -- NOTE: not to be confused with BuildInfo, an Stack-internal datatype. readDotBuildinfo :: MonadIO m => Path Abs File -> m HookedBuildInfo readDotBuildinfo buildinfofp = liftIO $ readHookedBuildInfo D.silent (toFilePath buildinfofp) -- | Print cabal file warnings. printCabalFileWarning :: (MonadLogger m) => Path Abs File -> PWarning -> m () printCabalFileWarning cabalfp = \case (PWarning x) -> $logWarn ("Cabal file warning in " <> T.pack (toFilePath cabalfp) <> ": " <> T.pack x) (UTFWarning ln msg) -> $logWarn ("Cabal file warning in " <> T.pack (toFilePath cabalfp) <> ":" <> T.pack (show ln) <> ": " <> T.pack msg) -- | Check if the given name in the @Package@ matches the name of the .cabal file checkCabalFileName :: MonadThrow m => PackageName -> Path Abs File -> m () checkCabalFileName name cabalfp = do -- Previously, we just use parsePackageNameFromFilePath. However, that can -- lead to confusing error messages. See: -- https://github.com/commercialhaskell/stack/issues/895 let expected = packageNameString name ++ ".cabal" when (expected /= toFilePath (filename cabalfp)) $ throwM $ MismatchedCabalName cabalfp name -- | Resolve a parsed cabal file into a 'Package', which contains all of -- the info needed for stack to build the 'Package' given the current -- configuration. resolvePackage :: PackageConfig -> GenericPackageDescription -> Package resolvePackage packageConfig gpkg = packageFromPackageDescription packageConfig (genPackageFlags gpkg) (resolvePackageDescription packageConfig gpkg) packageFromPackageDescription :: PackageConfig -> [D.Flag] -> PackageDescription -> Package packageFromPackageDescription packageConfig pkgFlags pkg = Package { packageName = name , packageVersion = fromCabalVersion (pkgVersion pkgId) , packageLicense = license pkg , packageDeps = deps , packageFiles = pkgFiles , packageTools = packageDescTools pkg , packageGhcOptions = packageConfigGhcOptions packageConfig , packageFlags = packageConfigFlags packageConfig , packageDefaultFlags = M.fromList [(fromCabalFlagName (flagName flag), flagDefault flag) | flag <- pkgFlags] , packageAllDeps = S.fromList (M.keys deps) , packageHasLibrary = maybe False (buildable . libBuildInfo) (library pkg) , packageTests = M.fromList [(T.pack (testName t), testInterface t) | t <- testSuites pkg , buildable (testBuildInfo t)] , packageBenchmarks = S.fromList [T.pack (benchmarkName biBuildInfo) | biBuildInfo <- benchmarks pkg , buildable (benchmarkBuildInfo biBuildInfo)] , packageExes = S.fromList [T.pack (exeName biBuildInfo) | biBuildInfo <- executables pkg , buildable (buildInfo biBuildInfo)] -- This is an action used to collect info needed for "stack ghci". -- This info isn't usually needed, so computation of it is deferred. , packageOpts = GetPackageOpts $ \sourceMap installedMap omitPkgs addPkgs cabalfp -> do (componentsModules,componentFiles,_,_) <- getPackageFiles pkgFiles cabalfp componentsOpts <- generatePkgDescOpts sourceMap installedMap omitPkgs addPkgs cabalfp pkg componentFiles return (componentsModules,componentFiles,componentsOpts) , packageHasExposedModules = maybe False (not . null . exposedModules) (library pkg) , packageBuildType = buildType pkg , packageSetupDeps = msetupDeps } where -- Gets all of the modules, files, build files, and data files that -- constitute the package. This is primarily used for dirtiness -- checking during build, as well as use by "stack ghci" pkgFiles = GetPackageFiles $ \cabalfp -> $debugBracket ("getPackageFiles" <+> display cabalfp) $ do let pkgDir = parent cabalfp distDir <- distDirFromDir pkgDir (componentModules,componentFiles,dataFiles',warnings) <- runReaderT (packageDescModulesAndFiles pkg) (cabalfp, buildDir distDir) setupFiles <- if buildType pkg `elem` [Nothing, Just Custom] then do let setupHsPath = pkgDir $(mkRelFile "Setup.hs") setupLhsPath = pkgDir $(mkRelFile "Setup.lhs") setupHsExists <- doesFileExist setupHsPath if setupHsExists then return (S.singleton setupHsPath) else do setupLhsExists <- doesFileExist setupLhsPath if setupLhsExists then return (S.singleton setupLhsPath) else return S.empty else return S.empty buildFiles <- liftM (S.insert cabalfp . S.union setupFiles) $ do let hpackPath = pkgDir $(mkRelFile Hpack.packageConfig) hpackExists <- doesFileExist hpackPath return $ if hpackExists then S.singleton hpackPath else S.empty return (componentModules, componentFiles, buildFiles <> dataFiles', warnings) pkgId = package pkg name = fromCabalPackageName (pkgName pkgId) deps = M.filterWithKey (const . (/= name)) (M.union (packageDependencies pkg) -- We include all custom-setup deps - if present - in the -- package deps themselves. Stack always works with the -- invariant that there will be a single installed package -- relating to a package name, and this applies at the setup -- dependency level as well. (fromMaybe M.empty msetupDeps)) msetupDeps = fmap (M.fromList . map (depName &&& depRange) . setupDepends) (setupBuildInfo pkg) -- | Generate GHC options for the package's components, and a list of -- options which apply generally to the package, not one specific -- component. generatePkgDescOpts :: (HasEnvConfig env, MonadThrow m, MonadReader env m, MonadIO m) => SourceMap -> InstalledMap -> [PackageName] -- ^ Packages to omit from the "-package" / "-package-id" flags -> [PackageName] -- ^ Packages to add to the "-package" flags -> Path Abs File -> PackageDescription -> Map NamedComponent (Set DotCabalPath) -> m (Map NamedComponent BuildInfoOpts) generatePkgDescOpts sourceMap installedMap omitPkgs addPkgs cabalfp pkg componentPaths = do config <- view configL distDir <- distDirFromDir cabalDir let cabalMacros = autogenDir distDir $(mkRelFile "cabal_macros.h") exists <- doesFileExist cabalMacros let mcabalMacros = if exists then Just cabalMacros else Nothing let generate namedComponent binfo = ( namedComponent , generateBuildInfoOpts BioInput { biSourceMap = sourceMap , biInstalledMap = installedMap , biCabalMacros = mcabalMacros , biCabalDir = cabalDir , biDistDir = distDir , biOmitPackages = omitPkgs , biAddPackages = addPkgs , biBuildInfo = binfo , biDotCabalPaths = fromMaybe mempty (M.lookup namedComponent componentPaths) , biConfigLibDirs = configExtraLibDirs config , biConfigIncludeDirs = configExtraIncludeDirs config , biComponentName = namedComponent } ) return ( M.fromList (concat [ maybe [] (return . generate CLib . libBuildInfo) (library pkg) , fmap (\exe -> generate (CExe (T.pack (exeName exe))) (buildInfo exe)) (executables pkg) , fmap (\bench -> generate (CBench (T.pack (benchmarkName bench))) (benchmarkBuildInfo bench)) (benchmarks pkg) , fmap (\test -> generate (CTest (T.pack (testName test))) (testBuildInfo test)) (testSuites pkg)])) where cabalDir = parent cabalfp -- | Input to 'generateBuildInfoOpts' data BioInput = BioInput { biSourceMap :: !SourceMap , biInstalledMap :: !InstalledMap , biCabalMacros :: !(Maybe (Path Abs File)) , biCabalDir :: !(Path Abs Dir) , biDistDir :: !(Path Abs Dir) , biOmitPackages :: ![PackageName] , biAddPackages :: ![PackageName] , biBuildInfo :: !BuildInfo , biDotCabalPaths :: !(Set DotCabalPath) , biConfigLibDirs :: !(Set FilePath) , biConfigIncludeDirs :: !(Set FilePath) , biComponentName :: !NamedComponent } -- | Generate GHC options for the target. Since Cabal also figures out -- these options, currently this is only used for invoking GHCI (via -- stack ghci). generateBuildInfoOpts :: BioInput -> BuildInfoOpts generateBuildInfoOpts BioInput {..} = BuildInfoOpts { bioOpts = ghcOpts ++ cppOptions biBuildInfo -- NOTE for future changes: Due to this use of nubOrd (and other uses -- downstream), these generated options must not rely on multiple -- argument sequences. For example, ["--main-is", "Foo.hs", "--main- -- is", "Bar.hs"] would potentially break due to the duplicate -- "--main-is" being removed. -- -- See https://github.com/commercialhaskell/stack/issues/1255 , bioOneWordOpts = nubOrd $ concat [extOpts, srcOpts, includeOpts, libOpts, fworks, cObjectFiles] , bioPackageFlags = deps , bioCabalMacros = biCabalMacros } where cObjectFiles = mapMaybe (fmap toFilePath . makeObjectFilePathFromC biCabalDir biComponentName biDistDir) cfiles cfiles = mapMaybe dotCabalCFilePath (S.toList biDotCabalPaths) -- Generates: -package=base -package=base16-bytestring-0.1.1.6 ... deps = concat [ case M.lookup name biInstalledMap of Just (_, Stack.Types.Package.Library _ident ipid) -> ["-package-id=" <> ghcPkgIdString ipid] _ -> ["-package=" <> packageNameString name <> maybe "" -- This empty case applies to e.g. base. ((("-" <>) . versionString) . piiVersion) (M.lookup name biSourceMap)] | name <- pkgs] pkgs = biAddPackages ++ [ name | Dependency cname _ <- targetBuildDepends biBuildInfo , let name = fromCabalPackageName cname , name `notElem` biOmitPackages] ghcOpts = concatMap snd . filter (isGhc . fst) $ options biBuildInfo where isGhc GHC = True isGhc _ = False extOpts = map (("-X" ++) . D.display) (usedExtensions biBuildInfo) srcOpts = map (("-i" <>) . toFilePathNoTrailingSep) ([biCabalDir | null (hsSourceDirs biBuildInfo)] <> mapMaybe toIncludeDir (hsSourceDirs biBuildInfo) <> [autogenDir biDistDir,buildDir biDistDir] <> [makeGenDir (buildDir biDistDir) | Just makeGenDir <- [fileGenDirFromComponentName biComponentName]]) ++ ["-stubdir=" ++ toFilePathNoTrailingSep (buildDir biDistDir)] toIncludeDir "." = Just biCabalDir toIncludeDir relDir = concatAndColapseAbsDir biCabalDir relDir includeOpts = map ("-I" <>) (configExtraIncludeDirs <> pkgIncludeOpts) configExtraIncludeDirs = S.toList biConfigIncludeDirs pkgIncludeOpts = [ toFilePathNoTrailingSep absDir | dir <- includeDirs biBuildInfo , absDir <- handleDir dir ] libOpts = map ("-l" <>) (extraLibs biBuildInfo) <> map ("-L" <>) (configExtraLibDirs <> pkgLibDirs) configExtraLibDirs = S.toList biConfigLibDirs pkgLibDirs = [ toFilePathNoTrailingSep absDir | dir <- extraLibDirs biBuildInfo , absDir <- handleDir dir ] handleDir dir = case (parseAbsDir dir, parseRelDir dir) of (Just ab, _ ) -> [ab] (_ , Just rel) -> [biCabalDir rel] (Nothing, Nothing ) -> [] fworks = map (\fwk -> "-framework=" <> fwk) (frameworks biBuildInfo) -- | Make the .o path from the .c file path for a component. Example: -- -- @ -- executable FOO -- c-sources: cbits/text_search.c -- @ -- -- Produces -- -- /build/FOO/FOO-tmp/cbits/text_search.o -- -- Example: -- -- λ> makeObjectFilePathFromC -- $(mkAbsDir "/Users/chris/Repos/hoogle") -- CLib -- $(mkAbsDir "/Users/chris/Repos/hoogle/.stack-work/Cabal-x.x.x/dist") -- $(mkAbsFile "/Users/chris/Repos/hoogle/cbits/text_search.c") -- Just "/Users/chris/Repos/hoogle/.stack-work/Cabal-x.x.x/dist/build/cbits/text_search.o" -- λ> makeObjectFilePathFromC -- $(mkAbsDir "/Users/chris/Repos/hoogle") -- (CExe "hoogle") -- $(mkAbsDir "/Users/chris/Repos/hoogle/.stack-work/Cabal-x.x.x/dist") -- $(mkAbsFile "/Users/chris/Repos/hoogle/cbits/text_search.c") -- Just "/Users/chris/Repos/hoogle/.stack-work/Cabal-x.x.x/dist/build/hoogle/hoogle-tmp/cbits/text_search.o" -- λ> makeObjectFilePathFromC :: MonadThrow m => Path Abs Dir -- ^ The cabal directory. -> NamedComponent -- ^ The name of the component. -> Path Abs Dir -- ^ Dist directory. -> Path Abs File -- ^ The path to the .c file. -> m (Path Abs File) -- ^ The path to the .o file for the component. makeObjectFilePathFromC cabalDir namedComponent distDir cFilePath = do relCFilePath <- stripDir cabalDir cFilePath relOFilePath <- parseRelFile (replaceExtension (toFilePath relCFilePath) "o") addComponentPrefix <- fileGenDirFromComponentName namedComponent return (addComponentPrefix (buildDir distDir) relOFilePath) -- | The directory where generated files are put like .o or .hs (from .x files). fileGenDirFromComponentName :: MonadThrow m => NamedComponent -> m (Path b Dir -> Path b Dir) fileGenDirFromComponentName namedComponent = case namedComponent of CLib -> return id CExe name -> makeTmp name CTest name -> makeTmp name CBench name -> makeTmp name where makeTmp name = do prefix <- parseRelDir (T.unpack name <> "/" <> T.unpack name <> "-tmp") return ( prefix) -- | Make the autogen dir. autogenDir :: Path Abs Dir -> Path Abs Dir autogenDir distDir = buildDir distDir $(mkRelDir "autogen") -- | Make the build dir. buildDir :: Path Abs Dir -> Path Abs Dir buildDir distDir = distDir $(mkRelDir "build") -- | Make the component-specific subdirectory of the build directory. getBuildComponentDir :: Maybe String -> Maybe (Path Rel Dir) getBuildComponentDir Nothing = Nothing getBuildComponentDir (Just name) = parseRelDir (name FilePath. (name ++ "-tmp")) -- | Get all dependencies of the package (buildable targets only). packageDependencies :: PackageDescription -> Map PackageName VersionRange packageDependencies pkg = M.fromListWith intersectVersionRanges $ map (depName &&& depRange) $ concatMap targetBuildDepends (allBuildInfo' pkg) ++ maybe [] setupDepends (setupBuildInfo pkg) -- | Get all build tool dependencies of the package (buildable targets only). packageToolDependencies :: PackageDescription -> Map Text VersionRange packageToolDependencies = M.fromList . concatMap (fmap (packageNameText . depName &&& depRange) . buildTools) . allBuildInfo' -- | Get all dependencies of the package (buildable targets only). packageDescTools :: PackageDescription -> [Dependency] packageDescTools = concatMap buildTools . allBuildInfo' -- | This is a copy-paste from Cabal's @allBuildInfo@ function, but with the -- @buildable@ test removed. The implementation is broken. -- See: https://github.com/haskell/cabal/issues/1725 allBuildInfo' :: PackageDescription -> [BuildInfo] allBuildInfo' pkg_descr = [ bi | Just lib <- [library pkg_descr] , let bi = libBuildInfo lib , True || buildable bi ] ++ [ bi | exe <- executables pkg_descr , let bi = buildInfo exe , True || buildable bi ] ++ [ bi | tst <- testSuites pkg_descr , let bi = testBuildInfo tst , True || buildable bi , testEnabled tst ] ++ [ bi | tst <- benchmarks pkg_descr , let bi = benchmarkBuildInfo tst , True || buildable bi , benchmarkEnabled tst ] -- | Get all files referenced by the package. packageDescModulesAndFiles :: (MonadLogger m, MonadIO m, MonadReader (Path Abs File, Path Abs Dir) m, MonadCatch m) => PackageDescription -> m (Map NamedComponent (Set ModuleName), Map NamedComponent (Set DotCabalPath), Set (Path Abs File), [PackageWarning]) packageDescModulesAndFiles pkg = do (libraryMods,libDotCabalFiles,libWarnings) <- maybe (return (M.empty, M.empty, [])) (asModuleAndFileMap libComponent libraryFiles) (library pkg) (executableMods,exeDotCabalFiles,exeWarnings) <- liftM foldTuples (mapM (asModuleAndFileMap exeComponent executableFiles) (executables pkg)) (testMods,testDotCabalFiles,testWarnings) <- liftM foldTuples (mapM (asModuleAndFileMap testComponent testFiles) (testSuites pkg)) (benchModules,benchDotCabalPaths,benchWarnings) <- liftM foldTuples (mapM (asModuleAndFileMap benchComponent benchmarkFiles) (benchmarks pkg)) dfiles <- resolveGlobFiles (extraSrcFiles pkg ++ map (dataDir pkg FilePath.) (dataFiles pkg)) let modules = libraryMods <> executableMods <> testMods <> benchModules files = libDotCabalFiles <> exeDotCabalFiles <> testDotCabalFiles <> benchDotCabalPaths warnings = libWarnings <> exeWarnings <> testWarnings <> benchWarnings return (modules, files, dfiles, warnings) where libComponent = const CLib exeComponent = CExe . T.pack . exeName testComponent = CTest . T.pack . testName benchComponent = CBench . T.pack . benchmarkName asModuleAndFileMap label f lib = do (a,b,c) <- f lib return (M.singleton (label lib) a, M.singleton (label lib) b, c) foldTuples = foldl' (<>) (M.empty, M.empty, []) -- | Resolve globbing of files (e.g. data files) to absolute paths. resolveGlobFiles :: (MonadLogger m,MonadIO m,MonadReader (Path Abs File, Path Abs Dir) m,MonadCatch m) => [String] -> m (Set (Path Abs File)) resolveGlobFiles = liftM (S.fromList . catMaybes . concat) . mapM resolve where resolve name = if '*' `elem` name then explode name else liftM return (resolveFileOrWarn name) explode name = do dir <- asks (parent . fst) names <- matchDirFileGlob' (FL.toFilePath dir) name mapM resolveFileOrWarn names matchDirFileGlob' dir glob = catch (matchDirFileGlob_ dir glob) (\(e :: IOException) -> if isUserError e then do $logWarn ("Wildcard does not match any files: " <> T.pack glob <> "\n" <> "in directory: " <> T.pack dir) return [] else throwM e) -- | This is a copy/paste of the Cabal library function, but with -- -- @ext == ext'@ -- -- Changed to -- -- @isSuffixOf ext ext'@ -- -- So that this will work: -- -- @ -- λ> matchDirFileGlob_ "." "test/package-dump/*.txt" -- ["test/package-dump/ghc-7.8.txt","test/package-dump/ghc-7.10.txt"] -- @ -- matchDirFileGlob_ :: (MonadLogger m, MonadIO m) => String -> String -> m [String] matchDirFileGlob_ dir filepath = case parseFileGlob filepath of Nothing -> liftIO $ die $ "invalid file glob '" ++ filepath ++ "'. Wildcards '*' are only allowed in place of the file" ++ " name, not in the directory name or file extension." ++ " If a wildcard is used it must be with an file extension." Just (NoGlob filepath') -> return [filepath'] Just (FileGlob dir' ext) -> do efiles <- liftIO $ try $ D.getDirectoryContents (dir FilePath. dir') let matches = case efiles of Left (_ :: IOException) -> [] Right files -> [ dir' FilePath. file | file <- files , let (name, ext') = splitExtensions file , not (null name) && isSuffixOf ext ext' ] when (null matches) $ $logWarn $ "WARNING: filepath wildcard '" <> T.pack filepath <> "' does not match any files." return matches -- | Get all files referenced by the benchmark. benchmarkFiles :: (MonadLogger m, MonadIO m, MonadCatch m, MonadReader (Path Abs File, Path Abs Dir) m) => Benchmark -> m (Set ModuleName, Set DotCabalPath, [PackageWarning]) benchmarkFiles bench = do dirs <- mapMaybeM resolveDirOrWarn (hsSourceDirs build) dir <- asks (parent . fst) (modules,files,warnings) <- resolveFilesAndDeps (Just $ benchmarkName bench) (dirs ++ [dir]) (bnames <> exposed) haskellModuleExts cfiles <- buildOtherSources build return (modules, files <> cfiles, warnings) where exposed = case benchmarkInterface bench of BenchmarkExeV10 _ fp -> [DotCabalMain fp] BenchmarkUnsupported _ -> [] bnames = map DotCabalModule (otherModules build) build = benchmarkBuildInfo bench -- | Get all files referenced by the test. testFiles :: (MonadLogger m, MonadIO m, MonadCatch m, MonadReader (Path Abs File, Path Abs Dir) m) => TestSuite -> m (Set ModuleName, Set DotCabalPath, [PackageWarning]) testFiles test = do dirs <- mapMaybeM resolveDirOrWarn (hsSourceDirs build) dir <- asks (parent . fst) (modules,files,warnings) <- resolveFilesAndDeps (Just $ testName test) (dirs ++ [dir]) (bnames <> exposed) haskellModuleExts cfiles <- buildOtherSources build return (modules, files <> cfiles, warnings) where exposed = case testInterface test of TestSuiteExeV10 _ fp -> [DotCabalMain fp] TestSuiteLibV09 _ mn -> [DotCabalModule mn] TestSuiteUnsupported _ -> [] bnames = map DotCabalModule (otherModules build) build = testBuildInfo test -- | Get all files referenced by the executable. executableFiles :: (MonadLogger m, MonadIO m, MonadCatch m, MonadReader (Path Abs File, Path Abs Dir) m) => Executable -> m (Set ModuleName, Set DotCabalPath, [PackageWarning]) executableFiles exe = do dirs <- mapMaybeM resolveDirOrWarn (hsSourceDirs build) dir <- asks (parent . fst) (modules,files,warnings) <- resolveFilesAndDeps (Just $ exeName exe) (dirs ++ [dir]) (map DotCabalModule (otherModules build) ++ [DotCabalMain (modulePath exe)]) haskellModuleExts cfiles <- buildOtherSources build return (modules, files <> cfiles, warnings) where build = buildInfo exe -- | Get all files referenced by the library. libraryFiles :: (MonadLogger m, MonadIO m, MonadCatch m, MonadReader (Path Abs File, Path Abs Dir) m) => Library -> m (Set ModuleName, Set DotCabalPath, [PackageWarning]) libraryFiles lib = do dirs <- mapMaybeM resolveDirOrWarn (hsSourceDirs build) dir <- asks (parent . fst) (modules,files,warnings) <- resolveFilesAndDeps Nothing (dirs ++ [dir]) names haskellModuleExts cfiles <- buildOtherSources build return (modules, files <> cfiles, warnings) where names = bnames ++ exposed exposed = map DotCabalModule (exposedModules lib) bnames = map DotCabalModule (otherModules build) build = libBuildInfo lib -- | Get all C sources and extra source files in a build. buildOtherSources :: (MonadLogger m,MonadIO m,MonadCatch m,MonadReader (Path Abs File, Path Abs Dir) m) => BuildInfo -> m (Set DotCabalPath) buildOtherSources build = do csources <- liftM (S.map DotCabalCFilePath . S.fromList) (mapMaybeM resolveFileOrWarn (cSources build)) jsources <- liftM (S.map DotCabalFilePath . S.fromList) (mapMaybeM resolveFileOrWarn (targetJsSources build)) return (csources <> jsources) -- | Get the target's JS sources. targetJsSources :: BuildInfo -> [FilePath] targetJsSources = jsSources -- | Evaluates the conditions of a 'GenericPackageDescription', yielding -- a resolved 'PackageDescription'. resolvePackageDescription :: PackageConfig -> GenericPackageDescription -> PackageDescription resolvePackageDescription packageConfig (GenericPackageDescription desc defaultFlags mlib exes tests benches) = desc {library = fmap (resolveConditions rc updateLibDeps) mlib ,executables = map (\(n, v) -> (resolveConditions rc updateExeDeps v){exeName=n}) exes ,testSuites = map (\(n,v) -> (resolveConditions rc updateTestDeps v){testName=n}) tests ,benchmarks = map (\(n,v) -> (resolveConditions rc updateBenchmarkDeps v){benchmarkName=n}) benches} where flags = M.union (packageConfigFlags packageConfig) (flagMap defaultFlags) rc = mkResolveConditions (packageConfigCompilerVersion packageConfig) (packageConfigPlatform packageConfig) flags updateLibDeps lib deps = lib {libBuildInfo = (libBuildInfo lib) {targetBuildDepends = deps}} updateExeDeps exe deps = exe {buildInfo = (buildInfo exe) {targetBuildDepends = deps}} updateTestDeps test deps = test {testBuildInfo = (testBuildInfo test) {targetBuildDepends = deps} ,testEnabled = packageConfigEnableTests packageConfig} updateBenchmarkDeps benchmark deps = benchmark {benchmarkBuildInfo = (benchmarkBuildInfo benchmark) {targetBuildDepends = deps} ,benchmarkEnabled = packageConfigEnableBenchmarks packageConfig} -- | Make a map from a list of flag specifications. -- -- What is @flagManual@ for? flagMap :: [Flag] -> Map FlagName Bool flagMap = M.fromList . map pair where pair :: Flag -> (FlagName, Bool) pair (MkFlag (fromCabalFlagName -> name) _desc def _manual) = (name,def) data ResolveConditions = ResolveConditions { rcFlags :: Map FlagName Bool , rcCompilerVersion :: CompilerVersion , rcOS :: OS , rcArch :: Arch } -- | Generic a @ResolveConditions@ using sensible defaults. mkResolveConditions :: CompilerVersion -- ^ Compiler version -> Platform -- ^ installation target platform -> Map FlagName Bool -- ^ enabled flags -> ResolveConditions mkResolveConditions compilerVersion (Platform arch os) flags = ResolveConditions { rcFlags = flags , rcCompilerVersion = compilerVersion , rcOS = os , rcArch = arch } -- | Resolve the condition tree for the library. resolveConditions :: (Monoid target,Show target) => ResolveConditions -> (target -> cs -> target) -> CondTree ConfVar cs target -> target resolveConditions rc addDeps (CondNode lib deps cs) = basic <> children where basic = addDeps lib deps children = mconcat (map apply cs) where apply (cond,node,mcs) = if condSatisfied cond then resolveConditions rc addDeps node else maybe mempty (resolveConditions rc addDeps) mcs condSatisfied c = case c of Var v -> varSatisifed v Lit b -> b CNot c' -> not (condSatisfied c') COr cx cy -> condSatisfied cx || condSatisfied cy CAnd cx cy -> condSatisfied cx && condSatisfied cy varSatisifed v = case v of OS os -> os == rcOS rc Arch arch -> arch == rcArch rc Flag flag -> fromMaybe False $ M.lookup (fromCabalFlagName flag) (rcFlags rc) -- NOTE: ^^^^^ This should never happen, as all flags -- which are used must be declared. Defaulting to -- False. Impl flavor range -> case (flavor, rcCompilerVersion rc) of (GHC, GhcVersion vghc) -> vghc `withinRange` range (GHC, GhcjsVersion _ vghc) -> vghc `withinRange` range (GHCJS, GhcjsVersion vghcjs _) -> vghcjs `withinRange` range _ -> False -- | Get the name of a dependency. depName :: Dependency -> PackageName depName (Dependency n _) = fromCabalPackageName n -- | Get the version range of a dependency. depRange :: Dependency -> VersionRange depRange (Dependency _ r) = r -- | Try to resolve the list of base names in the given directory by -- looking for unique instances of base names applied with the given -- extensions, plus find any of their module and TemplateHaskell -- dependencies. resolveFilesAndDeps :: (MonadIO m, MonadLogger m, MonadCatch m, MonadReader (Path Abs File, Path Abs Dir) m) => Maybe String -- ^ Package component name -> [Path Abs Dir] -- ^ Directories to look in. -> [DotCabalDescriptor] -- ^ Base names. -> [Text] -- ^ Extensions. -> m (Set ModuleName,Set DotCabalPath,[PackageWarning]) resolveFilesAndDeps component dirs names0 exts = do (dotCabalPaths, foundModules, missingModules) <- loop names0 S.empty warnings <- liftM2 (++) (warnUnlisted foundModules) (warnMissing missingModules) return (foundModules, dotCabalPaths, warnings) where loop [] _ = return (S.empty, S.empty, []) loop names doneModules0 = do resolved <- resolveFiles dirs names exts let foundFiles = mapMaybe snd resolved (foundModules', missingModules') = partition (isJust . snd) resolved foundModules = mapMaybe (dotCabalModule . fst) foundModules' missingModules = mapMaybe (dotCabalModule . fst) missingModules' pairs <- mapM (getDependencies component) foundFiles let doneModules = S.union doneModules0 (S.fromList (mapMaybe dotCabalModule names)) moduleDeps = S.unions (map fst pairs) thDepFiles = concatMap snd pairs modulesRemaining = S.difference moduleDeps doneModules -- Ignore missing modules discovered as dependencies - they may -- have been deleted. (resolvedFiles, resolvedModules, _) <- loop (map DotCabalModule (S.toList modulesRemaining)) doneModules return ( S.union (S.fromList (foundFiles <> map DotCabalFilePath thDepFiles)) resolvedFiles , S.union (S.fromList foundModules) resolvedModules , missingModules) warnUnlisted foundModules = do let unlistedModules = foundModules `S.difference` S.fromList (mapMaybe dotCabalModule names0) return $ if S.null unlistedModules then [] else [ UnlistedModulesWarning component (S.toList unlistedModules)] warnMissing _missingModules = do return [] -- TODO: bring this back - see -- https://github.com/commercialhaskell/stack/issues/2649 {- cabalfp <- asks fst return $ if null missingModules then [] else [ MissingModulesWarning cabalfp component missingModules] -} -- | Get the dependencies of a Haskell module file. getDependencies :: (MonadReader (Path Abs File, Path Abs Dir) m, MonadIO m, MonadCatch m, MonadLogger m) => Maybe String -> DotCabalPath -> m (Set ModuleName, [Path Abs File]) getDependencies component dotCabalPath = case dotCabalPath of DotCabalModulePath resolvedFile -> readResolvedHi resolvedFile DotCabalMainPath resolvedFile -> readResolvedHi resolvedFile DotCabalFilePath{} -> return (S.empty, []) DotCabalCFilePath{} -> return (S.empty, []) where readResolvedHi resolvedFile = do dumpHIDir <- getDumpHIDir dir <- asks (parent . fst) case stripDir dir resolvedFile of Nothing -> return (S.empty, []) Just fileRel -> do let dumpHIPath = FilePath.replaceExtension (toFilePath (dumpHIDir fileRel)) ".dump-hi" dumpHIExists <- liftIO $ D.doesFileExist dumpHIPath if dumpHIExists then parseDumpHI dumpHIPath else return (S.empty, []) getDumpHIDir = do bld <- asks snd return $ maybe bld (bld ) (getBuildComponentDir component) -- | Parse a .dump-hi file into a set of modules and files. parseDumpHI :: (MonadReader (Path Abs File, void) m, MonadIO m, MonadCatch m, MonadLogger m) => FilePath -> m (Set ModuleName, [Path Abs File]) parseDumpHI dumpHIPath = do dir <- asks (parent . fst) dumpHI <- liftIO $ fmap C8.lines (C8.readFile dumpHIPath) let startModuleDeps = dropWhile (not . ("module dependencies:" `C8.isPrefixOf`)) dumpHI moduleDeps = S.fromList $ mapMaybe (D.simpleParse . T.unpack . decodeUtf8) $ C8.words $ C8.concat $ C8.dropWhile (/= ' ') (headDef "" startModuleDeps) : takeWhile (" " `C8.isPrefixOf`) (tailSafe startModuleDeps) thDeps = -- The dependent file path is surrounded by quotes but is not escaped. -- It can be an absolute or relative path. mapMaybe (fmap T.unpack . (T.stripSuffix "\"" <=< T.stripPrefix "\"") . T.dropWhileEnd (== '\r') . decodeUtf8 . C8.dropWhile (/= '"')) $ filter ("addDependentFile \"" `C8.isPrefixOf`) dumpHI thDepsResolved <- liftM catMaybes $ forM thDeps $ \x -> do mresolved <- forgivingAbsence (resolveFile dir x) >>= rejectMissingFile when (isNothing mresolved) $ $logWarn $ "Warning: addDependentFile path (Template Haskell) listed in " <> T.pack dumpHIPath <> " does not exist: " <> T.pack x return mresolved return (moduleDeps, thDepsResolved) -- | Try to resolve the list of base names in the given directory by -- looking for unique instances of base names applied with the given -- extensions. resolveFiles :: (MonadIO m, MonadLogger m, MonadThrow m, MonadReader (Path Abs File, Path Abs Dir) m) => [Path Abs Dir] -- ^ Directories to look in. -> [DotCabalDescriptor] -- ^ Base names. -> [Text] -- ^ Extensions. -> m [(DotCabalDescriptor, Maybe DotCabalPath)] resolveFiles dirs names exts = forM names (\name -> liftM (name, ) (findCandidate dirs exts name)) -- | Find a candidate for the given module-or-filename from the list -- of directories and given extensions. findCandidate :: (MonadIO m, MonadLogger m, MonadThrow m, MonadReader (Path Abs File, Path Abs Dir) m) => [Path Abs Dir] -> [Text] -> DotCabalDescriptor -> m (Maybe DotCabalPath) findCandidate dirs exts name = do pkg <- asks fst >>= parsePackageNameFromFilePath candidates <- liftIO makeNameCandidates case candidates of [candidate] -> return (Just (cons candidate)) [] -> do case name of DotCabalModule mn | D.display mn /= paths_pkg pkg -> logPossibilities dirs mn _ -> return () return Nothing (candidate:rest) -> do warnMultiple name candidate rest return (Just (cons candidate)) where cons = case name of DotCabalModule{} -> DotCabalModulePath DotCabalMain{} -> DotCabalMainPath DotCabalFile{} -> DotCabalFilePath DotCabalCFile{} -> DotCabalCFilePath paths_pkg pkg = "Paths_" ++ packageNameString pkg makeNameCandidates = liftM (nubOrd . concat) (mapM makeDirCandidates dirs) makeDirCandidates :: Path Abs Dir -> IO [Path Abs File] makeDirCandidates dir = case name of DotCabalMain fp -> resolveCandidate dir fp DotCabalFile fp -> resolveCandidate dir fp DotCabalCFile fp -> resolveCandidate dir fp DotCabalModule mn -> liftM concat $ mapM ((\ ext -> resolveCandidate dir (Cabal.toFilePath mn ++ "." ++ ext)) . T.unpack) exts resolveCandidate :: (MonadIO m, MonadThrow m) => Path Abs Dir -> FilePath.FilePath -> m [Path Abs File] resolveCandidate x y = do -- The standard canonicalizePath does not work for this case p <- parseCollapsedAbsFile (toFilePath x FilePath. y) exists <- doesFileExist p return $ if exists then [p] else [] -- | Warn the user that multiple candidates are available for an -- entry, but that we picked one anyway and continued. warnMultiple :: MonadLogger m => DotCabalDescriptor -> Path b t -> [Path b t] -> m () warnMultiple name candidate rest = $logWarn ("There were multiple candidates for the Cabal entry \"" <> showName name <> "\" (" <> T.intercalate "," (map (T.pack . toFilePath) rest) <> "), picking " <> T.pack (toFilePath candidate)) where showName (DotCabalModule name') = T.pack (D.display name') showName (DotCabalMain fp) = T.pack fp showName (DotCabalFile fp) = T.pack fp showName (DotCabalCFile fp) = T.pack fp -- | Log that we couldn't find a candidate, but there are -- possibilities for custom preprocessor extensions. -- -- For example: .erb for a Ruby file might exist in one of the -- directories. logPossibilities :: (MonadIO m, MonadThrow m, MonadLogger m) => [Path Abs Dir] -> ModuleName -> m () logPossibilities dirs mn = do possibilities <- liftM concat (makePossibilities mn) case possibilities of [] -> return () _ -> $logWarn ("Unable to find a known candidate for the Cabal entry \"" <> T.pack (D.display mn) <> "\", but did find: " <> T.intercalate ", " (map (T.pack . toFilePath) possibilities) <> ". If you are using a custom preprocessor for this module " <> "with its own file extension, consider adding the file(s) " <> "to your .cabal under extra-source-files.") where makePossibilities name = mapM (\dir -> do (_,files) <- listDir dir return (map filename (filter (isPrefixOf (D.display name) . toFilePath . filename) files))) dirs -- | Get the filename for the cabal file in the given directory. -- -- If no .cabal file is present, or more than one is present, an exception is -- thrown via 'throwM'. -- -- If the directory contains a file named package.yaml, hpack is used to -- generate a .cabal file from it. findOrGenerateCabalFile :: forall m. (MonadThrow m, MonadIO m, MonadLogger m) => Path Abs Dir -- ^ package directory -> m (Path Abs File) findOrGenerateCabalFile pkgDir = do hpack pkgDir findCabalFile where findCabalFile :: m (Path Abs File) findCabalFile = findCabalFile' >>= either throwM return findCabalFile' :: m (Either PackageException (Path Abs File)) findCabalFile' = do files <- liftIO $ findFiles pkgDir (flip hasExtension "cabal" . FL.toFilePath) (const False) return $ case files of [] -> Left $ PackageNoCabalFileFound pkgDir [x] -> Right x -- If there are multiple files, ignore files that start with -- ".". On unixlike environments these are hidden, and this -- character is not valid in package names. The main goal is -- to ignore emacs lock files - see -- https://github.com/commercialhaskell/stack/issues/1897. (filter (not . ("." `isPrefixOf`) . toFilePath . filename) -> [x]) -> Right x _:_ -> Left $ PackageMultipleCabalFilesFound pkgDir files where hasExtension fp x = FilePath.takeExtension fp == "." ++ x -- | Generate .cabal file from package.yaml, if necessary. hpack :: (MonadIO m, MonadLogger m) => Path Abs Dir -> m () hpack pkgDir = do let hpackFile = pkgDir $(mkRelFile Hpack.packageConfig) exists <- liftIO $ doesFileExist hpackFile when exists $ do let fpt = T.pack (toFilePath hpackFile) $logDebug $ "Running hpack on " <> fpt #if MIN_VERSION_hpack(0,18,0) r <- liftIO $ Hpack.hpackResult (Just $ toFilePath pkgDir) #else r <- liftIO $ Hpack.hpackResult (toFilePath pkgDir) #endif forM_ (Hpack.resultWarnings r) $ \w -> $logWarn ("WARNING: " <> T.pack w) let cabalFile = T.pack (Hpack.resultCabalFile r) case Hpack.resultStatus r of Hpack.Generated -> $logDebug $ "hpack generated a modified version of " <> cabalFile Hpack.OutputUnchanged -> $logDebug $ "hpack output unchanged in " <> cabalFile -- NOTE: this is 'logInfo' so it will be outputted to the -- user by default. Hpack.AlreadyGeneratedByNewerHpack -> $logWarn $ "WARNING: " <> cabalFile <> " was generated with a newer version of hpack, please upgrade and try again." -- | Path for the package's build log. buildLogPath :: (MonadReader env m, HasBuildConfig env, MonadThrow m) => Package -> Maybe String -> m (Path Abs File) buildLogPath package' msuffix = do env <- ask let stack = getProjectWorkDir env fp <- parseRelFile $ concat $ packageIdentifierString (packageIdentifier package') : maybe id (\suffix -> ("-" :) . (suffix :)) msuffix [".log"] return $ stack $(mkRelDir "logs") fp -- Internal helper to define resolveFileOrWarn and resolveDirOrWarn resolveOrWarn :: (MonadLogger m, MonadIO m, MonadThrow m, MonadReader (Path Abs File, Path Abs Dir) m) => Text -> (Path Abs Dir -> String -> m (Maybe a)) -> FilePath.FilePath -> m (Maybe a) resolveOrWarn subject resolver path = do cwd <- getCurrentDir file <- asks fst dir <- asks (parent . fst) result <- resolver dir path when (isNothing result) $ $logWarn ("Warning: " <> subject <> " listed in " <> T.pack (maybe (FL.toFilePath file) FL.toFilePath (stripDir cwd file)) <> " file does not exist: " <> T.pack path) return result -- | Resolve the file, if it can't be resolved, warn for the user -- (purely to be helpful). resolveFileOrWarn :: (MonadCatch m,MonadIO m,MonadLogger m,MonadReader (Path Abs File, Path Abs Dir) m) => FilePath.FilePath -> m (Maybe (Path Abs File)) resolveFileOrWarn = resolveOrWarn "File" f where f p x = forgivingAbsence (resolveFile p x) >>= rejectMissingFile -- | Resolve the directory, if it can't be resolved, warn for the user -- (purely to be helpful). resolveDirOrWarn :: (MonadCatch m,MonadIO m,MonadLogger m,MonadReader (Path Abs File, Path Abs Dir) m) => FilePath.FilePath -> m (Maybe (Path Abs Dir)) resolveDirOrWarn = resolveOrWarn "Directory" f where f p x = forgivingAbsence (resolveDir p x) >>= rejectMissingDir -- | Extract the @PackageIdentifier@ given an exploded haskell package -- path. cabalFilePackageId :: (MonadIO m, MonadThrow m) => Path Abs File -> m PackageIdentifier cabalFilePackageId fp = do pkgDescr <- liftIO (D.readPackageDescription D.silent $ toFilePath fp) (toStackPI . D.package . D.packageDescription) pkgDescr where toStackPI (D.PackageIdentifier (D.PackageName name) ver) = do name' <- parsePackageNameFromString name ver' <- parseVersionFromString (showVersion ver) return (PackageIdentifier name' ver') stack-1.5.1/src/Stack/PackageDump.hs0000644000000000000000000004324213135652051015370 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE TemplateHaskell #-} module Stack.PackageDump ( Line , eachSection , eachPair , DumpPackage (..) , conduitDumpPackage , ghcPkgDump , ghcPkgDescribe , newInstalledCache , loadInstalledCache , saveInstalledCache , addProfiling , addHaddock , addSymbols , sinkMatching , pruneDeps ) where import Control.Applicative import Control.Arrow ((&&&)) import Control.Exception.Safe (tryIO) import Control.Monad (liftM) import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.Logger (MonadLogger) import Control.Monad.Trans.Control import Data.Attoparsec.Args import Data.Attoparsec.Text as P import Data.Conduit import qualified Data.Conduit.List as CL import qualified Data.Conduit.Text as CT import Data.Either (partitionEithers) import Data.IORef import Data.List (isPrefixOf) import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe (catMaybes, listToMaybe) import Data.Maybe.Extra (mapMaybeM) import qualified Data.Set as Set import Data.Store.VersionTagged import Data.Text (Text) import qualified Data.Text as T import Data.Typeable (Typeable) import qualified Distribution.License as C import qualified Distribution.System as OS import qualified Distribution.Text as C import Path import Path.Extra (toFilePathNoTrailingSep) import Prelude -- Fix AMP warning import Stack.GhcPkg import Stack.Types.Compiler import Stack.Types.GhcPkgId import Stack.Types.PackageDump import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.Version import System.Directory (getDirectoryContents, doesFileExist) import System.Process.Read -- | Call ghc-pkg dump with appropriate flags and stream to the given @Sink@, for a single database ghcPkgDump :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => EnvOverride -> WhichCompiler -> [Path Abs Dir] -- ^ if empty, use global -> Sink Text IO a -> m a ghcPkgDump = ghcPkgCmdArgs ["dump"] -- | Call ghc-pkg describe with appropriate flags and stream to the given @Sink@, for a single database ghcPkgDescribe :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => PackageName -> EnvOverride -> WhichCompiler -> [Path Abs Dir] -- ^ if empty, use global -> Sink Text IO a -> m a ghcPkgDescribe pkgName = ghcPkgCmdArgs ["describe", "--simple-output", packageNameString pkgName] -- | Call ghc-pkg and stream to the given @Sink@, for a single database ghcPkgCmdArgs :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => [String] -> EnvOverride -> WhichCompiler -> [Path Abs Dir] -- ^ if empty, use global -> Sink Text IO a -> m a ghcPkgCmdArgs cmd menv wc mpkgDbs sink = do case reverse mpkgDbs of (pkgDb:_) -> createDatabase menv wc pkgDb -- TODO maybe use some retry logic instead? _ -> return () sinkProcessStdout Nothing menv (ghcPkgExeName wc) args sink' where args = concat [ case mpkgDbs of [] -> ["--global", "--no-user-package-db"] _ -> ["--user", "--no-user-package-db"] ++ concatMap (\pkgDb -> ["--package-db", toFilePathNoTrailingSep pkgDb]) mpkgDbs , cmd , ["--expand-pkgroot"] ] sink' = CT.decodeUtf8 =$= sink -- | Create a new, empty @InstalledCache@ newInstalledCache :: MonadIO m => m InstalledCache newInstalledCache = liftIO $ InstalledCache <$> newIORef (InstalledCacheInner Map.empty) -- | Load a @InstalledCache@ from disk, swallowing any errors and returning an -- empty cache. loadInstalledCache :: (MonadLogger m, MonadIO m, MonadBaseControl IO m) => Path Abs File -> m InstalledCache loadInstalledCache path = do m <- $(versionedDecodeOrLoad installedCacheVC) path (return $ InstalledCacheInner Map.empty) liftIO $ InstalledCache <$> newIORef m -- | Save a @InstalledCache@ to disk saveInstalledCache :: (MonadLogger m, MonadIO m) => Path Abs File -> InstalledCache -> m () saveInstalledCache path (InstalledCache ref) = liftIO (readIORef ref) >>= $(versionedEncodeFile installedCacheVC) path -- | Prune a list of possible packages down to those whose dependencies are met. -- -- * id uniquely identifies an item -- -- * There can be multiple items per name pruneDeps :: (Ord name, Ord id) => (id -> name) -- ^ extract the name from an id -> (item -> id) -- ^ the id of an item -> (item -> [id]) -- ^ get the dependencies of an item -> (item -> item -> item) -- ^ choose the desired of two possible items -> [item] -- ^ input items -> Map name item pruneDeps getName getId getDepends chooseBest = Map.fromList . fmap (getName . getId &&& id) . loop Set.empty Set.empty [] where loop foundIds usedNames foundItems dps = case partitionEithers $ map depsMet dps of ([], _) -> foundItems (s', dps') -> let foundIds' = Map.fromListWith chooseBest s' foundIds'' = Set.fromList $ map getId $ Map.elems foundIds' usedNames' = Map.keysSet foundIds' foundItems' = Map.elems foundIds' in loop (Set.union foundIds foundIds'') (Set.union usedNames usedNames') (foundItems ++ foundItems') (catMaybes dps') where depsMet dp | name `Set.member` usedNames = Right Nothing | all (`Set.member` foundIds) (getDepends dp) = Left (name, dp) | otherwise = Right $ Just dp where id' = getId dp name = getName id' -- | Find the package IDs matching the given constraints with all dependencies installed. -- Packages not mentioned in the provided @Map@ are allowed to be present too. sinkMatching :: Monad m => Bool -- ^ require profiling? -> Bool -- ^ require haddock? -> Bool -- ^ require debugging symbols? -> Map PackageName Version -- ^ allowed versions -> Consumer (DumpPackage Bool Bool Bool) m (Map PackageName (DumpPackage Bool Bool Bool)) sinkMatching reqProfiling reqHaddock reqSymbols allowed = do dps <- CL.filter (\dp -> isAllowed (dpPackageIdent dp) && (not reqProfiling || dpProfiling dp) && (not reqHaddock || dpHaddock dp) && (not reqSymbols || dpSymbols dp)) =$= CL.consume return $ Map.fromList $ map (packageIdentifierName . dpPackageIdent &&& id) $ Map.elems $ pruneDeps id dpGhcPkgId dpDepends const -- Could consider a better comparison in the future dps where isAllowed (PackageIdentifier name version) = case Map.lookup name allowed of Just version' | version /= version' -> False _ -> True -- | Add profiling information to the stream of @DumpPackage@s addProfiling :: MonadIO m => InstalledCache -> Conduit (DumpPackage a b c) m (DumpPackage Bool b c) addProfiling (InstalledCache ref) = CL.mapM go where go dp = liftIO $ do InstalledCacheInner m <- readIORef ref let gid = dpGhcPkgId dp p <- case Map.lookup gid m of Just installed -> return (installedCacheProfiling installed) Nothing | null (dpLibraries dp) -> return True Nothing -> do let loop [] = return False loop (dir:dirs) = do econtents <- tryIO $ getDirectoryContents dir let contents = either (const []) id econtents if or [isProfiling content lib | content <- contents , lib <- dpLibraries dp ] && not (null contents) then return True else loop dirs loop $ dpLibDirs dp return dp { dpProfiling = p } isProfiling :: FilePath -- ^ entry in directory -> Text -- ^ name of library -> Bool isProfiling content lib = prefix `T.isPrefixOf` T.pack content where prefix = T.concat ["lib", lib, "_p"] -- | Add haddock information to the stream of @DumpPackage@s addHaddock :: MonadIO m => InstalledCache -> Conduit (DumpPackage a b c) m (DumpPackage a Bool c) addHaddock (InstalledCache ref) = CL.mapM go where go dp = liftIO $ do InstalledCacheInner m <- readIORef ref let gid = dpGhcPkgId dp h <- case Map.lookup gid m of Just installed -> return (installedCacheHaddock installed) Nothing | not (dpHasExposedModules dp) -> return True Nothing -> do let loop [] = return False loop (ifc:ifcs) = do exists <- doesFileExist ifc if exists then return True else loop ifcs loop $ dpHaddockInterfaces dp return dp { dpHaddock = h } -- | Add debugging symbol information to the stream of @DumpPackage@s addSymbols :: MonadIO m => InstalledCache -> Conduit (DumpPackage a b c) m (DumpPackage a b Bool) addSymbols (InstalledCache ref) = CL.mapM go where go dp = do InstalledCacheInner m <- liftIO $ readIORef ref let gid = dpGhcPkgId dp s <- case Map.lookup gid m of Just installed -> return (installedCacheSymbols installed) Nothing | null (dpLibraries dp) -> return True Nothing -> do let lib = T.unpack . head $ dpLibraries dp liftM or . mapM (\dir -> liftIO $ hasDebuggingSymbols dir lib) $ dpLibDirs dp return dp { dpSymbols = s } hasDebuggingSymbols :: FilePath -- ^ library directory -> String -- ^ name of library -> IO Bool hasDebuggingSymbols dir lib = do let path = concat [dir, "/lib", lib, ".a"] exists <- doesFileExist path if not exists then return False else case OS.buildOS of OS.OSX -> liftM (any (isPrefixOf "0x") . lines) $ readProcess "dwarfdump" [path] "" OS.Linux -> liftM (any (isPrefixOf "Contents") . lines) $ readProcess "readelf" ["--debug-dump=info", "--dwarf-depth=1", path] "" OS.FreeBSD -> liftM (any (isPrefixOf "Contents") . lines) $ readProcess "readelf" ["--debug-dump=info", "--dwarf-depth=1", path] "" OS.Windows -> return False -- No support, so it can't be there. _ -> return False -- | Dump information for a single package data DumpPackage profiling haddock symbols = DumpPackage { dpGhcPkgId :: !GhcPkgId , dpPackageIdent :: !PackageIdentifier , dpLicense :: !(Maybe C.License) , dpLibDirs :: ![FilePath] , dpLibraries :: ![Text] , dpHasExposedModules :: !Bool , dpDepends :: ![GhcPkgId] , dpHaddockInterfaces :: ![FilePath] , dpHaddockHtml :: !(Maybe FilePath) , dpProfiling :: !profiling , dpHaddock :: !haddock , dpSymbols :: !symbols , dpIsExposed :: !Bool } deriving (Show, Eq) data PackageDumpException = MissingSingleField Text (Map Text [Line]) | Couldn'tParseField Text [Line] deriving Typeable instance Exception PackageDumpException instance Show PackageDumpException where show (MissingSingleField name values) = unlines $ return (concat [ "Expected single value for field name " , show name , " when parsing ghc-pkg dump output:" ]) ++ map (\(k, v) -> " " ++ show (k, v)) (Map.toList values) show (Couldn'tParseField name ls) = "Couldn't parse the field " ++ show name ++ " from lines: " ++ show ls -- | Convert a stream of bytes into a stream of @DumpPackage@s conduitDumpPackage :: MonadThrow m => Conduit Text m (DumpPackage () () ()) conduitDumpPackage = (=$= CL.catMaybes) $ eachSection $ do pairs <- eachPair (\k -> (k, ) <$> CL.consume) =$= CL.consume let m = Map.fromList pairs let parseS k = case Map.lookup k m of Just [v] -> return v _ -> throwM $ MissingSingleField k m -- Can't fail: if not found, same as an empty list. See: -- https://github.com/fpco/stack/issues/182 parseM k = Map.findWithDefault [] k m parseDepend :: MonadThrow m => Text -> m (Maybe GhcPkgId) parseDepend "builtin_rts" = return Nothing parseDepend bs = liftM Just $ parseGhcPkgId bs' where (bs', _builtinRts) = case stripSuffixText " builtin_rts" bs of Nothing -> case stripPrefixText "builtin_rts " bs of Nothing -> (bs, False) Just x -> (x, True) Just x -> (x, True) case Map.lookup "id" m of Just ["builtin_rts"] -> return Nothing _ -> do name <- parseS "name" >>= parsePackageName version <- parseS "version" >>= parseVersion ghcPkgId <- parseS "id" >>= parseGhcPkgId -- if a package has no modules, these won't exist let libDirKey = "library-dirs" libraries = parseM "hs-libraries" exposedModules = parseM "exposed-modules" exposed = parseM "exposed" license = case parseM "license" of [licenseText] -> C.simpleParse (T.unpack licenseText) _ -> Nothing depends <- mapMaybeM parseDepend $ concatMap T.words $ parseM "depends" let parseQuoted key = case mapM (P.parseOnly (argsParser NoEscaping)) val of Left{} -> throwM (Couldn'tParseField key val) Right dirs -> return (concat dirs) where val = parseM key libDirPaths <- parseQuoted libDirKey haddockInterfaces <- parseQuoted "haddock-interfaces" haddockHtml <- parseQuoted "haddock-html" return $ Just DumpPackage { dpGhcPkgId = ghcPkgId , dpPackageIdent = PackageIdentifier name version , dpLicense = license , dpLibDirs = libDirPaths , dpLibraries = T.words $ T.unwords libraries , dpHasExposedModules = not (null libraries || null exposedModules) , dpDepends = depends , dpHaddockInterfaces = haddockInterfaces , dpHaddockHtml = listToMaybe haddockHtml , dpProfiling = () , dpHaddock = () , dpSymbols = () , dpIsExposed = exposed == ["True"] } stripPrefixText :: Text -> Text -> Maybe Text stripPrefixText x y | x `T.isPrefixOf` y = Just $ T.drop (T.length x) y | otherwise = Nothing stripSuffixText :: Text -> Text -> Maybe Text stripSuffixText x y | x `T.isSuffixOf` y = Just $ T.take (T.length y - T.length x) y | otherwise = Nothing -- | A single line of input, not including line endings type Line = Text -- | Apply the given Sink to each section of output, broken by a single line containing --- eachSection :: Monad m => Sink Line m a -> Conduit Text m a eachSection inner = CL.map (T.filter (/= '\r')) =$= CT.lines =$= start where peekText = await >>= maybe (return Nothing) (\bs -> if T.null bs then peekText else leftover bs >> return (Just bs)) start = peekText >>= maybe (return ()) (const go) go = do x <- toConsumer $ takeWhileC (/= "---") =$= inner yield x CL.drop 1 start -- | Grab each key/value pair eachPair :: Monad m => (Text -> Sink Line m a) -> Conduit Line m a eachPair inner = start where start = await >>= maybe (return ()) start' start' bs1 = toConsumer (valSrc =$= inner key) >>= yield >> start where (key, bs2) = T.break (== ':') bs1 (spaces, bs3) = T.span (== ' ') $ T.drop 1 bs2 indent = T.length key + 1 + T.length spaces valSrc | T.null bs3 = noIndent | otherwise = yield bs3 >> loopIndent indent noIndent = do mx <- await case mx of Nothing -> return () Just bs -> do let (spaces, val) = T.span (== ' ') bs if T.length spaces == 0 then leftover val else do yield val loopIndent (T.length spaces) loopIndent i = loop where loop = await >>= maybe (return ()) go go bs | T.length spaces == i && T.all (== ' ') spaces = yield val >> loop | otherwise = leftover bs where (spaces, val) = T.splitAt i bs -- | General purpose utility takeWhileC :: Monad m => (a -> Bool) -> Conduit a m a takeWhileC f = loop where loop = await >>= maybe (return ()) go go x | f x = yield x >> loop | otherwise = leftover x stack-1.5.1/src/Stack/PackageIndex.hs0000644000000000000000000004153613135652051015536 0ustar0000000000000000{-# LANGUAGE BangPatterns #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ScopedTypeVariables #-} -- | Dealing with the 01-index file and all its cabal files. module Stack.PackageIndex ( updateAllIndices , getPackageCaches , getPackageCachesIO , getPackageVersions , getPackageVersionsIO , lookupPackageVersions ) where import qualified Codec.Archive.Tar as Tar import Control.Exception (Exception) import Control.Exception.Safe (tryIO) import Control.Monad (unless, when, liftM, void, guard) import Control.Monad.Catch (throwM) import qualified Control.Monad.Catch as C import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Logger (logDebug, logInfo, logWarn) import Control.Monad.Trans.Control import Crypto.Hash as Hash (hashlazy, Digest, SHA1) import Data.Aeson.Extended import qualified Data.ByteArray.Encoding as Mem (convertToBase, Base(Base16)) import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Lazy as L import Data.Conduit (($$), (=$), (.|), runConduitRes) import Data.Conduit.Binary (sinkHandle, sourceHandle, sourceFile, sinkFile) import Data.Conduit.Zlib (ungzip) import Data.Foldable (forM_) import Data.IORef import Data.Int (Int64) import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HashMap import Data.Map (Map) import qualified Data.Map.Strict as Map import Data.Monoid import Data.Set (Set) import qualified Data.Set as Set import Data.Store.Version import Data.Store.VersionTagged import Data.Text (Text) import qualified Data.Text as T import Data.Text.Unsafe (unsafeTail) import Data.Time (getCurrentTime) import Data.Traversable (forM) import Data.Typeable (Typeable) import qualified Hackage.Security.Client as HS import qualified Hackage.Security.Client.Repository.Cache as HS import qualified Hackage.Security.Client.Repository.Remote as HS import qualified Hackage.Security.Client.Repository.HttpLib.HttpClient as HS import qualified Hackage.Security.Util.Path as HS import qualified Hackage.Security.Util.Pretty as HS import Network.HTTP.Client.TLS (getGlobalManager) import Network.HTTP.Download import Network.URI (parseURI) import Path (toFilePath, parseAbsFile) import Path.IO import Prelude -- Fix AMP warning import Stack.Types.BuildPlan (GitSHA1 (..)) import Stack.Types.Config import Stack.Types.PackageIdentifier import Stack.Types.PackageIndex import Stack.Types.PackageName import Stack.Types.StackT import Stack.Types.StringError import Stack.Types.Version import qualified System.Directory as D import System.FilePath ((<.>)) import System.IO (IOMode (ReadMode, WriteMode), withBinaryFile) -- | Populate the package index caches and return them. populateCache :: (StackMiniM env m, HasConfig env) => PackageIndex -> m PackageCacheMap populateCache index = do requireIndex index -- This uses full on lazy I/O instead of ResourceT to provide some -- protections. Caveat emptor path <- configPackageIndex (indexName index) let loadPIS = do $logSticky "Populating index cache ..." lbs <- liftIO $ L.readFile $ Path.toFilePath path loop 0 (Map.empty, HashMap.empty) (Tar.read lbs) (pis, gitPIs) <- loadPIS `C.catch` \e -> do $logWarn $ "Exception encountered when parsing index tarball: " <> T.pack (show (e :: Tar.FormatError)) $logWarn "Automatically updating index and trying again" updateIndex index loadPIS when (indexRequireHashes index) $ forM_ (Map.toList pis) $ \(ident, pc) -> case pcDownload pc of Just _ -> return () Nothing -> throwM $ MissingRequiredHashes (indexName index) ident $logStickyDone "Populated index cache." return $ PackageCacheMap pis gitPIs where loop !blockNo (!m, !hm) (Tar.Next e es) = loop (blockNo + entrySizeInBlocks e) (goE blockNo m hm e) es loop _ (m, hm) Tar.Done = return (m, hm) loop _ _ (Tar.Fail e) = throwM e goE blockNo m hm e = case Tar.entryContent e of Tar.NormalFile lbs size -> case parseNameVersionSuffix $ Tar.entryPath e of Just (ident, ".cabal") -> addCabal lbs ident size Just (ident, ".json") -> (addJSON id ident lbs, hm) _ -> case parsePackageJSON $ Tar.entryPath e of Just ident -> (addJSON unHSPackageDownload ident lbs, hm) Nothing -> (m, hm) _ -> (m, hm) where addCabal lbs ident size = ( Map.insertWith (\_ pcOld -> pcNew { pcDownload = pcDownload pcOld }) ident pcNew m , HashMap.insert gitSHA1 offsetSize hm ) where pcNew = PackageCache { pcOffsetSize = offsetSize , pcDownload = Nothing } offsetSize = OffsetSize ((blockNo + 1) * 512) size -- Calculate the Git SHA1 of the contents. This uses the -- Git algorithm of prepending "blob \0" to the raw -- contents. We use this to be able to share the same SHA -- information between the Git and tarball backends. gitSHA1 = GitSHA1 $ Mem.convertToBase Mem.Base16 $ hashSHA1 $ L.fromChunks $ "blob " : S8.pack (show $ L.length lbs) : "\0" : L.toChunks lbs hashSHA1 :: L.ByteString -> Hash.Digest Hash.SHA1 hashSHA1 = Hash.hashlazy addJSON :: FromJSON a => (a -> PackageDownload) -> PackageIdentifier -> L.ByteString -> Map PackageIdentifier PackageCache addJSON unwrap ident lbs = case decode lbs of Nothing -> m Just (unwrap -> pd) -> Map.insertWith (\_ pc -> pc { pcDownload = Just pd }) ident PackageCache { pcOffsetSize = OffsetSize 0 0 , pcDownload = Just pd } m breakSlash x | T.null z = Nothing | otherwise = Just (y, unsafeTail z) where (y, z) = T.break (== '/') x parseNameVersion t1 = do (p', t3) <- breakSlash $ T.map (\c -> if c == '\\' then '/' else c) $ T.pack t1 p <- parsePackageName p' (v', t5) <- breakSlash t3 v <- parseVersion v' return (p', p, v, t5) parseNameVersionSuffix t1 = do (p', p, v, t5) <- parseNameVersion t1 let (t6, suffix) = T.break (== '.') t5 guard $ t6 == p' return (PackageIdentifier p v, suffix) parsePackageJSON t1 = do (_, p, v, t5) <- parseNameVersion t1 guard $ t5 == "package.json" return $ PackageIdentifier p v data PackageIndexException = GitNotAvailable IndexName | MissingRequiredHashes IndexName PackageIdentifier deriving Typeable instance Exception PackageIndexException instance Show PackageIndexException where show (GitNotAvailable name) = concat [ "Package index " , T.unpack $ indexNameText name , " only provides Git access, and you do not have" , " the git executable on your PATH" ] show (MissingRequiredHashes name ident) = concat [ "Package index " , T.unpack $ indexNameText name , " is configured to require package hashes, but no" , " hash is available for " , packageIdentifierString ident ] -- | Require that an index be present, updating if it isn't. requireIndex :: (StackMiniM env m, HasConfig env) => PackageIndex -> m () requireIndex index = do tarFile <- configPackageIndex $ indexName index exists <- doesFileExist tarFile unless exists $ updateIndex index -- | Update all of the package indices updateAllIndices :: (StackMiniM env m, HasConfig env) => m () updateAllIndices = do clearPackageCaches view packageIndicesL >>= mapM_ updateIndex -- | Update the index tarball updateIndex :: (StackMiniM env m, HasConfig env) => PackageIndex -> m () updateIndex index = do let name = indexName index url = indexLocation index $logSticky $ "Updating package index " <> indexNameText (indexName index) <> " (mirrored at " <> url <> ") ..." case indexType index of ITVanilla -> updateIndexHTTP name url ITHackageSecurity hs -> updateIndexHackageSecurity name url hs -- Copy to the 00-index.tar filename for backwards -- compatibility. First wipe out the cache file if present. tarFile <- configPackageIndex name oldTarFile <- configPackageIndexOld name oldCacheFile <- configPackageIndexCacheOld name ignoringAbsence (removeFile oldCacheFile) runConduitRes $ sourceFile (toFilePath tarFile) .| sinkFile (toFilePath oldTarFile) -- | Update the index tarball via HTTP updateIndexHTTP :: (StackMiniM env m, HasConfig env) => IndexName -> Text -- ^ url -> m () updateIndexHTTP indexName' url = do req <- parseRequest $ T.unpack url $logInfo ("Downloading package index from " <> url) gz <- configPackageIndexGz indexName' tar <- configPackageIndex indexName' wasDownloaded <- redownload req gz toUnpack <- if wasDownloaded then return True else not `liftM` doesFileExist tar when toUnpack $ do let tmp = toFilePath tar <.> "tmp" tmpPath <- parseAbsFile tmp deleteCache indexName' liftIO $ do withBinaryFile (toFilePath gz) ReadMode $ \input -> withBinaryFile tmp WriteMode $ \output -> sourceHandle input $$ ungzip =$ sinkHandle output renameFile tmpPath tar -- | Update the index tarball via Hackage Security updateIndexHackageSecurity :: (StackMiniM env m, HasConfig env) => IndexName -> Text -- ^ base URL -> HackageSecurity -> m () updateIndexHackageSecurity indexName' url (HackageSecurity keyIds threshold) = do baseURI <- case parseURI $ T.unpack url of Nothing -> errorString $ "Invalid Hackage Security base URL: " ++ T.unpack url Just x -> return x manager <- liftIO getGlobalManager root <- configPackageIndexRoot indexName' logTUF <- embed_ ($logInfo . T.pack . HS.pretty) let withRepo = HS.withRepository (HS.makeHttpLib manager) [baseURI] HS.defaultRepoOpts HS.Cache { HS.cacheRoot = HS.fromAbsoluteFilePath $ toFilePath root , HS.cacheLayout = HS.cabalCacheLayout -- Have Hackage Security write to a temporary file -- to avoid invalidating the cache... continued -- below at case didUpdate { HS.cacheLayoutIndexTar = HS.rootPath $ HS.fragment "01-index.tar-tmp" } } HS.hackageRepoLayout HS.hackageIndexLayout logTUF didUpdate <- liftIO $ withRepo $ \repo -> HS.uncheckClientErrors $ do needBootstrap <- HS.requiresBootstrap repo when needBootstrap $ do HS.bootstrap repo (map (HS.KeyId . T.unpack) keyIds) (HS.KeyThreshold (fromIntegral threshold)) now <- getCurrentTime HS.checkForUpdates repo (Just now) case didUpdate of HS.HasUpdates -> do -- The index actually updated. Delete the old cache, and -- then move the temporary unpacked file to its real -- location tar <- configPackageIndex indexName' deleteCache indexName' liftIO $ D.renameFile (toFilePath tar ++ "-tmp") (toFilePath tar) $logInfo "Updated package list downloaded" HS.NoUpdates -> $logInfo "No updates to your package list were found" -- | Delete the package index cache deleteCache :: (StackMiniM env m, HasConfig env) => IndexName -> m () deleteCache indexName' = do fp <- configPackageIndexCache indexName' eres <- liftIO $ tryIO $ removeFile fp case eres of Left e -> $logDebug $ "Could not delete cache: " <> T.pack (show e) Right () -> $logDebug $ "Deleted index cache at " <> T.pack (toFilePath fp) -- | Lookup a package's versions from 'IO'. getPackageVersionsIO :: (StackMiniM env m, HasConfig env) => m (PackageName -> IO (Set Version)) getPackageVersionsIO = do getCaches <- getPackageCachesIO return $ \name -> fmap (lookupPackageVersions name . fst) getCaches -- | Get the known versions for a given package from the package caches. -- -- See 'getPackageCaches' for performance notes. getPackageVersions :: (StackMiniM env m, HasConfig env) => PackageName -> m (Set Version) getPackageVersions pkgName = fmap (lookupPackageVersions pkgName . fst) getPackageCaches lookupPackageVersions :: PackageName -> Map PackageIdentifier a -> Set Version lookupPackageVersions pkgName pkgCaches = Set.fromList [v | PackageIdentifier n v <- Map.keys pkgCaches, n == pkgName] -- | Access the package caches from 'IO'. -- -- FIXME: This is a temporary solution until a better solution -- to access the package caches from Stack.Build.ConstructPlan -- has been found. getPackageCachesIO :: (StackMiniM env m, HasConfig env) => m (IO ( Map PackageIdentifier (PackageIndex, PackageCache) , HashMap GitSHA1 (PackageIndex, OffsetSize))) getPackageCachesIO = toIO getPackageCaches where toIO :: (MonadIO m, MonadBaseControl IO m) => m a -> m (IO a) toIO m = do runInBase <- liftBaseWith $ \run -> return (void . run) return $ do i <- newIORef (error "Impossible evaluation in toIO") runInBase $ do x <- m liftIO $ writeIORef i x readIORef i -- | Load the package caches, or create the caches if necessary. -- -- This has two levels of caching: in memory, and the on-disk cache. So, -- feel free to call this function multiple times. getPackageCaches :: (StackMiniM env m, HasConfig env) => m ( Map PackageIdentifier (PackageIndex, PackageCache) , HashMap GitSHA1 (PackageIndex, OffsetSize) ) getPackageCaches = do config <- view configL mcached <- liftIO $ readIORef (configPackageCaches config) case mcached of Just cached -> return cached Nothing -> do result <- liftM mconcat $ forM (configPackageIndices config) $ \index -> do fp <- configPackageIndexCache (indexName index) PackageCacheMap pis' gitPIs <- $(versionedDecodeOrLoad (storeVersionConfig "pkg-v2" "WlAvAaRXlIMkjSmg5G3dD16UpT8=" :: VersionConfig PackageCacheMap)) fp (populateCache index) return (fmap (index,) pis', fmap (index,) gitPIs) liftIO $ writeIORef (configPackageCaches config) (Just result) return result -- | Clear the in-memory hackage index cache. This is needed when the -- hackage index is updated. clearPackageCaches :: (StackMiniM env m, HasConfig env) => m () clearPackageCaches = do cacheRef <- view packageCachesL liftIO $ writeIORef cacheRef Nothing --------------- Lifted from cabal-install, Distribution.Client.Tar: -- | Return the number of blocks in an entry. entrySizeInBlocks :: Tar.Entry -> Int64 entrySizeInBlocks entry = 1 + case Tar.entryContent entry of Tar.NormalFile _ size -> bytesToBlocks size Tar.OtherEntryType _ _ size -> bytesToBlocks size _ -> 0 where bytesToBlocks s = 1 + ((fromIntegral s - 1) `div` 512) stack-1.5.1/src/Stack/Path.hs0000644000000000000000000002054013135652051014077 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -- | Handy path information. module Stack.Path ( path , pathParser ) where import Control.Monad.Catch import Control.Monad.Logger import Control.Monad.Reader import Control.Monad.Trans.Control import Data.List (intercalate) import Data.Maybe.Extra import Data.Monoid import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as T import Lens.Micro (lens) import qualified Options.Applicative as OA import Path import Path.Extra import Stack.Constants import Stack.GhcPkg as GhcPkg import Stack.Types.Config import qualified System.FilePath as FP import System.IO (stderr) import System.Process.Read (EnvOverride(eoPath)) -- | Print out useful path information in a human-readable format (and -- support others later). path :: (MonadIO m, MonadBaseControl IO m, MonadReader env m, HasEnvConfig env, MonadCatch m, MonadLogger m) => [Text] -> m () path keys = do -- We must use a BuildConfig from an EnvConfig to ensure that it contains the -- full environment info including GHC paths etc. bc <- view $ envConfigL.buildConfigL -- This is the modified 'bin-path', -- including the local GHC or MSYS if not configured to operate on -- global GHC. -- It was set up in 'withBuildConfigAndLock -> withBuildConfigExt -> setupEnv'. -- So it's not the *minimal* override path. menv <- getMinimalEnvOverride snap <- packageDatabaseDeps plocal <- packageDatabaseLocal extra <- packageDatabaseExtra whichCompiler <- view $ actualCompilerVersionL.whichCompilerL global <- GhcPkg.getGlobalDB menv whichCompiler snaproot <- installationRootDeps localroot <- installationRootLocal distDir <- distRelativeDir hpcDir <- hpcReportDir compiler <- getCompilerPath whichCompiler let deprecated = filter ((`elem` keys) . fst) deprecatedPathKeys liftIO $ forM_ deprecated $ \(oldOption, newOption) -> T.hPutStrLn stderr $ T.unlines [ "" , "'--" <> oldOption <> "' will be removed in a future release." , "Please use '--" <> newOption <> "' instead." , "" ] forM_ -- filter the chosen paths in flags (keys), -- or show all of them if no specific paths chosen. (filter (\(_,key,_) -> (null keys && key /= T.pack deprecatedStackRootOptionName) || elem key keys) paths) (\(_,key,path') -> liftIO $ T.putStrLn -- If a single path type is requested, output it directly. -- Otherwise, name all the paths. ((if length keys == 1 then "" else key <> ": ") <> path' (PathInfo bc menv snap plocal global snaproot localroot distDir hpcDir extra compiler))) pathParser :: OA.Parser [Text] pathParser = mapMaybeA (\(desc,name,_) -> OA.flag Nothing (Just name) (OA.long (T.unpack name) <> OA.help desc)) paths -- | Passed to all the path printers as a source of info. data PathInfo = PathInfo { piBuildConfig :: BuildConfig , piEnvOverride :: EnvOverride , piSnapDb :: Path Abs Dir , piLocalDb :: Path Abs Dir , piGlobalDb :: Path Abs Dir , piSnapRoot :: Path Abs Dir , piLocalRoot :: Path Abs Dir , piDistDir :: Path Rel Dir , piHpcDir :: Path Abs Dir , piExtraDbs :: [Path Abs Dir] , piCompiler :: Path Abs File } instance HasPlatform PathInfo instance HasConfig PathInfo instance HasBuildConfig PathInfo where buildConfigL = lens piBuildConfig (\x y -> x { piBuildConfig = y }) . buildConfigL -- | The paths of interest to a user. The first tuple string is used -- for a description that the optparse flag uses, and the second -- string as a machine-readable key and also for @--foo@ flags. The user -- can choose a specific path to list like @--stack-root@. But -- really it's mainly for the documentation aspect. -- -- When printing output we generate @PathInfo@ and pass it to the -- function to generate an appropriate string. Trailing slashes are -- removed, see #506 paths :: [(String, Text, PathInfo -> Text)] paths = [ ( "Global stack root directory" , T.pack stackRootOptionName , view $ stackRootL.to toFilePathNoTrailingSep.to T.pack) , ( "Project root (derived from stack.yaml file)" , "project-root" , view $ projectRootL.to toFilePathNoTrailingSep.to T.pack) , ( "Configuration location (where the stack.yaml file is)" , "config-location" , view $ stackYamlL.to toFilePath.to T.pack) , ( "PATH environment variable" , "bin-path" , T.pack . intercalate [FP.searchPathSeparator] . eoPath . piEnvOverride ) , ( "Install location for GHC and other core tools" , "programs" , view $ configL.to configLocalPrograms.to toFilePathNoTrailingSep.to T.pack) , ( "Compiler binary (e.g. ghc)" , "compiler-exe" , T.pack . toFilePath . piCompiler ) , ( "Directory containing the compiler binary (e.g. ghc)" , "compiler-bin" , T.pack . toFilePathNoTrailingSep . parent . piCompiler ) , ( "Local bin dir where stack installs executables (e.g. ~/.local/bin)" , "local-bin" , view $ configL.to configLocalBin.to toFilePathNoTrailingSep.to T.pack) , ( "Extra include directories" , "extra-include-dirs" , T.intercalate ", " . map T.pack . Set.elems . configExtraIncludeDirs . view configL ) , ( "Extra library directories" , "extra-library-dirs" , T.intercalate ", " . map T.pack . Set.elems . configExtraLibDirs . view configL ) , ( "Snapshot package database" , "snapshot-pkg-db" , T.pack . toFilePathNoTrailingSep . piSnapDb ) , ( "Local project package database" , "local-pkg-db" , T.pack . toFilePathNoTrailingSep . piLocalDb ) , ( "Global package database" , "global-pkg-db" , T.pack . toFilePathNoTrailingSep . piGlobalDb ) , ( "GHC_PACKAGE_PATH environment variable" , "ghc-package-path" , \pi' -> mkGhcPackagePath True (piLocalDb pi') (piSnapDb pi') (piExtraDbs pi') (piGlobalDb pi')) , ( "Snapshot installation root" , "snapshot-install-root" , T.pack . toFilePathNoTrailingSep . piSnapRoot ) , ( "Local project installation root" , "local-install-root" , T.pack . toFilePathNoTrailingSep . piLocalRoot ) , ( "Snapshot documentation root" , "snapshot-doc-root" , \pi' -> T.pack (toFilePathNoTrailingSep (piSnapRoot pi' docDirSuffix))) , ( "Local project documentation root" , "local-doc-root" , \pi' -> T.pack (toFilePathNoTrailingSep (piLocalRoot pi' docDirSuffix))) , ( "Dist work directory, relative to package directory" , "dist-dir" , T.pack . toFilePathNoTrailingSep . piDistDir ) , ( "Where HPC reports and tix files are stored" , "local-hpc-root" , T.pack . toFilePathNoTrailingSep . piHpcDir ) , ( "DEPRECATED: Use '--local-bin' instead" , "local-bin-path" , T.pack . toFilePathNoTrailingSep . configLocalBin . view configL ) , ( "DEPRECATED: Use '--programs' instead" , "ghc-paths" , T.pack . toFilePathNoTrailingSep . configLocalPrograms . view configL ) , ( "DEPRECATED: Use '--" <> stackRootOptionName <> "' instead" , T.pack deprecatedStackRootOptionName , T.pack . toFilePathNoTrailingSep . view stackRootL ) ] deprecatedPathKeys :: [(Text, Text)] deprecatedPathKeys = [ (T.pack deprecatedStackRootOptionName, T.pack stackRootOptionName) , ("ghc-paths", "programs") , ("local-bin-path", "local-bin") ] stack-1.5.1/src/Stack/PrettyPrint.hs0000644000000000000000000001151113135652051015505 0ustar0000000000000000{-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} module Stack.PrettyPrint ( -- * Pretty printing functions displayPlain, displayWithColor -- * Logging based on pretty-print typeclass , prettyDebug, prettyInfo, prettyWarn, prettyError , debugBracket -- * Color utils -- | These are preferred to colors directly, so that we can -- encourage consistency of color meanings. , errorRed, goodGreen, shellMagenta , displayTargetPkgId, displayCurrentPkgId, displayCurrentPkgName, displayErrorPkgId , displayMilliseconds -- * Formatting utils , bulletedList -- * Re-exports from "Text.PrettyPrint.Leijen.Extended" , Display(..), AnsiDoc, AnsiAnn(..), HasAnsiAnn(..), Doc , nest, line, linebreak, group, softline, softbreak , align, hang, indent, encloseSep , (<+>) , hsep, vsep, fillSep, sep, hcat, vcat, fillCat, cat, punctuate , fill, fillBreak , enclose, squotes, dquotes, parens, angles, braces, brackets ) where import Control.Exception.Lifted import Control.Monad.Logger import Control.Monad.Reader import Data.List (intersperse) import Data.Monoid import Data.String (fromString) import qualified Data.Text as T import Language.Haskell.TH import Path import Stack.Types.Internal import Stack.Types.Package import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.Version import qualified System.Clock as Clock import Text.PrettyPrint.Leijen.Extended displayWithColor :: (HasLogOptions env, MonadReader env m, Display a, HasAnsiAnn (Ann a)) => a -> m T.Text displayWithColor x = do useAnsi <- liftM logUseColor $ view logOptionsL return $ if useAnsi then displayAnsi x else displayPlain x -- TODO: switch to using implicit callstacks once 7.8 support is dropped prettyDebug :: Q Exp prettyDebug = do loc <- location [e| monadLoggerLog loc "" LevelDebug <=< displayWithColor |] prettyInfo :: Q Exp prettyInfo = do loc <- location [e| monadLoggerLog loc "" LevelInfo <=< displayWithColor |] prettyWarn :: Q Exp prettyWarn = do loc <- location [e| monadLoggerLog loc "" LevelWarn <=< displayWithColor . (line <>) . (warningYellow "Warning:" <+>) |] prettyError :: Q Exp prettyError = do loc <- location [e| monadLoggerLog loc "" LevelError <=< displayWithColor . (line <>) . (errorRed "Error:" <+>) |] debugBracket :: Q Exp debugBracket = do loc <- location [e| \msg f -> do let output = monadLoggerLog loc "" LevelDebug <=< displayWithColor output $ "Start: " <> msg start <- liftIO $ Clock.getTime Clock.Monotonic x <- f `catch` \ex -> do end <- liftIO $ Clock.getTime Clock.Monotonic let diff = Clock.diffTimeSpec start end output $ "Finished with exception in" <+> displayMilliseconds diff <> ":" <+> msg <> line <> "Exception thrown: " <> fromString (show ex) throw (ex :: SomeException) end <- liftIO $ Clock.getTime Clock.Monotonic let diff = Clock.diffTimeSpec start end output $ "Finished in" <+> displayMilliseconds diff <> ":" <+> msg return x |] errorRed :: AnsiDoc -> AnsiDoc errorRed = dullred warningYellow :: AnsiDoc -> AnsiDoc warningYellow = yellow goodGreen :: AnsiDoc -> AnsiDoc goodGreen = green shellMagenta :: AnsiDoc -> AnsiDoc shellMagenta = magenta displayTargetPkgId :: PackageIdentifier -> AnsiDoc displayTargetPkgId = cyan . display displayCurrentPkgId :: PackageIdentifier -> AnsiDoc displayCurrentPkgId = yellow . display displayCurrentPkgName :: PackageName -> AnsiDoc displayCurrentPkgName = yellow . display displayErrorPkgId :: PackageIdentifier -> AnsiDoc displayErrorPkgId = errorRed . display instance Display PackageName where display = fromString . packageNameString instance Display PackageIdentifier where display = fromString . packageIdentifierString instance Display Version where display = fromString . versionString instance Display (Path b File) where display = bold . white . fromString . toFilePath instance Display (Path b Dir) where display = bold . blue . fromString . toFilePath instance Display (PackageName, NamedComponent) where display = cyan . fromString . T.unpack . renderPkgComponent -- Display milliseconds. displayMilliseconds :: Clock.TimeSpec -> AnsiDoc displayMilliseconds t = goodGreen $ (fromString . show . (`div` 10^(6 :: Int)) . Clock.toNanoSecs) t <> "ms" bulletedList :: [AnsiDoc] -> AnsiDoc bulletedList = mconcat . intersperse line . map ("*" <+>) stack-1.5.1/src/Stack/Runners.hs0000644000000000000000000002200213135652051014632 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} -- | Utilities for running stack commands. module Stack.Runners ( withGlobalConfigAndLock , withConfigAndLock , withMiniConfigAndLock , withBuildConfigAndLock , withBuildConfigAndLockNoDocker , withBuildConfig , withBuildConfigExt , loadConfigWithOpts , loadCompilerVersion , withUserFileLock , munlockFile ) where import Control.Monad hiding (forM) import Control.Monad.Logger import Control.Exception.Lifted as EL import Control.Monad.IO.Class import Control.Monad.Trans.Control import Data.IORef import Data.Traversable import Path import Path.IO import Stack.Config import qualified Stack.Docker as Docker import qualified Stack.Nix as Nix import Stack.Setup import Stack.Types.Compiler (CompilerVersion) import Stack.Types.Config import Stack.Types.StackT import System.Environment (getEnvironment) import System.IO import System.FileLock loadCompilerVersion :: GlobalOpts -> LoadConfig (StackT () IO) -> IO CompilerVersion loadCompilerVersion go lc = do bconfig <- runStackTGlobal () go $ lcLoadBuildConfig lc (globalCompiler go) return $ view wantedCompilerVersionL bconfig -- | Enforce mutual exclusion of every action running via this -- function, on this path, on this users account. -- -- A lock file is created inside the given directory. Currently, -- stack uses locks per-snapshot. In the future, stack may refine -- this to an even more fine-grain locking approach. -- withUserFileLock :: (MonadBaseControl IO m, MonadIO m) => GlobalOpts -> Path Abs Dir -> (Maybe FileLock -> m a) -> m a withUserFileLock go@GlobalOpts{} dir act = do env <- liftIO getEnvironment let toLock = lookup "STACK_LOCK" env == Just "true" if toLock then do let lockfile = $(mkRelFile "lockfile") let pth = dir lockfile ensureDir dir -- Just in case of asynchronous exceptions, we need to be careful -- when using tryLockFile here: EL.bracket (liftIO $ tryLockFile (toFilePath pth) Exclusive) (maybe (return ()) (liftIO . unlockFile)) (\fstTry -> case fstTry of Just lk -> EL.finally (act $ Just lk) (liftIO $ unlockFile lk) Nothing -> do let chatter = globalLogLevel go /= LevelOther "silent" when chatter $ liftIO $ hPutStrLn stderr $ "Failed to grab lock ("++show pth++ "); other stack instance running. Waiting..." EL.bracket (liftIO $ lockFile (toFilePath pth) Exclusive) (liftIO . unlockFile) (\lk -> do when chatter $ liftIO $ hPutStrLn stderr "Lock acquired, proceeding." act $ Just lk)) else act Nothing withConfigAndLock :: GlobalOpts -> StackT Config IO () -> IO () withConfigAndLock go@GlobalOpts{..} inner = do lc <- loadConfigWithOpts go withUserFileLock go (configStackRoot $ lcConfig lc) $ \lk -> runStackTGlobal (lcConfig lc) go $ Docker.reexecWithOptionalContainer (lcProjectRoot lc) Nothing (runStackTGlobal (lcConfig lc) go inner) Nothing (Just $ munlockFile lk) -- | Loads global config, ignoring any configuration which would be -- loaded due to $PWD. withGlobalConfigAndLock :: GlobalOpts -> StackT Config IO () -> IO () withGlobalConfigAndLock go@GlobalOpts{..} inner = do lc <- runStackTGlobal () go $ loadConfigMaybeProject globalConfigMonoid Nothing LCSNoProject withUserFileLock go (configStackRoot $ lcConfig lc) $ \_lk -> runStackTGlobal (lcConfig lc) go inner -- For now the non-locking version just unlocks immediately. -- That is, there's still a serialization point. withBuildConfig :: GlobalOpts -> StackT EnvConfig IO () -> IO () withBuildConfig go inner = withBuildConfigAndLock go (\lk -> do munlockFile lk inner) withBuildConfigAndLock :: GlobalOpts -> (Maybe FileLock -> StackT EnvConfig IO ()) -> IO () withBuildConfigAndLock go inner = withBuildConfigExt False go Nothing inner Nothing withBuildConfigAndLockNoDocker :: GlobalOpts -> (Maybe FileLock -> StackT EnvConfig IO ()) -> IO () withBuildConfigAndLockNoDocker go inner = withBuildConfigExt True go Nothing inner Nothing withBuildConfigExt :: Bool -> GlobalOpts -> Maybe (StackT Config IO ()) -- ^ Action to perform before the build. This will be run on the host -- OS even if Docker is enabled for builds. The build config is not -- available in this action, since that would require build tools to be -- installed on the host OS. -> (Maybe FileLock -> StackT EnvConfig IO ()) -- ^ Action that uses the build config. If Docker is enabled for builds, -- this will be run in a Docker container. -> Maybe (StackT Config IO ()) -- ^ Action to perform after the build. This will be run on the host -- OS even if Docker is enabled for builds. The build config is not -- available in this action, since that would require build tools to be -- installed on the host OS. -> IO () withBuildConfigExt skipDocker go@GlobalOpts{..} mbefore inner mafter = do lc <- loadConfigWithOpts go withUserFileLock go (configStackRoot $ lcConfig lc) $ \lk0 -> do -- A local bit of state for communication between callbacks: curLk <- newIORef lk0 let inner' lk = -- Locking policy: This is only used for build commands, which -- only need to lock the snapshot, not the global lock. We -- trade in the lock here. do dir <- installationRootDeps -- Hand-over-hand locking: withUserFileLock go dir $ \lk2 -> do liftIO $ writeIORef curLk lk2 liftIO $ munlockFile lk $logDebug "Starting to execute command inside EnvConfig" inner lk2 let inner'' lk = do bconfig <- runStackTGlobal () go $ lcLoadBuildConfig lc globalCompiler envConfig <- runStackTGlobal bconfig go (setupEnv Nothing) runStackTGlobal envConfig go (inner' lk) let getCompilerVersion = loadCompilerVersion go lc if skipDocker then runStackTGlobal (lcConfig lc) go $ do forM_ mbefore id liftIO $ inner'' lk0 forM_ mafter id else runStackTGlobal (lcConfig lc) go $ Docker.reexecWithOptionalContainer (lcProjectRoot lc) mbefore (runStackTGlobal (lcConfig lc) go $ Nix.reexecWithOptionalShell (lcProjectRoot lc) getCompilerVersion (inner'' lk0)) mafter (Just $ liftIO $ do lk' <- readIORef curLk munlockFile lk') -- | Load the configuration. Convenience function used -- throughout this module. loadConfigWithOpts :: GlobalOpts -> IO (LoadConfig (StackT () IO)) loadConfigWithOpts go@GlobalOpts{..} = do mstackYaml <- forM globalStackYaml resolveFile' runStackTGlobal () go $ do lc <- loadConfig globalConfigMonoid globalResolver mstackYaml -- If we have been relaunched in a Docker container, perform in-container initialization -- (switch UID, etc.). We do this after first loading the configuration since it must -- happen ASAP but needs a configuration. case globalDockerEntrypoint of Just de -> Docker.entrypoint (lcConfig lc) de Nothing -> return () return lc withMiniConfigAndLock :: GlobalOpts -> StackT MiniConfig IO () -> IO () withMiniConfigAndLock go@GlobalOpts{..} inner = do miniConfig <- runStackTGlobal () go $ (loadMiniConfig . lcConfig) <$> loadConfigMaybeProject globalConfigMonoid globalResolver LCSNoProject runStackTGlobal miniConfig go inner -- | Unlock a lock file, if the value is Just munlockFile :: MonadIO m => Maybe FileLock -> m () munlockFile Nothing = return () munlockFile (Just lk) = liftIO $ unlockFile lk stack-1.5.1/src/Stack/Script.hs0000644000000000000000000003021613135652051014450 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Stack.Script ( scriptCmd ) where import Control.Exception (assert) import Control.Exception.Safe (throwM) import Control.Monad (unless, forM) import Control.Monad.IO.Class (liftIO) import Control.Monad.Logger import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as S8 import qualified Data.Conduit.List as CL import Data.Foldable (fold) import Data.List.Split (splitWhen) import qualified Data.Map.Strict as Map import Data.Maybe (fromMaybe, mapMaybe) import Data.Monoid import Data.Set (Set) import qualified Data.Set as Set import Data.Store.VersionTagged (versionedDecodeOrLoad) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Path import Path.IO import qualified Stack.Build import Stack.BuildPlan (loadBuildPlan) import Stack.Exec import Stack.GhcPkg (ghcPkgExeName) import Stack.Options.ScriptParser import Stack.Runners import Stack.Types.BuildPlan import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.PackageName import Stack.Types.Resolver import Stack.Types.StackT import Stack.Types.StringError import System.FilePath (dropExtension, replaceExtension) import System.Process.Read -- | Run a Stack Script scriptCmd :: ScriptOpts -> GlobalOpts -> IO () scriptCmd opts go' = do let go = go' { globalConfigMonoid = (globalConfigMonoid go') { configMonoidInstallGHC = First $ Just True } , globalStackYaml = SYLNoConfig } withBuildConfigAndLock go $ \lk -> do -- Some warnings in case the user somehow tries to set a -- stack.yaml location. Note that in this functions we use -- logError instead of logWarn because, when using the -- interpreter mode, only error messages are shown. See: -- https://github.com/commercialhaskell/stack/issues/3007 case globalStackYaml go' of SYLOverride fp -> $logError $ T.pack $ "Ignoring override stack.yaml file for script command: " ++ fp SYLDefault -> return () SYLNoConfig -> assert False (return ()) config <- view configL menv <- liftIO $ configEnvOverride config defaultEnvSettings wc <- view $ actualCompilerVersionL.whichCompilerL (targetsSet, coresSet) <- case soPackages opts of [] -> -- Using the import parser getPackagesFromImports (globalResolver go) (soFile opts) packages -> do let targets = concatMap wordsComma packages targets' <- mapM parsePackageNameFromString targets return (Set.fromList targets', Set.empty) unless (Set.null targetsSet) $ do -- Optimization: use the relatively cheap ghc-pkg list -- --simple-output to check which packages are installed -- already. If all needed packages are available, we can -- skip the (rather expensive) build call below. bss <- sinkProcessStdout Nothing menv (ghcPkgExeName wc) ["list", "--simple-output"] CL.consume -- FIXME use the package info from envConfigPackages, or is that crazy? let installed = Set.fromList $ map toPackageName $ words $ S8.unpack $ S8.concat bss if Set.null $ Set.difference (Set.map packageNameString targetsSet) installed then $logDebug "All packages already installed" else do $logDebug "Missing packages, performing installation" Stack.Build.build (const $ return ()) lk defaultBuildOptsCLI { boptsCLITargets = map packageNameText $ Set.toList targetsSet } let ghcArgs = concat [ ["-hide-all-packages"] , map (\x -> "-package" ++ x) $ Set.toList $ Set.insert "base" $ Set.map packageNameString (Set.union targetsSet coresSet) , case soCompile opts of SEInterpret -> [] SECompile -> [] SEOptimize -> ["-O2"] ] munlockFile lk -- Unlock before transferring control away. case soCompile opts of SEInterpret -> exec menv ("run" ++ compilerExeName wc) (ghcArgs ++ soFile opts : soArgs opts) _ -> do file <- resolveFile' $ soFile opts let dir = parent file -- use sinkProcessStdout to ensure a ProcessFailed -- exception is generated for better error messages sinkProcessStdout (Just dir) menv (compilerExeName wc) (ghcArgs ++ [soFile opts]) CL.sinkNull exec menv (toExeName $ toFilePath file) (soArgs opts) where toPackageName = reverse . drop 1 . dropWhile (/= '-') . reverse -- Like words, but splits on both commas and spaces wordsComma = splitWhen (\c -> c == ' ' || c == ',') toExeName fp = if isWindows then replaceExtension fp "exe" else dropExtension fp isWindows :: Bool #ifdef WINDOWS isWindows = True #else isWindows = False #endif -- | Returns packages that need to be installed, and all of the core -- packages. Reason for the core packages: -- Ideally we'd have the list of modules per core package listed in -- the build plan, but that doesn't exist yet. Next best would be to -- list the modules available at runtime, but that gets tricky with when we install GHC. Instead, we'll just list all core packages getPackagesFromImports :: Maybe AbstractResolver -> FilePath -> StackT EnvConfig IO (Set PackageName, Set PackageName) getPackagesFromImports Nothing _ = throwM NoResolverWhenUsingNoLocalConfig getPackagesFromImports (Just (ARResolver (ResolverSnapshot name))) scriptFP = do (pns1, mns) <- liftIO $ parseImports <$> S8.readFile scriptFP mi <- loadModuleInfo name pns2 <- if Set.null mns then return Set.empty else do pns <- forM (Set.toList mns) $ \mn -> case Map.lookup mn $ miModules mi of Just pns -> case Set.toList pns of [] -> assert False $ return Set.empty [pn] -> return $ Set.singleton pn pns' -> throwString $ concat [ "Module " , S8.unpack $ unModuleName mn , " appears in multiple packages: " , unwords $ map packageNameString pns' ] Nothing -> return Set.empty return $ Set.unions pns `Set.difference` blacklist return (Set.union pns1 pns2, modifyForWindows $ miCorePackages mi) where modifyForWindows | isWindows = Set.insert $(mkPackageName "Win32") . Set.delete $(mkPackageName "unix") | otherwise = id getPackagesFromImports (Just (ARResolver (ResolverCompiler _))) _ = return (Set.empty, Set.empty) getPackagesFromImports (Just aresolver) _ = throwM $ InvalidResolverForNoLocalConfig $ show aresolver -- | The Stackage project introduced the concept of hidden packages, -- to deal with conflicting module names. However, this is a -- relatively recent addition (at time of writing). See: -- http://www.snoyman.com/blog/2017/01/conflicting-module-names. To -- kick this thing off a bit better, we're included a blacklist of -- packages that should never be auto-parsed in. blacklist :: Set PackageName blacklist = Set.fromList [ $(mkPackageName "async-dejafu") , $(mkPackageName "monads-tf") , $(mkPackageName "crypto-api") , $(mkPackageName "fay-base") , $(mkPackageName "hashmap") , $(mkPackageName "hxt-unicode") , $(mkPackageName "hledger-web") , $(mkPackageName "plot-gtk3") , $(mkPackageName "gtk3") , $(mkPackageName "regex-pcre-builtin") , $(mkPackageName "regex-compat-tdfa") , $(mkPackageName "log") , $(mkPackageName "zip") , $(mkPackageName "monad-extras") , $(mkPackageName "control-monad-free") , $(mkPackageName "prompt") , $(mkPackageName "kawhi") , $(mkPackageName "language-c") , $(mkPackageName "gl") , $(mkPackageName "svg-tree") , $(mkPackageName "Glob") , $(mkPackageName "nanospec") , $(mkPackageName "HTF") , $(mkPackageName "courier") , $(mkPackageName "newtype-generics") , $(mkPackageName "objective") , $(mkPackageName "binary-ieee754") , $(mkPackageName "rerebase") , $(mkPackageName "cipher-aes") , $(mkPackageName "cipher-blowfish") , $(mkPackageName "cipher-camellia") , $(mkPackageName "cipher-des") , $(mkPackageName "cipher-rc4") , $(mkPackageName "crypto-cipher-types") , $(mkPackageName "crypto-numbers") , $(mkPackageName "crypto-pubkey") , $(mkPackageName "crypto-random") , $(mkPackageName "cryptohash") , $(mkPackageName "cryptohash-conduit") , $(mkPackageName "cryptohash-md5") , $(mkPackageName "cryptohash-sha1") , $(mkPackageName "cryptohash-sha256") ] toModuleInfo :: BuildPlan -> ModuleInfo toModuleInfo bp = ModuleInfo { miCorePackages = Map.keysSet $ siCorePackages $ bpSystemInfo bp , miModules = Map.unionsWith Set.union $ map ((\(pn, mns) -> Map.fromList $ map (\mn -> (ModuleName $ encodeUtf8 mn, Set.singleton pn)) $ Set.toList mns) . fmap (sdModules . ppDesc)) $ filter (\(pn, pp) -> not (pcHide $ ppConstraints pp) && pn `Set.notMember` blacklist) $ Map.toList (bpPackages bp) } -- | Where to store module info caches moduleInfoCache :: SnapName -> StackT EnvConfig IO (Path Abs File) moduleInfoCache name = do root <- view stackRootL platform <- platformGhcVerOnlyRelDir name' <- parseRelDir $ T.unpack $ renderSnapName name -- These probably can't vary at all based on platform, even in the -- future, so it's safe to call this unnecessarily paranoid. return (root $(mkRelDir "script") name' platform $(mkRelFile "module-info.cache")) loadModuleInfo :: SnapName -> StackT EnvConfig IO ModuleInfo loadModuleInfo name = do path <- moduleInfoCache name $(versionedDecodeOrLoad moduleInfoVC) path $ toModuleInfo <$> loadBuildPlan name parseImports :: ByteString -> (Set PackageName, Set ModuleName) parseImports = fold . mapMaybe (parseLine . stripCR) . S8.lines where -- Remove any carriage return character present at the end, to -- support Windows-style line endings (CRLF) stripCR bs | S8.null bs = bs | S8.last bs == '\r' = S8.init bs | otherwise = bs stripPrefix x y | x `S8.isPrefixOf` y = Just $ S8.drop (S8.length x) y | otherwise = Nothing parseLine bs0 = do bs1 <- stripPrefix "import " bs0 let bs2 = S8.dropWhile (== ' ') bs1 bs3 = fromMaybe bs2 $ stripPrefix "qualified " bs2 case stripPrefix "\"" bs3 of Just bs4 -> do pn <- parsePackageNameFromString $ S8.unpack $ S8.takeWhile (/= '"') bs4 Just (Set.singleton pn, Set.empty) Nothing -> Just ( Set.empty , Set.singleton $ ModuleName $ S8.takeWhile (\c -> c /= ' ' && c /= '(') bs3 ) stack-1.5.1/src/Stack/SDist.hs0000644000000000000000000005010213140560217014224 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE TypeFamilies #-} -- Create a source distribution tarball module Stack.SDist ( getSDistTarball , checkSDistTarball , checkSDistTarball' , SDistOpts (..) ) where import qualified Codec.Archive.Tar as Tar import qualified Codec.Archive.Tar.Entry as Tar import qualified Codec.Compression.GZip as GZip import Control.Applicative import Control.Concurrent.Execute (ActionContext(..)) import Control.Monad (unless, void, liftM, filterM, foldM, when) import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.Reader.Class (local) import Control.Monad.Trans.Control (liftBaseWith) import Control.Monad.Trans.Unlift (MonadBaseUnlift) import qualified Data.ByteString as S import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Lazy as L import Data.Char (toLower) import Data.Data (Data, Typeable, cast, gmapT) import Data.Either (partitionEithers) import Data.IORef (newIORef, readIORef, writeIORef) import Data.List import Data.List.Extra (nubOrd) import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NE import qualified Data.Map.Strict as Map import Data.Maybe (fromMaybe, catMaybes) import Data.Monoid ((<>)) import qualified Data.Set as Set import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Encoding as TLE import Data.Time.Clock.POSIX import Distribution.Package (Dependency (..)) import qualified Distribution.PackageDescription as Cabal import qualified Distribution.PackageDescription.Check as Check import Distribution.PackageDescription.PrettyPrint (showGenericPackageDescription) import Distribution.Text (display) import Distribution.Version (simplifyVersionRange, orLaterVersion, earlierVersion) import Distribution.Version.Extra import Lens.Micro (set) import Path import Path.IO hiding (getModificationTime, getPermissions) import Prelude -- Fix redundant import warnings import Stack.Build (mkBaseConfigOpts, build) import Stack.Build.Execute import Stack.Build.Installed import Stack.Build.Source (loadSourceMap, getDefaultPackageConfig) import Stack.Build.Target import Stack.Config (resolvePackageEntry, removePathFromPackageEntry) import Stack.Constants import Stack.Package import Stack.Types.Build import Stack.Types.Config import Stack.Types.Package import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.StackT import Stack.Types.StringError import Stack.Types.Version import System.Directory (getModificationTime, getPermissions) import qualified System.FilePath as FP -- | Special exception to throw when you want to fail because of bad results -- of package check. data SDistOpts = SDistOpts { sdoptsDirsToWorkWith :: [String] -- ^ Directories to package , sdoptsPvpBounds :: Maybe PvpBounds -- ^ PVP Bounds overrides , sdoptsIgnoreCheck :: Bool -- ^ Whether to ignore check of the package for common errors , sdoptsSign :: Bool -- ^ Whether to sign the package , sdoptsSignServerUrl :: String -- ^ The URL of the signature server , sdoptsBuildTarball :: Bool -- ^ Whether to build the tarball } newtype CheckException = CheckException (NonEmpty Check.PackageCheck) deriving (Typeable) instance Exception CheckException instance Show CheckException where show (CheckException xs) = "Package check reported the following errors:\n" ++ (intercalate "\n" . fmap show . NE.toList $ xs) -- | Given the path to a local package, creates its source -- distribution tarball. -- -- While this yields a 'FilePath', the name of the tarball, this -- tarball is not written to the disk and instead yielded as a lazy -- bytestring. getSDistTarball :: (StackM env m, HasEnvConfig env) => Maybe PvpBounds -- ^ Override Config value -> Path Abs Dir -- ^ Path to local package -> m (FilePath, L.ByteString, Maybe (PackageIdentifier, L.ByteString)) -- ^ Filename, tarball contents, and option cabal file revision to upload getSDistTarball mpvpBounds pkgDir = do config <- view configL let PvpBounds pvpBounds asRevision = fromMaybe (configPvpBounds config) mpvpBounds tweakCabal = pvpBounds /= PvpBoundsNone pkgFp = toFilePath pkgDir lp <- readLocalPackage pkgDir $logInfo $ "Getting file list for " <> T.pack pkgFp (fileList, cabalfp) <- getSDistFileList lp $logInfo $ "Building sdist tarball for " <> T.pack pkgFp files <- normalizeTarballPaths (lines fileList) -- We're going to loop below and eventually find the cabal -- file. When we do, we'll upload this reference, if the -- mpvpBounds value indicates that we should be uploading a cabal -- file revision. cabalFileRevisionRef <- liftIO (newIORef Nothing) -- NOTE: Could make this use lazy I/O to only read files as needed -- for upload (both GZip.compress and Tar.write are lazy). -- However, it seems less error prone and more predictable to read -- everything in at once, so that's what we're doing for now: let tarPath isDir fp = either throwString return (Tar.toTarPath isDir (forceUtf8Enc (pkgId FP. fp))) -- convert a String of proper characters to a String of bytes -- in UTF8 encoding masquerading as characters. This is -- necessary for tricking the tar package into proper -- character encoding. forceUtf8Enc = S8.unpack . T.encodeUtf8 . T.pack packWith f isDir fp = liftIO $ f (pkgFp FP. fp) =<< tarPath isDir fp packDir = packWith Tar.packDirectoryEntry True packFile fp -- This is a cabal file, we're going to tweak it, but only -- tweak it as a revision. | tweakCabal && isCabalFp fp && asRevision = do lbsIdent <- getCabalLbs pvpBounds (Just 1) $ toFilePath cabalfp liftIO (writeIORef cabalFileRevisionRef (Just lbsIdent)) packWith packFileEntry False fp -- Same, except we'll include the cabal file in the -- original tarball upload. | tweakCabal && isCabalFp fp = do (_ident, lbs) <- getCabalLbs pvpBounds Nothing $ toFilePath cabalfp currTime <- liftIO getPOSIXTime -- Seconds from UNIX epoch tp <- liftIO $ tarPath False fp return $ (Tar.fileEntry tp lbs) { Tar.entryTime = floor currTime } | otherwise = packWith packFileEntry False fp isCabalFp fp = toFilePath pkgDir FP. fp == toFilePath cabalfp tarName = pkgId FP.<.> "tar.gz" pkgId = packageIdentifierString (packageIdentifier (lpPackage lp)) dirEntries <- mapM packDir (dirsFromFiles files) fileEntries <- mapM packFile files mcabalFileRevision <- liftIO (readIORef cabalFileRevisionRef) return (tarName, GZip.compress (Tar.write (dirEntries ++ fileEntries)), mcabalFileRevision) -- | Get the PVP bounds-enabled version of the given cabal file getCabalLbs :: (StackM env m, HasEnvConfig env) => PvpBoundsType -> Maybe Int -- ^ optional revision -> FilePath -> m (PackageIdentifier, L.ByteString) getCabalLbs pvpBounds mrev fp = do path <- liftIO $ resolveFile' fp (_warnings, gpd) <- readPackageUnresolved path (_, sourceMap) <- loadSourceMap AllowNoTargets defaultBuildOptsCLI menv <- getMinimalEnvOverride (installedMap, _, _, _) <- getInstalled menv GetInstalledOpts { getInstalledProfiling = False , getInstalledHaddock = False , getInstalledSymbols = False } sourceMap let gpd' = gtraverseT (addBounds sourceMap installedMap) gpd gpd'' = case mrev of Nothing -> gpd' Just rev -> gpd' { Cabal.packageDescription = (Cabal.packageDescription gpd') { Cabal.customFieldsPD = (("x-revision", show rev):) $ filter (\(x, _) -> map toLower x /= "x-revision") $ Cabal.customFieldsPD $ Cabal.packageDescription gpd' } } ident <- parsePackageIdentifierFromString $ display $ Cabal.package $ Cabal.packageDescription gpd'' return ( ident , TLE.encodeUtf8 $ TL.pack $ showGenericPackageDescription gpd'' ) where addBounds :: SourceMap -> InstalledMap -> Dependency -> Dependency addBounds sourceMap installedMap dep@(Dependency cname range) = case lookupVersion (fromCabalPackageName cname) of Nothing -> dep Just version -> Dependency cname $ simplifyVersionRange $ (if toAddUpper && not (hasUpper range) then addUpper version else id) $ (if toAddLower && not (hasLower range) then addLower version else id) range where lookupVersion name = case Map.lookup name sourceMap of Just (PSLocal lp) -> Just $ packageVersion $ lpPackage lp Just (PSUpstream version _ _ _ _) -> Just version Nothing -> case Map.lookup name installedMap of Just (_, installed) -> Just (installedVersion installed) Nothing -> Nothing addUpper version = intersectVersionRanges (earlierVersion $ toCabalVersion $ nextMajorVersion version) addLower version = intersectVersionRanges (orLaterVersion (toCabalVersion version)) (toAddLower, toAddUpper) = case pvpBounds of PvpBoundsNone -> (False, False) PvpBoundsUpper -> (False, True) PvpBoundsLower -> (True, False) PvpBoundsBoth -> (True, True) -- | Traverse a data type. gtraverseT :: (Data a,Typeable b) => (Typeable b => b -> b) -> a -> a gtraverseT f = gmapT (\x -> case cast x of Nothing -> gtraverseT f x Just b -> fromMaybe x (cast (f b))) -- | Read in a 'LocalPackage' config. This makes some default decisions -- about 'LocalPackage' fields that might not be appropriate for other -- use-cases. readLocalPackage :: (StackM env m, HasEnvConfig env) => Path Abs Dir -> m LocalPackage readLocalPackage pkgDir = do cabalfp <- findOrGenerateCabalFile pkgDir config <- getDefaultPackageConfig (warnings,package) <- readPackage config cabalfp mapM_ (printCabalFileWarning cabalfp) warnings return LocalPackage { lpPackage = package , lpWanted = False -- HACK: makes it so that sdist output goes to a log instead of a file. , lpDir = pkgDir , lpCabalFile = cabalfp -- NOTE: these aren't the 'correct values, but aren't used in -- the usage of this function in this module. , lpTestDeps = Map.empty , lpBenchDeps = Map.empty , lpTestBench = Nothing , lpForceDirty = False , lpDirtyFiles = Nothing , lpNewBuildCache = Map.empty , lpFiles = Set.empty , lpComponents = Set.empty , lpUnbuildable = Set.empty } -- | Returns a newline-separate list of paths, and the absolute path to the .cabal file. getSDistFileList :: (StackM env m, HasEnvConfig env) => LocalPackage -> m (String, Path Abs File) getSDistFileList lp = withSystemTempDir (stackProgName <> "-sdist") $ \tmpdir -> do menv <- getMinimalEnvOverride let bopts = defaultBuildOpts let boptsCli = defaultBuildOptsCLI baseConfigOpts <- mkBaseConfigOpts boptsCli (locals, _) <- loadSourceMap NeedTargets boptsCli runInBase <- liftBaseWith $ \run -> return (void . run) withExecuteEnv menv bopts boptsCli baseConfigOpts locals [] [] [] -- provide empty list of globals. This is a hack around custom Setup.hs files $ \ee -> withSingleContext runInBase ac ee task Nothing (Just "sdist") $ \_package cabalfp _pkgDir cabal _announce _console _mlogFile -> do let outFile = toFilePath tmpdir FP. "source-files-list" cabal KeepTHLoading ["sdist", "--list-sources", outFile] contents <- liftIO (readFile outFile) return (contents, cabalfp) where package = lpPackage lp ac = ActionContext Set.empty [] task = Task { taskProvides = PackageIdentifier (packageName package) (packageVersion package) , taskType = TTLocal lp , taskConfigOpts = TaskConfigOpts { tcoMissing = Set.empty , tcoOpts = \_ -> ConfigureOpts [] [] } , taskPresent = Map.empty , taskAllInOne = True , taskCachePkgSrc = CacheSrcLocal (toFilePath (lpDir lp)) } normalizeTarballPaths :: (StackM env m) => [FilePath] -> m [FilePath] normalizeTarballPaths fps = do -- TODO: consider whether erroring out is better - otherwise the -- user might upload an incomplete tar? unless (null outsideDir) $ $logWarn $ T.concat [ "Warning: These files are outside of the package directory, and will be omitted from the tarball: " , T.pack (show outsideDir)] return (nubOrd files) where (outsideDir, files) = partitionEithers (map pathToEither fps) pathToEither fp = maybe (Left fp) Right (normalizePath fp) normalizePath :: FilePath -> Maybe FilePath normalizePath = fmap FP.joinPath . go . FP.splitDirectories . FP.normalise where go [] = Just [] go ("..":_) = Nothing go (_:"..":xs) = go xs go (x:xs) = (x :) <$> go xs dirsFromFiles :: [FilePath] -> [FilePath] dirsFromFiles dirs = Set.toAscList (Set.delete "." results) where results = foldl' (\s -> go s . FP.takeDirectory) Set.empty dirs go s x | Set.member x s = s | otherwise = go (Set.insert x s) (FP.takeDirectory x) -- | Check package in given tarball. This will log all warnings -- and will throw an exception in case of critical errors. -- -- Note that we temporarily decompress the archive to analyze it. checkSDistTarball :: (StackM env m, HasEnvConfig env, MonadBaseUnlift IO m) => SDistOpts -- ^ The configuration of what to check -> Path Abs File -- ^ Absolute path to tarball -> m () checkSDistTarball opts tarball = withTempTarGzContents tarball $ \pkgDir' -> do pkgDir <- (pkgDir' ) `liftM` (parseRelDir . FP.takeBaseName . FP.takeBaseName . toFilePath $ tarball) -- ^ drop ".tar" ^ drop ".gz" when (sdoptsBuildTarball opts) (buildExtractedTarball pkgDir) unless (sdoptsIgnoreCheck opts) (checkPackageInExtractedTarball pkgDir) checkPackageInExtractedTarball :: (StackM env m, HasEnvConfig env, MonadBaseUnlift IO m) => Path Abs Dir -- ^ Absolute path to tarball -> m () checkPackageInExtractedTarball pkgDir = do cabalfp <- findOrGenerateCabalFile pkgDir name <- parsePackageNameFromFilePath cabalfp config <- getDefaultPackageConfig (gdesc, pkgDesc) <- readPackageDescriptionDir config pkgDir $logInfo $ "Checking package '" <> packageNameText name <> "' for common mistakes" let pkgChecks = Check.checkPackage gdesc (Just pkgDesc) fileChecks <- liftIO $ Check.checkPackageFiles pkgDesc (toFilePath pkgDir) let checks = pkgChecks ++ fileChecks (errors, warnings) = let criticalIssue (Check.PackageBuildImpossible _) = True criticalIssue (Check.PackageDistInexcusable _) = True criticalIssue _ = False in partition criticalIssue checks unless (null warnings) $ $logWarn $ "Package check reported the following warnings:\n" <> T.pack (intercalate "\n" . fmap show $ warnings) case NE.nonEmpty errors of Nothing -> return () Just ne -> throwM $ CheckException ne buildExtractedTarball :: (StackM env m, HasEnvConfig env, MonadBaseUnlift IO m) => Path Abs Dir -> m () buildExtractedTarball pkgDir = do projectRoot <- view projectRootL envConfig <- view envConfigL menv <- getMinimalEnvOverride localPackageToBuild <- readLocalPackage pkgDir let packageEntries = bcPackageEntries (envConfigBuildConfig envConfig) getPaths entry = do resolvedEntry <- resolvePackageEntry menv projectRoot entry return $ fmap fst resolvedEntry allPackagePaths <- fmap mconcat (mapM getPaths packageEntries) -- We remove the path based on the name of the package let isPathToRemove path = do localPackage <- readLocalPackage path return $ packageName (lpPackage localPackage) == packageName (lpPackage localPackageToBuild) pathsToRemove <- filterM isPathToRemove allPackagePaths let adjustPackageEntries entries path = do adjustedPackageEntries <- mapM (removePathFromPackageEntry menv projectRoot path) entries return (catMaybes adjustedPackageEntries) entriesWithoutBuiltPackage <- foldM adjustPackageEntries packageEntries pathsToRemove let newEntry = PackageEntry Nothing (PLFilePath (toFilePath pkgDir)) [] newPackagesRef <- liftIO (newIORef Nothing) let adjustEnvForBuild env = let updatedEnvConfig = envConfig {envConfigPackagesRef = newPackagesRef ,envConfigBuildConfig = updatePackageInBuildConfig (envConfigBuildConfig envConfig) } in set envConfigL updatedEnvConfig env updatePackageInBuildConfig buildConfig = buildConfig { bcPackageEntries = newEntry : entriesWithoutBuiltPackage , bcConfig = (bcConfig buildConfig) { configBuild = defaultBuildOpts { boptsTests = True } } } local adjustEnvForBuild $ build (const (return ())) Nothing defaultBuildOptsCLI -- | Version of 'checkSDistTarball' that first saves lazy bytestring to -- temporary directory and then calls 'checkSDistTarball' on it. checkSDistTarball' :: (StackM env m, HasEnvConfig env, MonadBaseUnlift IO m) => SDistOpts -> String -- ^ Tarball name -> L.ByteString -- ^ Tarball contents as a byte string -> m () checkSDistTarball' opts name bytes = withSystemTempDir "stack" $ \tpath -> do npath <- (tpath ) `liftM` parseRelFile name liftIO $ L.writeFile (toFilePath npath) bytes checkSDistTarball opts npath withTempTarGzContents :: (MonadIO m, MonadMask m) => Path Abs File -- ^ Location of tarball -> (Path Abs Dir -> m a) -- ^ Perform actions given dir with tarball contents -> m a withTempTarGzContents apath f = withSystemTempDir "stack" $ \tpath -> do archive <- liftIO $ L.readFile (toFilePath apath) liftIO . Tar.unpack (toFilePath tpath) . Tar.read . GZip.decompress $ archive f tpath -------------------------------------------------------------------------------- -- Copy+modified from the tar package to avoid issues with lazy IO ( see -- https://github.com/commercialhaskell/stack/issues/1344 ) packFileEntry :: FilePath -- ^ Full path to find the file on the local disk -> Tar.TarPath -- ^ Path to use for the tar Entry in the archive -> IO Tar.Entry packFileEntry filepath tarpath = do mtime <- getModTime filepath perms <- getPermissions filepath content <- S.readFile filepath let size = fromIntegral (S.length content) return (Tar.simpleEntry tarpath (Tar.NormalFile (L.fromStrict content) size)) { Tar.entryPermissions = if executable perms then Tar.executableFilePermissions else Tar.ordinaryFilePermissions, Tar.entryTime = mtime } getModTime :: FilePath -> IO Tar.EpochTime getModTime path = do t <- getModificationTime path return . floor . utcTimeToPOSIXSeconds $ t stack-1.5.1/src/Stack/Setup.hs0000644000000000000000000024303313135652051014307 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveDataTypeable #-} -- ghc < 7.10 {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PackageImports #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE CPP #-} module Stack.Setup ( setupEnv , ensureCompiler , ensureDockerStackExe , getSystemCompiler , getCabalInstallVersion , SetupOpts (..) , defaultSetupInfoYaml , removeHaskellEnvVars -- * Stack binary download , StackReleaseInfo , getDownloadVersion , stackVersion , preferredPlatforms , downloadStackReleaseInfo , downloadStackExe ) where import qualified Codec.Archive.Tar as Tar import Control.Applicative import Control.Concurrent.Async.Lifted (Concurrently(..)) import Control.Exception.Safe (catchIO, tryAny) import Control.Monad (liftM, when, join, void, unless, guard) import Control.Monad.Catch import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Logger import Control.Monad.Reader (MonadReader, ReaderT (..)) import Control.Monad.State (get, put, modify) import Control.Monad.Trans.Control import "cryptonite" Crypto.Hash (SHA1(..)) import Data.Aeson.Extended import qualified Data.ByteString as S import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Lazy as LBS import Data.Char (isSpace) import Data.Conduit (Conduit, (=$), await, yield, awaitForever, (.|)) import Data.Conduit.Lazy (lazyConsume) import Data.Conduit.Lift (evalStateC) import qualified Data.Conduit.List as CL import Data.Conduit.Zlib (ungzip) import Data.Either import Data.Foldable hiding (concatMap, or, maximum) import qualified Data.HashMap.Strict as HashMap import Data.IORef import Data.IORef.RunOnce (runOnce) import Data.List hiding (concat, elem, maximumBy, any) import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe import Data.Monoid import Data.Ord (comparing) import Data.Set (Set) import qualified Data.Set as Set import Data.String import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.Encoding.Error as T import Data.Time.Clock (NominalDiffTime, diffUTCTime, getCurrentTime) import Data.Typeable (Typeable) import qualified Data.Yaml as Yaml import Distribution.System (OS (Linux), Arch (..), Platform (..)) import qualified Distribution.System as Cabal import Distribution.Text (simpleParse) import Lens.Micro (set) import Network.HTTP.Simple (getResponseBody, httpLBS, withResponse, getResponseStatusCode) import Network.HTTP.Download import Path import Path.CheckInstall (warnInstallSearchPathIssues) import Path.Extra (toFilePathNoTrailingSep) import Path.IO hiding (findExecutable) import qualified Paths_stack as Meta import Prelude hiding (concat, elem, any) -- Fix AMP warning import Safe (headMay, readMay) import Stack.Build (build) import Stack.Config (loadConfig) import Stack.Constants (distRelativeDir, stackProgName) import Stack.Exec (defaultEnvSettings) import Stack.Fetch import Stack.GhcPkg (createDatabase, getCabalPkgVer, getGlobalDB, mkGhcPackagePath) import Stack.PrettyPrint import Stack.Setup.Installed import Stack.Types.Build import Stack.Types.Compiler import Stack.Types.CompilerBuild import Stack.Types.Config import Stack.Types.Docker import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.StackT import Stack.Types.StringError import Stack.Types.Version import qualified System.Directory as D import System.Environment (getExecutablePath) import System.Exit (ExitCode (..), exitFailure) import System.FilePath (searchPathSeparator) import qualified System.FilePath as FP import System.Process (rawSystem) import System.Process.Log (withProcessTimeLog) import System.Process.Read import System.Process.Run (runCmd, Cmd(..)) import Text.Printf (printf) #if !WINDOWS import System.Posix.Files (setFileMode) #endif -- | Default location of the stack-setup.yaml file defaultSetupInfoYaml :: String defaultSetupInfoYaml = "https://raw.githubusercontent.com/fpco/stackage-content/master/stack/stack-setup-2.yaml" data SetupOpts = SetupOpts { soptsInstallIfMissing :: !Bool , soptsUseSystem :: !Bool -- ^ Should we use a system compiler installation, if available? , soptsWantedCompiler :: !CompilerVersion , soptsCompilerCheck :: !VersionCheck , soptsStackYaml :: !(Maybe (Path Abs File)) -- ^ If we got the desired GHC version from that file , soptsForceReinstall :: !Bool , soptsSanityCheck :: !Bool -- ^ Run a sanity check on the selected GHC , soptsSkipGhcCheck :: !Bool -- ^ Don't check for a compatible GHC version/architecture , soptsSkipMsys :: !Bool -- ^ Do not use a custom msys installation on Windows , soptsUpgradeCabal :: !(Maybe UpgradeTo) -- ^ Upgrade the global Cabal library in the database to the newest -- version. Only works reliably with a stack-managed installation. , soptsResolveMissingGHC :: !(Maybe Text) -- ^ Message shown to user for how to resolve the missing GHC , soptsSetupInfoYaml :: !FilePath -- ^ Location of the main stack-setup.yaml file , soptsGHCBindistURL :: !(Maybe String) -- ^ Alternate GHC binary distribution (requires custom GHCVariant) , soptsGHCJSBootOpts :: [String] -- ^ Additional ghcjs-boot options, the default is "--clean" } deriving Show data SetupException = UnsupportedSetupCombo OS Arch | MissingDependencies [String] | UnknownCompilerVersion Text CompilerVersion [CompilerVersion] | UnknownOSKey Text | GHCSanityCheckCompileFailed ReadProcessException (Path Abs File) | WantedMustBeGHC | RequireCustomGHCVariant | ProblemWhileDecompressing (Path Abs File) | SetupInfoMissingSevenz | GHCJSRequiresStandardVariant | GHCJSNotBooted | DockerStackExeNotFound Version Text deriving Typeable instance Exception SetupException instance Show SetupException where show (UnsupportedSetupCombo os arch) = concat [ "I don't know how to install GHC for " , show (os, arch) , ", please install manually" ] show (MissingDependencies tools) = "The following executables are missing and must be installed: " ++ intercalate ", " tools show (UnknownCompilerVersion oskey wanted known) = concat [ "No information found for " , compilerVersionString wanted , ".\nSupported versions for OS key '" ++ T.unpack oskey ++ "': " , intercalate ", " (map show known) ] show (UnknownOSKey oskey) = "Unable to find installation URLs for OS key: " ++ T.unpack oskey show (GHCSanityCheckCompileFailed e ghc) = concat [ "The GHC located at " , toFilePath ghc , " failed to compile a sanity check. Please see:\n\n" , " http://docs.haskellstack.org/en/stable/install_and_upgrade/\n\n" , "for more information. Exception was:\n" , show e ] show WantedMustBeGHC = "The wanted compiler must be GHC" show RequireCustomGHCVariant = "A custom --ghc-variant must be specified to use --ghc-bindist" show (ProblemWhileDecompressing archive) = "Problem while decompressing " ++ toFilePath archive show SetupInfoMissingSevenz = "SetupInfo missing Sevenz EXE/DLL" show GHCJSRequiresStandardVariant = "stack does not yet support using --ghc-variant with GHCJS" show GHCJSNotBooted = "GHCJS does not yet have its boot packages installed. Use \"stack setup\" to attempt to run ghcjs-boot." show (DockerStackExeNotFound stackVersion' osKey) = concat [ stackProgName , "-" , versionString stackVersion' , " executable not found for " , T.unpack osKey , "\nUse the '" , T.unpack dockerStackExeArgName , "' option to specify a location"] -- | Modify the environment variables (like PATH) appropriately, possibly doing installation too setupEnv :: (StackM env m, HasBuildConfig env, HasGHCVariant env) => Maybe Text -- ^ Message to give user when necessary GHC is not available -> m EnvConfig setupEnv mResolveMissingGHC = do config <- view configL bconfig <- view buildConfigL let stackYaml = bcStackYaml bconfig platform <- view platformL wcVersion <- view wantedCompilerVersionL wc <- view $ wantedCompilerVersionL.whichCompilerL let sopts = SetupOpts { soptsInstallIfMissing = configInstallGHC config , soptsUseSystem = configSystemGHC config , soptsWantedCompiler = wcVersion , soptsCompilerCheck = configCompilerCheck config , soptsStackYaml = Just stackYaml , soptsForceReinstall = False , soptsSanityCheck = False , soptsSkipGhcCheck = configSkipGHCCheck config , soptsSkipMsys = configSkipMsys config , soptsUpgradeCabal = Nothing , soptsResolveMissingGHC = mResolveMissingGHC , soptsSetupInfoYaml = defaultSetupInfoYaml , soptsGHCBindistURL = Nothing , soptsGHCJSBootOpts = ["--clean"] } (mghcBin, compilerBuild, _) <- ensureCompiler sopts -- Modify the initial environment to include the GHC path, if a local GHC -- is being used menv0 <- getMinimalEnvOverride env <- removeHaskellEnvVars <$> augmentPathMap (maybe [] edBins mghcBin) (unEnvOverride menv0) menv <- mkEnvOverride platform env (compilerVer, cabalVer, globaldb) <- runConcurrently $ (,,) <$> Concurrently (getCompilerVersion menv wc) <*> Concurrently (getCabalPkgVer menv wc) <*> Concurrently (getGlobalDB menv wc) $logDebug "Resolving package entries" packagesRef <- liftIO $ newIORef Nothing bc <- view buildConfigL let envConfig0 = EnvConfig { envConfigBuildConfig = bc , envConfigCabalVersion = cabalVer , envConfigCompilerVersion = compilerVer , envConfigCompilerBuild = compilerBuild , envConfigPackagesRef = packagesRef } -- extra installation bin directories mkDirs <- runReaderT extraBinDirs envConfig0 let mpath = Map.lookup "PATH" env depsPath <- augmentPath (mkDirs False) mpath localsPath <- augmentPath (mkDirs True) mpath deps <- runReaderT packageDatabaseDeps envConfig0 createDatabase menv wc deps localdb <- runReaderT packageDatabaseLocal envConfig0 createDatabase menv wc localdb extras <- runReaderT packageDatabaseExtra envConfig0 let mkGPP locals = mkGhcPackagePath locals localdb deps extras globaldb distDir <- runReaderT distRelativeDir envConfig0 executablePath <- liftIO getExecutablePath utf8EnvVars <- getUtf8EnvVars menv compilerVer envRef <- liftIO $ newIORef Map.empty let getEnvOverride' es = do m <- readIORef envRef case Map.lookup es m of Just eo -> return eo Nothing -> do eo <- mkEnvOverride platform $ Map.insert "PATH" (if esIncludeLocals es then localsPath else depsPath) $ (if esIncludeGhcPackagePath es then Map.insert (case wc of { Ghc -> "GHC_PACKAGE_PATH"; Ghcjs -> "GHCJS_PACKAGE_PATH" }) (mkGPP (esIncludeLocals es)) else id) $ (if esStackExe es then Map.insert "STACK_EXE" (T.pack executablePath) else id) $ (if esLocaleUtf8 es then Map.union utf8EnvVars else id) $ case (soptsSkipMsys sopts, platform) of (False, Platform Cabal.I386 Cabal.Windows) -> Map.insert "MSYSTEM" "MINGW32" (False, Platform Cabal.X86_64 Cabal.Windows) -> Map.insert "MSYSTEM" "MINGW64" _ -> id -- For reasoning and duplication, see: https://github.com/fpco/stack/issues/70 $ Map.insert "HASKELL_PACKAGE_SANDBOX" (T.pack $ toFilePathNoTrailingSep deps) $ Map.insert "HASKELL_PACKAGE_SANDBOXES" (T.pack $ if esIncludeLocals es then intercalate [searchPathSeparator] [ toFilePathNoTrailingSep localdb , toFilePathNoTrailingSep deps , "" ] else intercalate [searchPathSeparator] [ toFilePathNoTrailingSep deps , "" ]) $ Map.insert "HASKELL_DIST_DIR" (T.pack $ toFilePathNoTrailingSep distDir) env () <- atomicModifyIORef envRef $ \m' -> (Map.insert es eo m', ()) return eo return EnvConfig { envConfigBuildConfig = bconfig { bcConfig = maybe id addIncludeLib mghcBin (view configL bconfig) { configEnvOverride = getEnvOverride' } } , envConfigCabalVersion = cabalVer , envConfigCompilerVersion = compilerVer , envConfigCompilerBuild = compilerBuild , envConfigPackagesRef = envConfigPackagesRef envConfig0 } -- | Add the include and lib paths to the given Config addIncludeLib :: ExtraDirs -> Config -> Config addIncludeLib (ExtraDirs _bins includes libs) config = config { configExtraIncludeDirs = Set.union (configExtraIncludeDirs config) (Set.fromList (map toFilePathNoTrailingSep includes)) , configExtraLibDirs = Set.union (configExtraLibDirs config) (Set.fromList (map toFilePathNoTrailingSep libs)) } -- | Ensure compiler (ghc or ghcjs) is installed and provide the PATHs to add if necessary ensureCompiler :: (StackM env m, HasConfig env, HasGHCVariant env) => SetupOpts -> m (Maybe ExtraDirs, CompilerBuild, Bool) ensureCompiler sopts = do let wc = whichCompiler (soptsWantedCompiler sopts) when (getGhcVersion (soptsWantedCompiler sopts) < $(mkVersion "7.8")) $ do $logWarn "stack will almost certainly fail with GHC below version 7.8" $logWarn "Valiantly attempting to run anyway, but I know this is doomed" $logWarn "For more information, see: https://github.com/commercialhaskell/stack/issues/648" $logWarn "" -- Check the available GHCs menv0 <- getMinimalEnvOverride msystem <- if soptsUseSystem sopts then do $logDebug "Getting system compiler version" getSystemCompiler menv0 wc else return Nothing Platform expectedArch _ <- view platformL let canUseCompiler compilerVersion arch | soptsSkipGhcCheck sopts = True | otherwise = isWanted compilerVersion && arch == expectedArch isWanted = isWantedCompiler (soptsCompilerCheck sopts) (soptsWantedCompiler sopts) needLocal = not (any (uncurry canUseCompiler) msystem) getSetupInfo' <- runOnce (getSetupInfo (soptsSetupInfoYaml sopts)) let getMmsys2Tool = do platform <- view platformL localPrograms <- view $ configL.to configLocalPrograms installed <- listInstalled localPrograms case platform of Platform _ Cabal.Windows | not (soptsSkipMsys sopts) -> case getInstalledTool installed $(mkPackageName "msys2") (const True) of Just tool -> return (Just tool) Nothing | soptsInstallIfMissing sopts -> do si <- getSetupInfo' osKey <- getOSKey platform config <- view configL VersionedDownloadInfo version info <- case Map.lookup osKey $ siMsys2 si of Just x -> return x Nothing -> throwString $ "MSYS2 not found for " ++ T.unpack osKey let tool = Tool (PackageIdentifier $(mkPackageName "msys2") version) Just <$> downloadAndInstallTool (configLocalPrograms config) si info tool (installMsys2Windows osKey) | otherwise -> do $logWarn "Continuing despite missing tool: msys2" return Nothing _ -> return Nothing -- If we need to install a GHC or MSYS, try to do so -- Return the additional directory paths of GHC & MSYS. (mtools, compilerBuild) <- if needLocal then do -- Install GHC ghcVariant <- view ghcVariantL config <- view configL let localPrograms = configLocalPrograms config installed <- listInstalled localPrograms (installedCompiler, compilerBuild) <- case wc of Ghc -> do ghcBuild <- getGhcBuild menv0 ghcPkgName <- parsePackageNameFromString ("ghc" ++ ghcVariantSuffix ghcVariant ++ compilerBuildSuffix ghcBuild) return (getInstalledTool installed ghcPkgName (isWanted . GhcVersion), ghcBuild) Ghcjs -> return (getInstalledGhcjs installed isWanted, CompilerBuildStandard) compilerTool <- case (installedCompiler, soptsForceReinstall sopts) of (Just tool, False) -> return tool _ | soptsInstallIfMissing sopts -> do si <- getSetupInfo' downloadAndInstallCompiler compilerBuild si (soptsWantedCompiler sopts) (soptsCompilerCheck sopts) (soptsGHCBindistURL sopts) | otherwise -> do recommendSystemGhc <- if soptsUseSystem sopts then return False else do msystemGhc <- getSystemCompiler menv0 wc return (any (uncurry canUseCompiler) msystemGhc) let suggestion = fromMaybe (mconcat ([ "To install the correct GHC into " , T.pack (toFilePath (configLocalPrograms config)) , ", try running \"stack setup\" or use the \"--install-ghc\" flag." ] ++ [ " To use your system GHC installation, run \"stack config set system-ghc --global true\", or use the \"--system-ghc\" flag." | recommendSystemGhc ])) (soptsResolveMissingGHC sopts) throwM $ CompilerVersionMismatch msystem (soptsWantedCompiler sopts, expectedArch) ghcVariant compilerBuild (soptsCompilerCheck sopts) (soptsStackYaml sopts) suggestion -- Install msys2 on windows, if necessary mmsys2Tool <- getMmsys2Tool return (Just (Just compilerTool, mmsys2Tool), compilerBuild) -- Have the right ghc, may still need msys else do mmsys2Tool <- getMmsys2Tool return (Just (Nothing, mmsys2Tool), CompilerBuildStandard) mpaths <- case mtools of Nothing -> return Nothing Just (compilerTool, mmsys2Tool) -> do -- Add GHC's and MSYS's paths to the config. let idents = catMaybes [compilerTool, mmsys2Tool] paths <- mapM extraDirs idents return $ Just $ mconcat paths menv <- case mpaths of Nothing -> return menv0 Just ed -> do config <- view configL m <- augmentPathMap (edBins ed) (unEnvOverride menv0) mkEnvOverride (configPlatform config) (removeHaskellEnvVars m) forM_ (soptsUpgradeCabal sopts) $ \version -> do unless needLocal $ do $logWarn "Trying to change a Cabal library on a GHC not installed by stack." $logWarn "This may fail, caveat emptor!" upgradeCabal menv wc version case mtools of Just (Just (ToolGhcjs cv), _) -> ensureGhcjsBooted menv cv (soptsInstallIfMissing sopts) (soptsGHCJSBootOpts sopts) _ -> return () when (soptsSanityCheck sopts) $ sanityCheck menv wc return (mpaths, compilerBuild, needLocal) -- | Determine which GHC build to use depending on which shared libraries are available -- on the system. getGhcBuild :: (StackM env m, HasConfig env) => EnvOverride -> m CompilerBuild getGhcBuild menv = do config <- view configL case configGHCBuild config of Just ghcBuild -> return ghcBuild Nothing -> determineGhcBuild where determineGhcBuild = do -- TODO: a more reliable, flexible, and data driven approach would be to actually download small -- "test" executables (from setup-info) that link to the same gmp/tinfo versions -- that GHC does (i.e. built in same environment as the GHC bindist). The algorithm would go -- something like this: -- -- check for previous 'uname -a'/`ldconfig -p` plus compiler version/variant in cache -- if cached, then use that as suffix -- otherwise: -- download setup-info -- go through all with right prefix for os/version/variant -- first try "standard" (no extra suffix), then the rest -- download "compatibility check" exe if not already downloaded -- try running it -- if successful, then choose that -- cache compiler suffix with the uname -a and ldconfig -p output hash plus compiler version -- -- Of course, could also try to make a static GHC bindist instead of all this rigamarole. platform <- view platformL case platform of Platform _ Linux -> do -- Some systems don't have ldconfig in the PATH, so make sure to look in /sbin and /usr/sbin as well sbinEnv <- modifyEnvOverride menv $ Map.insert "PATH" $ "/sbin:/usr/sbin" <> maybe "" (":" <>) (Map.lookup "PATH" (eoTextMap menv)) eldconfigOut <- tryProcessStdout Nothing sbinEnv "ldconfig" ["-p"] egccErrOut <- tryProcessStderrStdout Nothing menv "gcc" ["-v"] let firstWords = case eldconfigOut of Right ldconfigOut -> mapMaybe (headMay . T.words) $ T.lines $ T.decodeUtf8With T.lenientDecode ldconfigOut Left _ -> [] checkLib lib | libT `elem` firstWords = do $logDebug ("Found shared library " <> libT <> " in 'ldconfig -p' output") return True | otherwise = do #ifdef WINDOWS -- (mkAbsDir "/usr/lib") fails to compile on Windows, thus the CPP return False #else -- This is a workaround for the fact that libtinfo.so.6 doesn't appear in -- the 'ldconfig -p' output on Arch even when it exists. -- There doesn't seem to be an easy way to get the true list of directories -- to scan for shared libs, but this works for our particular case. e <- doesFileExist ($(mkAbsDir "/usr/lib") lib) if e then $logDebug ("Found shared library " <> libT <> " in /usr/lib") else $logDebug ("Did not find shared library " <> libT) return e #endif where libT = T.pack (toFilePath lib) noPie = case egccErrOut of Right (gccErr,gccOut) -> "--enable-default-pie" `elem` S8.words gccOutput || "Gentoo Hardened" `S8.isInfixOf` gccOutput where gccOutput = gccOut <> gccErr Left _ -> False $logDebug $ if noPie then "PIE disabled" else "PIE enabled" hastinfo5 <- checkLib $(mkRelFile "libtinfo.so.5") hastinfo6 <- checkLib $(mkRelFile "libtinfo.so.6") hasncurses6 <- checkLib $(mkRelFile "libncursesw.so.6") hasgmp5 <- checkLib $(mkRelFile "libgmp.so.10") hasgmp4 <- checkLib $(mkRelFile "libgmp.so.3") let libComponents = if | hastinfo5 && hasgmp5 -> [] | hastinfo6 && hasgmp5 -> ["tinfo6"] | hasncurses6 && hasgmp5 -> ["ncurses6"] | hasgmp4 && hastinfo5 -> ["gmp4"] | otherwise -> [] pieComponents = if noPie then ["nopie"] else [] case libComponents ++ pieComponents of [] -> useBuild CompilerBuildStandard components -> useBuild (CompilerBuildSpecialized (intercalate "-" components)) _ -> useBuild CompilerBuildStandard useBuild CompilerBuildStandard = do $logDebug "Using standard GHC build" return CompilerBuildStandard useBuild (CompilerBuildSpecialized s) = do $logDebug ("Using " <> T.pack s <> " GHC build") return (CompilerBuildSpecialized s) -- | Ensure Docker container-compatible 'stack' executable is downloaded ensureDockerStackExe :: (StackM env m, HasConfig env) => Platform -> m (Path Abs File) ensureDockerStackExe containerPlatform = do config <- view configL containerPlatformDir <- runReaderT platformOnlyRelDir (containerPlatform,PlatformVariantNone) let programsPath = configLocalProgramsBase config containerPlatformDir tool = Tool (PackageIdentifier $(mkPackageName "stack") stackVersion) stackExeDir <- installDir programsPath tool let stackExePath = stackExeDir $(mkRelFile "stack") stackExeExists <- doesFileExist stackExePath unless stackExeExists $ do $logInfo $ mconcat ["Downloading Docker-compatible ", T.pack stackProgName, " executable"] sri <- downloadStackReleaseInfo Nothing Nothing (Just (versionString stackVersion)) let platforms = preferredPlatforms (containerPlatform, PlatformVariantNone) downloadStackExe platforms sri stackExeDir (const $ return ()) return stackExePath -- | Install the newest version or a specific version of Cabal globally upgradeCabal :: (StackM env m, HasConfig env, HasGHCVariant env) => EnvOverride -> WhichCompiler -> UpgradeTo -> m () upgradeCabal menv wc cabalVersion = do $logInfo "Manipulating the global Cabal is only for debugging purposes" let name = $(mkPackageName "Cabal") rmap <- resolvePackages Nothing Map.empty (Set.singleton name) installed <- getCabalPkgVer menv wc case cabalVersion of Specific version -> do if installed /= version then doCabalInstall menv wc installed version else $logInfo $ T.concat ["No install necessary. Cabal " , T.pack $ versionString installed , " is already installed"] Latest -> case map rpIdent rmap of [] -> throwString "No Cabal library found in index, cannot upgrade" [PackageIdentifier name' version] | name == name' -> do if installed > version then doCabalInstall menv wc installed version else $logInfo $ "No upgrade necessary. Latest Cabal already installed" x -> error $ "Unexpected results for resolvePackages: " ++ show x -- Configure and run the necessary commands for a cabal install doCabalInstall :: (StackM env m, HasConfig env, HasGHCVariant env) => EnvOverride -> WhichCompiler -> Version -> Version -> m () doCabalInstall menv wc installed version = do withSystemTempDir "stack-cabal-upgrade" $ \tmpdir -> do $logInfo $ T.concat [ "Installing Cabal-" , T.pack $ versionString version , " to replace " , T.pack $ versionString installed ] let name = $(mkPackageName "Cabal") ident = PackageIdentifier name version m <- unpackPackageIdents tmpdir Nothing (Map.singleton ident Nothing) compilerPath <- join $ findExecutable menv (compilerExeName wc) versionDir <- parseRelDir $ versionString version let installRoot = toFilePath $ parent (parent compilerPath) $(mkRelDir "new-cabal") versionDir dir <- case Map.lookup ident m of Nothing -> error "upgradeCabal: Invariant violated, dir missing" Just dir -> return dir runCmd (Cmd (Just dir) (compilerExeName wc) menv ["Setup.hs"]) Nothing platform <- view platformL let setupExe = toFilePath $ dir case platform of Platform _ Cabal.Windows -> $(mkRelFile "Setup.exe") _ -> $(mkRelFile "Setup") dirArgument name' = concat [ "--" , name' , "dir=" , installRoot FP. name' ] args = "configure" : map dirArgument (words "lib bin data doc") runCmd (Cmd (Just dir) setupExe menv args) Nothing runCmd (Cmd (Just dir) setupExe menv ["build"]) Nothing runCmd (Cmd (Just dir) setupExe menv ["install"]) Nothing $logInfo "New Cabal library installed" -- | Get the version of the system compiler, if available getSystemCompiler :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => EnvOverride -> WhichCompiler -> m (Maybe (CompilerVersion, Arch)) getSystemCompiler menv wc = do let exeName = case wc of Ghc -> "ghc" Ghcjs -> "ghcjs" exists <- doesExecutableExist menv exeName if exists then do eres <- tryProcessStdout Nothing menv exeName ["--info"] let minfo = do Right bs <- Just eres pairs_ <- readMay $ S8.unpack bs :: Maybe [(String, String)] version <- lookup "Project version" pairs_ >>= parseVersionFromString arch <- lookup "Target platform" pairs_ >>= simpleParse . takeWhile (/= '-') return (version, arch) case (wc, minfo) of (Ghc, Just (version, arch)) -> return (Just (GhcVersion version, arch)) (Ghcjs, Just (_, arch)) -> do eversion <- tryAny $ getCompilerVersion menv Ghcjs case eversion of Left _ -> return Nothing Right version -> return (Just (version, arch)) (_, Nothing) -> return Nothing else return Nothing -- | Download the most recent SetupInfo getSetupInfo :: (MonadIO m, MonadThrow m, MonadLogger m, MonadReader env m, HasConfig env) => String -> m SetupInfo getSetupInfo stackSetupYaml = do config <- view configL setupInfos <- mapM loadSetupInfo (SetupInfoFileOrURL stackSetupYaml : configSetupInfoLocations config) return (mconcat setupInfos) where loadSetupInfo (SetupInfoInline si) = return si loadSetupInfo (SetupInfoFileOrURL urlOrFile) = do bs <- case parseUrlThrow urlOrFile of Just req -> liftM (LBS.toStrict . getResponseBody) $ httpLBS req Nothing -> liftIO $ S.readFile urlOrFile WithJSONWarnings si warnings <- either throwM return (Yaml.decodeEither' bs) when (urlOrFile /= defaultSetupInfoYaml) $ logJSONWarnings urlOrFile warnings return si getInstalledTool :: [Tool] -- ^ already installed -> PackageName -- ^ package to find -> (Version -> Bool) -- ^ which versions are acceptable -> Maybe Tool getInstalledTool installed name goodVersion = if null available then Nothing else Just $ Tool $ maximumBy (comparing packageIdentifierVersion) available where available = mapMaybe goodPackage installed goodPackage (Tool pi') = if packageIdentifierName pi' == name && goodVersion (packageIdentifierVersion pi') then Just pi' else Nothing goodPackage _ = Nothing getInstalledGhcjs :: [Tool] -> (CompilerVersion -> Bool) -> Maybe Tool getInstalledGhcjs installed goodVersion = if null available then Nothing else Just $ ToolGhcjs $ maximum available where available = mapMaybe goodPackage installed goodPackage (ToolGhcjs cv) = if goodVersion cv then Just cv else Nothing goodPackage _ = Nothing downloadAndInstallTool :: StackMiniM env m => Path Abs Dir -> SetupInfo -> DownloadInfo -> Tool -> (SetupInfo -> Path Abs File -> ArchiveType -> Path Abs Dir -> Path Abs Dir -> m ()) -> m Tool downloadAndInstallTool programsDir si downloadInfo tool installer = do ensureDir programsDir (file, at) <- downloadFromInfo programsDir downloadInfo tool dir <- installDir programsDir tool tempDir <- tempInstallDir programsDir tool ignoringAbsence (removeDirRecur tempDir) ensureDir tempDir unmarkInstalled programsDir tool installer si file at tempDir dir markInstalled programsDir tool ignoringAbsence (removeDirRecur tempDir) return tool downloadAndInstallCompiler :: (StackM env m, HasConfig env, HasGHCVariant env) => CompilerBuild -> SetupInfo -> CompilerVersion -> VersionCheck -> Maybe String -> m Tool downloadAndInstallCompiler ghcBuild si wanted@GhcVersion{} versionCheck mbindistURL = do ghcVariant <- view ghcVariantL (selectedVersion, downloadInfo) <- case mbindistURL of Just bindistURL -> do case ghcVariant of GHCCustom _ -> return () _ -> throwM RequireCustomGHCVariant case wanted of GhcVersion version -> return (version, GHCDownloadInfo mempty mempty (DownloadInfo (T.pack bindistURL) Nothing Nothing)) _ -> throwM WantedMustBeGHC _ -> do ghcKey <- getGhcKey ghcBuild case Map.lookup ghcKey $ siGHCs si of Nothing -> throwM $ UnknownOSKey ghcKey Just pairs_ -> getWantedCompilerInfo ghcKey versionCheck wanted GhcVersion pairs_ config <- view configL let installer = case configPlatform config of Platform _ Cabal.Windows -> installGHCWindows selectedVersion _ -> installGHCPosix selectedVersion downloadInfo $logInfo $ "Preparing to install GHC" <> (case ghcVariant of GHCStandard -> "" v -> " (" <> T.pack (ghcVariantName v) <> ")") <> (case ghcBuild of CompilerBuildStandard -> "" b -> " (" <> T.pack (compilerBuildName b) <> ")") <> " to an isolated location." $logInfo "This will not interfere with any system-level installation." ghcPkgName <- parsePackageNameFromString ("ghc" ++ ghcVariantSuffix ghcVariant ++ compilerBuildSuffix ghcBuild) let tool = Tool $ PackageIdentifier ghcPkgName selectedVersion downloadAndInstallTool (configLocalPrograms config) si (gdiDownloadInfo downloadInfo) tool installer downloadAndInstallCompiler compilerBuild si wanted versionCheck _mbindistUrl = do config <- view configL ghcVariant <- view ghcVariantL case (ghcVariant, compilerBuild) of (GHCStandard, CompilerBuildStandard) -> return () _ -> throwM GHCJSRequiresStandardVariant (selectedVersion, downloadInfo) <- case Map.lookup "source" $ siGHCJSs si of Nothing -> throwM $ UnknownOSKey "source" Just pairs_ -> getWantedCompilerInfo "source" versionCheck wanted id pairs_ $logInfo "Preparing to install GHCJS to an isolated location." $logInfo "This will not interfere with any system-level installation." let tool = ToolGhcjs selectedVersion downloadAndInstallTool (configLocalPrograms config) si downloadInfo tool installGHCJS getWantedCompilerInfo :: (Ord k, MonadThrow m) => Text -> VersionCheck -> CompilerVersion -> (k -> CompilerVersion) -> Map k a -> m (k, a) getWantedCompilerInfo key versionCheck wanted toCV pairs_ = case mpair of Just pair -> return pair Nothing -> throwM $ UnknownCompilerVersion key wanted (map toCV (Map.keys pairs_)) where mpair = listToMaybe $ sortBy (flip (comparing fst)) $ filter (isWantedCompiler versionCheck wanted . toCV . fst) (Map.toList pairs_) getGhcKey :: (MonadReader env m, HasPlatform env, HasGHCVariant env, MonadCatch m) => CompilerBuild -> m Text getGhcKey ghcBuild = do ghcVariant <- view ghcVariantL platform <- view platformL osKey <- getOSKey platform return $ osKey <> T.pack (ghcVariantSuffix ghcVariant) <> T.pack (compilerBuildSuffix ghcBuild) getOSKey :: (MonadThrow m) => Platform -> m Text getOSKey platform = case platform of Platform I386 Cabal.Linux -> return "linux32" Platform X86_64 Cabal.Linux -> return "linux64" Platform I386 Cabal.OSX -> return "macosx" Platform X86_64 Cabal.OSX -> return "macosx" Platform I386 Cabal.FreeBSD -> return "freebsd32" Platform X86_64 Cabal.FreeBSD -> return "freebsd64" Platform I386 Cabal.OpenBSD -> return "openbsd32" Platform X86_64 Cabal.OpenBSD -> return "openbsd64" Platform I386 Cabal.Windows -> return "windows32" Platform X86_64 Cabal.Windows -> return "windows64" Platform Arm Cabal.Linux -> return "linux-armv7" Platform arch os -> throwM $ UnsupportedSetupCombo os arch downloadFromInfo :: StackMiniM env m => Path Abs Dir -> DownloadInfo -> Tool -> m (Path Abs File, ArchiveType) downloadFromInfo programsDir downloadInfo tool = do at <- case extension of ".tar.xz" -> return TarXz ".tar.bz2" -> return TarBz2 ".tar.gz" -> return TarGz ".7z.exe" -> return SevenZ _ -> throwString $ "Error: Unknown extension for url: " ++ url relativeFile <- parseRelFile $ toolString tool ++ extension path <- case url of (parseUrlThrow -> Just _) -> do let path = programsDir relativeFile ensureDir programsDir chattyDownload (T.pack (toolString tool)) downloadInfo path return path (parseAbsFile -> Just path) -> do let DownloadInfo{downloadInfoContentLength=contentLength, downloadInfoSha1=sha1} = downloadInfo when (isJust contentLength) $ $logWarn ("`content-length` in not checked \n" <> "and should not be specified when `url` is a file path") when (isJust sha1) $ $logWarn ("`sha1` is not checked and \n" <> "should not be specified when `url` is a file path") return path _ -> throwString $ "Error: `url` must be either an HTTP URL or absolute file path: " ++ url return (path, at) where url = T.unpack $ downloadInfoUrl downloadInfo extension = loop url where loop fp | ext `elem` [".tar", ".bz2", ".xz", ".exe", ".7z", ".gz"] = loop fp' ++ ext | otherwise = "" where (fp', ext) = FP.splitExtension fp data ArchiveType = TarBz2 | TarXz | TarGz | SevenZ installGHCPosix :: (StackM env m, HasConfig env) => Version -> GHCDownloadInfo -> SetupInfo -> Path Abs File -> ArchiveType -> Path Abs Dir -> Path Abs Dir -> m () installGHCPosix version downloadInfo _ archiveFile archiveType tempDir destDir = do platform <- view platformL menv0 <- getMinimalEnvOverride menv <- mkEnvOverride platform (removeHaskellEnvVars (unEnvOverride menv0)) $logDebug $ "menv = " <> T.pack (show (unEnvOverride menv)) (zipTool', compOpt) <- case archiveType of TarXz -> return ("xz", 'J') TarBz2 -> return ("bzip2", 'j') TarGz -> return ("gzip", 'z') SevenZ -> throwString "Don't know how to deal with .7z files on non-Windows" -- Slight hack: OpenBSD's tar doesn't support xz. -- https://github.com/commercialhaskell/stack/issues/2283#issuecomment-237980986 let tarDep = case (platform, archiveType) of (Platform _ Cabal.OpenBSD, TarXz) -> checkDependency "gtar" _ -> checkDependency "tar" (zipTool, makeTool, tarTool) <- checkDependencies $ (,,) <$> checkDependency zipTool' <*> (checkDependency "gmake" <|> checkDependency "make") <*> tarDep $logDebug $ "ziptool: " <> T.pack zipTool $logDebug $ "make: " <> T.pack makeTool $logDebug $ "tar: " <> T.pack tarTool dir <- liftM (tempDir ) $ parseRelDir $ "ghc-" ++ versionString version let runStep step wd env cmd args = do menv' <- modifyEnvOverride menv (Map.union env) result <- try (readProcessNull (Just wd) menv' cmd args) case result of Right _ -> return () Left ex -> do $logError (T.pack (show (ex :: ReadProcessException))) $prettyError $ hang 2 ("Error encountered while" <+> step <+> "GHC with" <> line <> shellMagenta (fromString (unwords (cmd : args))) <> line <> -- TODO: Figure out how to insert \ in the appropriate spots -- hang 2 (shellMagenta (fillSep (fromString cmd : map fromString args))) <> line <> "run in " <> display wd) <> line <> line <> "The following directories may now contain files, but won't be used by stack:" <> line <> " -" <+> display tempDir <> line <> " -" <+> display destDir <> line liftIO exitFailure $logSticky $ T.concat ["Unpacking GHC into ", T.pack . toFilePath $ tempDir, " ..."] $logDebug $ "Unpacking " <> T.pack (toFilePath archiveFile) runStep "unpacking" tempDir mempty tarTool [compOpt : "xf", toFilePath archiveFile] $logSticky "Configuring GHC ..." runStep "configuring" dir (gdiConfigureEnv downloadInfo) (toFilePath $ dir $(mkRelFile "configure")) (("--prefix=" ++ toFilePath destDir) : map T.unpack (gdiConfigureOpts downloadInfo)) $logSticky "Installing GHC ..." runStep "installing" dir mempty makeTool ["install"] $logStickyDone $ "Installed GHC." $logDebug $ "GHC installed to " <> T.pack (toFilePath destDir) installGHCJS :: (StackM env m, HasConfig env) => SetupInfo -> Path Abs File -> ArchiveType -> Path Abs Dir -> Path Abs Dir -> m () installGHCJS si archiveFile archiveType _tempDir destDir = do platform <- view platformL menv0 <- getMinimalEnvOverride -- This ensures that locking is disabled for the invocations of -- stack below. let removeLockVar = Map.delete "STACK_LOCK" menv <- mkEnvOverride platform (removeLockVar (removeHaskellEnvVars (unEnvOverride menv0))) $logDebug $ "menv = " <> T.pack (show (unEnvOverride menv)) -- NOTE: this is a bit of a hack - instead of using the temp -- directory, leave the unpacked source tarball in the destination -- directory. This way, the absolute paths in the wrapper scripts -- will point to executables that exist in -- src/.stack-work/install/... - see -- https://github.com/commercialhaskell/stack/issues/1016 -- -- This is also used by 'ensureGhcjsBooted', because it can use the -- environment of the stack.yaml which came with ghcjs, in order to -- install cabal-install. This lets us also fix the version of -- cabal-install used. let unpackDir = destDir $(mkRelDir "src") runUnpack <- case platform of Platform _ Cabal.Windows -> return $ withUnpackedTarball7z "GHCJS" si archiveFile archiveType Nothing unpackDir _ -> do zipTool' <- case archiveType of TarXz -> return "xz" TarBz2 -> return "bzip2" TarGz -> return "gzip" SevenZ -> throwString "Don't know how to deal with .7z files on non-Windows" (zipTool, tarTool) <- checkDependencies $ (,) <$> checkDependency zipTool' <*> checkDependency "tar" $logDebug $ "ziptool: " <> T.pack zipTool $logDebug $ "tar: " <> T.pack tarTool return $ do ignoringAbsence (removeDirRecur destDir) ignoringAbsence (removeDirRecur unpackDir) readProcessNull (Just destDir) menv tarTool ["xf", toFilePath archiveFile] innerDir <- expectSingleUnpackedDir archiveFile destDir renameDir innerDir unpackDir $logSticky $ T.concat ["Unpacking GHCJS into ", T.pack . toFilePath $ unpackDir, " ..."] $logDebug $ "Unpacking " <> T.pack (toFilePath archiveFile) runUnpack $logSticky "Setting up GHCJS build environment" let stackYaml = unpackDir $(mkRelFile "stack.yaml") destBinDir = destDir $(mkRelDir "bin") ensureDir destBinDir envConfig' <- loadGhcjsEnvConfig stackYaml destBinDir -- On windows we need to copy options files out of the install dir. Argh! -- This is done before the build, so that if it fails, things fail -- earlier. mwindowsInstallDir <- case platform of Platform _ Cabal.Windows -> liftM Just $ runInnerStackT envConfig' installationRootLocal _ -> return Nothing $logSticky "Installing GHCJS (this will take a long time) ..." runInnerStackT (set (buildOptsL.buildOptsInstallExesL) True $ set (buildOptsL.buildOptsHaddockL) False envConfig') $ build (\_ -> return ()) Nothing defaultBuildOptsCLI -- Copy over *.options files needed on windows. forM_ mwindowsInstallDir $ \dir -> do (_, files) <- listDir (dir $(mkRelDir "bin")) forM_ (filter ((".options" `isSuffixOf`). toFilePath) files) $ \optionsFile -> do let dest = destDir $(mkRelDir "bin") filename optionsFile ignoringAbsence (removeFile dest) copyFile optionsFile dest $logStickyDone "Installed GHCJS." ensureGhcjsBooted :: (StackM env m, HasConfig env) => EnvOverride -> CompilerVersion -> Bool -> [String] -> m () ensureGhcjsBooted menv cv shouldBoot bootOpts = do eres <- try $ sinkProcessStdout Nothing menv "ghcjs" [] (return ()) case eres of Right () -> return () Left (ProcessFailed _ _ _ err) | "no input files" `S.isInfixOf` LBS.toStrict err -> return () Left (ProcessFailed _ _ _ err) | "ghcjs_boot.completed" `S.isInfixOf` LBS.toStrict err -> if not shouldBoot then throwM GHCJSNotBooted else do config <- view configL destDir <- installDir (configLocalPrograms config) (ToolGhcjs cv) let stackYaml = destDir $(mkRelFile "src/stack.yaml") -- TODO: Remove 'actualStackYaml' and just use -- 'stackYaml' for a version after 0.1.6. It's for -- compatibility with the directories setup used for -- most of the life of the development branch between -- 0.1.5 and 0.1.6. See -- https://github.com/commercialhaskell/stack/issues/749#issuecomment-147382783 -- This only affects the case where GHCJS has been -- installed with an older version and not yet booted. stackYamlExists <- doesFileExist stackYaml ghcjsVersion <- case cv of GhcjsVersion version _ -> return version _ -> error "ensureGhcjsBooted invoked on non GhcjsVersion" actualStackYaml <- if stackYamlExists then return stackYaml else liftM ((destDir $(mkRelDir "src")) ) $ parseRelFile $ "ghcjs-" ++ versionString ghcjsVersion ++ "/stack.yaml" actualStackYamlExists <- doesFileExist actualStackYaml unless actualStackYamlExists $ throwString "Error: Couldn't find GHCJS stack.yaml in old or new location." bootGhcjs ghcjsVersion actualStackYaml destDir bootOpts Left err -> throwM err bootGhcjs :: StackM env m => Version -> Path Abs File -> Path Abs Dir -> [String] -> m () bootGhcjs ghcjsVersion stackYaml destDir bootOpts = do envConfig <- loadGhcjsEnvConfig stackYaml (destDir $(mkRelDir "bin")) menv <- liftIO $ configEnvOverride (view configL envConfig) defaultEnvSettings -- Install cabal-install if missing, or if the installed one is old. mcabal <- getCabalInstallVersion menv shouldInstallCabal <- case mcabal of Nothing -> do $logInfo "No cabal-install binary found for use with GHCJS." return True Just v | v < $(mkVersion "1.22.4") -> do $logInfo $ "The cabal-install found on PATH is too old to be used for booting GHCJS (version " <> versionText v <> ")." return True | v >= $(mkVersion "1.23") -> do $logWarn $ "The cabal-install found on PATH is a version stack doesn't know about, version " <> versionText v <> ". This may or may not work.\n" <> "See this issue: https://github.com/ghcjs/ghcjs/issues/470" return False | ghcjsVersion >= $(mkVersion "0.2.0.20160413") && v >= $(mkVersion "1.22.8") -> do $logWarn $ "The cabal-install found on PATH, version " <> versionText v <> ", is >= 1.22.8.\n" <> "That version has a bug preventing ghcjs < 0.2.0.20160413 from booting.\n" <> "See this issue: https://github.com/ghcjs/ghcjs/issues/470" return True | otherwise -> return False let envSettings = defaultEnvSettings { esIncludeGhcPackagePath = False } menv' <- liftIO $ configEnvOverride (view configL envConfig) envSettings when shouldInstallCabal $ do $logInfo "Building a local copy of cabal-install from source." runInnerStackT envConfig $ build (\_ -> return ()) Nothing defaultBuildOptsCLI { boptsCLITargets = ["cabal-install"] } mcabal' <- getCabalInstallVersion menv' case mcabal' of Nothing -> $logError $ "Failed to get cabal-install version after installing it.\n" <> "This shouldn't happen, because it gets built to the snapshot bin directory, which should be treated as being on the PATH." Just v | v >= $(mkVersion "1.22.8") && v < $(mkVersion "1.23") -> $logWarn $ "Installed version of cabal-install is in a version range which may not work.\n" <> "See this issue: https://github.com/ghcjs/ghcjs/issues/470\n" <> "This version is specified by the stack.yaml file included in the ghcjs tarball.\n" _ -> return () $logSticky "Booting GHCJS (this will take a long time) ..." logProcessStderrStdout Nothing "ghcjs-boot" menv' bootOpts $logStickyDone "GHCJS booted." loadGhcjsEnvConfig :: StackM env m => Path Abs File -> Path b t -> m EnvConfig loadGhcjsEnvConfig stackYaml binPath = runInnerStackT () $ do lc <- loadConfig (mempty { configMonoidInstallGHC = First (Just True) , configMonoidLocalBinPath = First (Just (toFilePath binPath)) }) Nothing (SYLOverride stackYaml) bconfig <- lcLoadBuildConfig lc Nothing runInnerStackT bconfig $ setupEnv Nothing getCabalInstallVersion :: (MonadIO m, MonadBaseControl IO m, MonadLogger m, MonadCatch m) => EnvOverride -> m (Maybe Version) getCabalInstallVersion menv = do ebs <- tryProcessStdout Nothing menv "cabal" ["--numeric-version"] case ebs of Left _ -> return Nothing Right bs -> Just <$> parseVersion (T.dropWhileEnd isSpace (T.decodeUtf8 bs)) -- | Check if given processes appear to be present, throwing an exception if -- missing. checkDependencies :: (MonadIO m, MonadThrow m, MonadReader env m, HasConfig env) => CheckDependency a -> m a checkDependencies (CheckDependency f) = do menv <- getMinimalEnvOverride liftIO (f menv) >>= either (throwM . MissingDependencies) return checkDependency :: String -> CheckDependency String checkDependency tool = CheckDependency $ \menv -> do exists <- doesExecutableExist menv tool return $ if exists then Right tool else Left [tool] newtype CheckDependency a = CheckDependency (EnvOverride -> IO (Either [String] a)) deriving Functor instance Applicative CheckDependency where pure x = CheckDependency $ \_ -> return (Right x) CheckDependency f <*> CheckDependency x = CheckDependency $ \menv -> do f' <- f menv x' <- x menv return $ case (f', x') of (Left e1, Left e2) -> Left $ e1 ++ e2 (Left e, Right _) -> Left e (Right _, Left e) -> Left e (Right f'', Right x'') -> Right $ f'' x'' instance Alternative CheckDependency where empty = CheckDependency $ \_ -> return $ Left [] CheckDependency x <|> CheckDependency y = CheckDependency $ \menv -> do res1 <- x menv case res1 of Left _ -> y menv Right x' -> return $ Right x' installGHCWindows :: (StackMiniM env m, HasConfig env) => Version -> SetupInfo -> Path Abs File -> ArchiveType -> Path Abs Dir -> Path Abs Dir -> m () installGHCWindows version si archiveFile archiveType _tempDir destDir = do tarComponent <- parseRelDir $ "ghc-" ++ versionString version withUnpackedTarball7z "GHC" si archiveFile archiveType (Just tarComponent) destDir $logInfo $ "GHC installed to " <> T.pack (toFilePath destDir) installMsys2Windows :: (StackMiniM env m, HasConfig env) => Text -- ^ OS Key -> SetupInfo -> Path Abs File -> ArchiveType -> Path Abs Dir -> Path Abs Dir -> m () installMsys2Windows osKey si archiveFile archiveType _tempDir destDir = do exists <- liftIO $ D.doesDirectoryExist $ toFilePath destDir when exists $ liftIO (D.removeDirectoryRecursive $ toFilePath destDir) `catchIO` \e -> do $logError $ T.pack $ "Could not delete existing msys directory: " ++ toFilePath destDir throwM e msys <- parseRelDir $ "msys" ++ T.unpack (fromMaybe "32" $ T.stripPrefix "windows" osKey) withUnpackedTarball7z "MSYS2" si archiveFile archiveType (Just msys) destDir -- I couldn't find this officially documented anywhere, but you need to run -- the MSYS shell once in order to initialize some pacman stuff. Once that -- run happens, you can just run commands as usual. platform <- view platformL menv0 <- getMinimalEnvOverride newEnv0 <- modifyEnvOverride menv0 $ Map.insert "MSYSTEM" "MSYS" newEnv <- augmentPathMap [destDir $(mkRelDir "usr") $(mkRelDir "bin")] (unEnvOverride newEnv0) menv <- mkEnvOverride platform newEnv runCmd (Cmd (Just destDir) "sh" menv ["--login", "-c", "true"]) Nothing -- No longer installing git, it's unreliable -- (https://github.com/commercialhaskell/stack/issues/1046) and the -- MSYS2-installed version has bad CRLF defaults. -- -- Install git. We could install other useful things in the future too. -- runCmd (Cmd (Just destDir) "pacman" menv ["-Sy", "--noconfirm", "git"]) Nothing -- | Unpack a compressed tarball using 7zip. Expects a single directory in -- the unpacked results, which is renamed to the destination directory. withUnpackedTarball7z :: (StackMiniM env m, HasConfig env) => String -- ^ Name of tool, used in error messages -> SetupInfo -> Path Abs File -- ^ Path to archive file -> ArchiveType -> Maybe (Path Rel Dir) -- ^ Name of directory expected in archive. If Nothing, expects a single folder. -> Path Abs Dir -- ^ Destination directory. -> m () withUnpackedTarball7z name si archiveFile archiveType msrcDir destDir = do suffix <- case archiveType of TarXz -> return ".xz" TarBz2 -> return ".bz2" TarGz -> return ".gz" _ -> throwString $ name ++ " must be a tarball file" tarFile <- case T.stripSuffix suffix $ T.pack $ toFilePath archiveFile of Nothing -> throwString $ "Invalid " ++ name ++ " filename: " ++ show archiveFile Just x -> parseAbsFile $ T.unpack x run7z <- setup7z si let tmpName = toFilePathNoTrailingSep (dirname destDir) ++ "-tmp" ensureDir (parent destDir) withTempDir (parent destDir) tmpName $ \tmpDir -> do ignoringAbsence (removeDirRecur destDir) run7z (parent archiveFile) archiveFile run7z tmpDir tarFile absSrcDir <- case msrcDir of Just srcDir -> return $ tmpDir srcDir Nothing -> expectSingleUnpackedDir archiveFile tmpDir removeFile tarFile `catchIO` \e -> $logWarn (T.concat [ "Exception when removing " , T.pack $ toFilePath tarFile , ": " , T.pack $ show e ]) renameDir absSrcDir destDir expectSingleUnpackedDir :: (MonadIO m, MonadThrow m) => Path Abs File -> Path Abs Dir -> m (Path Abs Dir) expectSingleUnpackedDir archiveFile destDir = do contents <- listDir destDir case contents of ([dir], []) -> return dir _ -> throwString $ "Expected a single directory within unpacked " ++ toFilePath archiveFile -- | Download 7z as necessary, and get a function for unpacking things. -- -- Returned function takes an unpack directory and archive. setup7z :: (MonadIO n, MonadLogger n, StackMiniM env m, HasConfig env) => SetupInfo -> m (Path Abs Dir -> Path Abs File -> n ()) setup7z si = do dir <- view $ configL.to configLocalPrograms ensureDir dir let exe = dir $(mkRelFile "7z.exe") dll = dir $(mkRelFile "7z.dll") case (siSevenzDll si, siSevenzExe si) of (Just sevenzDll, Just sevenzExe) -> do chattyDownload "7z.dll" sevenzDll dll chattyDownload "7z.exe" sevenzExe exe return $ \outdir archive -> do let cmd = toFilePath exe args = [ "x" , "-o" ++ toFilePath outdir , "-y" , toFilePath archive ] ec <- $withProcessTimeLog cmd args $ liftIO $ rawSystem cmd args when (ec /= ExitSuccess) $ liftIO $ throwM (ProblemWhileDecompressing archive) _ -> throwM SetupInfoMissingSevenz chattyDownload :: StackMiniM env m => Text -- ^ label -> DownloadInfo -- ^ URL, content-length, and sha1 -> Path Abs File -- ^ destination -> m () chattyDownload label downloadInfo path = do let url = downloadInfoUrl downloadInfo req <- parseUrlThrow $ T.unpack url $logSticky $ T.concat [ "Preparing to download " , label , " ..." ] $logDebug $ T.concat [ "Downloading from " , url , " to " , T.pack $ toFilePath path , " ..." ] hashChecks <- case downloadInfoSha1 downloadInfo of Just sha1ByteString -> do let sha1 = CheckHexDigestByteString sha1ByteString $logDebug $ T.concat [ "Will check against sha1 hash: " , T.decodeUtf8With T.lenientDecode sha1ByteString ] return [HashCheck SHA1 sha1] Nothing -> do $logWarn $ T.concat [ "No sha1 found in metadata," , " download hash won't be checked." ] return [] let dReq = DownloadRequest { drRequest = req , drHashChecks = hashChecks , drLengthCheck = mtotalSize , drRetryPolicy = drRetryPolicyDefault } runInBase <- liftBaseWith $ \run -> return (void . run) x <- verifiedDownload dReq path (chattyDownloadProgress runInBase) if x then $logStickyDone ("Downloaded " <> label <> ".") else $logStickyDone "Already downloaded." where mtotalSize = downloadInfoContentLength downloadInfo chattyDownloadProgress runInBase _ = do _ <- liftIO $ runInBase $ $logSticky $ label <> ": download has begun" CL.map (Sum . S.length) =$ chunksOverTime 1 =$ go where go = evalStateC 0 $ awaitForever $ \(Sum size) -> do modify (+ size) totalSoFar <- get liftIO $ runInBase $ $logSticky $ T.pack $ case mtotalSize of Nothing -> chattyProgressNoTotal totalSoFar Just 0 -> chattyProgressNoTotal totalSoFar Just totalSize -> chattyProgressWithTotal totalSoFar totalSize -- Example: ghc: 42.13 KiB downloaded... chattyProgressNoTotal totalSoFar = printf ("%s: " <> bytesfmt "%7.2f" totalSoFar <> " downloaded...") (T.unpack label) -- Example: ghc: 50.00 MiB / 100.00 MiB (50.00%) downloaded... chattyProgressWithTotal totalSoFar total = printf ("%s: " <> bytesfmt "%7.2f" totalSoFar <> " / " <> bytesfmt "%.2f" total <> " (%6.2f%%) downloaded...") (T.unpack label) percentage where percentage :: Double percentage = fromIntegral totalSoFar / fromIntegral total * 100 -- | Given a printf format string for the decimal part and a number of -- bytes, formats the bytes using an appropiate unit and returns the -- formatted string. -- -- >>> bytesfmt "%.2" 512368 -- "500.359375 KiB" bytesfmt :: Integral a => String -> a -> String bytesfmt formatter bs = printf (formatter <> " %s") (fromIntegral (signum bs) * dec :: Double) (bytesSuffixes !! i) where (dec,i) = getSuffix (abs bs) getSuffix n = until p (\(x,y) -> (x / 1024, y+1)) (fromIntegral n,0) where p (n',numDivs) = n' < 1024 || numDivs == (length bytesSuffixes - 1) bytesSuffixes :: [String] bytesSuffixes = ["B","KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"] -- Await eagerly (collect with monoidal append), -- but space out yields by at least the given amount of time. -- The final yield may come sooner, and may be a superfluous mempty. -- Note that Integer and Float literals can be turned into NominalDiffTime -- (these literals are interpreted as "seconds") chunksOverTime :: (Monoid a, MonadIO m) => NominalDiffTime -> Conduit a m a chunksOverTime diff = do currentTime <- liftIO getCurrentTime evalStateC (currentTime, mempty) go where -- State is a tuple of: -- * the last time a yield happened (or the beginning of the sink) -- * the accumulated awaits since the last yield go = await >>= \case Nothing -> do (_, acc) <- get yield acc Just a -> do (lastTime, acc) <- get let acc' = acc <> a currentTime <- liftIO getCurrentTime if diff < diffUTCTime currentTime lastTime then put (currentTime, mempty) >> yield acc' else put (lastTime, acc') go -- | Perform a basic sanity check of GHC sanityCheck :: (MonadIO m, MonadMask m, MonadLogger m, MonadBaseControl IO m) => EnvOverride -> WhichCompiler -> m () sanityCheck menv wc = withSystemTempDir "stack-sanity-check" $ \dir -> do let fp = toFilePath $ dir $(mkRelFile "Main.hs") liftIO $ writeFile fp $ unlines [ "import Distribution.Simple" -- ensure Cabal library is present , "main = putStrLn \"Hello World\"" ] let exeName = compilerExeName wc ghc <- join $ findExecutable menv exeName $logDebug $ "Performing a sanity check on: " <> T.pack (toFilePath ghc) eres <- tryProcessStdout (Just dir) menv exeName [ fp , "-no-user-package-db" ] case eres of Left e -> throwM $ GHCSanityCheckCompileFailed e ghc Right _ -> return () -- TODO check that the output of running the command is correct -- Remove potentially confusing environment variables removeHaskellEnvVars :: Map Text Text -> Map Text Text removeHaskellEnvVars = Map.delete "GHCJS_PACKAGE_PATH" . Map.delete "GHC_PACKAGE_PATH" . Map.delete "HASKELL_PACKAGE_SANDBOX" . Map.delete "HASKELL_PACKAGE_SANDBOXES" . Map.delete "HASKELL_DIST_DIR" . -- https://github.com/commercialhaskell/stack/issues/1460 Map.delete "DESTDIR" -- | Get map of environment variables to set to change the GHC's encoding to UTF-8 getUtf8EnvVars :: forall m env. (MonadReader env m, HasPlatform env, MonadLogger m, MonadCatch m, MonadBaseControl IO m, MonadIO m) => EnvOverride -> CompilerVersion -> m (Map Text Text) getUtf8EnvVars menv compilerVer = if getGhcVersion compilerVer >= $(mkVersion "7.10.3") -- GHC_CHARENC supported by GHC >=7.10.3 then return $ Map.singleton "GHC_CHARENC" "UTF-8" else legacyLocale where legacyLocale = do Platform _ os <- view platformL if os == Cabal.Windows then -- On Windows, locale is controlled by the code page, so we don't set any environment -- variables. return Map.empty else do let checkedVars = map checkVar (Map.toList $ eoTextMap menv) -- List of environment variables that will need to be updated to set UTF-8 (because -- they currently do not specify UTF-8). needChangeVars = concatMap fst checkedVars -- Set of locale-related environment variables that have already have a value. existingVarNames = Set.unions (map snd checkedVars) -- True if a locale is already specified by one of the "global" locale variables. hasAnyExisting = any (`Set.member` existingVarNames) ["LANG", "LANGUAGE", "LC_ALL"] if null needChangeVars && hasAnyExisting then -- If no variables need changes and at least one "global" variable is set, no -- changes to environment need to be made. return Map.empty else do -- Get a list of known locales by running @locale -a@. elocales <- tryProcessStdout Nothing menv "locale" ["-a"] let -- Filter the list to only include locales with UTF-8 encoding. utf8Locales = case elocales of Left _ -> [] Right locales -> filter isUtf8Locale (T.lines $ T.decodeUtf8With T.lenientDecode locales) mfallback = getFallbackLocale utf8Locales when (isNothing mfallback) ($logWarn "Warning: unable to set locale to UTF-8 encoding; GHC may fail with 'invalid character'") let -- Get the new values of variables to adjust. changes = Map.unions $ map (adjustedVarValue utf8Locales mfallback) needChangeVars -- Get the values of variables to add. adds | hasAnyExisting = -- If we already have a "global" variable, then nothing needs -- to be added. Map.empty | otherwise = -- If we don't already have a "global" variable, then set LANG to the -- fallback. case mfallback of Nothing -> Map.empty Just fallback -> Map.singleton "LANG" fallback return (Map.union changes adds) -- Determines whether an environment variable is locale-related and, if so, whether it needs to -- be adjusted. checkVar :: (Text, Text) -> ([Text], Set Text) checkVar (k,v) = if k `elem` ["LANG", "LANGUAGE"] || "LC_" `T.isPrefixOf` k then if isUtf8Locale v then ([], Set.singleton k) else ([k], Set.singleton k) else ([], Set.empty) -- Adjusted value of an existing locale variable. Looks for valid UTF-8 encodings with -- same language /and/ territory, then with same language, and finally the first UTF-8 locale -- returned by @locale -a@. adjustedVarValue :: [Text] -> Maybe Text -> Text -> Map Text Text adjustedVarValue utf8Locales mfallback k = case Map.lookup k (eoTextMap menv) of Nothing -> Map.empty Just v -> case concatMap (matchingLocales utf8Locales) [ T.takeWhile (/= '.') v <> "." , T.takeWhile (/= '_') v <> "_"] of (v':_) -> Map.singleton k v' [] -> case mfallback of Just fallback -> Map.singleton k fallback Nothing -> Map.empty -- Determine the fallback locale, by looking for any UTF-8 locale prefixed with the list in -- @fallbackPrefixes@, and if not found, picking the first UTF-8 encoding returned by @locale -- -a@. getFallbackLocale :: [Text] -> Maybe Text getFallbackLocale utf8Locales = case concatMap (matchingLocales utf8Locales) fallbackPrefixes of (v:_) -> Just v [] -> case utf8Locales of [] -> Nothing (v:_) -> Just v -- Filter the list of locales for any with the given prefixes (case-insitive). matchingLocales :: [Text] -> Text -> [Text] matchingLocales utf8Locales prefix = filter (\v -> T.toLower prefix `T.isPrefixOf` T.toLower v) utf8Locales -- Does the locale have one of the encodings in @utf8Suffixes@ (case-insensitive)? isUtf8Locale locale = any (\ v -> T.toLower v `T.isSuffixOf` T.toLower locale) utf8Suffixes -- Prefixes of fallback locales (case-insensitive) fallbackPrefixes = ["C.", "en_US.", "en_"] -- Suffixes of UTF-8 locales (case-insensitive) utf8Suffixes = [".UTF-8", ".utf8"] -- Binary Stack upgrades newtype StackReleaseInfo = StackReleaseInfo Value downloadStackReleaseInfo :: (MonadIO m, MonadThrow m) => Maybe String -- Github org -> Maybe String -- Github repo -> Maybe String -- ^ optional version -> m StackReleaseInfo downloadStackReleaseInfo morg mrepo mver = liftIO $ do let org = fromMaybe "commercialhaskell" morg repo = fromMaybe "stack" mrepo let url = concat [ "https://api.github.com/repos/" , org , "/" , repo , "/releases/" , case mver of Nothing -> "latest" Just ver -> "tags/v" ++ ver ] req <- parseRequest url res <- httpJSON $ setGithubHeaders req let code = getResponseStatusCode res if code >= 200 && code < 300 then return $ StackReleaseInfo $ getResponseBody res else throwString $ "Could not get release information for Stack from: " ++ url preferredPlatforms :: (MonadReader env m, HasPlatform env) => m [(Bool, String)] preferredPlatforms = do Platform arch' os' <- view platformL (isWindows, os) <- case os' of Cabal.Linux -> return (False, "linux") Cabal.Windows -> return (True, "windows") Cabal.OSX -> return (False, "osx") Cabal.FreeBSD -> return (False, "freebsd") _ -> errorString $ "Binary upgrade not yet supported on OS: " ++ show os' arch <- case arch' of I386 -> return "i386" X86_64 -> return "x86_64" Arm -> return "arm" _ -> errorString $ "Binary upgrade not yet supported on arch: " ++ show arch' hasgmp4 <- return False -- FIXME import relevant code from Stack.Setup? checkLib $(mkRelFile "libgmp.so.3") let suffixes | hasgmp4 = ["-static", "-gmp4", ""] | otherwise = ["-static", ""] return $ map (\suffix -> (isWindows, concat [os, "-", arch, suffix])) suffixes downloadStackExe :: (MonadIO m, MonadLogger m, MonadThrow m, MonadReader env m, HasConfig env) => [(Bool, String)] -- ^ acceptable platforms -> StackReleaseInfo -> Path Abs Dir -- ^ destination directory -> (Path Abs File -> IO ()) -- ^ test the temp exe before renaming -> m () downloadStackExe platforms0 archiveInfo destDir testExe = do (isWindows, archiveURL) <- let loop [] = throwString $ "Unable to find binary Stack archive for platforms: " ++ unwords (map snd platforms0) loop ((isWindows, p'):ps) = do let p = T.pack p' $logInfo $ "Querying for archive location for platform: " <> p case findArchive archiveInfo p of Just x -> return (isWindows, x) Nothing -> loop ps in loop platforms0 let (destFile, tmpFile) | isWindows = ( destDir $(mkRelFile "stack.exe") , destDir $(mkRelFile "stack.tmp.exe") ) | otherwise = ( destDir $(mkRelFile "stack") , destDir $(mkRelFile "stack.tmp") ) $logInfo $ "Downloading from: " <> archiveURL liftIO $ do case () of () | ".tar.gz" `T.isSuffixOf` archiveURL -> handleTarball tmpFile isWindows archiveURL | ".zip" `T.isSuffixOf` archiveURL -> error "FIXME: Handle zip files" | otherwise -> error $ "Unknown archive format for Stack archive: " ++ T.unpack archiveURL $logInfo "Download complete, testing executable" platform <- view platformL liftIO $ do #if !WINDOWS setFileMode (toFilePath tmpFile) 0o755 #endif testExe tmpFile currExe <- getExecutablePath case platform of Platform _ Cabal.Windows | FP.equalFilePath (toFilePath destFile) currExe -> do old <- parseAbsFile (toFilePath destFile ++ ".old") renameFile destFile old renameFile tmpFile destFile _ -> renameFile tmpFile destFile destDir' <- liftIO . D.canonicalizePath . toFilePath $ destDir warnInstallSearchPathIssues destDir' ["stack"] $logInfo $ T.pack $ "New stack executable available at " ++ toFilePath destFile where findArchive (StackReleaseInfo val) pattern = do Object top <- return val Array assets <- HashMap.lookup "assets" top getFirst $ fold $ fmap (First . findMatch pattern') assets where pattern' = mconcat ["-", pattern, "."] findMatch pattern'' (Object o) = do String name <- HashMap.lookup "name" o guard $ not $ ".asc" `T.isSuffixOf` name guard $ pattern'' `T.isInfixOf` name String url <- HashMap.lookup "browser_download_url" o Just url findMatch _ _ = Nothing handleTarball :: Path Abs File -> Bool -> T.Text -> IO () handleTarball tmpFile isWindows url = do req <- fmap setGithubHeaders $ parseUrlThrow $ T.unpack url withResponse req $ \res -> do entries <- fmap (Tar.read . LBS.fromChunks) $ lazyConsume $ getResponseBody res .| ungzip let loop Tar.Done = error $ concat [ "Stack executable " , show exeName , " not found in archive from " , T.unpack url ] loop (Tar.Fail e) = throwM e loop (Tar.Next e es) | Tar.entryPath e == exeName = case Tar.entryContent e of Tar.NormalFile lbs _ -> do ensureDir destDir LBS.writeFile (toFilePath tmpFile) lbs _ -> error $ concat [ "Invalid file type for tar entry named " , exeName , " downloaded from " , T.unpack url ] | otherwise = loop es loop entries where -- The takeBaseName drops the .gz, dropExtension drops the .tar exeName = let base = FP.dropExtension (FP.takeBaseName (T.unpack url)) FP. "stack" in if isWindows then base FP.<.> "exe" else base getDownloadVersion :: StackReleaseInfo -> Maybe Version getDownloadVersion (StackReleaseInfo val) = do Object o <- Just val String rawName <- HashMap.lookup "name" o -- drop the "v" at the beginning of the name parseVersion $ T.drop 1 rawName stackVersion :: Version stackVersion = fromCabalVersion Meta.version stack-1.5.1/src/Stack/Setup/Installed.hs0000644000000000000000000001660513135652051016231 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE LambdaCase #-} module Stack.Setup.Installed ( getCompilerVersion , markInstalled , unmarkInstalled , listInstalled , Tool (..) , toolString , toolNameString , parseToolText , ExtraDirs (..) , extraDirs , installDir , tempInstallDir ) where import Control.Applicative import Control.Monad.Catch import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Logger import Control.Monad.Reader (MonadReader) import Control.Monad.Trans.Control import qualified Data.ByteString.Char8 as S8 import Data.List hiding (concat, elem, maximumBy) import Data.Maybe import Data.Monoid import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T import Distribution.System (Platform (..)) import qualified Distribution.System as Cabal import GHC.Generics (Generic) import Generics.Deriving.Monoid (mappenddefault, memptydefault) import Path import Path.IO import Prelude hiding (concat, elem) -- Fix AMP warning import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.StackT import Stack.Types.Version import System.Process.Read data Tool = Tool PackageIdentifier -- ^ e.g. ghc-7.8.4, msys2-20150512 | ToolGhcjs CompilerVersion -- ^ e.g. ghcjs-0.1.0_ghc-7.10.2 toolString :: Tool -> String toolString (Tool ident) = packageIdentifierString ident toolString (ToolGhcjs cv) = compilerVersionString cv toolNameString :: Tool -> String toolNameString (Tool ident) = packageNameString $ packageIdentifierName ident toolNameString ToolGhcjs{} = "ghcjs" parseToolText :: Text -> Maybe Tool parseToolText (parseCompilerVersion -> Just (cv@GhcjsVersion{})) = Just (ToolGhcjs cv) parseToolText (parsePackageIdentifierFromString . T.unpack -> Just pkgId) = Just (Tool pkgId) parseToolText _ = Nothing markInstalled :: (MonadIO m, MonadThrow m) => Path Abs Dir -> Tool -> m () markInstalled programsPath tool = do fpRel <- parseRelFile $ toolString tool ++ ".installed" liftIO $ writeFile (toFilePath $ programsPath fpRel) "installed" unmarkInstalled :: (MonadIO m, MonadCatch m) => Path Abs Dir -> Tool -> m () unmarkInstalled programsPath tool = do fpRel <- parseRelFile $ toolString tool ++ ".installed" ignoringAbsence (removeFile $ programsPath fpRel) listInstalled :: (MonadIO m, MonadThrow m) => Path Abs Dir -> m [Tool] listInstalled programsPath = do doesDirExist programsPath >>= \case False -> return [] True -> do (_, files) <- listDir programsPath return $ mapMaybe toTool files where toTool fp = do x <- T.stripSuffix ".installed" $ T.pack $ toFilePath $ filename fp parseToolText x getCompilerVersion :: (MonadLogger m, MonadCatch m, MonadBaseControl IO m, MonadIO m) => EnvOverride -> WhichCompiler -> m CompilerVersion getCompilerVersion menv wc = case wc of Ghc -> do $logDebug "Asking GHC for its version" bs <- readProcessStdout Nothing menv "ghc" ["--numeric-version"] let (_, ghcVersion) = versionFromEnd bs GhcVersion <$> parseVersion (T.decodeUtf8 ghcVersion) Ghcjs -> do $logDebug "Asking GHCJS for its version" -- Output looks like -- -- The Glorious Glasgow Haskell Compilation System for JavaScript, version 0.1.0 (GHC 7.10.2) bs <- readProcessStdout Nothing menv "ghcjs" ["--version"] let (rest, ghcVersion) = T.decodeUtf8 <$> versionFromEnd bs (_, ghcjsVersion) = T.decodeUtf8 <$> versionFromEnd rest GhcjsVersion <$> parseVersion ghcjsVersion <*> parseVersion ghcVersion where versionFromEnd = S8.spanEnd isValid . fst . S8.breakEnd isValid isValid c = c == '.' || ('0' <= c && c <= '9') -- | Binary directories for the given installed package extraDirs :: (StackM env m, HasConfig env) => Tool -> m ExtraDirs extraDirs tool = do config <- view configL dir <- installDir (configLocalPrograms config) tool case (configPlatform config, toolNameString tool) of (Platform _ Cabal.Windows, isGHC -> True) -> return mempty { edBins = [ dir $(mkRelDir "bin") , dir $(mkRelDir "mingw") $(mkRelDir "bin") ] } (Platform Cabal.I386 Cabal.Windows, "msys2") -> return mempty { edBins = [ dir $(mkRelDir "mingw32") $(mkRelDir "bin") , dir $(mkRelDir "usr") $(mkRelDir "bin") , dir $(mkRelDir "usr") $(mkRelDir "local") $(mkRelDir "bin") ] , edInclude = [ dir $(mkRelDir "mingw32") $(mkRelDir "include") ] , edLib = [ dir $(mkRelDir "mingw32") $(mkRelDir "lib") , dir $(mkRelDir "mingw32") $(mkRelDir "bin") ] } (Platform Cabal.X86_64 Cabal.Windows, "msys2") -> return mempty { edBins = [ dir $(mkRelDir "mingw64") $(mkRelDir "bin") , dir $(mkRelDir "usr") $(mkRelDir "bin") , dir $(mkRelDir "usr") $(mkRelDir "local") $(mkRelDir "bin") ] , edInclude = [ dir $(mkRelDir "mingw64") $(mkRelDir "include") ] , edLib = [ dir $(mkRelDir "mingw64") $(mkRelDir "lib") , dir $(mkRelDir "mingw64") $(mkRelDir "bin") ] } (_, isGHC -> True) -> return mempty { edBins = [ dir $(mkRelDir "bin") ] } (_, isGHCJS -> True) -> return mempty { edBins = [ dir $(mkRelDir "bin") ] } (Platform _ x, toolName) -> do $logWarn $ "binDirs: unexpected OS/tool combo: " <> T.pack (show (x, toolName)) return mempty where isGHC n = "ghc" == n || "ghc-" `isPrefixOf` n isGHCJS n = "ghcjs" == n data ExtraDirs = ExtraDirs { edBins :: ![Path Abs Dir] , edInclude :: ![Path Abs Dir] , edLib :: ![Path Abs Dir] } deriving (Show, Generic) instance Monoid ExtraDirs where mempty = memptydefault mappend = mappenddefault installDir :: (MonadReader env m, MonadThrow m) => Path Abs Dir -> Tool -> m (Path Abs Dir) installDir programsDir tool = do relativeDir <- parseRelDir $ toolString tool return $ programsDir relativeDir tempInstallDir :: (MonadReader env m, MonadThrow m) => Path Abs Dir -> Tool -> m (Path Abs Dir) tempInstallDir programsDir tool = do relativeDir <- parseRelDir $ toolString tool ++ ".temp" return $ programsDir relativeDir stack-1.5.1/src/Stack/SetupCmd.hs0000644000000000000000000001225313135652051014731 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} -- | Install GHC/GHCJS and Cabal. module Stack.SetupCmd ( setup , setupParser , SetupCmdOpts(..) ) where import Control.Applicative import Control.Monad.Logger import Control.Monad.Reader import Data.Monoid import qualified Data.Text as T import qualified Options.Applicative as OA import qualified Options.Applicative.Builder.Extra as OA import qualified Options.Applicative.Types as OA import Path import Prelude -- silence redundant import warnings import Stack.Setup import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.StackT import Stack.Types.Version data SetupCmdOpts = SetupCmdOpts { scoCompilerVersion :: !(Maybe CompilerVersion) , scoForceReinstall :: !Bool , scoUpgradeCabal :: !(Maybe UpgradeTo) , scoSetupInfoYaml :: !String , scoGHCBindistURL :: !(Maybe String) , scoGHCJSBootOpts :: ![String] , scoGHCJSBootClean :: !Bool } setupYamlCompatParser :: OA.Parser String setupYamlCompatParser = stackSetupYaml <|> setupInfoYaml where stackSetupYaml = OA.strOption ( OA.long "stack-setup-yaml" <> OA.help "DEPRECATED: Use 'setup-info-yaml' instead" <> OA.metavar "URL" <> OA.hidden ) setupInfoYaml = OA.strOption ( OA.long "setup-info-yaml" <> OA.help "Alternate URL or absolute path for stack dependencies" <> OA.metavar "URL" <> OA.value defaultSetupInfoYaml ) cabalUpgradeParser :: OA.Parser UpgradeTo cabalUpgradeParser = Specific <$> version' <|> latestParser where versionReader = do s <- OA.readerAsk case parseVersion (T.pack s) of Nothing -> OA.readerError $ "Invalid version: " ++ s Just v -> return v version' = OA.option versionReader ( OA.long "install-cabal" <> OA.metavar "VERSION" <> OA.help "Install a specific version of Cabal" ) latestParser = OA.flag' Latest ( OA.long "upgrade-cabal" <> OA.help "Install latest version of Cabal globally" ) setupParser :: OA.Parser SetupCmdOpts setupParser = SetupCmdOpts <$> OA.optional (OA.argument readVersion (OA.metavar "GHC_VERSION" <> OA.help ("Version of GHC to install, e.g. 7.10.2. " ++ "The default is to install the version implied by the resolver."))) <*> OA.boolFlags False "reinstall" "reinstalling GHC, even if available (incompatible with --system-ghc)" OA.idm <*> OA.optional cabalUpgradeParser <*> setupYamlCompatParser <*> OA.optional (OA.strOption (OA.long "ghc-bindist" <> OA.metavar "URL" <> OA.help "Alternate GHC binary distribution (requires custom --ghc-variant)")) <*> OA.many (OA.strOption (OA.long "ghcjs-boot-options" <> OA.metavar "GHCJS_BOOT" <> OA.help "Additional ghcjs-boot options")) <*> OA.boolFlags True "ghcjs-boot-clean" "Control if ghcjs-boot should have --clean option present" OA.idm where readVersion = do s <- OA.readerAsk case parseCompilerVersion ("ghc-" <> T.pack s) of Nothing -> case parseCompilerVersion (T.pack s) of Nothing -> OA.readerError $ "Invalid version: " ++ s Just x -> return x Just x -> return x setup :: (StackM env m, HasConfig env, HasGHCVariant env) => SetupCmdOpts -> CompilerVersion -> VersionCheck -> Maybe (Path Abs File) -> m () setup SetupCmdOpts{..} wantedCompiler compilerCheck mstack = do Config{..} <- view configL (_, _, sandboxedGhc) <- ensureCompiler SetupOpts { soptsInstallIfMissing = True , soptsUseSystem = configSystemGHC && not scoForceReinstall , soptsWantedCompiler = wantedCompiler , soptsCompilerCheck = compilerCheck , soptsStackYaml = mstack , soptsForceReinstall = scoForceReinstall , soptsSanityCheck = True , soptsSkipGhcCheck = False , soptsSkipMsys = configSkipMsys , soptsUpgradeCabal = scoUpgradeCabal , soptsResolveMissingGHC = Nothing , soptsSetupInfoYaml = scoSetupInfoYaml , soptsGHCBindistURL = scoGHCBindistURL , soptsGHCJSBootOpts = scoGHCJSBootOpts ++ ["--clean" | scoGHCJSBootClean] } let compiler = case wantedCompiler of GhcVersion _ -> "GHC" GhcjsVersion {} -> "GHCJS" if sandboxedGhc then $logInfo $ "stack will use a sandboxed " <> compiler <> " it installed" else $logInfo $ "stack will use the " <> compiler <> " on your PATH" $logInfo "For more information on paths, see 'stack path' and 'stack exec env'" $logInfo $ "To use this " <> compiler <> " and packages outside of a project, consider using:" $logInfo "stack ghc, stack ghci, stack runghc, or stack exec" stack-1.5.1/src/Stack/Sig.hs0000644000000000000000000000047613135652051013733 0ustar0000000000000000{-| Module : Stack.Sig Description : GPG Signatures for Stack Copyright : (c) FPComplete.com, 2015 License : BSD3 Maintainer : Tim Dysinger Stability : experimental Portability : POSIX -} module Stack.Sig (module Sig) where import Stack.Sig.GPG as Sig import Stack.Sig.Sign as Sig stack-1.5.1/src/Stack/Sig/GPG.hs0000644000000000000000000000770413135652051014351 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TemplateHaskell #-} {-| Module : Stack.Sig.GPG Description : GPG Functions Copyright : (c) FPComplete.com, 2015 License : BSD3 Maintainer : Tim Dysinger Stability : experimental Portability : POSIX -} module Stack.Sig.GPG (gpgSign, gpgVerify) where import Prelude () import Prelude.Compat import Control.Monad (unless, when) import Control.Monad.Catch (MonadThrow, throwM) import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Logger (MonadLogger, logWarn) import qualified Data.ByteString.Char8 as C import Data.List (find, isPrefixOf) import Data.Monoid ((<>)) import qualified Data.Text as T import Path import Stack.Types.Sig import System.Directory (findExecutable) import System.Environment (lookupEnv) import System.Exit (ExitCode(..)) import System.IO (Handle, hGetContents, hPutStrLn) import System.Info (os) import System.Process (ProcessHandle, runInteractiveProcess, waitForProcess) -- | Sign a file path with GPG, returning the @Signature@. gpgSign :: (MonadIO m, MonadLogger m, MonadThrow m) => Path Abs File -> m Signature gpgSign path = do gpgWarnTTY (_hIn,hOut,hErr,process) <- gpg [ "--output" , "-" , "--use-agent" , "--detach-sig" , "--armor" , toFilePath path] (out,err,code) <- liftIO ((,,) <$> hGetContents hOut <*> hGetContents hErr <*> waitForProcess process) if code /= ExitSuccess then throwM (GPGSignException $ out <> "\n" <> err) else return (Signature $ C.pack out) -- | Verify the @Signature@ of a file path returning the -- @Fingerprint@. gpgVerify :: (MonadIO m, MonadThrow m) => Signature -> Path Abs File -> m Fingerprint gpgVerify (Signature signature) path = do (hIn,hOut,hErr,process) <- gpg ["--verify", "--with-fingerprint", "-", toFilePath path] (_in,out,err,code) <- liftIO ((,,,) <$> hPutStrLn hIn (C.unpack signature) <*> hGetContents hOut <*> hGetContents hErr <*> waitForProcess process) if code /= ExitSuccess then throwM (GPGVerifyException (out ++ "\n" ++ err)) else maybe (throwM (GPGFingerprintException ("unable to extract fingerprint from output\n: " <> out))) return (mkFingerprint . T.pack . concat . drop 3 <$> find ((==) ["Primary", "key", "fingerprint:"] . take 3) (map words (lines err))) -- | Try to execute `gpg2` but fallback to `gpg` (as a backup) gpg :: (MonadIO m, MonadThrow m) => [String] -> m (Handle, Handle, Handle, ProcessHandle) gpg args = do mGpg2Path <- liftIO (findExecutable "gpg2") case mGpg2Path of Just _ -> liftIO (runInteractiveProcess "gpg2" args Nothing Nothing) Nothing -> do mGpgPath <- liftIO (findExecutable "gpg") case mGpgPath of Just _ -> liftIO (runInteractiveProcess "gpg" args Nothing Nothing) Nothing -> throwM GPGNotFoundException -- | `man gpg-agent` shows that you need GPG_TTY environment variable set to -- properly deal with interactions with gpg-agent. (Doesn't apply to Windows -- though) gpgWarnTTY :: (MonadIO m, MonadLogger m) => m () gpgWarnTTY = unless ("ming" `isPrefixOf` os) (do mTTY <- liftIO (lookupEnv "GPG_TTY") when (null mTTY) ($logWarn "Environment variable GPG_TTY is not set (see `man gpg-agent`)")) stack-1.5.1/src/Stack/Sig/Sign.hs0000644000000000000000000001122713135652051014627 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} {-| Module : Stack.Sig.Sign Description : Signing Packages Copyright : (c) FPComplete.com, 2015 License : BSD3 Maintainer : Tim Dysinger Stability : experimental Portability : POSIX -} module Stack.Sig.Sign (sign, signPackage, signTarBytes) where import Prelude () import Prelude.Compat import qualified Codec.Archive.Tar as Tar import qualified Codec.Compression.GZip as GZip import Control.Monad (when) import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.Logger import qualified Data.ByteString.Lazy as BS import qualified Data.ByteString.Lazy as L import Data.Monoid ((<>)) import qualified Data.Text as T import Network.HTTP.Client (RequestBody (RequestBodyBS)) import Network.HTTP.Download import Network.HTTP.Simple import Network.HTTP.Types (methodPut) import Path import Path.IO import Stack.Package import Stack.Sig.GPG import Stack.Types.PackageIdentifier import Stack.Types.Sig import qualified System.FilePath as FP -- | Sign a haskell package with the given url of the signature -- service and a path to a tarball. sign #if __GLASGOW_HASKELL__ < 710 :: (Applicative m, MonadIO m, MonadLogger m, MonadMask m) #else :: (MonadIO m, MonadLogger m, MonadMask m) #endif => String -> Path Abs File -> m Signature sign url filePath = withSystemTempDir "stack" (\tempDir -> do bytes <- liftIO (fmap GZip.decompress (BS.readFile (toFilePath filePath))) maybePath <- extractCabalFile tempDir (Tar.read bytes) case maybePath of Nothing -> throwM SigInvalidSDistTarBall Just cabalPath -> do pkg <- cabalFilePackageId (tempDir cabalPath) signPackage url pkg filePath) where extractCabalFile tempDir (Tar.Next entry entries) = case Tar.entryContent entry of (Tar.NormalFile lbs _) -> case FP.splitFileName (Tar.entryPath entry) of (folder,file) | length (FP.splitDirectories folder) == 1 && FP.takeExtension file == ".cabal" -> do cabalFile <- parseRelFile file liftIO (BS.writeFile (toFilePath (tempDir cabalFile)) lbs) return (Just cabalFile) (_,_) -> extractCabalFile tempDir entries _ -> extractCabalFile tempDir entries extractCabalFile _ _ = return Nothing -- | Sign a haskell package with the given url to the signature -- service, a package tarball path (package tarball name) and a lazy -- bytestring of bytes that represent the tarball bytestream. The -- function will write the bytes to the path in a temp dir and sign -- the tarball with GPG. signTarBytes #if __GLASGOW_HASKELL__ < 710 :: (Applicative m, MonadIO m, MonadLogger m, MonadMask m) #else :: (MonadIO m, MonadLogger m, MonadMask m) #endif => String -> Path Rel File -> L.ByteString -> m Signature signTarBytes url tarPath bs = withSystemTempDir "stack" (\tempDir -> do let tempTarBall = tempDir tarPath liftIO (L.writeFile (toFilePath tempTarBall) bs) sign url tempTarBall) -- | Sign a haskell package given the url to the signature service, a -- @PackageIdentifier@ and a file path to the package on disk. signPackage :: (MonadIO m, MonadLogger m, MonadThrow m) => String -> PackageIdentifier -> Path Abs File -> m Signature signPackage url pkg filePath = do sig@(Signature signature) <- gpgSign filePath let (PackageIdentifier name version) = pkg fingerprint <- gpgVerify sig filePath let fullUrl = url <> "/upload/signature/" <> show name <> "/" <> show version <> "/" <> show fingerprint req <- parseUrlThrow fullUrl let put = setRequestMethod methodPut $ setRequestBody (RequestBodyBS signature) req res <- liftIO (httpLbs put) when (getResponseStatusCode res /= 200) (throwM (GPGSignException "unable to sign & upload package")) $logInfo ("Signature uploaded to " <> T.pack fullUrl) return sig stack-1.5.1/src/Stack/Solver.hs0000644000000000000000000010202413135652051014453 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} module Stack.Solver ( checkResolverSpec , cabalPackagesCheck , findCabalFiles , getResolverConstraints , mergeConstraints , solveExtraDeps , solveResolverSpec -- * Internal - for tests , parseCabalOutputLine ) where import Prelude () import Prelude.Compat import Control.Applicative import Control.Exception (assert) import Control.Exception.Safe (tryIO) import Control.Monad (when,void,join,liftM,unless,mapAndUnzipM, zipWithM_) import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.Logger import Data.Aeson.Extended (object, (.=), toJSON) import qualified Data.ByteString as S import Data.Char (isSpace) import Data.Either import Data.Foldable (forM_) import Data.Function (on) import qualified Data.HashMap.Strict as HashMap import qualified Data.HashSet as HashSet import Data.List ( (\\), isSuffixOf, intercalate , minimumBy, isPrefixOf) import Data.List.Extra (groupSortOn) import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe (catMaybes, isNothing, mapMaybe) import Data.Monoid import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeUtf8, encodeUtf8) import Data.Text.Encoding.Error (lenientDecode) import Data.Text.Extra (stripCR) import qualified Data.Text.Lazy as LT import Data.Text.Lazy.Encoding (decodeUtf8With) import Data.Tuple (swap) import qualified Data.Yaml as Yaml import qualified Distribution.Package as C import qualified Distribution.PackageDescription as C import qualified Distribution.Text as C import Path import Path.Find (findFiles) import Path.IO hiding (findExecutable, findFiles) import Stack.BuildPlan import Stack.Config (getLocalPackages, loadConfigYaml) import Stack.Constants (stackDotYaml, wiredInPackages) import Stack.Package (printCabalFileWarning , hpack , readPackageUnresolved) import Stack.PrettyPrint import Stack.Setup import Stack.Setup.Installed import Stack.Types.Build import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.FlagName import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.Resolver import Stack.Types.StackT (StackM) import Stack.Types.Version import qualified System.Directory as D import qualified System.FilePath as FP import System.Process.Read import Text.Regex.Applicative.Text (match, sym, psym, anySym, few) import qualified Data.Text.Normalize as T ( normalize , NormalizationMode(NFC) ) data ConstraintType = Constraint | Preference deriving (Eq) type ConstraintSpec = Map PackageName (Version, Map FlagName Bool) cabalSolver :: (StackM env m, HasConfig env) => EnvOverride -> [Path Abs Dir] -- ^ cabal files -> ConstraintType -> ConstraintSpec -- ^ src constraints -> ConstraintSpec -- ^ dep constraints -> [String] -- ^ additional arguments -> m (Either [PackageName] ConstraintSpec) cabalSolver menv cabalfps constraintType srcConstraints depConstraints cabalArgs = withSystemTempDir "cabal-solver" $ \dir' -> do let versionConstraints = fmap fst depConstraints dir = toFilePath dir' configLines <- getCabalConfig dir constraintType versionConstraints let configFile = dir FP. "cabal.config" liftIO $ S.writeFile configFile $ encodeUtf8 $ T.unlines configLines -- Run from a temporary directory to avoid cabal getting confused by any -- sandbox files, see: -- https://github.com/commercialhaskell/stack/issues/356 -- -- In theory we could use --ignore-sandbox, but not all versions of cabal -- support it. tmpdir <- getTempDir let args = ("--config-file=" ++ configFile) : "install" : "--enable-tests" : "--enable-benchmarks" : "--dry-run" : "--reorder-goals" : "--max-backjumps=-1" : "--package-db=clear" : "--package-db=global" : cabalArgs ++ toConstraintArgs (flagConstraints constraintType) ++ fmap toFilePath cabalfps catch (liftM Right (readProcessStdout (Just tmpdir) menv "cabal" args)) (\ex -> case ex of ProcessFailed _ _ _ err -> return $ Left err _ -> throwM ex) >>= either parseCabalErrors parseCabalOutput where errCheck = T.isInfixOf "Could not resolve dependencies" linesNoCR = map stripCR . T.lines cabalBuildErrMsg e = ">>>> Cabal errors begin\n" <> e <> "<<<< Cabal errors end\n" parseCabalErrors err = do let errExit e = error $ "Could not parse cabal-install errors:\n\n" ++ cabalBuildErrMsg (T.unpack e) msg = LT.toStrict $ decodeUtf8With lenientDecode err if errCheck msg then do $logInfo "Attempt failed.\n" $logInfo $ cabalBuildErrMsg msg let pkgs = parseConflictingPkgs msg mPkgNames = map (C.simpleParse . T.unpack) pkgs pkgNames = map (fromCabalPackageName . C.pkgName) (catMaybes mPkgNames) when (any isNothing mPkgNames) $ do $logInfo $ "*** Only some package names could be parsed: " <> T.pack (intercalate ", " (map show pkgNames)) error $ T.unpack $ "*** User packages involved in cabal failure: " <> T.intercalate ", " (parseConflictingPkgs msg) if pkgNames /= [] then do return $ Left pkgNames else errExit msg else errExit msg parseConflictingPkgs msg = let ls = dropWhile (not . errCheck) $ linesNoCR msg select s = (T.isPrefixOf "trying:" s || T.isPrefixOf "next goal:" s) && T.isSuffixOf "(user goal)" s pkgName = take 1 . T.words . T.drop 1 . T.dropWhile (/= ':') in concatMap pkgName (filter select ls) parseCabalOutput bs = do let ls = drop 1 $ dropWhile (not . T.isPrefixOf "In order, ") $ linesNoCR $ decodeUtf8 bs (errs, pairs) = partitionEithers $ map parseCabalOutputLine ls if null errs then return $ Right (Map.fromList pairs) else error $ "The following lines from cabal-install output could \ \not be parsed: \n" ++ T.unpack (T.intercalate "\n" errs) toConstraintArgs userFlagMap = [formatFlagConstraint package flag enabled | (package, fs) <- Map.toList userFlagMap , (flag, enabled) <- Map.toList fs] formatFlagConstraint package flag enabled = let sign = if enabled then '+' else '-' in "--constraint=" ++ unwords [packageNameString package, sign : flagNameString flag] -- Note the order of the Map union is important -- We override a package in snapshot by a src package flagConstraints Constraint = fmap snd (Map.union srcConstraints depConstraints) -- Even when using preferences we want to -- keep the src package flags unchanged -- TODO - this should be done only for manual flags. flagConstraints Preference = fmap snd srcConstraints -- An ugly parser to extract module id and flags parseCabalOutputLine :: Text -> Either Text (PackageName, (Version, Map FlagName Bool)) parseCabalOutputLine t0 = maybe (Left t0) Right . join . match re $ t0 -- Sample outputs to parse: -- text-1.2.1.1 (latest: 1.2.2.0) -integer-simple (via: parsec-3.1.9) (new package)) -- hspec-snap-1.0.0.0 *test (via: servant-snap-0.5) (new package) -- time-locale-compat-0.1.1.1 -old-locale (via: http-api-data-0.2.2) (new package)) -- flowdock-rest-0.2.0.0 -aeson-compat *test (via: haxl-fxtra-0.0.0.0) (new package) where re = mk <$> some (psym $ not . isSpace) <*> many (lexeme reMaybeFlag) reMaybeFlag = (\s -> Just (True, s)) <$ sym '+' <*> some (psym $ not . isSpace) <|> (\s -> Just (False, s)) <$ sym '-' <*> some (psym $ not . isSpace) <|> Nothing <$ sym '*' <* some (psym $ not . isSpace) <|> Nothing <$ sym '(' <* few anySym <* sym ')' mk :: String -> [Maybe (Bool, String)] -> Maybe (PackageName, (Version, Map FlagName Bool)) mk ident fl = do PackageIdentifier name version <- parsePackageIdentifierFromString ident fl' <- (traverse . traverse) parseFlagNameFromString $ catMaybes fl return (name, (version, Map.fromList $ map swap fl')) lexeme r = some (psym isSpace) *> r getCabalConfig :: (StackM env m, HasConfig env) => FilePath -- ^ temp dir -> ConstraintType -> Map PackageName Version -- ^ constraints -> m [Text] getCabalConfig dir constraintType constraints = do indices <- view $ configL.to configPackageIndices remotes <- mapM goIndex indices let cache = T.pack $ "remote-repo-cache: " ++ dir return $ cache : remotes ++ map goConstraint (Map.toList constraints) where goIndex index = do src <- configPackageIndex $ indexName index let dstdir = dir FP. T.unpack (indexNameText $ indexName index) -- NOTE: see https://github.com/commercialhaskell/stack/issues/2888 -- for why we are pretending that a 01-index.tar is actually a -- 00-index.tar file. dst0 = dstdir FP. "00-index.tar" dst1 = dstdir FP. "01-index.tar" liftIO $ void $ tryIO $ do D.createDirectoryIfMissing True dstdir D.copyFile (toFilePath src) dst0 D.copyFile (toFilePath src) dst1 return $ T.concat [ "remote-repo: " , indexNameText $ indexName index , ":http://0.0.0.0/fake-url" ] goConstraint (name, version) = assert (not . null . versionString $ version) $ T.concat [ if constraintType == Constraint || name `HashSet.member` wiredInPackages then "constraint: " else "preference: " , T.pack $ packageNameString name , "==" , T.pack $ versionString version ] setupCompiler :: (StackM env m, HasConfig env, HasGHCVariant env) => CompilerVersion -> m (Maybe ExtraDirs) setupCompiler compiler = do let msg = Just $ T.concat [ "Compiler version (" <> compilerVersionText compiler <> ") " , "required by your resolver specification cannot be found.\n\n" , "Please use '--install-ghc' command line switch to automatically " , "install the compiler or '--system-ghc' to use a suitable " , "compiler available on your PATH." ] config <- view configL (dirs, _, _) <- ensureCompiler SetupOpts { soptsInstallIfMissing = configInstallGHC config , soptsUseSystem = configSystemGHC config , soptsWantedCompiler = compiler , soptsCompilerCheck = configCompilerCheck config , soptsStackYaml = Nothing , soptsForceReinstall = False , soptsSanityCheck = False , soptsSkipGhcCheck = False , soptsSkipMsys = configSkipMsys config , soptsUpgradeCabal = Nothing , soptsResolveMissingGHC = msg , soptsSetupInfoYaml = defaultSetupInfoYaml , soptsGHCBindistURL = Nothing , soptsGHCJSBootOpts = ["--clean"] } return dirs setupCabalEnv :: (StackM env m, HasConfig env, HasGHCVariant env) => CompilerVersion -> m EnvOverride setupCabalEnv compiler = do mpaths <- setupCompiler compiler menv0 <- getMinimalEnvOverride envMap <- removeHaskellEnvVars <$> augmentPathMap (maybe [] edBins mpaths) (unEnvOverride menv0) platform <- view platformL menv <- mkEnvOverride platform envMap mcabal <- getCabalInstallVersion menv case mcabal of Nothing -> throwM SolverMissingCabalInstall Just version | version < $(mkVersion "1.24") -> $prettyWarn $ "Installed version of cabal-install (" <> display version <> ") doesn't support custom-setup clause, and so may not yield correct results." <> line <> "To resolve this, install a newer version via 'stack install cabal-install'." <> line | version >= $(mkVersion "1.25") -> $prettyWarn $ "Installed version of cabal-install (" <> display version <> ") is newer than stack has been tested with. If you run into difficulties, consider downgrading." <> line | otherwise -> return () mver <- getSystemCompiler menv (whichCompiler compiler) case mver of Just (version, _) -> $logInfo $ "Using compiler: " <> compilerVersionText version Nothing -> error "Failed to determine compiler version. \ \This is most likely a bug." return menv -- | Merge two separate maps, one defining constraints on package versions and -- the other defining package flagmap, into a single map of version and flagmap -- tuples. mergeConstraints :: Map PackageName v -> Map PackageName (Map p f) -> Map PackageName (v, Map p f) mergeConstraints = Map.mergeWithKey -- combine entry in both maps (\_ v f -> Just (v, f)) -- convert entry in first map only (fmap (flip (,) Map.empty)) -- convert entry in second map only (\m -> if Map.null m then Map.empty else error "Bug: An entry in flag map must have a corresponding \ \entry in the version map") -- | Given a resolver, user package constraints (versions and flags) and extra -- dependency constraints determine what extra dependencies are required -- outside the resolver snapshot and the specified extra dependencies. -- -- First it tries by using the snapshot and the input extra dependencies -- as hard constraints, if no solution is arrived at by using hard -- constraints it then tries using them as soft constraints or preferences. -- -- It returns either conflicting packages when no solution is arrived at -- or the solution in terms of src package flag settings and extra -- dependencies. solveResolverSpec :: (StackM env m, HasConfig env, HasGHCVariant env) => Path Abs File -- ^ stack.yaml file location -> [Path Abs Dir] -- ^ package dirs containing cabal files -> ( Resolver , ConstraintSpec , ConstraintSpec) -- ^ ( resolver -- , src package constraints -- , extra dependency constraints ) -> m (Either [PackageName] (ConstraintSpec , ConstraintSpec)) -- ^ (Conflicting packages -- (resulting src package specs, external dependency specs)) solveResolverSpec stackYaml cabalDirs (resolver, srcConstraints, extraConstraints) = do $logInfo $ "Using resolver: " <> resolverName resolver (compilerVer, snapConstraints) <- getResolverConstraints stackYaml resolver menv <- setupCabalEnv compilerVer let -- Note - The order in Map.union below is important. -- We want to override snapshot with extra deps depConstraints = Map.union extraConstraints snapConstraints -- Make sure to remove any user packages from the dep constraints -- There are two reasons for this: -- 1. We do not want snapshot versions to override the sources -- 2. Sources may have blank versions leading to bad cabal constraints depOnlyConstraints = Map.difference depConstraints srcConstraints solver t = cabalSolver menv cabalDirs t srcConstraints depOnlyConstraints $ "-v" : -- TODO make it conditional on debug ["--ghcjs" | whichCompiler compilerVer == Ghcjs] let srcNames = T.intercalate " and " $ ["packages from " <> resolverName resolver | not (Map.null snapConstraints)] ++ [T.pack (show (Map.size extraConstraints) <> " external packages") | not (Map.null extraConstraints)] $logInfo "Asking cabal to calculate a build plan..." unless (Map.null depOnlyConstraints) ($logInfo $ "Trying with " <> srcNames <> " as hard constraints...") eresult <- solver Constraint eresult' <- case eresult of Left _ | not (Map.null depOnlyConstraints) -> do $logInfo $ "Retrying with " <> srcNames <> " as preferences..." solver Preference _ -> return eresult case eresult' of Right deps -> do let -- All src package constraints returned by cabal. -- Flags may have changed. srcs = Map.intersection deps srcConstraints inSnap = Map.intersection deps snapConstraints -- All packages which are in the snapshot but cabal solver -- returned versions or flags different from the snapshot. inSnapChanged = Map.differenceWith diffConstraints inSnap snapConstraints -- Packages neither in snapshot, nor srcs extra = Map.difference deps (Map.union srcConstraints snapConstraints) external = Map.union inSnapChanged extra -- Just in case. -- If cabal output contains versions of user packages, those -- versions better be the same as those in our cabal file i.e. -- cabal should not be solving using versions from external -- indices. let outVers = fmap fst srcs inVers = fmap fst srcConstraints bothVers = Map.intersectionWith (\v1 v2 -> (v1, v2)) inVers outVers unless (outVers `Map.isSubmapOf` inVers) $ do let msg = "Error: user package versions returned by cabal \ \solver are not the same as the versions in the \ \cabal files:\n" -- TODO We can do better in formatting the message error $ T.unpack $ msg <> showItems (map show (Map.toList bothVers)) $logInfo $ "Successfully determined a build plan with " <> T.pack (show $ Map.size external) <> " external dependencies." return $ Right (srcs, external) Left x -> do $logInfo $ "*** Failed to arrive at a workable build plan." return $ Left x where -- Think of the first map as the deps reported in cabal output and -- the second as the snapshot packages -- Note: For flags we only require that the flags in cabal output be a -- subset of the snapshot flags. This is to avoid a false difference -- reporting due to any spurious flags in the build plan which will -- always be absent in the cabal output. diffConstraints :: (Eq v, Eq a, Ord k) => (v, Map k a) -> (v, Map k a) -> Maybe (v, Map k a) diffConstraints (v, f) (v', f') | (v == v') && (f `Map.isSubmapOf` f') = Nothing | otherwise = Just (v, f) -- | Given a resolver (snpashot, compiler or custom resolver) -- return the compiler version, package versions and packages flags -- for that resolver. getResolverConstraints :: (StackM env m, HasConfig env, HasGHCVariant env) => Path Abs File -> Resolver -> m (CompilerVersion, Map PackageName (Version, Map FlagName Bool)) getResolverConstraints stackYaml resolver = do (mbp, _loadedResolver) <- loadResolver (Just stackYaml) resolver return (mbpCompilerVersion mbp, mbpConstraints mbp) where mpiConstraints mpi = (mpiVersion mpi, mpiFlags mpi) mbpConstraints mbp = fmap mpiConstraints (mbpPackages mbp) -- | Given a bundle of user packages, flag constraints on those packages and a -- resolver, determine if the resolver fully, partially or fails to satisfy the -- dependencies of the user packages. -- -- If the package flags are passed as 'Nothing' then flags are chosen -- automatically. checkResolverSpec :: (StackM env m, HasConfig env, HasGHCVariant env) => [C.GenericPackageDescription] -> Maybe (Map PackageName (Map FlagName Bool)) -> Resolver -> m BuildPlanCheck checkResolverSpec gpds flags resolver = do case resolver of ResolverSnapshot name -> checkSnapBuildPlan gpds flags name ResolverCompiler {} -> return $ BuildPlanCheckPartial Map.empty Map.empty -- TODO support custom resolver for stack init ResolverCustom {} -> return $ BuildPlanCheckPartial Map.empty Map.empty -- | Finds all files with a .cabal extension under a given directory. If -- a `hpack` `package.yaml` file exists, this will be used to generate a cabal -- file. -- Subdirectories can be included depending on the @recurse@ parameter. findCabalFiles :: (MonadIO m, MonadLogger m) => Bool -> Path Abs Dir -> m [Path Abs File] findCabalFiles recurse dir = do liftIO (findFiles dir isHpack subdirFilter) >>= mapM_ (hpack . parent) liftIO (findFiles dir isCabal subdirFilter) where subdirFilter subdir = recurse && not (isIgnored subdir) isHpack = (== "package.yaml") . toFilePath . filename isCabal = (".cabal" `isSuffixOf`) . toFilePath isIgnored path = "." `isPrefixOf` dirName || dirName `Set.member` ignoredDirs where dirName = FP.dropTrailingPathSeparator (toFilePath (dirname path)) -- | Special directories that we don't want to traverse for .cabal files ignoredDirs :: Set FilePath ignoredDirs = Set.fromList [ "dist" ] -- | Perform some basic checks on a list of cabal files to be used for creating -- stack config. It checks for duplicate package names, package name and -- cabal file name mismatch and reports any issues related to those. -- -- If no error occurs it returns filepath and @GenericPackageDescription@s -- pairs as well as any filenames for duplicate packages not included in the -- pairs. cabalPackagesCheck :: (StackM env m, HasConfig env, HasGHCVariant env) => [Path Abs File] -> String -> Maybe String -> m ( Map PackageName (Path Abs File, C.GenericPackageDescription) , [Path Abs File]) cabalPackagesCheck cabalfps noPkgMsg dupErrMsg = do when (null cabalfps) $ error noPkgMsg relpaths <- mapM prettyPath cabalfps $logInfo $ "Using cabal packages:" $logInfo $ T.pack (formatGroup relpaths) (warnings, gpds) <- mapAndUnzipM readPackageUnresolved cabalfps zipWithM_ (mapM_ . printCabalFileWarning) cabalfps warnings -- package name cannot be empty or missing otherwise -- it will result in cabal solver failure. -- stack requires packages name to match the cabal file name -- Just the latter check is enough to cover both the cases let packages = zip cabalfps gpds normalizeString = T.unpack . T.normalize T.NFC . T.pack getNameMismatchPkg (fp, gpd) | (normalizeString . show . gpdPackageName) gpd /= (normalizeString . FP.takeBaseName . toFilePath) fp = Just fp | otherwise = Nothing nameMismatchPkgs = mapMaybe getNameMismatchPkg packages when (nameMismatchPkgs /= []) $ do rels <- mapM prettyPath nameMismatchPkgs error $ "Package name as defined in the .cabal file must match the \ \.cabal file name.\n\ \Please fix the following packages and try again:\n" <> formatGroup rels let dupGroups = filter ((> 1) . length) . groupSortOn (gpdPackageName . snd) dupAll = concat $ dupGroups packages -- Among duplicates prefer to include the ones in upper level dirs pathlen = length . FP.splitPath . toFilePath . fst getmin = minimumBy (compare `on` pathlen) dupSelected = map getmin (dupGroups packages) dupIgnored = dupAll \\ dupSelected unique = packages \\ dupIgnored when (dupIgnored /= []) $ do dups <- mapM (mapM (prettyPath. fst)) (dupGroups packages) $logWarn $ T.pack $ "Following packages have duplicate package names:\n" <> intercalate "\n" (map formatGroup dups) case dupErrMsg of Nothing -> $logWarn $ T.pack $ "Packages with duplicate names will be ignored.\n" <> "Packages in upper level directories will be preferred.\n" Just msg -> error msg return (Map.fromList $ map (\(file, gpd) -> (gpdPackageName gpd,(file, gpd))) unique , map fst dupIgnored) formatGroup :: [String] -> String formatGroup = concatMap (\path -> "- " <> path <> "\n") reportMissingCabalFiles :: (MonadIO m, MonadThrow m, MonadLogger m) => [Path Abs File] -- ^ Directories to scan -> Bool -- ^ Whether to scan sub-directories -> m () reportMissingCabalFiles cabalfps includeSubdirs = do allCabalfps <- findCabalFiles includeSubdirs =<< getCurrentDir relpaths <- mapM prettyPath (allCabalfps \\ cabalfps) unless (null relpaths) $ do $logWarn $ "The following packages are missing from the config:" $logWarn $ T.pack (formatGroup relpaths) -- TODO Currently solver uses a stack.yaml in the parent chain when there is -- no stack.yaml in the current directory. It should instead look for a -- stack yaml only in the current directory and suggest init if there is -- none available. That will make the behavior consistent with init and provide -- a correct meaning to a --ignore-subdirs option if implemented. -- | Verify the combination of resolver, package flags and extra -- dependencies in an existing stack.yaml and suggest changes in flags or -- extra dependencies so that the specified packages can be compiled. solveExtraDeps :: (StackM env m, HasEnvConfig env) => Bool -- ^ modify stack.yaml? -> m () solveExtraDeps modStackYaml = do bconfig <- view buildConfigL let stackYaml = bcStackYaml bconfig relStackYaml <- prettyPath stackYaml $logInfo $ "Using configuration file: " <> T.pack relStackYaml packages <- getLocalPackages let cabalDirs = Map.keys packages noPkgMsg = "No cabal packages found in " <> relStackYaml <> ". Please add at least one directory containing a .cabal \ \file. You can also use 'stack init' to automatically \ \generate the config file." dupPkgFooter = "Please remove the directories containing duplicate \ \entries from '" <> relStackYaml <> "'." cabalfps <- liftM concat (mapM (findCabalFiles False) cabalDirs) -- TODO when solver supports --ignore-subdirs option pass that as the -- second argument here. reportMissingCabalFiles cabalfps True (bundle, _) <- cabalPackagesCheck cabalfps noPkgMsg (Just dupPkgFooter) let gpds = Map.elems $ fmap snd bundle oldFlags = unPackageFlags (bcFlags bconfig) oldExtraVersions = bcExtraDeps bconfig resolver = bcResolver bconfig oldSrcs = gpdPackages gpds oldSrcFlags = Map.intersection oldFlags oldSrcs oldExtraFlags = Map.intersection oldFlags oldExtraVersions srcConstraints = mergeConstraints oldSrcs oldSrcFlags extraConstraints = mergeConstraints oldExtraVersions oldExtraFlags let resolver' = toResolverNotLoaded resolver resolverResult <- checkResolverSpec gpds (Just oldSrcFlags) resolver' resultSpecs <- case resolverResult of BuildPlanCheckOk flags -> return $ Just (mergeConstraints oldSrcs flags, Map.empty) BuildPlanCheckPartial {} -> do eres <- solveResolverSpec stackYaml cabalDirs (resolver', srcConstraints, extraConstraints) -- TODO Solver should also use the init code to ignore incompatible -- packages return $ either (const Nothing) Just eres BuildPlanCheckFail {} -> throwM $ ResolverMismatch IsSolverCmd resolver (show resolverResult) (srcs, edeps) <- case resultSpecs of Nothing -> throwM (SolverGiveUp giveUpMsg) Just x -> return x mOldResolver <- view $ configL.to (fmap (projectResolver . fst) . configMaybeProject) let flags = removeSrcPkgDefaultFlags gpds (fmap snd (Map.union srcs edeps)) versions = fmap fst edeps vDiff v v' = if v == v' then Nothing else Just v versionsDiff = Map.differenceWith vDiff newVersions = versionsDiff versions oldExtraVersions goneVersions = versionsDiff oldExtraVersions versions fDiff f f' = if f == f' then Nothing else Just f flagsDiff = Map.differenceWith fDiff newFlags = flagsDiff flags oldFlags goneFlags = flagsDiff oldFlags flags changed = any (not . Map.null) [newVersions, goneVersions] || any (not . Map.null) [newFlags, goneFlags] || any (/= resolver') mOldResolver if changed then do $logInfo "" $logInfo $ "The following changes will be made to " <> T.pack relStackYaml <> ":" printResolver mOldResolver resolver' printFlags newFlags "* Flags to be added" printDeps newVersions "* Dependencies to be added" printFlags goneFlags "* Flags to be deleted" printDeps goneVersions "* Dependencies to be deleted" -- TODO backup the old config file if modStackYaml then do writeStackYaml stackYaml resolver versions flags $logInfo $ "Updated " <> T.pack relStackYaml else do $logInfo $ "To automatically update " <> T.pack relStackYaml <> ", rerun with '--update-config'" else $logInfo $ "No changes needed to " <> T.pack relStackYaml where indentLines t = T.unlines $ fmap (" " <>) (T.lines t) printResolver mOldRes res = do forM_ mOldRes $ \oldRes -> when (res /= oldRes) $ do $logInfo $ T.concat [ "* Resolver changes from " , resolverName oldRes , " to " , resolverName res ] printFlags fl msg = do unless (Map.null fl) $ do $logInfo $ T.pack msg $logInfo $ indentLines $ decodeUtf8 $ Yaml.encode $ object ["flags" .= fl] printDeps deps msg = do unless (Map.null deps) $ do $logInfo $ T.pack msg $logInfo $ indentLines $ decodeUtf8 $ Yaml.encode $ object ["extra-deps" .= map fromTuple (Map.toList deps)] writeStackYaml path res deps fl = do let fp = toFilePath path obj <- liftIO (Yaml.decodeFileEither fp) >>= either throwM return -- Check input file and show warnings _ <- loadConfigYaml (parseProjectAndConfigMonoid (parent path)) path let obj' = HashMap.insert "extra-deps" (toJSON $ map fromTuple $ Map.toList deps) $ HashMap.insert ("flags" :: Text) (toJSON fl) $ HashMap.insert ("resolver" :: Text) (toJSON (resolverName res)) obj liftIO $ Yaml.encodeFile fp obj' giveUpMsg = concat [ " - Update external packages with 'stack update' and try again.\n" , " - Tweak " <> toFilePath stackDotYaml <> " and try again\n" , " - Remove any unnecessary packages.\n" , " - Add any missing remote packages.\n" , " - Add extra dependencies to guide solver.\n" , " - Adjust resolver.\n" ] prettyPath :: forall r t m. (MonadIO m, RelPath (Path r t) ~ Path Rel t, AnyPath (Path r t)) => Path r t -> m String prettyPath path = do eres <- liftIO $ try $ makeRelativeToCurrentDir path return $ case eres of Left (_ :: PathParseException) -> toFilePath path Right res -> toFilePath (res :: Path Rel t) stack-1.5.1/src/Stack/Types/Build.hs0000644000000000000000000006455713135652051015366 0ustar0000000000000000{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} -- | Build-specific types. module Stack.Types.Build (StackBuildException(..) ,FlagSource(..) ,UnusedFlags(..) ,InstallLocation(..) ,ModTime ,modTime ,Installed(..) ,PackageInstallInfo(..) ,Task(..) ,taskIsTarget ,taskLocation ,LocalPackage(..) ,BaseConfigOpts(..) ,Plan(..) ,TestOpts(..) ,BenchmarkOpts(..) ,FileWatchOpts(..) ,BuildOpts(..) ,BuildSubset(..) ,defaultBuildOpts ,TaskType(..) ,TaskConfigOpts(..) ,BuildCache(..) ,buildCacheVC ,ConfigCache(..) ,configCacheVC ,configureOpts ,CachePkgSrc (..) ,toCachePkgSrc ,isStackOpt ,wantedLocalPackages ,FileCacheInfo (..) ,ConfigureOpts (..) ,PrecompiledCache (..) ,precompiledCacheVC) where import Control.DeepSeq import Control.Exception import Data.Binary (Binary) import Data.Binary.Tagged (HasSemanticVersion, HasStructuralInfo) import qualified Data.ByteString as S import Data.Char (isSpace) import Data.Data import Data.Hashable import Data.List.Extra import qualified Data.Map as Map import Data.Map.Strict (Map) import Data.Maybe import Data.Monoid import Data.Set (Set) import qualified Data.Set as Set import Data.Store.Internal (Store) import Data.Store.Version import Data.Store.VersionTagged import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeUtf8With) import Data.Text.Encoding.Error (lenientDecode) import Data.Time.Calendar import Data.Time.Clock import Data.Version (showVersion) import Distribution.PackageDescription (TestSuiteInterface) import Distribution.System (Arch) import qualified Distribution.Text as C import GHC.Generics (Generic) import Path (Abs, Dir, File, Path, mkRelDir, parseRelDir, toFilePath, ()) import Path.Extra (toFilePathNoTrailingSep) import Paths_stack as Meta import Prelude import Stack.Constants import Stack.Types.BuildPlan (GitSHA1) import Stack.Types.Compiler import Stack.Types.CompilerBuild import Stack.Types.Config import Stack.Types.FlagName import Stack.Types.GhcPkgId import Stack.Types.Package import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.Version import System.Exit (ExitCode (ExitFailure)) import System.FilePath (pathSeparator) import System.Process.Log (showProcessArgDebug) ---------------------------------------------- -- Exceptions data StackBuildException = Couldn'tFindPkgId PackageName | CompilerVersionMismatch (Maybe (CompilerVersion, Arch)) -- found (CompilerVersion, Arch) -- expected GHCVariant -- expected CompilerBuild -- expected VersionCheck (Maybe (Path Abs File)) -- Path to the stack.yaml file Text -- recommended resolution | Couldn'tParseTargets [Text] | UnknownTargets (Set PackageName) -- no known version (Map PackageName Version) -- not in snapshot, here's the most recent version in the index (Path Abs File) -- stack.yaml | TestSuiteFailure PackageIdentifier (Map Text (Maybe ExitCode)) (Maybe (Path Abs File)) S.ByteString | TestSuiteTypeUnsupported TestSuiteInterface | ConstructPlanFailed String | CabalExitedUnsuccessfully ExitCode PackageIdentifier (Path Abs File) -- cabal Executable [String] -- cabal arguments (Maybe (Path Abs File)) -- logfiles location [Text] -- log contents | ExecutionFailure [SomeException] | LocalPackageDoesn'tMatchTarget PackageName Version -- local version Version -- version specified on command line | NoSetupHsFound (Path Abs Dir) | InvalidFlagSpecification (Set UnusedFlags) | TargetParseException [Text] | DuplicateLocalPackageNames [(PackageName, [Path Abs Dir])] | SolverGiveUp String | SolverMissingCabalInstall | SomeTargetsNotBuildable [(PackageName, NamedComponent)] | TestSuiteExeMissing Bool String String String | CabalCopyFailed Bool String | LocalPackagesPresent [PackageIdentifier] deriving Typeable data FlagSource = FSCommandLine | FSStackYaml deriving (Show, Eq, Ord) data UnusedFlags = UFNoPackage FlagSource PackageName | UFFlagsNotDefined FlagSource Package (Set FlagName) | UFSnapshot PackageName deriving (Show, Eq, Ord) instance Show StackBuildException where show (Couldn'tFindPkgId name) = "After installing " <> packageNameString name <> ", the package id couldn't be found " <> "(via ghc-pkg describe " <> packageNameString name <> "). This shouldn't happen, " <> "please report as a bug" show (CompilerVersionMismatch mactual (expected, earch) ghcVariant ghcBuild check mstack resolution) = concat [ case mactual of Nothing -> "No compiler found, expected " Just (actual, arch) -> concat [ "Compiler version mismatched, found " , compilerVersionString actual , " (" , C.display arch , ")" , ", but expected " ] , case check of MatchMinor -> "minor version match with " MatchExact -> "exact version " NewerMinor -> "minor version match or newer with " , compilerVersionString expected , " (" , C.display earch , ghcVariantSuffix ghcVariant , compilerBuildSuffix ghcBuild , ") (based on " , case mstack of Nothing -> "command line arguments" Just stack -> "resolver setting in " ++ toFilePath stack , ").\n" , T.unpack resolution ] show (Couldn'tParseTargets targets) = unlines $ "The following targets could not be parsed as package names or directories:" : map T.unpack targets show (UnknownTargets noKnown notInSnapshot stackYaml) = unlines $ noKnown' ++ notInSnapshot' where noKnown' | Set.null noKnown = [] | otherwise = return $ "The following target packages were not found: " ++ intercalate ", " (map packageNameString $ Set.toList noKnown) ++ "\nSee https://docs.haskellstack.org/en/v" <> showVersion Meta.version <> "/build_command/#target-syntax for details." notInSnapshot' | Map.null notInSnapshot = [] | otherwise = "The following packages are not in your snapshot, but exist" : "in your package index. Recommended action: add them to your" : ("extra-deps in " ++ toFilePath stackYaml) : "(Note: these are the most recent versions," : "but there's no guarantee that they'll build together)." : "" : map (\(name, version') -> "- " ++ packageIdentifierString (PackageIdentifier name version')) (Map.toList notInSnapshot) show (TestSuiteFailure ident codes mlogFile bs) = unlines $ concat [ ["Test suite failure for package " ++ packageIdentifierString ident] , flip map (Map.toList codes) $ \(name, mcode) -> concat [ " " , T.unpack name , ": " , case mcode of Nothing -> " executable not found" Just ec -> " exited with: " ++ show ec ] , return $ case mlogFile of Nothing -> "Logs printed to console" -- TODO Should we load up the full error output and print it here? Just logFile -> "Full log available at " ++ toFilePath logFile , if S.null bs then [] else ["", "", doubleIndent $ T.unpack $ decodeUtf8With lenientDecode bs] ] where indent = dropWhileEnd isSpace . unlines . fmap (\line -> " " ++ line) . lines doubleIndent = indent . indent show (TestSuiteTypeUnsupported interface) = "Unsupported test suite type: " <> show interface -- Supressing duplicate output show (CabalExitedUnsuccessfully exitCode taskProvides' execName fullArgs logFiles bss) = let fullCmd = unwords $ dropQuotes (toFilePath execName) : map (T.unpack . showProcessArgDebug) fullArgs logLocations = maybe "" (\fp -> "\n Logs have been written to: " ++ toFilePath fp) logFiles in "\n-- While building package " ++ dropQuotes (show taskProvides') ++ " using:\n" ++ " " ++ fullCmd ++ "\n" ++ " Process exited with code: " ++ show exitCode ++ (if exitCode == ExitFailure (-9) then " (THIS MAY INDICATE OUT OF MEMORY)" else "") ++ logLocations ++ (if null bss then "" else "\n\n" ++ doubleIndent (map T.unpack bss)) where doubleIndent = dropWhileEnd isSpace . unlines . fmap (\line -> " " ++ line) dropQuotes = filter ('\"' /=) show (ExecutionFailure es) = intercalate "\n\n" $ map show es show (LocalPackageDoesn'tMatchTarget name localV requestedV) = concat [ "Version for local package " , packageNameString name , " is " , versionString localV , ", but you asked for " , versionString requestedV , " on the command line" ] show (NoSetupHsFound dir) = "No Setup.hs or Setup.lhs file found in " ++ toFilePath dir show (InvalidFlagSpecification unused) = unlines $ "Invalid flag specification:" : map go (Set.toList unused) where showFlagSrc :: FlagSource -> String showFlagSrc FSCommandLine = " (specified on command line)" showFlagSrc FSStackYaml = " (specified in stack.yaml)" go :: UnusedFlags -> String go (UFNoPackage src name) = concat [ "- Package '" , packageNameString name , "' not found" , showFlagSrc src ] go (UFFlagsNotDefined src pkg flags) = concat [ "- Package '" , name , "' does not define the following flags" , showFlagSrc src , ":\n" , intercalate "\n" (map (\flag -> " " ++ flagNameString flag) (Set.toList flags)) , "\n- Flags defined by package '" ++ name ++ "':\n" , intercalate "\n" (map (\flag -> " " ++ name ++ ":" ++ flagNameString flag) (Set.toList pkgFlags)) ] where name = packageNameString (packageName pkg) pkgFlags = packageDefinedFlags pkg go (UFSnapshot name) = concat [ "- Attempted to set flag on snapshot package " , packageNameString name , ", please add to extra-deps" ] show (TargetParseException [err]) = "Error parsing targets: " ++ T.unpack err show (TargetParseException errs) = unlines $ "The following errors occurred while parsing the build targets:" : map (("- " ++) . T.unpack) errs show (DuplicateLocalPackageNames pairs) = concat $ "The same package name is used in multiple local packages\n" : map go pairs where go (name, dirs) = unlines $ "" : (packageNameString name ++ " used in:") : map goDir dirs goDir dir = "- " ++ toFilePath dir show (SolverGiveUp msg) = concat [ "\nSolver could not resolve package dependencies.\n" , "You can try the following:\n" , msg ] show SolverMissingCabalInstall = unlines [ "Solver requires that cabal be on your PATH" , "Try running 'stack install cabal-install'" ] show (SomeTargetsNotBuildable xs) = "The following components have 'buildable: False' set in the cabal configuration, and so cannot be targets:\n " ++ T.unpack (renderPkgComponents xs) ++ "\nTo resolve this, either provide flags such that these components are buildable, or only specify buildable targets." show (TestSuiteExeMissing isSimpleBuildType exeName pkgName testName) = missingExeError isSimpleBuildType $ concat [ "Test suite executable \"" , exeName , " not found for " , pkgName , ":test:" , testName ] show (CabalCopyFailed isSimpleBuildType innerMsg) = missingExeError isSimpleBuildType $ concat [ "'cabal copy' failed. Error message:\n" , innerMsg , "\n" ] show (ConstructPlanFailed msg) = msg show (LocalPackagesPresent locals) = unlines $ "Local packages are not allowed when using the script command. Packages found:" : map (\ident -> "- " ++ packageIdentifierString ident) locals missingExeError :: Bool -> String -> String missingExeError isSimpleBuildType msg = unlines $ msg : case possibleCauses of [] -> [] [cause] -> ["One possible cause of this issue is:\n* " <> cause] _ -> "Possible causes of this issue:" : map ("* " <>) possibleCauses where possibleCauses = "No module named \"Main\". The 'main-is' source file should usually have a header indicating that it's a 'Main' module." : if isSimpleBuildType then [] else ["The Setup.hs file is changing the installation target dir."] instance Exception StackBuildException ---------------------------------------------- -- | Package dependency oracle. newtype PkgDepsOracle = PkgDeps PackageName deriving (Show,Typeable,Eq,Hashable,Store,NFData) -- | Stored on disk to know whether the files have changed. newtype BuildCache = BuildCache { buildCacheTimes :: Map FilePath FileCacheInfo -- ^ Modification times of files. } deriving (Generic, Eq, Show, Data, Typeable) instance NFData BuildCache instance Store BuildCache buildCacheVC :: VersionConfig BuildCache buildCacheVC = storeVersionConfig "build-v1" "KVUoviSWWAd7tiRRGeWAvd0UIN4=" -- | Stored on disk to know whether the flags have changed. data ConfigCache = ConfigCache { configCacheOpts :: !ConfigureOpts -- ^ All options used for this package. , configCacheDeps :: !(Set GhcPkgId) -- ^ The GhcPkgIds of all of the dependencies. Since Cabal doesn't take -- the complete GhcPkgId (only a PackageIdentifier) in the configure -- options, just using the previous value is insufficient to know if -- dependencies have changed. , configCacheComponents :: !(Set S.ByteString) -- ^ The components to be built. It's a bit of a hack to include this in -- here, as it's not a configure option (just a build option), but this -- is a convenient way to force compilation when the components change. , configCacheHaddock :: !Bool -- ^ Are haddocks to be built? , configCachePkgSrc :: !CachePkgSrc } deriving (Generic, Eq, Show, Data, Typeable) instance Store ConfigCache instance NFData ConfigCache data CachePkgSrc = CacheSrcUpstream | CacheSrcLocal FilePath deriving (Generic, Eq, Show, Data, Typeable) instance Store CachePkgSrc instance NFData CachePkgSrc toCachePkgSrc :: PackageSource -> CachePkgSrc toCachePkgSrc (PSLocal lp) = CacheSrcLocal (toFilePath (lpDir lp)) toCachePkgSrc PSUpstream{} = CacheSrcUpstream configCacheVC :: VersionConfig ConfigCache configCacheVC = storeVersionConfig "config-v3" "z7N_NxX7Gbz41Gi9AGEa1zoLE-4=" -- | A task to perform when building data Task = Task { taskProvides :: !PackageIdentifier -- ^ the package/version to be built , taskType :: !TaskType -- ^ the task type, telling us how to build this , taskConfigOpts :: !TaskConfigOpts , taskPresent :: !(Map PackageIdentifier GhcPkgId) -- ^ GhcPkgIds of already-installed dependencies , taskAllInOne :: !Bool -- ^ indicates that the package can be built in one step , taskCachePkgSrc :: !CachePkgSrc } deriving Show -- | Given the IDs of any missing packages, produce the configure options data TaskConfigOpts = TaskConfigOpts { tcoMissing :: !(Set PackageIdentifier) -- ^ Dependencies for which we don't yet have an GhcPkgId , tcoOpts :: !(Map PackageIdentifier GhcPkgId -> ConfigureOpts) -- ^ Produce the list of options given the missing @GhcPkgId@s } instance Show TaskConfigOpts where show (TaskConfigOpts missing f) = concat [ "Missing: " , show missing , ". Without those: " , show $ f Map.empty ] -- | The type of a task, either building local code or something from the -- package index (upstream) data TaskType = TTLocal LocalPackage | TTUpstream Package InstallLocation (Maybe GitSHA1) deriving Show taskIsTarget :: Task -> Bool taskIsTarget t = case taskType t of TTLocal lp -> lpWanted lp _ -> False taskLocation :: Task -> InstallLocation taskLocation task = case taskType task of TTLocal _ -> Local TTUpstream _ loc _ -> loc -- | A complete plan of what needs to be built and how to do it data Plan = Plan { planTasks :: !(Map PackageName Task) , planFinals :: !(Map PackageName Task) -- ^ Final actions to be taken (test, benchmark, etc) , planUnregisterLocal :: !(Map GhcPkgId (PackageIdentifier, Text)) -- ^ Text is reason we're unregistering, for display only , planInstallExes :: !(Map Text InstallLocation) -- ^ Executables that should be installed after successful building } deriving Show -- | Basic information used to calculate what the configure options are data BaseConfigOpts = BaseConfigOpts { bcoSnapDB :: !(Path Abs Dir) , bcoLocalDB :: !(Path Abs Dir) , bcoSnapInstallRoot :: !(Path Abs Dir) , bcoLocalInstallRoot :: !(Path Abs Dir) , bcoBuildOpts :: !BuildOpts , bcoBuildOptsCLI :: !BuildOptsCLI , bcoExtraDBs :: ![Path Abs Dir] } deriving Show -- | Render a @BaseConfigOpts@ to an actual list of options configureOpts :: EnvConfig -> BaseConfigOpts -> Map PackageIdentifier GhcPkgId -- ^ dependencies -> Bool -- ^ local non-extra-dep? -> InstallLocation -> Package -> ConfigureOpts configureOpts econfig bco deps isLocal loc package = ConfigureOpts { coDirs = configureOptsDirs bco loc package , coNoDirs = configureOptsNoDir econfig bco deps isLocal package } -- options set by stack isStackOpt :: Text -> Bool isStackOpt t = any (`T.isPrefixOf` t) [ "--dependency=" , "--constraint=" , "--package-db=" , "--libdir=" , "--bindir=" , "--datadir=" , "--libexecdir=" , "--sysconfdir" , "--docdir=" , "--htmldir=" , "--haddockdir=" , "--enable-tests" , "--enable-benchmarks" , "--exact-configuration" -- Treat these as causing dirtiness, to resolve -- https://github.com/commercialhaskell/stack/issues/2984 -- -- , "--enable-library-profiling" -- , "--enable-executable-profiling" -- , "--enable-profiling" ] || t == "--user" configureOptsDirs :: BaseConfigOpts -> InstallLocation -> Package -> [String] configureOptsDirs bco loc package = concat [ ["--user", "--package-db=clear", "--package-db=global"] , map (("--package-db=" ++) . toFilePathNoTrailingSep) $ case loc of Snap -> bcoExtraDBs bco ++ [bcoSnapDB bco] Local -> bcoExtraDBs bco ++ [bcoSnapDB bco] ++ [bcoLocalDB bco] , [ "--libdir=" ++ toFilePathNoTrailingSep (installRoot $(mkRelDir "lib")) , "--bindir=" ++ toFilePathNoTrailingSep (installRoot bindirSuffix) , "--datadir=" ++ toFilePathNoTrailingSep (installRoot $(mkRelDir "share")) , "--libexecdir=" ++ toFilePathNoTrailingSep (installRoot $(mkRelDir "libexec")) , "--sysconfdir=" ++ toFilePathNoTrailingSep (installRoot $(mkRelDir "etc")) , "--docdir=" ++ toFilePathNoTrailingSep docDir , "--htmldir=" ++ toFilePathNoTrailingSep docDir , "--haddockdir=" ++ toFilePathNoTrailingSep docDir] ] where installRoot = case loc of Snap -> bcoSnapInstallRoot bco Local -> bcoLocalInstallRoot bco docDir = case pkgVerDir of Nothing -> installRoot docDirSuffix Just dir -> installRoot docDirSuffix dir pkgVerDir = parseRelDir (packageIdentifierString (PackageIdentifier (packageName package) (packageVersion package)) ++ [pathSeparator]) -- | Same as 'configureOpts', but does not include directory path options configureOptsNoDir :: EnvConfig -> BaseConfigOpts -> Map PackageIdentifier GhcPkgId -- ^ dependencies -> Bool -- ^ is this a local, non-extra-dep? -> Package -> [String] configureOptsNoDir econfig bco deps isLocal package = concat [ depOptions , ["--enable-library-profiling" | boptsLibProfile bopts || boptsExeProfile bopts] -- Cabal < 1.21.1 does not support --enable-profiling, use --enable-executable-profiling instead , let profFlag = "--enable-" <> concat ["executable-" | not newerCabal] <> "profiling" in [ profFlag | boptsExeProfile bopts && isLocal] , ["--enable-split-objs" | boptsSplitObjs bopts] , ["--disable-library-stripping" | not $ boptsLibStrip bopts || boptsExeStrip bopts] , ["--disable-executable-stripping" | not (boptsExeStrip bopts) && isLocal] , map (\(name,enabled) -> "-f" <> (if enabled then "" else "-") <> flagNameString name) (Map.toList flags) , concatMap (\x -> [compilerOptionsCabalFlag wc, T.unpack x]) (packageGhcOptions package) , map ("--extra-include-dirs=" ++) (Set.toList (configExtraIncludeDirs config)) , map ("--extra-lib-dirs=" ++) (Set.toList (configExtraLibDirs config)) , maybe [] (\customGcc -> ["--with-gcc=" ++ toFilePath customGcc]) (configOverrideGccPath config) , ["--ghcjs" | wc == Ghcjs] , ["--exact-configuration" | useExactConf] ] where wc = view (actualCompilerVersionL.to whichCompiler) econfig config = view configL econfig bopts = bcoBuildOpts bco -- TODO: instead always enable this when the cabal version is new -- enough. That way we'll detect bugs with --exact-configuration -- earlier. Cabal also might do less work then. useExactConf = configAllowNewer config newerCabal = view cabalVersionL econfig >= $(mkVersion "1.22") -- Unioning atop defaults is needed so that all flags are specified -- with --exact-configuration. flags | useExactConf = packageFlags package `Map.union` packageDefaultFlags package | otherwise = packageFlags package depOptions = map (uncurry toDepOption) $ Map.toList deps where toDepOption = if newerCabal then toDepOption1_22 else toDepOption1_18 toDepOption1_22 ident gid = concat [ "--dependency=" , packageNameString $ packageIdentifierName ident , "=" , ghcPkgIdString gid ] toDepOption1_18 ident _gid = concat [ "--constraint=" , packageNameString name , "==" , versionString version' ] where PackageIdentifier name version' = ident -- | Get set of wanted package names from locals. wantedLocalPackages :: [LocalPackage] -> Set PackageName wantedLocalPackages = Set.fromList . map (packageName . lpPackage) . filter lpWanted -- | One-way conversion to serialized time. modTime :: UTCTime -> ModTime modTime x = ModTime ( toModifiedJulianDay (utctDay x) , toRational (utctDayTime x)) -- | Configure options to be sent to Setup.hs configure data ConfigureOpts = ConfigureOpts { coDirs :: ![String] -- ^ Options related to various paths. We separate these out since they do -- not have an impact on the contents of the compiled binary for checking -- if we can use an existing precompiled cache. , coNoDirs :: ![String] } deriving (Show, Eq, Generic, Data, Typeable) instance Store ConfigureOpts instance NFData ConfigureOpts -- | Information on a compiled package: the library conf file (if relevant), -- and all of the executable paths. data PrecompiledCache = PrecompiledCache -- Use FilePath instead of Path Abs File for Binary instances { pcLibrary :: !(Maybe FilePath) -- ^ .conf file inside the package database , pcExes :: ![FilePath] -- ^ Full paths to executables } deriving (Show, Eq, Generic, Data, Typeable) instance Binary PrecompiledCache instance HasSemanticVersion PrecompiledCache instance HasStructuralInfo PrecompiledCache instance Store PrecompiledCache instance NFData PrecompiledCache precompiledCacheVC :: VersionConfig PrecompiledCache precompiledCacheVC = storeVersionConfig "precompiled-v1" "eMzSOwaHJMamA5iNKs1A025frlQ=" stack-1.5.1/src/Stack/Types/BuildPlan.hs0000644000000000000000000004146213135652051016167 0ustar0000000000000000{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE DataKinds #-} -- | Shared types for various stackage packages. module Stack.Types.BuildPlan ( -- * Types BuildPlan (..) , PackagePlan (..) , PackageConstraints (..) , TestState (..) , SystemInfo (..) , Maintainer (..) , ExeName (..) , SimpleDesc (..) , Snapshots (..) , DepInfo (..) , Component (..) , SnapName (..) , MiniBuildPlan (..) , miniBuildPlanVC , MiniPackageInfo (..) , CabalFileInfo (..) , GitSHA1 (..) , renderSnapName , parseSnapName , SnapshotHash (..) , trimmedSnapshotHash , ModuleName (..) , ModuleInfo (..) , moduleInfoVC ) where import Control.Applicative import Control.Arrow ((&&&)) import Control.DeepSeq (NFData) import Control.Exception (Exception) import Control.Monad.Catch (MonadThrow, throwM) import Data.Aeson (FromJSON (..), FromJSONKey(..), ToJSON (..), ToJSONKey (..), object, withObject, withText, (.!=), (.:), (.:?), (.=)) import Data.ByteString (ByteString) import qualified Data.ByteString as BS import Data.Data import qualified Data.HashMap.Strict as HashMap import Data.Hashable (Hashable) import Data.IntMap (IntMap) import qualified Data.IntMap as IntMap import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe (fromMaybe) import Data.Monoid import Data.Set (Set) import Data.Store (Store) import Data.Store.Version import Data.Store.VersionTagged import Data.String (IsString, fromString) import Data.Text (Text, pack, unpack) import qualified Data.Text as T import Data.Text.Read (decimal) import Data.Time (Day) import qualified Data.Traversable as T import Data.Vector (Vector) import Distribution.System (Arch, OS (..)) import qualified Distribution.Text as DT import qualified Distribution.Version as C import GHC.Generics (Generic) import Prelude -- Fix AMP warning import Safe (readMay) import Stack.Types.Compiler import Stack.Types.FlagName import Stack.Types.PackageName import Stack.Types.Version -- | The name of an LTS Haskell or Stackage Nightly snapshot. data SnapName = LTS !Int !Int | Nightly !Day deriving (Show, Eq, Ord) data BuildPlan = BuildPlan { bpSystemInfo :: SystemInfo , bpTools :: Vector (PackageName, Version) , bpPackages :: Map PackageName PackagePlan , bpGithubUsers :: Map Text (Set Text) } deriving (Show, Eq) instance ToJSON BuildPlan where toJSON BuildPlan {..} = object [ "system-info" .= bpSystemInfo , "tools" .= fmap goTool bpTools , "packages" .= bpPackages , "github-users" .= bpGithubUsers ] where goTool (k, v) = object [ "name" .= k , "version" .= v ] instance FromJSON BuildPlan where parseJSON = withObject "BuildPlan" $ \o -> do bpSystemInfo <- o .: "system-info" bpTools <- o .: "tools" >>= T.mapM goTool bpPackages <- o .: "packages" bpGithubUsers <- o .:? "github-users" .!= mempty return BuildPlan {..} where goTool = withObject "Tool" $ \o -> (,) <$> o .: "name" <*> o .: "version" data PackagePlan = PackagePlan { ppVersion :: Version , ppCabalFileInfo :: Maybe CabalFileInfo , ppGithubPings :: Set Text , ppUsers :: Set PackageName , ppConstraints :: PackageConstraints , ppDesc :: SimpleDesc } deriving (Show, Eq) instance ToJSON PackagePlan where toJSON PackagePlan {..} = object $ maybe id (\cfi -> (("cabal-file-info" .= cfi):)) ppCabalFileInfo [ "version" .= ppVersion , "github-pings" .= ppGithubPings , "users" .= ppUsers , "constraints" .= ppConstraints , "description" .= ppDesc ] instance FromJSON PackagePlan where parseJSON = withObject "PackageBuild" $ \o -> do ppVersion <- o .: "version" ppCabalFileInfo <- o .:? "cabal-file-info" ppGithubPings <- o .:? "github-pings" .!= mempty ppUsers <- o .:? "users" .!= mempty ppConstraints <- o .: "constraints" ppDesc <- o .: "description" return PackagePlan {..} -- | Information on the contents of a cabal file data CabalFileInfo = CabalFileInfo { cfiSize :: !Int -- ^ File size in bytes , cfiHashes :: !(Map.Map Text Text) -- ^ Various hashes of the file contents } deriving (Show, Eq, Generic) instance ToJSON CabalFileInfo where toJSON CabalFileInfo {..} = object [ "size" .= cfiSize , "hashes" .= cfiHashes ] instance FromJSON CabalFileInfo where parseJSON = withObject "CabalFileInfo" $ \o -> do cfiSize <- o .: "size" cfiHashes <- o .: "hashes" return CabalFileInfo {..} display :: DT.Text a => a -> Text display = fromString . DT.display simpleParse :: (MonadThrow m, DT.Text a, Typeable a) => Text -> m a simpleParse orig = withTypeRep $ \rep -> case DT.simpleParse str of Nothing -> throwM (ParseFailedException rep (pack str)) Just v -> return v where str = unpack orig withTypeRep :: Typeable a => (TypeRep -> m a) -> m a withTypeRep f = res where res = f (typeOf (unwrap res)) unwrap :: m a -> a unwrap _ = error "unwrap" data BuildPlanTypesException = ParseSnapNameException Text | ParseFailedException TypeRep Text deriving Typeable instance Exception BuildPlanTypesException instance Show BuildPlanTypesException where show (ParseSnapNameException t) = "Invalid snapshot name: " ++ T.unpack t show (ParseFailedException rep t) = "Unable to parse " ++ show t ++ " as " ++ show rep data PackageConstraints = PackageConstraints { pcVersionRange :: VersionRange , pcMaintainer :: Maybe Maintainer , pcTests :: TestState , pcHaddocks :: TestState , pcBuildBenchmarks :: Bool , pcFlagOverrides :: Map FlagName Bool , pcEnableLibProfile :: Bool , pcHide :: Bool } deriving (Show, Eq) instance ToJSON PackageConstraints where toJSON PackageConstraints {..} = object $ addMaintainer [ "version-range" .= display pcVersionRange , "tests" .= pcTests , "haddocks" .= pcHaddocks , "build-benchmarks" .= pcBuildBenchmarks , "flags" .= pcFlagOverrides , "library-profiling" .= pcEnableLibProfile , "hide" .= pcHide ] where addMaintainer = maybe id (\m -> (("maintainer" .= m):)) pcMaintainer instance FromJSON PackageConstraints where parseJSON = withObject "PackageConstraints" $ \o -> do pcVersionRange <- (o .: "version-range") >>= either (fail . show) return . simpleParse pcTests <- o .: "tests" pcHaddocks <- o .: "haddocks" pcBuildBenchmarks <- o .: "build-benchmarks" pcFlagOverrides <- o .: "flags" pcMaintainer <- o .:? "maintainer" pcEnableLibProfile <- fmap (fromMaybe True) (o .:? "library-profiling") pcHide <- o .:? "hide" .!= False return PackageConstraints {..} data TestState = ExpectSuccess | ExpectFailure | Don'tBuild -- ^ when the test suite will pull in things we don't want deriving (Show, Eq, Ord, Bounded, Enum) testStateToText :: TestState -> Text testStateToText ExpectSuccess = "expect-success" testStateToText ExpectFailure = "expect-failure" testStateToText Don'tBuild = "do-not-build" instance ToJSON TestState where toJSON = toJSON . testStateToText instance FromJSON TestState where parseJSON = withText "TestState" $ \t -> case HashMap.lookup t states of Nothing -> fail $ "Invalid state: " ++ unpack t Just v -> return v where states = HashMap.fromList $ map (\x -> (testStateToText x, x)) [minBound..maxBound] data SystemInfo = SystemInfo { siCompilerVersion :: CompilerVersion , siOS :: OS , siArch :: Arch , siCorePackages :: Map PackageName Version , siCoreExecutables :: Set ExeName } deriving (Show, Eq, Ord) instance ToJSON SystemInfo where toJSON SystemInfo {..} = object $ (case siCompilerVersion of GhcVersion version -> "ghc-version" .= version _ -> "compiler-version" .= siCompilerVersion) : [ "os" .= display siOS , "arch" .= display siArch , "core-packages" .= siCorePackages , "core-executables" .= siCoreExecutables ] instance FromJSON SystemInfo where parseJSON = withObject "SystemInfo" $ \o -> do let helper name = (o .: name) >>= either (fail . show) return . simpleParse ghcVersion <- o .:? "ghc-version" compilerVersion <- o .:? "compiler-version" siCompilerVersion <- case (ghcVersion, compilerVersion) of (Just _, Just _) -> fail "can't have both compiler-version and ghc-version fields" (Just ghc, _) -> return (GhcVersion ghc) (_, Just compiler) -> return compiler _ -> fail "expected field \"ghc-version\" or \"compiler-version\" not present" siOS <- helper "os" siArch <- helper "arch" siCorePackages <- o .: "core-packages" siCoreExecutables <- o .: "core-executables" return SystemInfo {..} newtype Maintainer = Maintainer { unMaintainer :: Text } deriving (Show, Eq, Ord, Hashable, ToJSON, FromJSON, IsString) -- | Name of an executable. newtype ExeName = ExeName { unExeName :: Text } deriving (Show, Eq, Ord, Hashable, IsString, Generic, Store, NFData, Data, Typeable, ToJSON, ToJSONKey, FromJSONKey) instance FromJSON ExeName where parseJSON = withText "ExeName" $ return . ExeName -- | A simplified package description that tracks: -- -- * Package dependencies -- -- * Build tool dependencies -- -- * Provided executables -- -- It has fully resolved all conditionals data SimpleDesc = SimpleDesc { sdPackages :: Map PackageName DepInfo , sdTools :: Map ExeName DepInfo , sdProvidedExes :: Set ExeName , sdModules :: Set Text -- ^ modules exported by the library } deriving (Show, Eq) instance Monoid SimpleDesc where mempty = SimpleDesc mempty mempty mempty mempty mappend (SimpleDesc a b c d) (SimpleDesc w x y z) = SimpleDesc (Map.unionWith (<>) a w) (Map.unionWith (<>) b x) (c <> y) (d <> z) instance ToJSON SimpleDesc where toJSON SimpleDesc {..} = object [ "packages" .= sdPackages , "tools" .= sdTools , "provided-exes" .= sdProvidedExes , "modules" .= sdModules ] instance FromJSON SimpleDesc where parseJSON = withObject "SimpleDesc" $ \o -> do sdPackages <- o .: "packages" sdTools <- o .: "tools" sdProvidedExes <- o .: "provided-exes" sdModules <- o .: "modules" return SimpleDesc {..} data DepInfo = DepInfo { diComponents :: Set Component , diRange :: VersionRange } deriving (Show, Eq) instance Monoid DepInfo where mempty = DepInfo mempty C.anyVersion DepInfo a x `mappend` DepInfo b y = DepInfo (mappend a b) (C.intersectVersionRanges x y) instance ToJSON DepInfo where toJSON DepInfo {..} = object [ "components" .= diComponents , "range" .= display diRange ] instance FromJSON DepInfo where parseJSON = withObject "DepInfo" $ \o -> do diComponents <- o .: "components" diRange <- o .: "range" >>= either (fail . show) return . simpleParse return DepInfo {..} data Component = CompLibrary | CompExecutable | CompTestSuite | CompBenchmark deriving (Show, Read, Eq, Ord, Enum, Bounded) compToText :: Component -> Text compToText CompLibrary = "library" compToText CompExecutable = "executable" compToText CompTestSuite = "test-suite" compToText CompBenchmark = "benchmark" instance ToJSON Component where toJSON = toJSON . compToText instance FromJSON Component where parseJSON = withText "Component" $ \t -> maybe (fail $ "Invalid component: " ++ unpack t) return (HashMap.lookup t comps) where comps = HashMap.fromList $ map (compToText &&& id) [minBound..maxBound] -- | Convert a 'SnapName' into its short representation, e.g. @lts-2.8@, -- @nightly-2015-03-05@. renderSnapName :: SnapName -> Text renderSnapName (LTS x y) = T.pack $ concat ["lts-", show x, ".", show y] renderSnapName (Nightly d) = T.pack $ "nightly-" ++ show d -- | Parse the short representation of a 'SnapName'. parseSnapName :: MonadThrow m => Text -> m SnapName parseSnapName t0 = case lts <|> nightly of Nothing -> throwM $ ParseSnapNameException t0 Just sn -> return sn where lts = do t1 <- T.stripPrefix "lts-" t0 Right (x, t2) <- Just $ decimal t1 t3 <- T.stripPrefix "." t2 Right (y, "") <- Just $ decimal t3 return $ LTS x y nightly = do t1 <- T.stripPrefix "nightly-" t0 Nightly <$> readMay (T.unpack t1) -- | Most recent Nightly and newest LTS version per major release. data Snapshots = Snapshots { snapshotsNightly :: !Day , snapshotsLts :: !(IntMap Int) } deriving Show instance FromJSON Snapshots where parseJSON = withObject "Snapshots" $ \o -> Snapshots <$> (o .: "nightly" >>= parseNightly) <*> fmap IntMap.unions (mapM (parseLTS . snd) $ filter (isLTS . fst) $ HashMap.toList o) where parseNightly t = case parseSnapName t of Left e -> fail $ show e Right (LTS _ _) -> fail "Unexpected LTS value" Right (Nightly d) -> return d isLTS = ("lts-" `T.isPrefixOf`) parseLTS = withText "LTS" $ \t -> case parseSnapName t of Left e -> fail $ show e Right (LTS x y) -> return $ IntMap.singleton x y Right (Nightly _) -> fail "Unexpected nightly value" -- | A simplified version of the 'BuildPlan' + cabal file. data MiniBuildPlan = MiniBuildPlan { mbpCompilerVersion :: !CompilerVersion , mbpPackages :: !(Map PackageName MiniPackageInfo) } deriving (Generic, Show, Eq, Data, Typeable) instance Store MiniBuildPlan instance NFData MiniBuildPlan miniBuildPlanVC :: VersionConfig MiniBuildPlan miniBuildPlanVC = storeVersionConfig "mbp-v2" "C8q73RrYq3plf9hDCapjWpnm_yc=" -- | Information on a single package for the 'MiniBuildPlan'. data MiniPackageInfo = MiniPackageInfo { mpiVersion :: !Version , mpiFlags :: !(Map FlagName Bool) , mpiGhcOptions :: ![Text] , mpiPackageDeps :: !(Set PackageName) , mpiToolDeps :: !(Set Text) -- ^ Due to ambiguity in Cabal, it is unclear whether this refers to the -- executable name, the package name, or something else. We have to guess -- based on what's available, which is why we store this in an unwrapped -- 'Text'. , mpiExes :: !(Set ExeName) -- ^ Executables provided by this package , mpiHasLibrary :: !Bool -- ^ Is there a library present? , mpiGitSHA1 :: !(Maybe GitSHA1) -- ^ An optional SHA1 representation in hex format of the blob containing -- the cabal file contents. Useful for grabbing the correct cabal file -- revision directly from a Git repo or the 01-index.tar file } deriving (Generic, Show, Eq, Data, Typeable) instance Store MiniPackageInfo instance NFData MiniPackageInfo -- | A SHA1 hash, but in Git format. This means that the contents are -- prefixed with @blob@ and the size of the payload before hashing, as -- Git itself does. newtype GitSHA1 = GitSHA1 ByteString deriving (Generic, Show, Eq, NFData, Store, Data, Typeable, Ord, Hashable) newtype SnapshotHash = SnapshotHash { unShapshotHash :: ByteString } deriving (Generic, Show, Eq) trimmedSnapshotHash :: SnapshotHash -> ByteString trimmedSnapshotHash = BS.take 12 . unShapshotHash newtype ModuleName = ModuleName { unModuleName :: ByteString } deriving (Show, Eq, Ord, Generic, Store, NFData, Typeable, Data) data ModuleInfo = ModuleInfo { miCorePackages :: !(Set PackageName) , miModules :: !(Map ModuleName (Set PackageName)) } deriving (Show, Eq, Ord, Generic, Typeable, Data) instance Store ModuleInfo instance NFData ModuleInfo moduleInfoVC :: VersionConfig ModuleInfo moduleInfoVC = storeVersionConfig "mi-v1" "zyCpzzGXA8fTeBmKEWLa_6kF2_s=" stack-1.5.1/src/Stack/Types/CompilerBuild.hs0000644000000000000000000000246713135652051017051 0ustar0000000000000000module Stack.Types.CompilerBuild (CompilerBuild(..) ,compilerBuildName ,compilerBuildSuffix ,parseCompilerBuild ) where import Control.Monad.Catch (MonadThrow) import Data.Aeson.Extended (FromJSON, parseJSON, withText) import Data.Text as T data CompilerBuild = CompilerBuildStandard | CompilerBuildSpecialized String deriving (Show) instance FromJSON CompilerBuild where -- Strange structuring is to give consistent error messages parseJSON = withText "CompilerBuild" (either (fail . show) return . parseCompilerBuild . T.unpack) -- | Descriptive name for compiler build compilerBuildName :: CompilerBuild -> String compilerBuildName CompilerBuildStandard = "standard" compilerBuildName (CompilerBuildSpecialized s) = s -- | Suffix to use for filenames/directories constructed with compiler build compilerBuildSuffix :: CompilerBuild -> String compilerBuildSuffix CompilerBuildStandard = "" compilerBuildSuffix (CompilerBuildSpecialized s) = '-' : s -- | Parse compiler build from a String. parseCompilerBuild :: (MonadThrow m) => String -> m CompilerBuild parseCompilerBuild "" = return CompilerBuildStandard parseCompilerBuild "standard" = return CompilerBuildStandard parseCompilerBuild name = return (CompilerBuildSpecialized name) stack-1.5.1/src/Stack/Types/Urls.hs0000644000000000000000000000244613135652051015241 0ustar0000000000000000{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} module Stack.Types.Urls where import Control.Applicative import Data.Aeson.Extended import Data.Text (Text) import Data.Monoid import GHC.Generics (Generic) import Generics.Deriving.Monoid (memptydefault, mappenddefault) import Prelude data Urls = Urls { urlsLatestSnapshot :: !Text , urlsLtsBuildPlans :: !Text , urlsNightlyBuildPlans :: !Text } deriving Show -- TODO: Really need this instance? instance FromJSON (WithJSONWarnings Urls) where parseJSON = withObjectWarnings "Urls" $ \o -> do Urls <$> o ..: "latest-snapshot" <*> o ..: "lts-build-plans" <*> o ..: "nightly-build-plans" data UrlsMonoid = UrlsMonoid { urlsMonoidLatestSnapshot :: !(First Text) , urlsMonoidLtsBuildPlans :: !(First Text) , urlsMonoidNightlyBuildPlans :: !(First Text) } deriving (Show, Generic) instance FromJSON (WithJSONWarnings UrlsMonoid) where parseJSON = withObjectWarnings "UrlsMonoid" $ \o -> do UrlsMonoid <$> o ..: "latest-snapshot" <*> o ..: "lts-build-plans" <*> o ..: "nightly-build-plans" instance Monoid UrlsMonoid where mempty = memptydefault mappend = mappenddefault stack-1.5.1/src/Stack/Types/Compiler.hs0000644000000000000000000000645113135652051016066 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} module Stack.Types.Compiler where import Control.DeepSeq import Data.Aeson import Data.Data import Data.Monoid ((<>)) import Data.Store (Store) import qualified Data.Text as T import GHC.Generics (Generic) import Stack.Types.Version -- | Variety of compiler to use. data WhichCompiler = Ghc | Ghcjs deriving (Show, Eq, Ord) -- | Specifies a compiler and its version number(s). -- -- Note that despite having this datatype, stack isn't in a hurry to -- support compilers other than GHC. -- -- NOTE: updating this will change its binary serialization. The -- version number in the 'BinarySchema' instance for 'MiniBuildPlan' -- should be updated. data CompilerVersion = GhcVersion {-# UNPACK #-} !Version | GhcjsVersion {-# UNPACK #-} !Version -- GHCJS version {-# UNPACK #-} !Version -- GHC version deriving (Generic, Show, Eq, Ord, Data, Typeable) instance Store CompilerVersion instance NFData CompilerVersion instance ToJSON CompilerVersion where toJSON = toJSON . compilerVersionText instance FromJSON CompilerVersion where parseJSON (String t) = maybe (fail "Failed to parse compiler version") return (parseCompilerVersion t) parseJSON _ = fail "Invalid CompilerVersion, must be String" instance FromJSONKey CompilerVersion where fromJSONKey = FromJSONKeyTextParser $ \k -> case parseCompilerVersion k of Nothing -> fail $ "Failed to parse CompilerVersion " ++ T.unpack k Just parsed -> return parsed parseCompilerVersion :: T.Text -> Maybe CompilerVersion parseCompilerVersion t | Just t' <- T.stripPrefix "ghc-" t , Just v <- parseVersionFromString $ T.unpack t' = Just (GhcVersion v) | Just t' <- T.stripPrefix "ghcjs-" t , [tghcjs, tghc] <- T.splitOn "_ghc-" t' , Just vghcjs <- parseVersionFromString $ T.unpack tghcjs , Just vghc <- parseVersionFromString $ T.unpack tghc = Just (GhcjsVersion vghcjs vghc) | otherwise = Nothing compilerVersionText :: CompilerVersion -> T.Text compilerVersionText (GhcVersion vghc) = "ghc-" <> versionText vghc compilerVersionText (GhcjsVersion vghcjs vghc) = "ghcjs-" <> versionText vghcjs <> "_ghc-" <> versionText vghc compilerVersionString :: CompilerVersion -> String compilerVersionString = T.unpack . compilerVersionText whichCompiler :: CompilerVersion -> WhichCompiler whichCompiler GhcVersion {} = Ghc whichCompiler GhcjsVersion {} = Ghcjs isWantedCompiler :: VersionCheck -> CompilerVersion -> CompilerVersion -> Bool isWantedCompiler check (GhcVersion wanted) (GhcVersion actual) = checkVersion check wanted actual isWantedCompiler check (GhcjsVersion wanted wantedGhc) (GhcjsVersion actual actualGhc) = checkVersion check wanted actual && checkVersion check wantedGhc actualGhc isWantedCompiler _ _ _ = False getGhcVersion :: CompilerVersion -> Version getGhcVersion (GhcVersion v) = v getGhcVersion (GhcjsVersion _ v) = v compilerExeName :: WhichCompiler -> String compilerExeName Ghc = "ghc" compilerExeName Ghcjs = "ghcjs" haddockExeName :: WhichCompiler -> String haddockExeName Ghc = "haddock" haddockExeName Ghcjs = "haddock-ghcjs" stack-1.5.1/src/Stack/Types/Config.hs0000644000000000000000000022742013135652051015522 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE StandaloneDeriving #-} -- | The Config type. module Stack.Types.Config ( -- * Main configuration types and classes -- ** HasPlatform & HasStackRoot HasPlatform(..) ,PlatformVariant(..) -- ** Config & HasConfig ,Config(..) ,HasConfig(..) ,askLatestSnapshotUrl ,explicitSetupDeps ,getMinimalEnvOverride -- ** BuildConfig & HasBuildConfig ,BuildConfig(..) ,stackYamlL ,projectRootL ,HasBuildConfig(..) -- ** GHCVariant & HasGHCVariant ,GHCVariant(..) ,ghcVariantName ,ghcVariantSuffix ,parseGHCVariant ,HasGHCVariant(..) ,snapshotsDir -- ** Constraint synonym for use with StackMini ,StackMiniM -- ** EnvConfig & HasEnvConfig ,EnvConfig(..) ,HasEnvConfig(..) ,getCompilerPath -- * Details -- ** ApplyGhcOptions ,ApplyGhcOptions(..) -- ** ConfigException ,ConfigException(..) -- ** WhichSolverCmd ,WhichSolverCmd(..) -- ** ConfigMonoid ,ConfigMonoid(..) ,configMonoidInstallGHCName ,configMonoidSystemGHCName ,parseConfigMonoid -- ** DumpLogs ,DumpLogs(..) -- ** EnvSettings ,EnvSettings(..) ,minimalEnvSettings -- ** GlobalOpts & GlobalOptsMonoid ,GlobalOpts(..) ,GlobalOptsMonoid(..) ,StackYamlLoc(..) ,defaultLogLevel -- ** LoadConfig ,LoadConfig(..) -- ** PackageEntry & PackageLocation ,PackageEntry(..) ,TreatLikeExtraDep ,PackageLocation(..) ,RemotePackageType(..) -- ** PackageIndex, IndexName & IndexLocation -- Re-exports ,PackageIndex(..) ,IndexName(..) ,indexNameText -- Config fields ,configPackageIndex ,configPackageIndexOld ,configPackageIndexCache ,configPackageIndexCacheOld ,configPackageIndexGz ,configPackageIndexRoot ,configPackageTarball -- ** Project & ProjectAndConfigMonoid ,Project(..) ,ProjectAndConfigMonoid(..) ,parseProjectAndConfigMonoid -- ** PvpBounds ,PvpBounds(..) ,PvpBoundsType(..) ,parsePvpBounds -- ** ColorWhen ,ColorWhen(..) ,readColorWhen -- ** SCM ,SCM(..) -- ** CustomSnapshot ,CustomSnapshot(..) -- ** GhcOptions ,GhcOptions(..) ,ghcOptionsFor -- ** PackageFlags ,PackageFlags(..) -- * Paths ,bindirSuffix ,configInstalledCache ,configMiniBuildPlanCache ,getProjectWorkDir ,docDirSuffix ,flagCacheLocal ,extraBinDirs ,hpcReportDir ,installationRootDeps ,installationRootLocal ,hoogleRoot ,hoogleDatabasePath ,packageDatabaseDeps ,packageDatabaseExtra ,packageDatabaseLocal ,platformOnlyRelDir ,platformGhcRelDir ,platformGhcVerOnlyRelDir ,useShaPathOnWindows ,workDirL -- * Command-specific types -- ** Eval ,EvalOpts(..) -- ** Exec ,ExecOpts(..) ,SpecialExecCmd(..) ,ExecOptsExtra(..) -- ** Setup ,DownloadInfo(..) ,VersionedDownloadInfo(..) ,GHCDownloadInfo(..) ,SetupInfo(..) ,SetupInfoLocation(..) -- ** Docker entrypoint ,DockerEntrypoint(..) ,DockerUser(..) ,module X -- * Lens helpers ,wantedCompilerVersionL ,actualCompilerVersionL ,buildOptsL ,globalOptsL ,buildOptsInstallExesL ,buildOptsMonoidHaddockL ,buildOptsMonoidTestsL ,buildOptsMonoidBenchmarksL ,buildOptsMonoidInstallExesL ,buildOptsHaddockL ,globalOptsBuildOptsMonoidL ,packageIndicesL ,packageCachesL ,stackRootL ,configUrlsL ,cabalVersionL ,whichCompilerL -- * Lens reexport ,view ,to ) where import Control.Applicative import Control.Arrow ((&&&)) import Control.Exception import Control.Monad (liftM, mzero, join) import Control.Monad.Catch (MonadThrow, MonadMask) import Control.Monad.Logger (LogLevel(..), MonadLoggerIO) import Control.Monad.Reader (MonadReader, MonadIO, liftIO) import Control.Monad.Trans.Control import Data.Aeson.Extended (ToJSON, toJSON, FromJSON, parseJSON, withText, object, (.=), (..:), (..:?), (..!=), Value(Bool, String), withObjectWarnings, WarningParser, Object, jsonSubWarnings, jsonSubWarningsT, jsonSubWarningsTT, WithJSONWarnings(..), noJSONWarnings) import Data.Attoparsec.Args import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as S8 import Data.Either (partitionEithers) import Data.HashMap.Strict (HashMap) import Data.IORef (IORef) import Data.List (stripPrefix) import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NonEmpty import Data.Map (Map) import qualified Data.Map as Map import qualified Data.Map.Strict as M import Data.Maybe import Data.Monoid.Extra import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Data.Typeable import Data.Yaml (ParseException) import qualified Data.Yaml as Yaml import Distribution.System (Platform) import qualified Distribution.Text import Distribution.Version (anyVersion) import GHC.Generics (Generic) import Generics.Deriving.Monoid (memptydefault, mappenddefault) import Lens.Micro (Lens', lens, _1, _2, to, Getting) import Lens.Micro.Mtl (view) import Network.HTTP.Client (parseRequest) import Options.Applicative (ReadM) import qualified Options.Applicative as OA import qualified Options.Applicative.Types as OA import Path import qualified Paths_stack as Meta import Stack.Types.BuildPlan (GitSHA1, MiniBuildPlan(..), SnapName, renderSnapName) import Stack.Types.Compiler import Stack.Types.CompilerBuild import Stack.Types.Docker import Stack.Types.FlagName import Stack.Types.Image import Stack.Types.Nix import Stack.Types.PackageIdentifier import Stack.Types.PackageIndex import Stack.Types.PackageName import Stack.Types.Resolver import Stack.Types.TemplateName import Stack.Types.Urls import Stack.Types.Version import qualified System.FilePath as FilePath import System.PosixCompat.Types (UserID, GroupID, FileMode) import System.Process.Read (EnvOverride, findExecutable) -- Re-exports import Stack.Types.Config.Build as X #ifdef mingw32_HOST_OS import Crypto.Hash (hashWith, SHA1(..)) import qualified Data.ByteArray.Encoding as Mem (convertToBase, Base(Base16)) #endif -- | The top-level Stackage configuration. data Config = Config {configStackRoot :: !(Path Abs Dir) -- ^ ~/.stack more often than not ,configWorkDir :: !(Path Rel Dir) -- ^ this allows to override .stack-work directory ,configUserConfigPath :: !(Path Abs File) -- ^ Path to user configuration file (usually ~/.stack/config.yaml) ,configBuild :: !BuildOpts -- ^ Build configuration ,configDocker :: !DockerOpts -- ^ Docker configuration ,configNix :: !NixOpts -- ^ Execution environment (e.g nix-shell) configuration ,configEnvOverride :: !(EnvSettings -> IO EnvOverride) -- ^ Environment variables to be passed to external tools ,configLocalProgramsBase :: !(Path Abs Dir) -- ^ Non-platform-specific path containing local installations ,configLocalPrograms :: !(Path Abs Dir) -- ^ Path containing local installations (mainly GHC) ,configConnectionCount :: !Int -- ^ How many concurrent connections are allowed when downloading ,configHideTHLoading :: !Bool -- ^ Hide the Template Haskell "Loading package ..." messages from the -- console ,configPlatform :: !Platform -- ^ The platform we're building for, used in many directory names ,configPlatformVariant :: !PlatformVariant -- ^ Variant of the platform, also used in directory names ,configGHCVariant0 :: !(Maybe GHCVariant) -- ^ The variant of GHC requested by the user. -- In most cases, use 'BuildConfig' or 'MiniConfig's version instead, -- which will have an auto-detected default. ,configGHCBuild :: !(Maybe CompilerBuild) -- ^ Override build of the compiler distribution (e.g. standard, gmp4, tinfo6) ,configUrls :: !Urls -- ^ URLs for other files used by stack. -- TODO: Better document -- e.g. The latest snapshot file. -- A build plan name (e.g. lts5.9.yaml) is appended when downloading -- the build plan actually. ,configPackageIndices :: ![PackageIndex] -- ^ Information on package indices. This is left biased, meaning that -- packages in an earlier index will shadow those in a later index. -- -- Warning: if you override packages in an index vs what's available -- upstream, you may correct your compiled snapshots, as different -- projects may have different definitions of what pkg-ver means! This -- feature is primarily intended for adding local packages, not -- overriding. Overriding is better accomplished by adding to your -- list of packages. -- -- Note that indices specified in a later config file will override -- previous indices, /not/ extend them. -- -- Using an assoc list instead of a Map to keep track of priority ,configSystemGHC :: !Bool -- ^ Should we use the system-installed GHC (on the PATH) if -- available? Can be overridden by command line options. ,configInstallGHC :: !Bool -- ^ Should we automatically install GHC if missing or the wrong -- version is available? Can be overridden by command line options. ,configSkipGHCCheck :: !Bool -- ^ Don't bother checking the GHC version or architecture. ,configSkipMsys :: !Bool -- ^ On Windows: don't use a sandboxed MSYS ,configCompilerCheck :: !VersionCheck -- ^ Specifies which versions of the compiler are acceptable. ,configLocalBin :: !(Path Abs Dir) -- ^ Directory we should install executables into ,configRequireStackVersion :: !VersionRange -- ^ Require a version of stack within this range. ,configJobs :: !Int -- ^ How many concurrent jobs to run, defaults to number of capabilities ,configOverrideGccPath :: !(Maybe (Path Abs File)) -- ^ Optional gcc override path ,configExtraIncludeDirs :: !(Set FilePath) -- ^ --extra-include-dirs arguments ,configExtraLibDirs :: !(Set FilePath) -- ^ --extra-lib-dirs arguments ,configConcurrentTests :: !Bool -- ^ Run test suites concurrently ,configImage :: !ImageOpts ,configTemplateParams :: !(Map Text Text) -- ^ Parameters for templates. ,configScmInit :: !(Maybe SCM) -- ^ Initialize SCM (e.g. git) when creating new projects. ,configGhcOptions :: !GhcOptions -- ^ Additional GHC options to apply to either all packages (Nothing) -- or a specific package (Just). ,configSetupInfoLocations :: ![SetupInfoLocation] -- ^ Additional SetupInfo (inline or remote) to use to find tools. ,configPvpBounds :: !PvpBounds -- ^ How PVP upper bounds should be added to packages ,configModifyCodePage :: !Bool -- ^ Force the code page to UTF-8 on Windows ,configExplicitSetupDeps :: !(Map (Maybe PackageName) Bool) -- ^ See 'explicitSetupDeps'. 'Nothing' provides the default value. ,configRebuildGhcOptions :: !Bool -- ^ Rebuild on GHC options changes ,configApplyGhcOptions :: !ApplyGhcOptions -- ^ Which packages to ghc-options on the command line apply to? ,configAllowNewer :: !Bool -- ^ Ignore version ranges in .cabal files. Funny naming chosen to -- match cabal. ,configDefaultTemplate :: !(Maybe TemplateName) -- ^ The default template to use when none is specified. -- (If Nothing, the default default is used.) ,configAllowDifferentUser :: !Bool -- ^ Allow users other than the stack root owner to use the stack -- installation. ,configPackageCaches :: !(IORef (Maybe (Map PackageIdentifier (PackageIndex, PackageCache), HashMap GitSHA1 (PackageIndex, OffsetSize)))) -- ^ In memory cache of hackage index. ,configDumpLogs :: !DumpLogs -- ^ Dump logs of local non-dependencies when doing a build. ,configMaybeProject :: !(Maybe (Project, Path Abs File)) -- ^ 'Just' when a local project can be found, 'Nothing' when stack must -- fall back on the implicit global project. ,configAllowLocals :: !Bool -- ^ Are we allowed to build local packages? The script -- command disallows this. ,configSaveHackageCreds :: !Bool -- ^ Should we save Hackage credentials to a file? } -- | Which packages do ghc-options on the command line apply to? data ApplyGhcOptions = AGOTargets -- ^ all local targets | AGOLocals -- ^ all local packages, even non-targets | AGOEverything -- ^ every package deriving (Show, Read, Eq, Ord, Enum, Bounded) instance FromJSON ApplyGhcOptions where parseJSON = withText "ApplyGhcOptions" $ \t -> case t of "targets" -> return AGOTargets "locals" -> return AGOLocals "everything" -> return AGOEverything _ -> fail $ "Invalid ApplyGhcOptions: " ++ show t -- | Which build log files to dump data DumpLogs = DumpNoLogs -- ^ don't dump any logfiles | DumpWarningLogs -- ^ dump logfiles containing warnings | DumpAllLogs -- ^ dump all logfiles deriving (Show, Read, Eq, Ord, Enum, Bounded) instance FromJSON DumpLogs where parseJSON (Bool True) = return DumpAllLogs parseJSON (Bool False) = return DumpNoLogs parseJSON v = withText "DumpLogs" (\t -> if | t == "none" -> return DumpNoLogs | t == "warning" -> return DumpWarningLogs | t == "all" -> return DumpAllLogs | otherwise -> fail ("Invalid DumpLogs: " ++ show t)) v -- | Controls which version of the environment is used data EnvSettings = EnvSettings { esIncludeLocals :: !Bool -- ^ include local project bin directory, GHC_PACKAGE_PATH, etc , esIncludeGhcPackagePath :: !Bool -- ^ include the GHC_PACKAGE_PATH variable , esStackExe :: !Bool -- ^ set the STACK_EXE variable to the current executable name , esLocaleUtf8 :: !Bool -- ^ set the locale to C.UTF-8 } deriving (Show, Eq, Ord) data ExecOpts = ExecOpts { eoCmd :: !SpecialExecCmd , eoArgs :: ![String] , eoExtra :: !ExecOptsExtra } deriving (Show) data SpecialExecCmd = ExecCmd String | ExecGhc | ExecRunGhc deriving (Show, Eq) data ExecOptsExtra = ExecOptsPlain | ExecOptsEmbellished { eoEnvSettings :: !EnvSettings , eoPackages :: ![String] , eoRtsOptions :: ![String] } deriving (Show) data EvalOpts = EvalOpts { evalArg :: !String , evalExtra :: !ExecOptsExtra } deriving (Show) -- | Parsed global command-line options. data GlobalOpts = GlobalOpts { globalReExecVersion :: !(Maybe String) -- ^ Expected re-exec in container version , globalDockerEntrypoint :: !(Maybe DockerEntrypoint) -- ^ Data used when stack is acting as a Docker entrypoint (internal use only) , globalLogLevel :: !LogLevel -- ^ Log level , globalTimeInLog :: !Bool -- ^ Whether to include timings in logs. , globalConfigMonoid :: !ConfigMonoid -- ^ Config monoid, for passing into 'loadConfig' , globalResolver :: !(Maybe AbstractResolver) -- ^ Resolver override , globalCompiler :: !(Maybe CompilerVersion) -- ^ Compiler override , globalTerminal :: !Bool -- ^ We're in a terminal? , globalColorWhen :: !ColorWhen -- ^ When to use ansi terminal colors , globalStackYaml :: !(StackYamlLoc FilePath) -- ^ Override project stack.yaml } deriving (Show) data StackYamlLoc filepath = SYLDefault | SYLOverride !filepath | SYLNoConfig deriving (Show,Functor,Foldable,Traversable) -- | Parsed global command-line options monoid. data GlobalOptsMonoid = GlobalOptsMonoid { globalMonoidReExecVersion :: !(First String) -- ^ Expected re-exec in container version , globalMonoidDockerEntrypoint :: !(First DockerEntrypoint) -- ^ Data used when stack is acting as a Docker entrypoint (internal use only) , globalMonoidLogLevel :: !(First LogLevel) -- ^ Log level , globalMonoidTimeInLog :: !(First Bool) -- ^ Whether to include timings in logs. , globalMonoidConfigMonoid :: !ConfigMonoid -- ^ Config monoid, for passing into 'loadConfig' , globalMonoidResolver :: !(First AbstractResolver) -- ^ Resolver override , globalMonoidCompiler :: !(First CompilerVersion) -- ^ Compiler override , globalMonoidTerminal :: !(First Bool) -- ^ We're in a terminal? , globalMonoidColorWhen :: !(First ColorWhen) -- ^ When to use ansi colors , globalMonoidStackYaml :: !(First FilePath) -- ^ Override project stack.yaml } deriving (Show, Generic) instance Monoid GlobalOptsMonoid where mempty = memptydefault mappend = mappenddefault -- | Default logging level should be something useful but not crazy. defaultLogLevel :: LogLevel defaultLogLevel = LevelInfo data ColorWhen = ColorNever | ColorAlways | ColorAuto deriving (Show, Generic) readColorWhen :: ReadM ColorWhen readColorWhen = do s <- OA.readerAsk case s of "never" -> return ColorNever "always" -> return ColorAlways "auto" -> return ColorAuto _ -> OA.readerError "Expected values of color option are 'never', 'always', or 'auto'." -- | A superset of 'Config' adding information on how to build code. The reason -- for this breakdown is because we will need some of the information from -- 'Config' in order to determine the values here. -- -- These are the components which know nothing about local configuration. data BuildConfig = BuildConfig { bcConfig :: !Config , bcResolver :: !LoadedResolver -- ^ How we resolve which dependencies to install given a set of -- packages. , bcWantedMiniBuildPlan :: !MiniBuildPlan -- ^ Build plan wanted for this build , bcGHCVariant :: !GHCVariant -- ^ The variant of GHC used to select a GHC bindist. , bcPackageEntries :: ![PackageEntry] -- ^ Local packages , bcExtraDeps :: !(Map PackageName Version) -- ^ Extra dependencies specified in configuration. -- -- These dependencies will not be installed to a shared location, and -- will override packages provided by the resolver. , bcExtraPackageDBs :: ![Path Abs Dir] -- ^ Extra package databases , bcStackYaml :: !(Path Abs File) -- ^ Location of the stack.yaml file. -- -- Note: if the STACK_YAML environment variable is used, this may be -- different from projectRootL "stack.yaml" -- -- FIXME MSS 2016-12-08: is the above comment still true? projectRootL -- is defined in terms of bcStackYaml , bcFlags :: !PackageFlags -- ^ Per-package flag overrides , bcImplicitGlobal :: !Bool -- ^ Are we loading from the implicit global stack.yaml? This is useful -- for providing better error messages. } stackYamlL :: HasBuildConfig env => Lens' env (Path Abs File) stackYamlL = buildConfigL.lens bcStackYaml (\x y -> x { bcStackYaml = y }) -- | Directory containing the project's stack.yaml file projectRootL :: HasBuildConfig env => Getting r env (Path Abs Dir) projectRootL = stackYamlL.to parent -- | Configuration after the environment has been setup. data EnvConfig = EnvConfig {envConfigBuildConfig :: !BuildConfig ,envConfigCabalVersion :: !Version -- ^ This is the version of Cabal that stack will use to compile Setup.hs files -- in the build process. -- -- Note that this is not necessarily the same version as the one that stack -- depends on as a library and which is displayed when running -- @stack list-dependencies | grep Cabal@ in the stack project. ,envConfigCompilerVersion :: !CompilerVersion -- ^ The actual version of the compiler to be used, as opposed to -- 'wantedCompilerL', which provides the version specified by the -- build plan. ,envConfigCompilerBuild :: !CompilerBuild ,envConfigPackagesRef :: !(IORef (Maybe (Map (Path Abs Dir) TreatLikeExtraDep))) -- ^ Cache for 'getLocalPackages'. } -- | Value returned by 'Stack.Config.loadConfig'. data LoadConfig m = LoadConfig { lcConfig :: !Config -- ^ Top-level Stack configuration. , lcLoadBuildConfig :: !(Maybe CompilerVersion -> m BuildConfig) -- ^ Action to load the remaining 'BuildConfig'. , lcProjectRoot :: !(Maybe (Path Abs Dir)) -- ^ The project root directory, if in a project. } data PackageEntry = PackageEntry { peExtraDepMaybe :: !(Maybe TreatLikeExtraDep) , peLocation :: !PackageLocation , peSubdirs :: ![FilePath] } deriving Show -- | Perform defaulting of peExtraDepMaybe peExtraDepDef :: PackageEntry -> TreatLikeExtraDep peExtraDepDef = fromMaybe False . peExtraDepMaybe -- | Should a package be treated just like an extra-dep? -- -- 'True' means, it will only be built as a dependency -- for others, and its test suite/benchmarks will not be run. -- -- Useful modifying an upstream package, see: -- https://github.com/commercialhaskell/stack/issues/219 -- https://github.com/commercialhaskell/stack/issues/386 type TreatLikeExtraDep = Bool instance ToJSON PackageEntry where toJSON pe | not (peExtraDepDef pe) && null (peSubdirs pe) = toJSON $ peLocation pe toJSON pe = object $ maybe id (\e -> (("extra-dep" .= e):)) (peExtraDepMaybe pe) [ "location" .= peLocation pe , "subdirs" .= peSubdirs pe ] instance FromJSON (WithJSONWarnings PackageEntry) where parseJSON (String t) = do WithJSONWarnings loc _ <- parseJSON $ String t return $ noJSONWarnings PackageEntry { peExtraDepMaybe = Nothing , peLocation = loc , peSubdirs = [] } parseJSON v = withObjectWarnings "PackageEntry" (\o -> PackageEntry <$> o ..:? "extra-dep" <*> jsonSubWarnings (o ..: "location") <*> o ..:? "subdirs" ..!= []) v data PackageLocation = PLFilePath FilePath -- ^ Note that we use @FilePath@ and not @Path@s. The goal is: first parse -- the value raw, and then use @canonicalizePath@ and @parseAbsDir@. | PLRemote Text RemotePackageType -- ^ URL and further details deriving Show data RemotePackageType = RPTHttp | RPTGit Text -- ^ Commit | RPTHg Text -- ^ Commit deriving Show instance ToJSON PackageLocation where toJSON (PLFilePath fp) = toJSON fp toJSON (PLRemote t RPTHttp) = toJSON t toJSON (PLRemote x (RPTGit y)) = object [("git", toJSON x), ("commit", toJSON y)] toJSON (PLRemote x (RPTHg y)) = object [( "hg", toJSON x), ("commit", toJSON y)] instance FromJSON (WithJSONWarnings PackageLocation) where parseJSON v = (noJSONWarnings <$> withText "PackageLocation" (\t -> http t <|> file t) v) <|> git v <|> hg v where file t = pure $ PLFilePath $ T.unpack t http t = case parseRequest $ T.unpack t of Left _ -> mzero Right _ -> return $ PLRemote t RPTHttp git = withObjectWarnings "PackageGitLocation" $ \o -> PLRemote <$> o ..: "git" <*> (RPTGit <$> o ..: "commit") hg = withObjectWarnings "PackageHgLocation" $ \o -> PLRemote <$> o ..: "hg" <*> (RPTHg <$> o ..: "commit") -- | A project is a collection of packages. We can have multiple stack.yaml -- files, but only one of them may contain project information. data Project = Project { projectUserMsg :: !(Maybe String) -- ^ A warning message to display to the user when the auto generated -- config may have issues. , projectPackages :: ![PackageEntry] -- ^ Components of the package list , projectExtraDeps :: !(Map PackageName Version) -- ^ Components of the package list referring to package/version combos, -- see: https://github.com/fpco/stack/issues/41 , projectFlags :: !PackageFlags -- ^ Per-package flag overrides , projectResolver :: !Resolver -- ^ How we resolve which dependencies to use , projectCompiler :: !(Maybe CompilerVersion) -- ^ When specified, overrides which compiler to use , projectExtraPackageDBs :: ![FilePath] } deriving Show instance ToJSON Project where toJSON p = object $ maybe id (\cv -> (("compiler" .= cv) :)) (projectCompiler p) $ maybe id (\msg -> (("user-message" .= msg) :)) (projectUserMsg p) [ "packages" .= projectPackages p , "extra-deps" .= map fromTuple (Map.toList $ projectExtraDeps p) , "flags" .= projectFlags p , "resolver" .= projectResolver p , "extra-package-dbs" .= projectExtraPackageDBs p ] -- | Constraint synonym for constraints satisfied by a 'MiniConfig' -- environment. type StackMiniM r m = ( MonadReader r m, MonadIO m, MonadBaseControl IO m, MonadLoggerIO m, MonadMask m ) -- An uninterpreted representation of configuration options. -- Configurations may be "cascaded" using mappend (left-biased). data ConfigMonoid = ConfigMonoid { configMonoidStackRoot :: !(First (Path Abs Dir)) -- ^ See: 'configStackRoot' , configMonoidWorkDir :: !(First (Path Rel Dir)) -- ^ See: 'configWorkDir'. , configMonoidBuildOpts :: !BuildOptsMonoid -- ^ build options. , configMonoidDockerOpts :: !DockerOptsMonoid -- ^ Docker options. , configMonoidNixOpts :: !NixOptsMonoid -- ^ Options for the execution environment (nix-shell or container) , configMonoidConnectionCount :: !(First Int) -- ^ See: 'configConnectionCount' , configMonoidHideTHLoading :: !(First Bool) -- ^ See: 'configHideTHLoading' , configMonoidLatestSnapshotUrl :: !(First Text) -- ^ Deprecated in favour of 'urlsMonoidLatestSnapshot' , configMonoidUrls :: !UrlsMonoid -- ^ See: 'configUrls , configMonoidPackageIndices :: !(First [PackageIndex]) -- ^ See: 'configPackageIndices' , configMonoidSystemGHC :: !(First Bool) -- ^ See: 'configSystemGHC' ,configMonoidInstallGHC :: !(First Bool) -- ^ See: 'configInstallGHC' ,configMonoidSkipGHCCheck :: !(First Bool) -- ^ See: 'configSkipGHCCheck' ,configMonoidSkipMsys :: !(First Bool) -- ^ See: 'configSkipMsys' ,configMonoidCompilerCheck :: !(First VersionCheck) -- ^ See: 'configCompilerCheck' ,configMonoidRequireStackVersion :: !IntersectingVersionRange -- ^ See: 'configRequireStackVersion' ,configMonoidArch :: !(First String) -- ^ Used for overriding the platform ,configMonoidGHCVariant :: !(First GHCVariant) -- ^ Used for overriding the platform ,configMonoidGHCBuild :: !(First CompilerBuild) -- ^ Used for overriding the GHC build ,configMonoidJobs :: !(First Int) -- ^ See: 'configJobs' ,configMonoidExtraIncludeDirs :: !(Set FilePath) -- ^ See: 'configExtraIncludeDirs' ,configMonoidExtraLibDirs :: !(Set FilePath) -- ^ See: 'configExtraLibDirs' , configMonoidOverrideGccPath :: !(First (Path Abs File)) -- ^ Allow users to override the path to gcc ,configMonoidConcurrentTests :: !(First Bool) -- ^ See: 'configConcurrentTests' ,configMonoidLocalBinPath :: !(First FilePath) -- ^ Used to override the binary installation dir ,configMonoidImageOpts :: !ImageOptsMonoid -- ^ Image creation options. ,configMonoidTemplateParameters :: !(Map Text Text) -- ^ Template parameters. ,configMonoidScmInit :: !(First SCM) -- ^ Initialize SCM (e.g. git init) when making new projects? ,configMonoidGhcOptions :: !GhcOptions -- ^ See 'configGhcOptions' ,configMonoidExtraPath :: ![Path Abs Dir] -- ^ Additional paths to search for executables in ,configMonoidSetupInfoLocations :: ![SetupInfoLocation] -- ^ Additional setup info (inline or remote) to use for installing tools ,configMonoidLocalProgramsBase :: !(First (Path Abs Dir)) -- ^ Override the default local programs dir, where e.g. GHC is installed. ,configMonoidPvpBounds :: !(First PvpBounds) -- ^ See 'configPvpBounds' ,configMonoidModifyCodePage :: !(First Bool) -- ^ See 'configModifyCodePage' ,configMonoidExplicitSetupDeps :: !(Map (Maybe PackageName) Bool) -- ^ See 'configExplicitSetupDeps' ,configMonoidRebuildGhcOptions :: !(First Bool) -- ^ See 'configMonoidRebuildGhcOptions' ,configMonoidApplyGhcOptions :: !(First ApplyGhcOptions) -- ^ See 'configApplyGhcOptions' ,configMonoidAllowNewer :: !(First Bool) -- ^ See 'configMonoidAllowNewer' ,configMonoidDefaultTemplate :: !(First TemplateName) -- ^ The default template to use when none is specified. -- (If Nothing, the default default is used.) , configMonoidAllowDifferentUser :: !(First Bool) -- ^ Allow users other than the stack root owner to use the stack -- installation. , configMonoidDumpLogs :: !(First DumpLogs) -- ^ See 'configDumpLogs' , configMonoidSaveHackageCreds :: !(First Bool) -- ^ See 'configSaveHackageCreds' } deriving (Show, Generic) instance Monoid ConfigMonoid where mempty = memptydefault mappend = mappenddefault parseConfigMonoid :: Path Abs Dir -> Value -> Yaml.Parser (WithJSONWarnings ConfigMonoid) parseConfigMonoid = withObjectWarnings "ConfigMonoid" . parseConfigMonoidObject -- | Parse a partial configuration. Used both to parse both a standalone config -- file and a project file, so that a sub-parser is not required, which would interfere with -- warnings for missing fields. parseConfigMonoidObject :: Path Abs Dir -> Object -> WarningParser ConfigMonoid parseConfigMonoidObject rootDir obj = do -- Parsing 'stackRoot' from 'stackRoot'/config.yaml would be nonsensical let configMonoidStackRoot = First Nothing configMonoidWorkDir <- First <$> obj ..:? configMonoidWorkDirName configMonoidBuildOpts <- jsonSubWarnings (obj ..:? configMonoidBuildOptsName ..!= mempty) configMonoidDockerOpts <- jsonSubWarnings (obj ..:? configMonoidDockerOptsName ..!= mempty) configMonoidNixOpts <- jsonSubWarnings (obj ..:? configMonoidNixOptsName ..!= mempty) configMonoidConnectionCount <- First <$> obj ..:? configMonoidConnectionCountName configMonoidHideTHLoading <- First <$> obj ..:? configMonoidHideTHLoadingName configMonoidLatestSnapshotUrl <- First <$> obj ..:? configMonoidLatestSnapshotUrlName configMonoidUrls <- jsonSubWarnings (obj ..:? configMonoidUrlsName ..!= mempty) configMonoidPackageIndices <- First <$> jsonSubWarningsTT (obj ..:? configMonoidPackageIndicesName) configMonoidSystemGHC <- First <$> obj ..:? configMonoidSystemGHCName configMonoidInstallGHC <- First <$> obj ..:? configMonoidInstallGHCName configMonoidSkipGHCCheck <- First <$> obj ..:? configMonoidSkipGHCCheckName configMonoidSkipMsys <- First <$> obj ..:? configMonoidSkipMsysName configMonoidRequireStackVersion <- IntersectingVersionRange . unVersionRangeJSON <$> ( obj ..:? configMonoidRequireStackVersionName ..!= VersionRangeJSON anyVersion) configMonoidArch <- First <$> obj ..:? configMonoidArchName configMonoidGHCVariant <- First <$> obj ..:? configMonoidGHCVariantName configMonoidGHCBuild <- First <$> obj ..:? configMonoidGHCBuildName configMonoidJobs <- First <$> obj ..:? configMonoidJobsName configMonoidExtraIncludeDirs <- fmap (Set.map (toFilePath rootDir FilePath.)) $ obj ..:? configMonoidExtraIncludeDirsName ..!= Set.empty configMonoidExtraLibDirs <- fmap (Set.map (toFilePath rootDir FilePath.)) $ obj ..:? configMonoidExtraLibDirsName ..!= Set.empty configMonoidOverrideGccPath <- First <$> obj ..:? configMonoidOverrideGccPathName configMonoidConcurrentTests <- First <$> obj ..:? configMonoidConcurrentTestsName configMonoidLocalBinPath <- First <$> obj ..:? configMonoidLocalBinPathName configMonoidImageOpts <- jsonSubWarnings (obj ..:? configMonoidImageOptsName ..!= mempty) templates <- obj ..:? "templates" (configMonoidScmInit,configMonoidTemplateParameters) <- case templates of Nothing -> return (First Nothing,M.empty) Just tobj -> do scmInit <- tobj ..:? configMonoidScmInitName params <- tobj ..:? configMonoidTemplateParametersName return (First scmInit,fromMaybe M.empty params) configMonoidCompilerCheck <- First <$> obj ..:? configMonoidCompilerCheckName configMonoidGhcOptions <- obj ..:? configMonoidGhcOptionsName ..!= mempty configMonoidExtraPath <- obj ..:? configMonoidExtraPathName ..!= [] configMonoidSetupInfoLocations <- maybeToList <$> jsonSubWarningsT (obj ..:? configMonoidSetupInfoLocationsName) configMonoidLocalProgramsBase <- First <$> obj ..:? configMonoidLocalProgramsBaseName configMonoidPvpBounds <- First <$> obj ..:? configMonoidPvpBoundsName configMonoidModifyCodePage <- First <$> obj ..:? configMonoidModifyCodePageName configMonoidExplicitSetupDeps <- (obj ..:? configMonoidExplicitSetupDepsName ..!= mempty) >>= fmap Map.fromList . mapM handleExplicitSetupDep . Map.toList configMonoidRebuildGhcOptions <- First <$> obj ..:? configMonoidRebuildGhcOptionsName configMonoidApplyGhcOptions <- First <$> obj ..:? configMonoidApplyGhcOptionsName configMonoidAllowNewer <- First <$> obj ..:? configMonoidAllowNewerName configMonoidDefaultTemplate <- First <$> obj ..:? configMonoidDefaultTemplateName configMonoidAllowDifferentUser <- First <$> obj ..:? configMonoidAllowDifferentUserName configMonoidDumpLogs <- First <$> obj ..:? configMonoidDumpLogsName configMonoidSaveHackageCreds <- First <$> obj ..:? configMonoidSaveHackageCredsName return ConfigMonoid {..} where handleExplicitSetupDep :: Monad m => (Text, Bool) -> m (Maybe PackageName, Bool) handleExplicitSetupDep (name', b) = do name <- if name' == "*" then return Nothing else case parsePackageNameFromString $ T.unpack name' of Left e -> fail $ show e Right x -> return $ Just x return (name, b) configMonoidWorkDirName :: Text configMonoidWorkDirName = "work-dir" configMonoidBuildOptsName :: Text configMonoidBuildOptsName = "build" configMonoidDockerOptsName :: Text configMonoidDockerOptsName = "docker" configMonoidNixOptsName :: Text configMonoidNixOptsName = "nix" configMonoidConnectionCountName :: Text configMonoidConnectionCountName = "connection-count" configMonoidHideTHLoadingName :: Text configMonoidHideTHLoadingName = "hide-th-loading" configMonoidLatestSnapshotUrlName :: Text configMonoidLatestSnapshotUrlName = "latest-snapshot-url" configMonoidUrlsName :: Text configMonoidUrlsName = "urls" configMonoidPackageIndicesName :: Text configMonoidPackageIndicesName = "package-indices" configMonoidSystemGHCName :: Text configMonoidSystemGHCName = "system-ghc" configMonoidInstallGHCName :: Text configMonoidInstallGHCName = "install-ghc" configMonoidSkipGHCCheckName :: Text configMonoidSkipGHCCheckName = "skip-ghc-check" configMonoidSkipMsysName :: Text configMonoidSkipMsysName = "skip-msys" configMonoidRequireStackVersionName :: Text configMonoidRequireStackVersionName = "require-stack-version" configMonoidArchName :: Text configMonoidArchName = "arch" configMonoidGHCVariantName :: Text configMonoidGHCVariantName = "ghc-variant" configMonoidGHCBuildName :: Text configMonoidGHCBuildName = "ghc-build" configMonoidJobsName :: Text configMonoidJobsName = "jobs" configMonoidExtraIncludeDirsName :: Text configMonoidExtraIncludeDirsName = "extra-include-dirs" configMonoidExtraLibDirsName :: Text configMonoidExtraLibDirsName = "extra-lib-dirs" configMonoidOverrideGccPathName :: Text configMonoidOverrideGccPathName = "with-gcc" configMonoidConcurrentTestsName :: Text configMonoidConcurrentTestsName = "concurrent-tests" configMonoidLocalBinPathName :: Text configMonoidLocalBinPathName = "local-bin-path" configMonoidImageOptsName :: Text configMonoidImageOptsName = "image" configMonoidScmInitName :: Text configMonoidScmInitName = "scm-init" configMonoidTemplateParametersName :: Text configMonoidTemplateParametersName = "params" configMonoidCompilerCheckName :: Text configMonoidCompilerCheckName = "compiler-check" configMonoidGhcOptionsName :: Text configMonoidGhcOptionsName = "ghc-options" configMonoidExtraPathName :: Text configMonoidExtraPathName = "extra-path" configMonoidSetupInfoLocationsName :: Text configMonoidSetupInfoLocationsName = "setup-info" configMonoidLocalProgramsBaseName :: Text configMonoidLocalProgramsBaseName = "local-programs-path" configMonoidPvpBoundsName :: Text configMonoidPvpBoundsName = "pvp-bounds" configMonoidModifyCodePageName :: Text configMonoidModifyCodePageName = "modify-code-page" configMonoidExplicitSetupDepsName :: Text configMonoidExplicitSetupDepsName = "explicit-setup-deps" configMonoidRebuildGhcOptionsName :: Text configMonoidRebuildGhcOptionsName = "rebuild-ghc-options" configMonoidApplyGhcOptionsName :: Text configMonoidApplyGhcOptionsName = "apply-ghc-options" configMonoidAllowNewerName :: Text configMonoidAllowNewerName = "allow-newer" configMonoidDefaultTemplateName :: Text configMonoidDefaultTemplateName = "default-template" configMonoidAllowDifferentUserName :: Text configMonoidAllowDifferentUserName = "allow-different-user" configMonoidDumpLogsName :: Text configMonoidDumpLogsName = "dump-logs" configMonoidSaveHackageCredsName :: Text configMonoidSaveHackageCredsName = "save-hackage-creds" data ConfigException = ParseConfigFileException (Path Abs File) ParseException | ParseCustomSnapshotException Text ParseException | ParseResolverException Text | NoProjectConfigFound (Path Abs Dir) (Maybe Text) | UnexpectedArchiveContents [Path Abs Dir] [Path Abs File] | UnableToExtractArchive Text (Path Abs File) | BadStackVersionException VersionRange | NoMatchingSnapshot WhichSolverCmd (NonEmpty SnapName) | forall l. ResolverMismatch WhichSolverCmd (ResolverThat's l) String | ResolverPartial WhichSolverCmd Resolver String | NoSuchDirectory FilePath | ParseGHCVariantException String | BadStackRoot (Path Abs Dir) | Won'tCreateStackRootInDirectoryOwnedByDifferentUser (Path Abs Dir) (Path Abs Dir) -- ^ @$STACK_ROOT@, parent dir | UserDoesn'tOwnDirectory (Path Abs Dir) | FailedToCloneRepo String | ManualGHCVariantSettingsAreIncompatibleWithSystemGHC | NixRequiresSystemGhc | NoResolverWhenUsingNoLocalConfig | InvalidResolverForNoLocalConfig String deriving Typeable instance Show ConfigException where show (ParseConfigFileException configFile exception) = concat [ "Could not parse '" , toFilePath configFile , "':\n" , Yaml.prettyPrintParseException exception , "\nSee http://docs.haskellstack.org/en/stable/yaml_configuration/" ] show (ParseCustomSnapshotException url exception) = concat [ "Could not parse '" , T.unpack url , "':\n" , Yaml.prettyPrintParseException exception -- FIXME: Link to docs about custom snapshots -- , "\nSee http://docs.haskellstack.org/en/stable/yaml_configuration/" ] show (ParseResolverException t) = concat [ "Invalid resolver value: " , T.unpack t , ". Possible valid values include lts-2.12, nightly-YYYY-MM-DD, ghc-7.10.2, and ghcjs-0.1.0_ghc-7.10.2. " , "See https://www.stackage.org/snapshots for a complete list." ] show (NoProjectConfigFound dir mcmd) = concat [ "Unable to find a stack.yaml file in the current directory (" , toFilePath dir , ") or its ancestors" , case mcmd of Nothing -> "" Just cmd -> "\nRecommended action: stack " ++ T.unpack cmd ] show (UnexpectedArchiveContents dirs files) = concat [ "When unpacking an archive specified in your stack.yaml file, " , "did not find expected contents. Expected: a single directory. Found: " , show ( map (toFilePath . dirname) dirs , map (toFilePath . filename) files ) ] show (UnableToExtractArchive url file) = concat [ "Archive extraction failed. We support tarballs and zip, couldn't handle the following URL, " , T.unpack url, " downloaded to the file ", toFilePath $ filename file ] show (BadStackVersionException requiredRange) = concat [ "The version of stack you are using (" , show (fromCabalVersion Meta.version) , ") is outside the required\n" ,"version range specified in stack.yaml (" , T.unpack (versionRangeText requiredRange) , ")." ] show (NoMatchingSnapshot whichCmd names) = concat [ "None of the following snapshots provides a compiler matching " , "your package(s):\n" , unlines $ map (\name -> " - " <> T.unpack (renderSnapName name)) (NonEmpty.toList names) , showOptions whichCmd Don'tSuggestSolver ] show (ResolverMismatch whichCmd resolver errDesc) = concat [ "Resolver '" , T.unpack (resolverName resolver) , "' does not have a matching compiler to build some or all of your " , "package(s).\n" , errDesc , showOptions whichCmd Don'tSuggestSolver ] show (ResolverPartial whichCmd resolver errDesc) = concat [ "Resolver '" , T.unpack (resolverName resolver) , "' does not have all the packages to match your requirements.\n" , unlines $ fmap (" " <>) (lines errDesc) , showOptions whichCmd (case whichCmd of IsSolverCmd -> Don'tSuggestSolver _ -> SuggestSolver) ] show (NoSuchDirectory dir) = "No directory could be located matching the supplied path: " ++ dir show (ParseGHCVariantException v) = "Invalid ghc-variant value: " ++ v show (BadStackRoot stackRoot) = concat [ "Invalid stack root: '" , toFilePath stackRoot , "'. Please provide a valid absolute path." ] show (Won'tCreateStackRootInDirectoryOwnedByDifferentUser envStackRoot parentDir) = concat [ "Preventing creation of stack root '" , toFilePath envStackRoot , "'. Parent directory '" , toFilePath parentDir , "' is owned by someone else." ] show (UserDoesn'tOwnDirectory dir) = concat [ "You are not the owner of '" , toFilePath dir , "'. Aborting to protect file permissions." , "\nRetry with '--" , T.unpack configMonoidAllowDifferentUserName , "' to disable this precaution." ] show (FailedToCloneRepo commandName) = concat [ "Failed to use " , commandName , " to clone the repo. Please ensure that " , commandName , " is installed and available to stack on your PATH environment variable." ] show ManualGHCVariantSettingsAreIncompatibleWithSystemGHC = T.unpack $ T.concat [ "stack can only control the " , configMonoidGHCVariantName , " of its own GHC installations. Please use '--no-" , configMonoidSystemGHCName , "'." ] show NixRequiresSystemGhc = T.unpack $ T.concat [ "stack's Nix integration is incompatible with '--no-system-ghc'. " , "Please use '--" , configMonoidSystemGHCName , "' or disable the Nix integration." ] show NoResolverWhenUsingNoLocalConfig = "When using the script command, you must provide a resolver argument" show (InvalidResolverForNoLocalConfig ar) = "The script command requires a specific resolver, you provided " ++ ar instance Exception ConfigException showOptions :: WhichSolverCmd -> SuggestSolver -> String showOptions whichCmd suggestSolver = unlines $ "\nThis may be resolved by:" : options where options = (case suggestSolver of SuggestSolver -> [useSolver] Don'tSuggestSolver -> []) ++ (case whichCmd of IsSolverCmd -> [useResolver] IsInitCmd -> both IsNewCmd -> both) both = [omitPackages, useResolver] useSolver = " - Using '--solver' to ask cabal-install to generate extra-deps, atop the chosen snapshot." omitPackages = " - Using '--omit-packages to exclude mismatching package(s)." useResolver = " - Using '--resolver' to specify a matching snapshot/resolver" data WhichSolverCmd = IsInitCmd | IsSolverCmd | IsNewCmd data SuggestSolver = SuggestSolver | Don'tSuggestSolver -- | Get the URL to request the information on the latest snapshots askLatestSnapshotUrl :: (MonadReader env m, HasConfig env) => m Text askLatestSnapshotUrl = view $ configL.to configUrls.to urlsLatestSnapshot -- | Root for a specific package index configPackageIndexRoot :: (MonadReader env m, HasConfig env, MonadThrow m) => IndexName -> m (Path Abs Dir) configPackageIndexRoot (IndexName name) = do root <- view stackRootL dir <- parseRelDir $ S8.unpack name return (root $(mkRelDir "indices") dir) -- | Location of the 01-index.cache file configPackageIndexCache :: (MonadReader env m, HasConfig env, MonadThrow m) => IndexName -> m (Path Abs File) configPackageIndexCache = liftM ( $(mkRelFile "01-index.cache")) . configPackageIndexRoot -- | Location of the 00-index.cache file configPackageIndexCacheOld :: (MonadReader env m, HasConfig env, MonadThrow m) => IndexName -> m (Path Abs File) configPackageIndexCacheOld = liftM ( $(mkRelFile "00-index.cache")) . configPackageIndexRoot -- | Location of the 01-index.tar file configPackageIndex :: (MonadReader env m, HasConfig env, MonadThrow m) => IndexName -> m (Path Abs File) configPackageIndex = liftM ( $(mkRelFile "01-index.tar")) . configPackageIndexRoot -- | Location of the 00-index.tar file. This file is just a copy of -- the 01-index.tar file, provided for tools which still look for the -- 00-index.tar file. configPackageIndexOld :: (MonadReader env m, HasConfig env, MonadThrow m) => IndexName -> m (Path Abs File) configPackageIndexOld = liftM ( $(mkRelFile "00-index.tar")) . configPackageIndexRoot -- | Location of the 01-index.tar.gz file configPackageIndexGz :: (MonadReader env m, HasConfig env, MonadThrow m) => IndexName -> m (Path Abs File) configPackageIndexGz = liftM ( $(mkRelFile "01-index.tar.gz")) . configPackageIndexRoot -- | Location of a package tarball configPackageTarball :: (MonadReader env m, HasConfig env, MonadThrow m) => IndexName -> PackageIdentifier -> m (Path Abs File) configPackageTarball iname ident = do root <- configPackageIndexRoot iname name <- parseRelDir $ packageNameString $ packageIdentifierName ident ver <- parseRelDir $ versionString $ packageIdentifierVersion ident base <- parseRelFile $ packageIdentifierString ident ++ ".tar.gz" return (root $(mkRelDir "packages") name ver base) -- | @".stack-work"@ workDirL :: HasConfig env => Lens' env (Path Rel Dir) workDirL = configL.lens configWorkDir (\x y -> x { configWorkDir = y }) -- | Per-project work dir getProjectWorkDir :: (HasBuildConfig env, MonadReader env m) => m (Path Abs Dir) getProjectWorkDir = do root <- view projectRootL workDir <- view workDirL return (root workDir) -- | File containing the installed cache, see "Stack.PackageDump" configInstalledCache :: (HasBuildConfig env, MonadReader env m) => m (Path Abs File) configInstalledCache = liftM ( $(mkRelFile "installed-cache.bin")) getProjectWorkDir -- | Relative directory for the platform identifier platformOnlyRelDir :: (MonadReader env m, HasPlatform env, MonadThrow m) => m (Path Rel Dir) platformOnlyRelDir = do platform <- view platformL platformVariant <- view platformVariantL parseRelDir (Distribution.Text.display platform ++ platformVariantSuffix platformVariant) -- | Directory containing snapshots snapshotsDir :: (MonadReader env m, HasEnvConfig env, MonadThrow m) => m (Path Abs Dir) snapshotsDir = do root <- view stackRootL platform <- platformGhcRelDir return $ root $(mkRelDir "snapshots") platform -- | Installation root for dependencies installationRootDeps :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Path Abs Dir) installationRootDeps = do root <- view stackRootL -- TODO: also useShaPathOnWindows here, once #1173 is resolved. psc <- platformSnapAndCompilerRel return $ root $(mkRelDir "snapshots") psc -- | Installation root for locals installationRootLocal :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Path Abs Dir) installationRootLocal = do workDir <- getProjectWorkDir psc <- useShaPathOnWindows =<< platformSnapAndCompilerRel return $ workDir $(mkRelDir "install") psc -- | Hoogle directory. hoogleRoot :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Path Abs Dir) hoogleRoot = do workDir <- getProjectWorkDir psc <- useShaPathOnWindows =<< platformSnapAndCompilerRel return $ workDir $(mkRelDir "hoogle") psc -- | Get the hoogle database path. hoogleDatabasePath :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Path Abs File) hoogleDatabasePath = do dir <- hoogleRoot return (dir $(mkRelFile "database.hoo")) -- | Path for platform followed by snapshot name followed by compiler -- name. platformSnapAndCompilerRel :: (MonadReader env m, HasEnvConfig env, MonadThrow m) => m (Path Rel Dir) platformSnapAndCompilerRel = do resolver' <- view loadedResolverL platform <- platformGhcRelDir name <- parseRelDir $ T.unpack $ resolverDirName resolver' ghc <- compilerVersionDir useShaPathOnWindows (platform name ghc) -- | Relative directory for the platform and GHC identifier platformGhcRelDir :: (MonadReader env m, HasEnvConfig env, MonadThrow m) => m (Path Rel Dir) platformGhcRelDir = do ec <- view envConfigL verOnly <- platformGhcVerOnlyRelDirStr parseRelDir (mconcat [ verOnly , compilerBuildSuffix (envConfigCompilerBuild ec)]) -- | Relative directory for the platform and GHC identifier without GHC bindist build platformGhcVerOnlyRelDir :: (MonadReader env m, HasPlatform env, HasGHCVariant env, MonadThrow m) => m (Path Rel Dir) platformGhcVerOnlyRelDir = parseRelDir =<< platformGhcVerOnlyRelDirStr -- | Relative directory for the platform and GHC identifier without GHC bindist build -- (before parsing into a Path) platformGhcVerOnlyRelDirStr :: (MonadReader env m, HasPlatform env, HasGHCVariant env) => m FilePath platformGhcVerOnlyRelDirStr = do platform <- view platformL platformVariant <- view platformVariantL ghcVariant <- view ghcVariantL return $ mconcat [ Distribution.Text.display platform , platformVariantSuffix platformVariant , ghcVariantSuffix ghcVariant ] -- | This is an attempt to shorten stack paths on Windows to decrease our -- chances of hitting 260 symbol path limit. The idea is to calculate -- SHA1 hash of the path used on other architectures, encode with base -- 16 and take first 8 symbols of it. useShaPathOnWindows :: MonadThrow m => Path Rel Dir -> m (Path Rel Dir) useShaPathOnWindows = #ifdef mingw32_HOST_OS parseRelDir . S8.unpack . S8.take 8 . Mem.convertToBase Mem.Base16 . hashWith SHA1 . encodeUtf8 . T.pack . toFilePath #else return #endif compilerVersionDir :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Path Rel Dir) compilerVersionDir = do compilerVersion <- view actualCompilerVersionL parseRelDir $ case compilerVersion of GhcVersion version -> versionString version GhcjsVersion {} -> compilerVersionString compilerVersion -- | Package database for installing dependencies into packageDatabaseDeps :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Path Abs Dir) packageDatabaseDeps = do root <- installationRootDeps return $ root $(mkRelDir "pkgdb") -- | Package database for installing local packages into packageDatabaseLocal :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Path Abs Dir) packageDatabaseLocal = do root <- installationRootLocal return $ root $(mkRelDir "pkgdb") -- | Extra package databases packageDatabaseExtra :: (MonadReader env m, HasEnvConfig env) => m [Path Abs Dir] packageDatabaseExtra = view $ buildConfigL.to bcExtraPackageDBs -- | Directory for holding flag cache information flagCacheLocal :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Path Abs Dir) flagCacheLocal = do root <- installationRootLocal return $ root $(mkRelDir "flag-cache") -- | Where to store mini build plan caches configMiniBuildPlanCache :: (MonadThrow m, MonadReader env m, HasConfig env, HasGHCVariant env) => SnapName -> m (Path Abs File) configMiniBuildPlanCache name = do root <- view stackRootL platform <- platformGhcVerOnlyRelDir file <- parseRelFile $ T.unpack (renderSnapName name) ++ ".cache" -- Yes, cached plans differ based on platform return (root $(mkRelDir "build-plan-cache") platform file) -- | Suffix applied to an installation root to get the bin dir bindirSuffix :: Path Rel Dir bindirSuffix = $(mkRelDir "bin") -- | Suffix applied to an installation root to get the doc dir docDirSuffix :: Path Rel Dir docDirSuffix = $(mkRelDir "doc") -- | Where HPC reports and tix files get stored. hpcReportDir :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Path Abs Dir) hpcReportDir = do root <- installationRootLocal return $ root $(mkRelDir "hpc") -- | Get the extra bin directories (for the PATH). Puts more local first -- -- Bool indicates whether or not to include the locals extraBinDirs :: (MonadThrow m, MonadReader env m, HasEnvConfig env) => m (Bool -> [Path Abs Dir]) extraBinDirs = do deps <- installationRootDeps local <- installationRootLocal return $ \locals -> if locals then [local bindirSuffix, deps bindirSuffix] else [deps bindirSuffix] -- | Get the minimal environment override, useful for just calling external -- processes like git or ghc getMinimalEnvOverride :: (MonadReader env m, HasConfig env, MonadIO m) => m EnvOverride getMinimalEnvOverride = do config' <- view configL liftIO $ configEnvOverride config' minimalEnvSettings minimalEnvSettings :: EnvSettings minimalEnvSettings = EnvSettings { esIncludeLocals = False , esIncludeGhcPackagePath = False , esStackExe = False , esLocaleUtf8 = False } -- | Get the path for the given compiler ignoring any local binaries. -- -- https://github.com/commercialhaskell/stack/issues/1052 getCompilerPath :: (MonadIO m, MonadThrow m, MonadReader env m, HasConfig env) => WhichCompiler -> m (Path Abs File) getCompilerPath wc = do config' <- view configL eoWithoutLocals <- liftIO $ configEnvOverride config' minimalEnvSettings { esLocaleUtf8 = True } join (findExecutable eoWithoutLocals (compilerExeName wc)) data ProjectAndConfigMonoid = ProjectAndConfigMonoid !Project !ConfigMonoid parseProjectAndConfigMonoid :: Path Abs Dir -> Value -> Yaml.Parser (WithJSONWarnings ProjectAndConfigMonoid) parseProjectAndConfigMonoid rootDir = withObjectWarnings "ProjectAndConfigMonoid" $ \o -> do dirs <- jsonSubWarningsTT (o ..:? "packages") ..!= [packageEntryCurrDir] extraDeps' <- o ..:? "extra-deps" ..!= [] extraDeps <- case partitionEithers $ goDeps extraDeps' of ([], x) -> return $ Map.fromList x (errs, _) -> fail $ unlines errs flags <- o ..:? "flags" ..!= mempty resolver <- jsonSubWarnings (o ..: "resolver") compiler <- o ..:? "compiler" msg <- o ..:? "user-message" config <- parseConfigMonoidObject rootDir o extraPackageDBs <- o ..:? "extra-package-dbs" ..!= [] let project = Project { projectUserMsg = msg , projectPackages = dirs , projectExtraDeps = extraDeps , projectFlags = flags , projectResolver = resolver , projectCompiler = compiler , projectExtraPackageDBs = extraPackageDBs } return $ ProjectAndConfigMonoid project config where goDeps = map toSingle . Map.toList . Map.unionsWith Set.union . map toMap where toMap i = Map.singleton (packageIdentifierName i) (Set.singleton (packageIdentifierVersion i)) toSingle (k, s) = case Set.toList s of [x] -> Right (k, x) xs -> Left $ concat [ "Multiple versions for package " , packageNameString k , ": " , unwords $ map versionString xs ] -- | A PackageEntry for the current directory, used as a default packageEntryCurrDir :: PackageEntry packageEntryCurrDir = PackageEntry { peExtraDepMaybe = Nothing , peLocation = PLFilePath "." , peSubdirs = [] } -- | A software control system. data SCM = Git deriving (Show) instance FromJSON SCM where parseJSON v = do s <- parseJSON v case s of "git" -> return Git _ -> fail ("Unknown or unsupported SCM: " <> s) instance ToJSON SCM where toJSON Git = toJSON ("git" :: Text) -- | A variant of the platform, used to differentiate Docker builds from host data PlatformVariant = PlatformVariantNone | PlatformVariant String -- | Render a platform variant to a String suffix. platformVariantSuffix :: PlatformVariant -> String platformVariantSuffix PlatformVariantNone = "" platformVariantSuffix (PlatformVariant v) = "-" ++ v -- | Specialized bariant of GHC (e.g. libgmp4 or integer-simple) data GHCVariant = GHCStandard -- ^ Standard bindist | GHCIntegerSimple -- ^ Bindist that uses integer-simple | GHCCustom String -- ^ Other bindists deriving (Show) instance FromJSON GHCVariant where -- Strange structuring is to give consistent error messages parseJSON = withText "GHCVariant" (either (fail . show) return . parseGHCVariant . T.unpack) -- | Render a GHC variant to a String. ghcVariantName :: GHCVariant -> String ghcVariantName GHCStandard = "standard" ghcVariantName GHCIntegerSimple = "integersimple" ghcVariantName (GHCCustom name) = "custom-" ++ name -- | Render a GHC variant to a String suffix. ghcVariantSuffix :: GHCVariant -> String ghcVariantSuffix GHCStandard = "" ghcVariantSuffix v = "-" ++ ghcVariantName v -- | Parse GHC variant from a String. parseGHCVariant :: (MonadThrow m) => String -> m GHCVariant parseGHCVariant s = case stripPrefix "custom-" s of Just name -> return (GHCCustom name) Nothing | s == "" -> return GHCStandard | s == "standard" -> return GHCStandard | s == "integersimple" -> return GHCIntegerSimple | otherwise -> return (GHCCustom s) -- | Build of the compiler distribution (e.g. standard, gmp4, tinfo6) -- | Information for a file to download. data DownloadInfo = DownloadInfo { downloadInfoUrl :: Text -- ^ URL or absolute file path , downloadInfoContentLength :: Maybe Int , downloadInfoSha1 :: Maybe ByteString } deriving (Show) instance FromJSON (WithJSONWarnings DownloadInfo) where parseJSON = withObjectWarnings "DownloadInfo" parseDownloadInfoFromObject -- | Parse JSON in existing object for 'DownloadInfo' parseDownloadInfoFromObject :: Object -> WarningParser DownloadInfo parseDownloadInfoFromObject o = do url <- o ..: "url" contentLength <- o ..:? "content-length" sha1TextMay <- o ..:? "sha1" return DownloadInfo { downloadInfoUrl = url , downloadInfoContentLength = contentLength , downloadInfoSha1 = fmap encodeUtf8 sha1TextMay } data VersionedDownloadInfo = VersionedDownloadInfo { vdiVersion :: Version , vdiDownloadInfo :: DownloadInfo } deriving Show instance FromJSON (WithJSONWarnings VersionedDownloadInfo) where parseJSON = withObjectWarnings "VersionedDownloadInfo" $ \o -> do version <- o ..: "version" downloadInfo <- parseDownloadInfoFromObject o return VersionedDownloadInfo { vdiVersion = version , vdiDownloadInfo = downloadInfo } data GHCDownloadInfo = GHCDownloadInfo { gdiConfigureOpts :: [Text] , gdiConfigureEnv :: Map Text Text , gdiDownloadInfo :: DownloadInfo } deriving Show instance FromJSON (WithJSONWarnings GHCDownloadInfo) where parseJSON = withObjectWarnings "GHCDownloadInfo" $ \o -> do configureOpts <- o ..:? "configure-opts" ..!= mempty configureEnv <- o ..:? "configure-env" ..!= mempty downloadInfo <- parseDownloadInfoFromObject o return GHCDownloadInfo { gdiConfigureOpts = configureOpts , gdiConfigureEnv = configureEnv , gdiDownloadInfo = downloadInfo } data SetupInfo = SetupInfo { siSevenzExe :: Maybe DownloadInfo , siSevenzDll :: Maybe DownloadInfo , siMsys2 :: Map Text VersionedDownloadInfo , siGHCs :: Map Text (Map Version GHCDownloadInfo) , siGHCJSs :: Map Text (Map CompilerVersion DownloadInfo) , siStack :: Map Text (Map Version DownloadInfo) } deriving Show instance FromJSON (WithJSONWarnings SetupInfo) where parseJSON = withObjectWarnings "SetupInfo" $ \o -> do siSevenzExe <- jsonSubWarningsT (o ..:? "sevenzexe-info") siSevenzDll <- jsonSubWarningsT (o ..:? "sevenzdll-info") siMsys2 <- jsonSubWarningsT (o ..:? "msys2" ..!= mempty) siGHCs <- jsonSubWarningsTT (o ..:? "ghc" ..!= mempty) siGHCJSs <- jsonSubWarningsTT (o ..:? "ghcjs" ..!= mempty) siStack <- jsonSubWarningsTT (o ..:? "stack" ..!= mempty) return SetupInfo {..} -- | For @siGHCs@ and @siGHCJSs@ fields maps are deeply merged. -- For all fields the values from the last @SetupInfo@ win. instance Monoid SetupInfo where mempty = SetupInfo { siSevenzExe = Nothing , siSevenzDll = Nothing , siMsys2 = Map.empty , siGHCs = Map.empty , siGHCJSs = Map.empty , siStack = Map.empty } mappend l r = SetupInfo { siSevenzExe = siSevenzExe r <|> siSevenzExe l , siSevenzDll = siSevenzDll r <|> siSevenzDll l , siMsys2 = siMsys2 r <> siMsys2 l , siGHCs = Map.unionWith (<>) (siGHCs r) (siGHCs l) , siGHCJSs = Map.unionWith (<>) (siGHCJSs r) (siGHCJSs l) , siStack = Map.unionWith (<>) (siStack l) (siStack r) } -- | Remote or inline 'SetupInfo' data SetupInfoLocation = SetupInfoFileOrURL String | SetupInfoInline SetupInfo deriving (Show) instance FromJSON (WithJSONWarnings SetupInfoLocation) where parseJSON v = (noJSONWarnings <$> withText "SetupInfoFileOrURL" (pure . SetupInfoFileOrURL . T.unpack) v) <|> inline where inline = do WithJSONWarnings si w <- parseJSON v return $ WithJSONWarnings (SetupInfoInline si) w -- | How PVP bounds should be added to .cabal files data PvpBoundsType = PvpBoundsNone | PvpBoundsUpper | PvpBoundsLower | PvpBoundsBoth deriving (Show, Read, Eq, Typeable, Ord, Enum, Bounded) data PvpBounds = PvpBounds { pbType :: !PvpBoundsType , pbAsRevision :: !Bool } deriving (Show, Read, Eq, Typeable, Ord) pvpBoundsText :: PvpBoundsType -> Text pvpBoundsText PvpBoundsNone = "none" pvpBoundsText PvpBoundsUpper = "upper" pvpBoundsText PvpBoundsLower = "lower" pvpBoundsText PvpBoundsBoth = "both" parsePvpBounds :: Text -> Either String PvpBounds parsePvpBounds t = maybe err Right $ do (t', asRevision) <- case T.break (== '-') t of (x, "") -> Just (x, False) (x, "-revision") -> Just (x, True) _ -> Nothing x <- Map.lookup t' m Just PvpBounds { pbType = x , pbAsRevision = asRevision } where m = Map.fromList $ map (pvpBoundsText &&& id) [minBound..maxBound] err = Left $ "Invalid PVP bounds: " ++ T.unpack t instance ToJSON PvpBounds where toJSON (PvpBounds typ asRevision) = toJSON (pvpBoundsText typ <> (if asRevision then "-revision" else "")) instance FromJSON PvpBounds where parseJSON = withText "PvpBounds" (either fail return . parsePvpBounds) -- | Provide an explicit list of package dependencies when running a custom Setup.hs explicitSetupDeps :: (MonadReader env m, HasConfig env) => PackageName -> m Bool explicitSetupDeps name = do m <- view $ configL.to configExplicitSetupDeps return $ -- Yes there are far cleverer ways to write this. I honestly consider -- the explicit pattern matching much easier to parse at a glance. case Map.lookup (Just name) m of Just b -> b Nothing -> case Map.lookup Nothing m of Just b -> b Nothing -> False -- default value -- | Data passed into Docker container for the Docker entrypoint's use newtype DockerEntrypoint = DockerEntrypoint { deUser :: Maybe DockerUser -- ^ UID/GID/etc of host user, if we wish to perform UID/GID switch in container } deriving (Read,Show) -- | Docker host user info data DockerUser = DockerUser { duUid :: UserID -- ^ uid , duGid :: GroupID -- ^ gid , duGroups :: [GroupID] -- ^ Supplemantal groups , duUmask :: FileMode -- ^ File creation mask } } deriving (Read,Show) -- TODO: See section of -- https://github.com/commercialhaskell/stack/issues/1265 about -- rationalizing the config. It would also be nice to share more code. -- For now it's more convenient just to extend this type. However, it's -- unpleasant that it has overlap with both 'Project' and 'Config'. data CustomSnapshot = CustomSnapshot { csCompilerVersion :: !(Maybe CompilerVersion) , csPackages :: !(Set PackageIdentifier) , csDropPackages :: !(Set PackageName) , csFlags :: !PackageFlags , csGhcOptions :: !GhcOptions } instance FromJSON (WithJSONWarnings (CustomSnapshot, Maybe Resolver)) where parseJSON = withObjectWarnings "CustomSnapshot" $ \o -> (,) <$> (CustomSnapshot <$> o ..:? "compiler" <*> o ..:? "packages" ..!= mempty <*> o ..:? "drop-packages" ..!= mempty <*> o ..:? "flags" ..!= mempty <*> o ..:? configMonoidGhcOptionsName ..!= mempty) <*> jsonSubWarningsT (o ..:? "resolver") newtype GhcOptions = GhcOptions { unGhcOptions :: Map (Maybe PackageName) [Text] } deriving Show instance FromJSON GhcOptions where parseJSON val = do ghcOptions <- parseJSON val fmap (GhcOptions . Map.fromList) $ mapM handleGhcOptions $ Map.toList ghcOptions where handleGhcOptions :: Monad m => (Text, Text) -> m (Maybe PackageName, [Text]) handleGhcOptions (name', vals') = do name <- if name' == "*" then return Nothing else case parsePackageNameFromString $ T.unpack name' of Left e -> fail $ show e Right x -> return $ Just x case parseArgs Escaping vals' of Left e -> fail e Right vals -> return (name, map T.pack vals) instance Monoid GhcOptions where mempty = GhcOptions mempty -- FIXME: Should GhcOptions really monoid like this? Keeping it this -- way preserves the behavior of the ConfigMonoid. However, this -- means there isn't the ability to fully override snapshot -- ghc-options in the same way there is for flags. Do we want to -- change the semantics here? (particularly for extensible -- snapshots) mappend (GhcOptions l) (GhcOptions r) = GhcOptions (Map.unionWith (++) l r) ghcOptionsFor :: PackageName -> GhcOptions -> [Text] ghcOptionsFor name (GhcOptions mp) = M.findWithDefault [] Nothing mp ++ M.findWithDefault [] (Just name) mp newtype PackageFlags = PackageFlags { unPackageFlags :: Map PackageName (Map FlagName Bool) } deriving Show instance FromJSON PackageFlags where parseJSON val = PackageFlags <$> parseJSON val instance ToJSON PackageFlags where toJSON = toJSON . unPackageFlags instance Monoid PackageFlags where mempty = PackageFlags mempty mappend (PackageFlags l) (PackageFlags r) = PackageFlags (Map.unionWith Map.union l r) ----------------------------------- -- Lens classes ----------------------------------- -- | Class for environment values which have a Platform class HasPlatform env where platformL :: Lens' env Platform default platformL :: HasConfig env => Lens' env Platform platformL = configL.platformL {-# INLINE platformL #-} platformVariantL :: Lens' env PlatformVariant default platformVariantL :: HasConfig env => Lens' env PlatformVariant platformVariantL = configL.platformVariantL {-# INLINE platformVariantL #-} -- | Class for environment values which have a GHCVariant class HasGHCVariant env where ghcVariantL :: Lens' env GHCVariant default ghcVariantL :: HasBuildConfig env => Lens' env GHCVariant ghcVariantL = buildConfigL.ghcVariantL {-# INLINE ghcVariantL #-} -- | Class for environment values that can provide a 'Config'. class HasPlatform env => HasConfig env where configL :: Lens' env Config default configL :: HasBuildConfig env => Lens' env Config configL = buildConfigL.lens bcConfig (\x y -> x { bcConfig = y }) {-# INLINE configL #-} class HasConfig env => HasBuildConfig env where buildConfigL :: Lens' env BuildConfig default buildConfigL :: HasEnvConfig env => Lens' env BuildConfig buildConfigL = envConfigL.lens envConfigBuildConfig (\x y -> x { envConfigBuildConfig = y }) class (HasBuildConfig env, HasGHCVariant env) => HasEnvConfig env where envConfigL :: Lens' env EnvConfig ----------------------------------- -- Lens instances ----------------------------------- instance HasPlatform (Platform,PlatformVariant) where platformL = _1 platformVariantL = _2 instance HasPlatform Config where platformL = lens configPlatform (\x y -> x { configPlatform = y }) platformVariantL = lens configPlatformVariant (\x y -> x { configPlatformVariant = y }) instance HasPlatform BuildConfig instance HasPlatform EnvConfig instance HasGHCVariant GHCVariant where ghcVariantL = id {-# INLINE ghcVariantL #-} instance HasGHCVariant BuildConfig where ghcVariantL = lens bcGHCVariant (\x y -> x { bcGHCVariant = y }) instance HasGHCVariant EnvConfig instance HasConfig Config where configL = id {-# INLINE configL #-} instance HasConfig BuildConfig where configL = lens bcConfig (\x y -> x { bcConfig = y }) instance HasConfig EnvConfig instance HasBuildConfig BuildConfig where buildConfigL = id {-# INLINE buildConfigL #-} instance HasBuildConfig EnvConfig instance HasEnvConfig EnvConfig where envConfigL = id {-# INLINE envConfigL #-} ----------------------------------- -- Helper lenses ----------------------------------- stackRootL :: HasConfig s => Lens' s (Path Abs Dir) stackRootL = configL.lens configStackRoot (\x y -> x { configStackRoot = y }) -- | The compiler specified by the @MiniBuildPlan@. This may be -- different from the actual compiler used! wantedCompilerVersionL :: HasBuildConfig s => Lens' s CompilerVersion wantedCompilerVersionL = miniBuildPlanL.lens mbpCompilerVersion (\x y -> x { mbpCompilerVersion = y }) -- | The version of the compiler which will actually be used. May be -- different than that specified in the 'MiniBuildPlan' and returned -- by 'wantedCompilerVersionL'. actualCompilerVersionL :: HasEnvConfig s => Lens' s CompilerVersion actualCompilerVersionL = envConfigL.lens envConfigCompilerVersion (\x y -> x { envConfigCompilerVersion = y }) loadedResolverL :: HasBuildConfig s => Lens' s LoadedResolver loadedResolverL = buildConfigL.lens bcResolver (\x y -> x { bcResolver = y }) miniBuildPlanL :: HasBuildConfig s => Lens' s MiniBuildPlan miniBuildPlanL = buildConfigL.lens bcWantedMiniBuildPlan (\x y -> x { bcWantedMiniBuildPlan = y }) packageIndicesL :: HasConfig s => Lens' s [PackageIndex] packageIndicesL = configL.lens configPackageIndices (\x y -> x { configPackageIndices = y }) buildOptsL :: HasConfig s => Lens' s BuildOpts buildOptsL = configL.lens configBuild (\x y -> x { configBuild = y }) buildOptsMonoidHaddockL :: Lens' BuildOptsMonoid (Maybe Bool) buildOptsMonoidHaddockL = lens (getFirst . buildMonoidHaddock) (\buildMonoid t -> buildMonoid {buildMonoidHaddock = First t}) buildOptsMonoidTestsL :: Lens' BuildOptsMonoid (Maybe Bool) buildOptsMonoidTestsL = lens (getFirst . buildMonoidTests) (\buildMonoid t -> buildMonoid {buildMonoidTests = First t}) buildOptsMonoidBenchmarksL :: Lens' BuildOptsMonoid (Maybe Bool) buildOptsMonoidBenchmarksL = lens (getFirst . buildMonoidBenchmarks) (\buildMonoid t -> buildMonoid {buildMonoidBenchmarks = First t}) buildOptsMonoidInstallExesL :: Lens' BuildOptsMonoid (Maybe Bool) buildOptsMonoidInstallExesL = lens (getFirst . buildMonoidInstallExes) (\buildMonoid t -> buildMonoid {buildMonoidInstallExes = First t}) buildOptsInstallExesL :: Lens' BuildOpts Bool buildOptsInstallExesL = lens boptsInstallExes (\bopts t -> bopts {boptsInstallExes = t}) buildOptsHaddockL :: Lens' BuildOpts Bool buildOptsHaddockL = lens boptsHaddock (\bopts t -> bopts {boptsHaddock = t}) globalOptsL :: Lens' GlobalOpts ConfigMonoid globalOptsL = lens globalConfigMonoid (\x y -> x { globalConfigMonoid = y }) globalOptsBuildOptsMonoidL :: Lens' GlobalOpts BuildOptsMonoid globalOptsBuildOptsMonoidL = globalOptsL.lens configMonoidBuildOpts (\x y -> x { configMonoidBuildOpts = y }) packageCachesL :: HasConfig env => Lens' env (IORef (Maybe (Map PackageIdentifier (PackageIndex, PackageCache) ,HashMap GitSHA1 (PackageIndex, OffsetSize)))) packageCachesL = configL.lens configPackageCaches (\x y -> x { configPackageCaches = y }) configUrlsL :: HasConfig env => Lens' env Urls configUrlsL = configL.lens configUrls (\x y -> x { configUrls = y }) cabalVersionL :: HasEnvConfig env => Lens' env Version cabalVersionL = envConfigL.lens envConfigCabalVersion (\x y -> x { envConfigCabalVersion = y }) whichCompilerL :: Getting r CompilerVersion WhichCompiler whichCompilerL = to whichCompiler stack-1.5.1/src/Stack/Types/Config/Build.hs0000644000000000000000000003410113135652051016551 0ustar0000000000000000{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} -- | Configuration options for building. module Stack.Types.Config.Build ( BuildOpts(..) , BuildCommand(..) , defaultBuildOpts , defaultBuildOptsCLI , BuildOptsCLI(..) , BuildOptsMonoid(..) , TestOpts(..) , defaultTestOpts , TestOptsMonoid(..) , HaddockOpts(..) , defaultHaddockOpts , HaddockOptsMonoid(..) , BenchmarkOpts(..) , defaultBenchmarkOpts , BenchmarkOptsMonoid(..) , FileWatchOpts(..) , BuildSubset(..) ) where import Control.Applicative import Data.Aeson.Extended import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Monoid import Data.Text (Text) import GHC.Generics (Generic) import Generics.Deriving.Monoid (memptydefault, mappenddefault) import Prelude -- Fix AMP warning import Stack.Types.FlagName import Stack.Types.PackageName -- | Build options that is interpreted by the build command. -- This is built up from BuildOptsCLI and BuildOptsMonoid data BuildOpts = BuildOpts {boptsLibProfile :: !Bool ,boptsExeProfile :: !Bool ,boptsLibStrip :: !Bool ,boptsExeStrip :: !Bool ,boptsHaddock :: !Bool -- ^ Build haddocks? ,boptsHaddockOpts :: !HaddockOpts -- ^ Options to pass to haddock ,boptsOpenHaddocks :: !Bool -- ^ Open haddocks in the browser? ,boptsHaddockDeps :: !(Maybe Bool) -- ^ Build haddocks for dependencies? ,boptsHaddockInternal :: !Bool -- ^ Build haddocks for all symbols and packages, like @cabal haddock --internal@ ,boptsHaddockHyperlinkSource :: !Bool -- ^ Build hyperlinked source if possible. Fallback to -- @hscolour@. Disable for no sources. ,boptsInstallExes :: !Bool -- ^ Install executables to user path after building? ,boptsPreFetch :: !Bool -- ^ Fetch all packages immediately -- ^ Watch files for changes and automatically rebuild ,boptsKeepGoing :: !(Maybe Bool) -- ^ Keep building/running after failure ,boptsForceDirty :: !Bool -- ^ Force treating all local packages as having dirty files ,boptsTests :: !Bool -- ^ Turn on tests for local targets ,boptsTestOpts :: !TestOpts -- ^ Additional test arguments ,boptsBenchmarks :: !Bool -- ^ Turn on benchmarks for local targets ,boptsBenchmarkOpts :: !BenchmarkOpts -- ^ Additional test arguments -- ^ Commands (with arguments) to run after a successful build -- ^ Only perform the configure step when building ,boptsReconfigure :: !Bool -- ^ Perform the configure step even if already configured ,boptsCabalVerbose :: !Bool -- ^ Ask Cabal to be verbose in its builds ,boptsSplitObjs :: !Bool -- ^ Whether to enable split-objs. } deriving (Show) defaultBuildOpts :: BuildOpts defaultBuildOpts = BuildOpts { boptsLibProfile = False , boptsExeProfile = False , boptsLibStrip = True , boptsExeStrip = True , boptsHaddock = False , boptsHaddockOpts = defaultHaddockOpts , boptsOpenHaddocks = False , boptsHaddockDeps = Nothing , boptsHaddockInternal = False , boptsHaddockHyperlinkSource = True , boptsInstallExes = False , boptsPreFetch = False , boptsKeepGoing = Nothing , boptsForceDirty = False , boptsTests = False , boptsTestOpts = defaultTestOpts , boptsBenchmarks = False , boptsBenchmarkOpts = defaultBenchmarkOpts , boptsReconfigure = False , boptsCabalVerbose = False , boptsSplitObjs = False } defaultBuildOptsCLI ::BuildOptsCLI defaultBuildOptsCLI = BuildOptsCLI { boptsCLITargets = [] , boptsCLIDryrun = False , boptsCLIFlags = Map.empty , boptsCLIGhcOptions = [] , boptsCLIBuildSubset = BSAll , boptsCLIFileWatch = NoFileWatch , boptsCLIExec = [] , boptsCLIOnlyConfigure = False , boptsCLICommand = Build , boptsCLIInitialBuildSteps = False } -- | Build options that may only be specified from the CLI data BuildOptsCLI = BuildOptsCLI { boptsCLITargets :: ![Text] , boptsCLIDryrun :: !Bool , boptsCLIGhcOptions :: ![Text] , boptsCLIFlags :: !(Map (Maybe PackageName) (Map FlagName Bool)) , boptsCLIBuildSubset :: !BuildSubset , boptsCLIFileWatch :: !FileWatchOpts , boptsCLIExec :: ![(String, [String])] , boptsCLIOnlyConfigure :: !Bool , boptsCLICommand :: !BuildCommand , boptsCLIInitialBuildSteps :: !Bool } deriving Show -- | Command sum type for conditional arguments. data BuildCommand = Build | Test | Haddock | Bench | Install deriving (Eq, Show) -- | Build options that may be specified in the stack.yaml or from the CLI data BuildOptsMonoid = BuildOptsMonoid { buildMonoidTrace :: !Any , buildMonoidProfile :: !Any , buildMonoidNoStrip :: !Any , buildMonoidLibProfile :: !(First Bool) , buildMonoidExeProfile :: !(First Bool) , buildMonoidLibStrip :: !(First Bool) , buildMonoidExeStrip :: !(First Bool) , buildMonoidHaddock :: !(First Bool) , buildMonoidHaddockOpts :: !HaddockOptsMonoid , buildMonoidOpenHaddocks :: !(First Bool) , buildMonoidHaddockDeps :: !(First Bool) , buildMonoidHaddockInternal :: !(First Bool) , buildMonoidHaddockHyperlinkSource :: !(First Bool) , buildMonoidInstallExes :: !(First Bool) , buildMonoidPreFetch :: !(First Bool) , buildMonoidKeepGoing :: !(First Bool) , buildMonoidForceDirty :: !(First Bool) , buildMonoidTests :: !(First Bool) , buildMonoidTestOpts :: !TestOptsMonoid , buildMonoidBenchmarks :: !(First Bool) , buildMonoidBenchmarkOpts :: !BenchmarkOptsMonoid , buildMonoidReconfigure :: !(First Bool) , buildMonoidCabalVerbose :: !(First Bool) , buildMonoidSplitObjs :: !(First Bool) } deriving (Show, Generic) instance FromJSON (WithJSONWarnings BuildOptsMonoid) where parseJSON = withObjectWarnings "BuildOptsMonoid" (\o -> do let buildMonoidTrace = Any False buildMonoidProfile = Any False buildMonoidNoStrip = Any False buildMonoidLibProfile <- First <$> o ..:? buildMonoidLibProfileArgName buildMonoidExeProfile <-First <$> o ..:? buildMonoidExeProfileArgName buildMonoidLibStrip <- First <$> o ..:? buildMonoidLibStripArgName buildMonoidExeStrip <-First <$> o ..:? buildMonoidExeStripArgName buildMonoidHaddock <- First <$> o ..:? buildMonoidHaddockArgName buildMonoidHaddockOpts <- jsonSubWarnings (o ..:? buildMonoidHaddockOptsArgName ..!= mempty) buildMonoidOpenHaddocks <- First <$> o ..:? buildMonoidOpenHaddocksArgName buildMonoidHaddockDeps <- First <$> o ..:? buildMonoidHaddockDepsArgName buildMonoidHaddockInternal <- First <$> o ..:? buildMonoidHaddockInternalArgName buildMonoidHaddockHyperlinkSource <- First <$> o ..:? buildMonoidHaddockHyperlinkSourceArgName buildMonoidInstallExes <- First <$> o ..:? buildMonoidInstallExesArgName buildMonoidPreFetch <- First <$> o ..:? buildMonoidPreFetchArgName buildMonoidKeepGoing <- First <$> o ..:? buildMonoidKeepGoingArgName buildMonoidForceDirty <- First <$> o ..:? buildMonoidForceDirtyArgName buildMonoidTests <- First <$> o ..:? buildMonoidTestsArgName buildMonoidTestOpts <- jsonSubWarnings (o ..:? buildMonoidTestOptsArgName ..!= mempty) buildMonoidBenchmarks <- First <$> o ..:? buildMonoidBenchmarksArgName buildMonoidBenchmarkOpts <- jsonSubWarnings (o ..:? buildMonoidBenchmarkOptsArgName ..!= mempty) buildMonoidReconfigure <- First <$> o ..:? buildMonoidReconfigureArgName buildMonoidCabalVerbose <- First <$> o ..:? buildMonoidCabalVerboseArgName buildMonoidSplitObjs <- First <$> o ..:? buildMonoidSplitObjsName return BuildOptsMonoid{..}) buildMonoidLibProfileArgName :: Text buildMonoidLibProfileArgName = "library-profiling" buildMonoidExeProfileArgName :: Text buildMonoidExeProfileArgName = "executable-profiling" buildMonoidLibStripArgName :: Text buildMonoidLibStripArgName = "library-stripping" buildMonoidExeStripArgName :: Text buildMonoidExeStripArgName = "executable-stripping" buildMonoidHaddockArgName :: Text buildMonoidHaddockArgName = "haddock" buildMonoidHaddockOptsArgName :: Text buildMonoidHaddockOptsArgName = "haddock-arguments" buildMonoidOpenHaddocksArgName :: Text buildMonoidOpenHaddocksArgName = "open-haddocks" buildMonoidHaddockDepsArgName :: Text buildMonoidHaddockDepsArgName = "haddock-deps" buildMonoidHaddockInternalArgName :: Text buildMonoidHaddockInternalArgName = "haddock-internal" buildMonoidHaddockHyperlinkSourceArgName :: Text buildMonoidHaddockHyperlinkSourceArgName = "haddock-hyperlink-source" buildMonoidInstallExesArgName :: Text buildMonoidInstallExesArgName = "copy-bins" buildMonoidPreFetchArgName :: Text buildMonoidPreFetchArgName = "prefetch" buildMonoidKeepGoingArgName :: Text buildMonoidKeepGoingArgName = "keep-going" buildMonoidForceDirtyArgName :: Text buildMonoidForceDirtyArgName = "force-dirty" buildMonoidTestsArgName :: Text buildMonoidTestsArgName = "test" buildMonoidTestOptsArgName :: Text buildMonoidTestOptsArgName = "test-arguments" buildMonoidBenchmarksArgName :: Text buildMonoidBenchmarksArgName = "bench" buildMonoidBenchmarkOptsArgName :: Text buildMonoidBenchmarkOptsArgName = "benchmark-opts" buildMonoidReconfigureArgName :: Text buildMonoidReconfigureArgName = "reconfigure" buildMonoidCabalVerboseArgName :: Text buildMonoidCabalVerboseArgName = "cabal-verbose" buildMonoidSplitObjsName :: Text buildMonoidSplitObjsName = "split-objs" instance Monoid BuildOptsMonoid where mempty = memptydefault mappend = mappenddefault -- | Which subset of packages to build data BuildSubset = BSAll | BSOnlySnapshot -- ^ Only install packages in the snapshot database, skipping -- packages intended for the local database. | BSOnlyDependencies deriving (Show, Eq) -- | Options for the 'FinalAction' 'DoTests' data TestOpts = TestOpts {toRerunTests :: !Bool -- ^ Whether successful tests will be run gain ,toAdditionalArgs :: ![String] -- ^ Arguments passed to the test program ,toCoverage :: !Bool -- ^ Generate a code coverage report ,toDisableRun :: !Bool -- ^ Disable running of tests } deriving (Eq,Show) defaultTestOpts :: TestOpts defaultTestOpts = TestOpts { toRerunTests = True , toAdditionalArgs = [] , toCoverage = False , toDisableRun = False } data TestOptsMonoid = TestOptsMonoid { toMonoidRerunTests :: !(First Bool) , toMonoidAdditionalArgs :: ![String] , toMonoidCoverage :: !(First Bool) , toMonoidDisableRun :: !(First Bool) } deriving (Show, Generic) instance FromJSON (WithJSONWarnings TestOptsMonoid) where parseJSON = withObjectWarnings "TestOptsMonoid" (\o -> do toMonoidRerunTests <- First <$> o ..:? toMonoidRerunTestsArgName toMonoidAdditionalArgs <- o ..:? toMonoidAdditionalArgsName ..!= [] toMonoidCoverage <- First <$> o ..:? toMonoidCoverageArgName toMonoidDisableRun <- First <$> o ..:? toMonoidDisableRunArgName return TestOptsMonoid{..}) toMonoidRerunTestsArgName :: Text toMonoidRerunTestsArgName = "rerun-tests" toMonoidAdditionalArgsName :: Text toMonoidAdditionalArgsName = "additional-args" toMonoidCoverageArgName :: Text toMonoidCoverageArgName = "coverage" toMonoidDisableRunArgName :: Text toMonoidDisableRunArgName = "no-run-tests" instance Monoid TestOptsMonoid where mempty = memptydefault mappend = mappenddefault -- | Haddock Options newtype HaddockOpts = HaddockOpts { hoAdditionalArgs :: [String] -- ^ Arguments passed to haddock program } deriving (Eq,Show) newtype HaddockOptsMonoid = HaddockOptsMonoid {hoMonoidAdditionalArgs :: [String] } deriving (Show, Generic) defaultHaddockOpts :: HaddockOpts defaultHaddockOpts = HaddockOpts {hoAdditionalArgs = []} instance FromJSON (WithJSONWarnings HaddockOptsMonoid) where parseJSON = withObjectWarnings "HaddockOptsMonoid" (\o -> do hoMonoidAdditionalArgs <- o ..:? hoMonoidAdditionalArgsName ..!= [] return HaddockOptsMonoid{..}) instance Monoid HaddockOptsMonoid where mempty = memptydefault mappend = mappenddefault hoMonoidAdditionalArgsName :: Text hoMonoidAdditionalArgsName = "haddock-args" -- | Options for the 'FinalAction' 'DoBenchmarks' data BenchmarkOpts = BenchmarkOpts { beoAdditionalArgs :: !(Maybe String) -- ^ Arguments passed to the benchmark program , beoDisableRun :: !Bool -- ^ Disable running of benchmarks } deriving (Eq,Show) defaultBenchmarkOpts :: BenchmarkOpts defaultBenchmarkOpts = BenchmarkOpts { beoAdditionalArgs = Nothing , beoDisableRun = False } data BenchmarkOptsMonoid = BenchmarkOptsMonoid { beoMonoidAdditionalArgs :: !(First String) , beoMonoidDisableRun :: !(First Bool) } deriving (Show, Generic) instance FromJSON (WithJSONWarnings BenchmarkOptsMonoid) where parseJSON = withObjectWarnings "BenchmarkOptsMonoid" (\o -> do beoMonoidAdditionalArgs <- First <$> o ..:? beoMonoidAdditionalArgsArgName beoMonoidDisableRun <- First <$> o ..:? beoMonoidDisableRunArgName return BenchmarkOptsMonoid{..}) beoMonoidAdditionalArgsArgName :: Text beoMonoidAdditionalArgsArgName = "benchmark-arguments" beoMonoidDisableRunArgName :: Text beoMonoidDisableRunArgName = "no-run-benchmarks" instance Monoid BenchmarkOptsMonoid where mempty = memptydefault mappend = mappenddefault data FileWatchOpts = NoFileWatch | FileWatch | FileWatchPoll deriving (Show,Eq) stack-1.5.1/src/Stack/Types/Docker.hs0000644000000000000000000004306513135652051015525 0ustar0000000000000000{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} -- | Docker types. module Stack.Types.Docker where import Control.Applicative import Control.Monad.Catch import Data.Aeson.Extended import Data.List (intercalate) import Data.Monoid import Data.Text (Text) import qualified Data.Text as T import Data.Typeable import Distribution.System (Platform(..), OS(..), Arch(..)) import Distribution.Text (simpleParse, display) import Distribution.Version (anyVersion) import GHC.Generics (Generic) import Generics.Deriving.Monoid (mappenddefault, memptydefault) import Path import {-# SOURCE #-} Stack.Constants import Stack.Types.Version -- | Docker configuration. data DockerOpts = DockerOpts {dockerEnable :: !Bool -- ^ Is using Docker enabled? ,dockerImage :: !String -- ^ Exact Docker image tag or ID. Overrides docker-repo-*/tag. ,dockerRegistryLogin :: !Bool -- ^ Does registry require login for pulls? ,dockerRegistryUsername :: !(Maybe String) -- ^ Optional username for Docker registry. ,dockerRegistryPassword :: !(Maybe String) -- ^ Optional password for Docker registry. ,dockerAutoPull :: !Bool -- ^ Automatically pull new images. ,dockerDetach :: !Bool -- ^ Whether to run a detached container ,dockerPersist :: !Bool -- ^ Create a persistent container (don't remove it when finished). Implied by -- `dockerDetach`. ,dockerContainerName :: !(Maybe String) -- ^ Container name to use, only makes sense from command-line with `dockerPersist` -- or `dockerDetach`. ,dockerRunArgs :: ![String] -- ^ Arguments to pass directly to @docker run@. ,dockerMount :: ![Mount] -- ^ Volumes to mount in the container. ,dockerEnv :: ![String] -- ^ Environment variables to set in the container. ,dockerDatabasePath :: !(Path Abs File) -- ^ Location of image usage database. ,dockerStackExe :: !(Maybe DockerStackExe) -- ^ Location of container-compatible stack executable ,dockerSetUser :: !(Maybe Bool) -- ^ Set in-container user to match host's ,dockerRequireDockerVersion :: !VersionRange -- ^ Require a version of Docker within this range. } deriving (Show) -- | An uninterpreted representation of docker options. -- Configurations may be "cascaded" using mappend (left-biased). data DockerOptsMonoid = DockerOptsMonoid {dockerMonoidDefaultEnable :: !Any -- ^ Should Docker be defaulted to enabled (does @docker:@ section exist in the config)? ,dockerMonoidEnable :: !(First Bool) -- ^ Is using Docker enabled? ,dockerMonoidRepoOrImage :: !(First DockerMonoidRepoOrImage) -- ^ Docker repository name (e.g. @fpco/stack-build@ or @fpco/stack-full:lts-2.8@) ,dockerMonoidRegistryLogin :: !(First Bool) -- ^ Does registry require login for pulls? ,dockerMonoidRegistryUsername :: !(First String) -- ^ Optional username for Docker registry. ,dockerMonoidRegistryPassword :: !(First String) -- ^ Optional password for Docker registry. ,dockerMonoidAutoPull :: !(First Bool) -- ^ Automatically pull new images. ,dockerMonoidDetach :: !(First Bool) -- ^ Whether to run a detached container ,dockerMonoidPersist :: !(First Bool) -- ^ Create a persistent container (don't remove it when finished). Implied by -- `dockerDetach`. ,dockerMonoidContainerName :: !(First String) -- ^ Container name to use, only makes sense from command-line with `dockerPersist` -- or `dockerDetach`. ,dockerMonoidRunArgs :: ![String] -- ^ Arguments to pass directly to @docker run@ ,dockerMonoidMount :: ![Mount] -- ^ Volumes to mount in the container ,dockerMonoidEnv :: ![String] -- ^ Environment variables to set in the container ,dockerMonoidDatabasePath :: !(First (Path Abs File)) -- ^ Location of image usage database. ,dockerMonoidStackExe :: !(First DockerStackExe) -- ^ Location of container-compatible stack executable ,dockerMonoidSetUser :: !(First Bool) -- ^ Set in-container user to match host's ,dockerMonoidRequireDockerVersion :: !IntersectingVersionRange -- ^ See: 'dockerRequireDockerVersion' } deriving (Show, Generic) -- | Decode uninterpreted docker options from JSON/YAML. instance FromJSON (WithJSONWarnings DockerOptsMonoid) where parseJSON = withObjectWarnings "DockerOptsMonoid" (\o -> do dockerMonoidDefaultEnable <- pure (Any True) dockerMonoidEnable <- First <$> o ..:? dockerEnableArgName dockerMonoidRepoOrImage <- First <$> (((Just . DockerMonoidImage) <$> o ..: dockerImageArgName) <|> ((Just . DockerMonoidRepo) <$> o ..: dockerRepoArgName) <|> pure Nothing) dockerMonoidRegistryLogin <- First <$> o ..:? dockerRegistryLoginArgName dockerMonoidRegistryUsername <- First <$> o ..:? dockerRegistryUsernameArgName dockerMonoidRegistryPassword <- First <$> o ..:? dockerRegistryPasswordArgName dockerMonoidAutoPull <- First <$> o ..:? dockerAutoPullArgName dockerMonoidDetach <- First <$> o ..:? dockerDetachArgName dockerMonoidPersist <- First <$> o ..:? dockerPersistArgName dockerMonoidContainerName <- First <$> o ..:? dockerContainerNameArgName dockerMonoidRunArgs <- o ..:? dockerRunArgsArgName ..!= [] dockerMonoidMount <- o ..:? dockerMountArgName ..!= [] dockerMonoidEnv <- o ..:? dockerEnvArgName ..!= [] dockerMonoidDatabasePath <- First <$> o ..:? dockerDatabasePathArgName dockerMonoidStackExe <- First <$> o ..:? dockerStackExeArgName dockerMonoidSetUser <- First <$> o ..:? dockerSetUserArgName dockerMonoidRequireDockerVersion <- IntersectingVersionRange . unVersionRangeJSON <$> ( o ..:? dockerRequireDockerVersionArgName ..!= VersionRangeJSON anyVersion) return DockerOptsMonoid{..}) -- | Left-biased combine Docker options instance Monoid DockerOptsMonoid where mempty = memptydefault mappend = mappenddefault -- | Where to get the `stack` executable to run in Docker containers data DockerStackExe = DockerStackExeDownload -- ^ Download from official bindist | DockerStackExeHost -- ^ Host's `stack` (linux-x86_64 only) | DockerStackExeImage -- ^ Docker image's `stack` (versions must match) | DockerStackExePath (Path Abs File) -- ^ Executable at given path deriving (Show) instance FromJSON DockerStackExe where parseJSON a = do s <- parseJSON a case parseDockerStackExe s of Right dse -> return dse Left e -> fail (show e) -- | Parse 'DockerStackExe'. parseDockerStackExe :: (MonadThrow m) => String -> m DockerStackExe parseDockerStackExe t | t == dockerStackExeDownloadVal = return DockerStackExeDownload | t == dockerStackExeHostVal = return DockerStackExeHost | t == dockerStackExeImageVal = return DockerStackExeImage | otherwise = case parseAbsFile t of Just p -> return (DockerStackExePath p) Nothing -> throwM (DockerStackExeParseException t) -- | Docker volume mount. data Mount = Mount String String -- | For optparse-applicative. instance Read Mount where readsPrec _ s = case break (== ':') s of (a,':':b) -> [(Mount a b,"")] (a,[]) -> [(Mount a a,"")] _ -> fail "Invalid value for Docker mount (expect '/host/path:/container/path')" -- | Show instance. instance Show Mount where show (Mount a b) = if a == b then a else concat [a,":",b] -- | For YAML. instance FromJSON Mount where parseJSON v = fmap read (parseJSON v) -- | Options for Docker repository or image. data DockerMonoidRepoOrImage = DockerMonoidRepo String | DockerMonoidImage String deriving (Show) -- | Newtype for non-orphan FromJSON instance. newtype VersionRangeJSON = VersionRangeJSON { unVersionRangeJSON :: VersionRange } -- | Parse VersionRange. instance FromJSON VersionRangeJSON where parseJSON = withText "VersionRange" (\s -> maybe (fail ("Invalid cabal-style VersionRange: " ++ T.unpack s)) (return . VersionRangeJSON) (Distribution.Text.simpleParse (T.unpack s))) -- | Exceptions thrown by Stack.Docker. data StackDockerException = DockerMustBeEnabledException -- ^ Docker must be enabled to use the command. | OnlyOnHostException -- ^ Command must be run on host OS (not in a container). | InspectFailedException String -- ^ @docker inspect@ failed. | NotPulledException String -- ^ Image does not exist. | InvalidCleanupCommandException String -- ^ Input to @docker cleanup@ has invalid command. | InvalidImagesOutputException String -- ^ Invalid output from @docker images@. | InvalidPSOutputException String -- ^ Invalid output from @docker ps@. | InvalidInspectOutputException String -- ^ Invalid output from @docker inspect@. | PullFailedException String -- ^ Could not pull a Docker image. | DockerTooOldException Version Version -- ^ Installed version of @docker@ below minimum version. | DockerVersionProhibitedException [Version] Version -- ^ Installed version of @docker@ is prohibited. | BadDockerVersionException VersionRange Version -- ^ Installed version of @docker@ is out of range specified in config file. | InvalidVersionOutputException -- ^ Invalid output from @docker --version@. | HostStackTooOldException Version (Maybe Version) -- ^ Version of @stack@ on host is too old for version in image. | ContainerStackTooOldException Version Version -- ^ Version of @stack@ in container/image is too old for version on host. | CannotDetermineProjectRootException -- ^ Can't determine the project root (where to put docker sandbox). | DockerNotInstalledException -- ^ @docker --version@ failed. | UnsupportedStackExeHostPlatformException -- ^ Using host stack-exe on unsupported platform. | DockerStackExeParseException String -- ^ @stack-exe@ option fails to parse. deriving (Typeable) instance Exception StackDockerException instance Show StackDockerException where show DockerMustBeEnabledException = "Docker must be enabled in your configuration file to use this command." show OnlyOnHostException = "This command must be run on host OS (not in a Docker container)." show (InspectFailedException image) = concat ["'docker inspect' failed for image after pull: ",image,"."] show (NotPulledException image) = concat ["The Docker image referenced by your configuration file" ," has not\nbeen downloaded:\n " ,image ,"\n\nRun '" ,unwords [stackProgName, dockerCmdName, dockerPullCmdName] ,"' to download it, then try again."] show (InvalidCleanupCommandException line) = concat ["Invalid line in cleanup commands: '",line,"'."] show (InvalidImagesOutputException line) = concat ["Invalid 'docker images' output line: '",line,"'."] show (InvalidPSOutputException line) = concat ["Invalid 'docker ps' output line: '",line,"'."] show (InvalidInspectOutputException msg) = concat ["Invalid 'docker inspect' output: ",msg,"."] show (PullFailedException image) = concat ["Could not pull Docker image:\n " ,image ,"\nThere may not be an image on the registry for your resolver's LTS version in\n" ,"your configuration file."] show (DockerTooOldException minVersion haveVersion) = concat ["Minimum docker version '" ,versionString minVersion ,"' is required by " ,stackProgName ," (you have '" ,versionString haveVersion ,"')."] show (DockerVersionProhibitedException prohibitedVersions haveVersion) = concat ["These Docker versions are incompatible with " ,stackProgName ," (you have '" ,versionString haveVersion ,"'): " ,intercalate ", " (map versionString prohibitedVersions) ,"."] show (BadDockerVersionException requiredRange haveVersion) = concat ["The version of 'docker' you are using (" ,show haveVersion ,") is outside the required\n" ,"version range specified in stack.yaml (" ,T.unpack (versionRangeText requiredRange) ,")."] show InvalidVersionOutputException = "Cannot get Docker version (invalid 'docker --version' output)." show (HostStackTooOldException minVersion (Just hostVersion)) = concat ["The host's version of '" ,stackProgName ,"' is too old for this Docker image.\nVersion " ,versionString minVersion ," is required; you have " ,versionString hostVersion ,"."] show (HostStackTooOldException minVersion Nothing) = concat ["The host's version of '" ,stackProgName ,"' is too old.\nVersion " ,versionString minVersion ," is required."] show (ContainerStackTooOldException requiredVersion containerVersion) = concat ["The Docker container's version of '" ,stackProgName ,"' is too old.\nVersion " ,versionString requiredVersion ," is required; the container has " ,versionString containerVersion ,"."] show CannotDetermineProjectRootException = "Cannot determine project root directory for Docker sandbox." show DockerNotInstalledException = "Cannot find 'docker' in PATH. Is Docker installed?" show UnsupportedStackExeHostPlatformException = concat [ "Using host's " , stackProgName , " executable in Docker container is only supported on " , display dockerContainerPlatform , " platform" ] show (DockerStackExeParseException s) = concat [ "Failed to parse " , show s , ". Expected " , show dockerStackExeDownloadVal , ", " , show dockerStackExeHostVal , ", " , show dockerStackExeImageVal , " or absolute path to executable." ] -- | Docker enable argument name. dockerEnableArgName :: Text dockerEnableArgName = "enable" -- | Docker repo arg argument name. dockerRepoArgName :: Text dockerRepoArgName = "repo" -- | Docker image argument name. dockerImageArgName :: Text dockerImageArgName = "image" -- | Docker registry login argument name. dockerRegistryLoginArgName :: Text dockerRegistryLoginArgName = "registry-login" -- | Docker registry username argument name. dockerRegistryUsernameArgName :: Text dockerRegistryUsernameArgName = "registry-username" -- | Docker registry password argument name. dockerRegistryPasswordArgName :: Text dockerRegistryPasswordArgName = "registry-password" -- | Docker auto-pull argument name. dockerAutoPullArgName :: Text dockerAutoPullArgName = "auto-pull" -- | Docker detach argument name. dockerDetachArgName :: Text dockerDetachArgName = "detach" -- | Docker run args argument name. dockerRunArgsArgName :: Text dockerRunArgsArgName = "run-args" -- | Docker mount argument name. dockerMountArgName :: Text dockerMountArgName = "mount" -- | Docker environment variable argument name. dockerEnvArgName :: Text dockerEnvArgName = "env" -- | Docker container name argument name. dockerContainerNameArgName :: Text dockerContainerNameArgName = "container-name" -- | Docker persist argument name. dockerPersistArgName :: Text dockerPersistArgName = "persist" -- | Docker database path argument name. dockerDatabasePathArgName :: Text dockerDatabasePathArgName = "database-path" -- | Docker database path argument name. dockerStackExeArgName :: Text dockerStackExeArgName = "stack-exe" -- | Value for @--docker-stack-exe=download@ dockerStackExeDownloadVal :: String dockerStackExeDownloadVal = "download" -- | Value for @--docker-stack-exe=host@ dockerStackExeHostVal :: String dockerStackExeHostVal = "host" -- | Value for @--docker-stack-exe=image@ dockerStackExeImageVal :: String dockerStackExeImageVal = "image" -- | Docker @set-user@ argument name dockerSetUserArgName :: Text dockerSetUserArgName = "set-user" -- | Docker @require-version@ argument name dockerRequireDockerVersionArgName :: Text dockerRequireDockerVersionArgName = "require-docker-version" -- | Argument name used to pass docker entrypoint data (only used internally) dockerEntrypointArgName :: String dockerEntrypointArgName = "internal-docker-entrypoint" -- | Command-line argument for "docker" dockerCmdName :: String dockerCmdName = "docker" dockerHelpOptName :: String dockerHelpOptName = dockerCmdName ++ "-help" -- | Command-line argument for @docker pull@. dockerPullCmdName :: String dockerPullCmdName = "pull" -- | Command-line argument for @docker cleanup@. dockerCleanupCmdName :: String dockerCleanupCmdName = "cleanup" -- | Command-line option for @--internal-re-exec-version@. reExecArgName :: String reExecArgName = "internal-re-exec-version" -- | Platform that Docker containers run dockerContainerPlatform :: Platform dockerContainerPlatform = Platform X86_64 Linux stack-1.5.1/src/Stack/Types/FlagName.hs0000644000000000000000000000760713135652051015772 0ustar0000000000000000{-# LANGUAGE BangPatterns #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE TupleSections #-} -- | Names for flags. module Stack.Types.FlagName (FlagName ,FlagNameParseFail(..) ,flagNameParser ,parseFlagName ,parseFlagNameFromString ,flagNameString ,flagNameText ,fromCabalFlagName ,toCabalFlagName ,mkFlagName) where import Control.Applicative import Control.DeepSeq (NFData) import Control.Monad.Catch import Data.Aeson.Extended import Data.Attoparsec.Combinators import Data.Attoparsec.Text import Data.Char (isLetter, isDigit, toLower) import Data.Data import Data.Hashable import Data.Store (Store) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Binary () import qualified Distribution.PackageDescription as Cabal import GHC.Generics import Language.Haskell.TH import Language.Haskell.TH.Syntax import Stack.Types.StringError -- | A parse fail. newtype FlagNameParseFail = FlagNameParseFail Text deriving (Typeable) instance Exception FlagNameParseFail instance Show FlagNameParseFail where show (FlagNameParseFail bs) = "Invalid flag name: " ++ show bs -- | A flag name. newtype FlagName = FlagName Text deriving (Typeable,Data,Generic,Hashable,Store,NFData,ToJSONKey) instance Eq FlagName where x == y = compare x y == EQ instance Ord FlagName where compare (FlagName x) (FlagName y) = compare (T.map toLower x) (T.map toLower y) instance Lift FlagName where lift (FlagName n) = appE (conE 'FlagName) (stringE (T.unpack n)) instance Show FlagName where show (FlagName n) = T.unpack n instance FromJSON FlagName where parseJSON j = do s <- parseJSON j case parseFlagNameFromString s of Nothing -> fail ("Couldn't parse flag name: " ++ s) Just ver -> return ver instance FromJSONKey FlagName where fromJSONKey = FromJSONKeyTextParser $ \k -> either (fail . show) return $ parseFlagName k -- | Attoparsec parser for a flag name flagNameParser :: Parser FlagName flagNameParser = fmap (FlagName . T.pack) (appending (many1 (satisfy isLetter)) (concating (many (alternating (pured (satisfy isAlphaNum)) (appending (pured (satisfy separator)) (pured (satisfy isAlphaNum))))))) where separator c = c == '-' || c == '_' isAlphaNum c = isLetter c || isDigit c -- | Make a flag name. mkFlagName :: String -> Q Exp mkFlagName s = case parseFlagNameFromString s of Nothing -> errorString ("Invalid flag name: " ++ show s) Just pn -> [|pn|] -- | Convenient way to parse a flag name from a 'Text'. parseFlagName :: MonadThrow m => Text -> m FlagName parseFlagName x = go x where go = either (const (throwM (FlagNameParseFail x))) return . parseOnly (flagNameParser <* endOfInput) -- | Convenience function for parsing from a 'String' parseFlagNameFromString :: MonadThrow m => String -> m FlagName parseFlagNameFromString = parseFlagName . T.pack -- | Produce a string representation of a flag name. flagNameString :: FlagName -> String flagNameString (FlagName n) = T.unpack n -- | Produce a string representation of a flag name. flagNameText :: FlagName -> Text flagNameText (FlagName n) = n -- | Convert from a Cabal flag name. fromCabalFlagName :: Cabal.FlagName -> FlagName fromCabalFlagName (Cabal.FlagName name) = let !x = T.pack name in FlagName x -- | Convert to a Cabal flag name. toCabalFlagName :: FlagName -> Cabal.FlagName toCabalFlagName (FlagName name) = let !x = T.unpack name in Cabal.FlagName x stack-1.5.1/src/Stack/Types/GhcPkgId.hs0000644000000000000000000000443313135652051015732 0ustar0000000000000000{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveDataTypeable #-} -- | A ghc-pkg id. module Stack.Types.GhcPkgId (GhcPkgId ,ghcPkgIdParser ,parseGhcPkgId ,ghcPkgIdString) where import Control.Applicative import Control.DeepSeq import Control.Monad.Catch import Data.Aeson.Extended import Data.Attoparsec.Text import Data.Binary (Binary(..), putWord8, getWord8) import Data.Binary.Tagged import Data.Data import Data.Hashable import Data.Store import Data.Text (Text) import qualified Data.Text as T import GHC.Generics import Prelude -- Fix AMP warning -- | A parse fail. newtype GhcPkgIdParseFail = GhcPkgIdParseFail Text deriving Typeable instance Show GhcPkgIdParseFail where show (GhcPkgIdParseFail bs) = "Invalid package ID: " ++ show bs instance Exception GhcPkgIdParseFail -- | A ghc-pkg package identifier. newtype GhcPkgId = GhcPkgId Text deriving (Eq,Ord,Data,Typeable,Generic) instance Hashable GhcPkgId instance Binary GhcPkgId where put (GhcPkgId x) = do -- magic string putWord8 1 putWord8 3 putWord8 4 putWord8 7 put x get = do 1 <- getWord8 3 <- getWord8 4 <- getWord8 7 <- getWord8 fmap GhcPkgId get instance NFData GhcPkgId instance HasStructuralInfo GhcPkgId instance Store GhcPkgId instance Show GhcPkgId where show = show . ghcPkgIdString instance FromJSON GhcPkgId where parseJSON = withText "GhcPkgId" $ \t -> case parseGhcPkgId t of Left e -> fail $ show (e, t) Right x -> return x instance ToJSON GhcPkgId where toJSON g = toJSON (ghcPkgIdString g) -- | Convenient way to parse a package name from a 'Text'. parseGhcPkgId :: MonadThrow m => Text -> m GhcPkgId parseGhcPkgId x = go x where go = either (const (throwM (GhcPkgIdParseFail x))) return . parseOnly (ghcPkgIdParser <* endOfInput) -- | A parser for a package-version-hash pair. ghcPkgIdParser :: Parser GhcPkgId ghcPkgIdParser = GhcPkgId . T.pack <$> many1 (choice [digit, letter, satisfy (`elem` "_.-")]) -- | Get a string representation of GHC package id. ghcPkgIdString :: GhcPkgId -> String ghcPkgIdString (GhcPkgId x) = T.unpack x stack-1.5.1/src/Stack/Types/Image.hs0000644000000000000000000000660713135652051015341 0ustar0000000000000000{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} module Stack.Types.Image where import Data.Aeson.Extended import Data.Monoid import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe (maybeToList) import Data.Text (Text) import GHC.Generics (Generic) import Generics.Deriving.Monoid (mappenddefault, memptydefault) import Path import Prelude -- Fix redundant import warnings -- | Image options. Currently only Docker image options. newtype ImageOpts = ImageOpts { imgDockers :: [ImageDockerOpts] -- ^ One or more stanzas for docker image settings. } deriving (Show) data ImageDockerOpts = ImageDockerOpts { imgDockerBase :: !(Maybe String) -- ^ Maybe have a docker base image name. (Although we will not -- be able to create any Docker images without this.) , imgDockerEntrypoints :: !(Maybe [String]) -- ^ Maybe have a specific ENTRYPOINT list that will be used to -- create images. , imgDockerAdd :: !(Map FilePath (Path Abs Dir)) -- ^ Maybe have some static project content to include in a -- specific directory in all the images. , imgDockerImageName :: !(Maybe String) -- ^ Maybe have a name for the image we are creating , imgDockerExecutables :: !(Maybe [Path Rel File]) -- ^ Filenames of executables to add (if Nothing, add them all) } deriving (Show) newtype ImageOptsMonoid = ImageOptsMonoid { imgMonoidDockers :: [ImageDockerOpts] } deriving (Show, Generic) instance FromJSON (WithJSONWarnings ImageOptsMonoid) where parseJSON = withObjectWarnings "ImageOptsMonoid" (\o -> do (oldDocker :: Maybe ImageDockerOpts) <- jsonSubWarningsT (o ..:? imgDockerOldArgName) (dockers :: [ImageDockerOpts]) <- jsonSubWarningsT (o ..:? imgDockersArgName ..!= []) let imgMonoidDockers = dockers ++ maybeToList oldDocker return ImageOptsMonoid { .. }) instance Monoid ImageOptsMonoid where mempty = memptydefault mappend = mappenddefault instance FromJSON (WithJSONWarnings ImageDockerOpts) where parseJSON = withObjectWarnings "ImageDockerOpts" (\o -> do imgDockerBase <- o ..:? imgDockerBaseArgName imgDockerEntrypoints <- o ..:? imgDockerEntrypointsArgName imgDockerAdd <- o ..:? imgDockerAddArgName ..!= Map.empty imgDockerImageName <- o ..:? imgDockerImageNameArgName imgDockerExecutables <- o ..:? imgDockerExecutablesArgName return ImageDockerOpts { .. }) imgArgName :: Text imgArgName = "image" -- Kept for backward compatibility imgDockerOldArgName :: Text imgDockerOldArgName = "container" imgDockersArgName :: Text imgDockersArgName = "containers" imgDockerBaseArgName :: Text imgDockerBaseArgName = "base" imgDockerAddArgName :: Text imgDockerAddArgName = "add" imgDockerEntrypointsArgName :: Text imgDockerEntrypointsArgName = "entrypoints" imgDockerImageNameArgName :: Text imgDockerImageNameArgName = "name" imgDockerExecutablesArgName :: Text imgDockerExecutablesArgName = "executables" stack-1.5.1/src/Stack/Types/Internal.hs0000644000000000000000000000412013135652051016057 0ustar0000000000000000{-# LANGUAGE Rank2Types #-} -- | Internal types to the library. module Stack.Types.Internal ( Env (..) , HasTerminal (..) , HasReExec (..) , Sticky (..) , HasSticky (..) , LogOptions (..) , HasLogOptions (..) , view ) where import Control.Concurrent.MVar import Control.Monad.Logger (LogLevel) import Data.Text (Text) import Lens.Micro import Stack.Types.Config -- | Monadic environment. data Env config = Env {envConfig :: !config ,envReExec :: !Bool ,envLogOptions :: !LogOptions ,envTerminal :: !Bool ,envSticky :: !Sticky } envConfL :: Lens (Env a) (Env b) a b envConfL = lens envConfig (\x y -> x { envConfig = y }) instance HasPlatform config => HasPlatform (Env config) where platformL = envConfL.platformL platformVariantL = envConfL.platformVariantL instance HasGHCVariant config => HasGHCVariant (Env config) where ghcVariantL = envConfL.ghcVariantL instance HasConfig config => HasConfig (Env config) where configL = envConfL.configL instance HasBuildConfig config => HasBuildConfig (Env config) where buildConfigL = envConfL.buildConfigL instance HasEnvConfig config => HasEnvConfig (Env config) where envConfigL = envConfL.envConfigL class HasTerminal env where terminalL :: Lens' env Bool instance HasTerminal (Env config) where terminalL = lens envTerminal (\x y -> x { envTerminal = y }) class HasReExec env where reExecL :: Lens' env Bool instance HasReExec (Env config) where reExecL = lens envReExec (\x y -> x { envReExec = y }) newtype Sticky = Sticky { unSticky :: Maybe (MVar (Maybe Text)) } class HasSticky env where stickyL :: Lens' env Sticky instance HasSticky (Env config) where stickyL = lens envSticky (\x y -> x { envSticky = y }) data LogOptions = LogOptions { logUseColor :: Bool , logUseUnicode :: Bool , logUseTime :: Bool , logMinLevel :: LogLevel , logVerboseFormat :: Bool } class HasLogOptions env where logOptionsL :: Lens' env LogOptions instance HasLogOptions (Env config) where logOptionsL = lens envLogOptions (\x y -> x { envLogOptions = y }) stack-1.5.1/src/Stack/Types/Nix.hs0000644000000000000000000000711613135652051015051 0ustar0000000000000000{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} -- | Nix types. module Stack.Types.Nix where import Control.Applicative import Data.Aeson.Extended import Data.Monoid import Data.Text (Text) import GHC.Generics (Generic) import Generics.Deriving.Monoid (mappenddefault, memptydefault) import Prelude -- | Nix configuration. Parameterize by resolver type to avoid cyclic -- dependency. data NixOpts = NixOpts {nixEnable :: !Bool ,nixPureShell :: !Bool ,nixPackages :: ![Text] -- ^ The system packages to be installed in the environment before it runs ,nixInitFile :: !(Maybe FilePath) -- ^ The path of a file containing preconfiguration of the environment (e.g shell.nix) ,nixShellOptions :: ![Text] -- ^ Options to be given to the nix-shell command line ,nixAddGCRoots :: !Bool -- ^ Should we register gc roots so running nix-collect-garbage doesn't remove nix dependencies } deriving (Show) -- | An uninterpreted representation of nix options. -- Configurations may be "cascaded" using mappend (left-biased). data NixOptsMonoid = NixOptsMonoid {nixMonoidDefaultEnable :: !Any -- ^ Should nix-shell be defaulted to enabled (does @nix:@ section exist in the config)? ,nixMonoidEnable :: !(First Bool) -- ^ Is using nix-shell enabled? ,nixMonoidPureShell :: !(First Bool) -- ^ Should the nix-shell be pure ,nixMonoidPackages :: !(First [Text]) -- ^ System packages to use (given to nix-shell) ,nixMonoidInitFile :: !(First FilePath) -- ^ The path of a file containing preconfiguration of the environment (e.g shell.nix) ,nixMonoidShellOptions :: !(First [Text]) -- ^ Options to be given to the nix-shell command line ,nixMonoidPath :: !(First [Text]) -- ^ Override parts of NIX_PATH (notably 'nixpkgs') ,nixMonoidAddGCRoots :: !(First Bool) -- ^ Should we register gc roots so running nix-collect-garbage doesn't remove nix dependencies } deriving (Eq, Show, Generic) -- | Decode uninterpreted nix options from JSON/YAML. instance FromJSON (WithJSONWarnings NixOptsMonoid) where parseJSON = withObjectWarnings "NixOptsMonoid" (\o -> do nixMonoidDefaultEnable <- pure (Any False) nixMonoidEnable <- First <$> o ..:? nixEnableArgName nixMonoidPureShell <- First <$> o ..:? nixPureShellArgName nixMonoidPackages <- First <$> o ..:? nixPackagesArgName nixMonoidInitFile <- First <$> o ..:? nixInitFileArgName nixMonoidShellOptions <- First <$> o ..:? nixShellOptsArgName nixMonoidPath <- First <$> o ..:? nixPathArgName nixMonoidAddGCRoots <- First <$> o ..:? nixAddGCRootsArgName return NixOptsMonoid{..}) -- | Left-biased combine Nix options instance Monoid NixOptsMonoid where mempty = memptydefault mappend = mappenddefault -- | Nix enable argument name. nixEnableArgName :: Text nixEnableArgName = "enable" -- | Nix run in pure shell argument name. nixPureShellArgName :: Text nixPureShellArgName = "pure" -- | Nix packages (build inputs) argument name. nixPackagesArgName :: Text nixPackagesArgName = "packages" -- | shell.nix file path argument name. nixInitFileArgName :: Text nixInitFileArgName = "shell-file" -- | Extra options for the nix-shell command argument name. nixShellOptsArgName :: Text nixShellOptsArgName = "nix-shell-options" -- | NIX_PATH override argument name nixPathArgName :: Text nixPathArgName = "path" -- | Add GC roots arg name nixAddGCRootsArgName :: Text nixAddGCRootsArgName = "add-gc-roots" stack-1.5.1/src/Stack/Types/Package.hs0000644000000000000000000003677713140560217015663 0ustar0000000000000000{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE ConstraintKinds #-} module Stack.Types.Package where import Control.DeepSeq import Control.Exception hiding (try,catch) import qualified Data.ByteString as S import Data.Data import Data.Function import Data.List import qualified Data.Map as M import Data.Map.Strict (Map) import Data.Maybe import Data.Monoid import Data.Set (Set) import qualified Data.Set as Set import Data.Store (Store) import Data.Store.Version (VersionConfig) import Data.Store.VersionTagged (storeVersionConfig) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8, decodeUtf8) import Data.Word (Word64) import Distribution.InstalledPackageInfo (PError) import Distribution.License (License) import Distribution.ModuleName (ModuleName) import Distribution.Package hiding (Package,PackageName,packageName,packageVersion,PackageIdentifier) import Distribution.PackageDescription (TestSuiteInterface, BuildType) import Distribution.System (Platform (..)) import GHC.Generics (Generic) import Path as FL import Prelude import Stack.Types.BuildPlan (GitSHA1) import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.FlagName import Stack.Types.GhcPkgId import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.StackT (StackM) import Stack.Types.Version -- | All exceptions thrown by the library. data PackageException = PackageInvalidCabalFile (Either (Path Abs File) PackageIdentifier) PError | PackageNoCabalFileFound (Path Abs Dir) | PackageMultipleCabalFilesFound (Path Abs Dir) [Path Abs File] | MismatchedCabalName (Path Abs File) PackageName deriving Typeable instance Exception PackageException instance Show PackageException where show (PackageInvalidCabalFile (Left file) err) = concat [ "Unable to parse cabal file " , toFilePath file , ": " , show err ] show (PackageInvalidCabalFile (Right ident) err) = concat [ "Unable to parse cabal file for " , packageIdentifierString ident , ": " , show err ] show (PackageNoCabalFileFound dir) = concat [ "Stack looks for packages in the directories configured in" , " the 'packages' variable defined in your stack.yaml\n" , "The current entry points to " ++ toFilePath dir ++ " but no .cabal file could be found there." ] show (PackageMultipleCabalFilesFound dir files) = "Multiple .cabal files found in directory " ++ toFilePath dir ++ ": " ++ intercalate ", " (map (toFilePath . filename) files) show (MismatchedCabalName fp name) = concat [ "cabal file path " , toFilePath fp , " does not match the package name it defines.\n" , "Please rename the file to: " , packageNameString name , ".cabal\n" , "For more information, see: https://github.com/commercialhaskell/stack/issues/317" ] -- | Some package info. data Package = Package {packageName :: !PackageName -- ^ Name of the package. ,packageVersion :: !Version -- ^ Version of the package ,packageLicense :: !License -- ^ The license the package was released under. ,packageFiles :: !GetPackageFiles -- ^ Get all files of the package. ,packageDeps :: !(Map PackageName VersionRange) -- ^ Packages that the package depends on. ,packageTools :: ![Dependency] -- ^ A build tool name. ,packageAllDeps :: !(Set PackageName) -- ^ Original dependencies (not sieved). ,packageGhcOptions :: ![Text] -- ^ Ghc options used on package. ,packageFlags :: !(Map FlagName Bool) -- ^ Flags used on package. ,packageDefaultFlags :: !(Map FlagName Bool) -- ^ Defaults for unspecified flags. ,packageHasLibrary :: !Bool -- ^ does the package have a buildable library stanza? ,packageTests :: !(Map Text TestSuiteInterface) -- ^ names and interfaces of test suites ,packageBenchmarks :: !(Set Text) -- ^ names of benchmarks ,packageExes :: !(Set Text) -- ^ names of executables ,packageOpts :: !GetPackageOpts -- ^ Args to pass to GHC. ,packageHasExposedModules :: !Bool -- ^ Does the package have exposed modules? ,packageBuildType :: !(Maybe BuildType) -- ^ Package build-type. ,packageSetupDeps :: !(Maybe (Map PackageName VersionRange)) -- ^ If present: custom-setup dependencies } deriving (Show,Typeable) packageIdentifier :: Package -> PackageIdentifier packageIdentifier pkg = PackageIdentifier (packageName pkg) (packageVersion pkg) packageDefinedFlags :: Package -> Set FlagName packageDefinedFlags = M.keysSet . packageDefaultFlags -- | Files that the package depends on, relative to package directory. -- Argument is the location of the .cabal file newtype GetPackageOpts = GetPackageOpts { getPackageOpts :: forall env m. (StackM env m, HasEnvConfig env) => SourceMap -> InstalledMap -> [PackageName] -> [PackageName] -> Path Abs File -> m (Map NamedComponent (Set ModuleName) ,Map NamedComponent (Set DotCabalPath) ,Map NamedComponent BuildInfoOpts) } instance Show GetPackageOpts where show _ = "" -- | GHC options based on cabal information and ghc-options. data BuildInfoOpts = BuildInfoOpts { bioOpts :: [String] , bioOneWordOpts :: [String] , bioPackageFlags :: [String] -- ^ These options can safely have 'nubOrd' applied to them, as -- there are no multi-word options (see -- https://github.com/commercialhaskell/stack/issues/1255) , bioCabalMacros :: Maybe (Path Abs File) } deriving Show -- | Files to get for a cabal package. data CabalFileType = AllFiles | Modules -- | Files that the package depends on, relative to package directory. -- Argument is the location of the .cabal file newtype GetPackageFiles = GetPackageFiles { getPackageFiles :: forall m env. (StackM env m, HasEnvConfig env) => Path Abs File -> m (Map NamedComponent (Set ModuleName) ,Map NamedComponent (Set DotCabalPath) ,Set (Path Abs File) ,[PackageWarning]) } instance Show GetPackageFiles where show _ = "" -- | Warning generated when reading a package data PackageWarning = UnlistedModulesWarning (Maybe String) [ModuleName] -- ^ Modules found that are not listed in cabal file -- TODO: bring this back - see -- https://github.com/commercialhaskell/stack/issues/2649 {- | MissingModulesWarning (Path Abs File) (Maybe String) [ModuleName] -- ^ Modules not found in file system, which are listed in cabal file -} -- | Package build configuration data PackageConfig = PackageConfig {packageConfigEnableTests :: !Bool -- ^ Are tests enabled? ,packageConfigEnableBenchmarks :: !Bool -- ^ Are benchmarks enabled? ,packageConfigFlags :: !(Map FlagName Bool) -- ^ Configured flags. ,packageConfigGhcOptions :: ![Text] -- ^ Configured ghc options. ,packageConfigCompilerVersion :: !CompilerVersion -- ^ GHC version ,packageConfigPlatform :: !Platform -- ^ host platform } deriving (Show,Typeable) -- | Compares the package name. instance Ord Package where compare = on compare packageName -- | Compares the package name. instance Eq Package where (==) = on (==) packageName type SourceMap = Map PackageName PackageSource -- | Where the package's source is located: local directory or package index data PackageSource = PSLocal LocalPackage | PSUpstream Version InstallLocation (Map FlagName Bool) [Text] (Maybe GitSHA1) -- ^ Upstream packages could be installed in either local or snapshot -- databases; this is what 'InstallLocation' specifies. deriving Show instance PackageInstallInfo PackageSource where piiVersion (PSLocal lp) = packageVersion $ lpPackage lp piiVersion (PSUpstream v _ _ _ _) = v piiLocation (PSLocal _) = Local piiLocation (PSUpstream _ loc _ _ _) = loc -- | Datatype which tells how which version of a package to install and where -- to install it into class PackageInstallInfo a where piiVersion :: a -> Version piiLocation :: a -> InstallLocation -- | Information on a locally available package of source code data LocalPackage = LocalPackage { lpPackage :: !Package -- ^ The @Package@ info itself, after resolution with package flags, -- with tests and benchmarks disabled , lpComponents :: !(Set NamedComponent) -- ^ Components to build, not including the library component. , lpUnbuildable :: !(Set NamedComponent) -- ^ Components explicitly requested for build, that are marked -- "buildable: false". , lpWanted :: !Bool -- ^ Whether this package is wanted as a target. , lpTestDeps :: !(Map PackageName VersionRange) -- ^ Used for determining if we can use --enable-tests in a normal build. , lpBenchDeps :: !(Map PackageName VersionRange) -- ^ Used for determining if we can use --enable-benchmarks in a normal -- build. , lpTestBench :: !(Maybe Package) -- ^ This stores the 'Package' with tests and benchmarks enabled, if -- either is asked for by the user. , lpDir :: !(Path Abs Dir) -- ^ Directory of the package. , lpCabalFile :: !(Path Abs File) -- ^ The .cabal file , lpForceDirty :: !Bool , lpDirtyFiles :: !(Maybe (Set FilePath)) -- ^ Nothing == not dirty, Just == dirty. Note that the Set may be empty if -- we forced the build to treat packages as dirty. Also, the Set may not -- include all modified files. , lpNewBuildCache :: !(Map FilePath FileCacheInfo) -- ^ current state of the files , lpFiles :: !(Set (Path Abs File)) -- ^ all files used by this package } deriving Show -- | A single, fully resolved component of a package data NamedComponent = CLib | CExe !Text | CTest !Text | CBench !Text deriving (Show, Eq, Ord) renderComponent :: NamedComponent -> S.ByteString renderComponent CLib = "lib" renderComponent (CExe x) = "exe:" <> encodeUtf8 x renderComponent (CTest x) = "test:" <> encodeUtf8 x renderComponent (CBench x) = "bench:" <> encodeUtf8 x renderPkgComponents :: [(PackageName, NamedComponent)] -> Text renderPkgComponents = T.intercalate " " . map renderPkgComponent renderPkgComponent :: (PackageName, NamedComponent) -> Text renderPkgComponent (pkg, comp) = packageNameText pkg <> ":" <> decodeUtf8 (renderComponent comp) exeComponents :: Set NamedComponent -> Set Text exeComponents = Set.fromList . mapMaybe mExeName . Set.toList where mExeName (CExe name) = Just name mExeName _ = Nothing testComponents :: Set NamedComponent -> Set Text testComponents = Set.fromList . mapMaybe mTestName . Set.toList where mTestName (CTest name) = Just name mTestName _ = Nothing benchComponents :: Set NamedComponent -> Set Text benchComponents = Set.fromList . mapMaybe mBenchName . Set.toList where mBenchName (CBench name) = Just name mBenchName _ = Nothing isCLib :: NamedComponent -> Bool isCLib CLib{} = True isCLib _ = False isCExe :: NamedComponent -> Bool isCExe CExe{} = True isCExe _ = False isCTest :: NamedComponent -> Bool isCTest CTest{} = True isCTest _ = False isCBench :: NamedComponent -> Bool isCBench CBench{} = True isCBench _ = False -- | A location to install a package into, either snapshot or local data InstallLocation = Snap | Local deriving (Show, Eq) instance Monoid InstallLocation where mempty = Snap mappend Local _ = Local mappend _ Local = Local mappend Snap Snap = Snap data InstalledPackageLocation = InstalledTo InstallLocation | ExtraGlobal deriving (Show, Eq) data FileCacheInfo = FileCacheInfo { fciModTime :: !ModTime , fciSize :: !Word64 , fciHash :: !S.ByteString } deriving (Generic, Show, Eq, Data, Typeable) instance Store FileCacheInfo instance NFData FileCacheInfo -- | Used for storage and comparison. newtype ModTime = ModTime (Integer,Rational) deriving (Ord, Show, Generic, Eq, NFData, Store, Data, Typeable) modTimeVC :: VersionConfig ModTime modTimeVC = storeVersionConfig "mod-time-v1" "UBECpUI0JvM_SBOnRNdaiF9_yOU=" testSuccessVC :: VersionConfig Bool testSuccessVC = storeVersionConfig "test-v1" "jC_GB0SGtbpRQbDlm7oQJP7thu8=" -- | A descriptor from a .cabal file indicating one of the following: -- -- exposed-modules: Foo -- other-modules: Foo -- or -- main-is: Foo.hs -- data DotCabalDescriptor = DotCabalModule !ModuleName | DotCabalMain !FilePath | DotCabalFile !FilePath | DotCabalCFile !FilePath deriving (Eq,Ord,Show) -- | Maybe get the module name from the .cabal descriptor. dotCabalModule :: DotCabalDescriptor -> Maybe ModuleName dotCabalModule (DotCabalModule m) = Just m dotCabalModule _ = Nothing -- | Maybe get the main name from the .cabal descriptor. dotCabalMain :: DotCabalDescriptor -> Maybe FilePath dotCabalMain (DotCabalMain m) = Just m dotCabalMain _ = Nothing -- | A path resolved from the .cabal file, which is either main-is or -- an exposed/internal/referenced module. data DotCabalPath = DotCabalModulePath !(Path Abs File) | DotCabalMainPath !(Path Abs File) | DotCabalFilePath !(Path Abs File) | DotCabalCFilePath !(Path Abs File) deriving (Eq,Ord,Show) -- | Get the module path. dotCabalModulePath :: DotCabalPath -> Maybe (Path Abs File) dotCabalModulePath (DotCabalModulePath fp) = Just fp dotCabalModulePath _ = Nothing -- | Get the main path. dotCabalMainPath :: DotCabalPath -> Maybe (Path Abs File) dotCabalMainPath (DotCabalMainPath fp) = Just fp dotCabalMainPath _ = Nothing -- | Get the c file path. dotCabalCFilePath :: DotCabalPath -> Maybe (Path Abs File) dotCabalCFilePath (DotCabalCFilePath fp) = Just fp dotCabalCFilePath _ = Nothing -- | Get the path. dotCabalGetPath :: DotCabalPath -> Path Abs File dotCabalGetPath dcp = case dcp of DotCabalModulePath fp -> fp DotCabalMainPath fp -> fp DotCabalFilePath fp -> fp DotCabalCFilePath fp -> fp type InstalledMap = Map PackageName (InstallLocation, Installed) data Installed = Library PackageIdentifier GhcPkgId | Executable PackageIdentifier deriving (Show, Eq, Ord) installedPackageIdentifier :: Installed -> PackageIdentifier installedPackageIdentifier (Library pid _) = pid installedPackageIdentifier (Executable pid) = pid -- | Get the installed Version. installedVersion :: Installed -> Version installedVersion = packageIdentifierVersion . installedPackageIdentifier stack-1.5.1/src/Stack/Types/PackageDump.hs0000644000000000000000000000242413135652051016471 0ustar0000000000000000{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} module Stack.Types.PackageDump ( InstalledCache(..) , InstalledCacheInner(..) , InstalledCacheEntry(..) , installedCacheVC ) where import Data.Data import Data.IORef import Data.Map (Map) import Data.Store import Data.Store.Version import Data.Store.VersionTagged import GHC.Generics (Generic) import Stack.Types.GhcPkgId import Stack.Types.PackageIdentifier -- | Cached information on whether package have profiling libraries and haddocks. newtype InstalledCache = InstalledCache (IORef InstalledCacheInner) newtype InstalledCacheInner = InstalledCacheInner (Map GhcPkgId InstalledCacheEntry) deriving (Store, Generic, Eq, Show, Data, Typeable) -- | Cached information on whether a package has profiling libraries and haddocks. data InstalledCacheEntry = InstalledCacheEntry { installedCacheProfiling :: !Bool , installedCacheHaddock :: !Bool , installedCacheSymbols :: !Bool , installedCacheIdent :: !PackageIdentifier } deriving (Eq, Generic, Show, Data, Typeable) instance Store InstalledCacheEntry installedCacheVC :: VersionConfig InstalledCacheInner installedCacheVC = storeVersionConfig "installed-v1" "GGyaE6qY9FOqeWtozuadKqS7_QM=" stack-1.5.1/src/Stack/Types/PackageIdentifier.hs0000644000000000000000000000740013135652051017645 0ustar0000000000000000{-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# OPTIONS -fno-warn-unused-do-bind #-} -- | Package identifier (name-version). module Stack.Types.PackageIdentifier ( PackageIdentifier(..) , toTuple , fromTuple , parsePackageIdentifier , parsePackageIdentifierFromString , packageIdentifierParser , packageIdentifierString , packageIdentifierText , toCabalPackageIdentifier ) where import Control.Applicative import Control.DeepSeq import Control.Exception (Exception) import Control.Monad.Catch (MonadThrow, throwM) import Data.Aeson.Extended import Data.Attoparsec.Text import Data.Data import Data.Hashable import Data.Store (Store) import Data.Text (Text) import qualified Data.Text as T import qualified Distribution.Package as C import GHC.Generics import Prelude hiding (FilePath) import Stack.Types.PackageName import Stack.Types.Version -- | A parse fail. newtype PackageIdentifierParseFail = PackageIdentifierParseFail Text deriving (Typeable) instance Show PackageIdentifierParseFail where show (PackageIdentifierParseFail bs) = "Invalid package identifier: " ++ show bs instance Exception PackageIdentifierParseFail -- | A pkg-ver combination. data PackageIdentifier = PackageIdentifier { -- | Get the name part of the identifier. packageIdentifierName :: !PackageName -- | Get the version part of the identifier. , packageIdentifierVersion :: !Version } deriving (Eq,Ord,Generic,Data,Typeable) instance NFData PackageIdentifier where rnf (PackageIdentifier !p !v) = seq (rnf p) (rnf v) instance Hashable PackageIdentifier instance Store PackageIdentifier instance Show PackageIdentifier where show = show . packageIdentifierString instance ToJSON PackageIdentifier where toJSON = toJSON . packageIdentifierString instance FromJSON PackageIdentifier where parseJSON = withText "PackageIdentifier" $ \t -> case parsePackageIdentifier t of Left e -> fail $ show (e, t) Right x -> return x -- | Convert from a package identifier to a tuple. toTuple :: PackageIdentifier -> (PackageName,Version) toTuple (PackageIdentifier n v) = (n,v) -- | Convert from a tuple to a package identifier. fromTuple :: (PackageName,Version) -> PackageIdentifier fromTuple (n,v) = PackageIdentifier n v -- | A parser for a package-version pair. packageIdentifierParser :: Parser PackageIdentifier packageIdentifierParser = do name <- packageNameParser char '-' version <- versionParser return (PackageIdentifier name version) -- | Convenient way to parse a package identifier from a 'Text'. parsePackageIdentifier :: MonadThrow m => Text -> m PackageIdentifier parsePackageIdentifier x = go x where go = either (const (throwM (PackageIdentifierParseFail x))) return . parseOnly (packageIdentifierParser <* endOfInput) -- | Convenience function for parsing from a 'String'. parsePackageIdentifierFromString :: MonadThrow m => String -> m PackageIdentifier parsePackageIdentifierFromString = parsePackageIdentifier . T.pack -- | Get a string representation of the package identifier; name-ver. packageIdentifierString :: PackageIdentifier -> String packageIdentifierString (PackageIdentifier n v) = show n ++ "-" ++ show v -- | Get a Text representation of the package identifier; name-ver. packageIdentifierText :: PackageIdentifier -> Text packageIdentifierText = T.pack . packageIdentifierString toCabalPackageIdentifier :: PackageIdentifier -> C.PackageIdentifier toCabalPackageIdentifier x = C.PackageIdentifier (toCabalPackageName (packageIdentifierName x)) (toCabalVersion (packageIdentifierVersion x)) stack-1.5.1/src/Stack/Types/PackageIndex.hs0000644000000000000000000001313013135652051016627 0ustar0000000000000000{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveDataTypeable #-} module Stack.Types.PackageIndex ( PackageDownload (..) , HSPackageDownload (..) , PackageCache (..) , PackageCacheMap (..) , OffsetSize (..) -- ** PackageIndex, IndexName & IndexLocation , PackageIndex(..) , IndexName(..) , indexNameText , IndexType (..) , HackageSecurity (..) ) where import Control.DeepSeq (NFData) import Control.Monad (mzero) import Data.Aeson.Extended import Data.ByteString (ByteString) import qualified Data.Foldable as F import Data.Hashable (Hashable) import Data.Data (Data, Typeable) import Data.HashMap.Strict (HashMap) import Data.Int (Int64) import Data.Map (Map) import qualified Data.Map.Strict as Map import Data.Store (Store) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8, decodeUtf8) import Data.Word (Word64) import GHC.Generics (Generic) import Path import Stack.Types.BuildPlan (GitSHA1) import Stack.Types.PackageIdentifier data PackageCache = PackageCache { pcOffsetSize :: {-# UNPACK #-}!OffsetSize , pcDownload :: !(Maybe PackageDownload) } deriving (Generic, Eq, Show, Data, Typeable) instance Store PackageCache instance NFData PackageCache -- | offset in bytes into the 01-index.tar file for the .cabal file -- contents, and size in bytes of the .cabal file data OffsetSize = OffsetSize !Int64 !Int64 deriving (Generic, Eq, Show, Data, Typeable) instance Store OffsetSize instance NFData OffsetSize data PackageCacheMap = PackageCacheMap { pcmIdent :: !(Map PackageIdentifier PackageCache) -- ^ most recent revision of the package , pcmSHA :: !(HashMap GitSHA1 OffsetSize) -- ^ lookup via the GitSHA1 of the cabal file contents } deriving (Generic, Eq, Show, Data, Typeable) instance Store PackageCacheMap instance NFData PackageCacheMap data PackageDownload = PackageDownload { pdSHA256 :: !ByteString , pdUrl :: !ByteString , pdSize :: !Word64 } deriving (Show, Generic, Eq, Data, Typeable) instance Store PackageDownload instance NFData PackageDownload instance FromJSON PackageDownload where parseJSON = withObject "PackageDownload" $ \o -> do hashes <- o .: "package-hashes" sha256 <- maybe mzero return (Map.lookup ("SHA256" :: Text) hashes) locs <- o .: "package-locations" url <- case reverse locs of [] -> mzero x:_ -> return x size <- o .: "package-size" return PackageDownload { pdSHA256 = encodeUtf8 sha256 , pdUrl = encodeUtf8 url , pdSize = size } -- | Hackage Security provides a different JSON format, we'll have our -- own JSON parser for it. newtype HSPackageDownload = HSPackageDownload { unHSPackageDownload :: PackageDownload } instance FromJSON HSPackageDownload where parseJSON = withObject "HSPackageDownload" $ \o1 -> do o2 <- o1 .: "signed" Object o3 <- o2 .: "targets" Object o4:_ <- return $ F.toList o3 len <- o4 .: "length" hashes <- o4 .: "hashes" sha256 <- hashes .: "sha256" return $ HSPackageDownload PackageDownload { pdSHA256 = encodeUtf8 sha256 , pdSize = len , pdUrl = "" } -- | Unique name for a package index newtype IndexName = IndexName { unIndexName :: ByteString } deriving (Show, Eq, Ord, Hashable, Store) indexNameText :: IndexName -> Text indexNameText = decodeUtf8 . unIndexName instance ToJSON IndexName where toJSON = toJSON . indexNameText instance FromJSON IndexName where parseJSON = withText "IndexName" $ \t -> case parseRelDir (T.unpack t) of Left e -> fail $ "Invalid index name: " ++ show e Right _ -> return $ IndexName $ encodeUtf8 t data IndexType = ITHackageSecurity !HackageSecurity | ITVanilla deriving (Show, Eq, Ord) data HackageSecurity = HackageSecurity { hsKeyIds :: ![Text] , hsKeyThreshold :: !Int } deriving (Show, Eq, Ord) instance FromJSON HackageSecurity where parseJSON = withObject "HackageSecurity" $ \o -> HackageSecurity <$> o .: "keyids" <*> o .: "key-threshold" -- | Information on a single package index data PackageIndex = PackageIndex { indexName :: !IndexName , indexLocation :: !Text -- ^ URL for the tarball or, in the case of Hackage Security, the -- root of the directory , indexType :: !IndexType , indexDownloadPrefix :: !Text -- ^ URL prefix for downloading packages , indexRequireHashes :: !Bool -- ^ Require that hashes and package size information be available for packages in this index } deriving Show instance FromJSON (WithJSONWarnings PackageIndex) where parseJSON = withObjectWarnings "PackageIndex" $ \o -> do name <- o ..: "name" prefix <- o ..: "download-prefix" http <- o ..: "http" mhackageSecurity <- o ..:? "hackage-security" let indexType' = maybe ITVanilla ITHackageSecurity mhackageSecurity reqHashes <- o ..:? "require-hashes" ..!= False return PackageIndex { indexName = name , indexLocation = http , indexType = indexType' , indexDownloadPrefix = prefix , indexRequireHashes = reqHashes } stack-1.5.1/src/Stack/Types/PackageName.hs0000644000000000000000000001260713135652051016450 0ustar0000000000000000{-# LANGUAGE BangPatterns #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE TupleSections #-} -- | Names for packages. module Stack.Types.PackageName (PackageName ,PackageNameParseFail(..) ,packageNameParser ,parsePackageName ,parsePackageNameFromString ,packageNameString ,packageNameText ,fromCabalPackageName ,toCabalPackageName ,parsePackageNameFromFilePath ,mkPackageName ,packageNameArgument) where import Control.Applicative import Control.DeepSeq import Control.Monad import Control.Monad.Catch import Data.Aeson.Extended import Data.Attoparsec.Combinators import Data.Attoparsec.Text import Data.Data import Data.Hashable import Data.List (intercalate) import Data.Store (Store) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Binary () import qualified Distribution.Package as Cabal import GHC.Generics import Language.Haskell.TH import Language.Haskell.TH.Syntax import qualified Options.Applicative as O import Path import Stack.Types.StringError -- | A parse fail. data PackageNameParseFail = PackageNameParseFail Text | CabalFileNameParseFail FilePath | CabalFileNameInvalidPackageName FilePath deriving (Typeable) instance Exception PackageNameParseFail instance Show PackageNameParseFail where show (PackageNameParseFail bs) = "Invalid package name: " ++ show bs show (CabalFileNameParseFail fp) = "Invalid file path for cabal file, must have a .cabal extension: " ++ fp show (CabalFileNameInvalidPackageName fp) = "cabal file names must use valid package names followed by a .cabal extension, the following is invalid: " ++ fp -- | A package name. newtype PackageName = PackageName Text deriving (Eq,Ord,Typeable,Data,Generic,Hashable,NFData,Store,ToJSON,ToJSONKey) instance Lift PackageName where lift (PackageName n) = appE (conE 'PackageName) (stringE (T.unpack n)) instance Show PackageName where show (PackageName n) = T.unpack n instance FromJSON PackageName where parseJSON j = do s <- parseJSON j case parsePackageNameFromString s of Nothing -> fail ("Couldn't parse package name: " ++ s) Just ver -> return ver instance FromJSONKey PackageName where fromJSONKey = FromJSONKeyTextParser $ \k -> either (fail . show) return $ parsePackageName k -- | Attoparsec parser for a package name packageNameParser :: Parser PackageName packageNameParser = fmap (PackageName . T.pack . intercalate "-") (sepBy1 word (char '-')) where word = concat <$> sequence [many digit, pured letter, many (alternating letter digit)] -- | Make a package name. mkPackageName :: String -> Q Exp mkPackageName s = case parsePackageNameFromString s of Nothing -> errorString ("Invalid package name: " ++ show s) Just pn -> [|pn|] -- | Parse a package name from a 'Text'. parsePackageName :: MonadThrow m => Text -> m PackageName parsePackageName x = go x where go = either (const (throwM (PackageNameParseFail x))) return . parseOnly (packageNameParser <* endOfInput) -- | Parse a package name from a 'String'. parsePackageNameFromString :: MonadThrow m => String -> m PackageName parsePackageNameFromString = parsePackageName . T.pack -- | Produce a string representation of a package name. packageNameString :: PackageName -> String packageNameString (PackageName n) = T.unpack n -- | Produce a string representation of a package name. packageNameText :: PackageName -> Text packageNameText (PackageName n) = n -- | Convert from a Cabal package name. fromCabalPackageName :: Cabal.PackageName -> PackageName fromCabalPackageName (Cabal.PackageName name) = let !x = T.pack name in PackageName x -- | Convert to a Cabal package name. toCabalPackageName :: PackageName -> Cabal.PackageName toCabalPackageName (PackageName name) = let !x = T.unpack name in Cabal.PackageName x -- | Parse a package name from a file path. parsePackageNameFromFilePath :: MonadThrow m => Path a File -> m PackageName parsePackageNameFromFilePath fp = do base <- clean $ toFilePath $ filename fp case parsePackageNameFromString base of Nothing -> throwM $ CabalFileNameInvalidPackageName $ toFilePath fp Just x -> return x where clean = liftM reverse . strip . reverse strip ('l':'a':'b':'a':'c':'.':xs) = return xs strip _ = throwM (CabalFileNameParseFail (toFilePath fp)) -- | An argument which accepts a template name of the format -- @foo.hsfiles@. packageNameArgument :: O.Mod O.ArgumentFields PackageName -> O.Parser PackageName packageNameArgument = O.argument (do s <- O.str either O.readerError return (p s)) where p s = case parsePackageNameFromString s of Just x -> Right x Nothing -> Left $ unlines [ "Expected valid package name, but got: " ++ s , "Package names consist of one or more alphanumeric words separated by hyphens." , "To avoid ambiguity with version numbers, each of these words must contain at least one letter." ] stack-1.5.1/src/Stack/Types/Resolver.hs0000644000000000000000000001404613135652051016114 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE StandaloneDeriving #-} module Stack.Types.Resolver (Resolver ,IsLoaded(..) ,LoadedResolver ,ResolverThat's(..) ,parseResolverText ,resolverDirName ,resolverName ,customResolverHash ,toResolverNotLoaded ,AbstractResolver(..) ,readAbstractResolver ) where import Control.Applicative import Control.Monad.Catch (MonadThrow, throwM) import Data.Aeson.Extended (ToJSON, toJSON, FromJSON, parseJSON, object, WithJSONWarnings(..), Value(String, Object), (.=), noJSONWarnings, (..:), withObjectWarnings) import Data.Monoid.Extra import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeUtf8) import Data.Text.Read (decimal) import Options.Applicative (ReadM) import qualified Options.Applicative as OA import qualified Options.Applicative.Types as OA import Prelude import Stack.Types.BuildPlan (parseSnapName, renderSnapName, SnapName, SnapshotHash, trimmedSnapshotHash) import {-# SOURCE #-} Stack.Types.Config (ConfigException(..)) import Stack.Types.Compiler data IsLoaded = Loaded | NotLoaded type LoadedResolver = ResolverThat's 'Loaded type Resolver = ResolverThat's 'NotLoaded -- TODO: once GHC 8.0 is the lowest version we support, make these into -- actual haddock comments... -- | How we resolve which dependencies to install given a set of packages. data ResolverThat's (l :: IsLoaded) where -- Use an official snapshot from the Stackage project, either an LTS -- Haskell or Stackage Nightly. ResolverSnapshot :: !SnapName -> ResolverThat's l -- Require a specific compiler version, but otherwise provide no -- build plan. Intended for use cases where end user wishes to -- specify all upstream dependencies manually, such as using a -- dependency solver. ResolverCompiler :: !CompilerVersion -> ResolverThat's l -- A custom resolver based on the given name and URL. When a URL is -- provided, its contents must be completely immutable. Filepaths are -- always loaded. This constructor is used before the build-plan has -- been loaded, as we do not yet know the custom snapshot's hash. ResolverCustom :: !Text -> !Text -> ResolverThat's 'NotLoaded -- Like 'ResolverCustom', but after loading the build-plan, so we -- have a hash. This is necessary in order to identify the location -- files are stored for the resolver. ResolverCustomLoaded :: !Text -> !Text -> !SnapshotHash -> ResolverThat's 'Loaded deriving instance Eq (ResolverThat's k) deriving instance Show (ResolverThat's k) instance ToJSON (ResolverThat's k) where toJSON x = case x of ResolverSnapshot{} -> toJSON $ resolverName x ResolverCompiler{} -> toJSON $ resolverName x ResolverCustom n l -> handleCustom n l ResolverCustomLoaded n l _ -> handleCustom n l where handleCustom n l = object [ "name" .= n , "location" .= l ] instance FromJSON (WithJSONWarnings (ResolverThat's 'NotLoaded)) where -- Strange structuring is to give consistent error messages parseJSON v@(Object _) = withObjectWarnings "Resolver" (\o -> ResolverCustom <$> o ..: "name" <*> o ..: "location") v parseJSON (String t) = either (fail . show) return (noJSONWarnings <$> parseResolverText t) parseJSON _ = fail "Invalid Resolver, must be Object or String" -- | Convert a Resolver into its @Text@ representation, as will be used by -- directory names resolverDirName :: LoadedResolver -> Text resolverDirName (ResolverSnapshot name) = renderSnapName name resolverDirName (ResolverCompiler v) = compilerVersionText v resolverDirName (ResolverCustomLoaded name _ hash) = "custom-" <> name <> "-" <> decodeUtf8 (trimmedSnapshotHash hash) -- | Convert a Resolver into its @Text@ representation for human -- presentation. resolverName :: ResolverThat's l -> Text resolverName (ResolverSnapshot name) = renderSnapName name resolverName (ResolverCompiler v) = compilerVersionText v resolverName (ResolverCustom name _) = "custom-" <> name resolverName (ResolverCustomLoaded name _ _) = "custom-" <> name customResolverHash :: LoadedResolver-> Maybe SnapshotHash customResolverHash (ResolverCustomLoaded _ _ hash) = Just hash customResolverHash _ = Nothing -- | Try to parse a @Resolver@ from a @Text@. Won't work for complex resolvers (like custom). parseResolverText :: MonadThrow m => Text -> m Resolver parseResolverText t | Right x <- parseSnapName t = return $ ResolverSnapshot x | Just v <- parseCompilerVersion t = return $ ResolverCompiler v | otherwise = throwM $ ParseResolverException t toResolverNotLoaded :: LoadedResolver -> Resolver toResolverNotLoaded r = case r of ResolverSnapshot s -> ResolverSnapshot s ResolverCompiler v -> ResolverCompiler v ResolverCustomLoaded n l _ -> ResolverCustom n l -- | Either an actual resolver value, or an abstract description of one (e.g., -- latest nightly). data AbstractResolver = ARLatestNightly | ARLatestLTS | ARLatestLTSMajor !Int | ARResolver !Resolver | ARGlobal deriving Show readAbstractResolver :: ReadM AbstractResolver readAbstractResolver = do s <- OA.readerAsk case s of "global" -> return ARGlobal "nightly" -> return ARLatestNightly "lts" -> return ARLatestLTS 'l':'t':'s':'-':x | Right (x', "") <- decimal $ T.pack x -> return $ ARLatestLTSMajor x' _ -> case parseResolverText $ T.pack s of Left e -> OA.readerError $ show e Right x -> return $ ARResolver x stack-1.5.1/src/Stack/Types/Sig.hs0000644000000000000000000000570513135652051015037 0ustar0000000000000000{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-| Module : Stack.Types.Sig Description : Signature Types Copyright : (c) FPComplete.com, 2015 License : BSD3 Maintainer : Tim Dysinger Stability : experimental Portability : POSIX -} module Stack.Types.Sig (Signature(..), Fingerprint, mkFingerprint, SigException(..)) where import Prelude () import Prelude.Compat import Control.Exception (Exception) import Data.Aeson (Value(..), ToJSON(..), FromJSON(..)) import Data.ByteString (ByteString) import qualified Data.ByteString as SB import Data.Char (isHexDigit) import Data.Monoid ((<>)) import Data.String (IsString(..)) import Data.Text (Text) import qualified Data.Text as T import Data.Typeable (Typeable) import Stack.Types.PackageName -- | A GPG signature. newtype Signature = Signature ByteString deriving (Ord,Eq) instance Show Signature where show (Signature s) = "Signature " ++ (if SB.length s > 140 then show (SB.take 140 s) ++ "..." else show (SB.take 140 s)) -- | The GPG fingerprint. newtype Fingerprint = Fingerprint Text deriving (Eq,Ord) mkFingerprint :: Text -> Fingerprint mkFingerprint = Fingerprint . hexText hexText :: Text -> Text hexText = T.toUpper . T.dropWhile (not . isHexDigit) instance Show Fingerprint where show (Fingerprint hex) = T.unpack (hexText hex) instance FromJSON Fingerprint where parseJSON j = Fingerprint . hexText <$> parseJSON j instance ToJSON Fingerprint where toJSON (Fingerprint hex) = String (hexText hex) instance IsString Fingerprint where fromString = Fingerprint . hexText . T.pack instance FromJSON (Aeson PackageName) where parseJSON j = do s <- parseJSON j case parsePackageName s of Just name -> return (Aeson name) Nothing -> fail ("Invalid package name: " <> T.unpack s) -- | Handy wrapper for orphan instances. newtype Aeson a = Aeson { _unAeson :: a } deriving (Ord,Eq) -- | Exceptions data SigException = GPGFingerprintException String | GPGNotFoundException | GPGSignException String | GPGVerifyException String | SigInvalidSDistTarBall | SigNoProjectRootException | SigServiceException String deriving (Typeable) instance Exception SigException instance Show SigException where show (GPGFingerprintException e) = "Error extracting a GPG fingerprint " <> e show GPGNotFoundException = "Unable to find gpg2 or gpg executable" show (GPGSignException e) = "Error signing with GPG " <> e show (GPGVerifyException e) = "Error verifying with GPG " <> e show SigNoProjectRootException = "Missing Project Root" show SigInvalidSDistTarBall = "Invalid sdist tarball" show (SigServiceException e) = "Error with the Signature Service " <> e stack-1.5.1/src/Stack/Types/StackT.hs0000644000000000000000000002770513135652051015512 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE ConstraintKinds #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- | The monad used for the command-line executable @stack@. module Stack.Types.StackT (StackT ,HasEnv ,StackM ,runStackT ,runStackTGlobal ,runInnerStackT ,logSticky ,logStickyDone) where import Control.Applicative import Control.Concurrent.MVar import Control.Monad import Control.Monad.Base import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.Reader hiding (lift) import Control.Monad.Trans.Control import qualified Data.ByteString.Char8 as S8 import Data.Char import Data.List (stripPrefix) import Data.Maybe import Data.Monoid import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.Encoding.Error as T import qualified Data.Text.IO as T import Data.Time import GHC.Foreign (withCString, peekCString) import Language.Haskell.TH import Language.Haskell.TH.Syntax (lift) import Prelude -- Fix AMP warning import Stack.Types.Config (GlobalOpts (..), ColorWhen(..)) import Stack.Types.Internal import System.Console.ANSI import System.FilePath import System.IO import System.Log.FastLogger #ifndef MIN_VERSION_time #define MIN_VERSION_time(x, y, z) 0 #endif #if !MIN_VERSION_time(1, 5, 0) import System.Locale #endif -- | Constraint synonym for all of the common environment instances type HasEnv r = (HasLogOptions r, HasTerminal r, HasReExec r, HasSticky r) -- | Constraint synonym for constraints commonly satisifed by monads used in stack. type StackM r m = (MonadReader r m, MonadIO m, MonadBaseControl IO m, MonadLoggerIO m, MonadMask m, HasEnv r) -------------------------------------------------------------------------------- -- Main StackT monad transformer -- | The monad used for the executable @stack@. newtype StackT config m a = StackT {unStackT :: ReaderT (Env config) m a} deriving (Functor,Applicative,Monad,MonadIO,MonadReader (Env config),MonadThrow,MonadCatch,MonadMask,MonadTrans) deriving instance (MonadBase b m) => MonadBase b (StackT config m) instance MonadBaseControl b m => MonadBaseControl b (StackT config m) where type StM (StackT config m) a = ComposeSt (StackT config) m a liftBaseWith = defaultLiftBaseWith restoreM = defaultRestoreM instance MonadTransControl (StackT config) where type StT (StackT config) a = StT (ReaderT (Env config)) a liftWith = defaultLiftWith StackT unStackT restoreT = defaultRestoreT StackT -- | Takes the configured log level into account. instance MonadIO m => MonadLogger (StackT config m) where monadLoggerLog = stickyLoggerFunc instance MonadIO m => MonadLoggerIO (StackT config m) where askLoggerIO = getStickyLoggerFunc -- | Run a Stack action, using global options. runStackTGlobal :: (MonadIO m) => config -> GlobalOpts -> StackT config m a -> m a runStackTGlobal config GlobalOpts{..} = runStackT config globalLogLevel globalTimeInLog globalTerminal globalColorWhen (isJust globalReExecVersion) runStackT :: (MonadIO m) => config -> LogLevel -> Bool -> Bool -> ColorWhen -> Bool -> StackT config m a -> m a runStackT config logLevel useTime terminal colorWhen reExec m = do useColor <- case colorWhen of ColorNever -> return False ColorAlways -> return True ColorAuto -> liftIO $ hSupportsANSI stderr canUseUnicode <- liftIO getCanUseUnicode withSticky terminal $ \sticky -> runReaderT (unStackT m) Env { envConfig = config , envReExec = reExec , envLogOptions = LogOptions { logUseColor = useColor , logUseUnicode = canUseUnicode , logUseTime = useTime , logMinLevel = logLevel , logVerboseFormat = logLevel <= LevelDebug } , envTerminal = terminal , envSticky = sticky } -- | Taken from GHC: determine if we should use Unicode syntax getCanUseUnicode :: IO Bool getCanUseUnicode = do let enc = localeEncoding str = "\x2018\x2019" test = withCString enc str $ \cstr -> do str' <- peekCString enc cstr return (str == str') test `catchIOError` \_ -> return False runInnerStackT :: (HasEnv r, MonadReader r m, MonadIO m) => config -> StackT config IO a -> m a runInnerStackT config inner = do reExec <- view reExecL logOptions <- view logOptionsL terminal <- view terminalL sticky <- view stickyL liftIO $ runReaderT (unStackT inner) Env { envConfig = config , envReExec = reExec , envLogOptions = logOptions , envTerminal = terminal , envSticky = sticky } -------------------------------------------------------------------------------- -- Logging functionality stickyLoggerFunc :: (HasEnv r, ToLogStr msg, MonadReader r m, MonadIO m) => Loc -> LogSource -> LogLevel -> msg -> m () stickyLoggerFunc loc src level msg = do func <- getStickyLoggerFunc liftIO $ func loc src level msg getStickyLoggerFunc :: (HasEnv r, ToLogStr msg, MonadReader r m) => m (Loc -> LogSource -> LogLevel -> msg -> IO ()) getStickyLoggerFunc = do sticky <- view stickyL lo <- view logOptionsL return $ stickyLoggerFuncImpl sticky lo stickyLoggerFuncImpl :: ToLogStr msg => Sticky -> LogOptions -> (Loc -> LogSource -> LogLevel -> msg -> IO ()) stickyLoggerFuncImpl (Sticky mref) lo loc src level msg = case mref of Nothing -> loggerFunc lo out loc src (case level of LevelOther "sticky-done" -> LevelInfo LevelOther "sticky" -> LevelInfo _ -> level) msg Just ref -> modifyMVar_ ref $ \sticky -> do let backSpaceChar = '\8' repeating = S8.replicate (maybe 0 T.length sticky) clear = S8.hPutStr out (repeating backSpaceChar <> repeating ' ' <> repeating backSpaceChar) -- Convert some GHC-generated Unicode characters as necessary let msgText | logUseUnicode lo = msgTextRaw | otherwise = T.map replaceUnicode msgTextRaw case level of LevelOther "sticky-done" -> do clear T.hPutStrLn out msgText hFlush out return Nothing LevelOther "sticky" -> do clear T.hPutStr out msgText hFlush out return (Just msgText) _ | level >= logMinLevel lo -> do clear loggerFunc lo out loc src level $ toLogStr msgText case sticky of Nothing -> return Nothing Just line -> do T.hPutStr out line >> hFlush out return sticky | otherwise -> return sticky where out = stderr msgTextRaw = T.decodeUtf8With T.lenientDecode msgBytes msgBytes = fromLogStr (toLogStr msg) -- | Replace Unicode characters with non-Unicode equivalents replaceUnicode :: Char -> Char replaceUnicode '\x2018' = '`' replaceUnicode '\x2019' = '\'' replaceUnicode c = c -- | Logging function takes the log level into account. loggerFunc :: ToLogStr msg => LogOptions -> Handle -> Loc -> Text -> LogLevel -> msg -> IO () loggerFunc lo outputChannel loc _src level msg = when (level >= logMinLevel lo) (liftIO (do out <- getOutput T.hPutStrLn outputChannel out)) where getOutput = do timestamp <- getTimestamp l <- getLevel lc <- getLoc return $ T.concat [ T.pack timestamp , T.pack l , T.pack (ansi [Reset]) , T.decodeUtf8 (fromLogStr (toLogStr msg)) , T.pack lc , T.pack (ansi [Reset]) ] where ansi xs | logUseColor lo = setSGRCode xs | otherwise = "" getTimestamp | logVerboseFormat lo && logUseTime lo = do now <- getZonedTime return $ ansi [SetColor Foreground Vivid Black] ++ formatTime' now ++ ": " | otherwise = return "" where formatTime' = take timestampLength . formatTime defaultTimeLocale "%F %T.%q" getLevel | logVerboseFormat lo = return ((case level of LevelDebug -> ansi [SetColor Foreground Dull Green] LevelInfo -> ansi [SetColor Foreground Dull Blue] LevelWarn -> ansi [SetColor Foreground Dull Yellow] LevelError -> ansi [SetColor Foreground Dull Red] LevelOther _ -> ansi [SetColor Foreground Dull Magenta]) ++ "[" ++ map toLower (drop 5 (show level)) ++ "] ") | otherwise = return "" getLoc | logVerboseFormat lo = return $ ansi [SetColor Foreground Vivid Black] ++ "\n@(" ++ fileLocStr ++ ")" | otherwise = return "" fileLocStr = fromMaybe file (stripPrefix dirRoot file) ++ ':' : line loc ++ ':' : char loc where file = loc_filename loc line = show . fst . loc_start char = show . snd . loc_start dirRoot = $(lift . T.unpack . fromJust . T.stripSuffix (T.pack $ "Stack" "Types" "StackT.hs") . T.pack . loc_filename =<< location) -- | The length of a timestamp in the format "YYYY-MM-DD hh:mm:ss.μμμμμμ". -- This definition is top-level in order to avoid multiple reevaluation at runtime. timestampLength :: Int timestampLength = length (formatTime defaultTimeLocale "%F %T.000000" (UTCTime (ModifiedJulianDay 0) 0)) -- | With a sticky state, do the thing. withSticky :: (MonadIO m) => Bool -> (Sticky -> m b) -> m b withSticky terminal m = if terminal then do state <- liftIO (newMVar Nothing) originalMode <- liftIO (hGetBuffering stdout) liftIO (hSetBuffering stdout NoBuffering) a <- m (Sticky (Just state)) state' <- liftIO (takeMVar state) liftIO (when (isJust state') (S8.putStr "\n")) liftIO (hSetBuffering stdout originalMode) return a else m (Sticky Nothing) -- | Write a "sticky" line to the terminal. Any subsequent lines will -- overwrite this one, and that same line will be repeated below -- again. In other words, the line sticks at the bottom of the output -- forever. Running this function again will replace the sticky line -- with a new sticky line. When you want to get rid of the sticky -- line, run 'logStickyDone'. -- logSticky :: Q Exp logSticky = logOther "sticky" -- | This will print out the given message with a newline and disable -- any further stickiness of the line until a new call to 'logSticky' -- happens. -- -- It might be better at some point to have a 'runSticky' function -- that encompasses the logSticky->logStickyDone pairing. logStickyDone :: Q Exp logStickyDone = logOther "sticky-done" stack-1.5.1/src/Stack/Types/StringError.hs0000644000000000000000000000073013135652051016566 0ustar0000000000000000{-# LANGUAGE MagicHash #-} module Stack.Types.StringError where import Control.Exception import Control.Monad.Catch import Data.Typeable import GHC.Prim newtype StringError = StringError String deriving (Typeable) instance Exception StringError instance Show StringError where show (StringError str) = str throwString :: MonadThrow m => String -> m a throwString = throwM . StringError errorString :: String -> a errorString = raise# . toException . StringError stack-1.5.1/src/Stack/Types/TemplateName.hs0000644000000000000000000001043413135652051016664 0ustar0000000000000000{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | Template name handling. module Stack.Types.TemplateName where import Control.Error.Safe (justErr) import Control.Applicative import Data.Aeson.Extended (FromJSON, withText, parseJSON) import Data.Aeson.Types (typeMismatch) import Data.Foldable (asum) import Data.Monoid import Data.Text (Text) import qualified Data.Text as T import Data.Yaml (Value(Object), (.:?)) import Language.Haskell.TH import Network.HTTP.Client (parseRequest) import qualified Options.Applicative as O import Path import Path.Internal import Prelude import Stack.Types.StringError -- | A template name. data TemplateName = TemplateName !Text !TemplatePath deriving (Ord,Eq,Show) data TemplatePath = AbsPath (Path Abs File) -- ^ an absolute path on the filesystem | RelPath (Path Rel File) -- ^ a relative path on the filesystem, or relative to -- the template repository | UrlPath String -- ^ a full URL deriving (Eq, Ord, Show) instance FromJSON TemplateName where parseJSON = withText "TemplateName" $ either fail return . parseTemplateNameFromString . T.unpack data TemplateInfo = TemplateInfo { author :: Maybe Text , description :: Maybe Text } deriving (Eq, Ord, Show) instance FromJSON TemplateInfo where parseJSON (Object v) = TemplateInfo <$> v .:? "author" <*> v .:? "description" parseJSON invalid = typeMismatch "Template Info" invalid -- | An argument which accepts a template name of the format -- @foo.hsfiles@ or @foo@, ultimately normalized to @foo@. templateNameArgument :: O.Mod O.ArgumentFields TemplateName -> O.Parser TemplateName templateNameArgument = O.argument (do string <- O.str either O.readerError return (parseTemplateNameFromString string)) -- | An argument which accepts a @key:value@ pair for specifying parameters. templateParamArgument :: O.Mod O.OptionFields (Text,Text) -> O.Parser (Text,Text) templateParamArgument = O.option (do string <- O.str either O.readerError return (parsePair string)) where parsePair :: String -> Either String (Text, Text) parsePair s = case break (==':') s of (key,':':value@(_:_)) -> Right (T.pack key, T.pack value) _ -> Left ("Expected key:value format for argument: " <> s) -- | Parse a template name from a string. parseTemplateNameFromString :: String -> Either String TemplateName parseTemplateNameFromString fname = case T.stripSuffix ".hsfiles" (T.pack fname) of Nothing -> parseValidFile (T.pack fname) (fname <> ".hsfiles") fname Just prefix -> parseValidFile prefix fname fname where parseValidFile prefix hsf orig = justErr expected $ asum (validParses prefix hsf orig) validParses prefix hsf orig = -- NOTE: order is important [ TemplateName (T.pack orig) . UrlPath <$> (parseRequest orig *> Just orig) , TemplateName prefix . AbsPath <$> parseAbsFile hsf , TemplateName prefix . RelPath <$> parseRelFile hsf ] expected = "Expected a template like: foo or foo.hsfiles or\ \ https://example.com/foo.hsfiles" -- | Make a template name. mkTemplateName :: String -> Q Exp mkTemplateName s = case parseTemplateNameFromString s of Left{} -> errorString ("Invalid template name: " ++ show s) Right (TemplateName (T.unpack -> prefix) p) -> [|TemplateName (T.pack prefix) $(pn)|] where pn = case p of AbsPath (Path fp) -> [|AbsPath (Path fp)|] RelPath (Path fp) -> [|RelPath (Path fp)|] UrlPath fp -> [|UrlPath fp|] -- | Get a text representation of the template name. templateName :: TemplateName -> Text templateName (TemplateName prefix _) = prefix -- | Get the path of the template. templatePath :: TemplateName -> TemplatePath templatePath (TemplateName _ fp) = fp stack-1.5.1/src/Stack/Types/Version.hs0000644000000000000000000001672413135652051015745 0ustar0000000000000000{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE DeriveDataTypeable #-} -- | Versions for packages. module Stack.Types.Version (Version ,Cabal.VersionRange -- TODO in the future should have a newtype wrapper ,IntersectingVersionRange(..) ,VersionCheck(..) ,versionParser ,parseVersion ,parseVersionFromString ,versionString ,versionText ,toCabalVersion ,fromCabalVersion ,mkVersion ,versionRangeText ,withinRange ,Stack.Types.Version.intersectVersionRanges ,toMajorVersion ,latestApplicableVersion ,checkVersion ,nextMajorVersion ,UpgradeTo(..)) where import Control.Applicative import Control.DeepSeq import Control.Monad.Catch import Data.Aeson.Extended import Data.Attoparsec.Text import Data.Data import Data.Hashable import Data.List import Data.Maybe (listToMaybe) import Data.Monoid import Data.Set (Set) import qualified Data.Set as Set import Data.Store (Store) import Data.Text (Text) import qualified Data.Text as T import Data.Vector.Unboxed (Vector) import qualified Data.Vector.Unboxed as V import Data.Word import Distribution.Text (disp) import qualified Distribution.Version as Cabal import GHC.Generics import Language.Haskell.TH import Language.Haskell.TH.Syntax import Prelude -- Fix warning: Word in Prelude from base-4.8. import Stack.Types.StringError import Text.PrettyPrint (render) -- | A parse fail. newtype VersionParseFail = VersionParseFail Text deriving (Typeable) instance Exception VersionParseFail instance Show VersionParseFail where show (VersionParseFail bs) = "Invalid version: " ++ show bs -- | A Package upgrade; Latest or a specific version. data UpgradeTo = Specific Version | Latest deriving (Show) -- | A package version. newtype Version = Version {unVersion :: Vector Word} deriving (Eq,Ord,Typeable,Data,Generic,Store,NFData) instance Hashable Version where hashWithSalt i = hashWithSalt i . V.toList . unVersion instance Lift Version where lift (Version n) = appE (conE 'Version) (appE (varE 'V.fromList) (listE (map (litE . IntegerL . fromIntegral) (V.toList n)))) instance Show Version where show (Version v) = intercalate "." (map show (V.toList v)) instance ToJSON Version where toJSON = toJSON . versionText instance FromJSON Version where parseJSON j = do s <- parseJSON j case parseVersionFromString s of Nothing -> fail ("Couldn't parse package version: " ++ s) Just ver -> return ver instance FromJSONKey Version where fromJSONKey = FromJSONKeyTextParser $ \k -> either (fail . show) return $ parseVersion k newtype IntersectingVersionRange = IntersectingVersionRange { getIntersectingVersionRange :: Cabal.VersionRange } deriving Show instance Monoid IntersectingVersionRange where mempty = IntersectingVersionRange Cabal.anyVersion mappend (IntersectingVersionRange l) (IntersectingVersionRange r) = IntersectingVersionRange (l `Cabal.intersectVersionRanges` r) -- | Attoparsec parser for a package version. versionParser :: Parser Version versionParser = do ls <- (:) <$> num <*> many num' let !v = V.fromList ls return (Version v) where num = decimal num' = point *> num point = satisfy (== '.') -- | Convenient way to parse a package version from a 'Text'. parseVersion :: MonadThrow m => Text -> m Version parseVersion x = go x where go = either (const (throwM (VersionParseFail x))) return . parseOnly (versionParser <* endOfInput) -- | Migration function. parseVersionFromString :: MonadThrow m => String -> m Version parseVersionFromString = parseVersion . T.pack -- | Get a string representation of a package version. versionString :: Version -> String versionString (Version v) = intercalate "." (map show (V.toList v)) -- | Get a string representation of a package version. versionText :: Version -> Text versionText (Version v) = T.intercalate "." (map (T.pack . show) (V.toList v)) -- | Convert to a Cabal version. toCabalVersion :: Version -> Cabal.Version toCabalVersion (Version v) = Cabal.Version (map fromIntegral (V.toList v)) [] -- | Convert from a Cabal version. fromCabalVersion :: Cabal.Version -> Version fromCabalVersion (Cabal.Version vs _) = let !v = V.fromList (map fromIntegral vs) in Version v -- | Make a package version. mkVersion :: String -> Q Exp mkVersion s = case parseVersionFromString s of Nothing -> errorString ("Invalid package version: " ++ show s) Just pn -> [|pn|] -- | Display a version range versionRangeText :: Cabal.VersionRange -> Text versionRangeText = T.pack . render . disp -- | Check if a version is within a version range. withinRange :: Version -> Cabal.VersionRange -> Bool withinRange v r = toCabalVersion v `Cabal.withinRange` r -- | A modified intersection which also simplifies, for better display. intersectVersionRanges :: Cabal.VersionRange -> Cabal.VersionRange -> Cabal.VersionRange intersectVersionRanges x y = Cabal.simplifyVersionRange $ Cabal.intersectVersionRanges x y -- | Returns the first two components, defaulting to 0 if not present toMajorVersion :: Version -> Version toMajorVersion (Version v) = case V.length v of 0 -> Version (V.fromList [0, 0]) 1 -> Version (V.fromList [V.head v, 0]) _ -> Version (V.fromList [V.head v, v V.! 1]) -- | Given a version range and a set of versions, find the latest version from -- the set that is within the range. latestApplicableVersion :: Cabal.VersionRange -> Set Version -> Maybe Version latestApplicableVersion r = listToMaybe . filter (`withinRange` r) . Set.toDescList -- | Get the next major version number for the given version nextMajorVersion :: Version -> Version nextMajorVersion (Version v) = case V.length v of 0 -> Version (V.fromList [0, 1]) 1 -> Version (V.fromList [V.head v, 1]) _ -> Version (V.fromList [V.head v, (v V.! 1) + 1]) data VersionCheck = MatchMinor | MatchExact | NewerMinor deriving (Show, Eq, Ord) instance ToJSON VersionCheck where toJSON MatchMinor = String "match-minor" toJSON MatchExact = String "match-exact" toJSON NewerMinor = String "newer-minor" instance FromJSON VersionCheck where parseJSON = withText expected $ \t -> case t of "match-minor" -> return MatchMinor "match-exact" -> return MatchExact "newer-minor" -> return NewerMinor _ -> fail ("Expected " ++ expected ++ ", but got " ++ show t) where expected = "VersionCheck value (match-minor, match-exact, or newer-minor)" checkVersion :: VersionCheck -> Version -> Version -> Bool checkVersion check (Version wanted) (Version actual) = case check of MatchMinor -> V.and (V.take 3 matching) MatchExact -> V.length wanted == V.length actual && V.and matching NewerMinor -> V.and (V.take 2 matching) && newerMinor where matching = V.zipWith (==) wanted actual newerMinor = case (wanted V.!? 2, actual V.!? 2) of (Nothing, _) -> True (Just _, Nothing) -> False (Just w, Just a) -> a >= w stack-1.5.1/src/Stack/Upgrade.hs0000644000000000000000000002404513135652051014576 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} module Stack.Upgrade ( upgrade , UpgradeOpts , upgradeOpts ) where import Control.Exception.Safe (catchAny) import Control.Monad (unless, when) import Control.Monad.IO.Class import Control.Monad.Logger import Data.Foldable (forM_) import qualified Data.Map as Map import Data.Maybe (isNothing) import Data.Monoid.Extra import qualified Data.Text as T import Lens.Micro (set) import Options.Applicative import Path import Path.IO import qualified Paths_stack as Paths import Stack.Build import Stack.Config import Stack.Fetch import Stack.PackageIndex import Stack.Setup import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.Version import Stack.Types.Config import Stack.Types.Resolver import Stack.Types.StackT import Stack.Types.StringError import System.Exit (ExitCode (ExitSuccess)) import System.Process (rawSystem, readProcess) import System.Process.Run upgradeOpts :: Parser UpgradeOpts upgradeOpts = UpgradeOpts <$> (sourceOnly <|> optional binaryOpts) <*> (binaryOnly <|> optional sourceOpts) where binaryOnly = flag' Nothing (long "binary-only" <> help "Do not use a source upgrade path") sourceOnly = flag' Nothing (long "source-only" <> help "Do not use a binary upgrade path") binaryOpts = BinaryOpts <$> optional (strOption ( long "binary-platform" <> help "Platform type for archive to download" <> showDefault)) <*> switch (long "force-download" <> help "Download a stack executable, even if the version number is older than what we have") <*> optional (strOption (long "binary-version" <> help "Download a specific version, even if it's out of date")) <*> optional (strOption (long "github-org" <> help "Github organization name")) <*> optional (strOption (long "github-repo" <> help "Github repository name")) sourceOpts = SourceOpts <$> ((\fromGit repo -> if fromGit then Just repo else Nothing) <$> switch ( long "git" <> help "Clone from Git instead of downloading from Hackage (more dangerous)" ) <*> strOption ( long "git-repo" <> help "Clone from specified git repository" <> value "https://github.com/commercialhaskell/stack" <> showDefault )) data BinaryOpts = BinaryOpts { _boPlatform :: !(Maybe String) , _boForce :: !Bool -- ^ force a download, even if the downloaded version is older -- than what we are , _boVersion :: !(Maybe String) -- ^ specific version to download , _boGithubOrg :: !(Maybe String) , _boGithubRepo :: !(Maybe String) } deriving Show newtype SourceOpts = SourceOpts { _soRepo :: Maybe String } deriving Show data UpgradeOpts = UpgradeOpts { _uoBinary :: !(Maybe BinaryOpts) , _uoSource :: !(Maybe SourceOpts) } deriving Show upgrade :: (StackM env m, HasConfig env) => ConfigMonoid -> Maybe AbstractResolver -> Maybe String -- ^ git hash at time of building, if known -> UpgradeOpts -> m () upgrade gConfigMonoid mresolver builtHash (UpgradeOpts mbo mso) = case (mbo, mso) of -- FIXME It would be far nicer to capture this case in the -- options parser itself so we get better error messages, but -- I can't think of a way to make it happen. (Nothing, Nothing) -> throwString "You must allow either binary or source upgrade paths" (Just bo, Nothing) -> binary bo (Nothing, Just so) -> source so -- See #2977 - if --git or --git-repo is specified, do source upgrade. (_, Just so@(SourceOpts (Just _))) -> source so (Just bo, Just so) -> binary bo `catchAny` \e -> do $logWarn "Exception occured when trying to perform binary upgrade:" $logWarn $ T.pack $ show e $logWarn "Falling back to source upgrade" source so where binary bo = binaryUpgrade bo source so = sourceUpgrade gConfigMonoid mresolver builtHash so binaryUpgrade :: (StackM env m, HasConfig env) => BinaryOpts -> m () binaryUpgrade (BinaryOpts mplatform force' mver morg mrepo) = do platforms0 <- case mplatform of Nothing -> preferredPlatforms Just p -> return [("windows" `T.isInfixOf` T.pack p, p)] archiveInfo <- downloadStackReleaseInfo morg mrepo mver let mdownloadVersion = getDownloadVersion archiveInfo force = case mver of Nothing -> force' Just _ -> True -- specifying a version implies we're forcing things isNewer <- case mdownloadVersion of Nothing -> do $logError "Unable to determine upstream version from Github metadata" unless force $ $logError "Rerun with --force-download to force an upgrade" return False Just downloadVersion -> do $logInfo $ T.concat [ "Current Stack version: " , versionText stackVersion , ", available download version: " , versionText downloadVersion ] return $ downloadVersion > stackVersion toUpgrade <- case (force, isNewer) of (False, False) -> do $logInfo "Skipping binary upgrade, you are already running the most recent version" return False (True, False) -> do $logInfo "Forcing binary upgrade" return True (_, True) -> do $logInfo "Newer version detected, downloading" return True when toUpgrade $ do config <- view configL downloadStackExe platforms0 archiveInfo (configLocalBin config) $ \tmpFile -> do -- Sanity check! ec <- rawSystem (toFilePath tmpFile) ["--version"] unless (ec == ExitSuccess) $ throwString "Non-success exit code from running newly downloaded executable" sourceUpgrade :: (StackM env m, HasConfig env) => ConfigMonoid -> Maybe AbstractResolver -> Maybe String -> SourceOpts -> m () sourceUpgrade gConfigMonoid mresolver builtHash (SourceOpts gitRepo) = withSystemTempDir "stack-upgrade" $ \tmp -> do menv <- getMinimalEnvOverride mdir <- case gitRepo of Just repo -> do remote <- liftIO $ readProcess "git" ["ls-remote", repo, "master"] [] let latestCommit = head . words $ remote when (isNothing builtHash) $ $logWarn $ "Information about the commit this version of stack was " <> "built from is not available due to how it was built. " <> "Will continue by assuming an upgrade is needed " <> "because we have no information to the contrary." if builtHash == Just latestCommit then do $logInfo "Already up-to-date, no upgrade required" return Nothing else do $logInfo "Cloning stack" -- NOTE: "--recursive" was added after v1.0.0 (and before the -- next release). This means that we can't use submodules in -- the stack repo until we're comfortable with "stack upgrade -- --git" not working for earlier versions. let args = [ "clone", repo , "stack", "--depth", "1", "--recursive"] runCmd (Cmd (Just tmp) "git" menv args) Nothing return $ Just $ tmp $(mkRelDir "stack") Nothing -> do updateAllIndices (caches, _gitShaCaches) <- getPackageCaches let latest = Map.fromListWith max $ map toTuple $ Map.keys -- Mistaken upload to Hackage, just ignore it $ Map.delete (PackageIdentifier $(mkPackageName "stack") $(mkVersion "9.9.9")) caches case Map.lookup $(mkPackageName "stack") latest of Nothing -> throwString "No stack found in package indices" Just version | version <= fromCabalVersion Paths.version -> do $logInfo "Already at latest version, no upgrade required" return Nothing Just version -> do let ident = PackageIdentifier $(mkPackageName "stack") version paths <- unpackPackageIdents tmp Nothing -- accept latest cabal revision by not supplying a Git SHA $ Map.singleton ident Nothing case Map.lookup ident paths of Nothing -> error "Stack.Upgrade.upgrade: invariant violated, unpacked directory not found" Just path -> return $ Just path forM_ mdir $ \dir -> do lc <- loadConfig gConfigMonoid mresolver (SYLOverride $ dir $(mkRelFile "stack.yaml")) bconfig <- lcLoadBuildConfig lc Nothing envConfig1 <- runInnerStackT bconfig $ setupEnv $ Just $ "Try rerunning with --install-ghc to install the correct GHC into " <> T.pack (toFilePath (configLocalPrograms (view configL bconfig))) runInnerStackT (set (buildOptsL.buildOptsInstallExesL) True envConfig1) $ build (const $ return ()) Nothing defaultBuildOptsCLI { boptsCLITargets = ["stack"] } stack-1.5.1/src/Stack/Upload.hs0000644000000000000000000002065713135652051014440 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -- | Provide ability to upload tarballs to Hackage. module Stack.Upload ( -- * Upload upload , uploadBytes , uploadRevision -- * Credentials , HackageCreds , loadCreds ) where import Control.Applicative import Control.Exception.Safe (handleIO, tryIO) import qualified Control.Exception as E import Control.Monad (void, when, unless) import Data.Aeson (FromJSON (..), ToJSON (..), decode', encode, object, withObject, (.:), (.=)) import qualified Data.ByteString.Char8 as S import qualified Data.ByteString.Lazy as L import Data.Conduit (ConduitM, runConduit, (.|)) import qualified Data.Conduit.Binary as CB import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import qualified Data.Text.IO as TIO import Network.HTTP.Client (Response, RequestBody(RequestBodyLBS), Request) import Network.HTTP.Simple (withResponse, getResponseStatusCode, getResponseBody, setRequestHeader, parseRequest, httpNoBody) import Network.HTTP.Client.MultipartFormData (formDataBody, partFileRequestBody, partBS, partLBS) import Network.HTTP.Client.TLS (getGlobalManager, applyDigestAuth, displayDigestAuthException) import Path (toFilePath) import Prelude -- Fix redundant import warnings import Stack.Types.Config import Stack.Types.PackageIdentifier (PackageIdentifier, packageIdentifierString, packageIdentifierName) import Stack.Types.PackageName (packageNameString) import Stack.Types.StringError import System.Directory (createDirectoryIfMissing, removeFile) import System.FilePath ((), takeFileName) import System.IO (hFlush, stdout) import System.IO.Echo (withoutInputEcho) -- | Username and password to log into Hackage. -- -- Since 0.1.0.0 data HackageCreds = HackageCreds { hcUsername :: !Text , hcPassword :: !Text , hcCredsFile :: !FilePath } deriving Show instance ToJSON HackageCreds where toJSON (HackageCreds u p _) = object [ "username" .= u , "password" .= p ] instance FromJSON (FilePath -> HackageCreds) where parseJSON = withObject "HackageCreds" $ \o -> HackageCreds <$> o .: "username" <*> o .: "password" -- | Load Hackage credentials, either from a save file or the command -- line. -- -- Since 0.1.0.0 loadCreds :: Config -> IO HackageCreds loadCreds config = do fp <- credsFile config elbs <- tryIO $ L.readFile fp case either (const Nothing) Just elbs >>= decode' of Nothing -> fromPrompt fp Just mkCreds -> do unless (configSaveHackageCreds config) $ do putStrLn "WARNING: You've set save-hackage-creds to false" putStrLn "However, credentials were found at:" putStrLn $ " " ++ fp return $ mkCreds fp where fromPrompt fp = do when (configSaveHackageCreds config) $ do putStrLn "NOTE: Username and password will be saved in a local file" putStrLn "You can modify this behavior with the save-hackage-creds config option" putStr "Hackage username: " hFlush stdout username <- TIO.getLine password <- promptPassword let hc = HackageCreds { hcUsername = username , hcPassword = password , hcCredsFile = fp } L.writeFile fp (encode hc) return hc credsFile :: Config -> IO FilePath credsFile config = do let dir = toFilePath (configStackRoot config) "upload" createDirectoryIfMissing True dir return $ dir "credentials.json" -- | Lifted from cabal-install, Distribution.Client.Upload promptPassword :: IO Text promptPassword = do putStr "Hackage password: " hFlush stdout -- save/restore the terminal echoing status (no echoing for entering the password) passwd <- withoutInputEcho $ fmap T.pack getLine putStrLn "" return passwd applyCreds :: HackageCreds -> Request -> IO Request applyCreds creds req0 = do manager <- getGlobalManager ereq <- applyDigestAuth (encodeUtf8 $ hcUsername creds) (encodeUtf8 $ hcPassword creds) req0 manager case ereq of Left e -> do putStrLn "WARNING: No HTTP digest prompt found, this will probably fail" case E.fromException e of Just e' -> putStrLn $ displayDigestAuthException e' Nothing -> print e return req0 Right req -> return req -- | Upload a single tarball with the given @Uploader@. Instead of -- sending a file like 'upload', this sends a lazy bytestring. -- -- Since 0.1.2.1 uploadBytes :: HackageCreds -> String -- ^ tar file name -> L.ByteString -- ^ tar file contents -> IO () uploadBytes creds tarName bytes = do let req1 = setRequestHeader "Accept" ["text/plain"] "https://hackage.haskell.org/packages/" formData = [partFileRequestBody "package" tarName (RequestBodyLBS bytes)] req2 <- formDataBody formData req1 req3 <- applyCreds creds req2 putStr $ "Uploading " ++ tarName ++ "... " hFlush stdout withResponse req3 $ \res -> case getResponseStatusCode res of 200 -> putStrLn "done!" 401 -> do putStrLn "authentication failure" handleIO (const $ return ()) (removeFile (hcCredsFile creds)) throwString "Authentication failure uploading to server" 403 -> do putStrLn "forbidden upload" putStrLn "Usually means: you've already uploaded this package/version combination" putStrLn "Ignoring error and continuing, full message from Hackage below:\n" printBody res 503 -> do putStrLn "service unavailable" putStrLn "This error some times gets sent even though the upload succeeded" putStrLn "Check on Hackage to see if your pacakge is present" printBody res code -> do putStrLn $ "unhandled status code: " ++ show code printBody res throwString $ "Upload failed on " ++ tarName printBody :: Response (ConduitM () S.ByteString IO ()) -> IO () printBody res = runConduit $ getResponseBody res .| CB.sinkHandle stdout -- | Upload a single tarball with the given @Uploader@. -- -- Since 0.1.0.0 upload :: HackageCreds -> FilePath -> IO () upload creds fp = uploadBytes creds (takeFileName fp) =<< L.readFile fp uploadRevision :: HackageCreds -> PackageIdentifier -> L.ByteString -> IO () uploadRevision creds ident cabalFile = do req0 <- parseRequest $ concat [ "https://hackage.haskell.org/package/" , packageIdentifierString ident , "/" , packageNameString $ packageIdentifierName ident , ".cabal/edit" ] req1 <- formDataBody [ partLBS "cabalfile" cabalFile , partBS "publish" "on" ] req0 req2 <- applyCreds creds req1 void $ httpNoBody req2 stack-1.5.1/src/Text/PrettyPrint/Leijen/Extended.hs0000644000000000000000000002534313135652051020342 0ustar0000000000000000{-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UndecidableInstances #-} -- | This module re-exports some of the interface for -- "Text.PrettyPrint.Annotated.Leijen" along with additional definitions -- useful for stack. -- -- It defines a 'Monoid' instance for 'Doc'. module Text.PrettyPrint.Leijen.Extended ( -- * Pretty-print typeclass Display(..), -- * Ansi terminal Doc -- -- See "System.Console.ANSI" for 'SGR' values to use beyond the colors -- provided. AnsiDoc, AnsiAnn(..), HasAnsiAnn(..), hDisplayAnsi, displayAnsi, displayPlain, renderDefault, -- ** Color combinators black, red, green, yellow, blue, magenta, cyan, white, dullblack, dullred, dullgreen, dullyellow, dullblue, dullmagenta, dullcyan, dullwhite, onblack, onred, ongreen, onyellow, onblue, onmagenta, oncyan, onwhite, ondullblack, ondullred, ondullgreen, ondullyellow, ondullblue, ondullmagenta, ondullcyan, ondullwhite, -- ** Intensity combinators bold, faint, normal, -- * Selective re-exports from "Text.PrettyPrint.Annotated.Leijen" -- -- Documentation of omissions up-to-date with @annotated-wl-pprint-0.7.0@ -- ** Documents, parametrized by their annotations -- -- Omitted compared to original: @putDoc, hPutDoc@ Doc, -- ** Basic combinators -- -- Omitted compared to original: @empty, char, text, (<>)@ -- -- Instead of @text@ and @char@, use 'fromString'. -- -- Instead of @empty@, use 'mempty'. nest, line, linebreak, group, softline, softbreak, -- ** Alignment -- -- The combinators in this section can not be described by Wadler's -- original combinators. They align their output relative to the -- current output position - in contrast to @nest@ which always -- aligns to the current nesting level. This deprives these -- combinators from being \`optimal\'. In practice however they -- prove to be very useful. The combinators in this section should -- be used with care, since they are more expensive than the other -- combinators. For example, @align@ shouldn't be used to pretty -- print all top-level declarations of a language, but using @hang@ -- for let expressions is fine. -- -- Omitted compared to original: @list, tupled, semiBraces@ align, hang, indent, encloseSep, -- ** Operators -- -- Omitted compared to original: @(<$>), (), (<$$>), ()@ (<+>), -- ** List combinators hsep, vsep, fillSep, sep, hcat, vcat, fillCat, cat, punctuate, -- ** Fillers fill, fillBreak, -- ** Bracketing combinators enclose, squotes, dquotes, parens, angles, braces, brackets, -- ** Character documents -- Entirely omitted: -- -- @ -- lparen, rparen, langle, rangle, lbrace, rbrace, lbracket, rbracket, -- squote, dquote, semi, colon, comma, space, dot, backslash, equals, -- pipe -- @ -- ** Primitive type documents -- Entirely omitted: -- -- @ -- string, int, integer, float, double, rational, bool, -- @ -- ** Semantic annotations annotate, noAnnotate, -- ** Rendering -- Original entirely omitted: -- @ -- SimpleDoc(..), renderPretty, renderCompact, displayDecorated, displayDecoratedA, display, displayS, displayIO, -- SpanList(..), displaySpans -- @ -- ** Undocumented -- Entirely omitted: -- @ -- column, nesting, width -- @ ) where import Control.Monad.Reader import Data.Either (partitionEithers) import qualified Data.Map.Strict as M import Data.Maybe (mapMaybe) import Data.Monoid import Data.String import qualified Data.Text as T import qualified Data.Text.IO as T import qualified Data.Text.Lazy as LT import qualified Data.Text.Lazy.Builder as LTB import System.Console.ANSI (Color(..), ColorIntensity(..), ConsoleLayer(..), ConsoleIntensity(..), SGR(..), setSGRCode, hSupportsANSI) import System.IO (Handle) import qualified Text.PrettyPrint.Annotated.Leijen as P import Text.PrettyPrint.Annotated.Leijen hiding ((<>), display) -- TODO: consider smashing together the code for wl-annotated-pprint and -- wl-pprint-text. The code here already handles doing the -- ansi-wl-pprint stuff (better!) atop wl-annotated-pprint. So the -- result would be a package unifying 3 different wl inspired packages. -- -- Perhaps it can still have native string support, by adding a type -- parameter to Doc? instance Monoid (Doc a) where mappend = (P.<>) mempty = empty -------------------------------------------------------------------------------- -- Pretty-Print class class Display a where type Ann a type Ann a = AnsiAnn display :: a -> Doc (Ann a) default display :: Show a => a -> Doc (Ann a) display = fromString . show instance Display (Doc a) where type Ann (Doc a) = a display = id -------------------------------------------------------------------------------- -- Ansi Doc type AnsiDoc = Doc AnsiAnn newtype AnsiAnn = AnsiAnn [SGR] deriving (Eq, Ord, Show, Monoid) class HasAnsiAnn a where getAnsiAnn :: a -> AnsiAnn toAnsiDoc :: Doc a -> AnsiDoc toAnsiDoc = fmap getAnsiAnn instance HasAnsiAnn AnsiAnn where getAnsiAnn = id toAnsiDoc = id instance HasAnsiAnn () where getAnsiAnn _ = mempty displayPlain :: Display a => a -> T.Text displayPlain = LT.toStrict . displayAnsiSimple . renderDefault . fmap (const mempty) . display -- TODO: tweak these settings more? -- TODO: options for settings if this is released as a lib renderDefault :: Doc a -> SimpleDoc a renderDefault = renderPretty 1 120 displayAnsi :: (Display a, HasAnsiAnn (Ann a)) => a -> T.Text displayAnsi = LT.toStrict . displayAnsiSimple . renderDefault . toAnsiDoc . display hDisplayAnsi :: (Display a, HasAnsiAnn (Ann a), MonadIO m) => Handle -> a -> m () hDisplayAnsi h x = liftIO $ do useAnsi <- hSupportsANSI h T.hPutStr h $ if useAnsi then displayAnsi x else displayPlain x displayAnsiSimple :: SimpleDoc AnsiAnn -> LT.Text displayAnsiSimple doc = LTB.toLazyText $ flip runReader mempty $ displayDecoratedWrap go doc where go (AnsiAnn sgrs) inner = do old <- ask let sgrs' = mapMaybe (\sgr -> if sgr == Reset then Nothing else Just (getSGRTag sgr, sgr)) sgrs new = if Reset `elem` sgrs then M.fromList sgrs' else foldl (\mp (tag, sgr) -> M.insert tag sgr mp) old sgrs' (extra, contents) <- local (const new) inner return (extra, transitionCodes old new <> contents <> transitionCodes new old) transitionCodes old new = case (null removals, null additions) of (True, True) -> mempty (True, False) -> fromString (setSGRCode additions) (False, _) -> fromString (setSGRCode (Reset : M.elems new)) where (removals, additions) = partitionEithers $ M.elems $ M.mergeWithKey (\_ o n -> if o == n then Nothing else Just (Right n)) (fmap Left) (fmap Right) old new displayDecoratedWrap :: forall a m. Monad m => (forall b. a -> m (b, LTB.Builder) -> m (b, LTB.Builder)) -> SimpleDoc a -> m LTB.Builder displayDecoratedWrap f doc = do (mafter, result) <- go doc case mafter of Just _ -> error "Invariant violated by input to displayDecoratedWrap: no matching SAnnotStart for SAnnotStop." Nothing -> return result where spaces n = LTB.fromText (T.replicate n " ") go :: SimpleDoc a -> m (Maybe (SimpleDoc a), LTB.Builder) go SEmpty = return (Nothing, mempty) go (SChar c x) = liftM (fmap (LTB.singleton c <>)) (go x) -- NOTE: Could actually use the length to guess at an initial -- allocation. Better yet would be to just use Text in pprint.. go (SText _l s x) = liftM (fmap (fromString s <>)) (go x) go (SLine n x) = liftM (fmap ((LTB.singleton '\n' <>) . (spaces n <>))) (go x) go (SAnnotStart ann x) = do (mafter, contents) <- f ann (go x) case mafter of Just after -> liftM (fmap (contents <>)) (go after) Nothing -> error "Invariant violated by input to displayDecoratedWrap: no matching SAnnotStop for SAnnotStart." go (SAnnotStop x) = return (Just x, mempty) -- Foreground color combinators black, red, green, yellow, blue, magenta, cyan, white, dullblack, dullred, dullgreen, dullyellow, dullblue, dullmagenta, dullcyan, dullwhite, onblack, onred, ongreen, onyellow, onblue, onmagenta, oncyan, onwhite, ondullblack, ondullred, ondullgreen, ondullyellow, ondullblue, ondullmagenta, ondullcyan, ondullwhite :: Doc AnsiAnn -> Doc AnsiAnn (black, dullblack, onblack, ondullblack) = colorFunctions Black (red, dullred, onred, ondullred) = colorFunctions Red (green, dullgreen, ongreen, ondullgreen) = colorFunctions Green (yellow, dullyellow, onyellow, ondullyellow) = colorFunctions Yellow (blue, dullblue, onblue, ondullblue) = colorFunctions Blue (magenta, dullmagenta, onmagenta, ondullmagenta) = colorFunctions Magenta (cyan, dullcyan, oncyan, ondullcyan) = colorFunctions Cyan (white, dullwhite, onwhite, ondullwhite) = colorFunctions White type EndoAnsiDoc = Doc AnsiAnn -> Doc AnsiAnn colorFunctions :: Color -> (EndoAnsiDoc, EndoAnsiDoc, EndoAnsiDoc, EndoAnsiDoc) colorFunctions color = ( ansiAnn [SetColor Foreground Vivid color] , ansiAnn [SetColor Foreground Dull color] , ansiAnn [SetColor Background Vivid color] , ansiAnn [SetColor Background Dull color] ) ansiAnn :: [SGR] -> Doc AnsiAnn -> Doc AnsiAnn ansiAnn = annotate . AnsiAnn -- Intensity combinators bold, faint, normal :: Doc AnsiAnn -> Doc AnsiAnn bold = ansiAnn [SetConsoleIntensity BoldIntensity] faint = ansiAnn [SetConsoleIntensity FaintIntensity] normal = ansiAnn [SetConsoleIntensity NormalIntensity] -- | Tags for each field of state in SGR (Select Graphics Rendition). -- -- It's a bit of a hack that 'TagReset' is included. data SGRTag = TagReset | TagConsoleIntensity | TagItalicized | TagUnderlining | TagBlinkSpeed | TagVisible | TagSwapForegroundBackground | TagColorForeground | TagColorBackground deriving (Eq, Ord) getSGRTag :: SGR -> SGRTag getSGRTag Reset{} = TagReset getSGRTag SetConsoleIntensity{} = TagConsoleIntensity getSGRTag SetItalicized{} = TagItalicized getSGRTag SetUnderlining{} = TagUnderlining getSGRTag SetBlinkSpeed{} = TagBlinkSpeed getSGRTag SetVisible{} = TagVisible getSGRTag SetSwapForegroundBackground{} = TagSwapForegroundBackground getSGRTag (SetColor Foreground _ _) = TagColorForeground getSGRTag (SetColor Background _ _) = TagColorBackground stack-1.5.1/src/System/Process/Log.hs0000644000000000000000000000523613135652051015566 0ustar0000000000000000{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} -- | Separate module because TH. module System.Process.Log (logCreateProcess ,withProcessTimeLog ,showProcessArgDebug) where import Control.Monad.IO.Class import Control.Monad.Logger import Data.Monoid import Data.Text (Text) import qualified Data.Text as T import Language.Haskell.TH import qualified System.Clock as Clock import System.Process (CreateProcess(..), CmdSpec(..)) -- | Log running a process with its arguments, for debugging (-v). logCreateProcess :: Q Exp logCreateProcess = [|let f :: MonadLogger m => CreateProcess -> m () f CreateProcess { cmdspec = ShellCommand shellCmd } = $logDebug ("Creating shell process: " <> T.pack shellCmd) f CreateProcess { cmdspec = RawCommand name args } = $logDebug ("Creating process: " <> T.pack name <> " " <> T.intercalate " " (map showProcessArgDebug args)) in f|] -- | Log running a process with its arguments, for debugging (-v). -- -- This logs one message before running the process and one message after. withProcessTimeLog :: Q Exp withProcessTimeLog = [|let f :: (MonadIO m, MonadLogger m) => String -> [String] -> m a -> m a f name args proc = do let cmdText = T.intercalate " " (T.pack name : map showProcessArgDebug args) $logDebug ("Run process: " <> cmdText) start <- liftIO $ Clock.getTime Clock.Monotonic x <- proc end <- liftIO $ Clock.getTime Clock.Monotonic let diff = Clock.diffTimeSpec start end -- useAnsi <- asks getAnsiTerminal let useAnsi = True $logDebug ("Process finished in " <> (if useAnsi then "\ESC[92m" else "") <> -- green timeSpecMilliSecondText diff <> (if useAnsi then "\ESC[0m" else "") <> -- reset ": " <> cmdText) return x in f|] timeSpecMilliSecondText :: Clock.TimeSpec -> Text timeSpecMilliSecondText t = (T.pack . show . (`div` 10^(6 :: Int)) . Clock.toNanoSecs) t <> "ms" -- | Show a process arg including speechmarks when necessary. Just for -- debugging purposes, not functionally important. showProcessArgDebug :: String -> Text showProcessArgDebug x | any special x = T.pack (show x) | otherwise = T.pack x where special '"' = True special ' ' = True special _ = False stack-1.5.1/src/System/Process/PagerEditor.hs0000644000000000000000000001167013135652051017251 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables, RankNTypes, DeriveDataTypeable #-} -- | Run external pagers (@$PAGER@, @less@, @more@) and editors (@$VISUAL@, -- @$EDITOR@, @nano@, @pico@, @vi@). module System.Process.PagerEditor (-- * Pager pageWriter ,pageByteString ,pageBuilder ,pageFile ,pageString ,PagerException(..) -- * Editor ,editFile ,editReaderWriter ,editByteString ,editString ,EditorException(..)) where import Control.Exception (try,IOException,throwIO,Exception) import Data.ByteString.Lazy (ByteString,hPut,readFile) import Data.ByteString.Builder (Builder,stringUtf8,hPutBuilder) import Data.Typeable (Typeable) import System.Directory (findExecutable) import System.Environment (lookupEnv) import System.Exit (ExitCode(..)) import System.FilePath (()) import System.Process (createProcess,shell,proc,waitForProcess,StdStream (CreatePipe) ,CreateProcess(std_in, close_fds, delegate_ctlc)) import System.IO (hClose,Handle,hPutStr,readFile,withFile,IOMode(WriteMode),stdout) import System.IO.Temp (withSystemTempDirectory) -- | Run pager, providing a function that writes to the pager's input. pageWriter :: (Handle -> IO ()) -> IO () pageWriter writer = do mpager <- lookupEnv "PAGER" `orElse` findExecutable "less" `orElse` findExecutable "more" case mpager of Just pager -> do (Just h,_,_,procHandle) <- createProcess (shell pager) {std_in = CreatePipe ,close_fds = True ,delegate_ctlc = True} (_::Either IOException ()) <- try (do writer h hClose h) exit <- waitForProcess procHandle case exit of ExitSuccess -> return () ExitFailure n -> throwIO (PagerExitFailure pager n) return () Nothing -> writer stdout -- | Run pager to display a lazy ByteString. pageByteString :: ByteString -> IO () pageByteString = pageWriter . flip hPut -- | Run pager to display a ByteString-Builder. pageBuilder :: Builder -> IO () pageBuilder = pageWriter . flip hPutBuilder -- | Run pager to display contents of a file. pageFile :: FilePath -> IO () pageFile p = pageByteString =<< Data.ByteString.Lazy.readFile p -- | Run pager to display a string. pageString :: String -> IO () pageString = pageBuilder . stringUtf8 -- | Run editor to edit a file. editFile :: FilePath -> IO () editFile path = do meditor <- lookupEnv "VISUAL" `orElse` lookupEnv "EDITOR" `orElse` findExecutable "nano" `orElse` findExecutable "pico" `orElse` findExecutable "vi" case meditor of Just editor -> do (_,_,_,procHandle) <- createProcess (proc "sh" ["-c", editor ++ " \"$1\"", "sh", path]) {close_fds = True,delegate_ctlc = True} exitCode <- waitForProcess procHandle case exitCode of ExitSuccess -> return () ExitFailure n -> throwIO (EditorExitFailure editor n) Nothing -> throwIO EditorNotFound -- | Run editor, providing functions to write and read the file contents. editReaderWriter :: forall a. String -> (Handle -> IO ()) -> (FilePath -> IO a) -> IO a editReaderWriter filename writer reader = withSystemTempDirectory "" (\p -> do let p' = p filename withFile p' WriteMode writer editFile p' reader p') -- | Run editor on a ByteString. editByteString :: String -> ByteString -> IO ByteString editByteString f s = editReaderWriter f (`hPut` s) Data.ByteString.Lazy.readFile -- | Run editor on a String. editString :: String -> String -> IO String editString f s = editReaderWriter f (`hPutStr` s) System.IO.readFile -- | Short-circuit first Just. orElse :: (Monad m) => m (Maybe a) -> m (Maybe a) -> m (Maybe a) orElse a b = do m <- a case m of Just _ -> return m Nothing -> b -- | Exception running pager. data PagerException = PagerNotFound | PagerExitFailure FilePath Int deriving Typeable instance Show PagerException where show PagerNotFound = "No pager found (tried $PAGER, `less`, and `more`.)" show (PagerExitFailure p n) = "Pager (`" ++ p ++ "') exited with non-zero status: " ++ show n instance Exception PagerException -- | Exception running editor. data EditorException = EditorNotFound | EditorExitFailure FilePath Int deriving Typeable instance Show EditorException where show EditorNotFound = "No editor found (tried $VISUAL, $PAGER, `nano`, `pico`, and `vi`.)" show (EditorExitFailure p n) = "Editor (`" ++ p ++ "') exited with non-zero status: " ++ show n instance Exception EditorException stack-1.5.1/src/System/Process/Read.hs0000644000000000000000000004434313135652051015722 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} -- | Reading from external processes. module System.Process.Read (readProcessStdout ,readProcessStderrStdout ,tryProcessStdout ,tryProcessStderrStdout ,sinkProcessStdout ,sinkProcessStderrStdout ,sinkProcessStderrStdoutHandle ,logProcessStderrStdout ,readProcess ,EnvOverride(..) ,unEnvOverride ,mkEnvOverride ,modifyEnvOverride ,envHelper ,doesExecutableExist ,findExecutable ,getEnvOverride ,envSearchPath ,preProcess ,readProcessNull ,ReadProcessException (..) ,augmentPath ,augmentPathMap ,resetExeCache ) where import Control.Applicative import Control.Arrow ((***), first) import Control.Concurrent.Async (concurrently) import Control.Exception hiding (try, catch) import Control.Monad (join, liftM, unless, void) import Control.Monad.Catch (MonadThrow, MonadCatch, throwM, try, catch) import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Logger import Control.Monad.Trans.Control (MonadBaseControl, liftBaseWith) import qualified Data.ByteString as S import Data.ByteString.Builder import qualified Data.ByteString.Lazy as L import Data.Conduit import qualified Data.Conduit.Binary as CB import qualified Data.Conduit.List as CL import Data.Conduit.Process hiding (callProcess) import Data.IORef import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe (isJust, maybeToList, fromMaybe) import Data.Monoid import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding.Error (lenientDecode) import qualified Data.Text.Lazy as LT import qualified Data.Text.Lazy.Encoding as LT import Data.Typeable (Typeable) import Distribution.System (OS (Windows), Platform (Platform)) import Language.Haskell.TH as TH (location) import Path import Path.Extra import Path.IO hiding (findExecutable) import Prelude -- Fix AMP warning import qualified System.Directory as D import System.Environment (getEnvironment) import System.Exit import qualified System.FilePath as FP import System.IO (Handle, hClose) import System.Process.Log import Prelude () -- Hide post-AMP warnings -- | Override the environment received by a child process. data EnvOverride = EnvOverride { eoTextMap :: Map Text Text -- ^ Environment variables as map , eoStringList :: [(String, String)] -- ^ Environment variables as association list , eoPath :: [FilePath] -- ^ List of directories searched for executables (@PATH@) , eoExeCache :: IORef (Map FilePath (Either ReadProcessException (Path Abs File))) , eoExeExtensions :: [String] -- ^ @[""]@ on non-Windows systems, @["", ".exe", ".bat"]@ on Windows , eoPlatform :: Platform } -- | Get the environment variables from an 'EnvOverride'. unEnvOverride :: EnvOverride -> Map Text Text unEnvOverride = eoTextMap -- | Get the list of directories searched (@PATH@). envSearchPath :: EnvOverride -> [FilePath] envSearchPath = eoPath -- | Modify the environment variables of an 'EnvOverride'. modifyEnvOverride :: MonadIO m => EnvOverride -> (Map Text Text -> Map Text Text) -> m EnvOverride modifyEnvOverride eo f = mkEnvOverride (eoPlatform eo) (f $ eoTextMap eo) -- | Create a new 'EnvOverride'. mkEnvOverride :: MonadIO m => Platform -> Map Text Text -> m EnvOverride mkEnvOverride platform tm' = do ref <- liftIO $ newIORef Map.empty return EnvOverride { eoTextMap = tm , eoStringList = map (T.unpack *** T.unpack) $ Map.toList tm , eoPath = (if isWindows then (".":) else id) (maybe [] (FP.splitSearchPath . T.unpack) (Map.lookup "PATH" tm)) , eoExeCache = ref , eoExeExtensions = if isWindows then let pathext = fromMaybe ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC" (Map.lookup "PATHEXT" tm) in map T.unpack $ "" : T.splitOn ";" pathext else [""] , eoPlatform = platform } where -- Fix case insensitivity of the PATH environment variable on Windows. tm | isWindows = Map.fromList $ map (first T.toUpper) $ Map.toList tm' | otherwise = tm' -- Don't use CPP so that the Windows code path is at least type checked -- regularly isWindows = case platform of Platform _ Windows -> True _ -> False -- | Helper conversion function. envHelper :: EnvOverride -> Maybe [(String, String)] envHelper = Just . eoStringList -- | Read from the process, ignoring any output. -- -- Throws a 'ReadProcessException' exception if the process fails. readProcessNull :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => Maybe (Path Abs Dir) -- ^ Optional working directory -> EnvOverride -> String -- ^ Command -> [String] -- ^ Command line arguments -> m () readProcessNull wd menv name args = sinkProcessStdout wd menv name args CL.sinkNull -- | Try to produce a strict 'S.ByteString' from the stdout of a -- process. tryProcessStdout :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => Maybe (Path Abs Dir) -- ^ Optional directory to run in -> EnvOverride -> String -- ^ Command -> [String] -- ^ Command line arguments -> m (Either ReadProcessException S.ByteString) tryProcessStdout wd menv name args = try (readProcessStdout wd menv name args) -- | Try to produce strict 'S.ByteString's from the stderr and stdout of a -- process. tryProcessStderrStdout :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => Maybe (Path Abs Dir) -- ^ Optional directory to run in -> EnvOverride -> String -- ^ Command -> [String] -- ^ Command line arguments -> m (Either ReadProcessException (S.ByteString, S.ByteString)) tryProcessStderrStdout wd menv name args = try (readProcessStderrStdout wd menv name args) -- | Produce a strict 'S.ByteString' from the stdout of a process. -- -- Throws a 'ReadProcessException' exception if the process fails. readProcessStdout :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => Maybe (Path Abs Dir) -- ^ Optional directory to run in -> EnvOverride -> String -- ^ Command -> [String] -- ^ Command line arguments -> m S.ByteString readProcessStdout wd menv name args = sinkProcessStdout wd menv name args CL.consume >>= liftIO . evaluate . S.concat -- | Produce strict 'S.ByteString's from the stderr and stdout of a process. -- -- Throws a 'ReadProcessException' exception if the process fails. readProcessStderrStdout :: (MonadIO m, MonadLogger m, MonadBaseControl IO m) => Maybe (Path Abs Dir) -- ^ Optional directory to run in -> EnvOverride -> String -- ^ Command -> [String] -- ^ Command line arguments -> m (S.ByteString, S.ByteString) readProcessStderrStdout wd menv name args = do (e, o) <- sinkProcessStderrStdout wd menv name args CL.consume CL.consume liftIO $ (,) <$> evaluate (S.concat e) <*> evaluate (S.concat o) -- | An exception while trying to read from process. data ReadProcessException = ProcessFailed CreateProcess ExitCode L.ByteString L.ByteString -- ^ @'ProcessFailed' createProcess exitCode stdout stderr@ | NoPathFound | ExecutableNotFound String [FilePath] | ExecutableNotFoundAt FilePath deriving Typeable instance Show ReadProcessException where show (ProcessFailed cp ec out err) = concat $ [ "Running " , showSpec $ cmdspec cp] ++ maybe [] (\x -> [" in directory ", x]) (cwd cp) ++ [ " exited with " , show ec , "\n\n" , toStr out , "\n" , toStr err ] where toStr = LT.unpack . LT.decodeUtf8With lenientDecode showSpec (ShellCommand str) = str showSpec (RawCommand cmd args) = unwords $ cmd : map (T.unpack . showProcessArgDebug) args show NoPathFound = "PATH not found in EnvOverride" show (ExecutableNotFound name path) = concat [ "Executable named " , name , " not found on path: " , show path ] show (ExecutableNotFoundAt name) = "Did not find executable at specified path: " ++ name instance Exception ReadProcessException -- | Consume the stdout of a process feeding strict 'S.ByteString's to a consumer. -- If the process fails, spits out stdout and stderr as error log -- level. Should not be used for long-running processes or ones with -- lots of output; for that use 'sinkProcessStdoutLogStderr'. -- -- Throws a 'ReadProcessException' if unsuccessful. sinkProcessStdout :: (MonadIO m, MonadLogger m, MonadBaseControl IO m, MonadCatch m) => Maybe (Path Abs Dir) -- ^ Optional directory to run in -> EnvOverride -> String -- ^ Command -> [String] -- ^ Command line arguments -> Sink S.ByteString IO a -- ^ Sink for stdout -> m a sinkProcessStdout wd menv name args sinkStdout = do stderrBuffer <- liftIO (newIORef mempty) stdoutBuffer <- liftIO (newIORef mempty) (_,sinkRet) <- catch (sinkProcessStderrStdout wd menv name args (CL.mapM_ (\bytes -> liftIO (modifyIORef' stderrBuffer (<> byteString bytes)))) (CL.iterM (\bytes -> liftIO (modifyIORef' stdoutBuffer (<> byteString bytes))) $= sinkStdout)) (\(ProcessExitedUnsuccessfully cp ec) -> do stderrBuilder <- liftIO (readIORef stderrBuffer) stdoutBuilder <- liftIO (readIORef stdoutBuffer) throwM $ ProcessFailed cp ec (toLazyByteString stdoutBuilder) (toLazyByteString stderrBuilder)) return sinkRet logProcessStderrStdout :: (MonadIO m, MonadBaseControl IO m, MonadLogger m) => Maybe (Path Abs Dir) -> String -> EnvOverride -> [String] -> m () logProcessStderrStdout mdir name menv args = liftBaseWith $ \restore -> do let logLines = CB.lines =$ CL.mapM_ (void . restore . monadLoggerLog $(TH.location >>= liftLoc) "" LevelInfo . toLogStr) void $ restore $ sinkProcessStderrStdout mdir menv name args logLines logLines -- | Consume the stdout and stderr of a process feeding strict 'S.ByteString's to the consumers. -- -- Throws a 'ReadProcessException' if unsuccessful in launching, or 'ProcessExitedUnsuccessfully' if the process itself fails. sinkProcessStderrStdout :: forall m e o. (MonadIO m, MonadLogger m) => Maybe (Path Abs Dir) -- ^ Optional directory to run in -> EnvOverride -> String -- ^ Command -> [String] -- ^ Command line arguments -> Sink S.ByteString IO e -- ^ Sink for stderr -> Sink S.ByteString IO o -- ^ Sink for stdout -> m (e,o) sinkProcessStderrStdout wd menv name args sinkStderr sinkStdout = do name' <- preProcess wd menv name $withProcessTimeLog name' args $ liftIO $ withCheckedProcess (proc name' args) { env = envHelper menv, cwd = fmap toFilePath wd } (\ClosedStream out err -> f err out) where -- There is a bug in streaming-commons or conduit-extra which -- leads to a file descriptor leak. Ideally, we should be able to -- simply use the following code. Instead, we're using the code -- below it, which is explicit in closing Handles. When the -- upstream bug is fixed, we can consider moving back to the -- simpler code, though there's really no downside to the more -- complex version used here. -- -- f :: Source IO S.ByteString -> Source IO S.ByteString -> IO (e, o) -- f err out = (err $$ sinkStderr) `concurrently` (out $$ sinkStdout) f :: Handle -> Handle -> IO (e, o) f err out = ((CB.sourceHandle err $$ sinkStderr) `concurrently` (CB.sourceHandle out $$ sinkStdout)) `finally` hClose err `finally` hClose out -- | Like sinkProcessStderrStdout, but receives Handles for stderr and stdout instead of 'Sink's. -- -- Throws a 'ReadProcessException' if unsuccessful in launching, or 'ProcessExitedUnsuccessfully' if the process itself fails. sinkProcessStderrStdoutHandle :: (MonadIO m, MonadLogger m) => Maybe (Path Abs Dir) -- ^ Optional directory to run in -> EnvOverride -> String -- ^ Command -> [String] -- ^ Command line arguments -> Handle -> Handle -> m () sinkProcessStderrStdoutHandle wd menv name args err out = do name' <- preProcess wd menv name $withProcessTimeLog name' args $ liftIO $ withCheckedProcess (proc name' args) { env = envHelper menv , cwd = fmap toFilePath wd , std_err = UseHandle err , std_out = UseHandle out } (\ClosedStream UseProvidedHandle UseProvidedHandle -> return ()) -- | Perform pre-call-process tasks. Ensure the working directory exists and find the -- executable path. -- -- Throws a 'ReadProcessException' if unsuccessful. preProcess :: (MonadIO m) => Maybe (Path Abs Dir) -- ^ Optional directory to create if necessary -> EnvOverride -- ^ How to override environment -> String -- ^ Command name -> m FilePath preProcess wd menv name = do name' <- liftIO $ liftM toFilePath $ join $ findExecutable menv name maybe (return ()) ensureDir wd return name' -- | Check if the given executable exists on the given PATH. doesExecutableExist :: (MonadIO m) => EnvOverride -- ^ How to override environment -> String -- ^ Name of executable -> m Bool doesExecutableExist menv name = liftM isJust $ findExecutable menv name -- | Find the complete path for the executable. -- -- Throws a 'ReadProcessException' if unsuccessful. findExecutable :: (MonadIO m, MonadThrow n) => EnvOverride -- ^ How to override environment -> String -- ^ Name of executable -> m (n (Path Abs File)) -- ^ Full path to that executable on success findExecutable eo name0 | any FP.isPathSeparator name0 = do let names0 = map (name0 ++) (eoExeExtensions eo) testNames [] = return $ throwM $ ExecutableNotFoundAt name0 testNames (name:names) = do exists <- liftIO $ D.doesFileExist name if exists then do path <- liftIO $ resolveFile' name return $ return path else testNames names testNames names0 findExecutable eo name = liftIO $ do m <- readIORef $ eoExeCache eo epath <- case Map.lookup name m of Just epath -> return epath Nothing -> do let loop [] = return $ Left $ ExecutableNotFound name (eoPath eo) loop (dir:dirs) = do let fp0 = dir FP. name fps0 = map (fp0 ++) (eoExeExtensions eo) testFPs [] = loop dirs testFPs (fp:fps) = do exists <- D.doesFileExist fp existsExec <- if exists then liftM D.executable $ D.getPermissions fp else return False if existsExec then do fp' <- D.makeAbsolute fp >>= parseAbsFile return $ return fp' else testFPs fps testFPs fps0 epath <- loop $ eoPath eo () <- atomicModifyIORef (eoExeCache eo) $ \m' -> (Map.insert name epath m', ()) return epath return $ either throwM return epath -- | Reset the executable cache. resetExeCache :: MonadIO m => EnvOverride -> m () resetExeCache eo = liftIO (atomicModifyIORef (eoExeCache eo) (const mempty)) -- | Load up an 'EnvOverride' from the standard environment. getEnvOverride :: MonadIO m => Platform -> m EnvOverride getEnvOverride platform = liftIO $ getEnvironment >>= mkEnvOverride platform . Map.fromList . map (T.pack *** T.pack) newtype InvalidPathException = PathsInvalidInPath [FilePath] deriving Typeable instance Exception InvalidPathException instance Show InvalidPathException where show (PathsInvalidInPath paths) = unlines $ [ "Would need to add some paths to the PATH environment variable \ \to continue, but they would be invalid because they contain a " ++ show FP.searchPathSeparator ++ "." , "Please fix the following paths and try again:" ] ++ paths -- | Augment the PATH environment variable with the given extra paths. augmentPath :: MonadThrow m => [Path Abs Dir] -> Maybe Text -> m Text augmentPath dirs mpath = do let illegal = filter (FP.searchPathSeparator `elem`) (map toFilePath dirs) unless (null illegal) (throwM $ PathsInvalidInPath illegal) return $ T.intercalate (T.singleton FP.searchPathSeparator) $ map (T.pack . toFilePathNoTrailingSep) dirs ++ maybeToList mpath -- | Apply 'augmentPath' on the PATH value in the given Map. augmentPathMap :: MonadThrow m => [Path Abs Dir] -> Map Text Text -> m (Map Text Text) augmentPathMap dirs origEnv = do path <- augmentPath dirs mpath return $ Map.insert "PATH" path origEnv where mpath = Map.lookup "PATH" origEnv stack-1.5.1/src/System/Process/Run.hs0000644000000000000000000001307613135652051015612 0ustar0000000000000000{-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RecordWildCards #-} -- | Run sub-processes. module System.Process.Run (runCmd ,runCmd' ,callProcess ,callProcess' ,callProcessInheritStderrStdout ,callProcessObserveStdout ,createProcess' ,ProcessExitedUnsuccessfully ,Cmd(..) ) where import Control.Exception.Lifted import Control.Monad (liftM) import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Logger (MonadLogger, logError) import Control.Monad.Trans.Control (MonadBaseControl) import Data.Conduit.Process hiding (callProcess) import Data.Foldable (forM_) import Data.Text (Text) import qualified Data.Text as T import Path (Dir, Abs, Path, toFilePath) import Prelude -- Fix AMP warning import System.Exit (exitWith, ExitCode (..)) import System.IO import qualified System.Process import System.Process.Log import System.Process.Read -- | Cmd holds common infos needed to running a process in most cases data Cmd = Cmd { cmdDirectoryToRunIn :: Maybe (Path Abs Dir) -- ^ directory to run in , cmdCommandToRun :: FilePath -- ^ command to run , cmdEnvOverride :: EnvOverride , cmdCommandLineArguments :: [String] -- ^ command line arguments } -- | Run the given command in the given directory, inheriting stdout and stderr. -- -- If it exits with anything but success, prints an error -- and then calls 'exitWith' to exit the program. runCmd :: forall (m :: * -> *). (MonadLogger m,MonadIO m,MonadBaseControl IO m) => Cmd -> Maybe Text -- ^ optional additional error message -> m () runCmd = runCmd' id runCmd' :: forall (m :: * -> *). (MonadLogger m,MonadIO m,MonadBaseControl IO m) => (CreateProcess -> CreateProcess) -> Cmd -> Maybe Text -- ^ optional additional error message -> m () runCmd' modCP cmd@Cmd{..} mbErrMsg = do result <- try (callProcess' modCP cmd) case result of Left (ProcessExitedUnsuccessfully _ ec) -> do $logError $ T.pack $ concat $ [ "Exit code " , show ec , " while running " , show (cmdCommandToRun : cmdCommandLineArguments) ] ++ (case cmdDirectoryToRunIn of Nothing -> [] Just mbDir -> [" in ", toFilePath mbDir] ) forM_ mbErrMsg $logError liftIO (exitWith ec) Right () -> return () -- | Like 'System.Process.callProcess', but takes an optional working directory and -- environment override, and throws 'ProcessExitedUnsuccessfully' if the -- process exits unsuccessfully and a 'ReadProcessException' if the executable is not found. -- -- Inherits stdout and stderr. callProcess :: (MonadIO m, MonadLogger m) => Cmd -> m () callProcess = callProcess' id -- | Like 'System.Process.callProcess', but takes an optional working directory and -- environment override, and throws 'ProcessExitedUnsuccessfully' if the -- process exits unsuccessfully and a 'ReadProcessException' if the executable is not found. -- -- Inherits stdout and stderr. callProcess' :: (MonadIO m, MonadLogger m) => (CreateProcess -> CreateProcess) -> Cmd -> m () callProcess' modCP cmd = do c <- liftM modCP (cmdToCreateProcess cmd) $logCreateProcess c liftIO $ do (_, _, _, p) <- System.Process.createProcess c exit_code <- waitForProcess p case exit_code of ExitSuccess -> return () ExitFailure _ -> throwIO (ProcessExitedUnsuccessfully c exit_code) callProcessInheritStderrStdout :: (MonadIO m, MonadLogger m) => Cmd -> m () callProcessInheritStderrStdout cmd = do let inheritOutput cp = cp { std_in = CreatePipe, std_out = Inherit, std_err = Inherit } callProcess' inheritOutput cmd callProcessObserveStdout :: (MonadIO m, MonadLogger m) => Cmd -> m String callProcessObserveStdout cmd = do c <- liftM modCP (cmdToCreateProcess cmd) $logCreateProcess c liftIO $ do (_, Just hStdout, _, p) <- System.Process.createProcess c hSetBuffering hStdout NoBuffering exit_code <- waitForProcess p case exit_code of ExitSuccess -> hGetLine hStdout ExitFailure _ -> throwIO (ProcessExitedUnsuccessfully c exit_code) where modCP c = c { std_in = CreatePipe, std_out = CreatePipe, std_err = Inherit } -- | Like 'System.Process.Internal.createProcess_', but taking a 'Cmd'. -- Note that the 'Handle's provided by 'UseHandle' are not closed -- automatically. createProcess' :: (MonadIO m, MonadLogger m) => String -> (CreateProcess -> CreateProcess) -> Cmd -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) createProcess' tag modCP cmd = do c <- liftM modCP (cmdToCreateProcess cmd) $logCreateProcess c liftIO $ System.Process.createProcess_ tag c -- Throws a 'ReadProcessException' if process is not found. cmdToCreateProcess :: MonadIO m => Cmd -> m CreateProcess cmdToCreateProcess (Cmd wd cmd0 menv args) = do cmd <- preProcess wd menv cmd0 return $ (proc cmd args) { delegate_ctlc = True , cwd = fmap toFilePath wd , env = envHelper menv } stack-1.5.1/src/Hackage/Security/Client/Repository/HttpLib/HttpClient.hs0000644000000000000000000001554013135652051024315 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} -- Taken from -- https://github.com/well-typed/hackage-security/tree/master/hackage-security-http-client -- to avoid extra dependencies module Hackage.Security.Client.Repository.HttpLib.HttpClient ( withClient , makeHttpLib -- ** Re-exports , Manager -- opaque ) where import Control.Exception import Control.Monad (void) import Data.ByteString (ByteString) import Network.URI import Network.HTTP.Client (Manager) import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BS.C8 import qualified Network.HTTP.Client as HttpClient import qualified Network.HTTP.Client.Internal as HttpClient import qualified Network.HTTP.Types as HttpClient import Hackage.Security.Client hiding (Header) import Hackage.Security.Client.Repository.HttpLib import Hackage.Security.Util.Checked import qualified Hackage.Security.Util.Lens as Lens {------------------------------------------------------------------------------- Top-level API -------------------------------------------------------------------------------} -- | Initialization -- -- The proxy must be specified at initialization because @http-client@ does not -- allow to change the proxy once the 'Manager' is created. withClient :: ProxyConfig HttpClient.Proxy -> (Manager -> HttpLib -> IO a) -> IO a withClient proxyConfig callback = do manager <- HttpClient.newManager (setProxy HttpClient.defaultManagerSettings) callback manager $ makeHttpLib manager where setProxy = HttpClient.managerSetProxy $ case proxyConfig of ProxyConfigNone -> HttpClient.noProxy ProxyConfigUse p -> HttpClient.useProxy p ProxyConfigAuto -> HttpClient.proxyEnvironment Nothing -- | Create an 'HttpLib' value from a preexisting 'Manager'. makeHttpLib :: Manager -> HttpLib makeHttpLib manager = HttpLib { httpGet = get manager , httpGetRange = getRange manager } {------------------------------------------------------------------------------- Individual methods -------------------------------------------------------------------------------} get :: Throws SomeRemoteError => Manager -> [HttpRequestHeader] -> URI -> ([HttpResponseHeader] -> BodyReader -> IO a) -> IO a get manager reqHeaders uri callback = wrapCustomEx $ do -- TODO: setUri fails under certain circumstances; in particular, when -- the URI contains URL auth. Not sure if this is a concern. request' <- HttpClient.setUri HttpClient.defaultRequest uri let request = setRequestHeaders reqHeaders request' checkHttpException $ HttpClient.withResponse request manager $ \response -> do let br = wrapCustomEx $ HttpClient.responseBody response callback (getResponseHeaders response) br getRange :: Throws SomeRemoteError => Manager -> [HttpRequestHeader] -> URI -> (Int, Int) -> (HttpStatus -> [HttpResponseHeader] -> BodyReader -> IO a) -> IO a getRange manager reqHeaders uri (from, to) callback = wrapCustomEx $ do request' <- HttpClient.setUri HttpClient.defaultRequest uri let request = setRange from to $ setRequestHeaders reqHeaders request' checkHttpException $ HttpClient.withResponse request manager $ \response -> do let br = wrapCustomEx $ HttpClient.responseBody response case () of () | HttpClient.responseStatus response == HttpClient.partialContent206 -> callback HttpStatus206PartialContent (getResponseHeaders response) br () | HttpClient.responseStatus response == HttpClient.ok200 -> callback HttpStatus200OK (getResponseHeaders response) br _otherwise -> throwChecked $ HttpClient.HttpExceptionRequest request $ HttpClient.StatusCodeException (void response) "" -- | Wrap custom exceptions -- -- NOTE: The only other exception defined in @http-client@ is @TimeoutTriggered@ -- but it is currently disabled wrapCustomEx :: (Throws HttpClient.HttpException => IO a) -> (Throws SomeRemoteError => IO a) wrapCustomEx act = handleChecked (\(ex :: HttpClient.HttpException) -> go ex) act where go ex = throwChecked (SomeRemoteError ex) checkHttpException :: Throws HttpClient.HttpException => IO a -> IO a checkHttpException = handle $ \(ex :: HttpClient.HttpException) -> throwChecked ex {------------------------------------------------------------------------------- http-client auxiliary -------------------------------------------------------------------------------} hAcceptRanges :: HttpClient.HeaderName hAcceptRanges = "Accept-Ranges" hAcceptEncoding :: HttpClient.HeaderName hAcceptEncoding = "Accept-Encoding" setRange :: Int -> Int -> HttpClient.Request -> HttpClient.Request setRange from to req = req { HttpClient.requestHeaders = (HttpClient.hRange, rangeHeader) : HttpClient.requestHeaders req } where -- Content-Range header uses inclusive rather than exclusive bounds -- See rangeHeader = BS.C8.pack $ "bytes=" ++ show from ++ "-" ++ show (to - 1) -- | Set request headers setRequestHeaders :: [HttpRequestHeader] -> HttpClient.Request -> HttpClient.Request setRequestHeaders opts req = req { HttpClient.requestHeaders = trOpt disallowCompressionByDefault opts } where trOpt :: [(HttpClient.HeaderName, [ByteString])] -> [HttpRequestHeader] -> [HttpClient.Header] trOpt acc [] = concatMap finalizeHeader acc trOpt acc (HttpRequestMaxAge0:os) = trOpt (insert HttpClient.hCacheControl ["max-age=0"] acc) os trOpt acc (HttpRequestNoTransform:os) = trOpt (insert HttpClient.hCacheControl ["no-transform"] acc) os -- disable content compression (potential security issue) disallowCompressionByDefault :: [(HttpClient.HeaderName, [ByteString])] disallowCompressionByDefault = [(hAcceptEncoding, [])] -- Some headers are comma-separated, others need multiple headers for -- multiple options. -- -- TODO: Right we we just comma-separate all of them. finalizeHeader :: (HttpClient.HeaderName, [ByteString]) -> [HttpClient.Header] finalizeHeader (name, strs) = [(name, BS.intercalate ", " (reverse strs))] insert :: Eq a => a -> [b] -> [(a, [b])] -> [(a, [b])] insert x y = Lens.modify (Lens.lookupM x) (++ y) -- | Extract the response headers getResponseHeaders :: HttpClient.Response a -> [HttpResponseHeader] getResponseHeaders response = concat [ [ HttpResponseAcceptRangesBytes | (hAcceptRanges, "bytes") `elem` headers ] ] where headers = HttpClient.responseHeaders response stack-1.5.1/src/Stack/Constants.hs-boot0000644000000000000000000000006613135652051016121 0ustar0000000000000000module Stack.Constants where stackProgName :: String stack-1.5.1/src/Stack/Types/Config.hs-boot0000644000000000000000000000271313135652051016457 0ustar0000000000000000{-# LANGUAGE ExistentialQuantification #-} module Stack.Types.Config where import Control.Exception import Data.List.NonEmpty (NonEmpty) import Distribution.Version import Data.Text (Text) import Data.Yaml (ParseException) import Path import Stack.Types.BuildPlan (SnapName) import {-# SOURCE #-} Stack.Types.Resolver (Resolver, ResolverThat's) data WhichSolverCmd data ConfigException = ParseConfigFileException (Path Abs File) ParseException | ParseCustomSnapshotException Text ParseException | ParseResolverException Text | NoProjectConfigFound (Path Abs Dir) (Maybe Text) | UnexpectedArchiveContents [Path Abs Dir] [Path Abs File] | UnableToExtractArchive Text (Path Abs File) | BadStackVersionException VersionRange | NoMatchingSnapshot WhichSolverCmd (NonEmpty SnapName) | forall l. ResolverMismatch WhichSolverCmd (ResolverThat's l) String | ResolverPartial WhichSolverCmd Resolver String | NoSuchDirectory FilePath | ParseGHCVariantException String | BadStackRoot (Path Abs Dir) | Won'tCreateStackRootInDirectoryOwnedByDifferentUser (Path Abs Dir) (Path Abs Dir) -- ^ @$STACK_ROOT@, parent dir | UserDoesn'tOwnDirectory (Path Abs Dir) | FailedToCloneRepo String | ManualGHCVariantSettingsAreIncompatibleWithSystemGHC | NixRequiresSystemGhc | NoResolverWhenUsingNoLocalConfig | InvalidResolverForNoLocalConfig String instance Exception ConfigException stack-1.5.1/src/Stack/Types/Resolver.hs-boot0000644000000000000000000000126113135652051017050 0ustar0000000000000000{-# LANGUAGE DataKinds #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE GADTs #-} module Stack.Types.Resolver where import Data.Text (Text) import Stack.Types.BuildPlan (SnapName, SnapshotHash) import Stack.Types.Compiler data IsLoaded = Loaded | NotLoaded data ResolverThat's (l :: IsLoaded) where ResolverSnapshot :: !SnapName -> ResolverThat's l ResolverCompiler :: !CompilerVersion -> ResolverThat's l ResolverCustom :: !Text -> !Text -> ResolverThat's 'NotLoaded ResolverCustomLoaded :: !Text -> !Text -> !SnapshotHash -> ResolverThat's 'Loaded type LoadedResolver = ResolverThat's 'Loaded type Resolver = ResolverThat's 'NotLoaded stack-1.5.1/src/main/Main.hs0000644000000000000000000011437313135652051013756 0ustar0000000000000000{-# LANGUAGE TupleSections #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE RecordWildCards #-} -- | Main stack tool entry point. module Main (main) where #ifndef HIDE_DEP_VERSIONS import qualified Build_stack #endif import Control.Exception import Control.Monad hiding (mapM, forM) import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.Reader (local) import Control.Monad.Trans.Either (EitherT) import Control.Monad.Writer.Lazy (Writer) import Data.Attoparsec.Args (parseArgs, EscapingMode (Escaping)) import Data.Attoparsec.Interpreter (getInterpreterArgs) import qualified Data.ByteString.Lazy as L import Data.IORef.RunOnce (runOnce) import Data.List import qualified Data.Map as Map import Data.Maybe import Data.Monoid import Data.Text (Text) import qualified Data.Text as T import Data.Traversable import Data.Typeable (Typeable) import Data.Version (showVersion) import System.Process.Read #ifdef USE_GIT_INFO import Development.GitRev (gitCommitCount, gitHash) #endif import Distribution.System (buildArch, buildPlatform) import Distribution.Text (display) import GHC.IO.Encoding (mkTextEncoding, textEncodingName) import Lens.Micro import Options.Applicative import Options.Applicative.Help (errorHelp, stringChunk, vcatChunks) import Options.Applicative.Builder.Extra import Options.Applicative.Complicated #ifdef USE_GIT_INFO import Options.Applicative.Simple (simpleVersion) #endif import Options.Applicative.Types (ParserHelp(..)) import Path import Path.IO import qualified Paths_stack as Meta import Prelude hiding (pi, mapM) import Stack.Build import Stack.BuildPlan import Stack.Clean (CleanOpts, clean) import Stack.Config import Stack.ConfigCmd as ConfigCmd import Stack.Constants import Stack.Coverage import qualified Stack.Docker as Docker import Stack.Dot import Stack.Exec import Stack.GhcPkg (findGhcPkgField) import qualified Stack.Nix as Nix import Stack.Fetch import Stack.FileWatch import Stack.Ghci import Stack.Hoogle import qualified Stack.IDE as IDE import qualified Stack.Image as Image import Stack.Init import Stack.New import Stack.Options.BuildParser import Stack.Options.CleanParser import Stack.Options.DockerParser import Stack.Options.DotParser import Stack.Options.ExecParser import Stack.Options.GhciParser import Stack.Options.GlobalParser import Stack.Options.HpcReportParser import Stack.Options.NewParser import Stack.Options.NixParser import Stack.Options.ScriptParser import Stack.Options.SDistParser import Stack.Options.SolverParser import Stack.Options.Utils import qualified Stack.PackageIndex import qualified Stack.Path import Stack.Runners import Stack.Script import Stack.SDist (getSDistTarball, checkSDistTarball, checkSDistTarball', SDistOpts(..)) import Stack.SetupCmd import qualified Stack.Sig as Sig import Stack.Solver (solveExtraDeps) import Stack.Types.Version import Stack.Types.Config import Stack.Types.Compiler import Stack.Types.Resolver import Stack.Types.Nix import Stack.Types.StackT import Stack.Types.StringError import Stack.Upgrade import qualified Stack.Upload as Upload import qualified System.Directory as D import System.Environment (getProgName, getArgs, withArgs) import System.Exit import System.FilePath (pathSeparator) import System.IO (hIsTerminalDevice, stderr, stdin, stdout, hSetBuffering, BufferMode(..), hPutStrLn, Handle, hGetEncoding, hSetEncoding) -- | Change the character encoding of the given Handle to transliterate -- on unsupported characters instead of throwing an exception hSetTranslit :: Handle -> IO () hSetTranslit h = do menc <- hGetEncoding h case fmap textEncodingName menc of Just name | '/' `notElem` name -> do enc' <- mkTextEncoding $ name ++ "//TRANSLIT" hSetEncoding h enc' _ -> return () versionString' :: String #ifdef USE_GIT_INFO versionString' = concat $ concat [ [$(simpleVersion Meta.version)] -- Leave out number of commits for --depth=1 clone -- See https://github.com/commercialhaskell/stack/issues/792 , [" (" ++ commitCount ++ " commits)" | commitCount /= ("1"::String) && commitCount /= ("UNKNOWN" :: String)] , [" ", display buildArch] , [depsString] ] where commitCount = $gitCommitCount #else versionString' = showVersion Meta.version ++ ' ' : display buildArch ++ depsString where #endif #ifdef HIDE_DEP_VERSIONS depsString = " hpack-" ++ VERSION_hpack #else depsString = "\nCompiled with:\n" ++ unlines (map ("- " ++) Build_stack.deps) #endif main :: IO () main = do -- Line buffer the output by default, particularly for non-terminal runs. -- See https://github.com/commercialhaskell/stack/pull/360 hSetBuffering stdout LineBuffering hSetBuffering stdin LineBuffering hSetBuffering stderr LineBuffering hSetTranslit stdout hSetTranslit stderr args <- getArgs progName <- getProgName isTerminal <- hIsTerminalDevice stdout execExtraHelp args Docker.dockerHelpOptName (dockerOptsParser False) ("Only showing --" ++ Docker.dockerCmdName ++ "* options.") execExtraHelp args Nix.nixHelpOptName (nixOptsParser False) ("Only showing --" ++ Nix.nixCmdName ++ "* options.") currentDir <- D.getCurrentDirectory eGlobalRun <- try $ commandLineHandler currentDir progName False case eGlobalRun of Left (exitCode :: ExitCode) -> throwIO exitCode Right (globalMonoid,run) -> do let global = globalOptsFromMonoid isTerminal globalMonoid when (globalLogLevel global == LevelDebug) $ hPutStrLn stderr versionString' case globalReExecVersion global of Just expectVersion -> do expectVersion' <- parseVersionFromString expectVersion unless (checkVersion MatchMinor expectVersion' (fromCabalVersion Meta.version)) $ throwIO $ InvalidReExecVersion expectVersion (showVersion Meta.version) _ -> return () run global `catch` \e -> -- This special handler stops "stack: " from being printed before the -- exception case fromException e of Just ec -> exitWith ec Nothing -> do printExceptionStderr e exitFailure -- Vertically combine only the error component of the first argument with the -- error component of the second. vcatErrorHelp :: ParserHelp -> ParserHelp -> ParserHelp vcatErrorHelp (ParserHelp e1 _ _ _ _) (ParserHelp e2 h2 u2 b2 f2) = ParserHelp (vcatChunks [e2, e1]) h2 u2 b2 f2 commandLineHandler :: FilePath -> String -> Bool -> IO (GlobalOptsMonoid, GlobalOpts -> IO ()) commandLineHandler currentDir progName isInterpreter = complicatedOptions Meta.version (Just versionString') VERSION_hpack "stack - The Haskell Tool Stack" "" "stack's documentation is available at https://docs.haskellstack.org/" (globalOpts OuterGlobalOpts) (Just failureCallback) addCommands where failureCallback f args = case stripPrefix "Invalid argument" (fst (renderFailure f "")) of Just _ -> if isInterpreter then parseResultHandler args f else secondaryCommandHandler args f >>= interpreterHandler currentDir args Nothing -> parseResultHandler args f parseResultHandler args f = if isInterpreter then do let hlp = errorHelp $ stringChunk (unwords ["Error executing interpreter command:" , progName , unwords args]) handleParseResult (overFailure (vcatErrorHelp hlp) (Failure f)) else handleParseResult (Failure f) addCommands = do unless isInterpreter (do addBuildCommand' "build" "Build the package(s) in this directory/configuration" buildCmd (buildOptsParser Build) addBuildCommand' "install" "Shortcut for 'build --copy-bins'" buildCmd (buildOptsParser Install) addCommand' "uninstall" "DEPRECATED: This command performs no actions, and is present for documentation only" uninstallCmd (many $ strArgument $ metavar "IGNORED") addBuildCommand' "test" "Shortcut for 'build --test'" buildCmd (buildOptsParser Test) addBuildCommand' "bench" "Shortcut for 'build --bench'" buildCmd (buildOptsParser Bench) addBuildCommand' "haddock" "Shortcut for 'build --haddock'" buildCmd (buildOptsParser Haddock) addCommand' "new" "Create a new project from a template. Run `stack templates' to see available templates." newCmd newOptsParser addCommand' "templates" "List the templates available for `stack new'." templatesCmd (pure ()) addCommand' "init" "Create stack project config from cabal or hpack package specifications" initCmd initOptsParser addCommand' "solver" "Add missing extra-deps to stack project config" solverCmd solverOptsParser addCommand' "setup" "Get the appropriate GHC for your project" setupCmd setupParser addCommand' "path" "Print out handy path information" pathCmd Stack.Path.pathParser addCommand' "unpack" "Unpack one or more packages locally" unpackCmd (some $ strArgument $ metavar "PACKAGE") addCommand' "update" "Update the package index" updateCmd (pure ()) addCommand' "upgrade" "Upgrade to the latest stack" upgradeCmd upgradeOpts addCommand' "upload" "Upload a package to Hackage" uploadCmd (sdistOptsParser True) addCommand' "sdist" "Create source distribution tarballs" sdistCmd (sdistOptsParser False) addCommand' "dot" "Visualize your project's dependency graph using Graphviz dot" dotCmd (dotOptsParser False) -- Default for --external is False. addCommand' "ghc" "Run ghc" execCmd (execOptsParser $ Just ExecGhc) addCommand' "hoogle" ("Run hoogle, the Haskell API search engine. Use 'stack exec' syntax " ++ "to pass Hoogle arguments, e.g. stack hoogle -- --count=20") hoogleCmd ((,,) <$> many (strArgument (metavar "ARG")) <*> boolFlags True "setup" "If needed: install hoogle, build haddocks and generate a hoogle database" idm <*> switch (long "rebuild" <> help "Rebuild the hoogle database")) ) -- These are the only commands allowed in interpreter mode as well addCommand' "exec" "Execute a command" execCmd (execOptsParser Nothing) addGhciCommand' "ghci" "Run ghci in the context of package(s) (experimental)" ghciCmd ghciOptsParser addGhciCommand' "repl" "Run ghci in the context of package(s) (experimental) (alias for 'ghci')" ghciCmd ghciOptsParser addCommand' "runghc" "Run runghc" execCmd (execOptsParser $ Just ExecRunGhc) addCommand' "runhaskell" "Run runghc (alias for 'runghc')" execCmd (execOptsParser $ Just ExecRunGhc) addCommand' "script" "Run a Stack Script" scriptCmd scriptOptsParser unless isInterpreter (do addCommand' "eval" "Evaluate some haskell code inline. Shortcut for 'stack exec ghc -- -e CODE'" evalCmd (evalOptsParser "CODE") addCommand' "clean" "Clean the local packages" cleanCmd cleanOptsParser addCommand' "list-dependencies" "List the dependencies" listDependenciesCmd listDepsOptsParser addCommand' "query" "Query general build information (experimental)" queryCmd (many $ strArgument $ metavar "SELECTOR...") addSubCommands' "ide" "IDE-specific commands" (do addCommand' "packages" "List all available local loadable packages" idePackagesCmd (pure ()) addCommand' "targets" "List all available stack targets" ideTargetsCmd (pure ())) addSubCommands' Docker.dockerCmdName "Subcommands specific to Docker use" (do addCommand' Docker.dockerPullCmdName "Pull latest version of Docker image from registry" dockerPullCmd (pure ()) addCommand' "reset" "Reset the Docker sandbox" dockerResetCmd (switch (long "keep-home" <> help "Do not delete sandbox's home directory")) addCommand' Docker.dockerCleanupCmdName "Clean up Docker images and containers" dockerCleanupCmd dockerCleanupOptsParser) addSubCommands' ConfigCmd.cfgCmdName "Subcommands specific to modifying stack.yaml files" (addCommand' ConfigCmd.cfgCmdSetName "Sets a field in the project's stack.yaml to value" cfgSetCmd configCmdSetParser) addSubCommands' Image.imgCmdName "Subcommands specific to imaging" (addCommand' Image.imgDockerCmdName "Build a Docker image for the project" imgDockerCmd ((,) <$> boolFlags True "build" "building the project before creating the container" idm <*> many (textOption (long "image" <> help "A specific container image name to build")))) addSubCommands' "hpc" "Subcommands specific to Haskell Program Coverage" (addCommand' "report" "Generate unified HPC coverage report from tix files and project targets" hpcReportCmd hpcReportOptsParser) ) where -- addCommand hiding global options addCommand' :: String -> String -> (a -> GlobalOpts -> IO ()) -> Parser a -> AddCommand addCommand' cmd title constr = addCommand cmd title globalFooter constr (globalOpts OtherCmdGlobalOpts) addSubCommands' :: String -> String -> AddCommand -> AddCommand addSubCommands' cmd title = addSubCommands cmd title globalFooter (globalOpts OtherCmdGlobalOpts) -- Additional helper that hides global options and shows build options addBuildCommand' :: String -> String -> (a -> GlobalOpts -> IO ()) -> Parser a -> AddCommand addBuildCommand' cmd title constr = addCommand cmd title globalFooter constr (globalOpts BuildCmdGlobalOpts) -- Additional helper that hides global options and shows some ghci options addGhciCommand' :: String -> String -> (a -> GlobalOpts -> IO ()) -> Parser a -> AddCommand addGhciCommand' cmd title constr = addCommand cmd title globalFooter constr (globalOpts GhciCmdGlobalOpts) globalOpts :: GlobalOptsContext -> Parser GlobalOptsMonoid globalOpts kind = extraHelpOption hide progName (Docker.dockerCmdName ++ "*") Docker.dockerHelpOptName <*> extraHelpOption hide progName (Nix.nixCmdName ++ "*") Nix.nixHelpOptName <*> globalOptsParser currentDir kind (if isInterpreter -- Silent except when errors occur - see #2879 then Just LevelError else Nothing) where hide = kind /= OuterGlobalOpts globalFooter = "Run 'stack --help' for global options that apply to all subcommands." type AddCommand = EitherT (GlobalOpts -> IO ()) (Writer (Mod CommandFields (GlobalOpts -> IO (), GlobalOptsMonoid))) () -- | fall-through to external executables in `git` style if they exist -- (i.e. `stack something` looks for `stack-something` before -- failing with "Invalid argument `something'") secondaryCommandHandler :: [String] -> ParserFailure ParserHelp -> IO (ParserFailure ParserHelp) secondaryCommandHandler args f = -- don't even try when the argument looks like a path or flag if elem pathSeparator cmd || "-" `isPrefixOf` head args then return f else do mExternalExec <- D.findExecutable cmd case mExternalExec of Just ex -> do menv <- getEnvOverride buildPlatform -- TODO show the command in verbose mode -- hPutStrLn stderr $ unwords $ -- ["Running", "[" ++ ex, unwords (tail args) ++ "]"] _ <- runNoLoggingT (exec menv ex (tail args)) return f Nothing -> return $ fmap (vcatErrorHelp (noSuchCmd cmd)) f where -- FIXME this is broken when any options are specified before the command -- e.g. stack --verbosity silent cmd cmd = stackProgName ++ "-" ++ head args noSuchCmd name = errorHelp $ stringChunk ("Auxiliary command not found in path `" ++ name ++ "'") interpreterHandler :: Monoid t => FilePath -> [String] -> ParserFailure ParserHelp -> IO (GlobalOptsMonoid, (GlobalOpts -> IO (), t)) interpreterHandler currentDir args f = do -- args can include top-level config such as --extra-lib-dirs=... (set by -- nix-shell) - we need to find the first argument which is a file, everything -- afterwards is an argument to the script, everything before is an argument -- to Stack (stackArgs, fileArgs) <- spanM (fmap not . D.doesFileExist) args case fileArgs of (file:fileArgs') -> runInterpreterCommand file stackArgs fileArgs' [] -> parseResultHandler (errorCombine (noSuchFile firstArg)) where firstArg = head args spanM _ [] = return ([], []) spanM p xs@(x:xs') = do r <- p x if r then do (ys, zs) <- spanM p xs' return (x:ys, zs) else return ([], xs) -- if the first argument contains a path separator then it might be a file, -- or a Stack option referencing a file. In that case we only show the -- interpreter error message and exclude the command related error messages. errorCombine = if pathSeparator `elem` firstArg then overrideErrorHelp else vcatErrorHelp overrideErrorHelp (ParserHelp e1 _ _ _ _) (ParserHelp _ h2 u2 b2 f2) = ParserHelp e1 h2 u2 b2 f2 parseResultHandler fn = handleParseResult (overFailure fn (Failure f)) noSuchFile name = errorHelp $ stringChunk ("File does not exist or is not a regular file `" ++ name ++ "'") runInterpreterCommand path stackArgs fileArgs = do progName <- getProgName iargs <- getInterpreterArgs path let parseCmdLine = commandLineHandler currentDir progName True separator = if "--" `elem` iargs then [] else ["--"] cmdArgs = stackArgs ++ iargs ++ separator ++ path : fileArgs -- TODO show the command in verbose mode -- hPutStrLn stderr $ unwords $ -- ["Running", "[" ++ progName, unwords cmdArgs ++ "]"] (a,b) <- withArgs cmdArgs parseCmdLine return (a,(b,mempty)) pathCmd :: [Text] -> GlobalOpts -> IO () pathCmd keys go = withBuildConfig go (Stack.Path.path keys) setupCmd :: SetupCmdOpts -> GlobalOpts -> IO () setupCmd sco@SetupCmdOpts{..} go@GlobalOpts{..} = do lc <- loadConfigWithOpts go when (isJust scoUpgradeCabal && nixEnable (configNix (lcConfig lc))) $ do throwIO UpgradeCabalUnusable withUserFileLock go (configStackRoot $ lcConfig lc) $ \lk -> do let getCompilerVersion = loadCompilerVersion go lc runStackTGlobal (lcConfig lc) go $ Docker.reexecWithOptionalContainer (lcProjectRoot lc) Nothing (runStackTGlobal (lcConfig lc) go $ Nix.reexecWithOptionalShell (lcProjectRoot lc) getCompilerVersion $ runStackTGlobal () go $ do (wantedCompiler, compilerCheck, mstack) <- case scoCompilerVersion of Just v -> return (v, MatchMinor, Nothing) Nothing -> do bc <- lcLoadBuildConfig lc globalCompiler return ( view wantedCompilerVersionL bc , configCompilerCheck (lcConfig lc) , Just $ view stackYamlL bc ) let miniConfig = loadMiniConfig (lcConfig lc) runStackTGlobal miniConfig go $ setup sco wantedCompiler compilerCheck mstack ) Nothing (Just $ munlockFile lk) cleanCmd :: CleanOpts -> GlobalOpts -> IO () cleanCmd opts go = withBuildConfigAndLockNoDocker go (const (clean opts)) -- | Helper for build and install commands buildCmd :: BuildOptsCLI -> GlobalOpts -> IO () buildCmd opts go = do when (any (("-prof" `elem`) . either (const []) id . parseArgs Escaping) (boptsCLIGhcOptions opts)) $ do hPutStrLn stderr "Error: When building with stack, you should not use the -prof GHC option" hPutStrLn stderr "Instead, please use --library-profiling and --executable-profiling" hPutStrLn stderr "See: https://github.com/commercialhaskell/stack/issues/1015" exitFailure case boptsCLIFileWatch opts of FileWatchPoll -> fileWatchPoll stderr inner FileWatch -> fileWatch stderr inner NoFileWatch -> inner $ const $ return () where inner setLocalFiles = withBuildConfigAndLock go' $ \lk -> Stack.Build.build setLocalFiles lk opts -- Read the build command from the CLI and enable it to run go' = case boptsCLICommand opts of Test -> set (globalOptsBuildOptsMonoidL.buildOptsMonoidTestsL) (Just True) go Haddock -> set (globalOptsBuildOptsMonoidL.buildOptsMonoidHaddockL) (Just True) go Bench -> set (globalOptsBuildOptsMonoidL.buildOptsMonoidBenchmarksL) (Just True) go Install -> set (globalOptsBuildOptsMonoidL.buildOptsMonoidInstallExesL) (Just True) go Build -> go -- Default case is just Build uninstallCmd :: [String] -> GlobalOpts -> IO () uninstallCmd _ go = withConfigAndLock go $ do $logError "stack does not manage installations in global locations" $logError "The only global mutation stack performs is executable copying" $logError "For the default executable destination, please run 'stack path --local-bin'" -- | Unpack packages to the filesystem unpackCmd :: [String] -> GlobalOpts -> IO () unpackCmd names go = withConfigAndLock go $ do mMiniBuildPlan <- case globalResolver go of Nothing -> return Nothing Just ar -> fmap Just $ do r <- makeConcreteResolver ar case r of ResolverSnapshot snapName -> do config <- view configL let miniConfig = loadMiniConfig config runInnerStackT miniConfig (loadMiniBuildPlan snapName) ResolverCompiler _ -> throwString "Error: unpack does not work with compiler resolvers" ResolverCustom _ _ -> throwString "Error: unpack does not work with custom resolvers" Stack.Fetch.unpackPackages mMiniBuildPlan "." names -- | Update the package index updateCmd :: () -> GlobalOpts -> IO () updateCmd () go = withConfigAndLock go Stack.PackageIndex.updateAllIndices upgradeCmd :: UpgradeOpts -> GlobalOpts -> IO () upgradeCmd upgradeOpts' go = withGlobalConfigAndLock go $ upgrade (globalConfigMonoid go) (globalResolver go) #ifdef USE_GIT_INFO (find (/= "UNKNOWN") [$gitHash]) #else Nothing #endif upgradeOpts' -- | Upload to Hackage uploadCmd :: SDistOpts -> GlobalOpts -> IO () uploadCmd (SDistOpts [] _ _ _ _ _) _ = throwString "Error: To upload the current package, please run 'stack upload .'" uploadCmd sdistOpts go = do let partitionM _ [] = return ([], []) partitionM f (x:xs) = do r <- f x (as, bs) <- partitionM f xs return $ if r then (x:as, bs) else (as, x:bs) (files, nonFiles) <- partitionM D.doesFileExist (sdoptsDirsToWorkWith sdistOpts) (dirs, invalid) <- partitionM D.doesDirectoryExist nonFiles unless (null invalid) $ do hPutStrLn stderr $ "Error: stack upload expects a list sdist tarballs or cabal directories. Can't find " ++ show invalid exitFailure withBuildConfigAndLock go $ \_ -> do config <- view configL getCreds <- liftIO (runOnce (Upload.loadCreds config)) mapM_ (resolveFile' >=> checkSDistTarball sdistOpts) files forM_ files (\file -> do tarFile <- resolveFile' file liftIO $ do creds <- getCreds Upload.upload creds (toFilePath tarFile) when (sdoptsSign sdistOpts) (void $ Sig.sign (sdoptsSignServerUrl sdistOpts) tarFile)) unless (null dirs) $ forM_ dirs $ \dir -> do pkgDir <- resolveDir' dir (tarName, tarBytes, mcabalRevision) <- getSDistTarball (sdoptsPvpBounds sdistOpts) pkgDir checkSDistTarball' sdistOpts tarName tarBytes liftIO $ do creds <- getCreds Upload.uploadBytes creds tarName tarBytes forM_ mcabalRevision $ uncurry $ Upload.uploadRevision creds tarPath <- parseRelFile tarName when (sdoptsSign sdistOpts) (void $ Sig.signTarBytes (sdoptsSignServerUrl sdistOpts) tarPath tarBytes) sdistCmd :: SDistOpts -> GlobalOpts -> IO () sdistCmd sdistOpts go = withBuildConfig go $ do -- No locking needed. -- If no directories are specified, build all sdist tarballs. dirs' <- if null (sdoptsDirsToWorkWith sdistOpts) then liftM Map.keys getLocalPackages else mapM resolveDir' (sdoptsDirsToWorkWith sdistOpts) forM_ dirs' $ \dir -> do (tarName, tarBytes, _mcabalRevision) <- getSDistTarball (sdoptsPvpBounds sdistOpts) dir distDir <- distDirFromDir dir tarPath <- (distDir ) <$> parseRelFile tarName ensureDir (parent tarPath) liftIO $ L.writeFile (toFilePath tarPath) tarBytes checkSDistTarball sdistOpts tarPath $logInfo $ "Wrote sdist tarball to " <> T.pack (toFilePath tarPath) when (sdoptsSign sdistOpts) (void $ Sig.sign (sdoptsSignServerUrl sdistOpts) tarPath) -- | Execute a command. execCmd :: ExecOpts -> GlobalOpts -> IO () execCmd ExecOpts {..} go@GlobalOpts{..} = case eoExtra of ExecOptsPlain -> do (cmd, args) <- case (eoCmd, eoArgs) of (ExecCmd cmd, args) -> return (cmd, args) (ExecGhc, args) -> return ("ghc", args) (ExecRunGhc, args) -> return ("runghc", args) lc <- liftIO $ loadConfigWithOpts go withUserFileLock go (configStackRoot $ lcConfig lc) $ \lk -> do let getCompilerVersion = loadCompilerVersion go lc runStackTGlobal (lcConfig lc) go $ Docker.reexecWithOptionalContainer (lcProjectRoot lc) -- Unlock before transferring control away, whether using docker or not: (Just $ munlockFile lk) (runStackTGlobal (lcConfig lc) go $ do config <- view configL menv <- liftIO $ configEnvOverride config plainEnvSettings Nix.reexecWithOptionalShell (lcProjectRoot lc) getCompilerVersion (runStackTGlobal (lcConfig lc) go $ exec menv cmd args)) Nothing Nothing -- Unlocked already above. ExecOptsEmbellished {..} -> withBuildConfigAndLock go $ \lk -> do let targets = concatMap words eoPackages unless (null targets) $ Stack.Build.build (const $ return ()) lk defaultBuildOptsCLI { boptsCLITargets = map T.pack targets } config <- view configL menv <- liftIO $ configEnvOverride config eoEnvSettings -- Add RTS options to arguments let argsWithRts args = if null eoRtsOptions then args :: [String] else args ++ ["+RTS"] ++ eoRtsOptions ++ ["-RTS"] (cmd, args) <- case (eoCmd, argsWithRts eoArgs) of (ExecCmd cmd, args) -> return (cmd, args) (ExecGhc, args) -> getGhcCmd "" menv eoPackages args -- NOTE: this won't currently work for GHCJS, because it doesn't have -- a runghcjs binary. It probably will someday, though. (ExecRunGhc, args) -> getGhcCmd "run" menv eoPackages args munlockFile lk -- Unlock before transferring control away. exec menv cmd args where -- return the package-id of the first package in GHC_PACKAGE_PATH getPkgId menv wc name = do mId <- findGhcPkgField menv wc [] name "id" case mId of Just i -> return (head $ words (T.unpack i)) -- should never happen as we have already installed the packages _ -> liftIO $ do hPutStrLn stderr ("Could not find package id of package " ++ name) exitFailure getPkgOpts menv wc pkgs = do ids <- mapM (getPkgId menv wc) pkgs return $ map ("-package-id=" ++) ids getGhcCmd prefix menv pkgs args = do wc <- view $ actualCompilerVersionL.whichCompilerL pkgopts <- getPkgOpts menv wc pkgs return (prefix ++ compilerExeName wc, pkgopts ++ args) -- | Evaluate some haskell code inline. evalCmd :: EvalOpts -> GlobalOpts -> IO () evalCmd EvalOpts {..} go@GlobalOpts {..} = execCmd execOpts go where execOpts = ExecOpts { eoCmd = ExecGhc , eoArgs = ["-e", evalArg] , eoExtra = evalExtra } -- | Run GHCi in the context of a project. ghciCmd :: GhciOpts -> GlobalOpts -> IO () ghciCmd ghciOpts go@GlobalOpts{..} = withBuildConfigAndLock go $ \lk -> do munlockFile lk -- Don't hold the lock while in the GHCI. bopts <- view buildOptsL -- override env so running of tests and benchmarks is disabled let boptsLocal = bopts { boptsTestOpts = (boptsTestOpts bopts) { toDisableRun = True } , boptsBenchmarkOpts = (boptsBenchmarkOpts bopts) { beoDisableRun = True } } local (set buildOptsL boptsLocal) (ghci ghciOpts) -- | List packages in the project. idePackagesCmd :: () -> GlobalOpts -> IO () idePackagesCmd () go = withBuildConfig go IDE.listPackages -- | List targets in the project. ideTargetsCmd :: () -> GlobalOpts -> IO () ideTargetsCmd () go = withBuildConfig go IDE.listTargets -- | Pull the current Docker image. dockerPullCmd :: () -> GlobalOpts -> IO () dockerPullCmd _ go@GlobalOpts{..} = do lc <- liftIO $ loadConfigWithOpts go -- TODO: can we eliminate this lock if it doesn't touch ~/.stack/? withUserFileLock go (configStackRoot $ lcConfig lc) $ \_ -> runStackTGlobal (lcConfig lc) go $ Docker.preventInContainer Docker.pull -- | Reset the Docker sandbox. dockerResetCmd :: Bool -> GlobalOpts -> IO () dockerResetCmd keepHome go@GlobalOpts{..} = do lc <- liftIO (loadConfigWithOpts go) -- TODO: can we eliminate this lock if it doesn't touch ~/.stack/? withUserFileLock go (configStackRoot $ lcConfig lc) $ \_ -> runStackTGlobal (lcConfig lc) go $ Docker.preventInContainer $ Docker.reset (lcProjectRoot lc) keepHome -- | Cleanup Docker images and containers. dockerCleanupCmd :: Docker.CleanupOpts -> GlobalOpts -> IO () dockerCleanupCmd cleanupOpts go@GlobalOpts{..} = do lc <- liftIO $ loadConfigWithOpts go -- TODO: can we eliminate this lock if it doesn't touch ~/.stack/? withUserFileLock go (configStackRoot $ lcConfig lc) $ \_ -> runStackTGlobal (lcConfig lc) go $ Docker.preventInContainer $ Docker.cleanup cleanupOpts cfgSetCmd :: ConfigCmd.ConfigCmdSet -> GlobalOpts -> IO () cfgSetCmd co go@GlobalOpts{..} = withMiniConfigAndLock go (cfgCmdSet go co) imgDockerCmd :: (Bool, [Text]) -> GlobalOpts -> IO () imgDockerCmd (rebuild,images) go@GlobalOpts{..} = do mProjectRoot <- lcProjectRoot <$> loadConfigWithOpts go withBuildConfigExt False go Nothing (\lk -> do when rebuild $ Stack.Build.build (const (return ())) lk defaultBuildOptsCLI Image.stageContainerImageArtifacts mProjectRoot images) (Just $ Image.createContainerImageFromStage mProjectRoot images) -- | Project initialization initCmd :: InitOpts -> GlobalOpts -> IO () initCmd initOpts go = do pwd <- getCurrentDir withMiniConfigAndLock go (initProject IsInitCmd pwd initOpts (globalResolver go)) -- | Create a project directory structure and initialize the stack config. newCmd :: (NewOpts,InitOpts) -> GlobalOpts -> IO () newCmd (newOpts,initOpts) go@GlobalOpts{..} = withMiniConfigAndLock go $ do dir <- new newOpts (forceOverwrite initOpts) initProject IsNewCmd dir initOpts globalResolver -- | List the available templates. templatesCmd :: () -> GlobalOpts -> IO () templatesCmd _ go@GlobalOpts{..} = withConfigAndLock go listTemplates -- | Fix up extra-deps for a project solverCmd :: Bool -- ^ modify stack.yaml automatically? -> GlobalOpts -> IO () solverCmd fixStackYaml go = withBuildConfigAndLock go (\_ -> solveExtraDeps fixStackYaml) -- | Visualize dependencies dotCmd :: DotOpts -> GlobalOpts -> IO () dotCmd dotOpts go = withBuildConfigDot dotOpts go $ dot dotOpts -- | List the dependencies listDependenciesCmd :: ListDepsOpts -> GlobalOpts -> IO () listDependenciesCmd opts go = withBuildConfigDot (listDepsDotOpts opts) go $ listDependencies opts -- Plumbing for --test and --bench flags withBuildConfigDot :: DotOpts -> GlobalOpts -> StackT EnvConfig IO () -> IO () withBuildConfigDot opts go f = withBuildConfig go' f where go' = (if dotTestTargets opts then set (globalOptsBuildOptsMonoidL.buildOptsMonoidTestsL) (Just True) else id) $ (if dotBenchTargets opts then set (globalOptsBuildOptsMonoidL.buildOptsMonoidBenchmarksL) (Just True) else id) go -- | Query build information queryCmd :: [String] -> GlobalOpts -> IO () queryCmd selectors go = withBuildConfig go $ queryBuildInfo $ map T.pack selectors -- | Generate a combined HPC report hpcReportCmd :: HpcReportOpts -> GlobalOpts -> IO () hpcReportCmd hropts go = withBuildConfig go $ generateHpcReportForTargets hropts data MainException = InvalidReExecVersion String String | UpgradeCabalUnusable deriving (Typeable) instance Exception MainException instance Show MainException where show (InvalidReExecVersion expected actual) = concat [ "When re-executing '" , stackProgName , "' in a container, the incorrect version was found\nExpected: " , expected , "; found: " , actual] show UpgradeCabalUnusable = "--upgrade-cabal cannot be used when nix is activated" stack-1.5.1/test/integration/IntegrationSpec.hs0000644000000000000000000001223513135651271017754 0ustar0000000000000000{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE ScopedTypeVariables #-} import Control.Applicative import Control.Arrow import Control.Concurrent.Async import Control.Exception import Control.Monad import Control.Monad.IO.Class import Control.Monad.Trans.Resource import qualified Data.ByteString.Lazy as L import Data.Char import Data.Conduit import Data.Conduit.Binary (sinkLbs) import Data.Conduit.Filesystem (sourceDirectoryDeep) import qualified Data.Conduit.List as CL import Data.Conduit.Process import Data.List (isSuffixOf, stripPrefix, sort) import qualified Data.Map as Map import Data.Maybe (fromMaybe) import Data.Text.Encoding.Error (lenientDecode) import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Encoding as TL import Data.Typeable import Prelude -- Fix redundant import warnings import System.Directory import System.Environment import System.Exit import System.FilePath import System.IO.Temp import System.PosixCompat.Files import Test.Hspec main :: IO () main = do currDir <- canonicalizePath "test/integration" let findExe name = do mexe <- findExecutable name case mexe of Nothing -> error $ name ++ " not found on PATH" Just exe -> return exe runghc <- findExe "runghc" stack <- findExe "stack" let testDir = currDir "tests" tests <- getDirectoryContents testDir >>= filterM (hasTest testDir) . sort envOrig <- getEnvironment withSystemTempDirectory "stack-integration-home" $ \newHome -> do let env' = Map.toList $ Map.insert "STACK_EXE" stack $ Map.insert "HOME" newHome $ Map.insert "APPDATA" newHome $ Map.delete "GHC_PACKAGE_PATH" $ Map.delete "STACK_ROOT" $ Map.fromList $ map (first (map toUpper)) envOrig defaultStackRoot <- getAppUserDataDirectory "stack" let origStackRoot = fromMaybe defaultStackRoot (lookup "STACK_ROOT" envOrig) hspec $ mapM_ (test runghc env' currDir defaultStackRoot origStackRoot newHome) tests hasTest :: FilePath -> FilePath -> IO Bool hasTest root dir = doesFileExist $ root dir "Main.hs" test :: FilePath -- ^ runghc -> [(String, String)] -- ^ env -> FilePath -- ^ currdir -> FilePath -- ^ defaultStackRoot -> FilePath -- ^ origStackRoot -> FilePath -- ^ newHome -> String -> Spec test runghc env' currDir defaultStackRoot origStackRoot newHome name = it name $ withDir $ \dir -> do newHomeExists <- doesDirectoryExist newHome when newHomeExists (removeDirectoryRecursive newHome) let newStackRoot = newHome takeFileName defaultStackRoot copyTree toCopyRoot origStackRoot newStackRoot writeFile (newStackRoot "config.yaml") "system-ghc: true" let testDir = currDir "tests" name mainFile = testDir "Main.hs" libDir = currDir "lib" cp = (proc runghc [ "-clear-package-db" , "-global-package-db" , "-i" ++ libDir , mainFile ]) { cwd = Just dir , env = Just env' } copyTree (const True) (testDir "files") dir (ClosedStream, outSrc, errSrc, sph) <- streamingProcess cp (out, err, ec) <- runConcurrently $ (,,) <$> Concurrently (outSrc $$ sinkLbs) <*> Concurrently (errSrc $$ sinkLbs) <*> Concurrently (waitForStreamingProcess sph) when (ec /= ExitSuccess) $ throwIO $ TestFailure out err ec where withDir = withSystemTempDirectory ("stack-integration-" ++ name) data TestFailure = TestFailure L.ByteString L.ByteString ExitCode deriving Typeable instance Show TestFailure where show (TestFailure out err ec) = concat [ "Exited with " ++ show ec , "\n\nstdout:\n" , toStr out , "\n\nstderr:\n" , toStr err ] where toStr = TL.unpack . TL.decodeUtf8With lenientDecode instance Exception TestFailure copyTree :: (FilePath -> Bool) -> FilePath -> FilePath -> IO () copyTree toCopy src dst = runResourceT (sourceDirectoryDeep False src $$ CL.mapM_ go) `catch` \(_ :: IOException) -> return () where go srcfp = when (toCopy srcfp) $ liftIO $ do Just suffix <- return $ stripPrefix src srcfp let dstfp = dst ++ "/" ++ suffix createDirectoryIfMissing True $ takeDirectory dstfp createSymbolicLink srcfp dstfp `catch` \(_ :: IOException) -> copyFile srcfp dstfp -- for Windows toCopyRoot :: FilePath -> Bool toCopyRoot srcfp = any (`isSuffixOf` srcfp) -- FIXME command line parameters to control how many of these get -- copied, trade-off of runtime/bandwidth vs isolation of tests [ ".tar" , ".xz" -- , ".gz" , ".7z.exe" , "00-index.cache" ] stack-1.5.1/test/integration/lib/StackTest.hs0000644000000000000000000001374713135651621017341 0ustar0000000000000000module StackTest where import Control.Monad import Control.Monad.IO.Class import Control.Monad.Trans.Reader import Control.Concurrent import Control.Exception import Data.List (intercalate) import System.Environment import System.FilePath import System.Directory import System.IO import System.IO.Error import System.Process import System.Exit import System.Info (arch, os) run' :: FilePath -> [String] -> IO ExitCode run' cmd args = do logInfo $ "Running: " ++ cmd ++ " " ++ unwords (map showProcessArgDebug args) (Nothing, Nothing, Nothing, ph) <- createProcess (proc cmd args) waitForProcess ph run :: FilePath -> [String] -> IO () run cmd args = do ec <- run' cmd args unless (ec == ExitSuccess) $ error $ "Exited with exit code: " ++ show ec stack' :: [String] -> IO ExitCode stack' args = do stack <- getEnv "STACK_EXE" run' stack args stack :: [String] -> IO () stack args = do ec <- stack' args unless (ec == ExitSuccess) $ error $ "Exited with exit code: " ++ show ec stackErr :: [String] -> IO () stackErr args = do ec <- stack' args when (ec == ExitSuccess) $ error "stack was supposed to fail, but didn't" type Repl = ReaderT ReplConnection IO data ReplConnection = ReplConnection { replStdin :: Handle , replStdout :: Handle } nextPrompt :: Repl () nextPrompt = do (ReplConnection _ handle) <- ask c <- liftIO $ hGetChar handle if c == '>' then do _ <- liftIO $ hGetChar handle return () else nextPrompt replCommand :: String -> Repl () replCommand cmd = do (ReplConnection input _) <- ask liftIO $ hPutStrLn input cmd replGetLine :: Repl String replGetLine = fmap replStdout ask >>= liftIO . hGetLine replGetChar :: Repl Char replGetChar = fmap replStdout ask >>= liftIO . hGetChar runRepl :: FilePath -> [String] -> ReaderT ReplConnection IO () -> IO ExitCode runRepl cmd args actions = do logInfo $ "Running: " ++ cmd ++ " " ++ unwords (map showProcessArgDebug args) (Just rStdin, Just rStdout, Just rStderr, ph) <- createProcess (proc cmd args) { std_in = CreatePipe , std_out = CreatePipe , std_err = CreatePipe } hSetBuffering rStdin NoBuffering hSetBuffering rStdout NoBuffering hSetBuffering rStderr NoBuffering forkIO $ withFile "/tmp/stderr" WriteMode $ \err -> forever $ catch (hGetChar rStderr >>= hPutChar err) $ \e -> unless (isEOFError e) $ throw e runReaderT (nextPrompt >> actions) (ReplConnection rStdin rStdout) waitForProcess ph repl :: [String] -> Repl () -> IO () repl args action = do stack <- getEnv "STACK_EXE" ec <- runRepl stack ("repl":args) action unless (ec == ExitSuccess) $ return () -- TODO: Understand why the exit code is 1 despite running GHCi tests -- successfully. -- else error $ "Exited with exit code: " ++ show ec -- | Run stack with arguments and apply a check to the resulting -- stderr output if the process succeeded. stackCheckStderr :: [String] -> (String -> IO ()) -> IO () stackCheckStderr args check = do stack <- getEnv "STACK_EXE" logInfo $ "Running: " ++ stack ++ " " ++ unwords (map showProcessArgDebug args) (ec, _, err) <- readProcessWithExitCode stack args "" hPutStr stderr err if ec /= ExitSuccess then error $ "Exited with exit code: " ++ show ec else check err doesNotExist :: FilePath -> IO () doesNotExist fp = do logInfo $ "doesNotExist " ++ fp exists <- doesFileOrDirExist fp case exists of (Right msg) -> error msg (Left _) -> return () doesExist :: FilePath -> IO () doesExist fp = do logInfo $ "doesExist " ++ fp exists <- doesFileOrDirExist fp case exists of (Right msg) -> return () (Left _) -> error "No file or directory exists" doesFileOrDirExist :: FilePath -> IO (Either () String) doesFileOrDirExist fp = do isFile <- doesFileExist fp if isFile then return (Right ("File exists: " ++ fp)) else do isDir <- doesDirectoryExist fp if isDir then return (Right ("Directory exists: " ++ fp)) else return (Left ()) copy :: FilePath -> FilePath -> IO () copy src dest = do logInfo ("Copy " ++ show src ++ " to " ++ show dest) System.Directory.copyFile src dest fileContentsMatch :: FilePath -> FilePath -> IO () fileContentsMatch f1 f2 = do doesExist f1 doesExist f2 f1Contents <- readFile f1 f2Contents <- readFile f2 unless (f1Contents == f2Contents) $ error ("contents do not match for " ++ show f1 ++ " " ++ show f2) logInfo :: String -> IO () logInfo = hPutStrLn stderr -- TODO: use stack's process running utilties? (better logging) -- for now just copy+modifying this one from System.Process.Log -- | Show a process arg including speechmarks when necessary. Just for -- debugging purposes, not functionally important. showProcessArgDebug :: String -> String showProcessArgDebug x | any special x = show x | otherwise = x where special '"' = True special ' ' = True special _ = False -- | Extension of executables exeExt = if isWindows then ".exe" else "" -- | Is the OS Windows? isWindows = os == "mingw32" -- | Is the OS Alpine Linux? getIsAlpine = doesFileExist "/etc/alpine-release" -- | Is the architecture ARM? isARM = arch == "arm" -- | To avoid problems with GHC version mismatch when a new LTS major -- version is released, pass this argument to @stack@ when running in -- a global context. The LTS major version here should match that of -- the main @stack.yaml@ (and ordinarily be the `.0` minor version). -- -- NOTE: currently using lts-8.22 instead of lts-8.0 because the `cyclic-test-deps` integration test is broken with lts-8.0 because a hackage metadata revision invalidated the snapshot (snapshot has `test-framework-quickcheck2-0.3.0.3` and `QuickCheck-2.9.2`, which used to be fine, but now test-framework-quickcheck2 was revised to have a `QuickCheck < 2.8` constraint). defaultResolverArg = "--resolver=lts-8.22" stack-1.5.1/src/test/Test.hs0000644000000000000000000000011612546477354014051 0ustar0000000000000000import Test.Hspec (hspec) import Spec (spec) main :: IO () main = hspec spec stack-1.5.1/src/test/Spec.hs0000644000000000000000000000010513135652051014002 0ustar0000000000000000{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --module-name=Spec #-} stack-1.5.1/src/test/Stack/BuildPlanSpec.hs0000644000000000000000000001171613135652051016654 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} module Stack.BuildPlanSpec where import Stack.BuildPlan import Control.Monad.Logger import Control.Exception hiding (try) import Control.Monad.Catch (try) import Data.Monoid import qualified Data.Map as Map import qualified Data.Set as Set import Prelude -- Fix redundant import warnings import System.Directory import System.Environment import System.IO.Temp (withSystemTempDirectory) import Test.Hspec import Stack.Config import Stack.Types.BuildPlan import Stack.Types.PackageName import Stack.Types.Version import Stack.Types.Config import Stack.Types.Compiler import Stack.Types.StackT setup :: IO () setup = unsetEnv "STACK_YAML" main :: IO () main = hspec spec spec :: Spec spec = beforeAll setup $ do let logLevel = LevelDebug let loadConfig' = runStackT () logLevel True False ColorAuto False (loadConfig mempty Nothing SYLDefault) let loadBuildConfigRest = runStackT () logLevel True False ColorAuto False let inTempDir action = do currentDirectory <- getCurrentDirectory withSystemTempDirectory "Stack_BuildPlanSpec" $ \tempDir -> do let enterDir = setCurrentDirectory tempDir let exitDir = setCurrentDirectory currentDirectory bracket_ enterDir exitDir action it "finds missing transitive dependencies #159" $ inTempDir $ do -- Note: this test is somewhat fragile, depending on packages on -- Hackage remaining in a certain state. If it fails, confirm that -- github still depends on failure. writeFile "stack.yaml" "resolver: lts-2.9" LoadConfig{..} <- loadConfig' bconfig <- loadBuildConfigRest (lcLoadBuildConfig Nothing) runStackT bconfig logLevel True False ColorAuto False $ do mbp <- loadMiniBuildPlan $ LTS 2 9 eres <- try $ resolveBuildPlan mbp (const False) (Map.fromList [ ($(mkPackageName "github"), Set.empty) ]) case eres of Left (UnknownPackages _ unknown _) -> do case Map.lookup $(mkPackageName "github") unknown of Nothing -> error "doesn't list github as unknown" Just _ -> return () {- Currently not implemented, see: https://github.com/fpco/stack/issues/159#issuecomment-107809418 case Map.lookup $(mkPackageName "failure") unknown of Nothing -> error "failure not listed" Just _ -> return () -} _ -> error $ "Unexpected result from resolveBuildPlan: " ++ show eres return () describe "shadowMiniBuildPlan" $ do let version = $(mkVersion "1.0.0") -- unimportant for this test pn = either throw id . parsePackageNameFromString mkMPI deps = MiniPackageInfo { mpiVersion = version , mpiFlags = Map.empty , mpiGhcOptions = [] , mpiPackageDeps = Set.fromList $ map pn $ words deps , mpiToolDeps = Set.empty , mpiExes = Set.empty , mpiHasLibrary = True , mpiGitSHA1 = Nothing } go x y = (pn x, mkMPI y) resourcet = go "resourcet" "" conduit = go "conduit" "resourcet" conduitExtra = go "conduit-extra" "conduit" text = go "text" "" attoparsec = go "attoparsec" "text" aeson = go "aeson" "text attoparsec" mkMBP pkgs = MiniBuildPlan { mbpCompilerVersion = GhcVersion version , mbpPackages = Map.fromList pkgs } mbpAll = mkMBP [resourcet, conduit, conduitExtra, text, attoparsec, aeson] test name input shadowed output extra = it name $ const $ shadowMiniBuildPlan input (Set.fromList $ map pn $ words shadowed) `shouldBe` (output, Map.fromList extra) test "no shadowing" mbpAll "" mbpAll [] test "shadow something that isn't there" mbpAll "does-not-exist" mbpAll [] test "shadow a leaf" mbpAll "conduit-extra" (mkMBP [resourcet, conduit, text, attoparsec, aeson]) [] test "shadow direct dep" mbpAll "conduit" (mkMBP [resourcet, text, attoparsec, aeson]) [conduitExtra] test "shadow deep dep" mbpAll "resourcet" (mkMBP [text, attoparsec, aeson]) [conduit, conduitExtra] test "shadow deep dep and leaf" mbpAll "resourcet aeson" (mkMBP [text, attoparsec]) [conduit, conduitExtra] test "shadow deep dep and direct dep" mbpAll "resourcet conduit" (mkMBP [text, attoparsec, aeson]) [conduitExtra] stack-1.5.1/src/test/Stack/Build/ExecuteSpec.hs0000644000000000000000000000020513135652051017432 0ustar0000000000000000module Stack.Build.ExecuteSpec (main, spec) where import Test.Hspec main :: IO () main = hspec spec spec :: Spec spec = return () stack-1.5.1/src/test/Stack/Build/TargetSpec.hs0000644000000000000000000000221013135652051017254 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Stack.Build.TargetSpec (main, spec) where import qualified Data.Text as T import Stack.Build.Target import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.Version import Stack.Types.Package import Test.Hspec main :: IO () main = hspec spec spec :: Spec spec = do describe "parseRawTarget" $ do let test s e = it s $ parseRawTarget (T.pack s) `shouldBe` e test "foobar" $ Just $ RTPackage $(mkPackageName "foobar") test "foobar-1.2.3" $ Just $ RTPackageIdentifier $ PackageIdentifier $(mkPackageName "foobar") $(mkVersion "1.2.3") test "./foobar" Nothing test "foobar/" Nothing test "/foobar" Nothing test ":some-exe" $ Just $ RTComponent "some-exe" test "foobar:some-exe" $ Just $ RTPackageComponent $(mkPackageName "foobar") $ UnresolvedComponent "some-exe" test "foobar:exe:some-exe" $ Just $ RTPackageComponent $(mkPackageName "foobar") $ ResolvedComponent $ CExe "some-exe" stack-1.5.1/src/test/Stack/ConfigSpec.hs0000644000000000000000000001342013135652051016201 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} module Stack.ConfigSpec where import Control.Applicative import Control.Exception import Control.Monad.Logger import Data.Aeson.Extended import Data.Either import Data.Maybe import Data.Monoid import Data.Yaml import Path import Path.IO import Prelude -- Fix redundant import warnings import Stack.Config import Stack.Types.Config import Stack.Types.StackT import System.Directory import System.Environment import System.IO.Temp (withSystemTempDirectory) import Test.Hspec sampleConfig :: String sampleConfig = "resolver: lts-2.10\n" ++ "packages: ['.']\n" buildOptsConfig :: String buildOptsConfig = "resolver: lts-2.10\n" ++ "packages: ['.']\n" ++ "build:\n" ++ " library-profiling: true\n" ++ " executable-profiling: true\n" ++ " haddock: true\n" ++ " haddock-deps: true\n" ++ " copy-bins: true\n" ++ " prefetch: true\n" ++ " force-dirty: true\n" ++ " keep-going: true\n" ++ " test: true\n" ++ " test-arguments:\n" ++ " rerun-tests: true\n" ++ " additional-args: ['-fprof']\n" ++ " coverage: true\n" ++ " no-run-tests: true\n" ++ " bench: true\n" ++ " benchmark-opts:\n" ++ " benchmark-arguments: -O2\n" ++ " no-run-benchmarks: true\n" ++ " reconfigure: true\n" ++ " cabal-verbose: true\n" stackDotYaml :: Path Rel File stackDotYaml = $(mkRelFile "stack.yaml") setup :: IO () setup = unsetEnv "STACK_YAML" noException :: Selector SomeException noException = const False spec :: Spec spec = beforeAll setup $ do let logLevel = LevelDebug -- TODO(danburton): not use inTempDir let inTempDir action = do currentDirectory <- getCurrentDirectory withSystemTempDirectory "Stack_ConfigSpec" $ \tempDir -> do let enterDir = setCurrentDirectory tempDir let exitDir = setCurrentDirectory currentDirectory bracket_ enterDir exitDir action -- TODO(danburton): a safer version of this? let withEnvVar name newValue action = do originalValue <- fromMaybe "" <$> lookupEnv name let setVar = setEnv name newValue let resetVar = setEnv name originalValue bracket_ setVar resetVar action describe "loadConfig" $ do let loadConfig' = runStackT () logLevel True False ColorAuto False (loadConfig mempty Nothing SYLDefault) let loadBuildConfigRest = runStackT () logLevel True False ColorAuto False -- TODO(danburton): make sure parent dirs also don't have config file it "works even if no config file exists" $ example $ do _config <- loadConfig' return () it "works with a blank config file" $ inTempDir $ do writeFile (toFilePath stackDotYaml) "" -- TODO(danburton): more specific test for exception loadConfig' `shouldThrow` anyException it "parses build config options" $ inTempDir $ do writeFile (toFilePath stackDotYaml) buildOptsConfig BuildOpts{..} <- configBuild . lcConfig <$> loadConfig' boptsLibProfile `shouldBe` True boptsExeProfile `shouldBe` True boptsHaddock `shouldBe` True boptsHaddockDeps `shouldBe` Just True boptsInstallExes `shouldBe` True boptsPreFetch `shouldBe` True boptsKeepGoing `shouldBe` Just True boptsForceDirty `shouldBe` True boptsTests `shouldBe` True boptsTestOpts `shouldBe` TestOpts {toRerunTests = True ,toAdditionalArgs = ["-fprof"] ,toCoverage = True ,toDisableRun = True} boptsBenchmarks `shouldBe` True boptsBenchmarkOpts `shouldBe` BenchmarkOpts {beoAdditionalArgs = Just "-O2" ,beoDisableRun = True} boptsReconfigure `shouldBe` True boptsCabalVerbose `shouldBe` True it "finds the config file in a parent directory" $ inTempDir $ do writeFile (toFilePath stackDotYaml) sampleConfig parentDir <- getCurrentDirectory >>= parseAbsDir let childDir = "child" createDirectory childDir setCurrentDirectory childDir LoadConfig{..} <- loadConfig' bc <- loadBuildConfigRest (lcLoadBuildConfig Nothing) view projectRootL bc `shouldBe` parentDir it "respects the STACK_YAML env variable" $ inTempDir $ do withSystemTempDir "config-is-here" $ \dir -> do let stackYamlFp = toFilePath (dir stackDotYaml) writeFile stackYamlFp sampleConfig withEnvVar "STACK_YAML" stackYamlFp $ do LoadConfig{..} <- loadConfig' BuildConfig{..} <- loadBuildConfigRest (lcLoadBuildConfig Nothing) bcStackYaml `shouldBe` dir stackDotYaml parent bcStackYaml `shouldBe` dir it "STACK_YAML can be relative" $ inTempDir $ do parentDir <- getCurrentDirectory >>= parseAbsDir let childRel = $(mkRelDir "child") yamlRel = childRel $(mkRelFile "some-other-name.config") yamlAbs = parentDir yamlRel createDirectoryIfMissing True $ toFilePath $ parent yamlAbs writeFile (toFilePath yamlAbs) "resolver: ghc-7.8" withEnvVar "STACK_YAML" (toFilePath yamlRel) $ do LoadConfig{..} <- loadConfig' BuildConfig{..} <- loadBuildConfigRest (lcLoadBuildConfig Nothing) bcStackYaml `shouldBe` yamlAbs describe "defaultConfigYaml" $ it "is parseable" $ \_ -> do curDir <- getCurrentDir let parsed :: Either String (Either String (WithJSONWarnings ConfigMonoid)) parsed = parseEither (parseConfigMonoid curDir) <$> decodeEither defaultConfigYaml case parsed of Right (Right _) -> return () :: IO () _ -> fail "Failed to parse default config yaml" stack-1.5.1/src/test/Stack/DotSpec.hs0000644000000000000000000001263313135652051015527 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-} -- | Test suite for Stack.Dot module Stack.DotSpec where import Control.Monad (filterM) import Data.Foldable as F import Data.Functor.Identity import Data.List ((\\)) import qualified Data.Map as Map import Data.Maybe (fromMaybe) import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text) import Distribution.License (License (BSD3)) import Stack.Types.PackageName import Stack.Types.Version import Test.Hspec import Test.Hspec.QuickCheck (prop) import Test.QuickCheck (forAll,choose,Gen) import Stack.Dot dummyPayload :: DotPayload dummyPayload = DotPayload (parseVersionFromString "0.0.0.0") (Just BSD3) spec :: Spec spec = do let graph = Map.mapKeys pkgName . fmap (\p -> (Set.map pkgName p, dummyPayload)) . Map.fromList $ [("one",Set.fromList ["base","free"]) ,("two",Set.fromList ["base","free","mtl","transformers","one"]) ] describe "Stack.Dot" $ do it "does nothing if depth is 0" $ resolveDependencies (Just 0) graph stubLoader `shouldBe` return graph it "with depth 1, more dependencies are resolved" $ do let graph' = Map.insert (pkgName "cycle") (Set.singleton (pkgName "cycle"), dummyPayload) graph resultGraph = runIdentity (resolveDependencies (Just 0) graph stubLoader) resultGraph' = runIdentity (resolveDependencies (Just 1) graph' stubLoader) Map.size resultGraph < Map.size resultGraph' `shouldBe` True it "cycles are ignored" $ do let graph' = Map.insert (pkgName "cycle") (Set.singleton (pkgName "cycle"), dummyPayload) graph resultGraph = resolveDependencies Nothing graph stubLoader resultGraph' = resolveDependencies Nothing graph' stubLoader fmap Map.size resultGraph' `shouldBe` fmap ((+1) . Map.size) resultGraph let graphElem e = Set.member e . Set.unions . Map.elems prop "requested packages are pruned" $ do let resolvedGraph = runIdentity (resolveDependencies Nothing graph stubLoader) allPackages g = Set.map show (Map.keysSet g `Set.union` F.fold (fmap fst g)) forAll (sublistOf (Set.toList (allPackages resolvedGraph))) $ \toPrune -> let pruned = pruneGraph [pkgName "one", pkgName "two"] toPrune resolvedGraph in Set.null (allPackages pruned `Set.intersection` Set.fromList toPrune) prop "pruning removes orhpans" $ do let resolvedGraph = runIdentity (resolveDependencies Nothing graph stubLoader) allPackages g = Set.map show (Map.keysSet g `Set.union` F.fold (fmap fst g)) orphans g = Map.filterWithKey (\k _ -> not (graphElem k g)) g forAll (sublistOf (Set.toList (allPackages resolvedGraph))) $ \toPrune -> let pruned = pruneGraph [pkgName "one", pkgName "two"] toPrune resolvedGraph in null (Map.keys (orphans (fmap fst pruned)) \\ [pkgName "one", pkgName "two"]) {- Helper functions below -} -- Backport from QuickCheck 2.8 to 2.7.6 sublistOf :: [a] -> Gen [a] sublistOf = filterM (\_ -> choose (False, True)) -- Unsafe internal helper to create a package name pkgName :: Text -> PackageName pkgName = fromMaybe failure . parsePackageName where failure = error "Internal error during package name creation in DotSpec.pkgName" -- Stub, simulates the function to load package dependecies stubLoader :: PackageName -> Identity (Set PackageName, DotPayload) stubLoader name = return . (, dummyPayload) . Set.fromList . map pkgName $ case show name of "StateVar" -> ["stm","transformers"] "array" -> [] "bifunctors" -> ["semigroupoids","semigroups","tagged"] "binary" -> ["array","bytestring","containers"] "bytestring" -> ["deepseq","ghc-prim","integer-gmp"] "comonad" -> ["containers","contravariant","distributive" ,"semigroups","tagged","transformers","transformers-compat" ] "cont" -> ["StateVar","semigroups","transformers","transformers-compat","void"] "containers" -> ["array","deepseq","ghc-prim"] "deepseq" -> ["array"] "distributive" -> ["ghc-prim","tagged","transformers","transformers-compat"] "free" -> ["bifunctors","comonad","distributive","mtl" ,"prelude-extras","profunctors","semigroupoids" ,"semigroups","template-haskell","transformers" ] "ghc" -> [] "hashable" -> ["bytestring","ghc-prim","integer-gmp","text"] "integer" -> [] "mtl" -> ["transformers"] "nats" -> [] "one" -> ["free"] "prelude" -> [] "profunctors" -> ["comonad","distributive","semigroupoids","tagged","transformers"] "semigroupoids" -> ["comonad","containers","contravariant","distributive" ,"semigroups","transformers","transformers-compat" ] "semigroups" -> ["bytestring","containers","deepseq","hashable" ,"nats","text","unordered-containers" ] "stm" -> ["array"] "tagged" -> ["template-haskell"] "template" -> [] "text" -> ["array","binary","bytestring","deepseq","ghc-prim","integer-gmp"] "transformers" -> [] "two" -> ["free","mtl","one","transformers"] "unordered" -> ["deepseq","hashable"] "void" -> ["ghc-prim","hashable","semigroups"] _ -> [] stack-1.5.1/src/test/Stack/GhciSpec.hs0000644000000000000000000002165013135652051015652 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} -- | Test suite for GHCi like applications including both GHCi and Intero. module Stack.GhciSpec where import qualified Data.ByteString.Lazy as LBS import qualified Data.Map as M import qualified Data.Set as S import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T import Distribution.License (License (BSD3)) import qualified Distribution.ModuleName as ModuleName import Distribution.PackageDescription (BuildType(..)) import Stack.Types.Package import Stack.Types.PackageName import Stack.Types.Version import Test.Hspec import NeatInterpolation import Path import Path.Extra (pathToText) import qualified System.FilePath as FP import Stack.Ghci import Stack.Ghci.Script (scriptToLazyByteString) import Stack.Ghci.PortableFakePaths textToLazy :: Text -> LBS.ByteString textToLazy = LBS.fromStrict . T.encodeUtf8 -- | Matches two strings, after converting line-ends in the second to Unix ones -- (in a hacky way) and converting both to the same type. Workaround for -- https://github.com/nikita-volkov/neat-interpolation/issues/14. shouldBeLE :: LBS.ByteString -> Text -> Expectation shouldBeLE actual expected = shouldBe actual (textToLazy $ T.filter (/= '\r') expected) baseProjDir, projDirA, projDirB :: Path Abs Dir baseProjDir = $(mkAbsDir $ defaultDrive FP. "Users" FP. "someone" FP. "src") projDirA = baseProjDir $(mkRelDir "project-a") projDirB = baseProjDir $(mkRelDir "project-b") relFile :: Path Rel File relFile = $(mkRelFile $ "exe" FP. "Main.hs") absFile :: Path Abs File absFile = projDirA relFile projDirAT, projDirBT, relFileT, absFileT :: Text projDirAT = pathToText projDirA projDirBT = pathToText projDirB relFileT = pathToText relFile absFileT = pathToText absFile spec :: Spec spec = do describe "GHCi" $ do describe "Script rendering" $ do describe "should render GHCi scripts" $ do it "with one library package" $ do let res = scriptToLazyByteString $ renderScriptGhci packages_singlePackage Nothing [] res `shouldBeLE` ghciScript_projectWithLib it "with one main package" $ do let res = scriptToLazyByteString $ renderScriptGhci [] (Just absFile) [] res `shouldBeLE` ghciScript_projectWithMain it "with one library and main package" $ do let res = scriptToLazyByteString $ renderScriptGhci packages_singlePackage (Just absFile) [] res `shouldBeLE` ghciScript_projectWithLibAndMain it "with multiple library packages" $ do let res = scriptToLazyByteString $ renderScriptGhci packages_multiplePackages Nothing [] res `shouldBeLE` ghciScript_multipleProjectsWithLib describe "should render intero scripts" $ do it "with one library package" $ do let res = scriptToLazyByteString $ renderScriptIntero packages_singlePackage Nothing [] res `shouldBeLE` interoScript_projectWithLib it "with one main package" $ do let res = scriptToLazyByteString $ renderScriptIntero packages_singlePackage (Just absFile) [] res `shouldBeLE` interoScript_projectWithMain it "with one library and main package" $ do let res = scriptToLazyByteString $ renderScriptIntero packages_singlePackage (Just absFile) [] res `shouldBeLE` interoScript_projectWithLibAndMain it "with multiple library packages" $ do let res = scriptToLazyByteString $ renderScriptIntero packages_multiplePackages Nothing [] res `shouldBeLE` interoScript_multipleProjectsWithLib -- Exptected Intero scripts interoScript_projectWithLib :: Text interoScript_projectWithLib = [text| :cd-ghc $projDirAT :add Lib.A :module + Lib.A |] interoScript_projectWithMain :: Text interoScript_projectWithMain = [text| :cd-ghc $projDirAT :add Lib.A :cd-ghc $projDirAT :add $absFileT :module + Lib.A |] interoScript_projectWithLibAndMain :: Text interoScript_projectWithLibAndMain = [text| :cd-ghc $projDirAT :add Lib.A :cd-ghc $projDirAT :add $absFileT :module + Lib.A |] interoScript_multipleProjectsWithLib :: Text interoScript_multipleProjectsWithLib = [text| :cd-ghc $projDirAT :add Lib.A :cd-ghc $projDirBT :add Lib.B :module + Lib.A Lib.B |] -- Expected GHCi Scripts ghciScript_projectWithLib :: Text ghciScript_projectWithLib = [text| :add Lib.A :module + Lib.A |] ghciScript_projectWithMain :: Text ghciScript_projectWithMain = [text| :add $absFileT :module + |] ghciScript_projectWithLibAndMain :: Text ghciScript_projectWithLibAndMain = [text| :add Lib.A :add $absFileT :module + Lib.A |] ghciScript_multipleProjectsWithLib :: Text ghciScript_multipleProjectsWithLib = [text| :add Lib.A :add Lib.B :module + Lib.A Lib.B |] -- Expected Legacy GHCi scripts ghciLegacyScript_projectWithMain :: Text ghciLegacyScript_projectWithMain = [text| :add :add $absFileT :module + |] ghciLegacyScript_projectWithLibAndMain :: Text ghciLegacyScript_projectWithLibAndMain = [text| :add Lib.A :add $absFileT :module + Lib.A |] ghciLegacyScript_multipleProjectsWithLib :: Text ghciLegacyScript_multipleProjectsWithLib = [text| :add Lib.A Lib.B :module + Lib.A Lib.B |] -- Sample GHCi load configs packages_singlePackage :: [GhciPkgInfo] packages_singlePackage = [ GhciPkgInfo { ghciPkgModules = S.fromList [ModuleName.fromString "Lib.A"] , ghciPkgDir = projDirA , ghciPkgName = $(mkPackageName "package-a") , ghciPkgOpts = [] , ghciPkgModFiles = S.empty , ghciPkgCFiles = S.empty , ghciPkgMainIs = M.empty , ghciPkgTargetFiles = Nothing , ghciPkgPackage = Package { packageName = $(mkPackageName "package-a") , packageVersion = $(mkVersion "0.1.0.0") , packageLicense = BSD3 , packageFiles = GetPackageFiles undefined , packageDeps = M.empty , packageTools = [] , packageAllDeps = S.empty , packageGhcOptions = [] , packageFlags = M.empty , packageDefaultFlags = M.empty , packageHasLibrary = True , packageTests = M.empty , packageBenchmarks = S.empty , packageExes = S.empty , packageOpts = GetPackageOpts undefined , packageHasExposedModules = True , packageBuildType = Just Simple , packageSetupDeps = Nothing } } ] packages_multiplePackages :: [GhciPkgInfo] packages_multiplePackages = [ GhciPkgInfo { ghciPkgModules = S.fromList [ModuleName.fromString "Lib.A"] , ghciPkgDir = projDirA , ghciPkgName = $(mkPackageName "package-a") , ghciPkgOpts = [] , ghciPkgModFiles = S.empty , ghciPkgCFiles = S.empty , ghciPkgMainIs = M.empty , ghciPkgTargetFiles = Nothing , ghciPkgPackage = Package { packageName = $(mkPackageName "package-a") , packageVersion = $(mkVersion "0.1.0.0") , packageLicense = BSD3 , packageFiles = GetPackageFiles undefined , packageDeps = M.empty , packageTools = [] , packageAllDeps = S.empty , packageGhcOptions = [] , packageFlags = M.empty , packageDefaultFlags = M.empty , packageHasLibrary = True , packageTests = M.empty , packageBenchmarks = S.empty , packageExes = S.empty , packageOpts = GetPackageOpts undefined , packageHasExposedModules = True , packageBuildType = Just Simple , packageSetupDeps = Nothing } } , GhciPkgInfo { ghciPkgModules = S.fromList [ModuleName.fromString "Lib.B"] , ghciPkgDir = projDirB , ghciPkgName = $(mkPackageName "package-b") , ghciPkgOpts = [] , ghciPkgModFiles = S.empty , ghciPkgCFiles = S.empty , ghciPkgMainIs = M.empty , ghciPkgTargetFiles = Nothing , ghciPkgPackage = Package { packageName = $(mkPackageName "package-b") , packageVersion = $(mkVersion "0.1.0.0") , packageLicense = BSD3 , packageFiles = GetPackageFiles undefined , packageDeps = M.empty , packageTools = [] , packageAllDeps = S.empty , packageGhcOptions = [] , packageFlags = M.empty , packageDefaultFlags = M.empty , packageHasLibrary = True , packageTests = M.empty , packageBenchmarks = S.empty , packageExes = S.empty , packageOpts = GetPackageOpts undefined , packageHasExposedModules = True , packageBuildType = Just Simple , packageSetupDeps = Nothing } } ] stack-1.5.1/src/test/Stack/Ghci/ScriptSpec.hs0000644000000000000000000000477713135652051017131 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE TemplateHaskell #-} -- | Test suite for the GhciScript DSL module Stack.Ghci.ScriptSpec where import Data.Monoid import qualified Data.Set as S import Distribution.ModuleName import Test.Hspec import qualified System.FilePath as FP import Stack.Ghci.PortableFakePaths import Path import Path.Extra (pathToLazyByteString) import Stack.Ghci.Script spec :: Spec spec = do describe "GHCi" $ do describe "Script DSL" $ do describe "script" $ do it "should seperate commands with a newline" $ do let dir = $(mkAbsDir $ defaultDrive FP. "src" FP. "package-a") script = cmdCdGhc dir <> cmdAdd [fromString "Lib.A"] scriptToLazyByteString script `shouldBe` ":cd-ghc " <> pathToLazyByteString dir <> "\n:add Lib.A\n" describe ":add" $ do it "should not render empty add commands" $ do let script = cmdAdd [] scriptToLazyByteString script `shouldBe` "" it "should ensure that a space exists between each module in an add command" $ do let script = cmdAdd (S.fromList [fromString "Lib.A", fromString "Lib.B"]) scriptToLazyByteString script `shouldBe` ":add Lib.A Lib.B\n" describe ":add (by file)" $ do it "should render a full file path" $ do let file = $(mkAbsFile $ defaultDrive FP. "Users" FP. "someone" FP. "src" FP. "project" FP. "package-a" FP. "src" FP. "Main.hs") script = cmdAddFile file scriptToLazyByteString script `shouldBe` ":add " <> pathToLazyByteString file <> "\n" describe ":cd-ghc" $ do it "should render a full absolute path" $ do let dir = $(mkAbsDir $ defaultDrive FP. "Users" FP. "someone" FP. "src" FP. "project" FP. "package-a") script = cmdCdGhc dir scriptToLazyByteString script `shouldBe` ":cd-ghc " <> pathToLazyByteString dir <> "\n" describe ":module" $ do it "should render empty module as ':module +'" $ do let script = cmdModule [] scriptToLazyByteString script `shouldBe` ":module +\n" it "should ensure that a space exists between each module in a module command" $ do let script = cmdModule [fromString "Lib.A", fromString "Lib.B"] scriptToLazyByteString script `shouldBe` ":module + Lib.A Lib.B\n" stack-1.5.1/src/test/Stack/Ghci/PortableFakePaths.hs0000644000000000000000000000047312766643573020420 0ustar0000000000000000{-# LANGUAGE CPP #-} -- | Helpers for writing fake paths for test suite for the GhciScript DSL. -- This must be a separate module because it is used in Teplate Haskell splices. module Stack.Ghci.PortableFakePaths where defaultDrive :: FilePath #ifdef WINDOWS defaultDrive = "C:\\" #else defaultDrive = "/" #endif stack-1.5.1/src/test/Stack/PackageDumpSpec.hs0000644000000000000000000002763213135652051017167 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} module Stack.PackageDumpSpec where import Control.Applicative import Control.Monad.Logger import Control.Monad.Trans.Resource (runResourceT) import Data.Conduit import qualified Data.Conduit.Binary as CB import qualified Data.Conduit.List as CL import Data.Conduit.Text (decodeUtf8) import Data.Map (Map) import qualified Data.Map as Map import qualified Data.Set as Set import Distribution.System (buildPlatform) import Distribution.License (License(..)) import Prelude -- Fix redundant imports warnings import Stack.PackageDump import Stack.Types.Compiler import Stack.Types.GhcPkgId import Stack.Types.PackageIdentifier import Stack.Types.PackageName import Stack.Types.Version import System.Process.Read import Test.Hspec import Test.Hspec.QuickCheck main :: IO () main = hspec spec spec :: Spec spec = do describe "eachSection" $ do let test name content expected = it name $ do actual <- yield content $$ eachSection CL.consume =$ CL.consume actual `shouldBe` expected test "unix line endings" "foo\nbar\n---\nbaz---\nbin\n---\n" [ ["foo", "bar"] , ["baz---", "bin"] ] test "windows line endings" "foo\r\nbar\r\n---\r\nbaz---\r\nbin\r\n---\r\n" [ ["foo", "bar"] , ["baz---", "bin"] ] it "eachPair" $ do let bss = [ "key1: val1" , "key2: val2a" , " val2b" , "key3:" , "key4:" , " val4a" , " val4b" ] sink k = fmap (k, ) CL.consume actual <- mapM_ yield bss $$ eachPair sink =$ CL.consume actual `shouldBe` [ ("key1", ["val1"]) , ("key2", ["val2a", "val2b"]) , ("key3", []) , ("key4", ["val4a", "val4b"]) ] describe "conduitDumpPackage" $ do it "ghc 7.8" $ do haskell2010:_ <- runResourceT $ CB.sourceFile "test/package-dump/ghc-7.8.txt" =$= decodeUtf8 $$ conduitDumpPackage =$ CL.consume ghcPkgId <- parseGhcPkgId "haskell2010-1.1.2.0-05c8dd51009e08c6371c82972d40f55a" packageIdent <- parsePackageIdentifier "haskell2010-1.1.2.0" depends <- mapM parseGhcPkgId [ "array-0.5.0.0-470385a50d2b78598af85cfe9d988e1b" , "base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1" , "ghc-prim-0.3.1.0-a24f9c14c632d75b683d0f93283aea37" ] haskell2010 `shouldBe` DumpPackage { dpGhcPkgId = ghcPkgId , dpPackageIdent = packageIdent , dpLicense = Just BSD3 , dpLibDirs = ["/opt/ghc/7.8.4/lib/ghc-7.8.4/haskell2010-1.1.2.0"] , dpDepends = depends , dpLibraries = ["HShaskell2010-1.1.2.0"] , dpHasExposedModules = True , dpHaddockInterfaces = ["/opt/ghc/7.8.4/share/doc/ghc/html/libraries/haskell2010-1.1.2.0/haskell2010.haddock"] , dpHaddockHtml = Just "/opt/ghc/7.8.4/share/doc/ghc/html/libraries/haskell2010-1.1.2.0" , dpProfiling = () , dpHaddock = () , dpSymbols = () , dpIsExposed = False } it "ghc 7.10" $ do haskell2010:_ <- runResourceT $ CB.sourceFile "test/package-dump/ghc-7.10.txt" =$= decodeUtf8 $$ conduitDumpPackage =$ CL.consume ghcPkgId <- parseGhcPkgId "ghc-7.10.1-325809317787a897b7a97d646ceaa3a3" pkgIdent <- parsePackageIdentifier "ghc-7.10.1" depends <- mapM parseGhcPkgId [ "array-0.5.1.0-e29cdbe82692341ebb7ce6e2798294f9" , "base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a" , "bin-package-db-0.0.0.0-708fc7d634a370b311371a5bcde40b62" , "bytestring-0.10.6.0-0909f8f31271f3d75749190bf2ee35db" , "containers-0.5.6.2-2114032c163425cc264e6e1169dc2f6d" , "directory-1.2.2.0-b4959b472d9eee380c6b32291ade29e0" , "filepath-1.4.0.0-40d643aa87258c186441a1f8f3e13ca6" , "hoopl-3.10.0.2-8c8dfc4c3140e5f7c982da224c3cb1f0" , "hpc-0.6.0.2-ac9064885aa8cb08a93314222939ead4" , "process-1.2.3.0-3b1e9bca6ac38225806ff7bbf3f845b1" , "template-haskell-2.10.0.0-e895139a0ffff267d412e3d0191ce93b" , "time-1.5.0.1-e17a9220d438435579d2914e90774246" , "transformers-0.4.2.0-c1a7bb855a176fe475d7b665301cd48f" , "unix-2.7.1.0-e5915eb989e568b732bc7286b0d0817f" ] haskell2010 `shouldBe` DumpPackage { dpGhcPkgId = ghcPkgId , dpPackageIdent = pkgIdent , dpLicense = Just BSD3 , dpLibDirs = ["/opt/ghc/7.10.1/lib/ghc-7.10.1/ghc_EMlWrQ42XY0BNVbSrKixqY"] , dpHaddockInterfaces = ["/opt/ghc/7.10.1/share/doc/ghc/html/libraries/ghc-7.10.1/ghc.haddock"] , dpHaddockHtml = Just "/opt/ghc/7.10.1/share/doc/ghc/html/libraries/ghc-7.10.1" , dpDepends = depends , dpLibraries = ["HSghc-7.10.1-EMlWrQ42XY0BNVbSrKixqY"] , dpHasExposedModules = True , dpProfiling = () , dpHaddock = () , dpSymbols = () , dpIsExposed = False } it "ghc 7.8.4 (osx)" $ do hmatrix:_ <- runResourceT $ CB.sourceFile "test/package-dump/ghc-7.8.4-osx.txt" =$= decodeUtf8 $$ conduitDumpPackage =$ CL.consume ghcPkgId <- parseGhcPkgId "hmatrix-0.16.1.5-12d5d21f26aa98774cdd8edbc343fbfe" pkgId <- parsePackageIdentifier "hmatrix-0.16.1.5" depends <- mapM parseGhcPkgId [ "array-0.5.0.0-470385a50d2b78598af85cfe9d988e1b" , "base-4.7.0.2-918c7ac27f65a87103264a9f51652d63" , "binary-0.7.1.0-108d06eea2ef05e517f9c1facf10f63c" , "bytestring-0.10.4.0-78bc8f2c724c765c78c004a84acf6cc3" , "deepseq-1.3.0.2-0ddc77716bd2515426e1ba39f6788a4f" , "random-1.1-822c19b7507b6ac1aaa4c66731e775ae" , "split-0.2.2-34cfb851cc3784e22bfae7a7bddda9c5" , "storable-complex-0.2.2-e962c368d58acc1f5b41d41edc93da72" , "vector-0.10.12.3-f4222db607fd5fdd7545d3e82419b307"] hmatrix `shouldBe` DumpPackage { dpGhcPkgId = ghcPkgId , dpPackageIdent = pkgId , dpLicense = Just BSD3 , dpLibDirs = [ "/Users/alexbiehl/.stack/snapshots/x86_64-osx/lts-2.13/7.8.4/lib/x86_64-osx-ghc-7.8.4/hmatrix-0.16.1.5" , "/opt/local/lib/" , "/usr/local/lib/" , "C:/Program Files/Example/"] , dpHaddockInterfaces = ["/Users/alexbiehl/.stack/snapshots/x86_64-osx/lts-2.13/7.8.4/doc/html/hmatrix.haddock"] , dpHaddockHtml = Just "/Users/alexbiehl/.stack/snapshots/x86_64-osx/lts-2.13/7.8.4/doc/html" , dpDepends = depends , dpLibraries = ["HShmatrix-0.16.1.5"] , dpHasExposedModules = True , dpProfiling = () , dpHaddock = () , dpSymbols = () , dpIsExposed = True } it "ghc HEAD" $ do ghcBoot:_ <- runResourceT $ CB.sourceFile "test/package-dump/ghc-head.txt" =$= decodeUtf8 $$ conduitDumpPackage =$ CL.consume ghcPkgId <- parseGhcPkgId "ghc-boot-0.0.0.0" pkgId <- parsePackageIdentifier "ghc-boot-0.0.0.0" depends <- mapM parseGhcPkgId [ "base-4.9.0.0" , "binary-0.7.5.0" , "bytestring-0.10.7.0" , "directory-1.2.5.0" , "filepath-1.4.1.0" ] ghcBoot `shouldBe` DumpPackage { dpGhcPkgId = ghcPkgId , dpPackageIdent = pkgId , dpLicense = Just BSD3 , dpLibDirs = ["/opt/ghc/head/lib/ghc-7.11.20151213/ghc-boot-0.0.0.0"] , dpHaddockInterfaces = ["/opt/ghc/head/share/doc/ghc/html/libraries/ghc-boot-0.0.0.0/ghc-boot.haddock"] , dpHaddockHtml = Just "/opt/ghc/head/share/doc/ghc/html/libraries/ghc-boot-0.0.0.0" , dpDepends = depends , dpLibraries = ["HSghc-boot-0.0.0.0"] , dpHasExposedModules = True , dpProfiling = () , dpHaddock = () , dpSymbols = () , dpIsExposed = True } it "ghcPkgDump + addProfiling + addHaddock" $ (id :: IO () -> IO ()) $ runNoLoggingT $ do menv' <- getEnvOverride buildPlatform menv <- mkEnvOverride buildPlatform $ Map.delete "GHC_PACKAGE_PATH" $ unEnvOverride menv' icache <- newInstalledCache ghcPkgDump menv Ghc [] $ conduitDumpPackage =$ addProfiling icache =$ addHaddock icache =$ fakeAddSymbols =$ CL.sinkNull it "sinkMatching" $ do menv' <- getEnvOverride buildPlatform menv <- mkEnvOverride buildPlatform $ Map.delete "GHC_PACKAGE_PATH" $ unEnvOverride menv' icache <- newInstalledCache m <- runNoLoggingT $ ghcPkgDump menv Ghc [] $ conduitDumpPackage =$ addProfiling icache =$ addHaddock icache =$ fakeAddSymbols =$ sinkMatching False False False (Map.singleton $(mkPackageName "transformers") $(mkVersion "0.0.0.0.0.0.1")) case Map.lookup $(mkPackageName "base") m of Nothing -> error "base not present" Just _ -> return () Map.lookup $(mkPackageName "transformers") m `shouldBe` Nothing Map.lookup $(mkPackageName "ghc") m `shouldBe` Nothing describe "pruneDeps" $ do it "sanity check" $ do let prunes = [ ((1, 'a'), []) , ((1, 'b'), []) , ((2, 'a'), [(1, 'b')]) , ((2, 'b'), [(1, 'a')]) , ((3, 'a'), [(1, 'c')]) , ((4, 'a'), [(2, 'a')]) ] actual = fst <$> pruneDeps fst fst snd bestPrune prunes actual `shouldBe` Map.fromList [ (1, (1, 'b')) , (2, (2, 'a')) , (4, (4, 'a')) ] prop "invariant holds" $ \prunes' -> -- Force uniqueness let prunes = Map.toList $ Map.fromList prunes' in checkDepsPresent prunes $ fst <$> pruneDeps fst fst snd bestPrune prunes type PruneCheck = ((Int, Char), [(Int, Char)]) bestPrune :: PruneCheck -> PruneCheck -> PruneCheck bestPrune x y | fst x > fst y = x | otherwise = y checkDepsPresent :: [PruneCheck] -> Map Int (Int, Char) -> Bool checkDepsPresent prunes selected = all hasDeps $ Set.toList allIds where depMap = Map.fromList prunes allIds = Set.fromList $ Map.elems selected hasDeps ident = case Map.lookup ident depMap of Nothing -> error "checkDepsPresent: missing in depMap" Just deps -> Set.null $ Set.difference (Set.fromList deps) allIds -- addSymbols can't be reasonably tested like this fakeAddSymbols :: Monad m => Conduit (DumpPackage a b c) m (DumpPackage a b Bool) fakeAddSymbols = CL.map (\dp -> dp { dpSymbols = False }) stack-1.5.1/src/test/Stack/ArgsSpec.hs0000644000000000000000000001367413135652051015703 0ustar0000000000000000-- | Args parser test suite. module Stack.ArgsSpec where import Control.Applicative import Control.Exception.Base (assert) import Control.Monad import Data.Attoparsec.Interpreter (interpreterArgsParser) import qualified Data.Attoparsec.Text as P import Data.Text (pack) import Options.Applicative.Args import Stack.Constants (stackProgName) import Test.Hspec -- | Test spec. spec :: Spec spec = do argsSpec interpreterArgsSpec argsSpec :: Spec argsSpec = forM_ argsInputOutput (\(input,output) -> it input (parseArgsFromString input == output)) -- | Fairly comprehensive checks. argsInputOutput :: [(String, Either String [String])] argsInputOutput = [ ("x", Right ["x"]) , ("x y z", Right ["x", "y", "z"]) , ("aaa bbb ccc", Right ["aaa", "bbb", "ccc"]) , (" aaa bbb ccc ", Right ["aaa", "bbb", "ccc"]) , ("aaa\"", Left "unterminated string: endOfInput") , ("\"", Left "unterminated string: endOfInput") , ("\"\"", Right [""]) , ("\"aaa", Left "unterminated string: endOfInput") , ("\"aaa\" bbb ccc \"ddd\"", Right ["aaa", "bbb", "ccc", "ddd"]) , ("\"aa\\\"a\" bbb ccc \"ddd\"", Right ["aa\"a", "bbb", "ccc", "ddd"]) , ("\"aa\\\"a\" bb\\b ccc \"ddd\"", Right ["aa\"a", "bb\\b", "ccc", "ddd"]) , ("\"\" \"\" c", Right ["","","c"])] interpreterArgsSpec :: Spec interpreterArgsSpec = describe "Script interpreter parser" $ do describe "Success cases" $ do describe "Line comments" $ do checkLines "" checkLines " --x" checkLines " --x --y" describe "Literate line comments" $ do checkLiterateLines "" checkLiterateLines " --x" checkLiterateLines " --x --y" describe "Block comments" $ do checkBlocks "" checkBlocks "\n" checkBlocks " --x" checkBlocks "\n--x" checkBlocks " --x --y" checkBlocks "\n--x\n--y" checkBlocks "\n\t--x\n\t--y" describe "Literate block comments" $ do checkLiterateBlocks "" "" checkLiterateBlocks "\n>" "" checkLiterateBlocks " --x" " --x" checkLiterateBlocks "\n>--x" "--x" checkLiterateBlocks " --x --y " "--x --y" checkLiterateBlocks "\n>--x\n>--y" "--x --y" checkLiterateBlocks "\n>\t--x\n>\t--y" "--x --y" describe "Failure cases" $ do checkFailures describe "Bare directives in literate files" $ do forM_ (interpreterGenValid lineComment []) $ testAndCheck (acceptFailure True) [] forM_ (interpreterGenValid blockComment []) $ testAndCheck (acceptFailure True) [] where parse isLiterate s = P.parseOnly (interpreterArgsParser isLiterate stackProgName) (pack s) acceptSuccess :: Bool -> String -> String -> Bool acceptSuccess isLiterate args s = case parse isLiterate s of Right x | words x == words args -> True _ -> False acceptFailure isLiterate _ s = case parse isLiterate s of Left _ -> True Right _ -> False showInput i = "BEGIN =>" ++ i ++ "<= END" testAndCheck checker out inp = it (showInput inp) $ checker out inp checkLines args = forM_ (interpreterGenValid lineComment args) (testAndCheck (acceptSuccess False) args) checkLiterateLines args = forM_ (interpreterGenValid literateLineComment args) (testAndCheck (acceptSuccess True) args) checkBlocks args = forM_ (interpreterGenValid blockComment args) (testAndCheck (acceptSuccess False) args) checkLiterateBlocks inp args = forM_ (interpreterGenValid literateBlockComment inp) (testAndCheck (acceptSuccess True) args) checkFailures = forM_ interpreterGenInvalid (testAndCheck (acceptFailure False) "unused") -- Generate a set of acceptable inputs for given format and args interpreterGenValid fmt args = shebang <++> newLine <++> fmt args interpreterGenInvalid :: [String] -- Generate a set of Invalid inputs interpreterGenInvalid = ["-stack\n"] -- random input -- just the shebang <|> shebang <++> ["\n"] -- invalid shebang <|> blockSpace <++> [head (interpreterGenValid lineComment args)] -- something between shebang and stack comment <|> shebang <++> newLine <++> blockSpace <++> ([head (lineComment args)] <|> [head (blockComment args)]) -- unterminated block comment -- just chop the closing chars from a valid block comment <|> shebang <++> ["\n"] <++> let c = head (blockComment args) l = length c - 2 in [assert (drop l c == "-}") (take l c)] -- nested block comment <|> shebang <++> ["\n"] <++> [head (blockComment "--x {- nested -} --y")] where args = " --x --y" (<++>) = liftA2 (++) -- Generative grammar for the interpreter comments shebang = ["#!/usr/bin/env stack"] newLine = ["\n"] <|> ["\r\n"] -- A comment may be the last line or followed by something else postComment = [""] <|> newLine -- A command starts with zero or more whitespace followed by "stack" makeComment maker space args = let makePrefix s = (s <|> [""]) <++> [stackProgName] in (maker <$> (makePrefix space <++> [args])) <++> postComment lineSpace = [" "] <|> ["\t"] lineComment = makeComment makeLine lineSpace where makeLine s = "--" ++ s literateLineComment = makeComment ("> --" ++) lineSpace blockSpace = lineSpace <|> newLine blockComment = makeComment makeBlock blockSpace where makeBlock s = "{-" ++ s ++ "-}" literateBlockComment = makeComment (\s -> "> {-" ++ s ++ "-}") (lineSpace <|> map (++ ">") newLine) stack-1.5.1/src/test/Stack/NixSpec.hs0000644000000000000000000001037713135652051015542 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell, OverloadedStrings #-} module Stack.NixSpec where import Control.Exception import Control.Monad.Logger import Data.Maybe import Data.Monoid import Options.Applicative import Path import Prelude -- to remove the warning about Data.Monoid being redundant on GHC 7.10 import Stack.Config import Stack.Options.NixParser import Stack.Config.Nix import Stack.Types.Compiler import Stack.Types.Config import Stack.Types.Nix import Stack.Types.StackT import Stack.Types.Version import System.Directory import System.Environment import System.IO.Temp (withSystemTempDirectory) import Test.Hspec sampleConfigNixEnabled :: String sampleConfigNixEnabled = "resolver: lts-2.10\n" ++ "packages: ['.']\n" ++ "system-ghc: true\n" ++ "nix:\n" ++ " enable: True\n" ++ " packages: [glpk]" sampleConfigNixDisabled :: String sampleConfigNixDisabled = "resolver: lts-2.10\n" ++ "packages: ['.']\n" ++ "nix:\n" ++ " enable: False" stackDotYaml :: Path Rel File stackDotYaml = $(mkRelFile "stack.yaml") setup :: IO () setup = unsetEnv "STACK_YAML" spec :: Spec spec = beforeAll setup $ do let loadConfig' cmdLineArgs = runStackT () LevelDebug True False ColorAuto False (loadConfig cmdLineArgs Nothing SYLDefault) inTempDir test = do currentDirectory <- getCurrentDirectory withSystemTempDirectory "Stack_ConfigSpec" $ \tempDir -> do let enterDir = setCurrentDirectory tempDir exitDir = setCurrentDirectory currentDirectory bracket_ enterDir exitDir test withStackDotYaml config test = inTempDir $ do writeFile (toFilePath stackDotYaml) config test parseNixOpts cmdLineOpts = fromJust $ getParseResult $ execParserPure defaultPrefs (info (nixOptsParser False) mempty) cmdLineOpts parseOpts cmdLineOpts = mempty { configMonoidNixOpts = parseNixOpts cmdLineOpts } describe "nix disabled in config file" $ around_ (withStackDotYaml sampleConfigNixDisabled) $ do it "sees that the nix shell is not enabled" $ do lc <- loadConfig' mempty nixEnable (configNix $ lcConfig lc) `shouldBe` False describe "--nix given on command line" $ it "sees that the nix shell is enabled" $ do lc <- loadConfig' (parseOpts ["--nix"]) nixEnable (configNix $ lcConfig lc) `shouldBe` True describe "--nix-pure given on command line" $ it "sees that the nix shell is enabled" $ do lc <- loadConfig' (parseOpts ["--nix-pure"]) nixEnable (configNix $ lcConfig lc) `shouldBe` True describe "--no-nix given on command line" $ it "sees that the nix shell is not enabled" $ do lc <- loadConfig' (parseOpts ["--no-nix"]) nixEnable (configNix $ lcConfig lc) `shouldBe` False describe "--no-nix-pure given on command line" $ it "sees that the nix shell is not enabled" $ do lc <- loadConfig' (parseOpts ["--no-nix-pure"]) nixEnable (configNix $ lcConfig lc) `shouldBe` False describe "nix enabled in config file" $ around_ (withStackDotYaml sampleConfigNixEnabled) $ do it "sees that the nix shell is enabled" $ do lc <- loadConfig' mempty nixEnable (configNix $ lcConfig lc) `shouldBe` True describe "--no-nix given on command line" $ it "sees that the nix shell is not enabled" $ do lc <- loadConfig' (parseOpts ["--no-nix"]) nixEnable (configNix $ lcConfig lc) `shouldBe` False describe "--nix-pure given on command line" $ it "sees that the nix shell is enabled" $ do lc <- loadConfig' (parseOpts ["--nix-pure"]) nixEnable (configNix $ lcConfig lc) `shouldBe` True describe "--no-nix-pure given on command line" $ it "sees that the nix shell is enabled" $ do lc <- loadConfig' (parseOpts ["--no-nix-pure"]) nixEnable (configNix $ lcConfig lc) `shouldBe` True it "sees that the only package asked for is glpk and asks for the correct GHC derivation" $ do lc <- loadConfig' mempty nixPackages (configNix $ lcConfig lc) `shouldBe` ["glpk"] v <- parseVersion "7.10.3" nixCompiler (GhcVersion v) `shouldBe` "haskell.compiler.ghc7103" stack-1.5.1/src/test/Stack/StoreSpec.hs0000644000000000000000000000611413135652051016072 0ustar0000000000000000{-# OPTIONS_GHC -fno-warn-orphans -Wwarn #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE OverloadedStrings #-} module Stack.StoreSpec where import Control.Applicative import qualified Data.ByteString as BS import Data.Containers (mapFromList, setFromList) import Data.Hashable (Hashable) import Data.HashMap.Strict (HashMap) import Data.Int import Data.Map (Map) import Data.Sequences (fromList) import Data.Set (Set) import Data.Store.TH import Data.Text (Text) import qualified Data.Vector.Unboxed as UV import Data.Word import Language.Haskell.TH import Language.Haskell.TH.ReifyMany import Prelude import Stack.Types.Build import Stack.Types.BuildPlan import Stack.Types.PackageDump import Stack.Types.PackageIndex import Test.Hspec import Test.SmallCheck.Series -- NOTE: these were copied from Data.Store. Should probably be moved to -- smallcheck. instance (Monad m, Serial m k, Serial m a, Ord k) => Serial m (Map k a) where series = fmap mapFromList series instance (Monad m, Serial m k, Serial m a, Eq k, Hashable k) => Serial m (HashMap k a) where series = fmap mapFromList series instance Monad m => Serial m Text where series = fmap fromList series instance (Monad m, Serial m a, UV.Unbox a) => Serial m (UV.Vector a) where series = fmap fromList series instance Monad m => Serial m BS.ByteString where series = fmap BS.pack series instance (Monad m, Serial m a, Ord a) => Serial m (Set a) where series = fmap setFromList series addMinAndMaxBounds :: forall a. (Bounded a, Eq a) => [a] -> [a] addMinAndMaxBounds xs = (if (minBound :: a) `notElem` xs then [minBound] else []) ++ (if (maxBound :: a) `notElem` xs && (maxBound :: a) /= minBound then maxBound : xs else xs) $(do let ns = [ ''Int64, ''Word64, ''Word, ''Word8 ] f n = [d| instance Monad m => Serial m $(conT n) where series = generate (\_ -> addMinAndMaxBounds [0, 1]) |] concat <$> mapM f ns) $(do let tys = [ ''InstalledCacheInner , ''PackageCacheMap , ''MiniBuildPlan , ''BuildCache , ''ConfigCache ] ns <- reifyManyWithoutInstances ''Serial tys (`notElem` [''UV.Vector]) let f n = [d| instance Monad m => Serial m $(conT n) |] concat <$> mapM f ns) verbose :: Bool verbose = False spec :: Spec spec = do describe "Roundtrips binary formats" $ do $(smallcheckManyStore False 6 [ [t| InstalledCacheInner |] , [t| BuildCache |] ]) -- Blows up with > 5 $(smallcheckManyStore False 5 [ [t| PackageCacheMap |] , [t| MiniBuildPlan |] ]) -- Blows up with > 4 $(smallcheckManyStore False 4 [ [t| ConfigCache |] ]) stack-1.5.1/src/test/Network/HTTP/Download/VerifiedSpec.hs0000644000000000000000000001241013135652051021441 0ustar0000000000000000module Network.HTTP.Download.VerifiedSpec where import Control.Applicative import Control.Monad.Logger (runStdoutLoggingT) import Control.Retry (limitRetries) import Crypto.Hash import Data.Maybe import Network.HTTP.Client.Conduit import Network.HTTP.Download.Verified import Path import Path.IO import Prelude -- Fix redundant imports warnings import Test.Hspec -- TODO: share across test files withTempDir' :: (Path Abs Dir -> IO a) -> IO a withTempDir' = withSystemTempDir "NHD_VerifiedSpec" -- | An example path to download the exampleReq. getExamplePath :: Path Abs Dir -> IO (Path Abs File) getExamplePath dir = do file <- parseRelFile "cabal-install-1.22.4.0.tar.gz" return (dir file) -- | An example DownloadRequest that uses a SHA1 exampleReq :: DownloadRequest exampleReq = fromMaybe (error "exampleReq") $ do let req = parseRequest_ "http://download.fpcomplete.com/stackage-cli/linux64/cabal-install-1.22.4.0.tar.gz" return DownloadRequest { drRequest = req , drHashChecks = [exampleHashCheck] , drLengthCheck = Just exampleLengthCheck , drRetryPolicy = limitRetries 1 } exampleHashCheck :: HashCheck exampleHashCheck = HashCheck { hashCheckAlgorithm = SHA1 , hashCheckHexDigest = CheckHexDigestString "b98eea96d321cdeed83a201c192dac116e786ec2" } exampleLengthCheck :: LengthCheck exampleLengthCheck = 302513 -- | The wrong ContentLength for exampleReq exampleWrongContentLength :: Int exampleWrongContentLength = 302512 -- | The wrong SHA1 digest for exampleReq exampleWrongDigest :: CheckHexDigest exampleWrongDigest = CheckHexDigestString "b98eea96d321cdeed83a201c192dac116e786ec3" exampleWrongContent :: String exampleWrongContent = "example wrong content" isWrongContentLength :: VerifiedDownloadException -> Bool isWrongContentLength WrongContentLength{} = True isWrongContentLength _ = False isWrongDigest :: VerifiedDownloadException -> Bool isWrongDigest WrongDigest{} = True isWrongDigest _ = False spec :: Spec spec = do let exampleProgressHook _ = return () describe "verifiedDownload" $ do -- Preconditions: -- * the exampleReq server is running -- * the test runner has working internet access to it it "downloads the file correctly" $ withTempDir' $ \dir -> do examplePath <- getExamplePath dir doesFileExist examplePath `shouldReturn` False let go = runStdoutLoggingT $ verifiedDownload exampleReq examplePath exampleProgressHook go `shouldReturn` True doesFileExist examplePath `shouldReturn` True it "is idempotent, and doesn't redownload unnecessarily" $ withTempDir' $ \dir -> do examplePath <- getExamplePath dir doesFileExist examplePath `shouldReturn` False let go = runStdoutLoggingT $ verifiedDownload exampleReq examplePath exampleProgressHook go `shouldReturn` True doesFileExist examplePath `shouldReturn` True go `shouldReturn` False doesFileExist examplePath `shouldReturn` True -- https://github.com/commercialhaskell/stack/issues/372 it "does redownload when the destination file is wrong" $ withTempDir' $ \dir -> do examplePath <- getExamplePath dir let exampleFilePath = toFilePath examplePath writeFile exampleFilePath exampleWrongContent doesFileExist examplePath `shouldReturn` True readFile exampleFilePath `shouldReturn` exampleWrongContent let go = runStdoutLoggingT $ verifiedDownload exampleReq examplePath exampleProgressHook go `shouldReturn` True doesFileExist examplePath `shouldReturn` True readFile exampleFilePath `shouldNotReturn` exampleWrongContent it "rejects incorrect content length" $ withTempDir' $ \dir -> do examplePath <- getExamplePath dir let wrongContentLengthReq = exampleReq { drLengthCheck = Just exampleWrongContentLength } let go = runStdoutLoggingT $ verifiedDownload wrongContentLengthReq examplePath exampleProgressHook go `shouldThrow` isWrongContentLength doesFileExist examplePath `shouldReturn` False it "rejects incorrect digest" $ withTempDir' $ \dir -> do examplePath <- getExamplePath dir let wrongHashCheck = exampleHashCheck { hashCheckHexDigest = exampleWrongDigest } let wrongDigestReq = exampleReq { drHashChecks = [wrongHashCheck] } let go = runStdoutLoggingT $ verifiedDownload wrongDigestReq examplePath exampleProgressHook go `shouldThrow` isWrongDigest doesFileExist examplePath `shouldReturn` False -- https://github.com/commercialhaskell/stack/issues/240 it "can download hackage tarballs" $ withTempDir' $ \dir -> do dest <- (dir ) <$> parseRelFile "acme-missiles-0.3.tar.gz" let req = parseRequest_ "http://hackage.haskell.org/package/acme-missiles-0.3/acme-missiles-0.3.tar.gz" let dReq = DownloadRequest { drRequest = req , drHashChecks = [] , drLengthCheck = Nothing , drRetryPolicy = limitRetries 1 } let go = runStdoutLoggingT $ verifiedDownload dReq dest exampleProgressHook doesFileExist dest `shouldReturn` False go `shouldReturn` True doesFileExist dest `shouldReturn` True stack-1.5.1/src/test/Stack/SolverSpec.hs0000644000000000000000000000271013135652051016246 0ustar0000000000000000{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} -- | Test suite for "Stack.Solver" module Stack.SolverSpec where import Data.Text (unpack) import Stack.Types.FlagName import Stack.Types.PackageName import Stack.Types.Version import Test.Hspec import qualified Data.Map as Map import Stack.Solver spec :: Spec spec = describe "Stack.Solver" $ do successfulExample "text-1.2.1.1 (latest: 1.2.2.0) -integer-simple (via: parsec-3.1.9) (new package)" $(mkPackageName "text") $(mkVersion "1.2.1.1") [ ($(mkFlagName "integer-simple"), False) ] successfulExample "hspec-snap-1.0.0.0 *test (via: servant-snap-0.5) (new package)" $(mkPackageName "hspec-snap") $(mkVersion "1.0.0.0") [] successfulExample "time-locale-compat-0.1.1.1 -old-locale (via: http-api-data-0.2.2) (new package)" $(mkPackageName "time-locale-compat") $(mkVersion "0.1.1.1") [ ($(mkFlagName "old-locale"), False) ] successfulExample "flowdock-rest-0.2.0.0 -aeson-compat *test (via: haxl-fxtra-0.0.0.0) (new package)" $(mkPackageName "flowdock-rest") $(mkVersion "0.2.0.0") [ ($(mkFlagName "aeson-compat"), False) ] where successfulExample input pkgName pkgVersion flags = it ("parses " ++ unpack input) $ parseCabalOutputLine input `shouldBe` Right (pkgName, (pkgVersion, Map.fromList flags)) stack-1.5.1/src/test/Stack/Untar/UntarSpec.hs0000644000000000000000000000307213135652051017160 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Stack.Untar.UntarSpec where import Data.List (sort) import Path import Path.IO (removeDirRecur) import qualified System.FilePath as FP import Stack.Fetch (untar) import Test.Hspec spec :: Spec spec = do describe "Untarring ignores strange entries" $ mapM_ testTarFile tarFiles where -- XXX tests are run in the project root folder, but data files are next to -- this source data. currentFolder = $(mkRelDir $ "src" FP. "test" FP. "Stack" FP. "Untar") -- Pairs test tarball names + list of unexpected entries contained: for each -- entry, a tar pathname + description. tarFilesBase = [ ("test1", []) , ("test2", [ ("bar", "named pipe") , ("devB", "block device") , ("devC", "character device")])] -- Prepend tarball name to tar pathnames: tarFiles = [ (name, [ (name FP. entryName, d) | (entryName, d) <- entries]) | (name, entries) <- tarFilesBase ] testTarFile (name, expected) = it ("works on test " ++ name) $ getEntries name `shouldReturn` sort expected getEntries name = do tarballName <- parseRelFile $ name ++ ".tar.gz" expectedTarFolder <- parseRelDir name entries <- untar (currentFolder tarballName) expectedTarFolder currentFolder removeDirRecur $ currentFolder expectedTarFolder return $ sort entries stack-1.5.1/LICENSE0000644000000000000000000000273113063526313012023 0ustar0000000000000000Copyright (c) 2015-2017, Stack contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of stack nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL STACK CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. stack-1.5.1/Setup.hs0000644000000000000000000000553312766643573012476 0ustar0000000000000000{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-} module Main (main) where import Data.List ( nub, sortBy ) import Data.Ord ( comparing ) import Data.Version ( showVersion ) import Distribution.Package ( PackageName(PackageName), PackageId, InstalledPackageId, packageVersion, packageName ) import Distribution.PackageDescription ( PackageDescription(), Executable(..) ) import Distribution.InstalledPackageInfo (sourcePackageId, installedPackageId) import Distribution.Simple ( defaultMainWithHooks, UserHooks(..), simpleUserHooks ) import Distribution.Simple.Utils ( rewriteFile, createDirectoryIfMissingVerbose ) import Distribution.Simple.BuildPaths ( autogenModulesDir ) import Distribution.Simple.PackageIndex (allPackages, dependencyClosure) import Distribution.Simple.Setup ( BuildFlags(buildVerbosity), fromFlag ) import Distribution.Simple.LocalBuildInfo ( installedPkgs, withLibLBI, withExeLBI, LocalBuildInfo(), ComponentLocalBuildInfo(componentPackageDeps) ) import Distribution.Verbosity ( Verbosity ) import System.FilePath ( () ) main :: IO () main = defaultMainWithHooks simpleUserHooks { buildHook = \pkg lbi hooks flags -> do generateBuildModule (fromFlag (buildVerbosity flags)) pkg lbi buildHook simpleUserHooks pkg lbi hooks flags } generateBuildModule :: Verbosity -> PackageDescription -> LocalBuildInfo -> IO () generateBuildModule verbosity pkg lbi = do let dir = autogenModulesDir lbi createDirectoryIfMissingVerbose verbosity True dir withLibLBI pkg lbi $ \_ libcfg -> do withExeLBI pkg lbi $ \exe clbi -> rewriteFile (dir "Build_" ++ exeName exe ++ ".hs") $ unlines [ "module Build_" ++ exeName exe ++ " where" , "" , "deps :: [String]" , "deps = " ++ (show $ formatdeps (transDeps libcfg clbi)) ] where formatdeps = map formatone . sortBy (comparing unPackageName') formatone p = unPackageName' p ++ "-" ++ showVersion (packageVersion p) unPackageName' p = case packageName p of PackageName n -> n transDeps xs ys = either (map sourcePackageId . allPackages) handleDepClosureFailure $ dependencyClosure allInstPkgsIdx availInstPkgIds where allInstPkgsIdx = installedPkgs lbi allInstPkgIds = map installedPackageId $ allPackages allInstPkgsIdx -- instPkgIds includes `stack-X.X.X`, which is not a depedency hence is missing from allInstPkgsIdx. Filter that out. availInstPkgIds = filter (`elem` allInstPkgIds) . map fst $ testDeps xs ys handleDepClosureFailure unsatisfied = error $ "Computation of transitive dependencies failed." ++ if null unsatisfied then "" else " Unresolved dependencies: " ++ show unsatisfied testDeps :: ComponentLocalBuildInfo -> ComponentLocalBuildInfo -> [(InstalledPackageId, PackageId)] testDeps xs ys = nub $ componentPackageDeps xs ++ componentPackageDeps ys stack-1.5.1/stack.cabal0000644000000000000000000004232013140560265013105 0ustar0000000000000000name: stack version: 1.5.1 synopsis: The Haskell Tool Stack description: Please see the README.md for usage information, and the wiki on Github for more details. Also, note that the API for the library is not currently stable, and may change significantly, even between minor releases. It is currently only intended for use by the executable. license: BSD3 license-file: LICENSE author: Commercial Haskell SIG maintainer: manny@fpcomplete.com category: Development build-type: Custom cabal-version: >=1.10 homepage: http://haskellstack.org extra-source-files: CONTRIBUTING.md ChangeLog.md README.md doc/*.md src/setup-shim/StackSetupShim.hs -- Glob would be nice, but apparently Cabal doesn't support it: -- cabal: filepath wildcard 'test/package-dump/*.txt' does not match any files. -- Happened during cabal sdist test/package-dump/ghc-7.8.txt test/package-dump/ghc-7.8.4-osx.txt test/package-dump/ghc-7.10.txt stack.yaml custom-setup setup-depends: base , Cabal , filepath flag integration-tests manual: True default: False description: Run the integration test suite flag disable-git-info manual: True default: False description: Disable compile-time inclusion of current git info in stack -- disabling git info can lead to a quicker workflow in certain -- scenarios when you're developing on stack itself, but -- should otherwise be avoided -- see: https://github.com/commercialhaskell/stack/issues/1425 flag static manual: True default: False description: Pass -static/-pthread to ghc when linking the stack binary. -- Not intended for general use. Simply makes it easier to -- build a fully static binary on Linux platforms that enable it. flag hide-dependency-versions manual: True default: False description: Hides dependency versions from "stack --version", used only by building with stack.yaml library hs-source-dirs: src/ ghc-options: -Wall -fwarn-tabs -fwarn-incomplete-uni-patterns -fwarn-incomplete-record-updates -fwarn-identities exposed-modules: Control.Concurrent.Execute Data.Aeson.Extended Data.Attoparsec.Args Data.Attoparsec.Combinators Data.Attoparsec.Interpreter Data.IORef.RunOnce Data.Maybe.Extra Data.Monoid.Extra Data.Store.VersionTagged Data.Text.Extra Distribution.Version.Extra Network.HTTP.Download Network.HTTP.Download.Verified Options.Applicative.Args Options.Applicative.Builder.Extra Options.Applicative.Complicated Path.CheckInstall Path.Extra Path.Find Paths_stack Stack.Build Stack.Build.Cache Stack.Build.ConstructPlan Stack.Build.Execute Stack.Build.Haddock Stack.Build.Installed Stack.Build.Source Stack.Build.Target Stack.BuildPlan Stack.Clean Stack.Config Stack.Config.Build Stack.Config.Urls Stack.Config.Docker Stack.Config.Nix Stack.ConfigCmd Stack.Constants Stack.Coverage Stack.Docker Stack.Docker.GlobalDB Stack.Dot Stack.Exec Stack.Fetch Stack.FileWatch Stack.GhcPkg Stack.Ghci Stack.Ghci.Script Stack.Hoogle Stack.IDE Stack.Image Stack.Init Stack.New Stack.Nix Stack.Options.BenchParser Stack.Options.BuildMonoidParser Stack.Options.BuildParser Stack.Options.CleanParser Stack.Options.ConfigParser Stack.Options.Completion Stack.Options.DockerParser Stack.Options.DotParser Stack.Options.ExecParser Stack.Options.GhcBuildParser Stack.Options.GhciParser Stack.Options.GhcVariantParser Stack.Options.GlobalParser Stack.Options.HaddockParser Stack.Options.HpcReportParser Stack.Options.LogLevelParser Stack.Options.NewParser Stack.Options.NixParser Stack.Options.PackageParser Stack.Options.ResolverParser Stack.Options.ScriptParser Stack.Options.SDistParser Stack.Options.SolverParser Stack.Options.TestParser Stack.Options.Utils Stack.Package Stack.PackageDump Stack.PackageIndex Stack.Path Stack.PrettyPrint Stack.Runners Stack.Script Stack.SDist Stack.Setup Stack.Setup.Installed Stack.SetupCmd Stack.Sig Stack.Sig.GPG Stack.Sig.Sign Stack.Solver Stack.Types.Build Stack.Types.BuildPlan Stack.Types.CompilerBuild Stack.Types.Urls Stack.Types.Compiler Stack.Types.Config Stack.Types.Config.Build Stack.Types.Docker Stack.Types.FlagName Stack.Types.GhcPkgId Stack.Types.Image Stack.Types.Internal Stack.Types.Nix Stack.Types.Package Stack.Types.PackageDump Stack.Types.PackageIdentifier Stack.Types.PackageIndex Stack.Types.PackageName Stack.Types.Resolver Stack.Types.Sig Stack.Types.StackT Stack.Types.StringError Stack.Types.TemplateName Stack.Types.Version Stack.Upgrade Stack.Upload Text.PrettyPrint.Leijen.Extended System.Process.Log System.Process.PagerEditor System.Process.Read System.Process.Run other-modules: Hackage.Security.Client.Repository.HttpLib.HttpClient build-depends: Cabal >= 1.24 && < 1.25 , aeson (>= 1.0 && < 1.2) , ansi-terminal >= 0.6.2.3 , async >= 2.0.2 && < 2.2 , attoparsec >= 0.12.1.5 && < 0.14 , base >= 4.8 && <5 , base-compat >=0.6 && <0.10 , base64-bytestring , binary >= 0.7 && < 0.9 , binary-tagged >= 0.1.1 , blaze-builder , bytestring >= 0.10.4.0 , clock >= 0.7.2 , conduit >= 1.2.8 , conduit-extra >= 1.1.14 , containers >= 0.5.5.1 , cryptonite >= 0.19 && < 0.24 , cryptonite-conduit >= 0.1 && < 0.3 , directory >= 1.2.1.0 && < 1.4 , echo >= 0.1.3 && < 0.2 , either , errors < 2.3 , exceptions >= 0.8.0.2 , extra >= 1.4.10 && < 1.6 , fast-logger >= 2.3.1 , filelock >= 0.1.0.1 , filepath >= 1.3.0.2 , fsnotify >= 0.2.1 , generic-deriving >= 1.10.5 && < 1.12 , ghc-prim >= 0.4.0.0 , hackage-security , hashable >= 1.2.3.2 , hpc >= 0.6.0.2 , http-client >= 0.5.3.3 , http-client-tls >= 0.3.4 , http-conduit >= 2.2.3 , http-types >= 0.8.6 && < 0.10 , lifted-async >= 0.9.1.1 -- https://github.com/basvandijk/lifted-base/issues/31 , lifted-base < 0.2.3.7 || > 0.2.3.7 , memory >= 0.13 && < 0.15 , microlens >= 0.3.0.0 , microlens-mtl >= 0.1.10.0 , mintty >= 0.1.1 , monad-control >= 1.0.1.0 , monad-logger >= 0.3.13.1 , monad-unlift < 0.3 , mtl >= 2.1.3.1 , network-uri , open-browser >= 0.2.1 , optparse-applicative >= 0.13 && < 0.14 , path >= 0.5.8 && < 0.6 , path-io >= 1.1.0 && < 2.0.0 , persistent >= 2.1.2 && < 2.8 -- persistent-sqlite-2.5.0.1 has a bug -- (see https://github.com/yesodweb/persistent/pull/561#issuecomment-222329087) , persistent-sqlite (>= 2.1.4 && < 2.5.0.1) || (> 2.5.0.1 && < 2.7) , persistent-template >= 2.1.1 && < 2.6 , pretty >= 1.1.1.1 , process >= 1.2.1.0 && < 1.5 , regex-applicative-text >=0.1.0.1 && <0.2 , resourcet >= 1.1.4.1 , retry >= 0.6 && < 0.8 , safe >= 0.3 , safe-exceptions >= 0.1.5.0 , semigroups >= 0.5 && < 0.19 , split , stm >= 2.4.4 , streaming-commons >= 0.1.10.0 , tar >= 0.5.0.3 && < 0.6 , template-haskell >= 2.9.0.0 && < 2.12 , temporary >= 1.2.0.3 , text >= 1.2.0.4 , text-binary , text-metrics >= 0.1 && < 0.4 , time >= 1.4.2 && < 1.7 , tls >= 1.3.8 , transformers >= 0.3.0.0 && < 0.6 , transformers-base >= 0.4.4 , unicode-transforms >= 0.1 && <0.4 , unix-compat >= 0.4.1.4 , unordered-containers >= 0.2.5.1 , vector >= 0.10.12.3 && < 0.13 , vector-binary-instances , yaml >= 0.8.20 , zlib >= 0.5.4.2 && < 0.7 , deepseq >= 1.4 , hastache , project-template >= 0.2 , zip-archive >= 0.2.3.7 && < 0.4 , hpack >= 0.17.0 && < 0.19 , store >= 0.4.1 && < 0.5 , store-core >= 0.4 && < 0.5 , annotated-wl-pprint , file-embed >= 0.0.10 if os(windows) cpp-options: -DWINDOWS build-depends: Win32 else build-depends: unix >= 2.7.0.1 , pid1 >= 0.1 && < 0.2 default-language: Haskell2010 executable stack hs-source-dirs: src/main main-is: Main.hs ghc-options: -threaded -Wall -fwarn-tabs -fwarn-incomplete-uni-patterns -fwarn-incomplete-record-updates other-modules: Paths_stack if flag(static) ld-options: -static -pthread build-depends: Cabal >= 1.18.1.5 && < 1.25 , base >=4.7 && < 5 , bytestring >= 0.10.4.0 , conduit >= 1.2.8 , containers >= 0.5.5.1 , directory >= 1.2.1.0 && < 1.4 , either , filelock >= 0.1.0.1 , filepath >= 1.3.0.2 , hpack >= 0.17.0 && < 0.19 , http-client >= 0.5.3.3 -- https://github.com/basvandijk/lifted-base/issues/31 , lifted-base < 0.2.3.7 || > 0.2.3.7 , microlens >= 0.3.0.0 , monad-control >= 1.0.1.0 , monad-logger >= 0.3.13.1 , mtl >= 2.1.3.1 , optparse-applicative >= 0.13 && < 0.14 , path >= 0.5.8 && < 0.6 , path-io >= 1.1.0 && < 2.0.0 , split , stack , text >= 1.2.0.4 , transformers >= 0.3.0.0 && < 0.6 default-language: Haskell2010 if os(windows) build-depends: Win32 cpp-options: -DWINDOWS if !flag(disable-git-info) cpp-options: -DUSE_GIT_INFO build-depends: gitrev >= 1.1 && < 1.4 , optparse-simple >= 0.0.3 if flag(hide-dependency-versions) cpp-options: -DHIDE_DEP_VERSIONS test-suite stack-test type: exitcode-stdio-1.0 hs-source-dirs: src/test main-is: Test.hs other-modules: Spec , Stack.BuildPlanSpec , Stack.Build.ExecuteSpec , Stack.Build.TargetSpec , Stack.ConfigSpec , Stack.DotSpec , Stack.GhciSpec , Stack.Ghci.ScriptSpec , Stack.Ghci.PortableFakePaths , Stack.PackageDumpSpec , Stack.ArgsSpec , Stack.NixSpec , Stack.StoreSpec , Network.HTTP.Download.VerifiedSpec , Stack.SolverSpec , Stack.Untar.UntarSpec ghc-options: -threaded -Wall -fwarn-tabs -fwarn-incomplete-uni-patterns -fwarn-incomplete-record-updates build-depends: Cabal >= 1.18.1.5 && < 1.25 , QuickCheck >= 2.8.2 && < 2.10 , attoparsec < 0.14 , base >=4.7 && <5 , conduit >= 1.2.8 , conduit-extra , containers >= 0.5.5.1 , cryptonite >= 0.19 && < 0.24 , directory >= 1.2.1.0 && < 1.4 , exceptions , filepath >= 1.3.0.2 , hspec >= 2.2 && <2.5 , hashable >= 1.2.3.2 , http-client-tls >= 0.3.4 , http-conduit >= 2.2.3 , monad-logger , neat-interpolation >= 0.3 , optparse-applicative >= 0.13 && < 0.14 , path >= 0.5.8 && < 0.6 , path-io >= 1.1.0 && < 2.0.0 , resourcet , retry >= 0.6 && < 0.8 , stack , temporary >= 1.2.0.3 , text , transformers >= 0.3.0.0 && < 0.6 , mono-traversable >= 0.10.2 && <1.1 , th-reify-many >= 0.1.6 , smallcheck , bytestring >= 0.10.4.0 , store >= 0.4.1 && < 0.5 , vector >= 0.10.12.3 && < 0.13 , unordered-containers , template-haskell >= 2.9.0.0 && < 2.12 , yaml >= 0.8.20 default-language: Haskell2010 if os(windows) cpp-options: -DWINDOWS test-suite stack-integration-test type: exitcode-stdio-1.0 hs-source-dirs: test/integration main-is: IntegrationSpec.hs ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -fwarn-tabs -fwarn-incomplete-uni-patterns -fwarn-incomplete-record-updates if !flag(integration-tests) buildable: False build-depends: async < 2.2 , base >= 4.7 && < 5 , bytestring >= 0.10.4.0 , conduit >= 1.2.8 , conduit-extra >= 1.1.14 , containers >= 0.5.5.1 , directory >= 1.2.1.0 && < 1.4 , filepath >= 1.3.0.2 , hspec >= 2.2 && < 2.5 , process >= 1.2.0.0 && < 1.5 , resourcet , temporary >= 1.2.0.3 , text , transformers >= 0.3.0.0 && < 0.6 , unix-compat >= 0.4.1.4 default-language: Haskell2010 -- This isn't actually needed to build stack-integration-test, but it makes it -- easier to load up an individual integration test into stack ghci. It's -- still a little involved: -- -- stack exec -- stack ghci stack:stack-integration-test --flag stack:integration-tests --no-build -- -- Then, in ghci: -- -- :cd test/integration/tests/.../files -- :load ../Main.hs -- main other-modules: StackTest hs-source-dirs: test/integration/lib source-repository head type: git location: https://github.com/commercialhaskell/stack.git stack-1.5.1/CONTRIBUTING.md0000644000000000000000000000754313135651271013257 0ustar0000000000000000# Contributors Guide ## Bug Reports Please [open an issue](https://github.com/commercialhaskell/stack/issues/new) and use the provided template to include all necessary details. The more detailed your report, the faster it can be resolved and will ensure it is resolved in the right way. Once your bug has been resolved, the responsible person will tag the issue as _Needs confirmation_ and assign the issue back to you. Once you have tested and confirmed that the issue is resolved, close the issue. If you are not a member of the project, you will be asked for confirmation and we will close it. ## Documentation If you would like to help with documentation, please note that for most cases the Wiki has been deprecated in favor of markdown files placed in a new `/doc` subdirectory of the repository itself. Please submit a [pull request](https://help.github.com/articles/using-pull-requests/) with your changes/additions. The documentation is rendered on [haskellstack.org](http://haskellstack.org) by readthedocs.org using Sphinx and CommonMark. Since links and formatting vary from GFM, please check the documentation there before submitting a PR to fix those. In particular, links to other documentation files intentionally have `.html` extensions instead of `.md`, unfortunately (see [#1506](https://github.com/commercialhaskell/stack/issues/1506) for details). If your changes move or rename files, or subsume Wiki content, please continue to leave a file/page in the old location temporarily, in addition to the new location. This will allow users time to update any shared links to the old location. Please also update any links in other files, or on the Wiki, to point to the new file location. ## Code If you would like to contribute code to fix a bug, add a new feature, or otherwise improve `stack`, pull requests are most welcome. It's a good idea to [submit an issue](https://github.com/commercialhaskell/stack/issues/new) to discuss the change before plowing into writing code. If you'd like to help out but aren't sure what to work on, look for issues with the [awaiting pr](https://github.com/commercialhaskell/stack/issues?q=is%3Aopen+is%3Aissue+label%3A%22awaiting+pr%22) label. Issues that are suitable for newcomers to the codebase have the [newcomer](https://github.com/commercialhaskell/stack/issues?q=is%3Aopen+is%3Aissue+label%3A%22awaiting+pr%22+label%3Anewcomer) label. Best to post a comment to the issue before you start work, in case anyone has already started. Please include a [ChangeLog](https://github.com/commercialhaskell/stack/blob/master/ChangeLog.md) entry and [documentation](https://github.com/commercialhaskell/stack/tree/master/doc/) updates with your pull request. ## Code Quality The Stack projects uses [HLint](https://github.com/ndmitchell/hlint) as a code quality tool. Note that stack contributors need not dogmatically follow the suggested hints but are encouraged to debate their usefulness. If you find a hint is not useful and detracts from readability, consider marking it in the [configuration file](https://github.com/commercialhaskell/stack/blob/master/HLint.hs) to be ignored. Please refer to the [HLint manual](https://github.com/ndmitchell/hlint#ignoring-hints) for configuration syntax. Quoting [@mgsloan](https://github.com/commercialhaskell/stack/pulls?utf8=%E2%9C%93&q=is%3Apr%20author%3Amgsloan): > We are optimizing for code clarity, not code concision or what HLint thinks. You can install HLint with stack. You might want to install it in the global project in case you run into dependency conflicts. HLint can report hints in your favourite text editor. Refer to the HLint repository for more details. To install: ``` stack install hlint ``` Once installed, you can check your changes with: ``` hlint src/ test/ --cpp-simple --hint=HLint.hs ``` Where `--cpp-simple` strips `#` lines and `--hint` explicitly specifies the configuration file. stack-1.5.1/ChangeLog.md0000644000000000000000000023245113140560217013170 0ustar0000000000000000# Changelog ## 1.5.1 Bug fixes: * Stack eagerly tries to parse all cabal files related to a snapshot. Starting with Stackage Nightly 2017-07-31, snapshots are using GHC 8.2.1, and the `ghc.cabal` file implicitly referenced uses the (not yet supported) Cabal 2.0 file format. Future releases of Stack will both be less eager about cabal file parsing and support Cabal 2.0. This patch simply bypasses the error for invalid parsing. ## 1.5.0 Behavior changes: * `stack profile` and `stack trace` now add their extra RTS arguments for benchmarks and tests to the beginning of the args, instead of the end. See [#2399](https://github.com/commercialhaskell/stack/issues/2399) * Support for Git-based indices has been removed. Other enhancements: * `stack setup` allow to control options passed to ghcjs-boot with `--ghcjs-boot-options` (one word at a time) and `--[no-]ghcjs-boot-clean` * `stack setup` now accepts a `--install-cabal VERSION` option which will install a specific version of the Cabal library globally. * Updates to store-0.4.1, which has improved performance and better error reporting for version tags. A side-effect of this is that all of stack's binary caches will be invalidated. * `stack solver` will now warn about unexpected cabal-install versions. See [#3044](https://github.com/commercialhaskell/stack/issues/3044) * Upstream packages unpacked to a temp dir are now deleted as soon as possible to avoid running out of space in `/tmp`. See [#3018](https://github.com/commercialhaskell/stack/issues/3018) * Add short synonyms for `test-arguments` and `benchmark-arguments` options. * Adds `STACK_WORK` environment variable, to specify work dir. See [#3063](https://github.com/commercialhaskell/stack/issues/3063) * Can now use relative paths for `extra-include-dirs` and `extra-lib-dirs`. See [#2830](https://github.com/commercialhaskell/stack/issues/2830) * Improved bash completion for many options, including `--ghc-options`, `--flag`, targets, and project executables for `exec`. * `--haddock-arguments` is actually used now when `haddock` is invoked during documentation generation. * `--[no-]haddock-hyperlink-source` flag added which allows toggling of sources being included in Haddock output. See [#3099](https://github.com/commercialhaskell/stack/issues/3099) * `stack ghci` will now skip building all local targets, even if they have downstream deps, as long as it's registered in the DB. * The pvp-bounds feature now supports adding `-revision` to the end of each value, e.g. `pvp-bounds: both-revision`. This means that, when uploading to Hackage, Stack will first upload your tarball with an unmodified `.cabal` file, and then upload a cabal file revision with the PVP bounds added. This can be useful—especially combined with the [Stackage no-revisions feature](http://www.snoyman.com/blog/2017/04/stackages-no-revisions-field)—as a method to ensure PVP compliance without having to proactively fix bounds issues for Stackage maintenance. * Expose a `save-hackage-creds` configuration option * On GHC <= 7.8, filters out spurious linker warnings on windows See [#3127](https://github.com/commercialhaskell/stack/pull/3127) * Better error messages when creating or building packages which alias wired-in packages. See [#3172](https://github.com/commercialhaskell/stack/issues/3172). * MinGW bin folder now is searched for dynamic libraries. See [#3126](https://github.com/commercialhaskell/stack/issues/3126) * When using Nix, nix-shell now depends always on git to prevent runtime errors while fetching metadata * The `stack unpack` command now accepts a form where an explicit Hackage revision hash is specified, e.g. `stack unpack foo-1.2.3@gitsha1:deadbeef`. Note that this should be considered _experimental_, Stack will likely move towards a different hash format in the future. * Binary "stack upgrade" will now warn if the installed executable is not on the PATH or shadowed by another entry. * Allow running tests on tarball created by sdist and upload [#717](https://github.com/commercialhaskell/stack/issues/717). Bug fixes: * Fixes case where `stack build --profile` might not cause executables / tests / benchmarks to be rebuilt. See [#2984](https://github.com/commercialhaskell/stack/issues/2984) * `stack ghci file.hs` now loads the file even if it isn't part of your project. * `stack clean --full` now works when docker is enabled. See [#2010](https://github.com/commercialhaskell/stack/issues/2010) * Fixes an issue where cyclic deps can cause benchmarks or tests to be run before they are built. See [#2153](https://github.com/commercialhaskell/stack/issues/2153) * Fixes `stack build --file-watch` in cases where a directory is removed See [#1838](https://github.com/commercialhaskell/stack/issues/1838) * Fixes `stack dot` and `stack list-dependencies` to use info from the package database for wired-in-packages (ghc, base, etc). See [#3084](https://github.com/commercialhaskell/stack/issues/3084) * Fixes `stack --docker build` when user is part of libvirt/libvirtd groups on Ubuntu Yakkety (16.10). See [#3092](https://github.com/commercialhaskell/stack/issues/3092) * Switching a package between extra-dep and local package now forces rebuild (previously it wouldn't if versions were the same). See [#2147](https://github.com/commercialhaskell/stack/issues/2147) * `stack upload` no longer reveals your password when you type it on MinTTY-based Windows shells, such as Cygwin and MSYS2. See [#3142](https://github.com/commercialhaskell/stack/issues/3142) * `stack script`'s import parser will now properly parse files that have Windows-style line endings (CRLF) ## 1.4.0 Release notes: * Docker images: [fpco/stack-full](https://hub.docker.com/r/fpco/stack-full/) and [fpco/stack-run](https://hub.docker.com/r/fpco/stack-run/) are no longer being built for LTS 8.0 and above. [fpco/stack-build](https://hub.docker.com/r/fpco/stack-build/) images continue to be built with a [simplified process](https://github.com/commercialhaskell/stack/tree/master/etc/dockerfiles/stack-build). [#624](https://github.com/commercialhaskell/stack/issues/624) Major changes: * A new command, `script`, has been added, intended to make the script interpreter workflow more reliable, easier to use, and more efficient. This command forces the user to provide a `--resolver` value, ignores all config files for more reproducible results, and optimizes the existing package check to make the common case of all packages already being present much faster. This mode does require that all packages be present in a snapshot, however. [#2805](https://github.com/commercialhaskell/stack/issues/2805) Behavior changes: * The default package metadata backend has been changed from Git to the 01-index.tar.gz file, from the hackage-security project. This is intended to address some download speed issues from Github for people in certain geographic regions. There is now full support for checking out specific cabal file revisions from downloaded tarballs as well. If you manually specify a package index with only a Git URL, Git will still be used. See [#2780](https://github.com/commercialhaskell/stack/issues/2780) * When you provide the `--resolver` argument to the `stack unpack` command, any packages passed in by name only will be looked up in the given snapshot instead of taking the latest version. For example, `stack --resolver lts-7.14 unpack mtl` will get version 2.2.1 of `mtl`, regardless of the latest version available in the package indices. This will also force the same cabal file revision to be used as is specified in the snapshot. Unpacking via a package identifier (e.g. `stack --resolver lts-7.14 unpack mtl-2.2.1`) will ignore any settings in the snapshot and take the most recent revision. For backwards compatibility with tools relying on the presence of a `00-index.tar`, Stack will copy the `01-index.tar` file to `00-index.tar`. Note, however, that these files are different; most importantly, 00-index contains only the newest revisions of cabal files, while 01-index contains all versions. You may still need to update your tooling. * Passing `--(no-)nix-*` options now no longer implies `--nix`, except for `--nix-pure`, so that the user preference whether or not to use Nix is honored even in the presence of options that change the Nix behavior. Other enhancements: * Internal cleanup: configuration types are now based much more on lenses * `stack build` and related commands now allow the user to disable debug symbol stripping with new `--no-strip`, `--no-library-stripping`, and `--no-executable-shipping` flags, closing [#877](https://github.com/commercialhaskell/stack/issues/877). Also turned error message for missing targets more readable ([#2384](https://github.com/commercialhaskell/stack/issues/2384)) * `stack haddock` now shows index.html paths when documentation is already up to date. Resolved [#781](https://github.com/commercialhaskell/stack/issues/781) * Respects the `custom-setup` field introduced in Cabal 1.24. This supercedes any `explicit-setup-deps` settings in your `stack.yaml` and trusts the package's `.cabal` file to explicitly state all its dependencies. * If system package installation fails, `get-stack.sh` will fail as well. Also shows warning suggesting to run `apt-get update` or similar, depending on the OS. ([#2898](https://github.com/commercialhaskell/stack/issues/2898)) * When `stack ghci` is run with a config with no packages (e.g. global project), it will now look for source files in the current work dir. ([#2878](https://github.com/commercialhaskell/stack/issues/2878)) * Bump to hpack 0.17.0 to allow `custom-setup` and `!include "..."` in `package.yaml`. * The script interpreter will now output error logging. In particular, this means it will output info about plan construction errors. ([#2879](https://github.com/commercialhaskell/stack/issues/2879)) * `stack ghci` now takes `--flag` and `--ghc-options` again (inadverently removed in 1.3.0). ([#2986](https://github.com/commercialhaskell/stack/issues/2986)) * `stack exec` now takes `--rts-options` which passes the given arguments inside of `+RTS ... args .. -RTS` to the executable. This works around stack itself consuming the RTS flags on Windows. ([#2986](https://github.com/commercialhaskell/stack/issues/2640)) * Upgraded `http-client-tls` version, which now offers support for the `socks5://` and `socks5h://` values in the `http_proxy` and `https_proxy` environment variables. Bug fixes: * Bump to hpack 0.16.0 to avoid character encoding issues when reading and writing on non-UTF8 systems. * `stack ghci` will no longer ignore hsSourceDirs that contain `..`. ([#2895](https://github.com/commercialhaskell/stack/issues/2895)) * `stack list-dependencies --license` now works for wired-in-packages, like base. ([#2871](https://github.com/commercialhaskell/stack/issues/2871)) * `stack setup` now correctly indicates when it uses system ghc ([#2963](https://github.com/commercialhaskell/stack/issues/2963)) * Fix to `stack config set`, in 1.3.2 it always applied to the global project. ([#2709](https://github.com/commercialhaskell/stack/issues/2709)) * Previously, cabal files without exe or lib would fail on the "copy" step. ([#2862](https://github.com/commercialhaskell/stack/issues/2862)) * `stack upgrade --git` now works properly. Workaround for affected versions (>= 1.3.0) is to instead run `stack upgrade --git --source-only`. ([#2977](https://github.com/commercialhaskell/stack/issues/2977)) * Added support for GHC 8's slightly different warning format for dumping warnings from logs. * Work around a bug in Cabal/GHC in which package IDs are not unique for different source code, leading to Stack not always rebuilding packages depending on local packages which have changed. ([#2904](https://github.com/commercialhaskell/stack/issues/2904)) ## 1.3.2 Bug fixes: * `stack config set` can now be used without a compiler installed [#2852](https://github.com/commercialhaskell/stack/issues/2852). * `get-stack.sh` now installs correct binary on ARM for generic linux and raspbian, closing [#2856](https://github.com/commercialhaskell/stack/issues/2856). * Correct the testing of whether a package database exists by checking for the `package.cache` file itself instead of the containing directory. * Revert a change in the previous release which made it impossible to set local extra-dep packages as targets. This was overkill; we really only wanted to disable their test suites, which was already handled by a later patch. [#2849](https://github.com/commercialhaskell/stack/issues/2849) * `stack new` always treats templates as being UTF-8 encoding, ignoring locale settings on a local machine. See [Yesod mailing list discussion](https://groups.google.com/d/msg/yesodweb/ZyWLsJOtY0c/aejf9E7rCAAJ) ## 1.3.0 Release notes: * For the _next_ stack release after this one, we are planning changes to our Linux releases, including dropping our Ubuntu, Debian, CentOS, and Fedora package repositories and switching to statically linked binaries. See [#2534](https://github.com/commercialhaskell/stack/issues/2534). Note that upgrading without a package manager has gotten easier with new binary upgrade support in `stack upgrade` (see the Major Changes section below for more information). In addition, the get.haskellstack.org script no longer installs from Ubuntu, Debian, CentOS, or Fedora package repositories. Instead it places a generic binary in /usr/local/bin. Major changes: * Stack will now always use its own GHC installation, even when a suitable GHC installation is available on the PATH. To get the old behaviour, use the `--system-ghc` flag or run `stack config set system-ghc --global true`. Docker- and Nix-enabled projects continue to use the GHC installations in their environment by default. NB: Scripts that previously used stack in combination with a system GHC installation should now include a `stack setup` line or use the `--install-ghc` flag. [#2221](https://github.com/commercialhaskell/stack/issues/2221) * `stack ghci` now defaults to skipping the build of target packages, because support has been added for invoking "initial build steps", which create autogen files and run preprocessors. The `--no-build` flag is now deprecated because it should no longer be necessary. See [#1364](https://github.com/commercialhaskell/stack/issues/1364) * Stack is now capable of doing binary upgrades instead of always recompiling a new version from source. Running `stack upgrade` will now default to downloading a binary version of Stack from the most recent release, if one is available. See `stack upgrade --help` for more options. [#1238](https://github.com/commercialhaskell/stack/issues/1238) Behavior changes: * Passing `--resolver X` with a Stack command which forces creation of a global project config, will pass resolver X into the initial config. See [#2579](https://github.com/commercialhaskell/stack/issues/2229). * Switch the "Run from outside project" messages to debug-level, to avoid spamming users in the normal case of non-project usage * If a remote package is specified (such as a Git repo) without an explicit `extra-dep` setting, a warning is given to the user to provide one explicitly. Other enhancements: * `stack haddock` now supports `--haddock-internal`. See [#2229](https://github.com/commercialhaskell/stack/issues/2229) * Add support for `system-ghc` and `install-ghc` fields to `stack config set` command. * Add `ghc-build` option to override autodetected GHC build to use (e.g. gmp4, tinfo6, nopie) on Linux. * `stack setup` detects systems where gcc enables PIE by default (such as Ubuntu 16.10 and Hardened Gentoo) and adjusts the GHC `configure` options accordingly. [#2542](https://github.com/commercialhaskell/stack/issues/2542) * Upload to Hackage with HTTP digest instead of HTTP basic. * Make `stack list-dependencies` understand all of the `stack dot` options too. * Add the ability for `stack list-dependencies` to list dependency licenses by passing the `--license` flag. * Dump logs that contain warnings for any local non-dependency packages [#2545](https://github.com/commercialhaskell/stack/issues/2545) * Add the `dump-logs` config option and `--dump-logs` command line option to get full build output on the console. [#426](https://github.com/commercialhaskell/stack/issues/426) * Add the `--open` option to "stack hpc report" command, causing the report to be opened in the browser. * The `stack config set` command now accepts a `--global` flag for suitable fields which causes it to modify the global user configuration (`~/.stack/config.yaml`) instead of the project configuration. [#2675](https://github.com/commercialhaskell/stack/pull/2675) * Information on the latest available snapshots is now downloaded from S3 instead of stackage.org, increasing reliability in case of stackage.org outages. [#2653](https://github.com/commercialhaskell/stack/pull/2653) * `stack dot` and `stack list-dependencies` now take targets and flags. [#1919](https://github.com/commercialhaskell/stack/issues/1919) * Deprecate `stack setup --stack-setup-yaml` for `--setup-info-yaml` based on discussion in [#2647](https://github.com/commercialhaskell/stack/issues/2647). * The `--main-is` flag for GHCI now implies the TARGET, fixing [#1845](https://github.com/commercialhaskell/stack/issues/1845). * `stack ghci` no longer takes all build options, as many weren't useful [#2199](https://github.com/commercialhaskell/stack/issues/2199) * `--no-time-in-log` option, to make verbose logs more diffable [#2727](https://github.com/commercialhaskell/stack/issues/2727) * `--color` option added to override auto-detection of ANSI support [#2725](https://github.com/commercialhaskell/stack/issues/2725) * Missing extra-deps are now warned about, adding a degree of typo detection [#1521](https://github.com/commercialhaskell/stack/issues/1521) * No longer warns about missing build-tools if they are on the PATH. [#2235](https://github.com/commercialhaskell/stack/issues/2235) * Replace enclosed-exceptions with safe-exceptions. [#2768](https://github.com/commercialhaskell/stack/issues/2768) * The install location for GHC and other programs can now be configured with the `local-programs-path` option in `config.yaml`. [#1644](https://github.com/commercialhaskell/stack/issues/1644) * Added option to add nix dependencies as nix GC roots * Proper pid 1 (init) process for `stack exec` with Docker * Dump build logs if they contain warnings. [#2545](https://github.com/commercialhaskell/stack/issues/2545) * Docker: redirect stdout of `docker pull` to stderr so that it will not interfere with output of other commands. * Nix & docker can be activated at the same time, in order to run stack in a nix-shell in a container, preferably from an image already containing the nix dependencies in its /nix/store * Stack/nix: Dependencies can be added as nix GC roots, so they are not removed when running `nix-collect-garbage` Bug fixes: * Fixed a gnarly bug where programs and package tarballs sometimes have corrupted downloads. See [#2657](https://github.com/commercialhaskell/stack/issues/2568). * Add proper support for non-ASCII characters in file paths for the `sdist` command. See [#2549](https://github.com/commercialhaskell/stack/issues/2549) * Never treat `extra-dep` local packages as targets. This ensures things like test suites are not run for these packages, and that build output is not hidden due to their presence. * Fix a resource leak in `sinkProcessStderrStdout` which could affect much of the codebase, in particular copying precompiled packages. [#1979](https://github.com/commercialhaskell/stack/issues/1979) * Docker: ensure that interrupted extraction process does not cause corrupt file when downloading a Docker-compatible Stack executable [#2568](https://github.com/commercialhaskell/stack/issues/2568) * Fixed running `stack hpc report` on package targets. [#2664](https://github.com/commercialhaskell/stack/issues/2664) * Fix a long-standing performance regression where stack would parse the .dump-hi files of the library components of local packages twice. [#2658](https://github.com/commercialhaskell/stack/pull/2658) * Fixed a regression in "stack ghci --no-load", where it would prompt for a main module to load. [#2603](https://github.com/commercialhaskell/stack/pull/2603) * Build Setup.hs files with the threaded RTS, mirroring the behavior of cabal-install and enabling more complex build systems in those files. * Fixed a bug in passing along `--ghc-options` to ghcjs. They were being provided as `--ghc-options` to Cabal, when it needs to be `--ghcjs-options`. [#2714](https://github.com/commercialhaskell/stack/issues/2714) * Launch Docker from the project root regardless of the working directory Stack is invoked from. This means paths relative to the project root (e.g. environment files) can be specified in `stack.yaml`'s docker `run-args`. * `stack setup --reinstall` now behaves as expected. [#2554](https://github.com/commercialhaskell/stack/issues/2554) ## 1.2.0 Release notes: * On many Un*x systems, Stack can now be installed with a simple one-liner: wget -qO- https://get.haskellstack.org/ | sh * The fix for [#2175](https://github.com/commercialhaskell/stack/issues/2175) entails that stack must perform a full clone of a large Git repo of Hackage meta-information. The total download size is about 200 MB. Please be aware of this when upgrading your stack installation. * If you use Mac OS X, you may want to delay upgrading to macOS Sierra as there are reports of GHC panics when building some packages (including Stack itself). See [#2577](https://github.com/commercialhaskell/stack/issues/2577) * This version of Stack does not build on ARM or PowerPC systems (see [store#37](https://github.com/fpco/store/issues/37)). Please stay with version 1.1.2 for now on those architectures. This will be rectified soon! * We are now releasing a [statically linked Stack binary for 64-bit Linux](https://www.stackage.org/stack/linux-x86_64-static). Please try it and let us know if you run into any trouble on your platform. * We are planning some changes to our Linux releases, including dropping our Ubuntu, Debian, CentOS, and Fedora package repositories and switching to statically linked binaries. We would value your feedback in [#2534](https://github.com/commercialhaskell/stack/issues/2534). Major changes: * Add `stack hoogle` command. [#55](https://github.com/commercialhaskell/stack/issues/55) * Support for absolute file path in `url` field of `setup-info` or `--ghc-bindist` * Add support for rendering GHCi scripts targeting different GHCi like applications [#2457](https://github.com/commercialhaskell/stack/pull/2457) Behavior changes: * Remove `stack ide start` and `stack ide load-targets` commands. [#2178](https://github.com/commercialhaskell/stack/issues/2178) * Support .buildinfo files in `stack ghci`. [#2242](https://github.com/commercialhaskell/stack/pull/2242) * Support -ferror-spans syntax in GHC error messages. * Avoid unpacking ghc to `/tmp` [#996](https://github.com/commercialhaskell/stack/issues/996) * The Linux `gmp4` GHC bindist is no longer considered a full-fledged GHC variant and can no longer be specified using the `ghc-variant` option, and instead is treated more like a slightly different platform. Other enhancements: * Use the `store` package for binary serialization of most caches. * Only require minor version match for Docker stack exe. This way, we can make patch releases for version bounds and similar build issues without needing to upload new binaries for Docker. * Stack/Nix: Passes the right ghc derivation as an argument to the `shell.nix` when a custom `shell.nix` is used See [#2243](https://github.com/commercialhaskell/stack/issues/2243) * Stack/Nix: Sets `LD_LIBRARY_PATH` so packages using C libs for Template Haskell can work (See _e.g._ [this HaskellR issue](https://github.com/tweag/HaskellR/issues/253)) * Parse CLI arguments and configuration files into less permissive types, improving error messages for bad inputs. [#2267](https://github.com/commercialhaskell/stack/issues/2267) * Add the ability to explictly specify a gcc executable. [#593](https://github.com/commercialhaskell/stack/issues/593) * Nix: No longer uses LTS mirroring in nixpkgs. Gives to nix-shell a derivation like `haskell.compiler.ghc801` See [#2259](https://github.com/commercialhaskell/stack/issues/2259) * Perform some subprocesses during setup concurrently, slightly speeding up most commands. [#2346](https://github.com/commercialhaskell/stack/pull/2346) * `stack setup` no longer unpacks to the system temp dir on posix systems. [#996](https://github.com/commercialhaskell/stack/issues/996) * `stack setup` detects libtinfo6 and ncurses6 and can download alternate GHC bindists [#257](https://github.com/commercialhaskell/stack/issues/257) [#2302](https://github.com/commercialhaskell/stack/issues/2302). * `stack setup` detects Linux ARMv7 downloads appropriate GHC bindist [#2103](https://github.com/commercialhaskell/stack/issues/2103) * Custom `stack` binaries list dependency versions in output for `--version`. See [#2222](https://github.com/commercialhaskell/stack/issues/2222) and [#2450](https://github.com/commercialhaskell/stack/issues/2450). * Use a pretty printer to output dependency resolution errors. [#1912](https://github.com/commercialhaskell/stack/issues/1912) * Remove the `--os` flag [#2227](https://github.com/commercialhaskell/stack/issues/2227) * Add 'netbase' and 'ca-certificates' as dependency for .deb packages. [#2293](https://github.com/commercialhaskell/stack/issues/2293). * Add `stack ide targets` command. * Enhance debug logging with subprocess timings. * Pretty-print YAML parse errors [#2374](https://github.com/commercialhaskell/stack/issues/2374) * Clarify confusing `stack setup` output [#2314](https://github.com/commercialhaskell/stack/issues/2314) * Delete `Stack.Types` multimodule to improve build times [#2405](https://github.com/commercialhaskell/stack/issues/2405) * Remove spurious newlines in build logs [#2418](https://github.com/commercialhaskell/stack/issues/2418) * Interpreter: Provide a way to hide implicit packages [#1208](https://github.com/commercialhaskell/stack/issues/1208) * Check executability in exec lookup [#2489](https://github.com/commercialhaskell/stack/issues/2489) Bug fixes: * Fix cabal warning about use of a deprecated cabal flag [#2350](https://github.com/commercialhaskell/stack/issues/2350) * Support most executable extensions on Windows [#2225](https://github.com/commercialhaskell/stack/issues/2225) * Detect resolver change in `stack solver` [#2252](https://github.com/commercialhaskell/stack/issues/2252) * Fix a bug in docker image creation where the wrong base image was selected [#2376](https://github.com/commercialhaskell/stack/issues/2376) * Ignore special entries when unpacking tarballs [#2361](https://github.com/commercialhaskell/stack/issues/2361) * Fixes src directory pollution of `style.css` and `highlight.js` with GHC 8's haddock [#2429](https://github.com/commercialhaskell/stack/issues/2429) * Handle filepaths with spaces in `stack ghci` [#2266](https://github.com/commercialhaskell/stack/issues/2266) * Apply ghc-options to snapshot packages [#2289](https://github.com/commercialhaskell/stack/issues/2289) * stack sdist: Fix timestamp in tarball [#2394](https://github.com/commercialhaskell/stack/pull/2394) * Allow global Stack arguments with a script [#2316](https://github.com/commercialhaskell/stack/issues/2316) * Inconsistency between ToJSON and FromJSON instances of PackageLocation [#2412](https://github.com/commercialhaskell/stack/pull/2412) * Perform Unicode normalization on filepaths [#1810](https://github.com/commercialhaskell/stack/issues/1810) * Solver: always keep ghc wired-in as hard constraints [#2453](https://github.com/commercialhaskell/stack/issues/2453) * Support OpenBSD's tar where possible, require GNU tar for xz support [#2283](https://github.com/commercialhaskell/stack/issues/2283) * Fix using --coverage with Cabal-1.24 [#2424](https://github.com/commercialhaskell/stack/issues/2424) * When marking exe installed, remove old version [#2373](https://github.com/commercialhaskell/stack/issues/2373) * Stop truncating all-cabal-hashes git repo [#2175](https://github.com/commercialhaskell/stack/issues/2175) * Handle non-ASCII filenames on Windows [#2491](https://github.com/commercialhaskell/stack/issues/2491) * Avoid using multiple versions of a package in script interpreter by passing package-id to ghc/runghc [#1957](https://github.com/commercialhaskell/stack/issues/1957) * Only pre-load compiler version when using nix integration [#2459](https://github.com/commercialhaskell/stack/issues/2459) * Solver: parse cabal errors also on Windows [#2502](https://github.com/commercialhaskell/stack/issues/2502) * Allow exec and ghci commands in interpreter mode. Scripts can now automatically open in the repl by using `exec ghci` instead of `runghc` in the shebang command. [#2510](https://github.com/commercialhaskell/stack/issues/2510) * Now consider a package to be dirty when an extra-source-file is changed. See [#2040](https://github.com/commercialhaskell/stack/issues/2040) ## 1.1.2 Release notes: * Official FreeBSD binaries are [now available](http://docs.haskellstack.org/en/stable/install_and_upgrade/#freebsd) [#1253](https://github.com/commercialhaskell/stack/issues/1253). Major changes: * Extensible custom snapshots implemented. These allow you to define snapshots which extend other snapshots. See [#863](https://github.com/commercialhaskell/stack/issues/863). Local file custom snapshots can now be safely updated without changing their name. Remote custom snapshots should still be treated as immutable. Behavior changes: * `stack path --compiler` was added in the last release, to yield a path to the compiler. Unfortunately, `--compiler` is a global option that is useful to use with `stack path`. The same functionality is now provided by `stack path --compiler-exe`. See [#2123](https://github.com/commercialhaskell/stack/issues/2123) * For packages specified in terms of a git or hg repo, the hash used in the location has changed. This means that existing downloads from older stack versions won't be used. This is a side-effect of the fix to [#2133](https://github.com/commercialhaskell/stack/issues/2133) * `stack upgrade` no longer pays attention to local stack.yaml files, just the global config and CLI options. [#1392](https://github.com/commercialhaskell/stack/issues/1392) * `stack ghci` now uses `:add` instead of `:load`, making it potentially work better with user scripts. See [#1888](https://github.com/commercialhaskell/stack/issues/1888) Other enhancements: * Grab Cabal files via Git SHA to avoid regressions from Hackage revisions [#2070](https://github.com/commercialhaskell/stack/pull/2070) * Custom snapshots now support `ghc-options`. * Package git repos are now re-used rather than re-cloned. See [#1620](https://github.com/commercialhaskell/stack/issues/1620) * `DESTDIR` is filtered from environment when installing GHC. See [#1460](https://github.com/commercialhaskell/stack/issues/1460) * `stack haddock` now supports `--hadock-arguments`. See [#2144](https://github.com/commercialhaskell/stack/issues/2144) * Signing: warn if GPG_TTY is not set as per `man gpg-agent` Bug fixes: * Now ignore project config when doing `stack init` or `stack new`. See [#2110](https://github.com/commercialhaskell/stack/issues/2110) * Packages specified by git repo can now have submodules. See [#2133](https://github.com/commercialhaskell/stack/issues/2133) * Fix of hackage index fetch retry. See re-opening of [#1418](https://github.com/commercialhaskell/stack/issues/1418#issuecomment-217633843) * HPack now picks up changes to filesystem other than package.yaml. See [#2051](https://github.com/commercialhaskell/stack/issues/2051) * "stack solver" no longer suggests --omit-packages. See [#2031](https://github.com/commercialhaskell/stack/issues/2031) * Fixed an issue with building Cabal's Setup.hs. See [#1356](https://github.com/commercialhaskell/stack/issues/1356) * Package dirtiness now pays attention to deleted files. See [#1841](https://github.com/commercialhaskell/stack/issues/1841) * `stack ghci` now uses `extra-lib-dirs` and `extra-include-dirs`. See [#1656](https://github.com/commercialhaskell/stack/issues/1656) * Relative paths outside of source dir added via `qAddDependentFile` are now checked for dirtiness. See [#1982](https://github.com/commercialhaskell/stack/issues/1982) * Signing: always use `--with-fingerprints` ## 1.1.0 Release notes: * Added Ubuntu 16.04 LTS (xenial) Apt repo. * No longer uploading new versions to Fedora 21 repo. Behavior changes: * Snapshot packages are no longer built with executable profiling. See [#1179](https://github.com/commercialhaskell/stack/issues/1179). * `stack init` now ignores symlinks when searching for cabal files. It also now ignores any directory that begins with `.` (as well as `dist` dirs) - before it would only ignore `.git`, `.stack-work`, and `dist`. * The stack executable is no longer built with `-rtsopts`. Before, when `-rtsopts` was enabled, stack would process `+RTS` options even when intended for some other program, such as when used with `stack exec -- prog +RTS`. See [#2022](https://github.com/commercialhaskell/stack/issues/2022). * The `stack path --ghc-paths` option is deprecated and renamed to `--programs`. `--compiler` is added, which points directly at the compiler used in the current project. `--compiler-bin` points to the compiler's bin dir. * For consistency with the `$STACK_ROOT` environment variable, the `stack path --global-stack-root` flag and the `global-stack-root` field in the output of `stack path` are being deprecated and replaced with the `stack-root` flag and output field. Additionally, the stack root can now be specified via the `--stack-root` command-line flag. See [#1148](https://github.com/commercialhaskell/stack/issues/1148). * `stack sig` GPG-related sub-commands were removed (folded into `upload` and `sdist`) * GPG signing of packages while uploading to Hackage is now the default. Use `upload --no-signature` if you would rather not contribute your package signature. If you don't yet have a GPG keyset, read this [blog post on GPG keys](https://fpcomplete.com/blog/2016/05/stack-security-gnupg-keys). We can add a stack.yaml config setting to disable signing if some people desire it. We hope that people will sign. Later we will be adding GPG signature verification options. * `stack build pkg-1.2.3` will now build even if the snapshot has a different package version - it is treated as an extra-dep. `stack build local-pkg-1.2.3` is an error even if the version number matches the local package [#2028](https://github.com/commercialhaskell/stack/issues/2028). * Having a `nix:` section no longer implies enabling nix build. This allows the user to globally configure whether nix is used (unless the project overrides the default explicitly). See [#1924](https://github.com/commercialhaskell/stack/issues/1924). * Remove deprecated valid-wanted field. * Docker: mount home directory in container [#1949](https://github.com/commercialhaskell/stack/issues/1949). * Deprecate `--local-bin-path` instead `--local-bin`. * `stack image`: allow absolute source paths for `add`. Other enhancements: * `stack haddock --open [PACKAGE]` opens the local haddocks in the browser. * Fix too much rebuilding when enabling/disabling profiling flags. * `stack build pkg-1.0` will now build `pkg-1.0` even if the snapshot specifies a different version (it introduces a temporary extra-dep) * Experimental support for `--split-objs` added [#1284](https://github.com/commercialhaskell/stack/issues/1284). * `git` packages with submodules are supported by passing the `--recursive` flag to `git clone`. * When using [hpack](https://github.com/sol/hpack), only regenerate cabal files when hpack files change. * hpack files can now be used in templates * `stack ghci` now runs ghci as a separate process [#1306](https://github.com/commercialhaskell/stack/issues/1306) * Retry when downloading snapshots and package indices * Many build options are configurable now in `stack.yaml`: ``` build: library-profiling: true executable-profiling: true haddock: true haddock-deps: true copy-bins: true prefetch: true force-dirty: true keep-going: true test: true test-arguments: rerun-tests: true additional-args: ['-fprof'] coverage: true no-run-tests: true bench: true benchmark-opts: benchmark-arguments: -O2 no-run-benchmarks: true reconfigure: true cabal-verbose: true ``` * A number of URLs are now configurable, useful for firewalls. See [#1794](https://github.com/commercialhaskell/stack/issues/1884). * Suggest causes when executables are missing. * Allow `--omit-packages` even without `--solver`. * Improve the generated stack.yaml. * Improve ghci results after :load Main module collision with main file path. * Only load the hackage index if necessary [#1883](https://github.com/commercialhaskell/stack/issues/1883), [#1892](https://github.com/commercialhaskell/stack/issues/1892). * init: allow local packages to be deps of deps [#1965](https://github.com/commercialhaskell/stack/issues/1965). * Always use full fingerprints from GPG [#1952](https://github.com/commercialhaskell/stack/issues/1952). * Default to using `gpg2` and fall back to `gpg` [#1976](https://github.com/commercialhaskell/stack/issues/1976). * Add a flag for --verbosity silent. * Add `haddock --open` flag [#1396](https://github.com/commercialhaskell/stack/issues/1396). Bug fixes: * Package tarballs would fail to unpack. [#1884](https://github.com/commercialhaskell/stack/issues/1884). * Fixed errant warnings about missing modules, after deleted and removed from cabal file [#921](https://github.com/commercialhaskell/stack/issues/921) [#1805](https://github.com/commercialhaskell/stack/issues/1805). * Now considers a package to dirty when the hpack file is changed [#1819](https://github.com/commercialhaskell/stack/issues/1819). * Nix: cancelling a stack build now exits properly rather than dropping into a nix-shell [#1778](https://github.com/commercialhaskell/stack/issues/1778). * `allow-newer: true` now causes `--exact-configuration` to be passed to Cabal. See [#1579](https://github.com/commercialhaskell/stack/issues/1579). * `stack solver` no longer fails with `InvalidRelFile` for relative package paths including `..`. See [#1954](https://github.com/commercialhaskell/stack/issues/1954). * Ignore emacs lock files when finding .cabal [#1897](https://github.com/commercialhaskell/stack/issues/1897). * Use lenient UTF-8 decode for build output [#1945](https://github.com/commercialhaskell/stack/issues/1945). * Clear index cache whenever index updated [#1962](https://github.com/commercialhaskell/stack/issues/1962). * Fix: Building a container image drops a .stack-work dir in the current working (sub)directory [#1975](https://github.com/commercialhaskell/stack/issues/1975). * Fix: Rebuilding when disabling profiling [#2023](https://github.com/commercialhaskell/stack/issues/2023). ## 1.0.4.3 Bug fixes: * Don't delete contents of ~/.ssh when using `stack clean --full` with Docker enabled [#2000](https://github.com/commercialhaskell/stack/issues/2000) ## 1.0.4.2 Build with path-io-1.0.0. There are no changes in behaviour from 1.0.4, so no binaries are released for this version. ## 1.0.4.1 Fixes build with aeson-0.11.0.0. There are no changes in behaviour from 1.0.4, so no binaries are released for this version. ## 1.0.4 Major changes: * Some notable changes in `stack init`: * Overall it should now be able to initialize almost all existing cabal packages out of the box as long as the package itself is consistently defined. * Choose the best possible snapshot and add extra dependencies on top of a snapshot resolver rather than a compiler resolver - [#1583](https://github.com/commercialhaskell/stack/pull/1583) * Automatically omit a package (`--omit-packages`) when it is compiler incompatible or when there are packages with conflicting dependency requirements - [#1674](https://github.com/commercialhaskell/stack/pull/1674). * Some more changes for a better user experience. Please refer to the doc guide for details. * Add support for hpack, alternative package description format [#1679](https://github.com/commercialhaskell/stack/issues/1679) Other enhancements: * Docker: pass ~/.ssh and SSH auth socket into container, so that git repos work [#1358](https://github.com/commercialhaskell/stack/issues/1358). * Docker: strip suffix from docker --version. [#1653](https://github.com/commercialhaskell/stack/issues/1653) * Docker: pass USER and PWD environment variables into container. * On each run, stack will test the stack root directory (~/.stack), and the project and package work directories (.stack-work) for whether they are owned by the current user and abort if they are not. This precaution can be disabled with the `--allow-different-user` flag or `allow-different-user` option in the global config (~/.stack/config.yaml). [#471](https://github.com/commercialhaskell/stack/issues/471) * Added `stack clean --full` option for full working dir cleanup. * YAML config: support Zip archives. * Redownload build plan if parsing fails [#1702](https://github.com/commercialhaskell/stack/issues/1702). * Give mustache templates access to a 'year' tag [#1716](https://github.com/commercialhaskell/stack/pull/1716). * Have "stack ghci" warn about module name aliasing. * Add "stack ghci --load-local-deps". * Build Setup.hs with -rtsopts [#1687](https://github.com/commercialhaskell/stack/issues/1687). * `stack init` accepts a list of directories. * Add flag infos to DependencyPlanFailures (for better error output in case of flags) [#713](https://github.com/commercialhaskell/stack/issues/713) * `stack new --bare` complains for overwrites, and add `--force` option [#1597](https://github.com/commercialhaskell/stack/issues/1597). Bug fixes: * Previously, `stack ghci` would fail with `cannot satisfy -package-id` when the implicit build step changes the package key of some dependency. * Fix: Building with ghcjs: "ghc-pkg: Prelude.chr: bad argument: 2980338" [#1665](https://github.com/commercialhaskell/stack/issues/1665). * Fix running test / bench with `--profile` / `--trace`. * Fix: build progress counter is no longer visible [#1685](https://github.com/commercialhaskell/stack/issues/1685). * Use "-RTS" w/ profiling to allow extra args [#1772](https://github.com/commercialhaskell/stack/issues/1772). * Fix withUnpackedTarball7z to find name of srcDir after unpacking (fixes `stack setup` fails for ghcjs project on windows) [#1774](https://github.com/commercialhaskell/stack/issues/1774). * Add space before auto-generated bench opts (makes profiling options work uniformly for applications and benchmark suites) [#1771](https://github.com/commercialhaskell/stack/issues/1771). * Don't try to find plugin if it resembles flag. * Setup.hs changes cause package dirtiness [#1711](https://github.com/commercialhaskell/stack/issues/1711). * Send "stack templates" output to stdout [#1792](https://github.com/commercialhaskell/stack/issues/1792). ## 1.0.2 Release notes: - Arch Linux: Stack has been adopted into the [official community repository](https://www.archlinux.org/packages/community/x86_64/stack/), so we will no longer be updating the AUR with new versions. See the [install/upgrade guide](http://docs.haskellstack.org/en/stable/install_and_upgrade/#arch-linux) for current download instructions. Major changes: - `stack init` and `solver` overhaul [#1583](https://github.com/commercialhaskell/stack/pull/1583) Other enhancements: - Disable locale/codepage hacks when GHC >=7.10.3 [#1552](https://github.com/commercialhaskell/stack/issues/1552) - Specify multiple images to build for `stack image container` [docs](http://docs.haskellstack.org/en/stable/yaml_configuration/#image) - Specify which executables to include in images for `stack image container` [docs](http://docs.haskellstack.org/en/stable/yaml_configuration/#image) - Docker: pass supplemantary groups and umask into container - If git fetch fails wipe the directory and try again from scratch [#1418](https://github.com/commercialhaskell/stack/issues/1418) - Warn if newly installed executables won't be available on the PATH [#1362](https://github.com/commercialhaskell/stack/issues/1362) - stack.yaml: for `stack image container`, specify multiple images to generate, and which executables should be added to those images - GHCI: add interactive Main selection [#1068](https://github.com/commercialhaskell/stack/issues/1068) - Care less about the particular name of a GHCJS sdist folder [#1622](https://github.com/commercialhaskell/stack/issues/1622) - Unified Enable/disable help messaging [#1613](https://github.com/commercialhaskell/stack/issues/1613) Bug fixes: - Don't share precompiled packages between GHC/platform variants and Docker [#1551](https://github.com/commercialhaskell/stack/issues/1551) - Properly redownload corrupted downloads with the correct file size. [Mailing list discussion](https://groups.google.com/d/msg/haskell-stack/iVGDG5OHYxs/FjUrR5JsDQAJ) - Gracefully handle invalid paths in error/warning messages [#1561](https://github.com/commercialhaskell/stack/issues/1561) - Nix: select the correct GHC version corresponding to the snapshot even when an abstract resolver is passed via `--resolver` on the command-line. [#1641](https://github.com/commercialhaskell/stack/issues/1641) - Fix: Stack does not allow using an external package from ghci [#1557](https://github.com/commercialhaskell/stack/issues/1557) - Disable ambiguous global '--resolver' option for 'stack init' [#1531](https://github.com/commercialhaskell/stack/issues/1531) - Obey `--no-nix` flag - Fix: GHCJS Execute.hs: Non-exhaustive patterns in lambda [#1591](https://github.com/commercialhaskell/stack/issues/1591) - Send file-watch and sticky logger messages to stderr [#1302](https://github.com/commercialhaskell/stack/issues/1302) [#1635](https://github.com/commercialhaskell/stack/issues/1635) - Use globaldb path for querying Cabal version [#1647](https://github.com/commercialhaskell/stack/issues/1647) ## 1.0.0 Release notes: * We're calling this version 1.0.0 in preparation for Stackage LTS 4. Note, however, that this does not mean the code's API will be stable as this is primarily an end-user tool. Enhancements: * Added flag `--profile` flag: passed with `stack build`, it will enable profiling, and for `--bench` and `--test` it will generate a profiling report by passing `+RTS -p` to the executable(s). Great for using like `stack build --bench --profile` (remember that enabling profile will slow down your benchmarks by >4x). Run `stack build --bench` again to disable the profiling and get proper speeds * Added flag `--trace` flag: just like `--profile`, it enables profiling, but instead of generating a report for `--bench` and `--test`, prints out a stack trace on exception. Great for using like `stack build --test --trace` * Nix: all options can be overriden on command line [#1483](https://github.com/commercialhaskell/stack/issues/1483) * Nix: build environments (shells) are now pure by default. * Make verbosity silent by default in script interpreter mode [#1472](https://github.com/commercialhaskell/stack/issues/1472) * Show a message when resetting git commit fails [#1453](https://github.com/commercialhaskell/stack/issues/1453) * Improve Unicode handling in project/package names [#1337](https://github.com/commercialhaskell/stack/issues/1337) * Fix ambiguity between a stack command and a filename to execute (prefer `stack` subcommands) [#1471](https://github.com/commercialhaskell/stack/issues/1471) * Support multi line interpreter directive comments [#1394](https://github.com/commercialhaskell/stack/issues/1394) * Handle space separated pids in ghc-pkg dump (for GHC HEAD) [#1509](https://github.com/commercialhaskell/stack/issues/1509) * Add ghci --no-package-hiding option [#1517](https://github.com/commercialhaskell/stack/issues/1517) * `stack new` can download templates from URL [#1466](https://github.com/commercialhaskell/stack/issues/1466) Bug fixes: * Nix: stack exec options are passed properly to the stack sub process [#1538](https://github.com/commercialhaskell/stack/issues/1538) * Nix: specifying a shell-file works in any current working directory [#1547](https://github.com/commercialhaskell/stack/issues/1547) * Nix: use `--resolver` argument * Docker: fix missing image message and '--docker-auto-pull' * No HTML escaping for "stack new" template params [#1475](https://github.com/commercialhaskell/stack/issues/1475) * Set permissions for generated .ghci script [#1480](https://github.com/commercialhaskell/stack/issues/1480) * Restrict commands allowed in interpreter mode [#1504](https://github.com/commercialhaskell/stack/issues/1504) * stack ghci doesn't see preprocessed files for executables [#1347](https://github.com/commercialhaskell/stack/issues/1347) * All test suites run even when only one is requested [#1550](https://github.com/commercialhaskell/stack/pull/1550) * Edge cases in broken templates give odd errors [#1535](https://github.com/commercialhaskell/stack/issues/1535) * Fix test coverage bug on windows ## 0.1.10.1 Bug fixes: * `stack image container` did not actually build an image [#1473](https://github.com/commercialhaskell/stack/issues/1473) ## 0.1.10.0 Release notes: * The Stack home page is now at [haskellstack.org](http://haskellstack.org), which shows the documentation rendered by readthedocs.org. Note: this has necessitated some changes to the links in the documentation's markdown source code, so please check the links on the website before submitting a PR to fix them. * The locations of the [Ubuntu](http://docs.haskellstack.org/en/stable/install_and_upgrade/#ubuntu) and [Debian](http://docs.haskellstack.org/en/stable/install_and_upgrade/#debian) package repositories have changed to have correct URL semantics according to Debian's guidelines [#1378](https://github.com/commercialhaskell/stack/issues/1378). The old locations will continue to work for some months, but we suggest that you adjust your `/etc/apt/sources.list.d/fpco.list` to the new location to avoid future disruption. * [openSUSE and SUSE Linux Enterprise](http://docs.haskellstack.org/en/stable/install_and_upgrade/#suse) packages are now available, thanks to [@mimi1vx](https://github.com/mimi1vx). Note: there will be some lag before these pick up new versions, as they are based on Stackage LTS. Major changes: * Support for building inside a Nix-shell providing system dependencies [#1285](https://github.com/commercialhaskell/stack/pull/1285) * Add optional GPG signing on `stack upload --sign` or with `stack sig sign ...` Other enhancements: * Print latest applicable version of packages on conflicts [#508](https://github.com/commercialhaskell/stack/issues/508) * Support for packages located in Mercurial repositories [#1397](https://github.com/commercialhaskell/stack/issues/1397) * Only run benchmarks specified as build targets [#1412](https://github.com/commercialhaskell/stack/issues/1412) * Support git-style executable fall-through (`stack something` executes `stack-something` if present) [#1433](https://github.com/commercialhaskell/stack/issues/1433) * GHCi now loads intermediate dependencies [#584](https://github.com/commercialhaskell/stack/issues/584) * `--work-dir` option for overriding `.stack-work` [#1178](https://github.com/commercialhaskell/stack/issues/1178) * Support `detailed-0.9` tests [#1429](https://github.com/commercialhaskell/stack/issues/1429) * Docker: improved POSIX signal proxying to containers [#547](https://github.com/commercialhaskell/stack/issues/547) Bug fixes: * Show absolute paths in error messages in multi-package builds [#1348](https://github.com/commercialhaskell/stack/issues/1348) * Docker-built binaries and libraries in different path [#911](https://github.com/commercialhaskell/stack/issues/911) [#1367](https://github.com/commercialhaskell/stack/issues/1367) * Docker: `--resolver` argument didn't effect selected image tag * GHCi: Spaces in filepaths caused module loading issues [#1401](https://github.com/commercialhaskell/stack/issues/1401) * GHCi: cpp-options in cabal files weren't used [#1419](https://github.com/commercialhaskell/stack/issues/1419) * Benchmarks couldn't be run independently of eachother [#1412](https://github.com/commercialhaskell/stack/issues/1412) * Send output of building setup to stderr [#1410](https://github.com/commercialhaskell/stack/issues/1410) ## 0.1.8.0 Major changes: * GHCJS can now be used with stackage snapshots via the new `compiler` field. * Windows installers are now available: [download them here](http://docs.haskellstack.org/en/stable/install_and_upgrade/#windows) [#613](https://github.com/commercialhaskell/stack/issues/613) * Docker integration works with non-FPComplete generated images [#531](https://github.com/commercialhaskell/stack/issues/531) Other enhancements: * Added an `allow-newer` config option [#922](https://github.com/commercialhaskell/stack/issues/922) [#770](https://github.com/commercialhaskell/stack/issues/770) * When a Hackage revision invalidates a build plan in a snapshot, trust the snapshot [#770](https://github.com/commercialhaskell/stack/issues/770) * Added a `stack config set resolver RESOLVER` command. Part of work on [#115](https://github.com/commercialhaskell/stack/issues/115) * `stack setup` can now install GHCJS on windows. See [#1145](https://github.com/commercialhaskell/stack/issues/1145) and [#749](https://github.com/commercialhaskell/stack/issues/749) * `stack hpc report` command added, which generates reports for HPC tix files * `stack ghci` now accepts all the flags accepted by `stack build`. See [#1186](https://github.com/commercialhaskell/stack/issues/1186) * `stack ghci` builds the project before launching GHCi. If the build fails, optimistically launch GHCi anyway. Use `stack ghci --no-build` option to disable [#1065](https://github.com/commercialhaskell/stack/issues/1065) * `stack ghci` now detects and warns about various circumstances where it is liable to fail. See [#1270](https://github.com/commercialhaskell/stack/issues/1270) * Added `require-docker-version` configuration option * Packages will now usually be built along with their tests and benchmarks. See [#1166](https://github.com/commercialhaskell/stack/issues/1166) * Relative `local-bin-path` paths will be relative to the project's root directory, not the current working directory. [#1340](https://github.com/commercialhaskell/stack/issues/1340) * `stack clean` now takes an optional `[PACKAGE]` argument for use in multi-package projects. See [#583](https://github.com/commercialhaskell/stack/issues/583) * Ignore cabal_macros.h as a dependency [#1195](https://github.com/commercialhaskell/stack/issues/1195) * Pad timestamps and show local time in --verbose output [#1226](https://github.com/commercialhaskell/stack/issues/1226) * GHCi: Import all modules after loading them [#995](https://github.com/commercialhaskell/stack/issues/995) * Add subcommand aliases: `repl` for `ghci`, and `runhaskell` for `runghc` [#1241](https://github.com/commercialhaskell/stack/issues/1241) * Add typo recommendations for unknown package identifiers [#158](https://github.com/commercialhaskell/stack/issues/158) * Add `stack path --local-hpc-root` option * Overhaul dependencies' haddocks copying [#1231](https://github.com/commercialhaskell/stack/issues/1231) * Support for extra-package-dbs in 'stack ghci' [#1229](https://github.com/commercialhaskell/stack/pull/1229) * `stack new` disallows package names with "words" consisting solely of numbers [#1336](https://github.com/commercialhaskell/stack/issues/1336) * `stack build --fast` turns off optimizations * Show progress while downloading package index [#1223](https://github.com/commercialhaskell/stack/issues/1223). Bug fixes: * Fix: Haddocks not copied for dependencies [#1105](https://github.com/commercialhaskell/stack/issues/1105) * Fix: Global options did not work consistently after subcommand [#519](https://github.com/commercialhaskell/stack/issues/519) * Fix: 'stack ghci' doesn't notice that a module got deleted [#1180](https://github.com/commercialhaskell/stack/issues/1180) * Rebuild when cabal file is changed * Fix: Paths in GHC warnings not canonicalized, nor those for packages in subdirectories or outside the project root [#1259](https://github.com/commercialhaskell/stack/issues/1259) * Fix: unlisted files in tests and benchmarks trigger extraneous second build [#838](https://github.com/commercialhaskell/stack/issues/838) ## 0.1.6.0 Major changes: * `stack setup` now supports building and booting GHCJS from source tarball. * On Windows, build directories no longer display "pretty" information (like x86_64-windows/Cabal-1.22.4.0), but rather a hash of that content. The reason is to avoid the 260 character path limitation on Windows. See [#1027](https://github.com/commercialhaskell/stack/pull/1027) * Rename config files and clarify their purposes [#969](https://github.com/commercialhaskell/stack/issues/969) * `~/.stack/stack.yaml` --> `~/.stack/config.yaml` * `~/.stack/global` --> `~/.stack/global-project` * `/etc/stack/config` --> `/etc/stack/config.yaml` * Old locations still supported, with deprecation warnings * New command "stack eval CODE", which evaluates to "stack exec ghc -- -e CODE". Other enhancements: * No longer install `git` on Windows [#1046](https://github.com/commercialhaskell/stack/issues/1046). You can still get this behavior by running the following yourself: `stack exec -- pacman -Sy --noconfirm git`. * Typing enter during --file-watch triggers a rebuild [#1023](https://github.com/commercialhaskell/stack/pull/1023) * Use Haddock's `--hyperlinked-source` (crosslinked source), if available [#1070](https://github.com/commercialhaskell/stack/pull/1070) * Use Stack-installed GHCs for `stack init --solver` [#1072](https://github.com/commercialhaskell/stack/issues/1072) * New experimental `stack query` command [#1087](https://github.com/commercialhaskell/stack/issues/1087) * By default, stack no longer rebuilds a package due to GHC options changes. This behavior can be tweaked with the `rebuild-ghc-options` setting. [#1089](https://github.com/commercialhaskell/stack/issues/1089) * By default, ghc-options are applied to all local packages, not just targets. This behavior can be tweaked with the `apply-ghc-options` setting. [#1089](https://github.com/commercialhaskell/stack/issues/1089) * Docker: download or override location of stack executable to re-run in container [#974](https://github.com/commercialhaskell/stack/issues/974) * Docker: when Docker Engine is remote, don't run containerized processes as host's UID/GID [#194](https://github.com/commercialhaskell/stack/issues/194) * Docker: `set-user` option to enable/disable running containerized processes as host's UID/GID [#194](https://github.com/commercialhaskell/stack/issues/194) * Custom Setup.hs files are now precompiled instead of interpreted. This should be a major performance win for certain edge cases (biggest example: [building Cabal itself](https://github.com/commercialhaskell/stack/issues/1041)) while being either neutral or a minor slowdown for more common cases. * `stack test --coverage` now also generates a unified coverage report for multiple test-suites / packages. In the unified report, test-suites can contribute to the coverage of other packages. Bug fixes: * Ignore stack-built executables named `ghc` [#1052](https://github.com/commercialhaskell/stack/issues/1052) * Fix quoting of output failed command line arguments * Mark executable-only packages as installed when copied from cache [#1043](https://github.com/commercialhaskell/stack/pull/1043) * Canonicalize temporary directory paths [#1047](https://github.com/commercialhaskell/stack/pull/1047) * Put code page fix inside the build function itself [#1066](https://github.com/commercialhaskell/stack/issues/1066) * Add `explicit-setup-deps` option [#1110](https://github.com/commercialhaskell/stack/issues/1110), and change the default to the old behavior of using any package in the global and snapshot database [#1025](https://github.com/commercialhaskell/stack/issues/1025) * Precompiled cache checks full package IDs on Cabal < 1.22 [#1103](https://github.com/commercialhaskell/stack/issues/1103) * Pass -package-id to ghci [#867](https://github.com/commercialhaskell/stack/issues/867) * Ignore global packages when copying precompiled packages [#1146](https://github.com/commercialhaskell/stack/issues/1146) ## 0.1.5.0 Major changes: * On Windows, we now use a full MSYS2 installation in place of the previous PortableGit. This gives you access to the pacman package manager for more easily installing libraries. * Support for custom GHC binary distributions [#530](https://github.com/commercialhaskell/stack/issues/530) * `ghc-variant` option in stack.yaml to specify the variant (also `--ghc-variant` command-line option) * `setup-info` in stack.yaml, to specify where to download custom binary distributions (also `--ghc-bindist` command-line option) * Note: On systems with libgmp4 (aka `libgmp.so.3`), such as CentOS 6, you may need to re-run `stack setup` due to the centos6 GHC bindist being treated like a variant * A new `--pvp-bounds` flag to the sdist and upload commands allows automatic adding of PVP upper and/or lower bounds to your dependencies Other enhancements: * Adapt to upcoming Cabal installed package identifier format change [#851](https://github.com/commercialhaskell/stack/issues/851) * `stack setup` takes a `--stack-setup-yaml` argument * `--file-watch` is more discerning about which files to rebuild for [#912](https://github.com/commercialhaskell/stack/issues/912) * `stack path` now supports `--global-pkg-db` and `--ghc-package-path` * `--reconfigure` flag [#914](https://github.com/commercialhaskell/stack/issues/914) [#946](https://github.com/commercialhaskell/stack/issues/946) * Cached data is written with a checksum of its structure [#889](https://github.com/commercialhaskell/stack/issues/889) * Fully removed `--optimizations` flag * Added `--cabal-verbose` flag * Added `--file-watch-poll` flag for polling instead of using filesystem events (useful for running tests in a Docker container while modifying code in the host environment. When code is injected into the container via a volume, the container won't propagate filesystem events). * Give a preemptive error message when `-prof` is given as a GHC option [#1015](https://github.com/commercialhaskell/stack/issues/1015) * Locking is now optional, and will be turned on by setting the `STACK_LOCK` environment variable to `true` [#950](https://github.com/commercialhaskell/stack/issues/950) * Create default stack.yaml with documentation comments and commented out options [#226](https://github.com/commercialhaskell/stack/issues/226) * Out of memory warning if Cabal exits with -9 [#947](https://github.com/commercialhaskell/stack/issues/947) Bug fixes: * Hacky workaround for optparse-applicative issue with `stack exec --help` [#806](https://github.com/commercialhaskell/stack/issues/806) * Build executables for local extra deps [#920](https://github.com/commercialhaskell/stack/issues/920) * copyFile can't handle directories [#942](https://github.com/commercialhaskell/stack/pull/942) * Support for spaces in Haddock interface files [fpco/minghc#85](https://github.com/fpco/minghc/issues/85) * Temporarily building against a "shadowing" local package? [#992](https://github.com/commercialhaskell/stack/issues/992) * Fix Setup.exe name for --upgrade-cabal on Windows [#1002](https://github.com/commercialhaskell/stack/issues/1002) * Unlisted dependencies no longer trigger extraneous second build [#838](https://github.com/commercialhaskell/stack/issues/838) ## 0.1.4.1 Fix stack's own Haddocks. No changes to functionality (only comments updated). ## 0.1.4.0 Major changes: * You now have more control over how GHC versions are matched, e.g. "use exactly this version," "use the specified minor version, but allow patches," or "use the given minor version or any later minor in the given major release." The default has switched from allowing newer later minor versions to a specific minor version allowing patches. For more information, see [#736](https://github.com/commercialhaskell/stack/issues/736) and [#784](https://github.com/commercialhaskell/stack/pull/784). * Support added for compiling with GHCJS * stack can now reuse prebuilt binaries between snapshots. That means that, if you build package foo in LTS-3.1, that binary version can be reused in LTS-3.2, assuming it uses the same dependencies and flags. [#878](https://github.com/commercialhaskell/stack/issues/878) Other enhancements: * Added the `--docker-env` argument, to set environment variables in Docker container. * Set locale environment variables to UTF-8 encoding for builds to avoid "commitBuffer: invalid argument" errors from GHC [#793](https://github.com/commercialhaskell/stack/issues/793) * Enable translitation for encoding on stdout and stderr [#824](https://github.com/commercialhaskell/stack/issues/824) * By default, `stack upgrade` automatically installs GHC as necessary [#797](https://github.com/commercialhaskell/stack/issues/797) * Added the `ghc-options` field to stack.yaml [#796](https://github.com/commercialhaskell/stack/issues/796) * Added the `extra-path` field to stack.yaml * Code page changes on Windows only apply to the build command (and its synonyms), and can be controlled via a command line flag (still defaults to on) [#757](https://github.com/commercialhaskell/stack/issues/757) * Implicitly add packages to extra-deps when a flag for them is set [#807](https://github.com/commercialhaskell/stack/issues/807) * Use a precompiled Setup.hs for simple build types [#801](https://github.com/commercialhaskell/stack/issues/801) * Set --enable-tests and --enable-benchmarks optimistically [#805](https://github.com/commercialhaskell/stack/issues/805) * `--only-configure` option added [#820](https://github.com/commercialhaskell/stack/issues/820) * Check for duplicate local package names * Stop nagging people that call `stack test` [#845](https://github.com/commercialhaskell/stack/issues/845) * `--file-watch` will ignore files that are in your VCS boring/ignore files [#703](https://github.com/commercialhaskell/stack/issues/703) * Add `--numeric-version` option Bug fixes: * `stack init --solver` fails if `GHC_PACKAGE_PATH` is present [#860](https://github.com/commercialhaskell/stack/issues/860) * `stack solver` and `stack init --solver` check for test suite and benchmark dependencies [#862](https://github.com/commercialhaskell/stack/issues/862) * More intelligent logic for setting UTF-8 locale environment variables [#856](https://github.com/commercialhaskell/stack/issues/856) * Create missing directories for `stack sdist` * Don't ignore .cabal files with extra periods [#895](https://github.com/commercialhaskell/stack/issues/895) * Deprecate unused `--optimizations` flag * Truncated output on slow terminals [#413](https://github.com/commercialhaskell/stack/issues/413) ## 0.1.3.1 Bug fixes: * Ignore disabled executables [#763](https://github.com/commercialhaskell/stack/issues/763) ## 0.1.3.0 Major changes: * Detect when a module is compiled but not listed in the cabal file ([#32](https://github.com/commercialhaskell/stack/issues/32)) * A warning is displayed for any modules that should be added to `other-modules` in the .cabal file * These modules are taken into account when determining whether a package needs to be built * Respect TemplateHaskell addDependentFile dependency changes ([#105](https://github.com/commercialhaskell/stack/issues/105)) * TH dependent files are taken into account when determining whether a package needs to be built. * Overhauled target parsing, added `--test` and `--bench` options [#651](https://github.com/commercialhaskell/stack/issues/651) * For details, see [Build commands documentation](http://docs.haskellstack.org/en/stable/build_command/) Other enhancements: * Set the `HASKELL_DIST_DIR` environment variable [#524](https://github.com/commercialhaskell/stack/pull/524) * Track build status of tests and benchmarks [#525](https://github.com/commercialhaskell/stack/issues/525) * `--no-run-tests` [#517](https://github.com/commercialhaskell/stack/pull/517) * Targets outside of root dir don't build [#366](https://github.com/commercialhaskell/stack/issues/366) * Upper limit on number of flag combinations to test [#543](https://github.com/commercialhaskell/stack/issues/543) * Fuzzy matching support to give better error messages for close version numbers [#504](https://github.com/commercialhaskell/stack/issues/504) * `--local-bin-path` global option. Use to change where binaries get placed on a `--copy-bins` [#342](https://github.com/commercialhaskell/stack/issues/342) * Custom snapshots [#111](https://github.com/commercialhaskell/stack/issues/111) * --force-dirty flag: Force treating all local packages as having dirty files (useful for cases where stack can't detect a file change) * GHC error messages: display file paths as absolute instead of relative for better editor integration * Add the `--copy-bins` option [#569](https://github.com/commercialhaskell/stack/issues/569) * Give warnings on unexpected config keys [#48](https://github.com/commercialhaskell/stack/issues/48) * Remove Docker `pass-host` option * Don't require cabal-install to upload [#313](https://github.com/commercialhaskell/stack/issues/313) * Generate indexes for all deps and all installed snapshot packages [#143](https://github.com/commercialhaskell/stack/issues/143) * Provide `--resolver global` option [#645](https://github.com/commercialhaskell/stack/issues/645) * Also supports `--resolver nightly`, `--resolver lts`, and `--resolver lts-X` * Make `stack build --flag` error when flag or package is unknown [#617](https://github.com/commercialhaskell/stack/issues/617) * Preserve file permissions when unpacking sources [#666](https://github.com/commercialhaskell/stack/pull/666) * `stack build` etc work outside of a project * `list-dependencies` command [#638](https://github.com/commercialhaskell/stack/issues/638) * `--upgrade-cabal` option to `stack setup` [#174](https://github.com/commercialhaskell/stack/issues/174) * `--exec` option [#651](https://github.com/commercialhaskell/stack/issues/651) * `--only-dependencies` implemented correctly [#387](https://github.com/commercialhaskell/stack/issues/387) Bug fixes: * Extensions from the `other-extensions` field no longer enabled by default [#449](https://github.com/commercialhaskell/stack/issues/449) * Fix: haddock forces rebuild of empty packages [#452](https://github.com/commercialhaskell/stack/issues/452) * Don't copy over executables excluded by component selection [#605](https://github.com/commercialhaskell/stack/issues/605) * Fix: stack fails on Windows with git package in stack.yaml and no git binary on path [#712](https://github.com/commercialhaskell/stack/issues/712) * Fixed GHCi issue: Specifying explicit package versions (#678) * Fixed GHCi issue: Specifying -odir and -hidir as .stack-work/odir (#529) * Fixed GHCi issue: Specifying A instead of A.ext for modules (#498) ## 0.1.2.0 * Add `--prune` flag to `stack dot` [#487](https://github.com/commercialhaskell/stack/issues/487) * Add `--[no-]external`,`--[no-]include-base` flags to `stack dot` [#437](https://github.com/commercialhaskell/stack/issues/437) * Add `--ignore-subdirs` flag to init command [#435](https://github.com/commercialhaskell/stack/pull/435) * Handle attempt to use non-existing resolver [#436](https://github.com/commercialhaskell/stack/pull/436) * Add `--force` flag to `init` command * exec style commands accept the `--package` option (see [Reddit discussion](http://www.reddit.com/r/haskell/comments/3bd66h/stack_runghc_turtle_as_haskell_script_solution/)) * `stack upload` without arguments doesn't do anything [#439](https://github.com/commercialhaskell/stack/issues/439) * Print latest version of packages on conflicts [#450](https://github.com/commercialhaskell/stack/issues/450) * Flag to avoid rerunning tests that haven't changed [#451](https://github.com/commercialhaskell/stack/issues/451) * stack can act as a script interpreter (see [Script interpreter] (https://github.com/commercialhaskell/stack/wiki/Script-interpreter) and [Reddit discussion](http://www.reddit.com/r/haskell/comments/3bd66h/stack_runghc_turtle_as_haskell_script_solution/)) * Add the __`--file-watch`__ flag to auto-rebuild on file changes [#113](https://github.com/commercialhaskell/stack/issues/113) * Rename `stack docker exec` to `stack exec --plain` * Add the `--skip-msys` flag [#377](https://github.com/commercialhaskell/stack/issues/377) * `--keep-going`, turned on by default for tests and benchmarks [#478](https://github.com/commercialhaskell/stack/issues/478) * `concurrent-tests: BOOL` [#492](https://github.com/commercialhaskell/stack/issues/492) * Use hashes to check file dirtiness [#502](https://github.com/commercialhaskell/stack/issues/502) * Install correct GHC build on systems with libgmp.so.3 [#465](https://github.com/commercialhaskell/stack/issues/465) * `stack upgrade` checks version before upgrading [#447](https://github.com/commercialhaskell/stack/issues/447) ## 0.1.1.0 * Remove GHC uncompressed tar file after installation [#376](https://github.com/commercialhaskell/stack/issues/376) * Put stackage snapshots JSON on S3 [#380](https://github.com/commercialhaskell/stack/issues/380) * Specifying flags for multiple packages [#335](https://github.com/commercialhaskell/stack/issues/335) * single test suite failure should show entire log [#388](https://github.com/commercialhaskell/stack/issues/388) * valid-wanted is a confusing option name [#386](https://github.com/commercialhaskell/stack/issues/386) * stack init in multi-package project should use local packages for dependency checking [#384](https://github.com/commercialhaskell/stack/issues/384) * Display information on why a snapshot was rejected [#381](https://github.com/commercialhaskell/stack/issues/381) * Give a reason for unregistering packages [#389](https://github.com/commercialhaskell/stack/issues/389) * `stack exec` accepts the `--no-ghc-package-path` parameter * Don't require build plan to upload [#400](https://github.com/commercialhaskell/stack/issues/400) * Specifying test components only builds/runs those tests [#398](https://github.com/commercialhaskell/stack/issues/398) * `STACK_EXE` environment variable * Add the `stack dot` command * `stack upgrade` added [#237](https://github.com/commercialhaskell/stack/issues/237) * `--stack-yaml` command line flag [#378](https://github.com/commercialhaskell/stack/issues/378) * `--skip-ghc-check` command line flag [#423](https://github.com/commercialhaskell/stack/issues/423) Bug fixes: * Haddock links to global packages no longer broken on Windows [#375](https://github.com/commercialhaskell/stack/issues/375) * Make flags case-insensitive [#397](https://github.com/commercialhaskell/stack/issues/397) * Mark packages uninstalled before rebuilding [#365](https://github.com/commercialhaskell/stack/issues/365) ## 0.1.0.0 * Fall back to cabal dependency solver when a snapshot can't be found * Basic implementation of `stack new` [#137](https://github.com/commercialhaskell/stack/issues/137) * `stack solver` command [#364](https://github.com/commercialhaskell/stack/issues/364) * `stack path` command [#95](https://github.com/commercialhaskell/stack/issues/95) * Haddocks [#143](https://github.com/commercialhaskell/stack/issues/143): * Build for dependencies * Use relative links * Generate module contents and index for all packages in project ## 0.0.3 * `--prefetch` [#297](https://github.com/commercialhaskell/stack/issues/297) * `upload` command ported from stackage-upload [#225](https://github.com/commercialhaskell/stack/issues/225) * `--only-snapshot` [#310](https://github.com/commercialhaskell/stack/issues/310) * `--resolver` [#224](https://github.com/commercialhaskell/stack/issues/224) * `stack init` [#253](https://github.com/commercialhaskell/stack/issues/253) * `--extra-include-dirs` and `--extra-lib-dirs` [#333](https://github.com/commercialhaskell/stack/issues/333) * Specify intra-package target [#201](https://github.com/commercialhaskell/stack/issues/201) ## 0.0.2 * Fix some Windows specific bugs [#216](https://github.com/commercialhaskell/stack/issues/216) * Improve output for package index updates [#227](https://github.com/commercialhaskell/stack/issues/227) * Automatically update indices as necessary [#227](https://github.com/commercialhaskell/stack/issues/227) * --verbose flag [#217](https://github.com/commercialhaskell/stack/issues/217) * Remove packages (HTTPS and Git) [#199](https://github.com/commercialhaskell/stack/issues/199) * Config values for system-ghc and install-ghc * Merge `stack deps` functionality into `stack build` * `install` command [#153](https://github.com/commercialhaskell/stack/issues/153) and [#272](https://github.com/commercialhaskell/stack/issues/272) * overriding architecture value (useful to force 64-bit GHC on Windows, for example) * Overhauled test running (allows cycles, avoids unnecessary recompilation, etc) ## 0.0.1 * First public release, beta quality stack-1.5.1/README.md0000644000000000000000000000121512712350344012270 0ustar0000000000000000## The Haskell Tool Stack [![Build Status](https://travis-ci.org/commercialhaskell/stack.svg?branch=master)](https://travis-ci.org/commercialhaskell/stack) [![Windows build status](https://ci.appveyor.com/api/projects/status/c1c7uvmw6x1dupcl?svg=true)](https://ci.appveyor.com/project/snoyberg/stack) [![Release](https://img.shields.io/github/release/commercialhaskell/stack.svg)](https://github.com/commercialhaskell/stack/releases) Stack is a cross-platform program for developing Haskell projects. It is intended for Haskellers both new and experienced. See [haskellstack.org](http://haskellstack.org) or the `doc` directory for more information. stack-1.5.1/doc/architecture.md0000644000000000000000000002133413062242347014570 0ustar0000000000000000# Architecture ## Terminology * Package identifier: a package name and version, e.g. text-1.2.1.0 * GhcPkgId: a package identifier plus the unique hash for the generated binary, e.g. text-1.2.1.0-bb83023b42179dd898ebe815ada112c2 * Package index: a collection of packages available for download. This is a combination of an index containing all of the .cabal files (either a tarball downloaded via HTTP(S) or a Git repository) and some way to download package tarballs. * By default, stack uses a single package index (the Github/S3 mirrors of Hackage), but supports customization and adding more than one index * Package database: a collection of metadata about built libraries * Install root: a destination for installing packages into. Contains a bin path (for generated executables), lib (for the compiled libraries), pkgdb (for the package database), and a few other things * Snapshot: an LTS Haskell or Stackage Nightly, which gives information on a complete set of packages. This contains a lot of metadata, but importantly it can be converted into a mini build plan... * Mini build plan: a collection of package identifiers and their build flags that are known to build together * Resolver: the means by which stack resolves dependencies for your packages. The two currently supported options are snapshot (using LTS or Nightly), and GHC (which installs no extra dependencies). Others may be added in the future (such as a SAT-based dependency solver). These packages are always taken from a package index * extra-deps: additional packages to be taken from the package index for dependencies. This list will *shadow* packages provided by the resolver * Local packages: source code actually present on your file system, and referred to by the `packages` field in your stack.yaml file. Each local package has exactly one .cabal file * Project: a stack.yaml config file and all of the local packages it refers to. ## Databases Every build uses three distinct install roots, which means three separate package databases and bin paths. These are: * Global: the packages that ship with GHC. We never install anything into this database * Snapshot: a database shared by all projects using the same snapshot. Packages installed in this database must use the exact same dependencies and build flags as specified in the snapshot, and cannot be affected by user flags, ensuring that one project cannot corrupt another. There are two caveats to this: * If different projects use different package indices, then their definitions of what package foo-1.2.3 are may be different, in which case they *can* corrupt each other's shared databases. This is warned about in the FAQ * Turning on profiling may cause a package to be recompiled, which will result in a different GhcPkgId * Local: extra-deps, local packages, and snapshot packages which depend on them (more on that in shadowing) ## Building ### Shadowing Every project must have precisely one version of a package. If one of your local packages or extra dependencies conflicts with a package in the snapshot, the local/extradep *shadows* the snapshot version. The way this works is: * The package is removed from the list of packages in the snapshot * Any package that depends on that package (directly or indirectly) is moved from the snapshot to extra-deps, so that it is available to your packages as dependencies. * Note that there is no longer any guarantee that this package will build, since you're using an untested dependency After shadowing, you end up with what is called internally a `SourceMap`, which is `Map PackageName PackageSource`, where a `PackageSource` can be either a local package, or a package taken from a package index (specified as a version number and the build flags). ### Installed packages Once you have a `SourceMap`, you can inspect your three available databases and decide which of the installed packages you wish to use from them. We move from the global, to snapshot, and finally local, with the following rules: * If we require profiling, and the library does not provide profiling, do not use it * If the package is in the `SourceMap`, but belongs to a difference database, or has a different version, do not use it * If after the above two steps, any of the dependencies are unavailable, do not use it * Otherwise: include the package in the list of installed packages We do something similar for executables, but maintain our own database of installed executables, since GHC does not track them for us. ### Plan construction When running a build, we know which packages we want installed (inventively called "wanteds"), which packages are available to install, and which are already installed. In plan construction, we put them information together to decide which packages must be built. The code in Stack.Build.ConstructPlan is authoritative on this and should be consulted. The basic idea though is: * If any of the dependencies have changed, reconfigure and rebuild * If a local package has any files changed, rebuild (but don't bother reconfiguring) * If a local package is wanted and we're running tests or benchmarks, run the test or benchmark even if the code and dependencies haven't changed ### Plan execution Once we have the plan, execution is a relatively simple process of calling `runghc Setup.hs` in the correct order with the correct parameters. See Stack.Build.Execute for more information. ## Configuration stack has two layers of configuration: project and non-project. All of these are stored in stack.yaml files, but the former has extra fields (resolver, packages, extra-deps, and flags). The latter can be monoidally combined so that a system config file provides defaults, which a user can override with `~/.stack/config.yaml`, and a project can further customize. In addition, environment variables STACK\_ROOT and STACK\_YAML can be used to tweak where stack gets its configuration from. stack follows a simple algorithm for finding your project configuration file: start in the current directory, and keep going to the parent until it finds a `stack.yaml`. When using `stack ghc` or `stack exec` as mentioned above, you'll sometimes want to override that behavior and point to a specific project in order to use its databases and bin directories. To do so, simply set the `STACK_YAML` environment variable to point to the relevant `stack.yaml` file. ## Snapshot auto-detection When you run `stack build` with no stack.yaml, it will create a basic configuration with a single package (the current directory) and an auto-detected snapshot. The algorithm it uses for selecting this snapshot is: * Try the latest two LTS major versions at their most recent minor version release, and the most recent Stackage Nightly. For example, at the time of writing, this would be lts-2.10, lts-1.15, and nightly-2015-05-26 * For each of these, test the version bounds in the package's .cabal file to see if they are compatible with the snapshot, choosing the first one that matches * If no snapshot matches, uses the most recent LTS snapshot, even though it will not compile If you end up in the no compatible snapshot case, you typically have three options to fix things: * Manually specify a different snapshot that you know to be compatible. If you can do that, great, but typically if the auto-detection fails, it means that there's no compatible snapshot * Modify version bounds in your .cabal file to be compatible with the selected snapshot * Add `extra-deps` to your stack.yaml file to fix compatibility problems Remember that running `stack build` will give you information on why your build cannot occur, which should help guide you through the steps necessary for the second and third option above. Also, note that those options can be mixed-and-matched, e.g. you may decide to relax some version bounds in your .cabal file, while also adding some extra-deps. ## Explicit breakage As mentioned above, updating your package indices will not cause stack to invalidate any existing package databases. That's because stack is always explicit about build plans, via: 1. the selected snapshot 2. the extra-deps 3. local packages The only way to change a plan for packages to be installed is by modifying one of the above. This means that breakage of a set of installed packages is an *explicit* and *contained* activity. Specifically, you get the following guarantees: * Since snapshots are immutable, the snapshot package database will not be invalidated by any action. If you change the snapshot you're using, however, you may need to build those packages from scratch. * If you modify your extra-deps, stack may need to unregister and reinstall them. * Any changes to your local packages trigger a rebuild of that package and its dependencies. stack-1.5.1/doc/build_command.md0000644000000000000000000002232413135652051014701 0ustar0000000000000000# Build command ## Overview The primary command you use in stack is build. This page describes the build command's interface. The goal of the interface is to do the right thing for simple input, and allow a lot of flexibility for more complicated goals. See the [build command section of the user guide](GUIDE.md#the-build-command) for info beyond the CLI aspects of the build command. ## Synonyms One potential point of confusion is the synonym commands for `build`. These are provided to match commonly expected command line interfaces, and to make common workflows shorter. The important thing to note is that all of these are just the `build` command in disguise. Each of these commands are called out as synonyms in the `--help` output. These commands are: * `stack test` is the same as `stack build --test` * `stack bench` is the same as `stack build --bench` * `stack haddock` is the same as `stack build --haddock` * `stack install` is the same as `stack build --copy-bins` The advantage of the synonym commands is that they're convenient and short. The advantage of the options is that they compose. For example, `stack build --test --copy-bins` will build libraries, executables, and test suites, run the test suites, and then copy the executables to your local bin path (more on this below). ## Components Components are a subtle yet important point to how build operates under the surface. Every cabal package is made up of one or more components. It can have 0 or 1 libraries, and then 0 or more of executable, test, and benchmark components. stack allows you to call out a specific component to be built, e.g. `stack build mypackage:test:mytests` will build the `mytests` component of the `mypackage` package. `mytests` must be a test suite component. We'll get into the details of the target syntax for how to select components in the next section. In this section, the important point is: whenever you target a test suite or a benchmark, it's built __and also run__, unless you explicitly disable running via `--no-run-tests` or `--no-run-benchmarks`. Case in point: the previous command will in fact build the `mytests` test suite *and* run it, even though you haven't used the `stack test` command or the `--test` option. (We'll get to what exactly `--test` does below.) This gives you a lot of flexibility in choosing what you want stack to do. You can run a single test component from a package, run a test component from one package and a benchmark from another package, etc. One final note on components: you can only control components for local packages, not dependencies. With dependencies, stack will *always* build the library (if present) and all executables, and ignore test suites and benchmarks. If you want more control over a package, you must add it to your `packages` setting in your stack.yaml file. ## Target syntax In addition to a number of options (like the aforementioned `--test`), `stack build` takes a list of zero or more *targets* to be built. There are a number of different syntaxes supported for this list: * *package*, e.g. `stack build foobar`, is the most commonly used target. It will try to find the package in the following locations: local packages, extra dependencies, snapshots, and package index (e.g. Hackage). If it's found in the package index, then the latest version of that package from the index is implicitly added to your extra dependencies. This is where the `--test` and `--bench` flags come into play. If the package is a local package, then all of the test suite and benchmark components are selected to be built, respectively. In any event, the library and executable components are also selected to be built. * *package identifier*, e.g. `stack build foobar-1.2.3`, is usually used to include specific package versions from the index. If the version selected conflicts with an existing local package or extra dep, then stack fails with an error. Otherwise, this is the same as calling `stack build foobar`, except instead of using the latest version from the index, the version specified is used. * *component*. Instead of referring to an entire package and letting stack decide which components to build, you select individual components from inside a package. This can be done for more fine-grained control over which test suites to run, or to have a faster compilation cycle. There are multiple ways to refer to a specific component (provided for convenience): * `packagename:comptype:compname` is the most explicit. The available comptypes are `exe`, `test`, and `bench`. * Side note: When any `exe` component is specified, all of the package's executable components will be built. This is due to limitations in all currently released versions of Cabal. See [issue#1046](https://github.com/commercialhaskell/stack/issues/1406) * `packagename:compname` allows you to leave off the component type, as that will (almost?) always be redundant with the component name. For example, `stack build mypackage:mytestsuite`. * `:compname` is a useful shortcut, saying "find the component in all of the local packages." This will result in an error if multiple packages have a component with the same name. To continue the above example, `stack build :mytestsuite`. * Side note: the commonly requested `run` command is not available because it's a simple combination of `stack build :exename && stack exec exename` * *directory*, e.g. `stack build foo/bar`, will find all local packages that exist in the given directory hierarchy and then follow the same procedure as passing in package names as mentioned above. There's an important caveat here: if your directory name is parsed as one of the above target types, it will be treated as that. Explicitly starting your target with `./` can be a good way to avoid that, e.g. `stack build ./foo` Finally: if you provide no targets (e.g., running `stack build`), stack will implicitly pass in all of your local packages. If you only want to target packages in the current directory or deeper, you can pass in `.`, e.g. `stack build .`. To get a list of the available targets in your project, use `stack ide targets`. ## Controlling what gets built Stack will automatically build the necessary dependencies. See the [build command section of the user guide](GUIDE.md#the-build-command) for details of how these dependencies get specified. In addition to specifying targets, you can also control what gets built with the following flags: * `--haddock`, to build documentation. This may cause a lot of packages to get re-built, so that the documentation links work. * `--force-dirty`, to force rebuild of packages even when it doesn't seem necessary based on file dirtiness. * `--reconfigure`, to force reconfiguration even when it doesn't seem necessary based on file dirtiness. This is sometimes useful with custom Setup.hs files, in particular when they depend on external data files. * `--dry-run`, to build nothing and output information about the build plan. * `--only-dependencies`, to skip building the targets. * `--only-snapshot`, to only build snapshot dependencies, which are cached and shared with other projects. * `--keep-going`, to continue building packages even after some build step fails. The packages which depend upon the failed build won't get built. ## Flags There are a number of other flags accepted by `stack build`. Instead of listing all of them, please use `stack build --help`. Some particularly convenient ones worth mentioning here since they compose well with the rest of the build system as described: * `--file-watch` will rebuild your project every time a file changes * `--exec "cmd [args]"` will run a command after a successful build To come back to the composable approach described above, consider this final example (which uses the [wai repository](https://github.com/yesodweb/wai/): ``` stack build --file-watch --test --copy-bins --haddock wai-extra :warp warp:doctest --exec 'echo Yay, it worked!' ``` This command will: * Start stack up in file watch mode, waiting for files in your project to change. When first starting, and each time a file changes, it will do all of the following. * Build the wai-extra package and its test suites * Build the `warp` executable * Build the warp package's doctest component (which, as you may guess, is a test site) * Run all of the wai-extra package's test suite components and the doctest test suite component * If all of that succeeds: * Copy generated executables to the local bin path * Run the command `echo Yay, it worked!` ## Build output When building a single target package (e.g., `stack build` in a project with only one package, or `stack build package-name` in a multi-package project), the build output from GHC will be hidden for building all dependencies, and will be displayed for the one target package. By default, when building multiple target packages, the output from these will end up in a log file instead of on the console unless it contains errors or warnings, to avoid problems of interleaved output and decrease console noise. If you would like to see this content instead, you can use the `--dump-logs` command line option, or add `dump-logs: all` to your `stack.yaml` file. stack-1.5.1/doc/ChangeLog.md0000644000000000000000000023245113140560217013735 0ustar0000000000000000# Changelog ## 1.5.1 Bug fixes: * Stack eagerly tries to parse all cabal files related to a snapshot. Starting with Stackage Nightly 2017-07-31, snapshots are using GHC 8.2.1, and the `ghc.cabal` file implicitly referenced uses the (not yet supported) Cabal 2.0 file format. Future releases of Stack will both be less eager about cabal file parsing and support Cabal 2.0. This patch simply bypasses the error for invalid parsing. ## 1.5.0 Behavior changes: * `stack profile` and `stack trace` now add their extra RTS arguments for benchmarks and tests to the beginning of the args, instead of the end. See [#2399](https://github.com/commercialhaskell/stack/issues/2399) * Support for Git-based indices has been removed. Other enhancements: * `stack setup` allow to control options passed to ghcjs-boot with `--ghcjs-boot-options` (one word at a time) and `--[no-]ghcjs-boot-clean` * `stack setup` now accepts a `--install-cabal VERSION` option which will install a specific version of the Cabal library globally. * Updates to store-0.4.1, which has improved performance and better error reporting for version tags. A side-effect of this is that all of stack's binary caches will be invalidated. * `stack solver` will now warn about unexpected cabal-install versions. See [#3044](https://github.com/commercialhaskell/stack/issues/3044) * Upstream packages unpacked to a temp dir are now deleted as soon as possible to avoid running out of space in `/tmp`. See [#3018](https://github.com/commercialhaskell/stack/issues/3018) * Add short synonyms for `test-arguments` and `benchmark-arguments` options. * Adds `STACK_WORK` environment variable, to specify work dir. See [#3063](https://github.com/commercialhaskell/stack/issues/3063) * Can now use relative paths for `extra-include-dirs` and `extra-lib-dirs`. See [#2830](https://github.com/commercialhaskell/stack/issues/2830) * Improved bash completion for many options, including `--ghc-options`, `--flag`, targets, and project executables for `exec`. * `--haddock-arguments` is actually used now when `haddock` is invoked during documentation generation. * `--[no-]haddock-hyperlink-source` flag added which allows toggling of sources being included in Haddock output. See [#3099](https://github.com/commercialhaskell/stack/issues/3099) * `stack ghci` will now skip building all local targets, even if they have downstream deps, as long as it's registered in the DB. * The pvp-bounds feature now supports adding `-revision` to the end of each value, e.g. `pvp-bounds: both-revision`. This means that, when uploading to Hackage, Stack will first upload your tarball with an unmodified `.cabal` file, and then upload a cabal file revision with the PVP bounds added. This can be useful—especially combined with the [Stackage no-revisions feature](http://www.snoyman.com/blog/2017/04/stackages-no-revisions-field)—as a method to ensure PVP compliance without having to proactively fix bounds issues for Stackage maintenance. * Expose a `save-hackage-creds` configuration option * On GHC <= 7.8, filters out spurious linker warnings on windows See [#3127](https://github.com/commercialhaskell/stack/pull/3127) * Better error messages when creating or building packages which alias wired-in packages. See [#3172](https://github.com/commercialhaskell/stack/issues/3172). * MinGW bin folder now is searched for dynamic libraries. See [#3126](https://github.com/commercialhaskell/stack/issues/3126) * When using Nix, nix-shell now depends always on git to prevent runtime errors while fetching metadata * The `stack unpack` command now accepts a form where an explicit Hackage revision hash is specified, e.g. `stack unpack foo-1.2.3@gitsha1:deadbeef`. Note that this should be considered _experimental_, Stack will likely move towards a different hash format in the future. * Binary "stack upgrade" will now warn if the installed executable is not on the PATH or shadowed by another entry. * Allow running tests on tarball created by sdist and upload [#717](https://github.com/commercialhaskell/stack/issues/717). Bug fixes: * Fixes case where `stack build --profile` might not cause executables / tests / benchmarks to be rebuilt. See [#2984](https://github.com/commercialhaskell/stack/issues/2984) * `stack ghci file.hs` now loads the file even if it isn't part of your project. * `stack clean --full` now works when docker is enabled. See [#2010](https://github.com/commercialhaskell/stack/issues/2010) * Fixes an issue where cyclic deps can cause benchmarks or tests to be run before they are built. See [#2153](https://github.com/commercialhaskell/stack/issues/2153) * Fixes `stack build --file-watch` in cases where a directory is removed See [#1838](https://github.com/commercialhaskell/stack/issues/1838) * Fixes `stack dot` and `stack list-dependencies` to use info from the package database for wired-in-packages (ghc, base, etc). See [#3084](https://github.com/commercialhaskell/stack/issues/3084) * Fixes `stack --docker build` when user is part of libvirt/libvirtd groups on Ubuntu Yakkety (16.10). See [#3092](https://github.com/commercialhaskell/stack/issues/3092) * Switching a package between extra-dep and local package now forces rebuild (previously it wouldn't if versions were the same). See [#2147](https://github.com/commercialhaskell/stack/issues/2147) * `stack upload` no longer reveals your password when you type it on MinTTY-based Windows shells, such as Cygwin and MSYS2. See [#3142](https://github.com/commercialhaskell/stack/issues/3142) * `stack script`'s import parser will now properly parse files that have Windows-style line endings (CRLF) ## 1.4.0 Release notes: * Docker images: [fpco/stack-full](https://hub.docker.com/r/fpco/stack-full/) and [fpco/stack-run](https://hub.docker.com/r/fpco/stack-run/) are no longer being built for LTS 8.0 and above. [fpco/stack-build](https://hub.docker.com/r/fpco/stack-build/) images continue to be built with a [simplified process](https://github.com/commercialhaskell/stack/tree/master/etc/dockerfiles/stack-build). [#624](https://github.com/commercialhaskell/stack/issues/624) Major changes: * A new command, `script`, has been added, intended to make the script interpreter workflow more reliable, easier to use, and more efficient. This command forces the user to provide a `--resolver` value, ignores all config files for more reproducible results, and optimizes the existing package check to make the common case of all packages already being present much faster. This mode does require that all packages be present in a snapshot, however. [#2805](https://github.com/commercialhaskell/stack/issues/2805) Behavior changes: * The default package metadata backend has been changed from Git to the 01-index.tar.gz file, from the hackage-security project. This is intended to address some download speed issues from Github for people in certain geographic regions. There is now full support for checking out specific cabal file revisions from downloaded tarballs as well. If you manually specify a package index with only a Git URL, Git will still be used. See [#2780](https://github.com/commercialhaskell/stack/issues/2780) * When you provide the `--resolver` argument to the `stack unpack` command, any packages passed in by name only will be looked up in the given snapshot instead of taking the latest version. For example, `stack --resolver lts-7.14 unpack mtl` will get version 2.2.1 of `mtl`, regardless of the latest version available in the package indices. This will also force the same cabal file revision to be used as is specified in the snapshot. Unpacking via a package identifier (e.g. `stack --resolver lts-7.14 unpack mtl-2.2.1`) will ignore any settings in the snapshot and take the most recent revision. For backwards compatibility with tools relying on the presence of a `00-index.tar`, Stack will copy the `01-index.tar` file to `00-index.tar`. Note, however, that these files are different; most importantly, 00-index contains only the newest revisions of cabal files, while 01-index contains all versions. You may still need to update your tooling. * Passing `--(no-)nix-*` options now no longer implies `--nix`, except for `--nix-pure`, so that the user preference whether or not to use Nix is honored even in the presence of options that change the Nix behavior. Other enhancements: * Internal cleanup: configuration types are now based much more on lenses * `stack build` and related commands now allow the user to disable debug symbol stripping with new `--no-strip`, `--no-library-stripping`, and `--no-executable-shipping` flags, closing [#877](https://github.com/commercialhaskell/stack/issues/877). Also turned error message for missing targets more readable ([#2384](https://github.com/commercialhaskell/stack/issues/2384)) * `stack haddock` now shows index.html paths when documentation is already up to date. Resolved [#781](https://github.com/commercialhaskell/stack/issues/781) * Respects the `custom-setup` field introduced in Cabal 1.24. This supercedes any `explicit-setup-deps` settings in your `stack.yaml` and trusts the package's `.cabal` file to explicitly state all its dependencies. * If system package installation fails, `get-stack.sh` will fail as well. Also shows warning suggesting to run `apt-get update` or similar, depending on the OS. ([#2898](https://github.com/commercialhaskell/stack/issues/2898)) * When `stack ghci` is run with a config with no packages (e.g. global project), it will now look for source files in the current work dir. ([#2878](https://github.com/commercialhaskell/stack/issues/2878)) * Bump to hpack 0.17.0 to allow `custom-setup` and `!include "..."` in `package.yaml`. * The script interpreter will now output error logging. In particular, this means it will output info about plan construction errors. ([#2879](https://github.com/commercialhaskell/stack/issues/2879)) * `stack ghci` now takes `--flag` and `--ghc-options` again (inadverently removed in 1.3.0). ([#2986](https://github.com/commercialhaskell/stack/issues/2986)) * `stack exec` now takes `--rts-options` which passes the given arguments inside of `+RTS ... args .. -RTS` to the executable. This works around stack itself consuming the RTS flags on Windows. ([#2986](https://github.com/commercialhaskell/stack/issues/2640)) * Upgraded `http-client-tls` version, which now offers support for the `socks5://` and `socks5h://` values in the `http_proxy` and `https_proxy` environment variables. Bug fixes: * Bump to hpack 0.16.0 to avoid character encoding issues when reading and writing on non-UTF8 systems. * `stack ghci` will no longer ignore hsSourceDirs that contain `..`. ([#2895](https://github.com/commercialhaskell/stack/issues/2895)) * `stack list-dependencies --license` now works for wired-in-packages, like base. ([#2871](https://github.com/commercialhaskell/stack/issues/2871)) * `stack setup` now correctly indicates when it uses system ghc ([#2963](https://github.com/commercialhaskell/stack/issues/2963)) * Fix to `stack config set`, in 1.3.2 it always applied to the global project. ([#2709](https://github.com/commercialhaskell/stack/issues/2709)) * Previously, cabal files without exe or lib would fail on the "copy" step. ([#2862](https://github.com/commercialhaskell/stack/issues/2862)) * `stack upgrade --git` now works properly. Workaround for affected versions (>= 1.3.0) is to instead run `stack upgrade --git --source-only`. ([#2977](https://github.com/commercialhaskell/stack/issues/2977)) * Added support for GHC 8's slightly different warning format for dumping warnings from logs. * Work around a bug in Cabal/GHC in which package IDs are not unique for different source code, leading to Stack not always rebuilding packages depending on local packages which have changed. ([#2904](https://github.com/commercialhaskell/stack/issues/2904)) ## 1.3.2 Bug fixes: * `stack config set` can now be used without a compiler installed [#2852](https://github.com/commercialhaskell/stack/issues/2852). * `get-stack.sh` now installs correct binary on ARM for generic linux and raspbian, closing [#2856](https://github.com/commercialhaskell/stack/issues/2856). * Correct the testing of whether a package database exists by checking for the `package.cache` file itself instead of the containing directory. * Revert a change in the previous release which made it impossible to set local extra-dep packages as targets. This was overkill; we really only wanted to disable their test suites, which was already handled by a later patch. [#2849](https://github.com/commercialhaskell/stack/issues/2849) * `stack new` always treats templates as being UTF-8 encoding, ignoring locale settings on a local machine. See [Yesod mailing list discussion](https://groups.google.com/d/msg/yesodweb/ZyWLsJOtY0c/aejf9E7rCAAJ) ## 1.3.0 Release notes: * For the _next_ stack release after this one, we are planning changes to our Linux releases, including dropping our Ubuntu, Debian, CentOS, and Fedora package repositories and switching to statically linked binaries. See [#2534](https://github.com/commercialhaskell/stack/issues/2534). Note that upgrading without a package manager has gotten easier with new binary upgrade support in `stack upgrade` (see the Major Changes section below for more information). In addition, the get.haskellstack.org script no longer installs from Ubuntu, Debian, CentOS, or Fedora package repositories. Instead it places a generic binary in /usr/local/bin. Major changes: * Stack will now always use its own GHC installation, even when a suitable GHC installation is available on the PATH. To get the old behaviour, use the `--system-ghc` flag or run `stack config set system-ghc --global true`. Docker- and Nix-enabled projects continue to use the GHC installations in their environment by default. NB: Scripts that previously used stack in combination with a system GHC installation should now include a `stack setup` line or use the `--install-ghc` flag. [#2221](https://github.com/commercialhaskell/stack/issues/2221) * `stack ghci` now defaults to skipping the build of target packages, because support has been added for invoking "initial build steps", which create autogen files and run preprocessors. The `--no-build` flag is now deprecated because it should no longer be necessary. See [#1364](https://github.com/commercialhaskell/stack/issues/1364) * Stack is now capable of doing binary upgrades instead of always recompiling a new version from source. Running `stack upgrade` will now default to downloading a binary version of Stack from the most recent release, if one is available. See `stack upgrade --help` for more options. [#1238](https://github.com/commercialhaskell/stack/issues/1238) Behavior changes: * Passing `--resolver X` with a Stack command which forces creation of a global project config, will pass resolver X into the initial config. See [#2579](https://github.com/commercialhaskell/stack/issues/2229). * Switch the "Run from outside project" messages to debug-level, to avoid spamming users in the normal case of non-project usage * If a remote package is specified (such as a Git repo) without an explicit `extra-dep` setting, a warning is given to the user to provide one explicitly. Other enhancements: * `stack haddock` now supports `--haddock-internal`. See [#2229](https://github.com/commercialhaskell/stack/issues/2229) * Add support for `system-ghc` and `install-ghc` fields to `stack config set` command. * Add `ghc-build` option to override autodetected GHC build to use (e.g. gmp4, tinfo6, nopie) on Linux. * `stack setup` detects systems where gcc enables PIE by default (such as Ubuntu 16.10 and Hardened Gentoo) and adjusts the GHC `configure` options accordingly. [#2542](https://github.com/commercialhaskell/stack/issues/2542) * Upload to Hackage with HTTP digest instead of HTTP basic. * Make `stack list-dependencies` understand all of the `stack dot` options too. * Add the ability for `stack list-dependencies` to list dependency licenses by passing the `--license` flag. * Dump logs that contain warnings for any local non-dependency packages [#2545](https://github.com/commercialhaskell/stack/issues/2545) * Add the `dump-logs` config option and `--dump-logs` command line option to get full build output on the console. [#426](https://github.com/commercialhaskell/stack/issues/426) * Add the `--open` option to "stack hpc report" command, causing the report to be opened in the browser. * The `stack config set` command now accepts a `--global` flag for suitable fields which causes it to modify the global user configuration (`~/.stack/config.yaml`) instead of the project configuration. [#2675](https://github.com/commercialhaskell/stack/pull/2675) * Information on the latest available snapshots is now downloaded from S3 instead of stackage.org, increasing reliability in case of stackage.org outages. [#2653](https://github.com/commercialhaskell/stack/pull/2653) * `stack dot` and `stack list-dependencies` now take targets and flags. [#1919](https://github.com/commercialhaskell/stack/issues/1919) * Deprecate `stack setup --stack-setup-yaml` for `--setup-info-yaml` based on discussion in [#2647](https://github.com/commercialhaskell/stack/issues/2647). * The `--main-is` flag for GHCI now implies the TARGET, fixing [#1845](https://github.com/commercialhaskell/stack/issues/1845). * `stack ghci` no longer takes all build options, as many weren't useful [#2199](https://github.com/commercialhaskell/stack/issues/2199) * `--no-time-in-log` option, to make verbose logs more diffable [#2727](https://github.com/commercialhaskell/stack/issues/2727) * `--color` option added to override auto-detection of ANSI support [#2725](https://github.com/commercialhaskell/stack/issues/2725) * Missing extra-deps are now warned about, adding a degree of typo detection [#1521](https://github.com/commercialhaskell/stack/issues/1521) * No longer warns about missing build-tools if they are on the PATH. [#2235](https://github.com/commercialhaskell/stack/issues/2235) * Replace enclosed-exceptions with safe-exceptions. [#2768](https://github.com/commercialhaskell/stack/issues/2768) * The install location for GHC and other programs can now be configured with the `local-programs-path` option in `config.yaml`. [#1644](https://github.com/commercialhaskell/stack/issues/1644) * Added option to add nix dependencies as nix GC roots * Proper pid 1 (init) process for `stack exec` with Docker * Dump build logs if they contain warnings. [#2545](https://github.com/commercialhaskell/stack/issues/2545) * Docker: redirect stdout of `docker pull` to stderr so that it will not interfere with output of other commands. * Nix & docker can be activated at the same time, in order to run stack in a nix-shell in a container, preferably from an image already containing the nix dependencies in its /nix/store * Stack/nix: Dependencies can be added as nix GC roots, so they are not removed when running `nix-collect-garbage` Bug fixes: * Fixed a gnarly bug where programs and package tarballs sometimes have corrupted downloads. See [#2657](https://github.com/commercialhaskell/stack/issues/2568). * Add proper support for non-ASCII characters in file paths for the `sdist` command. See [#2549](https://github.com/commercialhaskell/stack/issues/2549) * Never treat `extra-dep` local packages as targets. This ensures things like test suites are not run for these packages, and that build output is not hidden due to their presence. * Fix a resource leak in `sinkProcessStderrStdout` which could affect much of the codebase, in particular copying precompiled packages. [#1979](https://github.com/commercialhaskell/stack/issues/1979) * Docker: ensure that interrupted extraction process does not cause corrupt file when downloading a Docker-compatible Stack executable [#2568](https://github.com/commercialhaskell/stack/issues/2568) * Fixed running `stack hpc report` on package targets. [#2664](https://github.com/commercialhaskell/stack/issues/2664) * Fix a long-standing performance regression where stack would parse the .dump-hi files of the library components of local packages twice. [#2658](https://github.com/commercialhaskell/stack/pull/2658) * Fixed a regression in "stack ghci --no-load", where it would prompt for a main module to load. [#2603](https://github.com/commercialhaskell/stack/pull/2603) * Build Setup.hs files with the threaded RTS, mirroring the behavior of cabal-install and enabling more complex build systems in those files. * Fixed a bug in passing along `--ghc-options` to ghcjs. They were being provided as `--ghc-options` to Cabal, when it needs to be `--ghcjs-options`. [#2714](https://github.com/commercialhaskell/stack/issues/2714) * Launch Docker from the project root regardless of the working directory Stack is invoked from. This means paths relative to the project root (e.g. environment files) can be specified in `stack.yaml`'s docker `run-args`. * `stack setup --reinstall` now behaves as expected. [#2554](https://github.com/commercialhaskell/stack/issues/2554) ## 1.2.0 Release notes: * On many Un*x systems, Stack can now be installed with a simple one-liner: wget -qO- https://get.haskellstack.org/ | sh * The fix for [#2175](https://github.com/commercialhaskell/stack/issues/2175) entails that stack must perform a full clone of a large Git repo of Hackage meta-information. The total download size is about 200 MB. Please be aware of this when upgrading your stack installation. * If you use Mac OS X, you may want to delay upgrading to macOS Sierra as there are reports of GHC panics when building some packages (including Stack itself). See [#2577](https://github.com/commercialhaskell/stack/issues/2577) * This version of Stack does not build on ARM or PowerPC systems (see [store#37](https://github.com/fpco/store/issues/37)). Please stay with version 1.1.2 for now on those architectures. This will be rectified soon! * We are now releasing a [statically linked Stack binary for 64-bit Linux](https://www.stackage.org/stack/linux-x86_64-static). Please try it and let us know if you run into any trouble on your platform. * We are planning some changes to our Linux releases, including dropping our Ubuntu, Debian, CentOS, and Fedora package repositories and switching to statically linked binaries. We would value your feedback in [#2534](https://github.com/commercialhaskell/stack/issues/2534). Major changes: * Add `stack hoogle` command. [#55](https://github.com/commercialhaskell/stack/issues/55) * Support for absolute file path in `url` field of `setup-info` or `--ghc-bindist` * Add support for rendering GHCi scripts targeting different GHCi like applications [#2457](https://github.com/commercialhaskell/stack/pull/2457) Behavior changes: * Remove `stack ide start` and `stack ide load-targets` commands. [#2178](https://github.com/commercialhaskell/stack/issues/2178) * Support .buildinfo files in `stack ghci`. [#2242](https://github.com/commercialhaskell/stack/pull/2242) * Support -ferror-spans syntax in GHC error messages. * Avoid unpacking ghc to `/tmp` [#996](https://github.com/commercialhaskell/stack/issues/996) * The Linux `gmp4` GHC bindist is no longer considered a full-fledged GHC variant and can no longer be specified using the `ghc-variant` option, and instead is treated more like a slightly different platform. Other enhancements: * Use the `store` package for binary serialization of most caches. * Only require minor version match for Docker stack exe. This way, we can make patch releases for version bounds and similar build issues without needing to upload new binaries for Docker. * Stack/Nix: Passes the right ghc derivation as an argument to the `shell.nix` when a custom `shell.nix` is used See [#2243](https://github.com/commercialhaskell/stack/issues/2243) * Stack/Nix: Sets `LD_LIBRARY_PATH` so packages using C libs for Template Haskell can work (See _e.g._ [this HaskellR issue](https://github.com/tweag/HaskellR/issues/253)) * Parse CLI arguments and configuration files into less permissive types, improving error messages for bad inputs. [#2267](https://github.com/commercialhaskell/stack/issues/2267) * Add the ability to explictly specify a gcc executable. [#593](https://github.com/commercialhaskell/stack/issues/593) * Nix: No longer uses LTS mirroring in nixpkgs. Gives to nix-shell a derivation like `haskell.compiler.ghc801` See [#2259](https://github.com/commercialhaskell/stack/issues/2259) * Perform some subprocesses during setup concurrently, slightly speeding up most commands. [#2346](https://github.com/commercialhaskell/stack/pull/2346) * `stack setup` no longer unpacks to the system temp dir on posix systems. [#996](https://github.com/commercialhaskell/stack/issues/996) * `stack setup` detects libtinfo6 and ncurses6 and can download alternate GHC bindists [#257](https://github.com/commercialhaskell/stack/issues/257) [#2302](https://github.com/commercialhaskell/stack/issues/2302). * `stack setup` detects Linux ARMv7 downloads appropriate GHC bindist [#2103](https://github.com/commercialhaskell/stack/issues/2103) * Custom `stack` binaries list dependency versions in output for `--version`. See [#2222](https://github.com/commercialhaskell/stack/issues/2222) and [#2450](https://github.com/commercialhaskell/stack/issues/2450). * Use a pretty printer to output dependency resolution errors. [#1912](https://github.com/commercialhaskell/stack/issues/1912) * Remove the `--os` flag [#2227](https://github.com/commercialhaskell/stack/issues/2227) * Add 'netbase' and 'ca-certificates' as dependency for .deb packages. [#2293](https://github.com/commercialhaskell/stack/issues/2293). * Add `stack ide targets` command. * Enhance debug logging with subprocess timings. * Pretty-print YAML parse errors [#2374](https://github.com/commercialhaskell/stack/issues/2374) * Clarify confusing `stack setup` output [#2314](https://github.com/commercialhaskell/stack/issues/2314) * Delete `Stack.Types` multimodule to improve build times [#2405](https://github.com/commercialhaskell/stack/issues/2405) * Remove spurious newlines in build logs [#2418](https://github.com/commercialhaskell/stack/issues/2418) * Interpreter: Provide a way to hide implicit packages [#1208](https://github.com/commercialhaskell/stack/issues/1208) * Check executability in exec lookup [#2489](https://github.com/commercialhaskell/stack/issues/2489) Bug fixes: * Fix cabal warning about use of a deprecated cabal flag [#2350](https://github.com/commercialhaskell/stack/issues/2350) * Support most executable extensions on Windows [#2225](https://github.com/commercialhaskell/stack/issues/2225) * Detect resolver change in `stack solver` [#2252](https://github.com/commercialhaskell/stack/issues/2252) * Fix a bug in docker image creation where the wrong base image was selected [#2376](https://github.com/commercialhaskell/stack/issues/2376) * Ignore special entries when unpacking tarballs [#2361](https://github.com/commercialhaskell/stack/issues/2361) * Fixes src directory pollution of `style.css` and `highlight.js` with GHC 8's haddock [#2429](https://github.com/commercialhaskell/stack/issues/2429) * Handle filepaths with spaces in `stack ghci` [#2266](https://github.com/commercialhaskell/stack/issues/2266) * Apply ghc-options to snapshot packages [#2289](https://github.com/commercialhaskell/stack/issues/2289) * stack sdist: Fix timestamp in tarball [#2394](https://github.com/commercialhaskell/stack/pull/2394) * Allow global Stack arguments with a script [#2316](https://github.com/commercialhaskell/stack/issues/2316) * Inconsistency between ToJSON and FromJSON instances of PackageLocation [#2412](https://github.com/commercialhaskell/stack/pull/2412) * Perform Unicode normalization on filepaths [#1810](https://github.com/commercialhaskell/stack/issues/1810) * Solver: always keep ghc wired-in as hard constraints [#2453](https://github.com/commercialhaskell/stack/issues/2453) * Support OpenBSD's tar where possible, require GNU tar for xz support [#2283](https://github.com/commercialhaskell/stack/issues/2283) * Fix using --coverage with Cabal-1.24 [#2424](https://github.com/commercialhaskell/stack/issues/2424) * When marking exe installed, remove old version [#2373](https://github.com/commercialhaskell/stack/issues/2373) * Stop truncating all-cabal-hashes git repo [#2175](https://github.com/commercialhaskell/stack/issues/2175) * Handle non-ASCII filenames on Windows [#2491](https://github.com/commercialhaskell/stack/issues/2491) * Avoid using multiple versions of a package in script interpreter by passing package-id to ghc/runghc [#1957](https://github.com/commercialhaskell/stack/issues/1957) * Only pre-load compiler version when using nix integration [#2459](https://github.com/commercialhaskell/stack/issues/2459) * Solver: parse cabal errors also on Windows [#2502](https://github.com/commercialhaskell/stack/issues/2502) * Allow exec and ghci commands in interpreter mode. Scripts can now automatically open in the repl by using `exec ghci` instead of `runghc` in the shebang command. [#2510](https://github.com/commercialhaskell/stack/issues/2510) * Now consider a package to be dirty when an extra-source-file is changed. See [#2040](https://github.com/commercialhaskell/stack/issues/2040) ## 1.1.2 Release notes: * Official FreeBSD binaries are [now available](http://docs.haskellstack.org/en/stable/install_and_upgrade/#freebsd) [#1253](https://github.com/commercialhaskell/stack/issues/1253). Major changes: * Extensible custom snapshots implemented. These allow you to define snapshots which extend other snapshots. See [#863](https://github.com/commercialhaskell/stack/issues/863). Local file custom snapshots can now be safely updated without changing their name. Remote custom snapshots should still be treated as immutable. Behavior changes: * `stack path --compiler` was added in the last release, to yield a path to the compiler. Unfortunately, `--compiler` is a global option that is useful to use with `stack path`. The same functionality is now provided by `stack path --compiler-exe`. See [#2123](https://github.com/commercialhaskell/stack/issues/2123) * For packages specified in terms of a git or hg repo, the hash used in the location has changed. This means that existing downloads from older stack versions won't be used. This is a side-effect of the fix to [#2133](https://github.com/commercialhaskell/stack/issues/2133) * `stack upgrade` no longer pays attention to local stack.yaml files, just the global config and CLI options. [#1392](https://github.com/commercialhaskell/stack/issues/1392) * `stack ghci` now uses `:add` instead of `:load`, making it potentially work better with user scripts. See [#1888](https://github.com/commercialhaskell/stack/issues/1888) Other enhancements: * Grab Cabal files via Git SHA to avoid regressions from Hackage revisions [#2070](https://github.com/commercialhaskell/stack/pull/2070) * Custom snapshots now support `ghc-options`. * Package git repos are now re-used rather than re-cloned. See [#1620](https://github.com/commercialhaskell/stack/issues/1620) * `DESTDIR` is filtered from environment when installing GHC. See [#1460](https://github.com/commercialhaskell/stack/issues/1460) * `stack haddock` now supports `--hadock-arguments`. See [#2144](https://github.com/commercialhaskell/stack/issues/2144) * Signing: warn if GPG_TTY is not set as per `man gpg-agent` Bug fixes: * Now ignore project config when doing `stack init` or `stack new`. See [#2110](https://github.com/commercialhaskell/stack/issues/2110) * Packages specified by git repo can now have submodules. See [#2133](https://github.com/commercialhaskell/stack/issues/2133) * Fix of hackage index fetch retry. See re-opening of [#1418](https://github.com/commercialhaskell/stack/issues/1418#issuecomment-217633843) * HPack now picks up changes to filesystem other than package.yaml. See [#2051](https://github.com/commercialhaskell/stack/issues/2051) * "stack solver" no longer suggests --omit-packages. See [#2031](https://github.com/commercialhaskell/stack/issues/2031) * Fixed an issue with building Cabal's Setup.hs. See [#1356](https://github.com/commercialhaskell/stack/issues/1356) * Package dirtiness now pays attention to deleted files. See [#1841](https://github.com/commercialhaskell/stack/issues/1841) * `stack ghci` now uses `extra-lib-dirs` and `extra-include-dirs`. See [#1656](https://github.com/commercialhaskell/stack/issues/1656) * Relative paths outside of source dir added via `qAddDependentFile` are now checked for dirtiness. See [#1982](https://github.com/commercialhaskell/stack/issues/1982) * Signing: always use `--with-fingerprints` ## 1.1.0 Release notes: * Added Ubuntu 16.04 LTS (xenial) Apt repo. * No longer uploading new versions to Fedora 21 repo. Behavior changes: * Snapshot packages are no longer built with executable profiling. See [#1179](https://github.com/commercialhaskell/stack/issues/1179). * `stack init` now ignores symlinks when searching for cabal files. It also now ignores any directory that begins with `.` (as well as `dist` dirs) - before it would only ignore `.git`, `.stack-work`, and `dist`. * The stack executable is no longer built with `-rtsopts`. Before, when `-rtsopts` was enabled, stack would process `+RTS` options even when intended for some other program, such as when used with `stack exec -- prog +RTS`. See [#2022](https://github.com/commercialhaskell/stack/issues/2022). * The `stack path --ghc-paths` option is deprecated and renamed to `--programs`. `--compiler` is added, which points directly at the compiler used in the current project. `--compiler-bin` points to the compiler's bin dir. * For consistency with the `$STACK_ROOT` environment variable, the `stack path --global-stack-root` flag and the `global-stack-root` field in the output of `stack path` are being deprecated and replaced with the `stack-root` flag and output field. Additionally, the stack root can now be specified via the `--stack-root` command-line flag. See [#1148](https://github.com/commercialhaskell/stack/issues/1148). * `stack sig` GPG-related sub-commands were removed (folded into `upload` and `sdist`) * GPG signing of packages while uploading to Hackage is now the default. Use `upload --no-signature` if you would rather not contribute your package signature. If you don't yet have a GPG keyset, read this [blog post on GPG keys](https://fpcomplete.com/blog/2016/05/stack-security-gnupg-keys). We can add a stack.yaml config setting to disable signing if some people desire it. We hope that people will sign. Later we will be adding GPG signature verification options. * `stack build pkg-1.2.3` will now build even if the snapshot has a different package version - it is treated as an extra-dep. `stack build local-pkg-1.2.3` is an error even if the version number matches the local package [#2028](https://github.com/commercialhaskell/stack/issues/2028). * Having a `nix:` section no longer implies enabling nix build. This allows the user to globally configure whether nix is used (unless the project overrides the default explicitly). See [#1924](https://github.com/commercialhaskell/stack/issues/1924). * Remove deprecated valid-wanted field. * Docker: mount home directory in container [#1949](https://github.com/commercialhaskell/stack/issues/1949). * Deprecate `--local-bin-path` instead `--local-bin`. * `stack image`: allow absolute source paths for `add`. Other enhancements: * `stack haddock --open [PACKAGE]` opens the local haddocks in the browser. * Fix too much rebuilding when enabling/disabling profiling flags. * `stack build pkg-1.0` will now build `pkg-1.0` even if the snapshot specifies a different version (it introduces a temporary extra-dep) * Experimental support for `--split-objs` added [#1284](https://github.com/commercialhaskell/stack/issues/1284). * `git` packages with submodules are supported by passing the `--recursive` flag to `git clone`. * When using [hpack](https://github.com/sol/hpack), only regenerate cabal files when hpack files change. * hpack files can now be used in templates * `stack ghci` now runs ghci as a separate process [#1306](https://github.com/commercialhaskell/stack/issues/1306) * Retry when downloading snapshots and package indices * Many build options are configurable now in `stack.yaml`: ``` build: library-profiling: true executable-profiling: true haddock: true haddock-deps: true copy-bins: true prefetch: true force-dirty: true keep-going: true test: true test-arguments: rerun-tests: true additional-args: ['-fprof'] coverage: true no-run-tests: true bench: true benchmark-opts: benchmark-arguments: -O2 no-run-benchmarks: true reconfigure: true cabal-verbose: true ``` * A number of URLs are now configurable, useful for firewalls. See [#1794](https://github.com/commercialhaskell/stack/issues/1884). * Suggest causes when executables are missing. * Allow `--omit-packages` even without `--solver`. * Improve the generated stack.yaml. * Improve ghci results after :load Main module collision with main file path. * Only load the hackage index if necessary [#1883](https://github.com/commercialhaskell/stack/issues/1883), [#1892](https://github.com/commercialhaskell/stack/issues/1892). * init: allow local packages to be deps of deps [#1965](https://github.com/commercialhaskell/stack/issues/1965). * Always use full fingerprints from GPG [#1952](https://github.com/commercialhaskell/stack/issues/1952). * Default to using `gpg2` and fall back to `gpg` [#1976](https://github.com/commercialhaskell/stack/issues/1976). * Add a flag for --verbosity silent. * Add `haddock --open` flag [#1396](https://github.com/commercialhaskell/stack/issues/1396). Bug fixes: * Package tarballs would fail to unpack. [#1884](https://github.com/commercialhaskell/stack/issues/1884). * Fixed errant warnings about missing modules, after deleted and removed from cabal file [#921](https://github.com/commercialhaskell/stack/issues/921) [#1805](https://github.com/commercialhaskell/stack/issues/1805). * Now considers a package to dirty when the hpack file is changed [#1819](https://github.com/commercialhaskell/stack/issues/1819). * Nix: cancelling a stack build now exits properly rather than dropping into a nix-shell [#1778](https://github.com/commercialhaskell/stack/issues/1778). * `allow-newer: true` now causes `--exact-configuration` to be passed to Cabal. See [#1579](https://github.com/commercialhaskell/stack/issues/1579). * `stack solver` no longer fails with `InvalidRelFile` for relative package paths including `..`. See [#1954](https://github.com/commercialhaskell/stack/issues/1954). * Ignore emacs lock files when finding .cabal [#1897](https://github.com/commercialhaskell/stack/issues/1897). * Use lenient UTF-8 decode for build output [#1945](https://github.com/commercialhaskell/stack/issues/1945). * Clear index cache whenever index updated [#1962](https://github.com/commercialhaskell/stack/issues/1962). * Fix: Building a container image drops a .stack-work dir in the current working (sub)directory [#1975](https://github.com/commercialhaskell/stack/issues/1975). * Fix: Rebuilding when disabling profiling [#2023](https://github.com/commercialhaskell/stack/issues/2023). ## 1.0.4.3 Bug fixes: * Don't delete contents of ~/.ssh when using `stack clean --full` with Docker enabled [#2000](https://github.com/commercialhaskell/stack/issues/2000) ## 1.0.4.2 Build with path-io-1.0.0. There are no changes in behaviour from 1.0.4, so no binaries are released for this version. ## 1.0.4.1 Fixes build with aeson-0.11.0.0. There are no changes in behaviour from 1.0.4, so no binaries are released for this version. ## 1.0.4 Major changes: * Some notable changes in `stack init`: * Overall it should now be able to initialize almost all existing cabal packages out of the box as long as the package itself is consistently defined. * Choose the best possible snapshot and add extra dependencies on top of a snapshot resolver rather than a compiler resolver - [#1583](https://github.com/commercialhaskell/stack/pull/1583) * Automatically omit a package (`--omit-packages`) when it is compiler incompatible or when there are packages with conflicting dependency requirements - [#1674](https://github.com/commercialhaskell/stack/pull/1674). * Some more changes for a better user experience. Please refer to the doc guide for details. * Add support for hpack, alternative package description format [#1679](https://github.com/commercialhaskell/stack/issues/1679) Other enhancements: * Docker: pass ~/.ssh and SSH auth socket into container, so that git repos work [#1358](https://github.com/commercialhaskell/stack/issues/1358). * Docker: strip suffix from docker --version. [#1653](https://github.com/commercialhaskell/stack/issues/1653) * Docker: pass USER and PWD environment variables into container. * On each run, stack will test the stack root directory (~/.stack), and the project and package work directories (.stack-work) for whether they are owned by the current user and abort if they are not. This precaution can be disabled with the `--allow-different-user` flag or `allow-different-user` option in the global config (~/.stack/config.yaml). [#471](https://github.com/commercialhaskell/stack/issues/471) * Added `stack clean --full` option for full working dir cleanup. * YAML config: support Zip archives. * Redownload build plan if parsing fails [#1702](https://github.com/commercialhaskell/stack/issues/1702). * Give mustache templates access to a 'year' tag [#1716](https://github.com/commercialhaskell/stack/pull/1716). * Have "stack ghci" warn about module name aliasing. * Add "stack ghci --load-local-deps". * Build Setup.hs with -rtsopts [#1687](https://github.com/commercialhaskell/stack/issues/1687). * `stack init` accepts a list of directories. * Add flag infos to DependencyPlanFailures (for better error output in case of flags) [#713](https://github.com/commercialhaskell/stack/issues/713) * `stack new --bare` complains for overwrites, and add `--force` option [#1597](https://github.com/commercialhaskell/stack/issues/1597). Bug fixes: * Previously, `stack ghci` would fail with `cannot satisfy -package-id` when the implicit build step changes the package key of some dependency. * Fix: Building with ghcjs: "ghc-pkg: Prelude.chr: bad argument: 2980338" [#1665](https://github.com/commercialhaskell/stack/issues/1665). * Fix running test / bench with `--profile` / `--trace`. * Fix: build progress counter is no longer visible [#1685](https://github.com/commercialhaskell/stack/issues/1685). * Use "-RTS" w/ profiling to allow extra args [#1772](https://github.com/commercialhaskell/stack/issues/1772). * Fix withUnpackedTarball7z to find name of srcDir after unpacking (fixes `stack setup` fails for ghcjs project on windows) [#1774](https://github.com/commercialhaskell/stack/issues/1774). * Add space before auto-generated bench opts (makes profiling options work uniformly for applications and benchmark suites) [#1771](https://github.com/commercialhaskell/stack/issues/1771). * Don't try to find plugin if it resembles flag. * Setup.hs changes cause package dirtiness [#1711](https://github.com/commercialhaskell/stack/issues/1711). * Send "stack templates" output to stdout [#1792](https://github.com/commercialhaskell/stack/issues/1792). ## 1.0.2 Release notes: - Arch Linux: Stack has been adopted into the [official community repository](https://www.archlinux.org/packages/community/x86_64/stack/), so we will no longer be updating the AUR with new versions. See the [install/upgrade guide](http://docs.haskellstack.org/en/stable/install_and_upgrade/#arch-linux) for current download instructions. Major changes: - `stack init` and `solver` overhaul [#1583](https://github.com/commercialhaskell/stack/pull/1583) Other enhancements: - Disable locale/codepage hacks when GHC >=7.10.3 [#1552](https://github.com/commercialhaskell/stack/issues/1552) - Specify multiple images to build for `stack image container` [docs](http://docs.haskellstack.org/en/stable/yaml_configuration/#image) - Specify which executables to include in images for `stack image container` [docs](http://docs.haskellstack.org/en/stable/yaml_configuration/#image) - Docker: pass supplemantary groups and umask into container - If git fetch fails wipe the directory and try again from scratch [#1418](https://github.com/commercialhaskell/stack/issues/1418) - Warn if newly installed executables won't be available on the PATH [#1362](https://github.com/commercialhaskell/stack/issues/1362) - stack.yaml: for `stack image container`, specify multiple images to generate, and which executables should be added to those images - GHCI: add interactive Main selection [#1068](https://github.com/commercialhaskell/stack/issues/1068) - Care less about the particular name of a GHCJS sdist folder [#1622](https://github.com/commercialhaskell/stack/issues/1622) - Unified Enable/disable help messaging [#1613](https://github.com/commercialhaskell/stack/issues/1613) Bug fixes: - Don't share precompiled packages between GHC/platform variants and Docker [#1551](https://github.com/commercialhaskell/stack/issues/1551) - Properly redownload corrupted downloads with the correct file size. [Mailing list discussion](https://groups.google.com/d/msg/haskell-stack/iVGDG5OHYxs/FjUrR5JsDQAJ) - Gracefully handle invalid paths in error/warning messages [#1561](https://github.com/commercialhaskell/stack/issues/1561) - Nix: select the correct GHC version corresponding to the snapshot even when an abstract resolver is passed via `--resolver` on the command-line. [#1641](https://github.com/commercialhaskell/stack/issues/1641) - Fix: Stack does not allow using an external package from ghci [#1557](https://github.com/commercialhaskell/stack/issues/1557) - Disable ambiguous global '--resolver' option for 'stack init' [#1531](https://github.com/commercialhaskell/stack/issues/1531) - Obey `--no-nix` flag - Fix: GHCJS Execute.hs: Non-exhaustive patterns in lambda [#1591](https://github.com/commercialhaskell/stack/issues/1591) - Send file-watch and sticky logger messages to stderr [#1302](https://github.com/commercialhaskell/stack/issues/1302) [#1635](https://github.com/commercialhaskell/stack/issues/1635) - Use globaldb path for querying Cabal version [#1647](https://github.com/commercialhaskell/stack/issues/1647) ## 1.0.0 Release notes: * We're calling this version 1.0.0 in preparation for Stackage LTS 4. Note, however, that this does not mean the code's API will be stable as this is primarily an end-user tool. Enhancements: * Added flag `--profile` flag: passed with `stack build`, it will enable profiling, and for `--bench` and `--test` it will generate a profiling report by passing `+RTS -p` to the executable(s). Great for using like `stack build --bench --profile` (remember that enabling profile will slow down your benchmarks by >4x). Run `stack build --bench` again to disable the profiling and get proper speeds * Added flag `--trace` flag: just like `--profile`, it enables profiling, but instead of generating a report for `--bench` and `--test`, prints out a stack trace on exception. Great for using like `stack build --test --trace` * Nix: all options can be overriden on command line [#1483](https://github.com/commercialhaskell/stack/issues/1483) * Nix: build environments (shells) are now pure by default. * Make verbosity silent by default in script interpreter mode [#1472](https://github.com/commercialhaskell/stack/issues/1472) * Show a message when resetting git commit fails [#1453](https://github.com/commercialhaskell/stack/issues/1453) * Improve Unicode handling in project/package names [#1337](https://github.com/commercialhaskell/stack/issues/1337) * Fix ambiguity between a stack command and a filename to execute (prefer `stack` subcommands) [#1471](https://github.com/commercialhaskell/stack/issues/1471) * Support multi line interpreter directive comments [#1394](https://github.com/commercialhaskell/stack/issues/1394) * Handle space separated pids in ghc-pkg dump (for GHC HEAD) [#1509](https://github.com/commercialhaskell/stack/issues/1509) * Add ghci --no-package-hiding option [#1517](https://github.com/commercialhaskell/stack/issues/1517) * `stack new` can download templates from URL [#1466](https://github.com/commercialhaskell/stack/issues/1466) Bug fixes: * Nix: stack exec options are passed properly to the stack sub process [#1538](https://github.com/commercialhaskell/stack/issues/1538) * Nix: specifying a shell-file works in any current working directory [#1547](https://github.com/commercialhaskell/stack/issues/1547) * Nix: use `--resolver` argument * Docker: fix missing image message and '--docker-auto-pull' * No HTML escaping for "stack new" template params [#1475](https://github.com/commercialhaskell/stack/issues/1475) * Set permissions for generated .ghci script [#1480](https://github.com/commercialhaskell/stack/issues/1480) * Restrict commands allowed in interpreter mode [#1504](https://github.com/commercialhaskell/stack/issues/1504) * stack ghci doesn't see preprocessed files for executables [#1347](https://github.com/commercialhaskell/stack/issues/1347) * All test suites run even when only one is requested [#1550](https://github.com/commercialhaskell/stack/pull/1550) * Edge cases in broken templates give odd errors [#1535](https://github.com/commercialhaskell/stack/issues/1535) * Fix test coverage bug on windows ## 0.1.10.1 Bug fixes: * `stack image container` did not actually build an image [#1473](https://github.com/commercialhaskell/stack/issues/1473) ## 0.1.10.0 Release notes: * The Stack home page is now at [haskellstack.org](http://haskellstack.org), which shows the documentation rendered by readthedocs.org. Note: this has necessitated some changes to the links in the documentation's markdown source code, so please check the links on the website before submitting a PR to fix them. * The locations of the [Ubuntu](http://docs.haskellstack.org/en/stable/install_and_upgrade/#ubuntu) and [Debian](http://docs.haskellstack.org/en/stable/install_and_upgrade/#debian) package repositories have changed to have correct URL semantics according to Debian's guidelines [#1378](https://github.com/commercialhaskell/stack/issues/1378). The old locations will continue to work for some months, but we suggest that you adjust your `/etc/apt/sources.list.d/fpco.list` to the new location to avoid future disruption. * [openSUSE and SUSE Linux Enterprise](http://docs.haskellstack.org/en/stable/install_and_upgrade/#suse) packages are now available, thanks to [@mimi1vx](https://github.com/mimi1vx). Note: there will be some lag before these pick up new versions, as they are based on Stackage LTS. Major changes: * Support for building inside a Nix-shell providing system dependencies [#1285](https://github.com/commercialhaskell/stack/pull/1285) * Add optional GPG signing on `stack upload --sign` or with `stack sig sign ...` Other enhancements: * Print latest applicable version of packages on conflicts [#508](https://github.com/commercialhaskell/stack/issues/508) * Support for packages located in Mercurial repositories [#1397](https://github.com/commercialhaskell/stack/issues/1397) * Only run benchmarks specified as build targets [#1412](https://github.com/commercialhaskell/stack/issues/1412) * Support git-style executable fall-through (`stack something` executes `stack-something` if present) [#1433](https://github.com/commercialhaskell/stack/issues/1433) * GHCi now loads intermediate dependencies [#584](https://github.com/commercialhaskell/stack/issues/584) * `--work-dir` option for overriding `.stack-work` [#1178](https://github.com/commercialhaskell/stack/issues/1178) * Support `detailed-0.9` tests [#1429](https://github.com/commercialhaskell/stack/issues/1429) * Docker: improved POSIX signal proxying to containers [#547](https://github.com/commercialhaskell/stack/issues/547) Bug fixes: * Show absolute paths in error messages in multi-package builds [#1348](https://github.com/commercialhaskell/stack/issues/1348) * Docker-built binaries and libraries in different path [#911](https://github.com/commercialhaskell/stack/issues/911) [#1367](https://github.com/commercialhaskell/stack/issues/1367) * Docker: `--resolver` argument didn't effect selected image tag * GHCi: Spaces in filepaths caused module loading issues [#1401](https://github.com/commercialhaskell/stack/issues/1401) * GHCi: cpp-options in cabal files weren't used [#1419](https://github.com/commercialhaskell/stack/issues/1419) * Benchmarks couldn't be run independently of eachother [#1412](https://github.com/commercialhaskell/stack/issues/1412) * Send output of building setup to stderr [#1410](https://github.com/commercialhaskell/stack/issues/1410) ## 0.1.8.0 Major changes: * GHCJS can now be used with stackage snapshots via the new `compiler` field. * Windows installers are now available: [download them here](http://docs.haskellstack.org/en/stable/install_and_upgrade/#windows) [#613](https://github.com/commercialhaskell/stack/issues/613) * Docker integration works with non-FPComplete generated images [#531](https://github.com/commercialhaskell/stack/issues/531) Other enhancements: * Added an `allow-newer` config option [#922](https://github.com/commercialhaskell/stack/issues/922) [#770](https://github.com/commercialhaskell/stack/issues/770) * When a Hackage revision invalidates a build plan in a snapshot, trust the snapshot [#770](https://github.com/commercialhaskell/stack/issues/770) * Added a `stack config set resolver RESOLVER` command. Part of work on [#115](https://github.com/commercialhaskell/stack/issues/115) * `stack setup` can now install GHCJS on windows. See [#1145](https://github.com/commercialhaskell/stack/issues/1145) and [#749](https://github.com/commercialhaskell/stack/issues/749) * `stack hpc report` command added, which generates reports for HPC tix files * `stack ghci` now accepts all the flags accepted by `stack build`. See [#1186](https://github.com/commercialhaskell/stack/issues/1186) * `stack ghci` builds the project before launching GHCi. If the build fails, optimistically launch GHCi anyway. Use `stack ghci --no-build` option to disable [#1065](https://github.com/commercialhaskell/stack/issues/1065) * `stack ghci` now detects and warns about various circumstances where it is liable to fail. See [#1270](https://github.com/commercialhaskell/stack/issues/1270) * Added `require-docker-version` configuration option * Packages will now usually be built along with their tests and benchmarks. See [#1166](https://github.com/commercialhaskell/stack/issues/1166) * Relative `local-bin-path` paths will be relative to the project's root directory, not the current working directory. [#1340](https://github.com/commercialhaskell/stack/issues/1340) * `stack clean` now takes an optional `[PACKAGE]` argument for use in multi-package projects. See [#583](https://github.com/commercialhaskell/stack/issues/583) * Ignore cabal_macros.h as a dependency [#1195](https://github.com/commercialhaskell/stack/issues/1195) * Pad timestamps and show local time in --verbose output [#1226](https://github.com/commercialhaskell/stack/issues/1226) * GHCi: Import all modules after loading them [#995](https://github.com/commercialhaskell/stack/issues/995) * Add subcommand aliases: `repl` for `ghci`, and `runhaskell` for `runghc` [#1241](https://github.com/commercialhaskell/stack/issues/1241) * Add typo recommendations for unknown package identifiers [#158](https://github.com/commercialhaskell/stack/issues/158) * Add `stack path --local-hpc-root` option * Overhaul dependencies' haddocks copying [#1231](https://github.com/commercialhaskell/stack/issues/1231) * Support for extra-package-dbs in 'stack ghci' [#1229](https://github.com/commercialhaskell/stack/pull/1229) * `stack new` disallows package names with "words" consisting solely of numbers [#1336](https://github.com/commercialhaskell/stack/issues/1336) * `stack build --fast` turns off optimizations * Show progress while downloading package index [#1223](https://github.com/commercialhaskell/stack/issues/1223). Bug fixes: * Fix: Haddocks not copied for dependencies [#1105](https://github.com/commercialhaskell/stack/issues/1105) * Fix: Global options did not work consistently after subcommand [#519](https://github.com/commercialhaskell/stack/issues/519) * Fix: 'stack ghci' doesn't notice that a module got deleted [#1180](https://github.com/commercialhaskell/stack/issues/1180) * Rebuild when cabal file is changed * Fix: Paths in GHC warnings not canonicalized, nor those for packages in subdirectories or outside the project root [#1259](https://github.com/commercialhaskell/stack/issues/1259) * Fix: unlisted files in tests and benchmarks trigger extraneous second build [#838](https://github.com/commercialhaskell/stack/issues/838) ## 0.1.6.0 Major changes: * `stack setup` now supports building and booting GHCJS from source tarball. * On Windows, build directories no longer display "pretty" information (like x86_64-windows/Cabal-1.22.4.0), but rather a hash of that content. The reason is to avoid the 260 character path limitation on Windows. See [#1027](https://github.com/commercialhaskell/stack/pull/1027) * Rename config files and clarify their purposes [#969](https://github.com/commercialhaskell/stack/issues/969) * `~/.stack/stack.yaml` --> `~/.stack/config.yaml` * `~/.stack/global` --> `~/.stack/global-project` * `/etc/stack/config` --> `/etc/stack/config.yaml` * Old locations still supported, with deprecation warnings * New command "stack eval CODE", which evaluates to "stack exec ghc -- -e CODE". Other enhancements: * No longer install `git` on Windows [#1046](https://github.com/commercialhaskell/stack/issues/1046). You can still get this behavior by running the following yourself: `stack exec -- pacman -Sy --noconfirm git`. * Typing enter during --file-watch triggers a rebuild [#1023](https://github.com/commercialhaskell/stack/pull/1023) * Use Haddock's `--hyperlinked-source` (crosslinked source), if available [#1070](https://github.com/commercialhaskell/stack/pull/1070) * Use Stack-installed GHCs for `stack init --solver` [#1072](https://github.com/commercialhaskell/stack/issues/1072) * New experimental `stack query` command [#1087](https://github.com/commercialhaskell/stack/issues/1087) * By default, stack no longer rebuilds a package due to GHC options changes. This behavior can be tweaked with the `rebuild-ghc-options` setting. [#1089](https://github.com/commercialhaskell/stack/issues/1089) * By default, ghc-options are applied to all local packages, not just targets. This behavior can be tweaked with the `apply-ghc-options` setting. [#1089](https://github.com/commercialhaskell/stack/issues/1089) * Docker: download or override location of stack executable to re-run in container [#974](https://github.com/commercialhaskell/stack/issues/974) * Docker: when Docker Engine is remote, don't run containerized processes as host's UID/GID [#194](https://github.com/commercialhaskell/stack/issues/194) * Docker: `set-user` option to enable/disable running containerized processes as host's UID/GID [#194](https://github.com/commercialhaskell/stack/issues/194) * Custom Setup.hs files are now precompiled instead of interpreted. This should be a major performance win for certain edge cases (biggest example: [building Cabal itself](https://github.com/commercialhaskell/stack/issues/1041)) while being either neutral or a minor slowdown for more common cases. * `stack test --coverage` now also generates a unified coverage report for multiple test-suites / packages. In the unified report, test-suites can contribute to the coverage of other packages. Bug fixes: * Ignore stack-built executables named `ghc` [#1052](https://github.com/commercialhaskell/stack/issues/1052) * Fix quoting of output failed command line arguments * Mark executable-only packages as installed when copied from cache [#1043](https://github.com/commercialhaskell/stack/pull/1043) * Canonicalize temporary directory paths [#1047](https://github.com/commercialhaskell/stack/pull/1047) * Put code page fix inside the build function itself [#1066](https://github.com/commercialhaskell/stack/issues/1066) * Add `explicit-setup-deps` option [#1110](https://github.com/commercialhaskell/stack/issues/1110), and change the default to the old behavior of using any package in the global and snapshot database [#1025](https://github.com/commercialhaskell/stack/issues/1025) * Precompiled cache checks full package IDs on Cabal < 1.22 [#1103](https://github.com/commercialhaskell/stack/issues/1103) * Pass -package-id to ghci [#867](https://github.com/commercialhaskell/stack/issues/867) * Ignore global packages when copying precompiled packages [#1146](https://github.com/commercialhaskell/stack/issues/1146) ## 0.1.5.0 Major changes: * On Windows, we now use a full MSYS2 installation in place of the previous PortableGit. This gives you access to the pacman package manager for more easily installing libraries. * Support for custom GHC binary distributions [#530](https://github.com/commercialhaskell/stack/issues/530) * `ghc-variant` option in stack.yaml to specify the variant (also `--ghc-variant` command-line option) * `setup-info` in stack.yaml, to specify where to download custom binary distributions (also `--ghc-bindist` command-line option) * Note: On systems with libgmp4 (aka `libgmp.so.3`), such as CentOS 6, you may need to re-run `stack setup` due to the centos6 GHC bindist being treated like a variant * A new `--pvp-bounds` flag to the sdist and upload commands allows automatic adding of PVP upper and/or lower bounds to your dependencies Other enhancements: * Adapt to upcoming Cabal installed package identifier format change [#851](https://github.com/commercialhaskell/stack/issues/851) * `stack setup` takes a `--stack-setup-yaml` argument * `--file-watch` is more discerning about which files to rebuild for [#912](https://github.com/commercialhaskell/stack/issues/912) * `stack path` now supports `--global-pkg-db` and `--ghc-package-path` * `--reconfigure` flag [#914](https://github.com/commercialhaskell/stack/issues/914) [#946](https://github.com/commercialhaskell/stack/issues/946) * Cached data is written with a checksum of its structure [#889](https://github.com/commercialhaskell/stack/issues/889) * Fully removed `--optimizations` flag * Added `--cabal-verbose` flag * Added `--file-watch-poll` flag for polling instead of using filesystem events (useful for running tests in a Docker container while modifying code in the host environment. When code is injected into the container via a volume, the container won't propagate filesystem events). * Give a preemptive error message when `-prof` is given as a GHC option [#1015](https://github.com/commercialhaskell/stack/issues/1015) * Locking is now optional, and will be turned on by setting the `STACK_LOCK` environment variable to `true` [#950](https://github.com/commercialhaskell/stack/issues/950) * Create default stack.yaml with documentation comments and commented out options [#226](https://github.com/commercialhaskell/stack/issues/226) * Out of memory warning if Cabal exits with -9 [#947](https://github.com/commercialhaskell/stack/issues/947) Bug fixes: * Hacky workaround for optparse-applicative issue with `stack exec --help` [#806](https://github.com/commercialhaskell/stack/issues/806) * Build executables for local extra deps [#920](https://github.com/commercialhaskell/stack/issues/920) * copyFile can't handle directories [#942](https://github.com/commercialhaskell/stack/pull/942) * Support for spaces in Haddock interface files [fpco/minghc#85](https://github.com/fpco/minghc/issues/85) * Temporarily building against a "shadowing" local package? [#992](https://github.com/commercialhaskell/stack/issues/992) * Fix Setup.exe name for --upgrade-cabal on Windows [#1002](https://github.com/commercialhaskell/stack/issues/1002) * Unlisted dependencies no longer trigger extraneous second build [#838](https://github.com/commercialhaskell/stack/issues/838) ## 0.1.4.1 Fix stack's own Haddocks. No changes to functionality (only comments updated). ## 0.1.4.0 Major changes: * You now have more control over how GHC versions are matched, e.g. "use exactly this version," "use the specified minor version, but allow patches," or "use the given minor version or any later minor in the given major release." The default has switched from allowing newer later minor versions to a specific minor version allowing patches. For more information, see [#736](https://github.com/commercialhaskell/stack/issues/736) and [#784](https://github.com/commercialhaskell/stack/pull/784). * Support added for compiling with GHCJS * stack can now reuse prebuilt binaries between snapshots. That means that, if you build package foo in LTS-3.1, that binary version can be reused in LTS-3.2, assuming it uses the same dependencies and flags. [#878](https://github.com/commercialhaskell/stack/issues/878) Other enhancements: * Added the `--docker-env` argument, to set environment variables in Docker container. * Set locale environment variables to UTF-8 encoding for builds to avoid "commitBuffer: invalid argument" errors from GHC [#793](https://github.com/commercialhaskell/stack/issues/793) * Enable translitation for encoding on stdout and stderr [#824](https://github.com/commercialhaskell/stack/issues/824) * By default, `stack upgrade` automatically installs GHC as necessary [#797](https://github.com/commercialhaskell/stack/issues/797) * Added the `ghc-options` field to stack.yaml [#796](https://github.com/commercialhaskell/stack/issues/796) * Added the `extra-path` field to stack.yaml * Code page changes on Windows only apply to the build command (and its synonyms), and can be controlled via a command line flag (still defaults to on) [#757](https://github.com/commercialhaskell/stack/issues/757) * Implicitly add packages to extra-deps when a flag for them is set [#807](https://github.com/commercialhaskell/stack/issues/807) * Use a precompiled Setup.hs for simple build types [#801](https://github.com/commercialhaskell/stack/issues/801) * Set --enable-tests and --enable-benchmarks optimistically [#805](https://github.com/commercialhaskell/stack/issues/805) * `--only-configure` option added [#820](https://github.com/commercialhaskell/stack/issues/820) * Check for duplicate local package names * Stop nagging people that call `stack test` [#845](https://github.com/commercialhaskell/stack/issues/845) * `--file-watch` will ignore files that are in your VCS boring/ignore files [#703](https://github.com/commercialhaskell/stack/issues/703) * Add `--numeric-version` option Bug fixes: * `stack init --solver` fails if `GHC_PACKAGE_PATH` is present [#860](https://github.com/commercialhaskell/stack/issues/860) * `stack solver` and `stack init --solver` check for test suite and benchmark dependencies [#862](https://github.com/commercialhaskell/stack/issues/862) * More intelligent logic for setting UTF-8 locale environment variables [#856](https://github.com/commercialhaskell/stack/issues/856) * Create missing directories for `stack sdist` * Don't ignore .cabal files with extra periods [#895](https://github.com/commercialhaskell/stack/issues/895) * Deprecate unused `--optimizations` flag * Truncated output on slow terminals [#413](https://github.com/commercialhaskell/stack/issues/413) ## 0.1.3.1 Bug fixes: * Ignore disabled executables [#763](https://github.com/commercialhaskell/stack/issues/763) ## 0.1.3.0 Major changes: * Detect when a module is compiled but not listed in the cabal file ([#32](https://github.com/commercialhaskell/stack/issues/32)) * A warning is displayed for any modules that should be added to `other-modules` in the .cabal file * These modules are taken into account when determining whether a package needs to be built * Respect TemplateHaskell addDependentFile dependency changes ([#105](https://github.com/commercialhaskell/stack/issues/105)) * TH dependent files are taken into account when determining whether a package needs to be built. * Overhauled target parsing, added `--test` and `--bench` options [#651](https://github.com/commercialhaskell/stack/issues/651) * For details, see [Build commands documentation](http://docs.haskellstack.org/en/stable/build_command/) Other enhancements: * Set the `HASKELL_DIST_DIR` environment variable [#524](https://github.com/commercialhaskell/stack/pull/524) * Track build status of tests and benchmarks [#525](https://github.com/commercialhaskell/stack/issues/525) * `--no-run-tests` [#517](https://github.com/commercialhaskell/stack/pull/517) * Targets outside of root dir don't build [#366](https://github.com/commercialhaskell/stack/issues/366) * Upper limit on number of flag combinations to test [#543](https://github.com/commercialhaskell/stack/issues/543) * Fuzzy matching support to give better error messages for close version numbers [#504](https://github.com/commercialhaskell/stack/issues/504) * `--local-bin-path` global option. Use to change where binaries get placed on a `--copy-bins` [#342](https://github.com/commercialhaskell/stack/issues/342) * Custom snapshots [#111](https://github.com/commercialhaskell/stack/issues/111) * --force-dirty flag: Force treating all local packages as having dirty files (useful for cases where stack can't detect a file change) * GHC error messages: display file paths as absolute instead of relative for better editor integration * Add the `--copy-bins` option [#569](https://github.com/commercialhaskell/stack/issues/569) * Give warnings on unexpected config keys [#48](https://github.com/commercialhaskell/stack/issues/48) * Remove Docker `pass-host` option * Don't require cabal-install to upload [#313](https://github.com/commercialhaskell/stack/issues/313) * Generate indexes for all deps and all installed snapshot packages [#143](https://github.com/commercialhaskell/stack/issues/143) * Provide `--resolver global` option [#645](https://github.com/commercialhaskell/stack/issues/645) * Also supports `--resolver nightly`, `--resolver lts`, and `--resolver lts-X` * Make `stack build --flag` error when flag or package is unknown [#617](https://github.com/commercialhaskell/stack/issues/617) * Preserve file permissions when unpacking sources [#666](https://github.com/commercialhaskell/stack/pull/666) * `stack build` etc work outside of a project * `list-dependencies` command [#638](https://github.com/commercialhaskell/stack/issues/638) * `--upgrade-cabal` option to `stack setup` [#174](https://github.com/commercialhaskell/stack/issues/174) * `--exec` option [#651](https://github.com/commercialhaskell/stack/issues/651) * `--only-dependencies` implemented correctly [#387](https://github.com/commercialhaskell/stack/issues/387) Bug fixes: * Extensions from the `other-extensions` field no longer enabled by default [#449](https://github.com/commercialhaskell/stack/issues/449) * Fix: haddock forces rebuild of empty packages [#452](https://github.com/commercialhaskell/stack/issues/452) * Don't copy over executables excluded by component selection [#605](https://github.com/commercialhaskell/stack/issues/605) * Fix: stack fails on Windows with git package in stack.yaml and no git binary on path [#712](https://github.com/commercialhaskell/stack/issues/712) * Fixed GHCi issue: Specifying explicit package versions (#678) * Fixed GHCi issue: Specifying -odir and -hidir as .stack-work/odir (#529) * Fixed GHCi issue: Specifying A instead of A.ext for modules (#498) ## 0.1.2.0 * Add `--prune` flag to `stack dot` [#487](https://github.com/commercialhaskell/stack/issues/487) * Add `--[no-]external`,`--[no-]include-base` flags to `stack dot` [#437](https://github.com/commercialhaskell/stack/issues/437) * Add `--ignore-subdirs` flag to init command [#435](https://github.com/commercialhaskell/stack/pull/435) * Handle attempt to use non-existing resolver [#436](https://github.com/commercialhaskell/stack/pull/436) * Add `--force` flag to `init` command * exec style commands accept the `--package` option (see [Reddit discussion](http://www.reddit.com/r/haskell/comments/3bd66h/stack_runghc_turtle_as_haskell_script_solution/)) * `stack upload` without arguments doesn't do anything [#439](https://github.com/commercialhaskell/stack/issues/439) * Print latest version of packages on conflicts [#450](https://github.com/commercialhaskell/stack/issues/450) * Flag to avoid rerunning tests that haven't changed [#451](https://github.com/commercialhaskell/stack/issues/451) * stack can act as a script interpreter (see [Script interpreter] (https://github.com/commercialhaskell/stack/wiki/Script-interpreter) and [Reddit discussion](http://www.reddit.com/r/haskell/comments/3bd66h/stack_runghc_turtle_as_haskell_script_solution/)) * Add the __`--file-watch`__ flag to auto-rebuild on file changes [#113](https://github.com/commercialhaskell/stack/issues/113) * Rename `stack docker exec` to `stack exec --plain` * Add the `--skip-msys` flag [#377](https://github.com/commercialhaskell/stack/issues/377) * `--keep-going`, turned on by default for tests and benchmarks [#478](https://github.com/commercialhaskell/stack/issues/478) * `concurrent-tests: BOOL` [#492](https://github.com/commercialhaskell/stack/issues/492) * Use hashes to check file dirtiness [#502](https://github.com/commercialhaskell/stack/issues/502) * Install correct GHC build on systems with libgmp.so.3 [#465](https://github.com/commercialhaskell/stack/issues/465) * `stack upgrade` checks version before upgrading [#447](https://github.com/commercialhaskell/stack/issues/447) ## 0.1.1.0 * Remove GHC uncompressed tar file after installation [#376](https://github.com/commercialhaskell/stack/issues/376) * Put stackage snapshots JSON on S3 [#380](https://github.com/commercialhaskell/stack/issues/380) * Specifying flags for multiple packages [#335](https://github.com/commercialhaskell/stack/issues/335) * single test suite failure should show entire log [#388](https://github.com/commercialhaskell/stack/issues/388) * valid-wanted is a confusing option name [#386](https://github.com/commercialhaskell/stack/issues/386) * stack init in multi-package project should use local packages for dependency checking [#384](https://github.com/commercialhaskell/stack/issues/384) * Display information on why a snapshot was rejected [#381](https://github.com/commercialhaskell/stack/issues/381) * Give a reason for unregistering packages [#389](https://github.com/commercialhaskell/stack/issues/389) * `stack exec` accepts the `--no-ghc-package-path` parameter * Don't require build plan to upload [#400](https://github.com/commercialhaskell/stack/issues/400) * Specifying test components only builds/runs those tests [#398](https://github.com/commercialhaskell/stack/issues/398) * `STACK_EXE` environment variable * Add the `stack dot` command * `stack upgrade` added [#237](https://github.com/commercialhaskell/stack/issues/237) * `--stack-yaml` command line flag [#378](https://github.com/commercialhaskell/stack/issues/378) * `--skip-ghc-check` command line flag [#423](https://github.com/commercialhaskell/stack/issues/423) Bug fixes: * Haddock links to global packages no longer broken on Windows [#375](https://github.com/commercialhaskell/stack/issues/375) * Make flags case-insensitive [#397](https://github.com/commercialhaskell/stack/issues/397) * Mark packages uninstalled before rebuilding [#365](https://github.com/commercialhaskell/stack/issues/365) ## 0.1.0.0 * Fall back to cabal dependency solver when a snapshot can't be found * Basic implementation of `stack new` [#137](https://github.com/commercialhaskell/stack/issues/137) * `stack solver` command [#364](https://github.com/commercialhaskell/stack/issues/364) * `stack path` command [#95](https://github.com/commercialhaskell/stack/issues/95) * Haddocks [#143](https://github.com/commercialhaskell/stack/issues/143): * Build for dependencies * Use relative links * Generate module contents and index for all packages in project ## 0.0.3 * `--prefetch` [#297](https://github.com/commercialhaskell/stack/issues/297) * `upload` command ported from stackage-upload [#225](https://github.com/commercialhaskell/stack/issues/225) * `--only-snapshot` [#310](https://github.com/commercialhaskell/stack/issues/310) * `--resolver` [#224](https://github.com/commercialhaskell/stack/issues/224) * `stack init` [#253](https://github.com/commercialhaskell/stack/issues/253) * `--extra-include-dirs` and `--extra-lib-dirs` [#333](https://github.com/commercialhaskell/stack/issues/333) * Specify intra-package target [#201](https://github.com/commercialhaskell/stack/issues/201) ## 0.0.2 * Fix some Windows specific bugs [#216](https://github.com/commercialhaskell/stack/issues/216) * Improve output for package index updates [#227](https://github.com/commercialhaskell/stack/issues/227) * Automatically update indices as necessary [#227](https://github.com/commercialhaskell/stack/issues/227) * --verbose flag [#217](https://github.com/commercialhaskell/stack/issues/217) * Remove packages (HTTPS and Git) [#199](https://github.com/commercialhaskell/stack/issues/199) * Config values for system-ghc and install-ghc * Merge `stack deps` functionality into `stack build` * `install` command [#153](https://github.com/commercialhaskell/stack/issues/153) and [#272](https://github.com/commercialhaskell/stack/issues/272) * overriding architecture value (useful to force 64-bit GHC on Windows, for example) * Overhauled test running (allows cycles, avoids unnecessary recompilation, etc) ## 0.0.1 * First public release, beta quality stack-1.5.1/doc/CONTRIBUTING.md0000644000000000000000000000754313135651271014024 0ustar0000000000000000# Contributors Guide ## Bug Reports Please [open an issue](https://github.com/commercialhaskell/stack/issues/new) and use the provided template to include all necessary details. The more detailed your report, the faster it can be resolved and will ensure it is resolved in the right way. Once your bug has been resolved, the responsible person will tag the issue as _Needs confirmation_ and assign the issue back to you. Once you have tested and confirmed that the issue is resolved, close the issue. If you are not a member of the project, you will be asked for confirmation and we will close it. ## Documentation If you would like to help with documentation, please note that for most cases the Wiki has been deprecated in favor of markdown files placed in a new `/doc` subdirectory of the repository itself. Please submit a [pull request](https://help.github.com/articles/using-pull-requests/) with your changes/additions. The documentation is rendered on [haskellstack.org](http://haskellstack.org) by readthedocs.org using Sphinx and CommonMark. Since links and formatting vary from GFM, please check the documentation there before submitting a PR to fix those. In particular, links to other documentation files intentionally have `.html` extensions instead of `.md`, unfortunately (see [#1506](https://github.com/commercialhaskell/stack/issues/1506) for details). If your changes move or rename files, or subsume Wiki content, please continue to leave a file/page in the old location temporarily, in addition to the new location. This will allow users time to update any shared links to the old location. Please also update any links in other files, or on the Wiki, to point to the new file location. ## Code If you would like to contribute code to fix a bug, add a new feature, or otherwise improve `stack`, pull requests are most welcome. It's a good idea to [submit an issue](https://github.com/commercialhaskell/stack/issues/new) to discuss the change before plowing into writing code. If you'd like to help out but aren't sure what to work on, look for issues with the [awaiting pr](https://github.com/commercialhaskell/stack/issues?q=is%3Aopen+is%3Aissue+label%3A%22awaiting+pr%22) label. Issues that are suitable for newcomers to the codebase have the [newcomer](https://github.com/commercialhaskell/stack/issues?q=is%3Aopen+is%3Aissue+label%3A%22awaiting+pr%22+label%3Anewcomer) label. Best to post a comment to the issue before you start work, in case anyone has already started. Please include a [ChangeLog](https://github.com/commercialhaskell/stack/blob/master/ChangeLog.md) entry and [documentation](https://github.com/commercialhaskell/stack/tree/master/doc/) updates with your pull request. ## Code Quality The Stack projects uses [HLint](https://github.com/ndmitchell/hlint) as a code quality tool. Note that stack contributors need not dogmatically follow the suggested hints but are encouraged to debate their usefulness. If you find a hint is not useful and detracts from readability, consider marking it in the [configuration file](https://github.com/commercialhaskell/stack/blob/master/HLint.hs) to be ignored. Please refer to the [HLint manual](https://github.com/ndmitchell/hlint#ignoring-hints) for configuration syntax. Quoting [@mgsloan](https://github.com/commercialhaskell/stack/pulls?utf8=%E2%9C%93&q=is%3Apr%20author%3Amgsloan): > We are optimizing for code clarity, not code concision or what HLint thinks. You can install HLint with stack. You might want to install it in the global project in case you run into dependency conflicts. HLint can report hints in your favourite text editor. Refer to the HLint repository for more details. To install: ``` stack install hlint ``` Once installed, you can check your changes with: ``` hlint src/ test/ --cpp-simple --hint=HLint.hs ``` Where `--cpp-simple` strips `#` lines and `--hint` explicitly specifies the configuration file. stack-1.5.1/doc/coverage.md0000644000000000000000000001236313063526313013702 0ustar0000000000000000# Code Coverage Code coverage is enabled by passing the `--coverage` flag to `stack build`. ## Usage `stack test --coverage` is quite streamlined for the following use-case: 1. You have test-suites which exercise your local packages. 2. These test-suites link against your library, rather than building the library directly. Coverage information is only given for libraries, ignoring the modules which get compiled directly into your executable. A common case where this doesn't happen is when your test-suite and library both have something like `hs-source-dirs: src/`. In this case, when building your test-suite you may also be compiling your library, instead of just linking against it. When your project has these properties, you will get the following: 1) Textual coverage reports in the build output. 2) A unified textual and HTML report, considering the coverage on all local libraries, based on all of the tests that were run. 3) An index of all generated HTML reports, at `$(stack path --local-hpc-root)/index.html`. ## "stack hpc report" command The `stack hpc report` command generates a report for a selection of targets and `.tix` files. For example, if we have 3 different packages with test-suites, packages `A`, `B`, and `C`, the default unified report will have coverage from all 3. If we want a unified report with just two, we can instead run: ``` $ stack hpc report A B ``` This will output a textual report for the combined coverage from `A` and `B`'s test-suites, along with a path to the HTML for the report. To further streamline this process, you can pass the `--open` option, to open the report in your browser. This command also supports taking extra `.tix` files. If you've also built an executable, against exactly the same library versions of `A`, `B`, and `C`, then you could do the following: ``` $ stack exec -- an-exe $ stack hpc report A B C an-exe.tix ``` This report will consider all test results as well as the newly generated `an-exe.tix` file. Since this is a common use-case, there is a convenient flag to use all stored results - `stack hpc report --all an-exe.tix`. ## "extra-tix-files" directory During the execution of the build, you can place additional tix files in `$(stack path --local-hpc-root)/extra-tix-files/` in order for them to be included in the unified report. A couple caveats: * These tix files must be generated by executables that are built against the exact same library versions. Also note that, on subsequent builds with coverage, the `$(stack path --local-hpc-root)` directory will be recursively deleted. It just stores the most recent coverage data. * These tix files will not be considered by `stack hpc report` unless listed explicitly by file name. ## Implementation details Most users can get away with just understanding the above documentation. However, advanced users may want to understand exactly how `--coverage` works: 1. The GHC option `-fhpc` gets passed to all local packages. This tells GHC to output executables that track coverage information and output them to `.tix` files. `the-exe-name.tix` files will get written to the working directory of the executable. When switching on this flag, it will usually cause all local packages to be rebuilt (see [#1940](https://github.com/commercialhaskell/stack/issues/1940). 2. Before the build runs with `--coverage`, the contents of `stack path --local-hpc-root` gets deleted. This prevents old reports from getting mixed with new reports. If you want to preserve report information from multiple runs, copy the contents of this path to a new folder. 3. Before a test run, if a `test-name.tix` file exists in the package directory, it will be deleted. 4. After a test run, it will expect a `test-name.tix` file to exist. This file will then get loaded, modified, and outputted to `$(stack path --local-hpc-root)/pkg-name/test-name/test-name.tix)`. The `.tix` file gets modified to remove coverage file that isn't associated with a library. So, this means that you won't get coverage information for the modules compiled in the `executable` or `test-suite` stanza of your cabal file. This makes it possible to directly union multiple `*.tix` files from different executables (assuming they are using the exact same versions of the local packages). If there is enough popular demand, it may be possible in the future to give coverage information for modules that are compiled directly into the executable. See [#1359](https://github.com/commercialhaskell/stack/issues/1359). 5. Once we have a `.tix` file for a test, we also generate a textual and HTML report for it. The textual report is sent to the terminal. The index of the test-specific HTML report is available at `$(stack path --local-hpc-root)/pkg-name/test-name/index.html` 6. After the build completes, if there are multiple output `*.tix` files, they get combined into a unified report. The index of this report will be available at `$(stack path --local-hpc-root)/combined/all/index.html` 7. Finally, an index of the resulting coverage reports is generated. It links to the individual coverage reports (one for each test-suite), as well as the unified report. This index is available at `$(stack path --local-hpc-root)/index.html` stack-1.5.1/doc/custom_snapshot.md0000644000000000000000000001355613135652051015344 0ustar0000000000000000# Custom Snapshots Custom snapshots allow you to create your own snapshots, which provide a list of specific hackage packages to use, along with flags and ghc-options. The definition of a basic snapshot looks like the following: ```yaml resolver: ghc-8.0 packages: - unordered-containers-0.2.7.1 - hashable-1.2.4.0 - text-1.2.2.1 flags: unordered-containers: debug: true ``` If you put this in a `snapshot.yaml` file in the same directory as your project, you can now use the custom snapshot like this: ```yaml resolver: name: simple-snapshot # Human readable name for the snapshot location: simple-snapshot.yaml ``` This is an example of a custom snapshot stored in the filesystem. They are assumed to be mutable, so you are free to modify it. We detect that the snapshot has changed by hashing the contents of the involved files, and using it to identify the snapshot internally. It is often reasonably efficient to modify a custom snapshot, due to stack sharing snapshot packages whenever possible. ## Using a URL instead of a filepath For efficiency, URLs are treated differently. If I uploaded the snapshot to `https://domain.org/snapshot-1.yaml`, it is expected to be immutable. If you change that file, then you lose any reproducibility guarantees. ## Extending snapshots The example custom snapshot above uses a compiler resolver, and so has few packages. We can also extend existing snapshots, by using the usual [resolver setting found in stack configurations](yaml_configuration.md#resolver). All possible resolver choices are valid, so this means that custom snapshots can even extend other custom snapshots. Lets say that we want to use `lts-7.1`, but use a different version of `text` than the one it comes with, `1.2.2.1`. To downgrade it to `1.2.2.0`, we need a custom snapshot file with the following: ```yaml resolver: lts-7.1 packages: - text-1.2.2.0 ``` ### Overriding the compiler The following snapshot specification will be identical to `lts-7.1`, but instead use `ghc-7.10.3` instead of `ghc-8.0.1`: ```yaml resolver: lts-7.1 compiler: ghc-7.10.3 ``` ### Dropping packages The following snapshot specification will be identical to `lts-7.1`, but without the `text` package in our snapshot. Removing this package will cause all the packages that depend on `text` to be unbuildable, but they will still be present in the snapshot. ```yaml resolver: lts-7.1 drop-packages: - text ``` ### Specifying ghc-options In order to specify ghc-options for a package, you use the same syntax as the [ghc-options](yaml_configuration.md#ghc-options) field for build configuration. The following snapshot specification will be identical to `lts-7.1`, but provides `-O1` as a ghc-option for `text`: ```yaml resolver: lts-7.1 packages: - text-1.2.2.1 ghc-options: text: -O1 ``` This works somewhat differently than the stack.yaml `ghc-options` field, in that options can only be specified for packages that are mentioned in the custom snapshot's `packages` list. It sets the ghc-options, rather than extending those specified in the snapshot being extended. Another difference is that the `*` entry for `ghc-options` applies to all packages in the `packages` list, rather than all packages in the snapshot. ### Specifying flags In order to specify flags for a package, you use the same syntax as the [flags](yaml_configuration.md#flags) field for build configuration. The following snapshot specification will be identical to `lts-7.1`, but it enables the `developer` cabal flag: ```yaml resolver: lts-7.1 packages: - text-1.2.2.1 flags: text: developer: true ``` ## YAML format In summary, the YAML format of custom snapshots has the following fields which are directly related to the same fields in the [build configuration format](yaml_configuration.md): * `resolver`, which specifies which snapshot to extend. It takes the same values as the [`resolver` field in stack.yaml](yaml_configuration.md#resolver). * `compiler`, which specifies or overrides the selection of compiler. If `resolver` is absent, then a specification of `compiler` is required. Its semantics are the same as the [`compiler` field in stack.yaml](yaml_configuration.md#compiler). Some fields look similar, but behave differently: * `flags` specifies which cabal flags to use with each package. In order to specify a flag for a package, it *must* be listed in the `packages` list. * `ghc-options`, which specifies which cabal flags to use with each package. In order to specify ghc-options for a package, it *must* be listed in the `packages` list. The `*` member of the map specifies flags that apply to every package in the `packages` list. There are two fields which work differently than in the build configuration format: * `packages`, which specifies a list of hackage package versions. Note that when a package version is overridden, no `flags` or `ghc-options` are taken from the snapshot that is being extended. If you want the same options as the snapshot being extended, they must be re-specified. * `drop-packages`, which specifies a list of packages to drop from the snapshot being overridden. ## Future enhancements We plan to enhance extensible snapshots in several ways in the future. See [issue #1265, about "implicit snapshots"](https://github.com/commercialhaskell/stack/issues/1265). In summary, in the future: 1) It will be possible to use a specific git repository + commit hash in the `packages` list, like in regular stack.yaml configuration. Currently, custom snapshots only work with packages on hackage. 2) `stack.yaml` configurations will implicitly create a snapshot. This means that the non-local packages will get shared between your projects, so there is less redundant compilation! 3) `flags` and `ghc-options` for packages which are not listed in `packages` are silently ignored. See [#2654](https://github.com/commercialhaskell/stack/issues/2654) for the current status of this. stack-1.5.1/doc/dependency_visualization.md0000644000000000000000000000511013063526313017176 0ustar0000000000000000# Dependency visualization You can use stack to visualize the dependencies between your packages and optionally also external dependencies. As an example, let's look at `wreq`: ``` $ stack dot | dot -Tpng -o wreq.png ``` [![wreq](https://cloud.githubusercontent.com/assets/591567/8478591/ae10a418-20d2-11e5-8945-55246dcfac62.png)](https://cloud.githubusercontent.com/assets/591567/8478591/ae10a418-20d2-11e5-8945-55246dcfac62.png) Okay that is a little boring, let's also look at external dependencies: ``` $ stack dot --external | dot -Tpng -o wreq.png ``` [![wreq_ext](https://cloud.githubusercontent.com/assets/591567/8478621/d247247e-20d2-11e5-993d-79096e382abd.png)](https://cloud.githubusercontent.com/assets/591567/8478621/d247247e-20d2-11e5-993d-79096e382abd.png) Well that is certainly a lot. As a start we can exclude `base` and then depending on our needs we can either limit the depth: ``` $ stack dot --no-include-base --external --depth 1 | dot -Tpng -o wreq.png ``` [![wreq_depth](https://cloud.githubusercontent.com/assets/591567/8484310/45b399a0-20f7-11e5-8068-031c2b352961.png)](https://cloud.githubusercontent.com/assets/591567/8484310/45b399a0-20f7-11e5-8068-031c2b352961.png) or prune packages explicitly: ``` $ stack dot --external --prune base,lens,wreq-examples,http-client,aeson,tls,http-client-tls,exceptions | dot -Tpng -o wreq_pruned.png ``` [![wreq_pruned](https://cloud.githubusercontent.com/assets/591567/8478768/adbad280-20d3-11e5-9992-914dc24fe569.png)](https://cloud.githubusercontent.com/assets/591567/8478768/adbad280-20d3-11e5-9992-914dc24fe569.png) Keep in mind that you can also save the dot file: ``` $ stack dot --external --depth 1 > wreq.dot $ dot -Tpng -o wreq.png wreq.dot ``` and pass in options to `dot` or use another graph layout engine like `twopi`: ``` $ stack dot --external --prune base,lens,wreq-examples,http-client,aeson,tls,http-client-tls,exceptions | twopi -Groot=wreq -Goverlap=false -Tpng -o wreq_pruned.png ``` [![wreq_pruned](https://cloud.githubusercontent.com/assets/591567/8495538/9fae1184-216e-11e5-9931-99e6147f8aed.png)](https://cloud.githubusercontent.com/assets/591567/8495538/9fae1184-216e-11e5-9931-99e6147f8aed.png) ## Specifying local targets and flags The `dot` and `list-dependencies` commands both also accept the following options which affect how local packages are considered: * `TARGET`, same as the targets passed to `build` * `--test`, specifying that test components should be considered * `--bench`, specifying that benchmark components should be considered * `--flag`, specifying flags which may affect cabal file `build-depends` stack-1.5.1/doc/docker_integration.md0000644000000000000000000005253013135651621015762 0ustar0000000000000000Docker integration =============================================================================== **Note:** This page is mainly about building Haskell packages inside docker containers. If you want to deploy your built Haskell programs into a docker container, look [here](GUIDE.md#docker) instead. `stack` has support for automatically performing builds inside a Docker container, using volume mounts and user ID switching to make it mostly seamless. FP Complete provides images for use with stack that include GHC, tools, and optionally have all of the Stackage LTS packages pre-installed in the global package database. The primary purpose for using stack/docker this way is for teams to ensure all developers are building in an exactly consistent environment without team members needing to deal with Docker themselves. See the [how stack can use Docker under the hood](https://www.fpcomplete.com/blog/2015/08/stack-docker) blog post for more information about the motivation and implementation of stack's Docker support. Prerequisites ------------------------------------------------------------------------------- ### Supported operating systems **Linux 64-bit**: Docker use requires machine (virtual or metal) running a Linux distribution [that Docker supports](https://docs.docker.com/installation/#installation), with a 64-bit kernel. If you do not already have one, we suggest Ubuntu 14.04 ("trusty") since this is what we test with. **macOS**: [Docker for Mac](https://docs.docker.com/docker-for-mac/) is the supported way to use Docker integration on macOS (the older Docker Machine (boot2docker) approach to using Docker on macOS is not supported due to issues with host volume mounting that make Stack nearly unusable for anything but the most trivial projects). Other Un*xen are not officially supported but there are ways to get them working. See [#194](https://github.com/commercialhaskell/stack/issues/194) for details and workarounds. **Windows does not work at all** (see [#2421](https://github.com/commercialhaskell/stack/issues/2421)). ### Docker Install the latest version of Docker by following the [instructions for your operating system](http://docs.docker.com/installation/). The Docker client should be able to connect to the Docker daemon as a non-root user. For example (from [here](http://docs.docker.com/installation/ubuntulinux/#ubuntu-raring-1304-and-saucy-1310-64-bit)): # Add the connected user "${USER}" to the docker group. # Change the user name to match your preferred user. sudo gpasswd -a ${USER} docker # Restart the Docker daemon. sudo service docker restart You will now need to log out and log in again for the group addition to take effect. Note the above has security implications. See [security](#security) for more. Usage ------------------------------------------------------------------------------- This section assumes that you already have Docker installed and working. If not, see the [prerequisites](#prerequisites) section. If you run into any trouble, see the [troubleshooting](#troubleshooting) section. ### Enable in stack.yaml The most basic configuration is to add this to your project's `stack.yaml`: docker: enable: true See [configuration](#configuration) for additional options. You can enable it on the command-line using `stack --docker`. Please note that in a docker-enabled configuration, stack uses the GHC installed in the Docker container by default. To use a compiler installed by stack, add system-ghc: false (see [`system-ghc`](yaml_configuration.md#system-ghc)). ### Use stack as normal With Docker enabled, most stack sub-commands will automatically launch themselves in an ephemeral Docker container (the container is deleted as soon as the command completes). The project directory and `~/.stack` are volume-mounted into the container, so any build artifacts are "permanent" (not deleted with the container). The first time you run a command with a new image, you will be prompted to run `stack docker pull` to pull the image first. This will pull a Docker image with a tag that matches your resolver. Only LTS resolvers are supported (we do not generate images for nightly snapshots). Not every LTS version is guaranteed to have an image existing, and new LTS images tend to lag behind the LTS snapshot being published on stackage.org. Be warned: these images are rather large! Docker sub-commands ------------------------------------------------------------------------------- These `stack docker` sub-commands have Docker-specific functionality. Most other `stack` commands will also use a Docker container under the surface if Docker is enabled. ### pull - Pull latest version of image `stack docker pull` pulls an image from the Docker registry for the first time, or updates the image by pulling the latest version. ### cleanup - Clean up old images and containers Docker images can take up quite a lot of disk space, and it's easy for them to build up if you switch between projects or your projects update their images. This sub-command will help to remove old images and containers. By default, `stack docker cleanup` will bring up an editor showing the images and containers on your system, with any stack images that haven't been used in the last seven days marked for removal. You can add or remove the `R` in the left-most column to flag or unflag an image/container for removal. When you save the file and quit the text editor, those images marked for removal will be deleted from your system. If you wish to abort the cleanup, delete all the lines from your editor. If you use Docker for purposes other than stack, you may have other images on your system as well. These will also appear in a separate section, but they will not be marked for removal by default. Run `stack docker cleanup --help` to see additional options to customize its behaviour. ### reset - Reset the Docker "sandbox" In order to preserve the contents of the in-container home directory between runs, a special "sandbox" directory is volume-mounted into the container. `stack docker reset` will reset that sandbox to its defaults. Note: `~/.stack` is separately volume-mounted, and is left alone during reset. Command-line options ------------------------------------------------------------------------------- The default Docker configuration can be overridden on the command-line. See `stack --docker-help` for a list of all Docker options, and consult [configuration](#configuration) section below for more information about their meanings. These are global options, and apply to all commands (not just `stack docker` sub-commands). Configuration ------------------------------------------------------------------------------- `stack.yaml` contains a `docker:` section with Docker settings. If this section is omitted, Docker containers will not be used. These settings can be included in project, user, or global configuration. Here is an annotated configuration file. The default values are shown unless otherwise noted. docker: # Set to false to disable using Docker. In the project configuration, # the presence of a `docker:` section implies docker is enabled unless # `enable: false` is set. In user and global configuration, this is not # the case. enable: true # The name of the repository to pull the image from. See the "repositories" # section of this document for more information about available repositories. # If this includes a tag (e.g. "my/image:tag"), that tagged image will be # used. Without a tag specified, the LTS version slug is added automatically. # Either `repo` or `image` may be specified, but not both. repo: "fpco/stack-build" # Exact Docker image name or ID. Overrides `repo`. Either `repo` or `image` # may be specified, but not both. (default none) image: "5c624ec1d63f" # Registry requires login. A login will be requested before attempting to # pull. registry-login: false # Username to log into the registry. (default none) registry-username: "myuser" # Password to log into the registry. (default none) registry-password: "SETME" # If true, the image will be pulled from the registry automatically, without # needing to run `stack docker pull`. See the "security" section of this # document for implications of enabling this. auto-pull: false # If true, the container will be run "detached" (in the background). Refer # to the Docker users guide for information about how to manage containers. # This option would rarely make sense in the configuration file, but can be # useful on the command-line. When true, implies `persist`. detach: false # If true, the container will not be deleted after it terminates. Refer to # the Docker users guide for information about how to manage containers. This # option would rarely make sense in the configuration file, but can be # useful on the command-line. `detach` implies `persist`. persist: false # What to name the Docker container. Only useful with `detach` or # `persist` true. (default none) container-name: "example-name" # Additional arguments to pass to `docker run`. (default none) run-args: ["--net=bridge"] # Directories from the host to volume-mount into the container. If it # contains a `:`, the part before the `:` is the directory on the host and # the part after the `:` is where it should be mounted in the container. # (default none, aside from the project and stack root directories which are # always mounted) mount: - "/foo/bar" - "/baz:/tmp/quux" # Environment variables to set in the container. Environment variables # are not automatically inherited from the host, so if you need any specific # variables, use the `--docker-env` command-line argument version of this to # pass them in. (default none) env: - "FOO=BAR" - "BAR=BAZ QUUX" # Location of database used to track image usage, which `stack docker cleanup` # uses to determine which images should be kept. On shared systems, it may # be useful to override this in the global configuration file so that # all users share a single database. database-path: "~/.stack/docker.db" # Location of a Docker container-compatible 'stack' executable with the # matching version. This executable must be built on linux-x86_64 and # statically linked. # Valid values are: # host: use the host's executable. This is the default when the host's # executable is known to work (e.g., from official linux-x86_64 bindist) # download: download a compatible executable matching the host's version. # This is the default when the host's executable is not known to work # image: use the 'stack' executable baked into the image. The version # must match the host's version # /path/to/stack: path on the host's local filesystem stack-exe: host # If true (the default when using the local Docker Engine), run processes # in the Docker container as the same UID/GID as the host. The ensures # that files written by the container are owned by you on the host. # When the Docker Engine is remote (accessed by tcp), defaults to false. set-user: true # Require the version of the Docker client to be within the specified # Cabal-style version range (e.g., ">= 1.6.0 && < 1.9.0") require-docker-version: "any" Image Repositories ------------------------------------------------------------------------------- FP Complete provides the following public image repositories on Docker Hub: - [fpco/stack-build](https://registry.hub.docker.com/u/fpco/stack-build/) (the default) - GHC (patched), tools (stack, cabal-install, happy, alex, etc.), and system developer libraries required to build all Stackage packages. FP Complete also builds custom variants of these images for their clients. These images can also be used directly with `docker run` and provide a complete Haskell build environment. In addition, most Docker images that contain the basics for running GHC can be used with Stack's Docker integration. For example, the [official Haskell image repository](https://hub.docker.com/_/haskell/) works. See [Custom images](#custom-images) for more details. Security ------------------------------------------------------------------------------- Having `docker` usable as a non-root user is always a security risk, and will allow root access to your system. It is also possible to craft a `stack.yaml` that will run arbitrary commands in an arbitrary docker container through that vector, thus a `stack.yaml` could cause stack to run arbitrary commands as root. While this is a risk, it is not really a greater risk than is posed by the docker permissions in the first place (for example, if you ever run an unknown shell script or executable, or ever compile an unknown Haskell package that uses Template Haskell, you are at equal risk). Nevertheless, there are [plans to close the stack.yaml loophole](https://github.com/commercialhaskell/stack/issues/260). One way to mitigate this risk is, instead of allowing `docker` to run as non-root, replace `docker` with a wrapper script that uses `sudo` to run the real Docker client as root. This way you will at least be prompted for your root password. As [@gregwebs](https://github.com/gregwebs) pointed out, put this script named `docker` in your PATH (and make sure you remove your user from the `docker` group as well, if you added it earlier): #!/bin/bash -e # The goal of this script is to maintain the security privileges of sudo # Without having to constantly type "sudo" exec sudo /usr/bin/docker "$@" Additional notes ------------------------------------------------------------------------------- ### Volume-mounts and ephemeral containers Since filesystem changes outside of the volume-mounted project directory are not persisted across runs, this means that if you `stack exec sudo apt-get install some-ubuntu-package`, that package will be installed but then the container it's installed in will disappear, thus causing it to have no effect. If you wish to make this kind of change permanent, see later instructions for how to create a [derivative Docker image](#derivative-image). Inside the container, your home directory is a special location that volume- mounted from within your project directory's `.stack-work` in such a way as that installed GHC/cabal packages are not shared between different Stackage snapshots. In addition, `~/.stack` is volume-mounted from the host. ### Network stack containers use the host's network stack within the container by default, meaning a process running in the container can connect to services running on the host, and a server process run within the container can be accessed from the host without needing to explicitly publish its port. To run the container with an isolated network, use `--docker-run-args` to pass the `--net` argument to `docker-run`. For example: stack --docker-run-args='--net=bridge --publish=3000:3000' \ exec some-server will run the container's network in "bridge" mode (which is Docker's default) and publish port 3000. ### Persistent container If you do want to do all your work, including editing, in the container, it might be better to use a persistent container in which you can install Ubuntu packages. You could get that by running something like `stack --docker-container-name=NAME --docker-persist exec --plain bash`. This means when the container exits, it won't be deleted. You can then restart it using `docker start -a -i NAME`. It's also possible to detach from a container while it continues running in the background using by pressing Ctrl-P Ctrl-Q, and then reattach to it using `docker attach NAME`. Note that each time you run `stack --docker-persist`, a _new_ persistent container is created (it will not automatically reuse the previous one). See the [Docker user guide](https://docs.docker.com/userguide/) for more information about managing Docker containers. ### Derivative image Creating your own custom derivative image can be useful if you need to install additional Ubuntu packages or make other changes to the operating system. Here is an example (replace `stack-build:custom` if you prefer a different name for your derived container, but it's best if the repo name matches what you're deriving from, only with a different tag, to avoid recompilation): ;;; On host $ sudo stack --docker-persist --docker-container-name=temp exec bash ;;; In container, make changes to OS # apt-get install r-cran-numderiv [...] # exit ;;; On host again $ docker commit temp stack-build:custom $ docker rm temp Now you have a new Docker image named `stack-build:custom`. To use the new image, run a command such as the following or update the corresponding values in your `stack.yaml`: stack --docker-image=stack-build:custom Note, however, that any time a new image is used, you will have to re-do this process. You could also use a Dockerfile to make this reusable. Consult the [Docker user guide](https://docs.docker.com/userguide/) for more on creating Docker images. ### Custom images The easiest way to create your own custom image us by extending FP Complete's images, but if you prefer to start from scratch, most images that include the basics for building code with GHC will work. The image doesn't even, strictly speaking, need to include GHC, but it does need to have libraries and tools that GHC requires (e.g., libgmp, gcc, etc.). There are also a few ways to set up images that tightens the integration: * Create a user and group named `stack`, and create a `~/.stack` directory for it. Any build plans and caches from it will be copied from the image by Stack, meaning they don't need to be downloaded separately. * Any packages in GHC's global package database will be available. This can be used to add private libraries to the image, or the make available a set of packages from an LTS release. Troubleshooting ------------------------------------------------------------------------------- ### "No Space Left on Device", but 'df' shows plenty of disk space This is likely due to the storage driver Docker is using, in combination with the large size and number of files in these images. Use `docker info|grep 'Storage Driver'` to determine the current storage driver. We recommend using either the `overlay` or `aufs` storage driver for stack, as they are least likely to give you trouble. On Ubuntu, `aufs` is the default for new installations, but older installations sometimes used `devicemapper`. The `devicemapper` storage driver's doesn't work well with large filesystems, and we have experienced other instabilities with it as well. We recommend against its use. The `btrfs` storage driver has problems running out of metadata space long before running out of actual disk space, which requires rebalancing or adding more metadata space. See [CoreOS's btrfs troubleshooting page](https://coreos.com/docs/cluster-management/debugging/btrfs-troubleshooting/) for details about how to do this. Pass the `-s ` argument to the Docker daemon to set the storage driver (in `/etc/default/docker` on Ubuntu). See [Docker daemon storage-driver option](https://docs.docker.com/reference/commandline/cli/#daemon-storage-driver-option) for more details. You may also be running out of inodes on your filesystem. Use `df -i` to check for this condition. Unfortunately, the number of inodes is set when creating the filesystem, so fixing this requires reformatting and passing the `-N` argument to mkfs.ext4. ### Name resolution doesn't work from within container On Ubuntu 12.04, by default `NetworkManager` runs `dnsmasq` service, which sets `127.0.0.1` as your DNS server. Since Docker containers cannot access this dnsmasq, Docker falls back to using Google DNS (8.8.8.8/8.8.4.4). This causes problems if you are forced to use internal DNS server. This can be fixed by executing: sudo sed 's@dns=dnsmasq@#dns=dnsmasq@' -i \ /etc/NetworkManager/NetworkManager.conf && \ sudo service network-manager restart If you have already installed Docker, you must restart the daemon for this change to take effect: sudo service docker restart The above commands turn off `dnsmasq` usage in NetworkManager configuration and restart network manager. They can be reversed by executing `sudo sed 's@#dns=dnsmasq@dns=dnsmasq@' -i /etc/NetworkManager/NetworkManager.conf && sudo service network-manager restart`. These instructions are adapted from [the Shipyard Project's QuickStart guide](https://github.com/shipyard/shipyard/wiki/QuickStart#127011-dns-server-problem-on-ubuntu). ### Cannot pull images from behind firewall that blocks TLS/SSL If you are behind a firewall that blocks TLS/SSL and pulling images from a private Docker registry, you must edit the system configuration so that the `--insecure-registry ` option is passed to the Docker daemon. For example, on Ubuntu: echo 'DOCKER_OPTS="--insecure-registry registry.example.com"' \ |sudo tee -a /etc/default/docker sudo service docker restart This does require the private registry to be available over plaintext HTTP. See [Docker daemon insecure registries documentation](https://docs.docker.com/reference/commandline/cli/#insecure-registries) for details. stack-1.5.1/doc/faq.md0000644000000000000000000005700213135651621012656 0ustar0000000000000000# FAQ So that this doesn't become repetitive: for the reasons behind the answers below, see the [Architecture](architecture.md) page. The goal of the answers here is to be as helpful and concise as possible. ## What version of GHC is used when I run something like `stack ghci`? The version of GHC, as well as which packages can be installed, are specified by the _resolver_. This may be something like `lts-8.12`, which is from the [Long Term Support (LTS) Haskell](https://github.com/fpco/lts-haskell/) project. The [user guide](GUIDE.md) discusses resolvers in more detail. Which resolver is used is determined by finding the relevant `stack.yaml` configuration file for the directory you're running the command from. This essentially works by: 1. Check for a `STACK_YAML` environment variable or the `--stack-yaml` command line argument 2. If none present, check for a `stack.yaml` file in the current directory or any parents 3. If no `stack.yaml` was found, use the _implicit global_ The implicit global is a shared project used whenever you're outside of another project. It's a sort of "mutable shared state" that you should be aware of when working with Stack. The most recent request when working with the implicit global is how to move to a more recent LTS snapshot. You can do this by running the following from outside of a project: stack config set resolver lts ## Where is stack installed and will it interfere with `ghc` (etc) I already have installed? Stack itself is installed in normal system locations based on the mechanism you used (see the [Install and upgrade](install_and_upgrade.md) page). Stack installs the Stackage libraries in `~/.stack` and any project libraries or extra dependencies in a `.stack-work` directory within each project's directory. None of this should affect any existing Haskell tools at all. ## What is the relationship between stack and cabal? * Cabal-the-library is used by stack to build your Haskell code. * cabal-install (the executable) is used by stack for its dependency solver functionality. * A .cabal file is provided for each package, and defines all package-level metadata just like it does in the cabal-install world: modules, executables, test suites, etc. No change at all on this front. * A stack.yaml file references 1 or more packages, and provides information on where dependencies come from. * `stack build` currently initializes a stack.yaml from the existing .cabal file. Project initialization is something that is still being discussed and there may be more options here for new projects in the future (see issue [253](https://github.com/commercialhaskell/stack/issues/253)) ## I need to use a different version of a package than what is provided by the LTS Haskell snapshot I'm using, what should I do? You can make tweaks to a snapshot by modifying the `extra-deps` configuration value in your `stack.yaml` file, e.g.: ```yaml resolver: lts-2.9 packages: - '.' extra-deps: - text-1.2.1.1 ``` ## I need to use a package (or version of a package) that is not available on hackage, what should I do? Add it to the `packages` list in your project's `stack.yaml`, specifying the package's source code location relative to the directory where your `stack.yaml` file lives, e.g. ```yaml resolver: lts-2.10 packages: - '.' - third-party/proprietary-dep - github-version-of/conduit - patched/diagrams extra-deps: [] ``` The above example specifies that it should include the package at the root directory (`'.'`), that the `proprietary-dep` package is found in the project's `third-party` folder, that the `conduit` package is found in the project's `github-version-of` folder, and that the `diagrams` package is found in the project's `patched` folder. This autodetects changes and reinstalls the package. To install packages directly from a Git repository, use e.g.: ```yaml resolver: lts-2.10 packages: - location: git: https://github.com/githubuser/reponame.git commit: somecommitID ``` Note that the `- '.'` line has been omitted, so the package in the root directory will not be used. ## What is the meaning of the arguments given to stack build, test, etc? Those are the targets of the build, and can have one of three formats: * A package name (e.g., `my-package`) will mean that the `my-package` package must be built * A package identifier (e.g., `my-package-1.2.3`), which includes a specific version. This is useful for passing to `stack install` for getting a specific version from upstream * A directory (e.g., `./my-package`) for including a local directory's package, including any packages in subdirectories ## I need to modify an upstream package, how should I do it? Typically, you will want to get the source for the package and then add it to your `packages` list in stack.yaml. (See the previous question.) `stack unpack` is one approach for getting the source. Another would be to add the upstream package as a submodule to your project. ## Am I required to use a Stackage snapshot to use stack? No, not at all. If you prefer dependency solving to curation, you can continue with that workflow. Instead of describing the details of how that works here, it's probably easiest to just say: run `stack init --solver` and look at the generated stack.yaml. ## How do I use this with sandboxes? Explicit sandboxing on the part of the user is not required by stack. All builds are automatically isolated into separate package databases without any user interaction. This ensures that you won't accidentally corrupt your installed packages with actions taken in other projects. ## Can I run `cabal` commands inside `stack exec`? With a recent enough version of cabal-install (>= 1.22), you can. For older versions, due to [haskell/cabal#1800](https://github.com/haskell/cabal/issues/1800), this does not work. Note that even with recent versions, for some commands you may need this extra level of indirection: ``` $ stack exec -- cabal exec -- cabal ``` However, virtually all `cabal` commands have an equivalent in stack, so this should not be necessary. In particular, `cabal` users may be accustomed to the `cabal run` command. In stack: ``` $ stack build && stack exec ``` Or, if you want to install the binaries in a shared location: ``` $ stack install $ ``` assuming your `$PATH` has been set appropriately. ## Using custom preprocessors If you have a custom preprocessor, for example, Ruby, you may have a file like: ***B.erb*** ``` haskell module B where <% (1..5).each do |i| %> test<%= i %> :: Int test<%= i %> = <%= i %> <% end %> ``` To ensure that Stack picks up changes to this file for rebuilds, add the following line to your .cabal file: extra-source-files: B.erb ## I already have GHC installed, can I still use stack? Yes. In its default configuration, stack will simply ignore any system GHC installation and use a sandboxed GHC that it has installed itself (typically via the `stack setup` command). You can find these sandboxed GHC installations in `~/.stack/programs/$platform/ghc-$version/`. If you would like stack to use your system GHC installation, use the [`--system-ghc` flag](yaml_configuration.md#system-ghc) or run `stack config set system-ghc --global true` to make stack check your `PATH` for a suitable GHC by default. Note that stack can only use a system GHC installation if its version is compatible with the configuration of the current project, particularly the [`resolver` setting](yaml_configuration.md#resolver). Note that GHC installation doesn't work for all OSes, so in some cases you will need to use `system-ghc` and install GHC yourself. ## How does stack determine what GHC to use? In its default configuration, stack determines from the current project which GHC version, architecture etc. it needs. It then looks in `~/.stack/programs/$platform/ghc-$version/` for a compatible GHC, requesting to install one via `stack setup` if none is found. If you are using the [`--system-ghc` flag](yaml_configuration.md/#system-ghc) or have configured `system-ghc: true` either in the project `stack.yaml` or the global `~/.stack/config.yaml`, stack will use the first GHC that it finds on your `PATH`, falling back on its sandboxed installations only if the found GHC doesn't comply with the various requirements (version, architecture) that your project needs. See [this issue](https://github.com/commercialhaskell/stack/issues/420) for a detailed discussion of stack's behavior when `system-ghc` is enabled. ## How do I upgrade to GHC 7.10.2 with stack? If you already have a prior version of GHC use `stack --resolver ghc-7.10 setup --reinstall`. If you don't have any GHC installed, you can skip the `--reinstall`. ## How do I get extra build tools? stack will automatically install build tools required by your packages or their dependencies, in particular alex and happy. __NOTE__: This works when using lts or nightly resolvers, not with ghc or custom resolvers. You can manually install build tools by running, e.g., `stack build alex happy`. ## How does stack choose which snapshot to use when creating a new config file? It checks the two most recent LTS Haskell major versions and the most recent Stackage Nightly for a snapshot that is compatible with all of the version bounds in your .cabal file, favoring the most recent LTS. For more information, see the snapshot auto-detection section in the architecture document. ## I'd like to use my installed packages in a different directory. How do I tell stack where to find my packages? Set the `STACK_YAML` environment variable to point to the `stack.yaml` config file for your project. Then you can run `stack exec`, `stack ghc`, etc., from any directory and still use your packages. ## My tests are failing. What should I do? Like all other targets, `stack test` runs test suites in parallel by default. This can cause problems with test suites that depend on global resources such as a database or binding to a fixed port number. A quick hack is to force stack to run all test suites in sequence, using `stack test --jobs=1`. For test suites to run in parallel developers should ensure that their test suites do not depend on global resources (e.g. by asking the OS for a random port to bind to) and where unavoidable, add a lock in order to serialize access to shared resources. ## Can I get bash autocompletion? Yes, see the [shell-autocompletion documentation](shell_autocompletion.md) ## How do I update my package index? Users of cabal are used to running `cabal update` regularly. You can do the same with stack by running `stack update`. But generally, it's not necessary: if the package index is missing, or if a snapshot refers to package/version that isn't available, stack will automatically update and then try again. If you run into a situation where stack doesn't automatically do the update for you, please report it as a bug. ## Isn't it dangerous to automatically update the index? Can't that corrupt build plans? No, stack is very explicit about which packages it's going to build for you. There are three sources of information to tell it which packages to install: the selected snapshot, the `extra-deps` configuration value, and your local packages. The only way to get stack to change its build plan is to modify one of those three. Updating the index will have no impact on stack's behavior. ## I have a custom package index I'd like to use, how do I do so? You can configure this in your stack.yaml. See [YAML configuration](yaml_configuration.md). ## How can I make sure my project builds against multiple ghc versions? You can create multiple yaml files for your project, one for each build plan. For example, you might set up your project directory like so: ``` myproject/ stack-7.8.yaml stack-7.10.yaml stack.yaml --> symlink to stack-7.8.yaml myproject.cabal src/ ... ``` When you run `stack build`, you can set the `STACK_YAML` environment variable to indicate which build plan to use. ``` $ stack build # builds using the default stack.yaml $ STACK_YAML=stack-7.10.yaml stack build # builds using the given yaml file ``` ## I heard you can use this with Docker? Yes, stack supports using Docker with images that contain preinstalled Stackage packages and the tools. See [Docker integration](docker_integration.md) for details. ## How do I use this with Travis CI? See the [Travis CI instructions](travis_ci.md) ## What is licensing restrictions on Windows? Currently on Windows GHC produces binaries linked statically with [GNU Multiple Precision Arithmetic Library](https://gmplib.org/) (GMP), which is used by [integer-gmp](https://hackage.haskell.org/package/integer-gmp) library to provide big integer implementation for Haskell. Contrary to the majority of Haskell code licensed under permissive BSD3 license, GMP library is licensed under LGPL, which means resulting binaries [have to be provided with source code or object files](http://www.gnu.org/licenses/gpl-faq.html#LGPLStaticVsDynamic). That may or may not be acceptable for your situation. Current workaround is to use GHC built with alternative big integer implementation called integer-simple, which is free from LGPL limitations as it's pure Haskell and does not use GMP. Unfortunately it has yet to be available out of the box with stack. See [issue #399](https://github.com/commercialhaskell/stack/issues/399) for the ongoing effort and information on workarounds. ## How to get a working executable on Windows? When executing a binary after building with `stack build` (e.g. for target "foo"), the command `foo.exe` might complain about missing runtime libraries (whereas `stack exec foo` works). Windows is not able to find the necessary C++ libraries from the standard prompt because they're not in the PATH environment variable. `stack exec` works because it's modifying PATH to include extra things. Those libraries are shipped with GHC (and, theoretically in some cases, MSYS). The easiest way to find them is `stack exec which`. E.g. >stack exec which libstdc++-6.dll /c/Users/Michael/AppData/Local/Programs/stack/i386-windows/ghc-7.8.4/mingw/bin/libstdc++-6.dll A quick workaround is adding this path to the PATH environment variable or copying the files somewhere Windows finds them (cf. https://msdn.microsoft.com/de-de/library/7d83bc18.aspx). Cf. issue [#425](https://github.com/commercialhaskell/stack/issues/425). ## Can I change stack's default temporary directory? Stack downloads and extracts files to `$STACK_ROOT/programs` on most platforms, which defaults to `~/.stack/programs`. On Windows `$LOCALAPPDATA\Programs\stack` is used. If there is not enough free space in this directory, Stack may fail. For instance, `stack setup` with a GHC installation requires roughly 1GB free. If this is an issue, you can set `local-programs-path` in your `~/.stack/config.yaml` to a directory on a file system with more free space. If you use Stack with Nix integration, be aware that Nix uses a `TMPDIR` variable, and if it is not set Nix sets it to some subdirectory of `/run`, which on most Linuxes is a Ramdir. Nix will run the builds in `TMPDIR`, therefore if you don't have enough RAM you will get errors about disk space. If this happens to you, please _manually_ set `TMPDIR` before launching Stack to some directory on the disk. ## Why doesn't stack rebuild my project when I specify `--ghc-options` on the command line? Because GHC options often only affect optimization levels and warning behavior, stack doesn't recompile when it detects an option change by default. This behavior can be changed though by setting the [`rebuild-ghc-options` option](yaml_configuration.md#rebuild-ghc-options) to `true`. To force recompilation manually, use the `--force-dirty` flag. If this still doesn't lead to a rebuild, add the `-fforce-recomp` flag to your `--ghc-options`. ## Why doesn't stack apply my `--ghc-options` to my dependencies? By default, stack applies command line GHC options only to local packages (these are all the packages that are specified in the `packages` section of your `stack.yaml`). For an explanation of this choice see [this discussion on the issue tracker](https://github.com/commercialhaskell/stack/issues/827#issuecomment-133263678). If you still want to set specific GHC options for a dependency, use the [`ghc-options` option](yaml_configuration.md#ghc-options) in your `stack.yaml` or global `~/.stack/config.yaml`. To change the set of packages that command line GHC options apply to, use the [`apply-ghc-options` option](yaml_configuration.md#apply-ghc-options). ## stack setup on a windows system only tells me to add certain paths to the PATH variable instead of doing it If you are using a powershell session, it is easy to automate even that step: $env:Path = ( stack setup | %{ $_ -replace '[^ ]+ ', ''} ), $env:Path -join ";" ## How do I reset / remove Stack (such as to to do a completely fresh build)? The first thing to remove is project-specific `.stack-work` directory within the project's directory. Next, remove `~/.stack` directory overall. You may have errors if you remove the latter but leave the former. Removing Stack itself will relate to how it was installed, and if you used GHC installed outside of Stack, that would need to be removed separately. ## How does stack handle parallel builds? What exactly does it run in parallel? See [issue #644](https://github.com/commercialhaskell/stack/issues/644) for more details. ## I get strange `ld` errors about recompiling with "-fPIC" Some users (myself included!) have come across a linker errors (example below) that seem to be dependent on the local environment, i.e. the package may compile on a different machine. The issue has been reported to be [non-deterministic](https://github.com/commercialhaskell/stack/issues/614) in some cases. I've had success using the docker functionality to build the project on a machine that would not compile it otherwise. ``` tmp-0.1.0.0: build Building tmp-0.1.0.0... Preprocessing executable 'tmp' for tmp-0.1.0.0... Linking dist-stack/x86_64-linux/Cabal-1.22.2.0/build/tmp/tmp ... /usr/bin/ld: dist-stack/x86_64-linux/Cabal-1.22.2.0/build/tmp/tmp-tmp/Main.o: relocation R_X86_64_32S against `stg_bh_upd_frame_info' can not be used when making a shared object; recompile with -fPIC dist-stack/x86_64-linux/Cabal-1.22.2.0/build/tmp/tmp-tmp/Main.o: error adding symbols: Bad value collect2: error: ld returned 1 exit status -- While building package tmp-0.1.0.0 using: /home/philip/.stack/programs/x86_64-linux/ghc-7.10.1/bin/runghc-7.10.1 -package=Cabal-1.22.2.0 -clear-package-db -global-package-db /home/philip/tmp/Setup.hs --builddir=dist-stack/x86_64-linux/Cabal-1.22.2.0/ build Process exited with code: ExitFailure 1 ``` The issue may be related to the use of hardening flags in some cases, specifically those related to producing position independent executables (PIE). This is tracked upstream in the [following ticket](https://ghc.haskell.org/trac/ghc/ticket/12759). Some distributions add such hardening flags by default which may be the cause of some instances of the problem. Therefore, a possible workaround might be to turn off PIE related flags. In Arch Linux, the support for this is provided by the `hardening-wrapper` package. Some possible workarounds: * Selectively disabling its PIE forcing by setting `HARDENING_PIE=0` in `/etc/hardening-wrapper.conf`. * Uninstalling the `hardening-wrapper` package and logging out then into your account again. If you manage to work around this in other distributions, please include instructions here. ## Where does the output from `--ghc-options=-ddump-splices` (and other `-ddump*` options) go? These are written to `*.dump-*` files inside the package's `.stack-work` directory. Specifically, they will be available at `PKG-DIR/$(stack path --dist-dir)/build/SOURCE-PATH`, where `SOURCE-PATH` is the path to the source file, relative to the location of the `*.cabal` file. When building named components such as test-suites, `SOURCE-PATH` will also include `COMPONENT/COMPONENT-tmp`, where `COMPONENT` is the name of the component. ## Why is DYLD_LIBRARY_PATH ignored? If you are on Mac OS X 10.11 ("El Capitan") or later, there is an [upstream GHC issue](https://ghc.haskell.org/trac/ghc/ticket/11617) which [prevents the `DYLD_LIBRARY_PATH` environment variable from being passed to GHC](https://github.com/commercialhaskell/stack/issues/1161) when System Integrity Protection (a.k.a. "rootless") is enabled. There are two known workarounds: 1. Known to work in all cases: [disable System Integrity Protection](http://osxdaily.com/2015/10/05/disable-rootless-system-integrity-protection-mac-os-x/). **WARNING: Disabling SIP will severely reduce the security of your system, so only do this if absolutely necessary!** 2. Experimental: [modify GHC's shell script wrappers to use a shell outside the protected directories](https://github.com/commercialhaskell/stack/issues/1161#issuecomment-186690904). ## Why do I get a `/usr/bin/ar: permission denied` error? If you are on OS X 10.11 ("El Capitan") or later, GHC 7.8.4 is [incompatible with System Integrity Protection (a.k.a. "rootless")](https://github.com/commercialhaskell/stack/issues/563). GHC 7.10.2 includes a fix, so this only affects users of GHC 7.8.4. If you cannot upgrade to GHC 7.10.2, you can work around it by [disabling System Integrity Protection](http://osxdaily.com/2015/10/05/disable-rootless-system-integrity-protection-mac-os-x/). **WARNING: Disabling SIP will severely reduce the security of your system, so only do this if absolutely necessary!** ## Why is the `--` argument separator ignored in Windows PowerShell Some versions of Windows PowerShell [don't pass the `--` to programs](https://github.com/commercialhaskell/stack/issues/813). The workaround is to quote the `"--"`, e.g.: stack exec "--" cabal --version This is known to be a problem on Windows 7, but seems to be fixed on Windows 10. ## Does stack also install the system/C libraries that some Cabal packages depend on? No, this is currently out of the scope of stack's target set of features. Instead of attempting to automate the installation of 3rd party dependencies, we have the following approaches for handling system dependencies: * Nix and docker help make your build and execution environment deterministic and predictable. This way, you can install system dependencies into a container, and share this container with all developers. * If you have installed some libraries into a non-standard location, [`extra-lib-dirs` / `extra-include-dirs`](yaml_configuration.md#extra-include-dirsextra-lib-dirs) to specify it. In the future, stack might give OS specific suggestions for how to install system libraries. ## How can I make `stack` aware of my custom SSL certificates? ### macOS In principle, you can use the following command to add a certificate to your system certificate keychain: sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain Some users have reported issues with this approach, see [#907](https://github.com/commercialhaskell/stack/issues/907) for more information. ### Other *NIX OSs Use the `SYSTEM_CERTIFICATE_PATH` environment variable to point at the directory where you keep your SSL certificates. ## How do I get `verbose` output from GHC when I build with cabal? Unfortunately `stack build` does not have an obvious equivalent to `cabal build -vN` which shows verbose output from GHC when building. The easiest workaround is to add `ghc-options: -vN` to the .cabal file or pass it via `stack build --ghc-options="-v"`. ## Does Stack support the Hpack specification? Yes. You can run `stack init` as usual and Stack will create a matching `stack.yaml`. stack-1.5.1/doc/ghci.md0000644000000000000000000000514513063526313013021 0ustar0000000000000000# GHCi `stack ghci` allows you to load components and files of your project into `ghci`. It uses the same TARGET syntax as `stack build`, and can also take options like `--test`, `--bench`, and `--flag`. Similarly to `stack build`, the default is to load up ghci with all libraries and executables in the project. In order to load multiple components, `stack ghci` combines all of the ghc options together. This doesn't work in the general case, but when the packages being loaded share similar conventions, it should work out. A common source of issues is when one component defines default extensions which aren't assumed by another component. For example, specifying `NoImplicitPrelude` in one component but not another is quite likely to cause failures. `ghci` will be run with `-XNoImplicitPrelude`, but it is likely that modules in the other component assume that the Prelude is implicitly imported. ## Selecting Main module When loading multiple packages, there may be multiple definitions for the `Main` module. You can specify which Main module to load by passing in the `--main-is TARGET` flag. If no selection is made and there are multiple `Main` modules, you will be asked to select from a list of options. ## Speeding up initial load There are two ways to speed up the initial startup of ghci: * `--no-build`, to skip an initial build step. This only works if the dependencies have already been built. * `--no-load`, to skip loading all defined modules into ghci. You can then directly use `:load MyModule` to load a specific module in your project. ## Loading a filepath directly Instead of the `TARGET` syntax, it is also possible to directly run `stack ghci src/MyFile.hs`. This will figure out which component the file is associated with, and use the options from that component. ## Specifying extra packages to build / depend on Sometimes you want to load ghci with an additional package, that isn't a direct dependency of your components. This can be achieved by using the `--package` flag. For example, if I want to experiment with the lens library, I can run `stack ghci --package lens`. ## Running plain ghci `stack ghci` always runs ghci configured to load code from packages in your project. In particular, this means it passes in flags like `-hide-all-packages` and `-package-id=` in order to configure which packages are visible to ghci. For doing experiments which just involve packages installed in your databases, it may be useful to run ghci plainly like `stack exec ghci`. This will run a plain `ghci` in an environment which includes `GHC_PACKAGE_PATH`, and so will have access to your databases. stack-1.5.1/doc/ghcjs.md0000644000000000000000000001622213135651271013205 0ustar0000000000000000# GHCJS To use GHCJS with stack, place a GHCJS version in the [`compiler`](yaml_configuration.md#compiler) field of `stack.yaml`. After this, all stack commands should work with GHCJS! In particular: * `stack setup` will install GHCJS from source and boot it, which takes a long time. * `stack build` will compile your code to JavaScript. In particular, the generated code for an executable ends up in `$(stack path --local-install-root)/bin/EXECUTABLE.jsexe/all.js` (bash syntax, where `EXECUTABLE` is the name of your executable). You can also build existing stack projects which target GHC, and instead build them with GHCJS. For example: `stack build --compiler ghcjs-0.2.0.9006020_ghc-7.10.3` There are advanced options for `stack setup`: `--ghcjs-boot-options` (one word at a time) and `--[no-]ghcjs-boot-clean` which will passyour settings down to the `ghcjs-boot`. You will need to know exacty what you are doing with them. Sidenote: If you receive a message like `The program 'ghcjs' version >=0.1 is required but the version of .../ghcjs could not be determined.`, then you may need to install a different version of `node`. See [issue #1496](https://github.com/commercialhaskell/stack/issues/1496). ## Example Configurations ### Recent versions of GHCJS, repacked for stack These versions of GHCJS were created by [Marcin Tolysz](https://github.com/tolysz), and were particularly crafted to include package versions which match those expected by particular stackage snapshots. For `ghcjs` based on `ghc-7.10.3` one could try: ```yaml resolver: lts-6.30 compiler: ghcjs-0.2.0.9006030_ghc-7.10.3 compiler-check: match-exact setup-info: ghcjs: source: ghcjs-0.2.0.9006030_ghc-7.10.3: url: http://ghcjs.tolysz.org/lts-6.30-9006030.tar.gz sha1: 2371e2ffe9e8781808b7a04313e6a0065b64ee51 ``` Or for the latest one based on `ghc-8.0.1` (with more features): ```yaml resolver: lts-7.19 compiler: ghcjs-0.2.1.9007019_ghc-8.0.1 compiler-check: match-exact setup-info: ghcjs: source: ghcjs-0.2.1.9007019_ghc-8.0.1: url: http://ghcjs.tolysz.org/ghc-8.0-2017-02-05-lts-7.19-9007019.tar.gz sha1: d2cfc25f9cda32a25a87d9af68891b2186ee52f9 ``` The later can be generated via: https://github.com/tolysz/prepare-ghcjs the fromer is a bit more manual. Those bundles are only tested against the latest `node-7.4.0`. In order to corrrectly boot and use ghcjs, one might need to install `alex` `happy` `hscolour` `hsc2hs` with the normal ghc. Older resolvers: |resolver|ghcjs|url|sha1| |---|---|---|---| | lts-7.15 |0.2.1| http://ghcjs.tolysz.org/ghc-8.0-2017-01-11-lts-7.15-9007015.tar.gz | 30d34e9d704bdb799066387dfa1ba98b8884d932 | | lts-7.14 |0.2.1| http://ghcjs.tolysz.org/ghc-8.0-2016-12-25-lts-7.14-9007014.tar.gz | 530c4ee5e19e2874e128431c7ad421e336df0303 | | lts-7.13 |0.2.1| http://ghcjs.tolysz.org/ghc-8.0-2016-12-18-lts-7.13-9007013.tar.gz | 0d2ebe0931b29adca7cb9d9b9f77d60095bfb864 | | lts-7.8 || http://ghcjs.tolysz.org/ghc-8.0-2016-11-07-lts-7.8-9007008.tar.gz | 190300a3725cde44b2a08be9ef829f2077bf8825 | | lts-7.7 || http://ghcjs.tolysz.org/ghc-8.0-2016-11-03-lts-7.7-9007007.tar.gz | ce169f85f1c49ad613ae77fc494d5565452ff59a | | lts-7.5 || http://ghcjs.tolysz.org/ghc-8.0-2016-10-24-lts-7.5-9007005.tar.gz | 450e81028d7f1eb82a16bc4b0809f30730c3e173 | | lts-7.4 || http://ghcjs.tolysz.org/ghc-8.0-2016-10-22-lts-7.4-9007004.tar.gz | ed77b3c15fedbadad5ab0e0afe1bd42c0a8695b4 | | lts-7.3 || http://ghcjs.tolysz.org/ghc-8.0-2016-10-11-lts-7.3-9007003.tar.gz | 3196fd5eaed670416083cf3678396d02c50096de | | lts-7.2 || http://ghcjs.tolysz.org/ghc-8.0-2016-10-01-lts-7.2-9007002.tar.gz | a41ae415328e2b257d40724d13d1386390c26322 | | lts-7.1 || http://ghcjs.tolysz.org/ghc-8.0-2016-09-26-lts-7.1-9007001-mem.tar.gz | e640724883238593e2d2f7f03991cb413ec0347b | | lts-6.25 | 0.2.0 | http://ghcjs.tolysz.org/lts-6.25-9006025.tar.gz | 3c87228579b55c05e227a7876682c2a7d4c9c007 | | lts-6.21 || http://ghcjs.tolysz.org/lts-6.21-9006021.tar.gz | 80b83f85dcec182093418e843979f4cee092fa85 | | lts-6.20 || http://ghcjs.tolysz.org/lts-6.20-9006020.tar.gz | a6cea90cd8121eee3afb201183c6e9bd6bacd94a | | lts-6.19 || http://ghcjs.tolysz.org/lts-6.19-9006019.tar.gz | ef4264d5a93b269ee4ec8f9d5139da030331d65a | | lts-6.18 || http://ghcjs.tolysz.org/lts-6.18-9006018.tar.gz | 3e9f345116c851349a5a551ffd94f7e0b74bfabb | If you do not use the same resolver, say, an older LTS snapshot, you will get some warnings like this: ``` Ignoring that the GHCJS boot package "aeson" has a different version, 0.9.0.1, than the resolver's wanted version, 0.8.0.2 Ignoring that the GHCJS boot package "attoparsec" has a different version, 0.13.0.1, than the resolver's wanted version, 0.12.1.6 Ignoring that the GHCJS boot package "scientific" has a different version, 0.3.3.8, than the resolver's wanted version, 0.3.4.2 ... ``` These warnings can usually be safely ignored, but they do indicate a divergence between your snapshot's packages, and those that are being used to compile your project. You will normally get these warnings when using a GHCJS tarball that has not been packaged with a particular snapshot in mind. ### GHCJS (old base) If you want to build some older GHCJS packages, you may need to use the "old base" GHCJS. To do this, use the following compiler info: ```yaml compiler: ghcjs-0.1.0.20150924_ghc-7.10.2 compiler-check: match-exact ``` ### Custom installed GHCJS (development branch) In order to use a GHCJS installed on your path, just add the following to your `stack.yaml`: ```yaml compiler: ghcjs-0.2.0_ghc-7.10.2 ``` (Or, `ghcjs-0.1.0_ghc-7.10.2` if you are working with an older version) ## Project with both client and server For projects with both a server and client, the recommended project organization is to put one or both of your `stack.yaml` files in sub-directories. This way, you can use the current working directory to specify whether you're working on the client or server. This will also allow more straightforward editor tooling, once projects like `ghc-mod` and `haskell-ide-engine` support GHCJS. For example, here's what a script for building both client and server looks like: ```bash #!/bin/bash # Build the client stack build --stack-yaml=client/stack.yaml # Copy over the javascript rm -f server/static/all.js cp $(stack path --stack-yaml=client/stack.yaml --local-install-root)/bin/client.jsexe/all.js server/static/all.js # Build the server stack build --stack-yaml=server/stack.yaml ``` You can also put both the yaml files in the same directory, and have e.g. `ghcjs-stack.yaml`, but this won't work well with editor integrations. ## Using stack without a snapshot If you don't want to use a snapshot, instead place the ghcjs version in the `resolver` field of your `stack.yaml`. This is also necessary when using stack `< 0.1.8`. ## Setting up GHCJS on Windows If `stack setup` command fails to successfully complete with message: `commitBuffer: invalid argument (invalid character)`, it means you have a locale problem. This problem is not exclusive to GHCJS, and might happen also during other builds. A workaround is to set _Language for non-Unicode programs_ to _English (US)_. For details see [stack issue #1448](https://github.com/commercialhaskell/stack/issues/1448). stack-1.5.1/doc/GUIDE.md0000644000000000000000000025052413135651621012750 0ustar0000000000000000# User guide stack is a modern, cross-platform build tool for Haskell code. This guide takes a new stack user through the typical workflows. This guide will not teach Haskell or involve much code, and it requires no prior experience with the Haskell packaging system or other build tools. ## Stack's functions stack handles the management of your toolchain (including GHC — the Glasgow Haskell Compiler — and, for Windows users, MSYS), building and registering libraries, building build tool dependencies, and more. While it can use existing tools on your system, stack has the capacity to be your one-stop shop for all Haskell tooling you need. This guide will follow that stack-centric approach. ### What makes stack special? The primary stack design point is __reproducible builds__. If you run `stack build` today, you should get the same result running `stack build` tomorrow. There are some cases that can break that rule (changes in your operating system configuration, for example), but, overall, stack follows this design philosophy closely. To make this a simple process, stack uses curated package sets called __snapshots__. stack has also been designed from the ground up to be user friendly, with an intuitive, discoverable command line interface. For many users, simply downloading stack and reading `stack --help` will be enough to get up and running. This guide provides a more gradual tour for users who prefer that learning style. To build your project, stack uses a `stack.yaml` file in the root directory of your project as a sort of blueprint. That file contains a reference, called a __resolver__, to the snapshot which your package will be built against. Finally, stack is __isolated__: it will not make changes outside of specific stack directories. stack-built files generally go in either the stack root directory (default `~/.stack`) or `./.stack-work` directories local to each project. The stack root directory holds packages belonging to snapshots and any stack-installed versions of GHC. Stack will not tamper with any system version of GHC or interfere with packages installed by `cabal` or any other build tools. _NOTE_ In this guide, we'll use commands as run on a GNU/Linux system (specifically Ubuntu 14.04, 64-bit) and share output from that. Output on other systems — or with different versions of stack — will be slightly different, but all commands work cross-platform, unless explicitly stated otherwise. ## Downloading and Installation The [documentation dedicated to downloading stack](install_and_upgrade.md) has the most up-to-date information for a variety of operating systems, including multiple GNU/Linux flavors. Instead of repeating that content here, please go check out that page and come back here when you can successfully run `stack --version`. The rest of this session will demonstrate the installation procedure on a vanilla Ubuntu 14.04 machine. ``` # Starting with a *really* bare machine michael@d30748af6d3d:~$ sudo apt-get install wget # Demonstrate that stack really isn't available michael@d30748af6d3d:~$ stack -bash: stack: command not found # Install stack wget -qO- https://get.haskellstack.org/ | sh # downloading... michael@d30748af6d3d:~$ stack --version Version 0.1.3.1, Git revision 908b04205e6f436d4a5f420b1c6c646ed2b804d7 ``` With stack now up and running, you're good to go. Though not required, we recommend setting your PATH environment variable to include `$HOME/.local/bin`: ``` michael@d30748af6d3d:~$ echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.bashrc ``` ## Hello World Example With stack installed, let's create a new project from a template and walk through the most common stack commands. ### stack new We'll start off with the `stack new` command to create a new *project*, that will contain a Haskell *package* of the same name. So let's pick a valid package name first: > A package is identified by a globally-unique package name, which consists of one or more alphanumeric words separated by hyphens. To avoid ambiguity, each of these words should contain at least one letter. (From the [Cabal users guide](https://www.haskell.org/cabal/users-guide/developing-packages.html#developing-packages)) We'll call our project `helloworld`, and we'll use the `new-template` project template: ``` michael@d30748af6d3d:~$ stack new helloworld new-template ``` For this first stack command, there's quite a bit of initial setup it needs to do (such as downloading the list of packages available upstream), so you'll see a lot of output. Though your exact results may vary, below is an example of the sort of output you will see. Over the course of this guide a lot of the content will begin to make more sense: ``` Downloading template "new-template" to create project "helloworld" in helloworld/ ... Using the following authorship configuration: author-email: example@example.com author-name: Example Author Name Copy these to /home/michael/.stack/config.yaml and edit to use different values. Writing default config file to: /home/michael/helloworld/stack.yaml Basing on cabal files: - /home/michael/helloworld/helloworld.cabal Downloaded lts-3.2 build plan. Caching build plan Fetched package index. Populated index cache. Checking against build plan lts-3.2 Selected resolver: lts-3.2 Wrote project config to: /home/michael/helloworld/stack.yaml ``` We now have a project in the `helloworld` directory! ### stack setup Instead of assuming you want stack to download and install GHC for you, it asks you to do this as a separate command: `setup`. If we don't run `stack setup` now, we'll later see a message that we are missing the right GHC version. Let's run stack setup: ``` michael@d30748af6d3d:~/helloworld$ stack setup Downloaded ghc-7.10.2. Installed GHC. stack will use a sandboxed GHC it installed For more information on paths, see 'stack path' and 'stack exec env' To use this GHC and packages outside of a project, consider using: stack ghc, stack ghci, stack runghc, or stack exec ``` It doesn't come through in the output here, but you'll get intermediate download percentage statistics while the download is occurring. This command may take some time, depending on download speeds. __NOTE__: GHC will be installed to your global stack root directory, so calling `ghc` on the command line won't work. See the `stack exec`, `stack ghc`, and `stack runghc` commands below for more information. ### stack build Next, we'll run the most important stack command: `stack build`. __NOTE__: If you forgot to run `stack setup` in the previous step you'll get an error: ``` michael@d30748af6d3d:~$ cd helloworld/ michael@d30748af6d3d:~/helloworld$ stack build No GHC found, expected version 7.10.2 (x86_64) (based on resolver setting in /home/michael/helloworld/stack.yaml). Try running stack setup ``` stack needs GHC in order to build your project, and `stack setup` must be run to check whether GHC is available (and install it if not). Having run `stack setup` successfully, `stack build` should build our project: ``` michael@d30748af6d3d:~/helloworld$ stack build helloworld-0.1.0.0: configure Configuring helloworld-0.1.0.0... helloworld-0.1.0.0: build Preprocessing library helloworld-0.1.0.0... [1 of 1] Compiling Lib ( src/Lib.hs, .stack-work/dist/x86_64-linux/Cabal-1.22.4.0/build/Lib.o ) In-place registering helloworld-0.1.0.0... Preprocessing executable 'helloworld-exe' for helloworld-0.1.0.0... [1 of 1] Compiling Main ( app/Main.hs, .stack-work/dist/x86_64-linux/Cabal-1.22.4.0/build/helloworld-exe/helloworld-exe-tmp/Main.o ) Linking .stack-work/dist/x86_64-linux/Cabal-1.22.4.0/build/helloworld-exe/helloworld-exe ... helloworld-0.1.0.0: install Installing library in /home/michael/helloworld/.stack-work/install/x86_64-linux/lts-3.2/7.10.2/lib/x86_64-linux-ghc-7.10.2/helloworld-0.1.0.0-6urpPe0MO7OHasGCFSyIAT Installing executable(s) in /home/michael/helloworld/.stack-work/install/x86_64-linux/lts-3.2/7.10.2/bin Registering helloworld-0.1.0.0... ``` ### stack exec Looking closely at the output of the previous command, you can see that it built both a library called "helloworld" and an executable called "helloworld-exe". We'll explain more in the next section, but, for now, just notice that the executables are installed in our project's `./.stack-work` directory. Now, Let's use `stack exec` to run our executable (which just outputs the string "someFunc"): ``` michael@d30748af6d3d:~/helloworld$ stack exec helloworld-exe someFunc ``` `stack exec` works by providing the same reproducible environment that was used to build your project to the command that you are running. Thus, it knew where to find `helloworld-exe` even though it is hidden in the `./.stack-work` directory. ### stack test Finally, like all good software, helloworld actually has a test suite. Let's run it with `stack test`: ``` michael@d30748af6d3d:~/helloworld$ stack test NOTE: the test command is functionally equivalent to 'build --test' helloworld-0.1.0.0: configure (test) Configuring helloworld-0.1.0.0... helloworld-0.1.0.0: build (test) Preprocessing library helloworld-0.1.0.0... In-place registering helloworld-0.1.0.0... Preprocessing test suite 'helloworld-test' for helloworld-0.1.0.0... [1 of 1] Compiling Main ( test/Spec.hs, .stack-work/dist/x86_64-linux/Cabal-1.22.4.0/build/helloworld-test/helloworld-test-tmp/Main.o ) Linking .stack-work/dist/x86_64-linux/Cabal-1.22.4.0/build/helloworld-test/helloworld-test ... helloworld-0.1.0.0: test (suite: helloworld-test) Test suite not yet implemented ``` Reading the output, you'll see that stack first builds the test suite and then automatically runs it for us. For both the `build` and `test` command, already built components are not built again. You can see this by running `stack build` and `stack test` a second time: ``` michael@d30748af6d3d:~/helloworld$ stack build michael@d30748af6d3d:~/helloworld$ stack test NOTE: the test command is functionally equivalent to 'build --test' helloworld-0.1.0.0: test (suite: helloworld-test) Test suite not yet implemented ``` ## Inner Workings of stack In this subsection, we'll dissect the helloworld example in more detail. ### Files in helloworld Before studying stack more, let's understand our project a bit better. ``` michael@d30748af6d3d:~/helloworld$ find * -type f LICENSE Setup.hs app/Main.hs helloworld.cabal src/Lib.hs stack.yaml test/Spec.hs ``` The `app/Main.hs`, `src/Lib.hs`, and `test/Spec.hs` files are all Haskell source files that compose the actual functionality of our project (we won't dwell on them here). The `LICENSE` file has no impact on the build, but is there for informational/legal purposes only. The files of interest here are `Setup.hs`, `helloworld.cabal`, and `stack.yaml`. The `Setup.hs` file is a component of the Cabal build system which stack uses. It's technically not needed by stack, but it is still considered good practice in the Haskell world to include it. The file we're using is straight boilerplate: ```haskell import Distribution.Simple main = defaultMain ``` Next, let's look at our `stack.yaml` file, which gives our project-level settings: ```yaml flags: {} packages: - '.' extra-deps: [] resolver: lts-3.2 ``` If you're familiar with YAML, you may recognize that the `flags` and `extra-deps` keys have empty values. We'll see more interesting usages for these fields later. Let's focus on the other two fields. `packages` tells stack which local packages to build. In our simple example, we have only a single package in our project, located in the same directory, so `'.'` suffices. However, stack has powerful support for multi-package projects, which we'll elaborate on as this guide progresses. The final field is `resolver`. This tells stack *how* to build your package: which GHC version to use, versions of package dependencies, and so on. Our value here says to use [LTS Haskell version 3.2](https://www.stackage.org/lts-3.2), which implies GHC 7.10.2 (which is why `stack setup` installs that version of GHC). There are a number of values you can use for `resolver`, which we'll cover later. The final file of import is `helloworld.cabal`. stack is built on top of the Cabal build system. In Cabal, we have individual *packages*, each of which contains a single `.cabal` file. The `.cabal` file can define 1 or more *components*: a library, executables, test suites, and benchmarks. It also specifies additional information such as library dependencies, default language pragmas, and so on. In this guide, we'll discuss the bare minimum necessary to understand how to modify a `.cabal` file. Haskell.org has the definitive [reference for the `.cabal` file format](https://www.haskell.org/cabal/users-guide/developing-packages.html). ### The setup command As we saw above, the `setup` command installed GHC for us. Just for kicks, let's run `setup` a second time: ``` michael@d30748af6d3d:~/helloworld$ stack setup stack will use a sandboxed GHC it installed For more information on paths, see 'stack path' and 'stack exec env' To use this GHC and packages outside of a project, consider using: stack ghc, stack ghci, stack runghc, or stack exec ``` Thankfully, the command is smart enough to know not to perform an installation twice. As the command output above indicates, you can use `stack path` for quite a bit of path information (which we'll play with more later). For now, we'll just look at where GHC is installed: ``` michael@d30748af6d3d:~/helloworld$ stack exec -- which ghc /home/michael/.stack/programs/x86_64-linux/ghc-7.10.2/bin/ghc ``` As you can see from that path (and as emphasized earlier), the installation is placed to not interfere with any other GHC installation, whether system-wide or even different GHC versions installed by stack. ### The build command The build command is the heart and soul of stack. It is the engine that powers building your code, testing it, getting dependencies, and more. Quite a bit of the remainder of this guide will cover more advanced `build` functions and features, such as building test and Haddocks at the same time, or constantly rebuilding blocking on file changes. *On a philosophical note:* Running the build command twice with the same options and arguments should generally be a no-op (besides things like rerunning test suites), and should, in general, produce a reproducible result between different runs. ## Adding dependencies Let's say we decide to modify our helloworld source a bit to use a new library, perhaps the ubiquitous text package. For example: ```haskell {-# LANGUAGE OverloadedStrings #-} module Lib ( someFunc ) where import qualified Data.Text.IO as T someFunc :: IO () someFunc = T.putStrLn "someFunc" ``` When we try to build this, things don't go as expected: ```haskell michael@d30748af6d3d:~/helloworld$ stack build helloworld-0.1.0.0-c91e853ce4bfbf6d394f54b135573db8: unregistering (local file changes) helloworld-0.1.0.0: configure Configuring helloworld-0.1.0.0... helloworld-0.1.0.0: build Preprocessing library helloworld-0.1.0.0... /home/michael/helloworld/src/Lib.hs:6:18: Could not find module `Data.Text.IO' Use -v to see a list of the files searched for. -- While building package helloworld-0.1.0.0 using: /home/michael/.stack/programs/x86_64-linux/ghc-7.10.2/bin/runhaskell -package=Cabal-1.22.4.0 -clear-package-db -global-package-db -package-db=/home/michael/.stack/snapshots/x86_64-linux/lts-3.2/7.10.2/pkgdb/ /tmp/stack5846/Setup.hs --builddir=.stack-work/dist/x86_64-linux/Cabal-1.22.4.0/ build exe:helloworld-exe --ghc-options -hpcdir .stack-work/dist/x86_64-linux/Cabal-1.22.4.0/hpc/.hpc/ -ddump-hi -ddump-to-file Process exited with code: ExitFailure 1 ``` Notice that it says "Could not find module." This means that the package containing the module in question is not available. To tell stack to use text, you need to add it to your `.cabal` file — specifically in your build-depends section, like this: ``` library hs-source-dirs: src exposed-modules: Lib build-depends: base >= 4.7 && < 5 -- This next line is the new one , text default-language: Haskell2010 ``` Now if we rerun `stack build`, we should get a successful result: ``` michael@d30748af6d3d:~/helloworld$ stack build text-1.2.1.3: download text-1.2.1.3: configure text-1.2.1.3: build text-1.2.1.3: install helloworld-0.1.0.0: configure Configuring helloworld-0.1.0.0... helloworld-0.1.0.0: build Preprocessing library helloworld-0.1.0.0... [1 of 1] Compiling Lib ( src/Lib.hs, .stack-work/dist/x86_64-linux/Cabal-1.22.4.0/build/Lib.o ) In-place registering helloworld-0.1.0.0... Preprocessing executable 'helloworld-exe' for helloworld-0.1.0.0... [1 of 1] Compiling Main ( app/Main.hs, .stack-work/dist/x86_64-linux/Cabal-1.22.4.0/build/helloworld-exe/helloworld-exe-tmp/Main.o ) [Lib changed] Linking .stack-work/dist/x86_64-linux/Cabal-1.22.4.0/build/helloworld-exe/helloworld-exe ... helloworld-0.1.0.0: install Installing library in /home/michael/helloworld/.stack-work/install/x86_64-linux/lts-3.2/7.10.2/lib/x86_64-linux-ghc-7.10.2/helloworld-0.1.0.0-HI1deOtDlWiAIDtsSJiOtw Installing executable(s) in /home/michael/helloworld/.stack-work/install/x86_64-linux/lts-3.2/7.10.2/bin Registering helloworld-0.1.0.0... Completed all 2 actions. ``` This output means that the text package was downloaded, configured, built, and locally installed. Once that was done, we moved on to building our local package (helloworld). At no point did we need to ask stack to build dependencies — it does so automatically. ### Listing Dependencies Let's have stack add a few more dependencies to our project. First, we'll include two new packages in the `build-depends` section for our library in our `helloworld.cabal`: ``` library hs-source-dirs: src exposed-modules: Lib build-depends: base >= 4.7 && < 5 , text -- a couple more dependencies... , filepath , containers ``` After adding these two dependencies, we can again run `stack build` to have them installed: ``` michael@d30748af6d3d:~/helloworld$ stack build helloworld-0.1.0.0: unregistering (dependencies changed) helloworld-0.1.0.0: configure Configuring helloworld-0.1.0.0... ... ``` Finally, to find out which versions of these libraries stack installed, we can ask stack to `list-dependencies`: ``` michael@d30748af6d3d:~/helloworld$ stack list-dependencies array 0.5.1.0 base 4.8.2.0 binary 0.7.5.0 bytestring 0.10.6.0 containers 0.5.6.2 deepseq 1.4.1.1 filepath 1.4.0.0 ghc-prim 0.4.0.0 helloworld 0.1.0.0 integer-gmp 1.0.0.0 text 1.2.2.1 ``` ### extra-deps Let's try a more off-the-beaten-track package: the joke [acme-missiles](http://www.stackage.org/package/acme-missiles) package. Our source code is simple: ```haskell module Lib ( someFunc ) where import Acme.Missiles someFunc :: IO () someFunc = launchMissiles ``` Again, we add this new dependency to the `.cabal` file like this: ``` library hs-source-dirs: src exposed-modules: Lib build-depends: base >= 4.7 && < 5 , text -- This next line is the new one , acme-missiles default-language: Haskell2010 ``` However, rerunning `stack build` shows us the following error message: ``` michael@d30748af6d3d:~/helloworld$ stack build While constructing the BuildPlan the following exceptions were encountered: -- While attempting to add dependency, Could not find package acme-missiles in known packages -- Failure when adding dependencies: acme-missiles: needed (-any), stack configuration has no specified version (latest applicable is 0.3) needed for package: helloworld-0.1.0.0 Recommended action: try adding the following to your extra-deps in /home/michael/helloworld/stack.yaml - acme-missiles-0.3 You may also want to try the 'stack solver' command ``` It says acme-missiles is "not present in build plan." This brings us to the next major topic in using stack. ## Curated package sets Remember above when `stack new` selected the lts-3.2 resolver for us? That defined our build plan and available packages. When we tried using the text package, it just worked, because it was part of the lts-3.2 *package set*. But acme-missiles is not part of that package set, so building failed. To add this new dependency, we'll use the `extra-deps` field in `stack.yaml` to define extra dependencies not present in the resolver. With that change, our `stack.yaml` looks like: ```yaml flags: {} packages: - '.' extra-deps: - acme-missiles-0.3 # not in lts-3.2 resolver: lts-3.2 ``` Now `stack build` will succeed. With that out of the way, let's dig a little bit more into these package sets, also known as *snapshots*. We mentioned lts-3.2, and you can get quite a bit of information about it at [https://www.stackage.org/lts-3.2](https://www.stackage.org/lts-3.2), including: * The appropriate resolver value (`resolver: lts-3.2`, as we used above) * The GHC version used * A full list of all packages available in this snapshot * The ability to perform a Hoogle search on the packages in this snapshot * A [list of all modules](https://www.stackage.org/lts-3.2/docs) in a snapshot, which can be useful when trying to determine which package to add to your `.cabal` file You can also see a [list of all available snapshots](https://www.stackage.org/snapshots). You'll notice two flavors: LTS (for "Long Term Support") and Nightly. You can read more about them on the [LTS Haskell Github page](https://github.com/fpco/lts-haskell#readme). If you're not sure which to use, start with LTS Haskell (which stack will lean towards by default as well). ## Resolvers and changing your compiler version Let's explore package sets a bit further. Instead of lts-3.2, let's change our `stack.yaml` file to use [nightly-2015-08-26](https://www.stackage.org/nightly-2015-08-26). Rerunning `stack build` will produce: ``` michael@d30748af6d3d:~/helloworld$ stack build Downloaded nightly-2015-08-26 build plan. Caching build plan stm-2.4.4: configure stm-2.4.4: build stm-2.4.4: install acme-missiles-0.3: configure acme-missiles-0.3: build acme-missiles-0.3: install helloworld-0.1.0.0: configure Configuring helloworld-0.1.0.0... helloworld-0.1.0.0: build Preprocessing library helloworld-0.1.0.0... In-place registering helloworld-0.1.0.0... Preprocessing executable 'helloworld-exe' for helloworld-0.1.0.0... Linking .stack-work/dist/x86_64-linux/Cabal-1.22.4.0/build/helloworld-exe/helloworld-exe ... helloworld-0.1.0.0: install Installing library in /home/michael/helloworld/.stack-work/install/x86_64-linux/nightly-2015-08-26/7.10.2/lib/x86_64-linux-ghc-7.10.2/helloworld-0.1.0.0-6cKaFKQBPsi7wB4XdqRv8w Installing executable(s) in /home/michael/helloworld/.stack-work/install/x86_64-linux/nightly-2015-08-26/7.10.2/bin Registering helloworld-0.1.0.0... Completed all 3 actions. ``` We can also change resolvers on the command line, which can be useful in a Continuous Integration (CI) setting, like on Travis. For example: ``` michael@d30748af6d3d:~/helloworld$ stack --resolver lts-3.1 build Downloaded lts-3.1 build plan. Caching build plan stm-2.4.4: configure # Rest is the same, no point copying it ``` When passed on the command line, you also get some additional "short-cut" versions of resolvers: `--resolver nightly` will use the newest Nightly resolver available, `--resolver lts` will use the newest LTS, and `--resolver lts-2` will use the newest LTS in the 2.X series. The reason these are only available on the command line and not in your `stack.yaml` file is that using them: 1. Will slow down your build (since stack then needs to download information on the latest available LTS each time it builds) 2. Produces unreliable results (since a build run today may proceed differently tomorrow because of changes outside of your control) ### Changing GHC versions Finally, let's try using an older LTS snapshot. We'll use the newest 2.X snapshot: ``` michael@d30748af6d3d:~/helloworld$ stack --resolver lts-2 build Selected resolver: lts-2.22 Downloaded lts-2.22 build plan. Caching build plan No GHC found, expected version 7.8.4 (x86_64) (based on resolver setting in /home/michael/helloworld/stack.yaml). Try running stack setup ``` This fails, because GHC 7.8.4 (which lts-2.22 uses) is not available on our system. So, we see that different LTS versions (2 vs 3 in this case) use different GHC versions. Now, how do we get the right GHC version after changing the LTS version? One answer is to use `stack setup` like we did above, this time with the `--resolver lts-2` option. However, there's another method worth mentioning: the `--install-ghc` flag. ``` michael@d30748af6d3d:~/helloworld$ stack --resolver lts-2 --install-ghc build Selected resolver: lts-2.22 Downloaded ghc-7.8.4. Installed GHC. stm-2.4.4: configure # Mostly same as before, nothing interesting to see ``` What's nice about `--install-ghc` is: 1. You don't need to have an extra step in your build script 2. It only requires downloading the information on latest snapshots once As mentioned above, the default behavior of stack is to *not* install new versions of GHC automatically. We want to avoid surprising users with large downloads/installs. The `--install-ghc` flag simply changes that default behavior. ### Other resolver values We've mentioned `nightly-YYYY-MM-DD` and `lts-X.Y` values for the resolver. There are actually other options available, and the list will grow over time. At the time of writing: * `ghc-X.Y.Z`, for requiring a specific GHC version but no additional packages * Experimental GHCJS support * Experimental custom snapshot support The most up-to-date information can always be found in the [stack.yaml documentation](yaml_configuration.md#resolver). ## Existing projects Alright, enough playing around with simple projects. Let's take an open source package and try to build it. We'll be ambitious and use [yackage](https://www.stackage.org/package/yackage), a local package server using [Yesod](http://www.yesodweb.com/). To get the code, we'll use the `stack unpack` command: ``` cueball:~$ stack unpack yackage-0.8.0 Unpacked yackage-0.8.0 to /var/home/harendra/yackage-0.8.0/ cueball:~$ cd yackage-0.8.0/ ``` ### stack init This new directory does not have a `stack.yaml` file, so we need to make one first. We could do it by hand, but let's be lazy instead with the `stack init` command: ``` cueball:~/yackage-0.8.0$ stack init Using cabal packages: - yackage.cabal Selecting the best among 6 snapshots... * Matches lts-4.1 Selected resolver: lts-4.1 Initialising configuration using resolver: lts-4.1 Total number of user packages considered: 1 Writing configuration to file: stack.yaml All done. ``` stack init does quite a few things for you behind the scenes: * Finds all of the `.cabal` files in your current directory and subdirectories (unless you use `--ignore-subdirs`) and determines the packages and versions they require * Finds the best combination of snapshot and package flags that allows everything to compile with minimum external dependencies * It tries to look for the best matching snapshot from latest LTS, latest nightly, other LTS versions in that order Assuming it finds a match, it will write your `stack.yaml` file, and everything will work. #### External Dependencies Given that LTS Haskell and Stackage Nightly have ~1400 of the most common Haskell packages, this will often be enough to build most packages. However, at times, you may find that not all dependencies required may be available in the Stackage snapshots. Let's simulate an unsatisfied dependency by adding acme-missiles to our build-depends and re-initing: ``` cueball:~/yackage-0.8.0$ stack init --force Using cabal packages: - yackage.cabal Selecting the best among 6 snapshots... * Partially matches lts-4.1 acme-missiles not found - yackage requires -any - yackage flags: upload = True * Partially matches nightly-2016-01-16 acme-missiles not found - yackage requires -any - yackage flags: upload = True * Partially matches lts-3.22 acme-missiles not found - yackage requires -any - yackage flags: upload = True . . . Selected resolver: lts-4.1 Resolver 'lts-4.1' does not have all the packages to match your requirements. acme-missiles not found - yackage requires -any - yackage flags: upload = True However, you can try '--solver' to use external packages. ``` stack has tested six different snapshots, and in every case discovered that acme-missiles is not available. In the end it suggested that you use the `--solver` command line switch if you want to use packages outside Stackage. So let's give it a try: ``` cueball:~/yackage-0.8.0$ stack init --force --solver Using cabal packages: - yackage.cabal Selecting the best among 6 snapshots... * Partially matches lts-4.1 acme-missiles not found - yackage requires -any - yackage flags: upload = True . . . Selected resolver: lts-4.1 *** Resolver lts-4.1 will need external packages: acme-missiles not found - yackage requires -any - yackage flags: upload = True Using resolver: lts-4.1 Using compiler: ghc-7.10.3 Asking cabal to calculate a build plan... Trying with packages from lts-4.1 as hard constraints... Successfully determined a build plan with 3 external dependencies. Initialising configuration using resolver: lts-4.1 Total number of user packages considered: 1 Warning! 3 external dependencies were added. Overwriting existing configuration file: stack.yaml All done. ``` As you can verify by viewing `stack.yaml`, three external dependencies were added by stack init: ``` # Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3) extra-deps: - acme-missiles-0.3 - text-1.2.2.0 - yaml-0.8.15.2 ``` Of course, you could have added the external dependencies by manually editing `stack.yaml` but stack init does the hard work for you. #### Excluded Packages Sometimes multiple packages in your project may have conflicting requirements. In that case `stack init` will fail, so what do you do? You could manually create `stack.yaml` by omitting some packages to resolve the conflict. Alternatively you can ask `stack init` to do that for you by specifying `--omit-packages` flag on the command line. Let's see how that works. To simulate a conflict we will use acme-missiles-0.3 in yackage and we will also copy `yackage.cabal` to another directory and change the name of the file and package to yackage-test. In this new package we will use acme-missiles-0.2 instead. Let's see what happens when we run solver: ``` cueball:~/yackage-0.8.0$ stack init --force --solver --omit-packages Using cabal packages: - yackage.cabal - example/yackage-test.cabal Selecting the best among 6 snapshots... * Partially matches lts-4.2 acme-missiles not found - yackage requires ==0.3 - yackage-test requires ==0.2 - yackage flags: upload = True - yackage-test flags: upload = True . . . *** Failed to arrive at a workable build plan. *** Ignoring package: yackage-test *** Resolver lts-4.2 will need external packages: acme-missiles not found - yackage requires ==0.3 - yackage flags: upload = True Using resolver: lts-4.2 Using compiler: ghc-7.10.3 Asking cabal to calculate a build plan... Trying with packages from lts-4.2 as hard constraints... Successfully determined a build plan with 3 external dependencies. Initialising configuration using resolver: lts-4.2 Total number of user packages considered: 2 Warning! Ignoring 1 packages due to dependency conflicts: - "example/yackage-test.cabal" Warning! 3 external dependencies were added. Overwriting existing configuration file: stack.yaml All done. ``` Looking at `stack.yaml`, you will see that the excluded packages have been commented out: ``` # Local packages, usually specified by relative directory name packages: - '.' # The following packages have been ignored due to incompatibility with the resolver compiler or dependency conflicts with other packages #- example/ ``` In case wrong packages are excluded you can uncomment the right one and comment the other one. Packages may get excluded due to conflicting requirements among user packages or due to conflicting requirements between a user package and the resolver compiler. If all of the packages have a conflict with the compiler then all of them may get commented out. When packages are commented out you will see a warning every time you run a command which needs the config file. The warning can be disabled by editing the config file and removing it. #### Using a specific resolver Sometimes you may want to use a specific resolver for your project instead of `stack init` picking one for you. You can do that by using `stack init --resolver `. You can also init with a compiler resolver if you do not want to use a snapshot. That will result in all of your project's dependencies being put under the `extra-deps` section. #### Installing the compiler You can install the required compiler if not already installed by using the `--install-ghc` flag with the `stack init` command. #### Miscellaneous and diagnostics _Add selected packages_: If you want to use only selected packages from your project directory you can do so by explicitly specifying the package directories on the command line. _Duplicate package names_: If multiple packages under the directory tree have same name, stack init will report those and automatically ignore one of them. _Ignore subdirectories_: By default stack init searches all the subdirectories for `.cabal` files. If you do not want that then you can use `--ignore-subdirs` command line switch. _Cabal warnings_: stack init will show warnings if there were issues in reading a cabal package file. You may want to pay attention to the warnings as sometimes they may result in incomprehensible errors later on during dependency solving. _Package naming_: If the `Name` field defined in a cabal file does not match with the cabal file name then `stack init` will refuse to continue. _Cabal install errors_: stack init uses `cabal-install` to determine external dependencies. When cabal-install encounters errors, cabal errors are displayed as is by stack init for diagnostics. _User warnings_: When packages are excluded or external dependencies added stack will show warnings every time configuration file is loaded. You can suppress the warnings by editing the config file and removing the warnings from it. You may see something like this: ``` cueball:~/yackage-0.8.0$ stack build Warning: Some packages were found to be incompatible with the resolver and have been left commented out in the packages section. Warning: Specified resolver could not satisfy all dependencies. Some external packages have been added as dependencies. You can suppress this message by removing it from stack.yaml ``` ### stack solver While `stack init` is used to create stack configuration file from existing cabal files, `stack solver` can be used to fine tune or fix an existing stack configuration file. `stack solver` uses the existing file as a constraint. For example it will use only those packages specified in the existing config file or use existing external dependencies as constraints to figure out other dependencies. Let's try `stack solver` to verify the config that we generated earlier with `stack init`: ``` cueball:~/yackage-0.8.0$ stack solver Using configuration file: stack.yaml The following packages are missing from the config: - example/yackage-test.cabal Using cabal packages: - yackage.cabal Using resolver: lts-4.2 Using compiler: ghc-7.10.3 Asking cabal to calculate a build plan... Trying with packages from lts-4.2 and 3 external packages as hard constraints... Successfully determined a build plan with 3 external dependencies. No changes needed to stack.yaml ``` It says there are no changes needed to your config. Notice that it also reports `example/yackage-test.cabal` as missing from the config. It was purposely omitted by `stack init` to resolve a conflict. Sometimes `stack init` may not be able to give you a perfect configuration. In that case, you can tweak the configuration file as per your requirements and then run `stack solver`, it will check the file and suggest or apply any fixes needed. For example, if `stack init` ignored certain packages due to name conflicts or dependency conflicts, the choice that `stack init` made may not be the correct one. In that case you can revert the choice and use solver to fix things. Let's try commenting out `.` and uncommenting `examples/` in our previously generated `stack.yaml` and then run `stack solver`: ``` cueball:~/yackage-0.8.0$ stack solver Using configuration file: stack.yaml The following packages are missing from the config: - yackage.cabal Using cabal packages: - example/yackage-test.cabal . . . Retrying with packages from lts-4.2 and 3 external packages as preferences... Successfully determined a build plan with 5 external dependencies. The following changes will be made to stack.yaml: * Resolver is lts-4.2 * Dependencies to be added extra-deps: - acme-missiles-0.2 - email-validate-2.2.0 - tar-0.5.0.1 * Dependencies to be deleted extra-deps: - acme-missiles-0.3 To automatically update stack.yaml, rerun with '--update-config' ``` Due to the change that we made, solver suggested some new dependencies. By default it does not make changes to the config. As it suggested you can use `--update-config` to make changes to the config. NOTE: You should probably back up your `stack.yaml` before doing this, such as committing to Git/Mercurial/Darcs. Sometimes, you may want to use specific versions of certain packages for your project. To do that you can fix those versions by specifying them in the extra-deps section and then use `stack solver` to figure out whether it is feasible to use those or what other dependencies are needed as a result. If you want to change the resolver for your project, you can run `stack solver --resolver ` and it will figure out the changes needed for you. Let's see what happens if we change the resolver to lts-2.22: ``` cueball:~/yackage-0.8.0$ stack solver --resolver lts-2.22 Using configuration file: stack.yaml The following packages are missing from the config: - yackage.cabal Using cabal packages: - example/yackage-test.cabal Using resolver: lts-2.22 Using compiler: ghc-7.8.4 . . . Retrying with packages from lts-2.22 and 3 external packages as preferences... Successfully determined a build plan with 19 external dependencies. The following changes will be made to stack.yaml: * Resolver is lts-2.22 * Flags to be added flags: - old-locale: true * Dependencies to be added extra-deps: - acme-missiles-0.2 - aeson-0.10.0.0 - aeson-compat-0.3.0.0 - attoparsec-0.13.0.1 - conduit-extra-1.1.9.2 - email-validate-2.2.0 - hex-0.1.2 - http-api-data-0.2.2 - http2-1.1.0 - persistent-2.2.4 - persistent-template-2.1.5 - primitive-0.6.1.0 - tar-0.5.0.1 - unix-time-0.3.6 - vector-0.11.0.0 - wai-extra-3.0.14 - warp-3.1.3.1 * Dependencies to be deleted extra-deps: - acme-missiles-0.3 To automatically update stack.yaml, rerun with '--update-config' ``` As you can see, it automatically suggested changes in `extra-deps` due to the change of resolver. ## Different databases Time to take a short break from hands-on examples and discuss a little architecture. stack has the concept of multiple *databases*. A database consists of a GHC package database (which contains the compiled version of a library), executables, and a few other things as well. To give you an idea: ``` michael@d30748af6d3d:~/helloworld$ ls .stack-work/install/x86_64-linux/lts-3.2/7.10.2/ bin doc flag-cache lib pkgdb ``` Databases in stack are *layered*. For example, the database listing we just gave is called a *local* database. That is layered on top of a *snapshot* database, which contains the libraries and executables specified in the snapshot itself. Finally, GHC itself ships with a number of libraries and executables, which forms the *global* database. To get a quick idea of this, we can look at the output of the `stack exec -- ghc-pkg list` command in our helloworld project: ``` /home/michael/.stack/programs/x86_64-linux/ghc-7.10.2/lib/ghc-7.10.2/package.conf.d Cabal-1.22.4.0 array-0.5.1.0 base-4.8.1.0 bin-package-db-0.0.0.0 binary-0.7.5.0 bytestring-0.10.6.0 containers-0.5.6.2 deepseq-1.4.1.1 directory-1.2.2.0 filepath-1.4.0.0 ghc-7.10.2 ghc-prim-0.4.0.0 haskeline-0.7.2.1 hoopl-3.10.0.2 hpc-0.6.0.2 integer-gmp-1.0.0.0 pretty-1.1.2.0 process-1.2.3.0 rts-1.0 template-haskell-2.10.0.0 terminfo-0.4.0.1 time-1.5.0.1 transformers-0.4.2.0 unix-2.7.1.0 xhtml-3000.2.1 /home/michael/.stack/snapshots/x86_64-linux/nightly-2015-08-26/7.10.2/pkgdb stm-2.4.4 /home/michael/helloworld/.stack-work/install/x86_64-linux/nightly-2015-08-26/7.10.2/pkgdb acme-missiles-0.3 helloworld-0.1.0.0 ``` Notice that acme-missiles ends up in the *local* database. Anything which is not installed from a snapshot ends up in the local database. This includes: your own code, extra-deps, and in some cases even snapshot packages, if you modify them in some way. The reason we have this structure is that: * it lets multiple projects reuse the same binary builds of many snapshot packages, * but doesn't allow different projects to "contaminate" each other by putting non-standard content into the shared snapshot database Typically, the process by which a snapshot package is marked as modified is referred to as "promoting to an extra-dep," meaning we treat it just like a package in the extra-deps section. This happens for a variety of reasons, including: * changing the version of the snapshot package * changing build flags * one of the packages that the package depends on has been promoted to an extra-dep As you probably guessed, there are multiple snapshot databases available, e.g.: ``` michael@d30748af6d3d:~/helloworld$ ls ~/.stack/snapshots/x86_64-linux/ lts-2.22 lts-3.1 lts-3.2 nightly-2015-08-26 ``` These databases don't get layered on top of each other; they are each used separately. In reality, you'll rarely — if ever — interact directly with these databases, but it's good to have a basic understanding of how they work so you can understand why rebuilding may occur at different points. ## The build synonyms Let's look at a subset of the `stack --help` output: ``` build Build the package(s) in this directory/configuration install Shortcut for 'build --copy-bins' test Shortcut for 'build --test' bench Shortcut for 'build --bench' haddock Shortcut for 'build --haddock' ``` Note that four of these commands are just synonyms for the `build` command. They are provided for convenience for common cases (e.g., `stack test` instead of `stack build --test`) and so that commonly expected commands just work. What's so special about these commands being synonyms? It allows us to make much more composable command lines. For example, we can have a command that builds executables, generates Haddock documentation (Haskell API-level docs), and builds and runs your test suites, with: ``` stack build --haddock --test ``` You can even get more inventive as you learn about other flags. For example, take the following: ``` stack build --pedantic --haddock --test --exec "echo Yay, it succeeded" --file-watch ``` This will: * turn on all warnings and errors * build your library and executables * generate Haddocks * build and run your test suite * run the command `echo Yay, it succeeded` when that completes * after building, watch for changes in the files used to build the project, and kick off a new build when done ### install and copy-bins It's worth calling out the behavior of the install command and `--copy-bins` option, since this has confused a number of users (especially when compared to behavior of other tools like cabal-install). The `install` command does precisely one thing in addition to the build command: it copies any generated executables to the local bin path. You may recognize the default value for that path: ``` michael@d30748af6d3d:~/helloworld$ stack path --local-bin-path /home/michael/.local/bin ``` That's why the download page recommends adding that directory to your `PATH` environment variable. This feature is convenient, because now you can simply run `executable-name` in your shell instead of having to run `stack exec executable-name` from inside your project directory. Since it's such a point of confusion, let me list a number of things stack does *not* do specially for the install command: * stack will always build any necessary dependencies for your code. The install command is not necessary to trigger this behavior. If you just want to build a project, run `stack build`. * stack will *not* track which files it's copied to your local bin path nor provide a way to automatically delete them. There are many great tools out there for managing installation of binaries, and stack does not attempt to replace those. * stack will not necessarily be creating a relocatable executable. If your executables hard-codes paths, copying the executable will not change those hard-coded paths. * At the time of writing, there's no way to change those kinds of paths with stack, but see [issue #848 about --prefix](https://github.com/commercialhaskell/stack/issues/848) for future plans. That's really all there is to the install command: for the simplicity of what it does, it occupies a much larger mental space than is warranted. ## Targets, locals, and extra-deps We haven't discussed this too much yet, but, in addition to having a number of synonyms *and* taking a number of options on the command line, the build command *also* takes many arguments. These are parsed in different ways, and can be used to achieve a high level of flexibility in telling stack exactly what you want to build. We're not going to cover the full generality of these arguments here; instead, there's [documentation covering the full build command syntax](build_command.md). Here, we'll just point out a few different types of arguments: * You can specify a *package name*, e.g. `stack build vector`. * This will attempt to build the vector package, whether it's a local package, in your extra-deps, in your snapshot, or just available upstream. If it's just available upstream but not included in your locals, extra-deps, or snapshot, the newest version is automatically promoted to an extra-dep. * You can also give a *package identifier*, which is a package name plus version, e.g. `stack build yesod-bin-1.4.14`. * This is almost identical to specifying a package name, except it will (1) choose the given version instead of latest, and (2) error out if the given version conflicts with the version of a local package. * The most flexibility comes from specifying individual *components*, e.g. `stack build helloworld:test:helloworld-test` says "build the test suite component named helloworld-test from the helloworld package." * In addition to this long form, you can also shorten it by skipping what type of component it is, e.g. `stack build helloworld:helloworld-test`, or even skip the package name entirely, e.g. `stack build :helloworld-test`. * Finally, you can specify individual *directories* to build to trigger building of any local packages included in those directories or subdirectories. When you give no specific arguments on the command line (e.g., `stack build`), it's the same as specifying the names of all of your local packages. If you just want to build the package for the directory you're currently in, you can use `stack build .`. ### Components, --test, and --bench Here's one final important yet subtle point. Consider our helloworld package: it has a library component, an executable helloworld-exe, and a test suite helloworld-test. When you run `stack build helloworld`, how does it know which ones to build? By default, it will build the library (if any) and all of the executables but ignore the test suites and benchmarks. This is where the `--test` and `--bench` flags come into play. If you use them, those components will also be included. So `stack build --test helloworld` will end up including the helloworld-test component as well. You can bypass this implicit adding of components by being much more explicit, and stating the components directly. For example, the following will not build the helloworld-exe executable: ``` michael@d30748af6d3d:~/helloworld$ stack clean michael@d30748af6d3d:~/helloworld$ stack build :helloworld-test helloworld-0.1.0.0: configure (test) Configuring helloworld-0.1.0.0... helloworld-0.1.0.0: build (test) Preprocessing library helloworld-0.1.0.0... [1 of 1] Compiling Lib ( src/Lib.hs, .stack-work/dist/x86_64-linux/Cabal-1.22.4.0/build/Lib.o ) In-place registering helloworld-0.1.0.0... Preprocessing test suite 'helloworld-test' for helloworld-0.1.0.0... [1 of 1] Compiling Main ( test/Spec.hs, .stack-work/dist/x86_64-linux/Cabal-1.22.4.0/build/helloworld-test/helloworld-test-tmp/Main.o ) Linking .stack-work/dist/x86_64-linux/Cabal-1.22.4.0/build/helloworld-test/helloworld-test ... helloworld-0.1.0.0: test (suite: helloworld-test) Test suite not yet implemented ``` We first cleaned our project to clear old results so we know exactly what stack is trying to do. Notice that it builds the helloworld-test test suite, and the helloworld library (since it's used by the test suite), but it does not build the helloworld-exe executable. And now the final point: the last line shows that our command also *runs* the test suite it just built. This may surprise some people who would expect tests to only be run when using `stack test`, but this design decision is what allows the `stack build` command to be as composable as it is (as described previously). The same rule applies to benchmarks. To spell it out completely: * The --test and --bench flags simply state which components of a package should be built, if no explicit set of components is given * The default behavior for any test suite or benchmark component which has been built is to also run it You can use the `--no-run-tests` and `--no-run-benchmarks` (from stack-0.1.4.0 and on) flags to disable running of these components. You can also use `--no-rerun-tests` to prevent running a test suite which has already passed and has not changed. NOTE: stack doesn't build or run test suites and benchmarks for non-local packages. This is done so that running a command like `stack test` doesn't need to run 200 test suites! ## Multi-package projects Until now, everything we've done with stack has used a single-package project. However, stack's power truly shines when you're working on multi-package projects. All the functionality you'd expect to work just does: dependencies between packages are detected and respected, dependencies of all packages are just as one cohesive whole, and if anything fails to build, the build commands exits appropriately. Let's demonstrate this with the wai-app-static and yackage packages: ``` michael@d30748af6d3d:~$ mkdir multi michael@d30748af6d3d:~$ cd multi/ michael@d30748af6d3d:~/multi$ stack unpack wai-app-static-3.1.1 yackage-0.8.0 wai-app-static-3.1.1: download Unpacked wai-app-static-3.1.1 to /home/michael/multi/wai-app-static-3.1.1/ Unpacked yackage-0.8.0 to /home/michael/multi/yackage-0.8.0/ michael@d30748af6d3d:~/multi$ stack init Writing default config file to: /home/michael/multi/stack.yaml Basing on cabal files: - /home/michael/multi/yackage-0.8.0/yackage.cabal - /home/michael/multi/wai-app-static-3.1.1/wai-app-static.cabal Checking against build plan lts-3.2 Selected resolver: lts-3.2 Wrote project config to: /home/michael/multi/stack.yaml michael@d30748af6d3d:~/multi$ stack build --haddock --test # Goes off to build a whole bunch of packages ``` If you look at the `stack.yaml`, you'll see exactly what you'd expect: ```yaml flags: yackage: upload: true wai-app-static: print: false packages: - yackage-0.8.0/ - wai-app-static-3.1.1/ extra-deps: [] resolver: lts-3.2 ``` Notice that multiple directories are listed in the `packages` key. In addition to local directories, you can also refer to packages available in a Git repository or in a tarball over HTTP/HTTPS. This can be useful for using a modified version of a dependency that hasn't yet been released upstream. Please note that when adding upstream packages directly to your project it is important to distinguish _local packages_ from the upstream _dependency packages_. Otherwise you may have trouble running `stack ghci`. See [stack.yaml documentation](yaml_configuration.md#packages) for more details. ## Flags and GHC options There are two common ways to alter how a package will install: with Cabal flags and with GHC options. ### Cabal flag management In the `stack.yaml` file above, you can see that `stack init` has detected that — for the yackage package — the upload flag can be set to true, and for wai-app-static, the print flag to false (it's chosen those values because they're the default flag values, and their dependencies are compatible with the snapshot we're using.) To change a flag setting, we can use the command line `--flag` option: stack build --flag yackage:-upload This means: when compiling the yackage package, turn off the upload flag (thus the `-`). Unlike other tools, stack is explicit about which package's flag you want to change. It does this for two reasons: 1. There's no global meaning for Cabal flags, and therefore two packages can use the same flag name for completely different things. 2. By following this approach, we can avoid unnecessarily recompiling snapshot packages that happen to use a flag that we're using. You can also change flag values on the command line for extra-dep and snapshot packages. If you do this, that package will automatically be promoted to an extra-dep, since the build plan is different than what the plan snapshot definition would entail. ### GHC options GHC options follow a similar logic as in managing Cabal flags, with a few nuances to adjust for common use cases. Let's consider: stack build --ghc-options="-Wall -Werror" This will set the `-Wall -Werror` options for all *local targets*. Note that this will not affect extra-dep and snapshot packages at all. This design provides us with reproducible and fast builds. (By the way: the above GHC options have a special convenience flag: `--pedantic`.) There's one extra nuance about command line GHC options: Since they only apply to local targets, if you change your local targets, they will no longer apply to other packages. Let's play around with an example from the wai repository, which includes the wai and warp packages, the latter depending on the former. If we run: stack build --ghc-options=-O0 wai It will build all of the dependencies of wai, and then build wai with all optimizations disabled. Now let's add in warp as well: stack build --ghc-options=-O0 wai warp This builds the additional dependencies for warp, and then builds warp with optimizations disabled. Importantly: it does not rebuild wai, since wai's configuration has not been altered. Now the surprising case: ``` michael@d30748af6d3d:~/wai$ stack build --ghc-options=-O0 warp wai-3.0.3.0-5a49351d03cba6cbaf906972d788e65d: unregistering (flags changed from ["--ghc-options","-O0"] to []) warp-3.1.3-a91c7c3108f63376877cb3cd5dbe8a7a: unregistering (missing dependencies: wai) wai-3.0.3.0: configure ``` You may expect this to be a no-op: neither wai nor warp has changed. However, stack will instead recompile wai with optimizations enabled again, and then rebuild warp (with optimizations disabled) against this newly built wai. The reason: reproducible builds. If we'd never built wai or warp before, trying to build warp would necessitate building all of its dependencies, and it would do so with default GHC options (optimizations enabled). This dependency would include wai. So when we run: stack build --ghc-options=-O0 warp We want its behavior to be unaffected by any previous build steps we took. While this specific corner case does catch people by surprise, the overall goal of reproducible builds is- in the stack maintainers' views- worth the confusion. Final point: if you have GHC options that you'll be regularly passing to your packages, you can add them to your `stack.yaml` file (starting with stack-0.1.4.0). See [the documentation section on ghc-options](yaml_configuration.md#ghc-options) for more information. ## path NOTE: That's it, the heavy content of this guide is done! Everything from here on out is simple explanations of commands. Congratulations! Generally, you don't need to worry about where stack stores various files. But some people like to know this stuff. That's when the `stack path` command is useful. ``` michael@d30748af6d3d:~/wai$ stack path global-stack-root: /home/michael/.stack stack-root: /home/michael/.stack project-root: /home/michael/wai config-location: /home/michael/wai/stack.yaml bin-path: /home/michael/.stack/snapshots/x86_64-linux/lts-2.17/7.8.4/bin:/home/michael/.stack/programs/x86_64-linux/ghc-7.8.4/bin:/home/michael/.stack/programs/x86_64-linux/ghc-7.10.2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin programs: /home/michael/.stack/programs/x86_64-linux compiler: /home/michael/.stack/programs/x86_64-linux/ghc-7.8.4/bin/ghc compiler-bin: /home/michael/.stack/programs/x86_64-linux/ghc-7.8.4/bin local-bin-path: /home/michael/.local/bin extra-include-dirs: extra-library-dirs: snapshot-pkg-db: /home/michael/.stack/snapshots/x86_64-linux/lts-2.17/7.8.4/pkgdb local-pkg-db: /home/michael/wai/.stack-work/install/x86_64-linux/lts-2.17/7.8.4/pkgdb global-pkg-db: /home/michael/.stack/programs/x86_64-linux/ghc-7.8.4/lib/ghc-7.8.4/package.conf.d ghc-package-path: /home/michael/wai/.stack-work/install/x86_64-linux/lts-2.17/7.8.4/pkgdb:/home/michael/.stack/snapshots/x86_64-linux/lts-2.17/7.8.4/pkgdb:/home/michael/.stack/programs/x86_64-linux/ghc-7.8.4/lib/ghc-7.8.4/package.conf.d snapshot-install-root: /home/michael/.stack/snapshots/x86_64-linux/lts-2.17/7.8.4 local-install-root: /home/michael/wai/.stack-work/install/x86_64-linux/lts-2.17/7.8.4 snapshot-doc-root: /home/michael/.stack/snapshots/x86_64-linux/lts-2.17/7.8.4/doc local-doc-root: /home/michael/wai/.stack-work/install/x86_64-linux/lts-2.17/7.8.4/doc dist-dir: .stack-work/dist/x86_64-linux/Cabal-1.18.1.5 local-hpc-root: /home/michael/wai/.stack-work/install/x86_64-linux/lts-2.17/7.8.4/hpc ``` In addition, `stack path` accepts command line arguments to state which of these keys you're interested in, which can be convenient for scripting. As a simple example, let's find out the sandboxed versions of GHC that stack installed: ``` michael@d30748af6d3d:~/wai$ ls $(stack path --programs)/*.installed /home/michael/.stack/programs/x86_64-linux/ghc-7.10.2.installed /home/michael/.stack/programs/x86_64-linux/ghc-7.8.4.installed ``` (Yes, that command requires a \*nix shell, and likely won't run on Windows.) While we're talking about paths, to wipe our stack install completely, here's what needs to be removed: 1. The stack executable itself 2. The stack root, e.g. `$HOME/.stack` on non-Windows systems. * See `stack path --stack-root` * On Windows, you will also need to delete `stack path --programs` 3. Any local `.stack-work` directories inside a project ## exec We've already used `stack exec` used multiple times in this guide. As you've likely already guessed, it allows you to run executables, but with a slightly modified environment. In particular: `stack exec` looks for executables on stack's bin paths, and sets a few additional environment variables (like `GHC_PACKAGE_PATH`, which tells GHC which package databases to use). If you want to see exactly what the modified environment looks like, try: stack exec env The only issue is how to distinguish flags to be passed to stack versus those for the underlying program. Thanks to the optparse-applicative library, stack follows the Unix convention of `--` to separate these, e.g.: ``` michael@d30748af6d3d:~$ stack exec --package stm -- echo I installed the stm package via --package stm Run from outside a project, using implicit global project config Using latest snapshot resolver: lts-3.2 Writing global (non-project-specific) config file to: /home/michael/.stack/global/stack.yaml Note: You can change the snapshot via the resolver field there. I installed the stm package via --package stm ``` Flags worth mentioning: * `--package foo` can be used to force a package to be installed before running the given command. * `--no-ghc-package-path` can be used to stop the `GHC_PACKAGE_PATH` environment variable from being set. Some tools — notably cabal-install — do not behave well with that variable set. ## ghci (the repl) GHCi is the interactive GHC environment, a.k.a. the REPL. You *could* access it with: stack exec ghci But that won't load up locally written modules for access. For that, use the `stack ghci` command. To then load modules from your project, use the `:m` command (for "module") followed by the module name. IMPORTANT NOTE: If you have added upstream packages to your project please make sure to mark them as *dependency package*s for faster and reliable usage of `stack ghci`. Otherwise GHCi may have trouble due to conflicts of compilation flags or having to unnecessarily interpret too many modules. See [stack.yaml documentation](yaml_configuration.md#packages) to learn how to mark a package as a *dependency package*. ## ghc/runghc You'll sometimes want to just compile (or run) a single Haskell source file, instead of creating an entire Cabal package for it. You can use `stack exec ghc` or `stack exec runghc` for that. As simple helpers, we also provide the `stack ghc` and `stack runghc` commands, for these common cases. ## script interpreter stack also offers a very useful feature for running files: a script interpreter. For too long have Haskellers felt shackled to bash or Python because it's just too hard to create reusable source-only Haskell scripts. stack attempts to solve that. You can use `stack ` to execute a Haskell source file or specify `stack` as the interpreter using a shebang line on a Unix like operating systems. Additional stack options can be specified using a special Haskell comment in the source file to specify dependencies and automatically install them before running the file. An example will be easiest to understand: ``` michael@d30748af6d3d:~$ cat turtle-example.hs #!/usr/bin/env stack -- stack --resolver lts-6.25 script --package turtle {-# LANGUAGE OverloadedStrings #-} import Turtle main = echo "Hello World!" michael@d30748af6d3d:~$ chmod +x turtle-example.hs michael@d30748af6d3d:~$ ./turtle-example.hs Completed 5 action(s). Hello World! michael@d30748af6d3d:~$ ./turtle-example.hs Hello World! ``` The first run can take a while (as it has to download GHC if necessary and build dependencies), but subsequent runs are able to reuse everything already built, and are therefore quite fast. The first line in the source file is the usual "shebang" to use stack as a script interpreter. The second line, is a Haskell comment providing additional options to stack (due to the common limitation of the "shebang" line only being allowed a single argument). In this case, the options tell stack to use the lts-3.2 resolver, automatically install GHC if it is not already installed, and ensure the turtle package is available. If you're on Windows: you can run `stack turtle.hs` instead of `./turtle.hs`. The shebang line is not required in that case. ### Using multiple packages You can also specify multiple packages, either with multiple `--package` arguments, or by providing a comma or space separated list. For example: ``` #!/usr/bin/env stack {- stack script --resolver lts-6.25 --package turtle --package "stm async" --package http-client,http-conduit -} ``` ### Stack configuration for scripts With the `script` command, all Stack configuration files are ignored to provide a completely reliable script running experience. However, see the example below with `runghc` for an approach to scripts which will respect your configuration files. When using `runghc`, if the current working directory is inside a project then that project's stack configuration is effective when running the script. Otherwise the script uses the global project configuration specified in `~/.stack/global-project/stack.yaml`. ### Specifying interpreter options The stack interpreter options comment must specify a single valid stack command line, starting with `stack` as the command followed by the stack options to use for executing this file. The comment must always be on the line immediately following the shebang line when the shebang line is present otherwise it must be the first line in the file. The comment must always start in the first column of the line. When many options are needed a block style comment may be more convenient to split the command on multiple lines for better readability. You can also specify ghc options the same way as you would on command line i.e. by separating the stack options and ghc options with a `--`. Here is an example of a multi line block comment with ghc options: ``` #!/usr/bin/env stack {- stack script --resolver lts-6.25 --package turtle -- +RTS -s -RTS -} ``` ### Writing independent and reliable scripts With the release of Stack 1.4.0, there is a new command, `script`, which will automatically: * Install GHC and libraries if missing * Require that all packages used be explicitly stated on the command line This ensures that your scripts are _independent_ of any prior deployment specific configuration, and are _reliable_ by using exactly the same version of all packages every time it runs so that the script does not break by accidentally using incompatible package versions. In previous versions of Stack, the `runghc` command was used for scripts instead. In order to achieve the same effect with the `runghc` command, you can do the following: 1. Use the `--install-ghc` option to install the compiler automatically 2. Explicitly specify all packages required by the script using the `--package` option. Use `-hide-all-packages` ghc option to force explicit specification of all packages. 3. Use the `--resolver` Stack option to ensure a specific GHC version and package set is used. Even with this configuration, it is still possible for configuration files to impact `stack runghc`, which is why `stack script` is strongly recommended in general. For those curious, here is an example with `runghc`: ``` #!/usr/bin/env stack {- stack --resolver lts-6.25 --install-ghc runghc --package base --package turtle -- -hide-all-packages -} ``` The `runghc` command is still very useful, especially when you're working on a project and want to access the package databases and configurations used by that project. See the next section for more information on configuration files. ### Loading scripts in ghci Sometimes you want to load your script in ghci REPL to play around with your program. In those cases, you can use `exec ghci` option in the script to achieve it. Here is an example: ``` #!/usr/bin/env stack {- stack --resolver lts-8.2 --install-ghc exec ghci --package text -} ``` ## Finding project configs, and the implicit global Whenever you run something with stack, it needs a `stack.yaml` project file. The algorithm stack uses to find this is: 1. Check for a `--stack-yaml` option on the command line 2. Check for a `STACK_YAML` environment variable 3. Check the current directory and all ancestor directories for a `stack.yaml` The first two provide a convenient method for using an alternate configuration. For example: `stack build --stack-yaml stack-7.8.yaml` can be used by your CI system to check your code against GHC 7.8. Setting the `STACK_YAML` environment variable can be convenient if you're going to be running commands like `stack ghc` in other directories, but you want to use the configuration you defined in a specific project. If stack does not find a `stack.yaml` in any of the three specified locations, the *implicit global* logic kicks in. You've probably noticed that phrase a few times in the output from commands above. Implicit global is essentially a hack to allow stack to be useful in a non-project setting. When no implicit global config file exists, stack creates one for you with the latest LTS snapshot as the resolver. This allows you to do things like: * compile individual files easily with `stack ghc` * build executables without starting a project, e.g. `stack install pandoc` Keep in mind that there's nothing magical about this implicit global configuration. It has no impact on projects at all. Every package you install with it is put into isolated databases just like everywhere else. The only magic is that it's the catch-all project whenever you're running stack somewhere else. ## `stack.yaml` vs `.cabal` files Now that we've covered a lot of stack use cases, this quick summary of `stack.yaml` vs `.cabal` files will hopefully make sense and be a good reminder for future uses of stack: * A project can have multiple packages. * Each project has a `stack.yaml`. * Each package has a `.cabal` file. * The `.cabal` file specifies which packages are dependencies. * The `stack.yaml` file specifies which packages are available to be used. * `.cabal` specifies the components, modules, and build flags provided by a package * `stack.yaml` can override the flag settings for individual packages * `stack.yaml` specifies which packages to include ## Comparison to other tools stack is not the only tool around for building Haskell code. stack came into existence due to limitations with some of the existing tools. If you're unaffected by those limitations and are happily building Haskell code, you may not need stack. If you're suffering from some of the common problems in other tools, give stack a try instead. If you're a new user who has no experience with other tools, we recommend going with stack. The defaults match modern best practices in Haskell development, and there are less corner cases you need to be aware of. You *can* develop Haskell code with other tools, but you probably want to spend your time writing code, not convincing a tool to do what you want. Before jumping into the differences, let me clarify an important similarity: __Same package format.__ stack, cabal-install, and presumably all other tools share the same underlying Cabal package format, consisting of a `.cabal` file, modules, etc. This is a Good Thing: we can share the same set of upstream libraries, and collaboratively work on the same project with stack, cabal-install, and NixOS. In that sense, we're sharing the same ecosystem. Now the differences: * __Curation vs dependency solving as a default__. * stack defaults to using curation (Stackage snapshots, LTS Haskell, Nightly, etc) as a default instead of defaulting to dependency solving, as cabal-install does. This is just a default: as described above, stack can use dependency solving if desired, and cabal-install can use curation. However, most users will stick to the defaults. The stack team firmly believes that the majority of users want to simply ignore dependency resolution nightmares and get a valid build plan from day 1, which is why we've made this selection of default behavior. * __Reproducible__. * stack goes to great lengths to ensure that `stack build` today does the same thing tomorrow. cabal-install does not: build plans can be affected by the presence of preinstalled packages, and running `cabal update` can cause a previously successful build to fail. With stack, changing the build plan is always an explicit decision. * __Automatically building dependencies__. * In cabal-install, you need to use `cabal install` to trigger dependency building. This is somewhat necessary due to the previous point, since building dependencies can, in some cases, break existing installed packages. So for example, in stack, `stack test` does the same job as `cabal install --run-tests`, though the latter *additionally* performs an installation that you may not want. The closer command equivalent is `cabal install --enable-tests --only-dependencies && cabal configure --enable-tests && cabal build && cabal test` (newer versions of cabal-install may make this command shorter). * __Isolated by default__. * This has been a pain point for new stack users. In cabal, the default behavior is a non-isolated build where working on two projects can cause the user package database to become corrupted. The cabal solution to this is sandboxes. stack, however, provides this behavior by default via its databases. In other words: when you use stack, there's __no need for sandboxes__, everything is (essentially) sandboxed by default. __Other tools for comparison (including active and historical)__ * [cabal-dev](https://hackage.haskell.org/package/cabal-dev) (deprecated in favor of cabal-install) * [cabal-meta](https://hackage.haskell.org/package/cabal-meta) inspired a lot of the multi-package functionality of stack. If you're still using cabal-install, cabal-meta is relevant. For stack work, the feature set is fully subsumed by stack. * [cabal-src](https://hackage.haskell.org/package/cabal-src) is mostly irrelevant in the presence of both stack and cabal sandboxes, both of which make it easier to add additional package sources easily. The mega-sdist executable that ships with cabal-src is, however, still relevant. Its functionality may some day be folded into stack * [stackage-cli](https://hackage.haskell.org/package/stackage-cli) was an initial attempt to make cabal-install work more easily with curated snapshots, but due to a slight impedance mismatch between cabal.config constraints and snapshots, it did not work as well as hoped. It is deprecated in favor of stack. ## More resources There are lots of resources available for learning more about stack: * `stack --help` * `stack --version` — identify the version and Git hash of the stack executable * `--verbose` (or `-v`) — much more info about internal operations (useful for bug reports) * The [home page](http://haskellstack.org) * The [stack mailing list](https://groups.google.com/d/forum/haskell-stack) * The [the FAQ](faq.md) * The [stack wiki](https://github.com/commercialhaskell/stack/wiki) * The [haskell-stack tag on Stack Overflow](http://stackoverflow.com/questions/tagged/haskell-stack) * [Another getting started with stack tutorial](http://seanhess.github.io/2015/08/04/practical-haskell-getting-started.html) * [Why is stack not cabal?](https://www.fpcomplete.com/blog/2015/06/why-is-stack-not-cabal) ## Fun features This is just a quick collection of fun and useful feature stack supports. ### Templates We started off using the `new` command to create a project. stack provides multiple templates to start a new project from: ``` michael@d30748af6d3d:~$ stack templates chrisdone hakyll-template new-template simple yesod-minimal yesod-mongo yesod-mysql yesod-postgres yesod-postgres-fay yesod-simple yesod-sqlite michael@d30748af6d3d:~$ stack new my-yesod-project yesod-simple Downloading template "yesod-simple" to create project "my-yesod-project" in my-yesod-project/ ... Using the following authorship configuration: author-email: example@example.com author-name: Example Author Name Copy these to /home/michael/.stack/config.yaml and edit to use different values. Writing default config file to: /home/michael/my-yesod-project/stack.yaml Basing on cabal files: - /home/michael/my-yesod-project/my-yesod-project.cabal Checking against build plan lts-3.2 Selected resolver: lts-3.2 Wrote project config to: /home/michael/my-yesod-project/stack.yaml ``` Alternatively you can use your own templates by specifying the path: ``` stack new project ~/location/of/your/template.hsfiles ``` As a starting point you can use [the "simple" template](https://github.com/commercialhaskell/stack-templates/blob/master/simple.hsfiles). An introduction into template-writing and a place for submitting official templates, you will find at [the stack-templates repository](https://github.com/commercialhaskell/stack-templates#readme). ### IDE stack has a work-in-progress suite of editor integrations, to do things like getting type information in Emacs. For more information, see [stack-ide](https://github.com/commercialhaskell/stack-ide#readme). ### Visualizing dependencies If you'd like to get some insight into the dependency tree of your packages, you can use the `stack dot` command and Graphviz. More information is [available in the Dependency visualization documentation](dependency_visualization.md). ### Travis with caching This content has been moved to a dedicated [Travis CI document](https://docs.haskellstack.org/en/stable/travis_ci/). ### Shell auto-completion Love tab-completion of commands? You're not alone. If you're on bash, just run the following (or add it to `.bashrc`): eval "$(stack --bash-completion-script stack)" For more information and other shells, see [the Shell auto-completion wiki page](https://github.com/commercialhaskell/stack/wiki/Shell-autocompletion) ### Docker stack provides two built-in Docker integrations. Firstly, you can build your code inside a Docker image, which means: * even more reproducibility to your builds, since you and the rest of your team will always have the same system libraries * the Docker images ship with entire precompiled snapshots. That means you have a large initial download, but much faster builds For more information, see [the Docker-integration documentation](docker_integration.md). stack can also generate Docker images for you containing your built executables. This feature is great for automating deployments from CI. This feature is not yet well-documented, but the basics are to add a section like the following to stack.yaml: ```yaml image: # YOU NEED A `container` YAML SECTION FOR `stack image container` container: # YOU NEED A BASE IMAGE NAME. STACK LAYERS EXES ON TOP OF # THE BASE IMAGE. PREPARE YOUR PROJECT IMAGE IN ADVANCE. PUT # ALL YOUR RUNTIME DEPENDENCIES IN THE IMAGE. base: "fpco/ubuntu-with-libgmp:14.04" # YOU CAN OPTIONALY NAME THE IMAGE. STACK WILL USE THE PROJECT # DIRECTORY NAME IF YOU LEAVE OUT THIS OPTION. name: "fpco/hello-world" # OPTIONALLY ADD A HASH OF LOCAL PROJECT DIRECTORIES AND THEIR # DESTINATIONS INSIDE THE DOCKER IMAGE. add: man/: /usr/local/share/man/ # OPTIONALLY SPECIFY A LIST OF EXECUTABLES. STACK WILL CREATE # A TAGGED IMAGE FOR EACH IN THE LIST. THESE IMAGES WILL HAVE # THEIR RESPECTIVE "ENTRYPOINT" SET. entrypoints: - stack ``` and then run `stack image container` and then `docker images` to list the images. Note that the executable will be built in the development environment and copied to the container, so the dev OS must match that of the container OS. This is easily accomplished using [Docker integration](docker_integration.md), under which the exe emitted by `stack build` will be built on the Docker container, not the local OS. The executable will be stored under `/usr/local/bin/-exe` in the running container. If you want the container to run the executable immediately on startup then set an entrypoint as follows: ```yaml entrypoints: - -exe ``` ### Nix stack provides an integration with [Nix](http://nixos.org/nix), providing you with the same two benefits as the first Docker integration discussed above: * more reproducible builds, since fixed versions of any system libraries and commands required to build the project are automatically built using Nix and managed locally per-project. These system packages never conflict with any existing versions of these libraries on your system. That they are managed locally to the project means that you don't need to alter your system in any way to build any odd project pulled from the Internet. * implicit sharing of system packages between projects, so you don't have more copies on-disk than you need to. When using the Nix integration, Stack downloads and builds Haskell dependencies as usual, but resorts on Nix to provide non-Haskell dependencies that exist in the Nixpkgs. Both Docker and Nix are methods to *isolate* builds and thereby make them more reproducible. They just differ in the means of achieving this isolation. Nix provides slightly weaker isolation guarantees than Docker, but is more lightweight and more portable (Linux and OS X mainly, but also Windows). For more on Nix, its command-line interface and its package description language, read the [Nix manual](http://nixos.org/nix/manual). But keep in mind that the point of stack's support is to obviate the need to write any Nix code in the common case or even to learn how to use the Nix tools (they're called under the hood). For more information, see [the Nix-integration documentation](nix_integration.md). ## Power user commands The following commands are a little more powerful, and won't be needed by all users. Here's a quick rundown: * `stack update` will download the most recent set of packages from your package indices (e.g. Hackage). Generally, stack runs this for you automatically when necessary, but it can be useful to do this manually sometimes (e.g., before running `stack solver`, to guarantee you have the most recent upstream packages available). * `stack unpack` is a command we've already used quite a bit for examples, but most users won't use it regularly. It does what you'd expect: downloads a tarball and unpacks it. * `stack sdist` generates an uploading tarball containing your package code * `stack upload` uploads an sdist to Hackage. As of version [1.1.0](https://docs.haskellstack.org/en/v1.1.0/ChangeLog/) stack will also attempt to GPG sign your packages as per [our blog post](https://www.fpcomplete.com/blog/2016/05/stack-security-gnupg-keys). * `--no-signature` disables signing of packages * `stack upgrade` will build a new version of stack from source. * `--git` is a convenient way to get the most recent version from master for those testing and living on the bleeding edge. * `stack setup --upgrade-cabal` can install a newer version of the Cabal library, used for performing actual builds. You shouldn't generally do this, since new Cabal versions may introduce incompatibilities with package sets, but it can be useful if you're trying to test a specific bugfix. * `stack list-dependencies` lists all of the packages and versions used for a project * `stack sig` subcommand can help you with GPG signing & verification * `sign` will sign an sdist tarball and submit the signature to sig.commercialhaskell.org for storage in the sig-archive git repo. (Signatures will be used later to verify package integrity.) ## Debugging To profile a component of the current project, simply pass the `--profile` flag to `stack`. The `--profile` flag turns on the `--enable-library-profiling` and `--enable-executable-profiling` Cabal options _and_ passes the `+RTS -p` runtime options to any testsuites and benchmarks. For example the following command will build the `my-tests` testsuite with profiling options and create a `my-tests.prof` file in the current directory as a result of the test run. stack test --profile my-tests The `my-tests.prof` file now contains time and allocation info for the test run. To create a profiling report for an executable, e.g. `my-exe`, you can run stack exec -- my-exe +RTS -p For more fine-grained control of compilation options there are the `--library-profiling` and `--executable-profiling` flags which will turn on the `--enable-library-profiling` and `--enable-executable-profiling` Cabal options respectively. Custom GHC options can be passed in with `--ghc-options "more options here"`. To enable compilation with profiling options by default you can add the following snippet to your `stack.yaml` or `~/.stack/config.yaml`: ``` build: library-profiling: true executable-profiling: true ``` ### Tracing To generate a backtrace in case of exceptions during a test or benchmarks run, use the `--trace` flag. Like `--profile` this compiles with profiling options, but adds the `+RTS -xc` runtime option. ### DWARF `stack` now supports debugging and profiling with [DWARF information](https://ghc.haskell.org/trac/ghc/wiki/DWARF), using the `--no-strip`, `--no-library-stripping`, and `--no-executable-shipping` flags to disable the default behavior of removing such information from compiled libraries and executables. ### Further reading For more commands and uses, see [the official GHC chapter on profiling](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/profiling.html), [the Haskell wiki](https://wiki.haskell.org/How_to_profile_a_Haskell_program), and [the chapter on profiling in Real World Haskell](http://book.realworldhaskell.org/read/profiling-and-optimization.html). stack-1.5.1/doc/install_and_upgrade.md0000644000000000000000000002772413140560214016107 0ustar0000000000000000# Install/upgrade For common Un*x operating systems (including macOS), all you need to do is run: curl -sSL https://get.haskellstack.org/ | sh or: wget -qO- https://get.haskellstack.org/ | sh Distribution packages are available for [Ubuntu](#ubuntu), [Debian](#debian), [Fedora](#fedora), [Arch Linux](#arch-linux) and [FreeBSD](#freebsd). Binaries for other operating systems are listed below, and available on [the Github releases page](https://github.com/fpco/stack/releases). For the future, we are open to supporting more OSes (to request one, please [submit an issue](https://github.com/commercialhaskell/stack/issues/new)). Binary packages are signed with this [signing key](SIGNING_KEY.md). If you are writing a script that needs to download the latest binary, you can find links that always point to the latest bindists [here](https://www.stackage.org/stack). ## Windows We recommend installing to the default location with these installers, as that will make `stack install` and `stack upgrade` work correctly out of the box. * [Windows 64-bit Installer](https://www.stackage.org/stack/windows-x86_64-installer) * [Windows 32-bit Installer](https://www.stackage.org/stack/windows-i386-installer) If in doubt: you should prefer the 64-bit installer. ### Manual download * Download the latest release: * [Windows 64-bit](https://www.stackage.org/stack/windows-x86_64) * [Windows 32-bit](https://www.stackage.org/stack/windows-i386) * Unpack the archive and place `stack.exe` somewhere on your `%PATH%` (see [Path section below](#path)) and you can then run `stack` on the command line. * Now you can run `stack` from the terminal. ## macOS We generally test on the current version of macOS, but Stack is known to work on Sierra, El Capitan, Yosemite and Mavericks as well, and may also work on older versions (YMMV). ### Installer script Run: curl -sSL https://get.haskellstack.org/ | sh ### Manual download * Download the latest release: * [macOS 64-bit](https://www.stackage.org/stack/osx-x86_64) * Extract the archive and place `stack` somewhere on your `$PATH` (see [Path section below](#path)) * Now you can run `stack` from the terminal. ### Using Homebrew If you have the popular [brew](https://brew.sh/) tool installed, you can just do: brew install haskell-stack * The Homebrew formula and bottles are **unofficial** and lag slightly behind new Stack releases, but tend to be updated within a day or two. * Normally, Homebrew will install from a pre-built binary (aka "pour from a bottle"), but if `brew` starts trying to build everything from source (which will take hours), see [their FAQ on the topic](https://github.com/Homebrew/brew/blob/master/docs/FAQ.md#why-do-you-compile-everything). ### Notes After installation, running `stack setup` might fail with `configure: error: cannot run C compiled programs.` in which case you should run: xcode-select --install If you are on OS X 10.11 ("El Capitan") and encounter either of these problems, see the linked FAQ entries: * [GHC 7.8.4 fails with `/usr/bin/ar: permission denied`](faq.md#usr-bin-ar-permission-denied) * [DYLD_LIBRARY_PATH is ignored](faq.md#dyld-library-path-ignored) If you are on OS X 10.12 ("Sierra") and encounter [GHC panic while building, see this issue](https://github.com/commercialhaskell/stack/issues/2577) ## Ubuntu Use the [generic Linux option](#linux). There is also a [Ubuntu package](http://packages.ubuntu.com/search?keywords=haskell-stack&searchon=names&suite=all§ion=all) for Ubuntu 16.04 and up. Note that the distribution's Stack version lags behind, so we recommend running `stack upgrade` after installing it. ## Debian Use the [generic Linux option](#linux). There is also a [Debian package](https://packages.debian.org/search?keywords=haskell-stack&searchon=names&suite=all§ion=all) for Stretch and up. Note that the distribution's Stack version lags behind, so we recommend running `stack upgrade` after installing it. ## CentOS / Red Hat / Amazon Linux Use the [generic Linux option](#linux). There is also an unofficial [Copr repo](https://copr.fedoraproject.org/coprs/petersen/stack/). Note that this Stack version may lag behind, so we recommend running `stack upgrade` after installing it. ## Fedora Use the [generic Linux option](#linux). There is also an unofficial [Fedora Copr repo](https://copr.fedoraproject.org/coprs/petersen/stack/) which can be enabled with: `sudo dnf copr enable petersen/stack`. Note that this Stack version may lag behind, so we recommend running `stack upgrade` after installing it. ## openSUSE / SUSE Linux Enterprise Use the [generic Linux option](#linux). There is also an unofficial SUSE package. Note that this Stack version may lag behind, so we recommend running `stack upgrade` after installing it. To install it: 1. Add the appropriate OBS repository: * openSUSE Tumbleweed all needed is in distribution * openSUSE Leap sudo zypper ar http://download.opensuse.org/repositories/devel:/languages:/haskell/openSUSE_Leap_42.1/devel:languages:haskell.repo * SUSE Linux Enterprise 12 sudo zypper ar http://download.opensuse.org/repositories/devel:/languages:/haskell/SLE_12/devel:languages:haskell.repo 2. Install: sudo zypper in stack ## Arch Linux There is an official package in the Arch community repository. So you can install it by simply doing: sudo pacman -S stack Note that this version may slightly lag behind, but it should be updated within the day. The package is also always rebuilt and updated when one of it's dependencies gets an update. - [stack](https://www.archlinux.org/packages/community/x86_64/stack/) _latest stable version_ - [haskell-stack-git](https://aur.archlinux.org/packages/haskell-stack-git/) _git version_ In order to use `stack setup` with older versions of GHC or on a 32-bit system, you may need the [ncurses5-compat-libs](https://aur.archlinux.org/packages/ncurses5-compat-libs/) AUR package installed. If this package is not installed, Stack may not be able to install older (< 7.10.3) or 32-bit GHC versions. If you use the [ArchHaskell repository](https://wiki.archlinux.org/index.php/ArchHaskell), you can also get the `haskell-stack-tool` package from there. ## NixOS Users who follow the `nixos-unstable` channel or the Nixpkgs `master` branch can install the latest `stack` release into their profile by running: nix-env -f "" -iA haskellPackages.stack Alternatively, the package can be built from source as follows. 1. Clone the git repo: git clone https://github.com/commercialhaskell/stack.git 2. Create a `shell.nix` file: cabal2nix --shell ./. --no-check --no-haddock > shell.nix Note that the tests fail on NixOS, so disable them with `--no-check`. Also, haddock currently doesn't work for stack, so `--no-haddock` disables it. 3. Install stack to your user profile: nix-env -i -f shell.nix For more information on using Stack together with Nix, please see [the NixOS manual section on Stack](http://nixos.org/nixpkgs/manual/#how-to-build-a-haskell-project-using-stack). ## Linux (generic) ### Installer script Run: curl -sSL https://get.haskellstack.org/ | sh or: wget -qO- https://get.haskellstack.org/ | sh ### Manual download * Download the latest release: * [Linux 64-bit, static](https://www.stackage.org/stack/linux-x86_64-static) * [Linux 32-bit, standard](https://www.stackage.org/stack/linux-i386) * [Linux 32-bit, libgmp4](https://www.stackage.org/stack/linux-i386-gmp4) (if you are on an older 32-bit distribution that only includes libgmp4 (libgmp.so.3), such as CentOS/RHEL/Amazon Linux 6.) * Extract the archive and place `stack` somewhere on your `$PATH` (see [Path section below](#path)) * Ensure you have required system dependencies installed. These include GCC, GNU make, xz, perl, libgmp, libffi, and zlib. We also recommend Git and GPG. To install these using your package manager: * Debian / Ubuntu: `sudo apt-get install g++ gcc libc6-dev libffi-dev libgmp-dev make xz-utils zlib1g-dev git gnupg` * Fedora / CentOS: `sudo dnf install perl make automake gcc gmp-devel libffi zlib xz tar git gnupg` (use `yum` instead of `dnf` on CentOS and Fedora <= 21) * Fedora 24: In order to use `stack setup` on a 32-bit system, you may need to run `sudo dnf install ncurses-compat-libs`. If this package is not installed, Stack may not be able to install 32-bit GHC versions. Also `sudo dnf install ncurses-compat-libs` if you nee * Arch Linux: `sudo pacman -S make gcc ncurses git gnupg xz zlib gmp libffi zlib` * In order to use `stack setup` with older versions of GHC or on a 32-bit system, you may need the [ncurses5-compat-libs](https://aur.archlinux.org/packages/ncurses5-compat-libs/) AUR package installed. If this package is not installed, Stack may not be able to install older (< 7.10.3) or 32-bit GHC versions. * Gentoo users, make sure to have the `ncurses` package with `USE=tinfo` (without it, stack will not be able to install GHC). * Now you can run `stack` from the terminal. ## FreeBSD (only 64-bit currently available, tested on FreeBSD 10.3-RELEASE) ### Installer script Run: curl -sSL https://get.haskellstack.org/ | sh ### Manual download * Install required dependencies: pkg install devel/gmake perl5 lang/gcc misc/compat8x misc/compat9x converters/libiconv ca_root_nss * Download the latest release: * [FreeBSD 64-bit](https://www.stackage.org/stack/freebsd-x86_64) * Extract the archive and place `stack` somewhere on your `$PATH` (see [Path section below](#path)) * Now you can run `stack` from the terminal. ## Path You can install stack by copying it anywhere on your PATH environment variable. We recommend installing in the same directory where stack itself will install executables (that way stack is able to upgrade itself!). On Windows, that directory is `%APPDATA%\local\bin`, e.g. "c:\Users\Michael\AppData\Roaming\local\bin". For other systems, use `$HOME/.local/bin`. If you don't have that directory in your PATH, you may need to update your PATH (such as by editing .bashrc). If you're curious about the choice of these paths, see [issue #153](https://github.com/commercialhaskell/stack/issues/153) ## Shell auto-completion To get tab-completion of commands on bash, just run the following (or add it to `.bashrc`): eval "$(stack --bash-completion-script stack)" For more information and other shells, see [the shell auto-completion page](shell_autocompletion.md) ## Upgrade There are essentially four different approaches to upgrade: * The `stack` tool itself ships with an `upgrade` command, which download a `stack` binary or build it from source and install it to the default install path (e.g. `~/.local/bin` or `%APPDATA%\local\bin`; see the [Path](#Path) section above). You can use `stack upgrade` to get the latest official release, and `stack upgrade --git` to install from Git and live on the bleeding edge. Make sure the default install directory is on your `PATH` and takes precedence over the system installed `stack`, or copy `stack` from that directory to the system location afterward. For more information, see [this discussion](https://github.com/commercialhaskell/stack/issues/237#issuecomment-126793301). * If you're using a package manager and are happy with sticking with the officially released binaries from the distribution (which may the lag behind latest version of Stack significantly), simply follow your normal package manager strategies for upgrading (e.g. `apt-get update && apt-get upgrade`). * The get.haskellstack.org script supports the `-f` argument to over-write the current stack executable. For example: curl -sSL https://get.haskellstack.org/ | sh -s - -f or: wget -qO- https://get.haskellstack.org/ | sh -s - -f * Manually follow the steps above to download the newest binaries from the release page and replace the old binary. stack-1.5.1/doc/MAINTAINER_GUIDE.md0000644000000000000000000004333413140560217014452 0ustar0000000000000000# Maintainer guide ## Next release: * Create release candidate process (maybe switch to GHC-style versioning) * Maybe drop 32-bit CentOS 6 bindists, since GHC 8.2.1 seems to have dropped them. * Replace non-static Linux bindists with static (but keep old links active so links don't break and 'stack upgrade' in old versions still work) ## Pre-release steps * Check for any P0 and P1 issues. * Ensure `release` and `stable` branches merged to `master` * Check compatibility with latest Stackage snapshots * stack-*.yaml (where `*` is not `nightly`): bump to use latest LTS minor version (be sure any extra-deps that exist only for custom flags have versions matching the snapshot) * Check for any redundant extra-deps * Run `stack --stack-yaml=stack-*.yaml test --pedantic` (replace `*` with the actual file) * Check compatibility with latest nightly stackage snapshot: * Update `stack-nightly.yaml` with latest nightly and remove extra-deps (be sure any extra-deps that exist only for custom flags have versions matching the snapshot) * Run `stack --stack-yaml=stack-nightly.yaml test --pedantic` * Check pvp-bounds compatibility with Stackage snapshots: * Create an sdist using `stack sdist --pvp-bounds=both` * Temporarily replace `stack.cabal` with the `stack.cabal` in that sdist * Run `stack --stack-yaml=stack-*.yaml test --pedantic` for each `stack-*.yaml` and adjust upper bounds in original `stack.cabal` until it works with pvp-bounds. * Ensure integration tests pass on a Windows, macOS, and Linux (Linux integration tests are run by [Gitlab](http://gitlab.fpcomplete.com/fpco-mirrors/stack/pipelines)): `stack install --pedantic && stack test --pedantic --flag stack:integration-tests`. The actual release script will perform a more thorough test for every platform/variant prior to uploading, so this is just a pre-check * In master branch: * stack.cabal: bump the version number to release (even third component) * ChangeLog: rename the "Unreleased changes" section to the new version * Cut a release candidate branch `rc/vX.Y.Z` from master * In master branch: * stack.cabal: bump version number to unstable (odd third component) * Changelog: add new "unreleased changes" section * In RC branch: * Update the ChangeLog: * Check for any important changes that missed getting an entry in Changelog (`git log origin/stable...HEAD`) * Check for any entries that snuck into the previous version's changes due to merges (`git diff origin/stable HEAD ChangeLog.md`) * Review documentation for any changes that need to be made * Search for old Stack version, unstable stack version, and the next "obvious" version in sequence (if doing a non-obvious jump), and `UNRELEASED` and replace with new version * Look for any links to "latest" documentation, replace with version tag * Ensure all documentation pages listed in `mkdocs.yaml` * Update `.github/ISSUE_TEMPLATE.md` to point at the new version. * Check for new [FreeBSD release](https://www.freebsd.org/releases/). * Check that no new entries need to be added to [releases.yaml](https://github.com/fpco/stackage-content/blob/master/stack/releases.yaml), [install_and_upgrade.md](https://github.com/commercialhaskell/stack/blob/master/doc/install_and_upgrade.md), and `README.md` * Remove unsupported/obsolete distribution versions from the release process. * [Ubuntu](https://wiki.ubuntu.com/Releases) * 14.04 EOL 2019-APR * 16.04 EOL 2021-APR * [CentOS](https://wiki.centos.org/Download) * 6 EOL 2020-NOV-30 * 7 EOL 2024-JUN-30 ## Release process See [stack-release-script's README](https://github.com/commercialhaskell/stack/blob/master/etc/scripts/README.md#prerequisites) for requirements to perform the release, and more details about the tool. A note about the `etc/scripts/*-releases.sh` scripts: if you run them from a different working tree than the scripts themselves (e.g. if you have `stack1` and `stack2` trees, and run `cd stack1; ../stack2/etc/scripts/vagrant-release.sh`) the scripts and Vagrantfiles from the tree containing the script will be used to build the stack code in the current directory. That allows you to iterate on the release process while building a consistent and clean stack version. * Create a [new draft Github release](https://github.com/commercialhaskell/stack/releases/new) with tag and name `vX.Y.Z` (where X.Y.Z is the stack package's version), targetting the RC branch * On each machine you'll be releasing from, set environment variables: `GITHUB_AUTHORIZATION_TOKEN`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION`. Note: since one of the tools (rpm-s3 on CentOS) doesn't support AWS temporary credentials, you can't use MFA with the AWS credentials (`AWS_SECURITY_TOKEN` is ignored). * On a machine with Vagrant installed: * Run `etc/scripts/vagrant-releases.sh` * On macOS: * Run `etc/scripts/osx-release.sh` * On Windows: * Ensure your working tree is in `C:\stack` (or a similarly short path) * Run `etc\scripts\windows-releases.bat` * Release Windows installers. See [stack-installer README](https://github.com/borsboom/stack-installer#readme) * On Linux ARMv7: * Run `etc/scripts/linux-armv7-release.sh` * Build sdist using `stack sdist . --pvp-bounds=both`, and upload it to the Github release with a name like `stack-X.Y.Z-sdist-0.tar.gz`. * Publish Github release. Use e.g. `git shortlog -s release..HEAD|sed $'s/^[0-9 \t]*/* /'|sort -f` to get the list of contributors. * Upload package to Hackage: `stack upload . --pvp-bounds=both` * Push signed Git tag, matching Github release tag name, e.g.: `git tag -d vX.Y.Z; git tag -u 0x575159689BEFB442 -m vX.Y.Z vX.Y.Z && git push -f origin vX.Y.Z` * Reset the `release` branch to the released commit, e.g.: `git checkout release && git merge --ff-only vX.Y.Z && git push origin release` * Update the `stable` branch similarly * Delete the RC branch and any RC tags (locally and on origin) * Activate version for new release tag on [readthedocs.org](https://readthedocs.org/dashboard/stack/versions/), and ensure that stable documentation has updated * Merge any changes made in the RC/release/stable branches to master. * On a machine with Vagrant installed: * Make sure you are on the same commit as when `vagrant-release.sh` was run. * Run `etc/scripts/vagrant-distros.sh` * Upload haddocks to Hackage: `etc/scripts/upload-haddocks.sh` (if they weren't auto-built) * Announce to haskell-cafe@haskell.org, haskell-stack@googlegroups.com, commercialhaskell@googlegroups.com mailing lists * Keep an eye on the [Hackage matrix builder](http://matrix.hackage.haskell.org/package/stack) ## Setting up a Windows VM for releases These instructions are a bit rough, but has the steps to get the Windows machine set up. 1. Download VM image: https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/mac/ 2. Launch the VM using Virtualbox and the image downloaded 3. Adjust settings: * Number of CPUs: match the host * Memory: at least 3 GB * Video RAM: the minimum recommended by Virtualbox * Enable 3D and 2D accelerated mode * Enabled shared clipboard (both directions) 4. Install the VMware guest additions, and reboot 5. In **Settings**->**Update & Security**->**Windows Update**->**Advanced options**: * Change **Choose how updates are installed** to **Notify to schedule restart** * Check **Defer upgrades** 6. Configure a shared folder for your home directory on the host, and mount it on Z: 7. Install Windows SDK (for signtool): http://microsoft.com/en-us/download/confirmation.aspx?id=8279 8. Install msysgit: https://msysgit.github.io/ 9. Install nsis-2.46.5-Unicode-setup.exe from http://www.scratchpaper.com/ 10: Install Stack using the Windows 64-bit installer 11. Visit https://hackage.haskell.org/ in Edge to ensure system has correct CA certificates 12. Get the object code certificate from [password-store](https://github.com/fpco/password-store), in `certificates/code_signing/fpcomplete_corporation_startssl_2015-09-22.pfx`. Double click it in explorer and import it 13. Run in command prompt: md C:\p md C:\p\tmp cd \p md c:\tmp 14. Create `C:\p\env.bat`: SET STACK_ROOT=C:\p\.sr SET TEMP=C:\p\tmp SET TMP=C:\p\tmp SET PATH=C:\Users\IEUser\AppData\Roaming\local\bin;"c:\Program Files\Git\usr\bin";"C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin";%PATH% 15. Run `C:\p\env.bat` (do this every time you open a new command prompt) 16. Import the `dev@fpcomplete.com` (0x575159689BEFB442) GPG secret key 17. Run in command prompt (adjust the `user.email` and `user.name` settings): stack setup stack install cabal-install md %HOMEPATH%\.ssh copy z:\.ssh\id_rsa %HOMEPATH%\.ssh git config --global user.email manny@fpcomplete.com git config --global user.name "Emanuel Borsboom" git config --global push.default simple git config --global core.autocrlf true git clone git@github.com:commercialhaskell/stack.git git clone git@github.com:borsboom/stack-installer.git ## Setting up an ARM VM for releases These instructions assume the host system is running macOS. Some steps will vary with a different host OS. ### Install qemu on host brew install qemu ### Install fuse-ext2 brew install e2fsprogs m4 automake autoconf libtool && \ git clone https://github.com/alperakcan/fuse-ext2.git && \ cd fuse-ext2 && \ Add `m4_ifdef([AM_PROG_AR], [AM_PROG_AR])` to the `configure.ac` after `m4_ifdef([AC_PROG_LIB],[AC_PROG_LIB],[m4_warn(portability,[Missing AC_PROJ_LIB])])` line. PKG_CONFIG_PATH="$(brew --prefix e2fsprogs)/lib/pkgconfig" \ CFLAGS="-idirafter/$(brew --prefix e2fsprogs)/include -idirafter/usr/local/include/osxfuse" \ LDFLAGS="-L$(brew --prefix e2fsprogs)/lib" \ ./configure ### Create VM and install Debian in it wget http://ftp.de.debian.org/debian/dists/jessie/main/installer-armhf/current/images/netboot/initrd.gz && \ wget http://ftp.de.debian.org/debian/dists/jessie/main/installer-armhf/current/images/netboot/vmlinuz && \ wget http://ftp.de.debian.org/debian/dists/jessie/main/installer-armhf/current/images/device-tree/vexpress-v2p-ca9.dtb && \ qemu-img create -f raw armdisk.raw 15G && \ qemu-system-arm -M vexpress-a9 -cpu cortex-a9 -kernel vmlinuz -initrd initrd.gz -sd armdisk.raw -append "root=/dev/mmcblk0p2" -m 1024M -redir tcp:2223::22 -dtb vexpress-v2p-ca9.dtb -append "console=ttyAMA0,115200" -serial stdio Now the Debian installer will run. Don't use LVM for partitioning (it won't boot), and add at least 4 GB swap during installation. ### Get boot files after install Adjust the disk number `/dev/disk3` below to match the output from `hdiutil attach`. hdiutil attach -imagekey diskimage-class=CRawDiskImage -nomount armdisk.raw && \ sudo mkdir -p /Volumes/debarm && \ sudo fuse-ext2 /dev/disk3s1 /Volumes/debarm/ && \ sleep 5 && \ cp /Volumes/debarm/vmlinuz-3.16.0-4-armmp . && \ cp /Volumes/debarm/initrd.img-3.16.0-4-armmp . && \ sudo umount /Volumes/debarm && \ hdiutil detach /dev/disk3 ### Boot VM Adjust `/dev/mmcblk0p3` below to the root partition you created during installation. qemu-system-arm -M vexpress-a9 -cpu cortex-a9 -kernel vmlinuz-3.16.0-4-armmp -initrd initrd.img-3.16.0-4-armmp -sd armdisk.raw -m 1024M -dtb vexpress-v2p-ca9.dtb -append "root=/dev/mmcblk0p3 console=ttyAMA0,115200" -serial stdio -redir tcp:2223::22 ### Setup rest of system Log onto the VM as root, then (replace `<<>>` with the user you set up during Debian installation): apt-get update && \ apt-get install -y sudo && \ adduser <<>> sudo Now you can SSH to the VM using `ssh -p 2223 <<>>@localhost` and use `sudo` in the shell. ### Install build tools and dependencies packages sudo apt-get install -y g++ gcc libc6-dev libffi-dev libgmp-dev make xz-utils zlib1g-dev git gnupg ### Install clang+llvm NOTE: the Debian jessie `llvm` packge does not work (executables built with it just exit with "schedule: re-entered unsafely."). The version of LLVM needed depends on the version of GHC you need. #### GHC 8.0.2 (the standard for building Stack) wget http://llvm.org/releases/3.7.1/clang+llvm-3.7.1-armv7a-linux-gnueabihf.tar.xz && \ sudo tar xvf clang+llvm-3.7.1-armv7a-linux-gnueabihf.tar.xz -C /opt Run this now and add it to the `.profile`: export PATH="$HOME/.local/bin:/opt/clang+llvm-3.7.1-armv7a-linux-gnueabihf/bin:$PATH" #### GHC 7.10.3 wget http://llvm.org/releases/3.5.2/clang+llvm-3.5.2-armv7a-linux-gnueabihf.tar.xz && \ sudo tar xvf clang+llvm-3.5.2-armv7a-linux-gnueabihf.tar.xz -C /opt Run this now and add it to the `.profile`: export PATH="$HOME/.local/bin:/opt/clang+llvm-3.5.2-armv7a-linux-gnueabihf/bin:$PATH" ### Install Stack #### Binary Get an [existing `stack` binary](https://github.com/commercialhaskell/stack/releases) and put it in `~/.local/bin`. #### From source (using cabal-install): wget http://downloads.haskell.org/~ghc/7.10.3/ghc-7.10.3-armv7-deb8-linux.tar.xz && \ tar xvf ghc-7.10.3-armv7-deb8-linux.tar.xz && \ cd ghc-7.10.3 && \ ./configure --prefix=/opt/ghc-7.10.3 && \ sudo make install && \ cd .. export PATH="/opt/ghc-7.10.3/bin:$PATH" wget https://www.haskell.org/cabal/release/cabal-install-1.24.0.0/cabal-install-1.24.0.0.tar.gz &&&&& \ tar xvf cabal-install-1.24.0.0.tar.gz && \ cd cabal-install-1.24.0.0 && \ EXTRA_CONFIGURE_OPTS="" ./bootstrap.sh && \ cd .. && \ export PATH="$HOME/.cabal/bin:$PATH" && \ cabal update Edit `~/.cabal/config`, and set `executable-stripping: False` and `library-stripping: False`. cabal unpack stack && \ cd stack-* && \ cabal install && \ mv ~/.cabal/bin/stack ~/.local/bin ### Import GPG private key Import the `dev@fpcomplete.com` (0x575159689BEFB442) GPG secret key ### Resources - http://mashu.github.io/2015/08/12/QEMU-Debian-armhf.html - https://www.aurel32.net/info/debian_arm_qemu.php - http://linuxdeveloper.blogspot.ca/2011/08/how-to-install-arm-debian-on-ubuntu.html - http://www.macworld.com/article/2855038/how-to-mount-and-manage-non-native-file-systems-in-os-x-with-fuse.html - https://github.com/alperakcan/fuse-ext2#mac-os - https://github.com/alperakcan/fuse-ext2/issues/31#issuecomment-214713801 - https://github.com/alperakcan/fuse-ext2/issues/33#issuecomment-216758378 - https://github.com/alperakcan/fuse-ext2/issues/32#issuecomment-216758019 - http://osxdaily.com/2007/03/23/create-a-ram-disk-in-mac-os-x/ ## Adding a new GHC version * Push new tag to our fork: git clone git@github.com:commercialhaskell/ghc.git cd ghc git remote add upstream git@github.com:ghc/ghc.git git fetch upstream git push origin ghc-X.Y.Z-release * [Publish a new Github release](https://github.com/commercialhaskell/ghc/releases/new) with tag `ghc-X.Y.Z-release` and same name. * Down all the relevant GHC bindists from https://www.haskell.org/ghc/download_ghc_X_Y_Z and upload them to the just-created Github release (see [stack-setup-2.yaml](https://github.com/fpco/stackage-content/blob/master/stack/stack-setup-2.yaml) for the ones we used in the last GHC release). In the case of macOS, repackage the `.xz` bindist as a `.bz2`, since macOS does not include `xz` by default or provide an easy way to install it. The script at `etc/scripts/mirror-ghc-bindists-to-github.sh` will help with this. See the comments within the script. * Build any additional required bindists (see below for instructions) * tinfo6 (`etc/vagrant/fedora24-x86_64`) * ncurses6 (`etc/vagrant/arch-x86_64`) * [Edit stack-setup-2.yaml](https://github.com/fpco/stackage-content/edit/master/stack/stack-setup-2.yaml) and add the new bindists, pointing to the Github release version. Be sure to update the `content-length` and `sha1` values. ### Building GHC On systems with a small `/tmp`, you should set TMP and TEMP to an alternate location. For GHC >= 7.10.2, set the `GHC_VERSION` environment variable to the version to build: * `export GHC_VERSION=8.0.2` * `export GHC_VERSION=8.0.1` * `export GHC_VERSION=7.10.3a` * `export GHC_VERSION=7.10.2` then, run (from [here](https://ghc.haskell.org/trac/ghc/wiki/Newcomers)): git config --global url."git://github.com/ghc/packages-".insteadOf git://github.com/ghc/packages/ && \ git clone -b ghc-${GHC_VERSION}-release --recursive git://github.com/ghc/ghc ghc-${GHC_VERSION} && \ cd ghc-${GHC_VERSION}/ && \ cp mk/build.mk.sample mk/build.mk && \ sed -i 's/^#BuildFlavour *= *perf$/BuildFlavour = perf/' mk/build.mk && \ ./boot && \ ./configure --enable-tarballs-autodownload && \ sed -i 's/^TAR_COMP *= *bzip2$/TAR_COMP = xz/' mk/config.mk && \ make -j$(cat /proc/cpuinfo|grep processor|wc -l) && \ make binary-dist GHC 7.8.4 is slightly different: export GHC_VERSION=7.8.4 && \ git config --global url."git://github.com/ghc/packages-".insteadOf git://github.com/ghc/packages/ && \ git clone -b ghc-${GHC_VERSION}-release --recursive git://github.com/ghc/ghc ghc-${GHC_VERSION} && \ cd ghc-${GHC_VERSION}/ && \ ./sync-all --extra --nofib -r git://git.haskell.org get -b ghc-7.8 && \ cp mk/build.mk.sample mk/build.mk && \ sed -i 's/^#BuildFlavour *= *perf$/BuildFlavour = perf/' mk/build.mk && \ perl boot && \ ./configure && \ sed -i 's/^TAR_COMP *= *bzip2$/TAR_COMP = xz/' mk/config.mk && \ make -j$(cat /proc/cpuinfo|grep processor|wc -l) && \ make binary-dist stack-1.5.1/doc/nix_integration.md0000644000000000000000000002333113063526313015305 0ustar0000000000000000# Nix integration (since 0.1.10.0) When using the Nix integration, Haskell dependencies are handled as usual: They are downloaded from Stackage and built locally by Stack. Nix is used by Stack to provide the _non-Haskell_ dependencies needed by these Haskell packages. `stack` can automatically create a build environment (the equivalent of a "container" in Docker parlance) using `nix-shell`, provided Nix is already installed on your system. To do so, please visit the [Nix download page](http://nixos.org/nix/download.html). There are two ways to create a build environment: - providing a list of packages (by "attribute name") from [Nixpkgs](http://nixos.org/nixos/packages.html), or - providing a custom `shell.nix` file containing a Nix expression that determines a *derivation*, i.e. a specification of what resources are available inside the shell. The second requires writing code in Nix's custom language. So use this option only if you already know Nix and have special requirements, such as using custom Nix packages that override the standard ones or using system libraries with special requirements. ### Checking Nix installation Follow the instructions on the [Nix download page](http://nixos.org/nix/download.html) to install Nix. After doing so, when opening a terminal, the nix commands (`nix-build`, `nix-shell`, etc) should be available. If they are not, it should be because the file located at `$HOME/.nix-profile/etc/profile.d/nix.sh` is not sourced by your shell. You should either run `source ~/.nix-profile/etc/profile.d/nix.sh` manually everytime you open a terminal and need Nix or add this command to your `~/.bashrc` or `~/.bash_profile`. ### Additions to your `stack.yaml` Add a section to your `stack.yaml` as follows: ```yaml nix: enable: true packages: [glpk, pcre] ``` This will instruct `stack` to build inside a local build environment that will have the `glpk` and `pcre` libraries installed and available. Further, the build environment will implicitly also include a version of GHC matching the configured resolver. Enabling Nix support means packages will always be built using a GHC available inside the shell, rather than your globally installed one if any. Note that in this mode `stack` can use only GHC versions than have already been mirrored into the Nix package repository. The [Nixpkgs master branch](https://github.com/NixOS/nixpkgs/tree/master/pkgs/development/haskell-modules) usually picks up new versions quickly, but it takes two or three days before those updates arrive in the `unstable` channel. Release channels, like `nixos-15.09`, receive those updates only occasionally -- say, every two or three months --, so you should not expect them to have the latest compiler available. Fresh NixOS installs use a release version by default. To know for sure whether a given compiler is available on your system, you can use the command ```sh $ nix-env -f "" -qaP -A haskell.compiler.ghc801 haskell.compiler.ghc801 ghc-8.0.1 ``` to check whether it's available. If Nix doesn't know that resolver yet, then you'll see the following error message instead: ```sh $ nix-env -f "" -qaP -A haskell.compiler.ghc999 error: attribute ‘ghc999’ in selection path ‘haskell.compiler.ghc999’ not found ``` You can list all known Haskell compilers in Nix with the following: ```sh $ nix-instantiate --eval -E "with import {}; lib.attrNames haskell.compiler" ``` Alternatively, install `nix-repl`, a convenient tool to explore nixpkgs: ```sh $ nix-env -i nix-repl $ nix-repl ``` In the REPL, load nixpkgs and get the same information through autocomplete: ```sh nix-repl> :l nix-repl> haskell.compiler.ghc ``` You can type and evaluate any nix expression in the nix-repl, such as the one we gave to `nix-instantiate` earlier. **Note:** currently, stack only discovers dynamic and static libraries in the `lib/` folder of any nix package, and likewise header files in the `include/` folder. If you're dealing with a package that doesn't follow this standard layout, you'll have to deal with that using a custom shell file (see below). ### Use stack as normal With Nix enabled, `stack build` and `stack exec` will automatically launch themselves in a local build environment (using `nix-shell` behind the scenes). `stack setup` will start a nix-shell, so it will gather all the required packages, but given nix handles GHC installation, instead of stack, this will happen when running `stack build` if no setup has been performed before. Therefore it is not longer necessary to run `stack setup` unless you want to cache a GHC installation before running the build. If `enable:` is omitted or set to `false`, you can still build in a nix-shell by passing the `--nix` flag to stack, for instance `stack --nix build`. Passing any `--nix*` option to the command line will do the same. **Known limitation on macOS:** currently, `stack --nix ghci` fails on macOS, due to a bug in GHCi when working with external shared libraries. ### The Nix shell By default, stack will run the build in a *pure* Nix build environment (or *shell*), which means two important things: - basically **no environment variable will be forwarded** from your user session to the nix-shell (variables like `HTTP_PROXY` or `PATH` notably will not be available), - the build should fail if you haven't specified all the dependencies in the `packages:` section of the `stack.yaml` file, even if these dependencies are installed elsewhere on your system. This behaviour enforces a complete description of the build environment to facilitate reproducibility. To override this behaviour, add `pure: false` to your `stack.yaml` or pass the `--no-nix-pure` option to the command line. **Note:** On macOS shells are non-pure by default currently. This is due soon to be resolved locale issues. So on macOS you'll need to be a bit more careful to check that you really have listed all dependencies. ### Package sources By default, `nix-shell` will look for the nixpkgs package set located by your `NIX_PATH` environment variable. You can override this by passing `--nix-path="nixpkgs=/my/own/nixpkgs/clone"` to ask Nix to use your own local checkout of the nixpkgs repository. You could in this way use a bleeding edge nixpkgs, cloned from the [nixpkgs](http://www.github.com/NixOS/nixpkgs) `master` branch, or edit the nix descriptions of some packages. Setting ```yml nix: path: [nixpkgs=/my/own/nixpkgs/clone] ``` in your `stack.yaml` will do the same. ## Command-line options The configuration present in your `stack.yaml` can be overridden on the command-line. See `stack --nix-help` for a list of all Nix options. ## Configuration `stack.yaml` contains a `nix:` section with Nix settings. Without this section, Nix will not be used. Here is a commented configuration file, showing the default values: ```yaml nix: # false by default. Must be present and set to `true` to enable Nix. # You can set set it in your `$HOME/.stack/config.yaml` to enable # Nix for all your projects without having to repeat it # enable: true # true by default. Tells Nix whether to run in a pure shell or not. pure: true # Empty by default. The list of packages you want to be # available in the nix-shell at build time (with `stack # build`) and run time (with `stack exec`). packages: [] # Unset by default. You cannot set this option if `packages:` # is already present and not empty. shell-file: shell.nix # A list of strings, empty by default. Additional options that # will be passed verbatim to the `nix-shell` command. nix-shell-options: [] # A list of strings, empty by default, such as # `[nixpkgs=/my/local/nixpkgs/clone]` that will be used to override # NIX_PATH. path: [] # false by default. Whether to add your nix dependencies as nix garbage # collection roots. This way, calling nix-collect-garbage will not remove # those packages from the nix store, saving you some time when running # stack build again with nix support activated. # This creates a `nix-gc-symlinks` directory in the project `.stack-work`. # To revert that, just delete this `nix-gc-symlinks` directory. add-gc-roots: false ``` ## Using a custom shell.nix file Nix is also a programming language, and as specified [here](#nix-integration) if you know it you can provide to the shell a fully customized derivation as an environment to use. Here is the equivalent of the configuration used in [this section](#additions-to-your-stackyaml), but with an explicit `shell.nix` file (make sure you're using a nixpkgs version later than 2015-03-05): ```nix {ghc}: with (import {}); haskell.lib.buildStackProject { inherit ghc; name = "myEnv"; buildInputs = [ glpk pcre ]; } ``` Defining manually a `shell.nix` file gives you the possibility to override some Nix derivations ("packages"), for instance to change some build options of the libraries you use, or to set additional environment variables. See the [Nix manual][nix-manual-exprs] for more. The `buildStackProject` utility function is documented in the [Nixpkgs manual][nixpkgs-manual-haskell]. In such case, stack expect this file to define a function of exactly one argument that should be called `ghc` (as arguments within a set are non-positional), which you should give to `buildStackProject`. This is the ghc from the resolver you set in the `stack.yaml`. And now for the `stack.yaml` file: ```yaml nix: enable: true shell-file: shell.nix ``` The `stack build` command will behave exactly the same as above. Note that specifying both `packages:` and a `shell-file:` results in an error. (Comment one out before adding the other.) [nix-manual-exprs]: http://nixos.org/nix/manual/#chap-writing-nix-expressions [nixpkgs-manual-haskell]: https://nixos.org/nixpkgs/manual/#users-guide-to-the-haskell-infrastructure stack-1.5.1/doc/nonstandard_project_init.md0000644000000000000000000001146613063526313017176 0ustar0000000000000000# Non-standard project initialization ## Introduction The purpose of this page is to collect information about issues that arise when users either have an existing cabal project or another nonstandard setup such as a private hackage database. ## Using a Cabal File New users may be confused by the fact that you must add dependencies to the package's cabal file, even in the case when you have already listed the package in the `stack.yaml`. In most cases, dependencies for your package that are in the Stackage snapshot need *only* be added to the cabal file. stack makes heavy use of Cabal the library under the hood. In general, your stack packages should also end up being valid cabal-install packages. ### Issues Referenced - https://github.com/commercialhaskell/stack/issues/105 ## Passing Flags to Cabal Any build command, `bench`, `install`, `haddock`, `test`, etc. takes a `--flag` option which passes flags to cabal. Another way to do this is using the flags field in a `stack.yaml`, with the option to specify flags on a per package basis. As an example, in a `stack.yaml` for multi-package project with packages `foo`, `bar`, `baz`: ``` flags: foo: release: true bar: default: true baz: manual: true ``` It is also possible to pass the same flag to multiple packages, i.e. `stack build --flag *:necessary` Currently one needs to list all of your modules that interpret flags in the `other-modules` section of a cabal file. `cabal-install` has a different behavior currently and doesn't require that the modules be listed. This may change in a future release. ### Issues Referenced - https://github.com/commercialhaskell/stack/issues/191 - https://github.com/commercialhaskell/stack/issues/417 - https://github.com/commercialhaskell/stack/issues/335 - https://github.com/commercialhaskell/stack/issues/301 - https://github.com/commercialhaskell/stack/issues/365 - https://github.com/commercialhaskell/stack/issues/105 ## Selecting a Resolver `stack init` or `stack new` will try to default to the current Haskell LTS present on `https://www.stackage.org/snapshots` if no snapshot has been previously used locally, and to the latest LTS snapshot locally used for a build otherwise. Using an incorrect resolver can cause a build to fail if the version of GHC it requires is not present. In order to override the resolver entry at project initialization one can pass `--prefer-lts` or `--prefer-nightly`. These options will choose the latest LTS or nightly versions locally used. Alternatively the `--resolver` option can be used with the name of any snapshots on Stackage, or with `lts` or `nightly` to select the latest versions, disregarding previously used ones. This is not the default so as to avoid unnecessary recompilation time. :TODO: Document `--solver` ### Issues Referenced - https://github.com/commercialhaskell/stack/issues/468 - https://github.com/commercialhaskell/stack/issues/464 ## Using git Repositories stack has support for packages that reside in remote git locations. Example: ``` packages: - '.' - location: git: https://github.com/kolmodin/binary commit: 8debedd3fcb6525ac0d7de2dd49217dce2abc0d9 ``` ### Issues Referenced - https://github.com/commercialhaskell/stack/issues/254 - https://github.com/commercialhaskell/stack/issues/199 ## Private Hackage Working with a private Hackage is currently supported in certain situations. There exist special entries in `stack.yaml` that may help you. In a `stack.yaml` file, it is possible to add lines for packages in your database referencing the sdist locations via an `http` entry, or to use a `Hackage` entry. The recommended stack workflow is to use git submodules instead of a private Hackage. Either by using git submodules and listing the directories in the packages section of `stack.yaml`, or by adding the private dependencies as git URIs with a commit SHA to the `stack.yaml`. This has the large benefit of eliminating the need to manage a Hackage database and pointless version bumps. For further information see [YAML configuration](yaml_configuration.md) ### Issues Referenced - https://github.com/commercialhaskell/stack/issues/445 - https://github.com/commercialhaskell/stack/issues/565 ## Custom Snapshots Currently WIP? ### Issues Referenced - https://github.com/commercialhaskell/stack/issues/111 - https://github.com/commercialhaskell/stack/issues/253 - https://github.com/commercialhaskell/stack/issues/137 ## Intra-package Targets stack supports intra-package targets, similar to `cabal build COMPONENTS` for situations when you don't want to build every target inside your package. Example: ``` stack build stack:lib:stack stack test stack:test:stack-integration-test ``` Note: this does require prefixing the component name with the package name. ### Issues referenced - https://github.com/commercialhaskell/stack/issues/201 stack-1.5.1/doc/README.md0000644000000000000000000001361213135651621013043 0ustar0000000000000000# The Haskell Tool Stack Stack is a cross-platform program for developing Haskell projects. It is aimed at Haskellers both new and experienced. It features: * Installing GHC automatically, in an isolated location. * Installing packages needed for your project. * Building your project. * Testing your project. * Benchmarking your project. #### How to install For many Un*x operating systems, all you need to do is run: curl -sSL https://get.haskellstack.org/ | sh or: wget -qO- https://get.haskellstack.org/ | sh On Windows, you can download and install the [Windows 64-bit Installer](https://www.stackage.org/stack/windows-x86_64-installer). For detailed instructions and downloads, including many additional operating systems, check out the [install and upgrade page](install_and_upgrade.md). #### Quick Start Guide First you need to [install it (see previous section)](#how-to-install). ##### Start your new project: ```bash stack new my-project cd my-project stack setup stack build stack exec my-project-exe ``` - The `stack new` command will create a new directory containing all the needed files to start a project correctly. - The `stack setup` will download the compiler if necessary in an isolated location (default `~/.stack`) that won't interfere with any system-level installations. (For information on installation paths, please use the `stack path` command.). - The `stack build` command will build the minimal project. - `stack exec my-project-exe` will execute the command. - If you just want to install an executable using stack, then all you have to do is`stack install `. If you want to launch a REPL: ```bash stack ghci ``` Run `stack` for a complete list of commands. ##### Workflow The `stack new` command should have created the following files: ``` . ├── LICENSE ├── Setup.hs ├── app │   └── Main.hs ├── my-project.cabal ├── src │   └── Lib.hs ├── stack.yaml └── test └── Spec.hs 3 directories, 7 files ``` So to manage your library: 1. Edit files in the `src/` directory. The `app` directory should preferably contain only files related to executables. 2. If you need to include another library (for example the package [`text`](https://hackage.haskell.org/package/text): - Add the package `text` to the file `my-project.cabal` in the section `build-depends: ...`. - run `stack build` another time 3. If you get an error that tells you your package isn't in the LTS. Just try to add a new version in the `stack.yaml` file in the `extra-deps` section. It was a really fast introduction on how to start to code in Haskell using `stack`. If you want to go further, we highly recommend you to read the [`stack` guide](GUIDE.md). #### How to contribute This assumes that you have already installed a version of stack, and have `git` installed. 1. Clone `stack` from git with `git clone https://github.com/commercialhaskell/stack.git`. 2. Enter into the stack folder with `cd stack`. 3. Build `stack` using a pre-existing `stack` install with `stack setup && stack build`. 4. Once `stack` finishes building, check the stack version with `stack exec stack -- --version`. Make sure the version is the latest. 5. Look for issues tagged with [`newcomer` and `awaiting-pr` labels](https://github.com/commercialhaskell/stack/issues?q=is%3Aopen+is%3Aissue+label%3Anewcomer+label%3A%22awaiting+pr%22). Build from source as a one-liner: ```bash git clone https://github.com/commercialhaskell/stack.git && \ cd stack && \ stack setup && \ stack build ``` #### Complete guide to stack This repository also contains a complete [user guide to using stack ](GUIDE.md), covering all of the most common use cases. #### Questions, Feedback, Discussion * For frequently asked questions about detailed or specific use-cases, please see [the FAQ](faq.md). * For general questions, comments, feedback and support please write to [the stack mailing list](https://groups.google.com/d/forum/haskell-stack). * For bugs, issues, or requests please [open an issue](https://github.com/commercialhaskell/stack/issues/new). * When using Stack Overflow, please use [the haskell-stack tag](http://stackoverflow.com/questions/tagged/haskell-stack). #### Why Stack? Stack is a build tool for Haskell designed to answer the needs of Haskell users new and experienced alike. It has a strong focus on reproducible build plans, multi-package projects, and a consistent, easy-to-learn interface, while providing the customizability and power experienced developers need. As a build tool, Stack does not stand alone. It is built on the great work provided by: * The __Glasgow Haskell Compiler__ (GHC), the premiere Haskell compiler. Stack will manage your GHC installations and automatically select the appropriate compiler version for your project. * The __Cabal build system__, a specification for defining Haskell packages, together with a library for performing builds. * The __Hackage package repository__, providing more than ten thousand open source libraries and applications to help you get your work done. * The __Stackage package collection__, a curated set of packages from Hackage which are regularly tested for compatibility. Stack defaults to using Stackage package sets to avoid dependency problems. Stack is provided by a team of volunteers and companies under the auspices of the [Commercial Haskell](http://commercialhaskell.com/) group. The project was spearheaded by [FP Complete](https://www.fpcomplete.com/) to answer the needs of commercial Haskell users, and has since become a thriving open source project meeting the needs of Haskell users of all stripes. If you'd like to get involved with Stack, check out the [newcomers label on the Github issue tracker](https://github.com/commercialhaskell/stack/issues?q=is%3Aopen+is%3Aissue+label%3Anewcomer). stack-1.5.1/doc/shell_autocompletion.md0000644000000000000000000000317713062242347016344 0ustar0000000000000000# Shell Auto-completion Note: if you installed a package for you Linux distribution, the bash completion file was automatically installed (you may need the `bash-completion` package to have it take effect). The following adds support for shell tab completion for standard Stack arguments, although completion for filenames and executables etc. within stack is still lacking (see [issue 823](https://github.com/commercialhaskell/stack/issues/832)). ## for bash users you need to run following command ``` eval "$(stack --bash-completion-script stack)" ``` You can also add it to your `.bashrc` file if you want. ## for ZSH users documentation says: > Zsh can handle bash completions functions. The latest development version of > zsh has a function bashcompinit, that when run will allow zsh to read bash > completion specifications and functions. This is documented in the zshcompsys > man page. To use it all **you need to do is run bashcompinit at any time > after compinit**. It will define complete and compgen functions corresponding > to the bash builtins. You must so: 1. launch compinint 2. launch bashcompinit 3. eval stack bash completion script ```shell autoload -U +X compinit && compinit autoload -U +X bashcompinit && bashcompinit eval "$(stack --bash-completion-script stack)" ``` :information_source: If you already have quite a large zshrc, or if you use oh-my-zsh, **compinit** will probably already be loaded. If you have a blank zsh config, all of the 3 lines above are necessary. :gem: tip: instead of running those 3 lines from your shell every time you want to use stack, you can add those 3 lines in your $HOME/.zshrc file stack-1.5.1/doc/SIGNING_KEY.md0000644000000000000000000000333512630352174013715 0ustar0000000000000000# Signing key Releases are signed with this key: ``` -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 mQENBFVs+cMBCAC5IsLWTikd1V70Ur1FPJMn14Sc/C2fbXc0zRcPuWX+JaXgrIJQ 74A3UGBpa07wJDZiQLLz4AasDQj++9gXdiM9MlK/xWt8BQpgQqSMgkktFVajSWX2 rSXPjqLtsl5dLsc8ziBkd/AARXoeITmXX+n6oRTy6QfdMv2Tacnq7r9M9J6bAz6/ 7UsKkyZVwsbUPea4SuD/s7jkXAuly15APaYDmF5mMlpoRWp442lJFpA0h52mREX1 s5FDbuKRQW7OpZdLcmOgoknJBDSpKHuHEoUhdG7Y3WDUGYFZcTtta1qSVHrm3nYa 7q5yOzPW4/VpftkBs1KzIxx0nQ5INT5W5+oTABEBAAG0H0ZQQ29tcGxldGUgPGRl dkBmcGNvbXBsZXRlLmNvbT6JATcEEwEKACEFAlVs+cMCGwMFCwkIBwMFFQoJCAsF FgMCAQACHgECF4AACgkQV1FZaJvvtEIP8gf/S/k4C3lp/BFb0K9DHHSt6EaGQPwy g+O8d+JvL7ghkvMjlQ+UxDw+LfRKANTpl8a4vHtEQLHEy1tPJfrnMA8DNci8HLVx rK3lIqMfv5t85VST9rz3X8huSw7qwFyxsmIqFtJC/BBQfsOXC+Q5Z2nbResXHMeA 5ZvDopZnqKPdmMOngabPGZd89hOKn6r8k7+yvZ/mXmrGOB8q5ZGbOXUbCshst7lc yZWmoK3VJdErQjGHCdF4MC9KFBQsYYUy9b1q0OUv9QLtq/TeKxfpvYk9zMWAoafk M8QBE/qqOpqkBRoKbQHCDQgx7AXJMKnOA0jPx1At57hWl7PuEH4rK38UtLkBDQRV bPnDAQgAx1+4ENyaMk8XznQQ4l+nl8qw4UedZhnR5Xxr6z2kcMO/0VdwmIDCpxaM spurOF+yExfY/Chbex7fThWTwVgfsItUc/QLLv9jkvpveMUDuPyh/4QrAQBYoW09 jMJcOTFQU+f4CtKaN/1PNoTSU2YkVpbhvtV3Jn2LPFjUSPb7z2NZ9NKe10M0/yN+ l0CuPlqu6GZR5L3pA5i8PZ0Nh47j0Ux5KIjrjCGne4p+J8qqeRhUf04yHAYfDLgE aLAG4v4pYbb1jNPUm1Kbk0lo2c3dxx0IU201uAQ6LNLdF/WW/ZF7w3iHn7kbbzXO jhbq2rvZEn3K9xDr7homVnnj21/LSQARAQABiQEfBBgBCgAJBQJVbPnDAhsMAAoJ EFdRWWib77RC3ukH/R9jQ4q6LpXynQPJJ9QKwstglKfoKNpGeAYVTEn0e7NB0HV5 BC+Da5SzBowboxC2YCD1wTAjBjLLQfAYNyR+tHpJBaBmruafj87nBCDhSWwWDXwx OUDpNOwKUkrwZDRlM7n4byoMRl7Vh/7CXxaTqkyao1c5v3mHh/DremiTvOJ4OXgJ 77NHaPXezHkCFZC8/sX6aY0DJxF+LIE84CoLI1LYBatH+NKxoICKA+yeF3RIVw0/ F3mtEFEtmJ6ljSks5tECxfJFvQlkpILBbGvHfuljKMeaj+iN+bsHmV4em/ELB1ku N9Obs/bFDBMmQklIdLP7dOunDjY4FwwcFcXdNyg= =YUsC -----END PGP PUBLIC KEY BLOCK----- ``` stack-1.5.1/doc/travis_ci.md0000644000000000000000000001532113063526313014067 0ustar0000000000000000# Travis CI This page documents how to use Stack on [Travis CI](https://travis-ci.org/). We assume you have basic familiarity with Travis. We provide two fully baked example files ready to be used on your projects: * [The simple Travis configuration](https://raw.githubusercontent.com/commercialhaskell/stack/master/doc/travis-simple.yml) is intended for applications that do not require multiple GHC support or cross-platform support. It builds and tests your project with just the settings present in your `stack.yaml` file. * [The complex Travis configuration](https://raw.githubusercontent.com/commercialhaskell/stack/master/doc/travis-complex.yml) is intended for projects that need to support multiple GHC versions and multiple OSes, such as open source libraries to be released to Hackage. It tests against cabal-install, as well as Stack on Linux and macOS. The configuration is significantly more involved to allow for all of this branching behavior. __NOTE__: It is likely going to be necessary to modify this configuration to match the needs of your project, such as tweaking the build matrix to alter which GHC versions you test against, or to specify GHC-version-specific `stack.yaml` files if necessary. Don't be surprised if it doesn't work the first time around. See the multiple GHC section below for more information. Each of these configurations is ready to be used immediately, just copy-paste the content into the `.travis.yml` file in the root or your repo, enable Travis on the repo, and you're good to go. You may also be interested in using AppVeyor, which supports Windows builds, for more cross-platform testing. There's a [short blog post available on how to do this](http://www.snoyman.com/blog/2016/08/appveyor-haskell-windows-ci). The rest of this document explains the details of common Travis configurations for those of you who want to tweak the above configuration files or write your own. *Note:* both Travis and Stack infrastructures are actively developed. We try to document best practices at the moment. ## Container infrastructure For Stack on Travis to be practical, we must use caching. Otherwise build times will take an incredibly long time, about 30 minutes versus 3-5. Caching is currently available only for [container-based Travis infrastructure](http://docs.travis-ci.com/user/workers/container-based-infrastructure/). Shortly we have to add ```yaml sudo: false # Caching so the next build will be fast too. cache: directories: - $HOME/.stack ``` To the `.travis.yml`. This however restricts how we can install GHC and Stack on the Travis machines. ## Installing Stack Currently there is only one reasonable way to install Stack: fetch precompiled binary from the Github. ```yaml before_install: # Download and unpack the stack executable - mkdir -p ~/.local/bin - export PATH=$HOME/.local/bin:$PATH - travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' ``` Once Travis whitelists the stack .deb files, we'll be able to simply include stack in the `addons` section, and automatically use the newest version of stack, avoiding that complicated `before_install` section This is being tracked in the [apt-source-whitelist](https://github.com/travis-ci/apt-source-whitelist/pull/7) and [apt-package-whitelist](https://github.com/travis-ci/apt-package-whitelist/issues/379) issue trackers. ## Installing GHC There are two ways to install GHC: - Let Stack download GHC - Install GHC using [apt plugin](http://docs.travis-ci.com/user/apt/) See the above scripts for an example of the first option (letting Stack download GHC). Here, we will explain the second option. With single GHC the situation is simple: ```yaml before_install: # Install stack as above # ... # Configure stack to use the system GHC installation - stack config set system-ghc --global true - export PATH=/opt/ghc/7.10.2/bin:$PATH addons: apt: sources: - hvr-ghc packages: - ghc-7.10.2 ``` ### Multiple GHC - parametrised builds Travis apt plugin doesn't yet support installing apt packages dynamically (https://github.com/travis-ci/travis-ci/issues/4291). That for we need to write a bit repetitive `.travis.yml`. Also for different GHC versions, you probably want to use different `stack.yaml` files. ```yaml # N.B. No top-level env: declaration! matrix: include: - env: GHCVER=7.8.4 STACK_YAML=stack.yaml addons: apt: sources: - hvr-ghc packages: - ghc-7.8.4 - env: GHCVER=7.10.1 STACK_YAML=stack-7.10.yaml addons: apt: sources: - hvr-ghc packages: - ghc-7.10.1 - env: GHCVER=head STACK_YAML=stack-head.yaml addons: apt: sources: - hvr-ghc packages: - ghc-head allow_failures: - env: GHCVER=head STACK_YAML=stack-head.yaml before_install: # ghc - export PATH=/opt/ghc/$GHCVER/bin:$PATH ``` Especially to use ghc `HEAD` you need to pass `--skip-ghc-check` option to Stack. ## Running tests After the environment setup, actual test running is simple: ```yaml script: - stack --no-terminal --skip-ghc-check test ``` In case you're wondering: we need `--no-terminal` because stack does some fancy sticky display on smart terminals to give nicer status and progress messages, and the terminal detection is broken on Travis. ## Other details Some Stack commands will run for long time (when cache is cold) without producing any output. To avoid timeouts, use the built in [travis_wait](https://docs.travis-ci.com/user/common-build-problems/#Build-times-out-because-no-output-was-received). ```yaml install: - travis_wait stack --no-terminal --skip-ghc-check setup - travis_wait stack --no-terminal --skip-ghc-check test --only-snapshot ``` ## Examples - [futurice/fum2github](https://github.com/futurice/fum2github/blob/master/.travis.yml) - [haskell-distributed/cloud-haskell](https://github.com/haskell-distributed/cloud-haskell/blob/master/.travis.yml) - [simonmichael/hledger](https://github.com/simonmichael/hledger/blob/master/.travis.yml) - [fpco/wai-middleware-crowd](https://github.com/fpco/wai-middleware-crowd/blob/master/.travis.yml) - [commercialhaskell/all-cabal-hashes-tool](https://github.com/commercialhaskell/all-cabal-hashes-tool/blob/master/.travis.yml) ## Future enhancements Once Travis whitelists the stack .deb files, we'll be able to simply include stack in the `addons` section, and automatically use the newest version of stack, avoiding that complicated `before_install` section This is being tracked in the [apt-source-whitelist](https://github.com/travis-ci/apt-source-whitelist/pull/7) and [apt-package-whitelist](https://github.com/travis-ci/apt-package-whitelist/issues/379) issue trackers. stack-1.5.1/doc/yaml_configuration.md0000644000000000000000000006171613135652051016005 0ustar0000000000000000# YAML Configuration This page is intended to fully document all configuration options available in the stack.yaml file. Note that this page is likely to be both *incomplete* and sometimes *inaccurate*. If you see such cases, please update the page, and if you're not sure how, open an issue labeled "question". The stack.yaml configuration options break down into [project-specific](#project-specific-config) options in: - `/stack.yaml` and [non-project-specific](#non-project-specific-config) options in: - `/etc/stack/config.yaml` -- for system global non-project default options - `~/.stack/config.yaml` -- for user non-project default options - The project file itself may also contain non-project specific options *Note:* When stack is invoked outside a stack project it will source project specific options from `~/.stack/global-project/stack.yaml`. When stack is invoked inside a stack project, only options from `/stack.yaml` are used, and `~/.stack/global-project/stack.yaml` is ignored. ## Project-specific config Project-specific options are only valid in the `stack.yaml` file local to a project, not in the user or global config files. > Note: We define **project** to mean a directory that contains a `stack.yaml` > file, which specifies how to build a set of packages. We define **package** to > be a package with a `.cabal` file. In your project-specific options, you specify both **which local packages** to build and **which dependencies to use** when building these packages. Unlike the user's local packages, these dependencies aren't built by default. They only get built when needed. Shadowing semantics, described [here](http://docs.haskellstack.org/en/stable/architecture/#shadowing), are applied to your configuration. So, if you add a package to your `packages` list, it will be used even if you're using a snapshot that specifies a particular version. Similarly, `extra-deps` will shadow the version specified in the resolver. ### packages The `packages` section lists all local (project) packages. The term _local package_ should be differentiated from a _dependency package_. A local package is something that you are developing as part of the project. Whereas a dependency package is an external package that your project depends on. In its simplest usage, it will be a list of directories or HTTP(S) URLs to a tarball or a zip. For example: ```yaml packages: - . - dir1/dir2 - https://example.com/foo/bar/baz-0.0.2.tar.gz ``` Each package directory or location specified must have a valid cabal file present. Note that the subdirectories of the directory are not searched for cabal files. Subdirectories will have to be specified as independent items in the list of packages. When the `packages` field is not present, it defaults to looking for a package in the project's root directory: ```yaml packages: - . ``` #### Complex package locations (`location`) More complex package locations can be specified in a key-value format with `location` as a mandatory key. In addition to `location` some optional key-value pairs can be specified to include specific subdirectories or to specify package attributes as descibed later in this section. In its simplest form a `location` key can have a single value in the same way as described above for single value items. Alternativel it can have key-value pairs as subfields to describe a git or mercurial repository location. For example: ```yaml packages: - location: . - location: dir1/dir2 - location: https://example.com/foo/bar/baz-0.0.2.tar.gz - location: http://github.com/yesodweb/wai/archive/2f8a8e1b771829f4a8a77c0111352ce45a14c30f.zip - location: git: git@github.com:commercialhaskell/stack.git commit: 6a86ee32e5b869a877151f74064572225e1a0398 - location: hg: https://example.com/hg/repo commit: da39a3ee5e6b4b0d3255bfef95601890afd80709 ``` Note: it is highly recommended that you only use SHA1 values for a Git or Mercurial commit. Other values may work, but they are not officially supported, and may result in unexpected behavior (namely, stack will not automatically pull to update to new versions). A `location` key can be accompanied by a `subdirs` key to look for cabal files in a list of subdirectories as well in addition to the top level directory. This could be useful for mega-repos like [wai](https://github.com/yesodweb/wai/) or [digestive-functors](https://github.com/jaspervdj/digestive-functors). The `subdirs` key can have multiple nested series items specifying a list of subdirectories. For example: ```yaml packages: - location: . subdirs: - subdir1 - subdir2 - location: git: git@github.com:yesodweb/wai commit: 2f8a8e1b771829f4a8a77c0111352ce45a14c30f subdirs: - auto-update - wai - location: http://github.com/yesodweb/wai/archive/2f8a8e1b771829f4a8a77c0111352ce45a14c30f.zip subdirs: - auto-update - wai ``` If unspecified, `subdirs` defaults to `['.']` (i.e. look only in the top-level directory). Note that if you specify a value of `subdirs`, then `'.'` is _not_ included by default and needs to be explicitly specified if a required package is found in the top-level directory of the repository. #### Local dependency packages (`extra-dep`) A `location` key can be accompanied by an `extra-dep` key. When the `extra-dep` key is set to `true` it indicates that the package should be treated in the same way as a dependency package and not as part of the project. This means the following: * A _dependency package_ is built only if a user package or its dependencies depend on it. Note that a regular _project package_ is built anyway even if no other package depends on it. * Its test suites and benchmarks will not be run. * It will not be directly loaded in ghci when `stack ghci` is run. This is important because if you specify huge dependencies as project packages then ghci will have a nightmare loading everything. This is especially useful when you are tweaking upstream packages or want to use latest versions of the upstream packages which are not yet on Hackage or Stackage. For example: ```yaml packages: - location: . - location: vendor/binary extra-dep: true - location: git: git@github.com:yesodweb/wai commit: 2f8a8e1b771829f4a8a77c0111352ce45a14c30f subdirs: - auto-update - wai extra-dep: true ``` ### extra-deps This is a list of package identifiers for additional packages from upstream to be included. This is usually used to augment an LTS Haskell or Stackage Nightly snapshot with a package that is not present or is at an different version than you wish to use. ```yaml extra-deps: - acme-missiles-0.3 ``` Note that the `extra-dep` attribute in the `packages` section as described in an earlier section is used for non-index local or remote packages while the `extra-deps` section is for packages to be automatically pulled from an index like Hackage. ### resolver Specifies how dependencies are resolved. There are currently four resolver types: * LTS Haskell snapshots, e.g. `resolver: lts-2.14` * Stackage Nightly snapshot, e.g. `resolver: nightly-2015-06-16` * No snapshot, just use packages shipped with the compiler * For GHC this looks like `resolver: ghc-7.10.2` * For GHCJS this looks like `resolver: ghcjs-0.1.0_ghc-7.10.2`. * [Custom snapshot](custom_snapshot.md) Each of these resolvers will also determine what constraints are placed on the compiler version. See the [compiler-check](#compiler-check) option for some additional control over compiler version. ### flags Flags can be set for each package separately, e.g. ```yaml flags: package-name: flag-name: true ``` Flags will only affect packages in your `packages` and `extra-deps` settings. Packages that come from the snapshot global database are not affected. ### image The image settings are used for the creation of container images using `stack image container`, e.g. ```yaml image: containers: - base: "fpco/stack-build" add: static: /data/static ``` `base` is the docker image that will be used to built upon. The `add` lines allow you to add additional directories to your image. You can specify the name of the image using `name` (otherwise it defaults to the same as your project). You can also specify `entrypoints`. By default all your executables are placed in `/usr/local/bin`, but you can specify a list using `executables` to only add some. When you specify `entrypoints`, multiple containers will be built: a project container, and one container for each entrypoint. For example the following configuration: ```yaml image: containers: - name: myproject base: fpco/stack-run add: production/app-backend/conf/: /etc/app-backend entrypoints: - app-backend ``` will build one container tagged `myproject:latest` which contains the project including the `/etc/app-backend` configuration data. Another container tagged `myproject-app-backend:latest` based on the `myproject:latest` will additionally contain the logic for starting the `app-backend` entrypoint. ### user-message A user-message is inserted by `stack init` when it omits packages or adds external dependencies. For example: ```yaml user-message: ! 'Warning: Some packages were found to be incompatible with the resolver and have been left commented out in the packages section. Warning: Specified resolver could not satisfy all dependencies. Some external packages have been added as dependencies. You can suppress this message by removing it from stack.yaml ' ``` This messages is displayed every time the config is loaded by stack and serves as a reminder for the user to review the configuration and make any changes if needed. The user can delete this message if the generated configuration is acceptable. ## Non-project-specific config Non-project config options may go in the global config (`/etc/stack/config.yaml`) or the user config (`~/.stack/config.yaml`). ### docker See [Docker integration](docker_integration.md#configuration). ### nix (since 0.1.10.0) See [Nix integration](nix_integration.md#configuration). ### connection-count Integer indicating how many simultaneous downloads are allowed to happen Default: `8` ### hide-th-loading Strip out the "Loading ..." lines from GHC build output, produced when using Template Haskell Default: `true` ### latest-snapshot-url URL providing a JSON with information on the latest LTS and Nightly snapshots, used for automatic project configuration. Default: `https://www.stackage.org/download/snapshots.json` ### local-bin-path Target directory for `stack install` and `stack build --copy-bins`. Default: `~/.local/bin` ### package-indices ```yaml package-indices: - name: Hackage download-prefix: https://s3.amazonaws.com/hackage.fpcomplete.com/package/ # HTTP location of the package index http: https://s3.amazonaws.com/hackage.fpcomplete.com/00-index.tar.gz # Or, if using Hackage Security below, give the root URL: http: https://s3.amazonaws.com/hackage.fpcomplete.com/ # optional fields, both default to false require-hashes: false # Starting with stack 1.4, we default to using Hackage Security hackage-security: keyids: ["deadbeef", "12345"] # list of all approved keys key-threshold: 3 # number of keys required ``` One thing you should be aware of: if you change the contents of package-version combination by setting a different package index, this *can* have an effect on other projects by installing into your shared snapshot database. Note that older versions of Stack supported Git-based indices. This feature has since been removed. A line such as: ```yaml git: https://github.com/commercialhaskell/all-cabal-hashes.git gpg-verify: false ``` Will now be ignored. ### system-ghc Enables or disables using the GHC available on the PATH. Useful to enable if you want to save the time, bandwidth or storage space needed to setup an isolated GHC. Default is `false` unless the [Docker](docker_integration.md) or [Nix](nix_integration.md) integration is enabled. In a Nix-enabled configuration, stack is incompatible with `system-ghc: false`. ```yaml # Turn on system GHC system-ghc: true ``` ### install-ghc Whether or not to automatically install GHC when necessary. Default is `false`, which means stack will prompt you to run `stack setup` as needed. ### skip-ghc-check Should we skip the check to confirm that your system GHC version (on the PATH) matches what your project expects? Default is `false`. ### require-stack-version Require a version of stack within the specified range ([cabal-style](https://www.haskell.org/cabal/users-guide/developing-packages.html#build-information)) to be used for this project. Example: `require-stack-version: "== 0.1.*"` Default: `"-any"` ### arch/os Set the architecture and operating system for GHC, build directories, etc. Values are those recognized by Cabal, e.g.: arch: i386, x86_64 os: windows, linux You likely only ever want to change the arch value. This can also be set via the command line. ### extra-include-dirs/extra-lib-dirs A list of extra paths to be searched for header files and libraries, respectively. Paths should be absolute ```yaml extra-include-dirs: - /opt/foo/include extra-lib-dirs: - /opt/foo/lib ``` ### with-gcc Specify a path to gcc explicitly, rather than relying on the normal path resolution. ```yaml with-gcc: /usr/local/bin/gcc-5 ``` ### compiler-check (Since 0.1.4) Specifies how the compiler version in the resolver is matched against concrete versions. Valid values: * `match-minor`: make sure that the first three components match, but allow patch-level differences. For example< 7.8.4.1 and 7.8.4.2 would both match 7.8.4. This is useful to allow for custom patch levels of a compiler. This is the default * `match-exact`: the entire version number must match precisely * `newer-minor`: the third component can be increased, e.g. if your resolver is `ghc-7.10.1`, then 7.10.2 will also be allowed. This was the default up through stack 0.1.3 ### compiler (Since 0.1.7) Overrides the compiler version in the resolver. Note that the `compiler-check` flag also applies to the version numbers. This uses the same syntax as compiler resolvers like `ghc-7.10.2` or `ghcjs-0.1.0.20150924_ghc-7.10.2` (version used for the 'old-base' version of GHCJS). While it's useful to override the compiler for a variety of reasons, the main usecase is to use GHCJS with a stackage snapshot, like this: ```yaml resolver: lts-3.10 compiler: ghcjs-0.1.0.20150924_ghc-7.10.2 compiler-check: match-exact ``` ### ghc-options (Since 0.1.4) Allows specifying per-package and global GHC options: ```yaml ghc-options: # All packages "*": -Wall some-package: -DSOME_CPP_FLAG ``` Caveat emptor: setting options like this will affect your snapshot packages, which can lead to unpredictable behavior versus official Stackage snapshots. This is in contrast to the `ghc-options` command line flag, which will only affect the packages specified by the [`apply-ghc-options` option](yaml_configuration.md#apply-ghc-options). ### apply-ghc-options (Since 0.1.6) Which packages do ghc-options on the command line get applied to? Before 0.1.6, the default value was `targets` ```yaml apply-ghc-options: locals # all local packages, the default # apply-ghc-options: targets # all local packages that are targets # apply-ghc-options: everything # applied even to snapshot and extra-deps ``` Note that `everything` is a slightly dangerous value, as it can break invariants about your snapshot database. ### rebuild-ghc-options (Since 0.1.6) Should we rebuild a package when its GHC options change? Before 0.1.6, this was a non-configurable true. However, in most cases, the flag is used to affect optimization levels and warning behavior, for which GHC itself doesn't actually recompile the modules anyway. Therefore, the new behavior is to not recompile on an options change, but this behavior can be changed back with the following: ```yaml rebuild-ghc-options: true ``` ### ghc-variant (Since 0.1.5) Specify a variant binary distribution of GHC to use. Known values: * `standard`: This is the default, uses the standard GHC binary distribution * `integersimple`: Use a GHC bindist that uses [integer-simple instead of GMP](https://ghc.haskell.org/trac/ghc/wiki/ReplacingGMPNotes) * any other value: Use a custom GHC bindist. You should specify [setup-info](#setup-info) so `stack setup` knows where to download it, or pass the `stack setup --ghc-bindist` argument on the command-line This option is incompatible with `system-ghc: true`. ### ghc-build (Since 1.3.0) Specify a specialized architecture bindist to use. Normally this is determined automatically, but you can override the autodetected value here. Possible arguments include `standard`, `gmp4`, `tinfo6`, and `nopie`. ### setup-info (Since 0.1.5) Allows overriding from where tools like GHC and msys2 (on Windows) are downloaded. Most useful for specifying locations of custom GHC binary distributions (for use with the [ghc-variant](#ghc-variant) option): ```yaml setup-info: ghc: windows32-custom-foo: 7.10.2: url: "https://example.com/ghc-7.10.2-i386-unknown-mingw32-foo.tar.xz" ``` Or without using `ghc-variant`: ```yaml setup-info: "https://raw.githubusercontent.com/fpco/stackage-content/master/stack/stack-setup-2.yaml" ``` `url` may be either URL or (since 1.2.0) absolute file path. ### pvp-bounds (Since 0.1.5) When using the `sdist` and `upload` commands, this setting determines whether the cabal file's dependencies should be modified to reflect PVP lower and upper bounds. Values are `none` (unchanged), `upper` (add upper bounds), `lower` (add lower bounds), and both (and upper and lower bounds). The algorithm it follows is: * If an upper or lower bound already exists on a dependency, it's left alone * When adding a lower bound, we look at the current version specified by stack.yaml, and set it as the lower bound (e.g., `foo >= 1.2.3`) * When adding an upper bound, we require less than the next major version (e.g., `foo < 1.3`) ```yaml pvp-bounds: none ``` For more information, see [the announcement blog post](https://www.fpcomplete.com/blog/2015/09/stack-pvp). __NOTE__ Since Stack 1.5.0, each of the values listed above supports adding `-revision` to the end of each value, e.g. `pvp-bounds: both-revision`. This means that, when uploading to Hackage, Stack will first upload your tarball with an unmodified `.cabal` file, and then upload a cabal file revision with the PVP bounds added. This can be useful—especially combined with the [Stackage no-revisions feature](http://www.snoyman.com/blog/2017/04/stackages-no-revisions-field)—as a method to ensure PVP compliance without having to proactively fix bounds issues for Stackage maintenance. ### modify-code-page (Since 0.1.6) Modify the code page for UTF-8 output when running on Windows. Default behavior is to modify. ```yaml modify-code-page: false ``` ### explicit-setup-deps (Since 0.1.6) Decide whether a custom `Setup.hs` script should be run with an explicit list of dependencies, based on the dependencies of the package itself. It associates the name of a local package with a boolean. When it's `true`, the `Setup.hs` script is built with an explicit list of packages. When it's `false` (default), the `Setup.hs` script is built without access to the local DB, but can access any package in the snapshot / global DB. Note that in the future, this will be unnecessary, once Cabal provides full support for explicit Setup.hs dependencies. ```yaml explicit-setup-deps: "*": true # change the default entropy: false # override the new default for one package ``` NOTE: since 1.4.0, Stack has support for Cabal's `custom-setup` block (introduced in Cabal 1.24). If a `custom-setup` block is provided in a `.cabal` file, it will override the setting of `explicit-setup-deps`, and instead rely on the stated dependencies. ### allow-newer (Since 0.1.7) Ignore version bounds in .cabal files. Default is false. ```yaml allow-newer: true ``` Note that this also ignores lower bounds. The name "allow-newer" is chosen to match the commonly used cabal option. ### allow-different-user (Since 1.0.1) Allow users other than the owner of the stack root directory (typically `~/.stack`) to use the stack installation. The default is `false`. POSIX systems only. ```yaml allow-different-user: true ``` The intention of this option is to prevent file permission problems, for example as the result of a `stack` command executed under `sudo`. The option is automatically enabled when `stack` is re-spawned in a Docker process. ### build (Since 1.1.0) Allows setting build options which are usually specified on the CLI. Here are the settings with their defaults: ```yaml build: library-profiling: false executable-profiling: false copy-bins: false prefetch: false keep-going: false # NOTE: global usage of haddock can cause build failures when documentation is # incorrectly formatted. This could also affect scripts which use stack. haddock: false haddock-arguments: haddock-args: [] # Additional arguments passed to haddock, --haddock-arguments # haddock-args: # - "--css=/home/user/my-css" open-haddocks: false # --open haddock-deps: false # if unspecified, defaults to true if haddock is set haddock-internal: false # These are inadvisable to use in your global configuration, as they make the # stack build CLI behave quite differently. test: false test-arguments: rerun-tests: true # Rerun successful tests additional-args: [] # --test-arguments # additional-args: # - "--fail-fast" coverage: false no-run-tests: false bench: false benchmark-opts: benchmark-arguments: "" # benchmark-arguments: "--csv bench.csv" no-run-benchmarks: false force-dirty: false reconfigure: false cabal-verbose: false split-objs: false ``` The meanings of these settings correspond directly with the CLI flags of the same name. See the [build command docs](build_command.md) and the [users guide](GUIDE.md#the-build-command) for more info. ### dump-logs (Since 1.3.0) Control which log output from local non-dependency packages to print to the console. By default, Stack will only do this when building a single target package or if the log contains warnings, to avoid generating unnecessarily verbose output. ```yaml dump-logs: none # don't dump logs even if they contain warnings dump-logs: warning # default: dump logs that contain warnings dump-logs: all # dump all logs for local non-dependency packages ``` ### templates Templates used with `stack new` have a number of parameters that affect the generated code. These can be set for all new projects you create. The result of them can be observed in the generated LICENSE and cabal files. The 5 parameters are: `author-email`, `author-name`, `category`, `copyright` and `github-username`. * _author-email_ - sets the `maintainer` property in cabal * _author-name_ - sets the `author` property in cabal and the name used in LICENSE * _category_ - sets the `category` property in cabal. This is used in Hackage. For examples of categories see [Packages by category](https://hackage.haskell.org/packages/). It makes sense for `category` to be set on a per project basis because it is uncommon for all projects a user creates to belong to the same category. The category can be set per project by passing `-p "category:value"` to the `stack new` command. * _copyright_ - sets the `copyright` property in cabal. It is typically the name of the holder of the copyright on the package and the year(s) from which copyright is claimed. For example: `Copyright: (c) 2006-2007 Joe Bloggs` * _github-username_ - used to generate `homepage` and `source-repository` in cabal. For instance `github-username: myusername` and `stack new my-project new-template` would result: ```yaml homepage: http://github.com/myusername/my-project#readme source-repository head type: git location: https://github.com/myusername/my-project ``` These properties can be set in `config.yaml` as follows: ```yaml templates: params: author-name: Your Name author-email: youremail@example.com category: Your Projects Category copyright: 'Copyright: (c) 2017 Your Name' github-username: yourusername ``` Additionally, `stack new` can automatically initialize source control repositories in the directories it creates. Source control tools can be specified with the `scm-init` option. At the moment, only `git` is supported. ```yaml templates: scm-init: git ``` ### save-hackage-creds Controls whether, when using `stack upload`, the user's Hackage username and password are stored in a local file. Default: true. ```yaml save-hackage-creds: true ``` Since 1.5.0 # urls Customize the URLs where `stack` looks for snapshot build plans. The default configuration is ```yaml urls: latest-snapshot: https://www.stackage.org/download/snapshots.json lts-build-plans: https://raw.githubusercontent.com/fpco/lts-haskell/master/ nightly-build-plans: https://raw.githubusercontent.com/fpco/stackage-nightly/master/ ``` **Note:** The `latest-snapshot-url` field has been deprecated in favor of `latest-snapshot` and will be removed in a future version of `stack`. stack-1.5.1/src/setup-shim/StackSetupShim.hs0000644000000000000000000000247413063526313017152 0ustar0000000000000000module StackSetupShim where import Main import Distribution.PackageDescription (PackageDescription, emptyHookedBuildInfo) import Distribution.Simple import Distribution.Simple.Build import Distribution.Simple.Setup (ReplFlags, fromFlag, replDistPref, replVerbosity) import Distribution.Simple.LocalBuildInfo (LocalBuildInfo) import System.Environment (getArgs) mainOverride :: IO () mainOverride = do args <- getArgs if "repl" `elem` args && "stack-initial-build-steps" `elem` args then do defaultMainWithHooks simpleUserHooks { preRepl = \_ _ -> return emptyHookedBuildInfo , replHook = stackReplHook , postRepl = \_ _ _ _ -> return () } else main stackReplHook :: PackageDescription -> LocalBuildInfo -> UserHooks -> ReplFlags -> [String] -> IO () stackReplHook pkg_descr lbi hooks flags args = do let distPref = fromFlag (replDistPref flags) verbosity = fromFlag (replVerbosity flags) case args of ("stack-initial-build-steps":rest) | null rest -> initialBuildSteps distPref pkg_descr lbi verbosity | otherwise -> fail "Misuse of running Setup.hs with stack-initial-build-steps, expected no arguments" _ -> replHook simpleUserHooks pkg_descr lbi hooks flags args stack-1.5.1/test/package-dump/ghc-7.8.txt0000644000000000000000000020004412546477354016163 0ustar0000000000000000name: haskell2010 version: 1.1.2.0 id: haskell2010-1.1.2.0-05c8dd51009e08c6371c82972d40f55a license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: http://www.haskell.org/onlinereport/haskell2010/ package-url: synopsis: Compatibility with Haskell 2010 description: This package provides exactly the library modules defined by the . category: Haskell2010, Prelude author: exposed: False exposed-modules: Prelude Control.Monad Data.Array Data.Bits Data.Char Data.Complex Data.Int Data.Ix Data.List Data.Maybe Data.Ratio Data.Word Foreign Foreign.C Foreign.C.Error Foreign.C.String Foreign.C.Types Foreign.ForeignPtr Foreign.Marshal Foreign.Marshal.Alloc Foreign.Marshal.Array Foreign.Marshal.Error Foreign.Marshal.Utils Foreign.Ptr Foreign.StablePtr Foreign.Storable Numeric System.Environment System.Exit System.IO System.IO.Error hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/haskell2010-1.1.2.0 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/haskell2010-1.1.2.0 hs-libraries: HShaskell2010-1.1.2.0 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: array-0.5.0.0-470385a50d2b78598af85cfe9d988e1b base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 ghc-prim-0.3.1.0-a24f9c14c632d75b683d0f93283aea37 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/haskell2010-1.1.2.0/haskell2010.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/haskell2010-1.1.2.0 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: haskell98 version: 2.0.0.3 id: haskell98-2.0.0.3-045e8778b656db76e2c729405eee707b license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: http://www.haskell.org/definition/ package-url: synopsis: Compatibility with Haskell 98 description: This package provides compatibility with the modules of Haskell 98 and the FFI addendum, by means of wrappers around modules from the base package (which in many cases have additional features). However "Prelude", "Numeric" and "Foreign" are provided directly by the @base@ package. category: Haskell98, Prelude author: exposed: False exposed-modules: Prelude Array CPUTime Char Complex Directory IO Ix List Locale Maybe Monad Numeric Random Ratio System Time Bits CError CForeign CString CTypes ForeignPtr Int MarshalAlloc MarshalArray MarshalError MarshalUtils Ptr StablePtr Storable Word hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/haskell98-2.0.0.3 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/haskell98-2.0.0.3 hs-libraries: HShaskell98-2.0.0.3 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: array-0.5.0.0-470385a50d2b78598af85cfe9d988e1b base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 directory-1.2.1.0-07cd1f59e3c6cac5e3e180019c59a115 old-locale-1.0.0.6-50b0125c49f76af85dc7aa22975cdc34 old-time-1.1.0.2-e3f776e97c1a6ff1770b04943a7ef7c6 process-1.2.0.0-06c3215a79834ce4886ae686a0f81122 time-1.4.2-9b3076800c33f8382c38628f35717951 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/haskell98-2.0.0.3/haskell98.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/haskell98-2.0.0.3 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: old-time version: 1.1.0.2 id: old-time-1.1.0.2-e3f776e97c1a6ff1770b04943a7ef7c6 license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: package-url: synopsis: Time library description: This package provides the old time library. . For new projects, the newer is recommended. category: System author: exposed: True exposed-modules: System.Time hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/old-time-1.1.0.2 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/old-time-1.1.0.2 hs-libraries: HSold-time-1.1.0.2 extra-libraries: extra-ghci-libraries: include-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/old-time-1.1.0.2/include includes: HsTime.h depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 old-locale-1.0.0.6-50b0125c49f76af85dc7aa22975cdc34 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/old-time-1.1.0.2/old-time.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/old-time-1.1.0.2 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: ghc version: 7.8.4 id: ghc-7.8.4-6c4818bc66adb23509058069f781d99a license: BSD3 copyright: maintainer: glasgow-haskell-users@haskell.org stability: homepage: http://www.haskell.org/ghc/ package-url: synopsis: The GHC API description: GHC's functionality can be useful for more things than just compiling Haskell programs. Important use cases are programs that analyse (and perhaps transform) Haskell code. Others include loading Haskell code dynamically in a GHCi-like manner. For this reason, a lot of GHC's functionality is made available through this package. category: Development author: The GHC Team exposed: False exposed-modules: Avail BasicTypes ConLike DataCon PatSyn Demand Exception GhcMonad Hooks Id IdInfo Literal Llvm Llvm.AbsSyn Llvm.MetaData Llvm.PpLlvm Llvm.Types LlvmCodeGen LlvmCodeGen.Base LlvmCodeGen.CodeGen LlvmCodeGen.Data LlvmCodeGen.Ppr LlvmCodeGen.Regs LlvmMangler MkId Module Name NameEnv NameSet OccName RdrName SrcLoc UniqSupply Unique Var VarEnv VarSet BlockId CLabel Cmm CmmBuildInfoTables CmmPipeline CmmCallConv CmmCommonBlockElim CmmContFlowOpt CmmExpr CmmInfo CmmLex CmmLint CmmLive CmmMachOp CmmNode CmmOpt CmmParse CmmProcPoint CmmRewriteAssignments CmmSink CmmType CmmUtils CmmLayoutStack MkGraph PprBase PprC PprCmm PprCmmDecl PprCmmExpr Bitmap CodeGen.Platform CodeGen.Platform.ARM CodeGen.Platform.NoRegs CodeGen.Platform.PPC CodeGen.Platform.PPC_Darwin CodeGen.Platform.SPARC CodeGen.Platform.X86 CodeGen.Platform.X86_64 CgUtils StgCmm StgCmmBind StgCmmClosure StgCmmCon StgCmmEnv StgCmmExpr StgCmmForeign StgCmmHeap StgCmmHpc StgCmmArgRep StgCmmLayout StgCmmMonad StgCmmPrim StgCmmProf StgCmmTicky StgCmmUtils StgCmmExtCode SMRep CoreArity CoreFVs CoreLint CorePrep CoreSubst CoreSyn TrieMap CoreTidy CoreUnfold CoreUtils ExternalCore MkCore MkExternalCore PprCore PprExternalCore Check Coverage Desugar DsArrows DsBinds DsCCall DsExpr DsForeign DsGRHSs DsListComp DsMonad DsUtils Match MatchCon MatchLit HsBinds HsDecls HsDoc HsExpr HsImpExp HsLit HsPat HsSyn HsTypes HsUtils BinIface BuildTyCl IfaceEnv IfaceSyn IfaceType LoadIface MkIface TcIface FlagChecker Annotations BreakArray CmdLineParser CodeOutput Config Constants DriverMkDepend DriverPhases PipelineMonad DriverPipeline DynFlags ErrUtils Finder GHC GhcMake GhcPlugins DynamicLoading HeaderInfo HscMain HscStats HscTypes InteractiveEval InteractiveEvalTypes PackageConfig Packages PlatformConstants PprTyThing StaticFlags SysTools TidyPgm Ctype HaddockUtils LexCore Lexer OptCoercion Parser ParserCore ParserCoreUtils RdrHsSyn ForeignCall PrelInfo PrelNames PrelRules PrimOp TysPrim TysWiredIn CostCentre ProfInit SCCfinal RnBinds RnEnv RnExpr RnHsDoc RnNames RnPat RnSource RnSplice RnTypes CoreMonad CSE FloatIn FloatOut LiberateCase OccurAnal SAT SetLevels SimplCore SimplEnv SimplMonad SimplUtils Simplify SimplStg StgStats UnariseStg Rules SpecConstr Specialise CoreToStg StgLint StgSyn DmdAnal WorkWrap WwLib FamInst Inst TcAnnotations TcArrows TcBinds TcClassDcl TcDefaults TcDeriv TcEnv TcExpr TcForeign TcGenDeriv TcGenGenerics TcHsSyn TcHsType TcInstDcls TcMType TcValidity TcMatches TcPat TcPatSyn TcRnDriver TcRnMonad TcRnTypes TcRules TcSimplify TcErrors TcTyClsDecls TcTyDecls TcType TcEvidence TcUnify TcInteract TcCanonical TcSMonad TcTypeNats TcSplice Class Coercion FamInstEnv FunDeps InstEnv TyCon CoAxiom Kind Type TypeRep Unify Bag Binary BooleanFormula BufWrite Digraph Encoding FastBool FastFunctions FastMutInt FastString FastTypes Fingerprint FiniteMap GraphBase GraphColor GraphOps GraphPpr IOEnv ListSetOps Maybes MonadUtils OrdList Outputable Pair Panic Pretty Serialized State Stream StringBuffer UniqFM UniqSet Util ExtsCompat46 Vectorise.Builtins.Base Vectorise.Builtins.Initialise Vectorise.Builtins Vectorise.Monad.Base Vectorise.Monad.Naming Vectorise.Monad.Local Vectorise.Monad.Global Vectorise.Monad.InstEnv Vectorise.Monad Vectorise.Utils.Base Vectorise.Utils.Closure Vectorise.Utils.Hoisting Vectorise.Utils.PADict Vectorise.Utils.Poly Vectorise.Utils Vectorise.Generic.Description Vectorise.Generic.PAMethods Vectorise.Generic.PADict Vectorise.Generic.PData Vectorise.Type.Env Vectorise.Type.Type Vectorise.Type.TyConDecl Vectorise.Type.Classify Vectorise.Convert Vectorise.Vect Vectorise.Var Vectorise.Env Vectorise.Exp Vectorise Hoopl.Dataflow Hoopl AsmCodeGen TargetReg NCGMonad Instruction Size Reg RegClass PIC Platform CPrim X86.Regs X86.RegInfo X86.Instr X86.Cond X86.Ppr X86.CodeGen PPC.Regs PPC.RegInfo PPC.Instr PPC.Cond PPC.Ppr PPC.CodeGen SPARC.Base SPARC.Regs SPARC.Imm SPARC.AddrMode SPARC.Cond SPARC.Instr SPARC.Stack SPARC.ShortcutJump SPARC.Ppr SPARC.CodeGen SPARC.CodeGen.Amode SPARC.CodeGen.Base SPARC.CodeGen.CondCode SPARC.CodeGen.Gen32 SPARC.CodeGen.Gen64 SPARC.CodeGen.Sanity SPARC.CodeGen.Expand RegAlloc.Liveness RegAlloc.Graph.Main RegAlloc.Graph.Stats RegAlloc.Graph.ArchBase RegAlloc.Graph.ArchX86 RegAlloc.Graph.Coalesce RegAlloc.Graph.Spill RegAlloc.Graph.SpillClean RegAlloc.Graph.SpillCost RegAlloc.Graph.TrivColorable RegAlloc.Linear.Main RegAlloc.Linear.JoinToTargets RegAlloc.Linear.State RegAlloc.Linear.Stats RegAlloc.Linear.FreeRegs RegAlloc.Linear.StackMap RegAlloc.Linear.Base RegAlloc.Linear.X86.FreeRegs RegAlloc.Linear.X86_64.FreeRegs RegAlloc.Linear.PPC.FreeRegs RegAlloc.Linear.SPARC.FreeRegs DsMeta Convert ByteCodeAsm ByteCodeGen ByteCodeInstr ByteCodeItbls ByteCodeLink Debugger LibFFI Linker ObjLink RtClosureInspect DebuggerUtils hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/ghc-7.8.4 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/ghc-7.8.4 hs-libraries: HSghc-7.8.4 extra-libraries: extra-ghci-libraries: include-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/ghc-7.8.4/include includes: depends: Cabal-1.18.1.5-6478013104bde01737bfd67d34bbee0a array-0.5.0.0-470385a50d2b78598af85cfe9d988e1b base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 bin-package-db-0.0.0.0-0f3da03684207f2dc4dce793df1db62e bytestring-0.10.4.0-d6f1d17d717e8652498cab8269a0acd5 containers-0.5.5.1-d4bd887fb97aa3a46cbadc13709b7653 directory-1.2.1.0-07cd1f59e3c6cac5e3e180019c59a115 filepath-1.3.0.2-25a474a9272ae6260626ce0d70ad1cab hoopl-3.10.0.1-267659e4b5b51c3d2e02f2a6d6f46936 hpc-0.6.0.1-cca17f12dab542e09c423a74a4590c5d process-1.2.0.0-06c3215a79834ce4886ae686a0f81122 template-haskell-2.9.0.0-6d27c2b362b15abb1822f2f34b9ae7f9 time-1.4.2-9b3076800c33f8382c38628f35717951 transformers-0.3.0.0-6458c21515cab7c1cf21e53141557a1c unix-2.7.0.1-f8658ba9ec1c4fba8a371a8e0f42ec6c hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/ghc-7.8.4/ghc.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/ghc-7.8.4 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: haskeline version: 0.7.1.2 id: haskeline-0.7.1.2-2dd2f2fb537352f5367ae77fe47ab211 license: BSD3 copyright: (c) Judah Jacobson maintainer: Judah Jacobson stability: Experimental homepage: http://trac.haskell.org/haskeline package-url: synopsis: A command-line interface for user input, written in Haskell. description: Haskeline provides a user interface for line input in command-line programs. This library is similar in purpose to readline, but since it is written in Haskell it is (hopefully) more easily used in other Haskell programs. . Haskeline runs both on POSIX-compatible systems and on Windows. category: User Interfaces author: Judah Jacobson exposed: True exposed-modules: System.Console.Haskeline System.Console.Haskeline.Completion System.Console.Haskeline.MonadException System.Console.Haskeline.History System.Console.Haskeline.IO hidden-modules: System.Console.Haskeline.Backend System.Console.Haskeline.Backend.WCWidth System.Console.Haskeline.Command System.Console.Haskeline.Command.Completion System.Console.Haskeline.Command.History System.Console.Haskeline.Command.KillRing System.Console.Haskeline.Directory System.Console.Haskeline.Emacs System.Console.Haskeline.InputT System.Console.Haskeline.Key System.Console.Haskeline.LineState System.Console.Haskeline.Monads System.Console.Haskeline.Prefs System.Console.Haskeline.RunCommand System.Console.Haskeline.Term System.Console.Haskeline.Command.Undo System.Console.Haskeline.Vi System.Console.Haskeline.Recover System.Console.Haskeline.Backend.Posix System.Console.Haskeline.Backend.Posix.Encoder System.Console.Haskeline.Backend.DumbTerm System.Console.Haskeline.Backend.Terminfo trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/haskeline-0.7.1.2 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/haskeline-0.7.1.2 hs-libraries: HShaskeline-0.7.1.2 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 bytestring-0.10.4.0-d6f1d17d717e8652498cab8269a0acd5 containers-0.5.5.1-d4bd887fb97aa3a46cbadc13709b7653 directory-1.2.1.0-07cd1f59e3c6cac5e3e180019c59a115 filepath-1.3.0.2-25a474a9272ae6260626ce0d70ad1cab terminfo-0.4.0.0-c1d02a7210b0d1bc250d87463b38b8d1 transformers-0.3.0.0-6458c21515cab7c1cf21e53141557a1c unix-2.7.0.1-f8658ba9ec1c4fba8a371a8e0f42ec6c hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/haskeline-0.7.1.2/haskeline.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/haskeline-0.7.1.2 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: terminfo version: 0.4.0.0 id: terminfo-0.4.0.0-c1d02a7210b0d1bc250d87463b38b8d1 license: BSD3 copyright: (c) Judah Jacobson maintainer: Judah Jacobson stability: Stable homepage: https://github.com/judah/terminfo package-url: synopsis: Haskell bindings to the terminfo library. description: This library provides an interface to the terminfo database (via bindings to the curses library). allows POSIX systems to interact with a variety of terminals using a standard set of capabilities. category: User Interfaces author: Judah Jacobson exposed: True exposed-modules: System.Console.Terminfo System.Console.Terminfo.Base System.Console.Terminfo.Cursor System.Console.Terminfo.Color System.Console.Terminfo.Edit System.Console.Terminfo.Effects System.Console.Terminfo.Keys hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/terminfo-0.4.0.0 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/terminfo-0.4.0.0 hs-libraries: HSterminfo-0.4.0.0 extra-libraries: tinfo extra-ghci-libraries: include-dirs: includes: ncurses.h term.h depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/terminfo-0.4.0.0/terminfo.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/terminfo-0.4.0.0 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: xhtml version: 3000.2.1 id: xhtml-3000.2.1-6a3ed472b07e58fe29db22a5bc2bdb06 license: BSD3 copyright: Bjorn Bringert 2004-2006, Andy Gill and the Oregon Graduate Institute of Science and Technology, 1999-2001 maintainer: Chris Dornan stability: Stable homepage: https://github.com/haskell/xhtml package-url: synopsis: An XHTML combinator library description: This package provides combinators for producing XHTML 1.0, including the Strict, Transitional and Frameset variants. category: Web, XML, Pretty Printer author: Bjorn Bringert exposed: True exposed-modules: Text.XHtml Text.XHtml.Frameset Text.XHtml.Strict Text.XHtml.Transitional Text.XHtml.Debug Text.XHtml.Table hidden-modules: Text.XHtml.Strict.Attributes Text.XHtml.Strict.Elements Text.XHtml.Frameset.Attributes Text.XHtml.Frameset.Elements Text.XHtml.Transitional.Attributes Text.XHtml.Transitional.Elements Text.XHtml.BlockTable Text.XHtml.Extras Text.XHtml.Internals trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/xhtml-3000.2.1 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/xhtml-3000.2.1 hs-libraries: HSxhtml-3000.2.1 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/xhtml-3000.2.1/xhtml.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/xhtml-3000.2.1 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: transformers version: 0.3.0.0 id: transformers-0.3.0.0-6458c21515cab7c1cf21e53141557a1c license: BSD3 copyright: maintainer: Ross Paterson stability: homepage: package-url: synopsis: Concrete functor and monad transformers description: A portable library of functor and monad transformers, inspired by the paper \"Functional Programming with Overloading and Higher-Order Polymorphism\", by Mark P Jones, in /Advanced School of Functional Programming/, 1995 (). . This package contains: . * the monad transformer class (in "Control.Monad.Trans.Class") . * concrete functor and monad transformers, each with associated operations and functions to lift operations associated with other transformers. . It can be used on its own in portable Haskell code, or with the monad classes in the @mtl@ or @monads-tf@ packages, which automatically lift operations introduced by monad transformers through other transformers. category: Control author: Andy Gill, Ross Paterson exposed: True exposed-modules: Control.Applicative.Backwards Control.Applicative.Lift Control.Monad.IO.Class Control.Monad.Trans.Class Control.Monad.Trans.Cont Control.Monad.Trans.Error Control.Monad.Trans.Identity Control.Monad.Trans.List Control.Monad.Trans.Maybe Control.Monad.Trans.Reader Control.Monad.Trans.RWS Control.Monad.Trans.RWS.Lazy Control.Monad.Trans.RWS.Strict Control.Monad.Trans.State Control.Monad.Trans.State.Lazy Control.Monad.Trans.State.Strict Control.Monad.Trans.Writer Control.Monad.Trans.Writer.Lazy Control.Monad.Trans.Writer.Strict Data.Functor.Compose Data.Functor.Constant Data.Functor.Identity Data.Functor.Product Data.Functor.Reverse hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/transformers-0.3.0.0 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/transformers-0.3.0.0 hs-libraries: HStransformers-0.3.0.0 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/transformers-0.3.0.0/transformers.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/transformers-0.3.0.0 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: hoopl version: 3.10.0.1 id: hoopl-3.10.0.1-267659e4b5b51c3d2e02f2a6d6f46936 license: BSD3 copyright: maintainer: nr@cs.tufts.edu stability: homepage: http://ghc.cs.tufts.edu/hoopl/ package-url: synopsis: A library to support dataflow analysis and optimization description: Higher-order optimization library . See /Norman Ramsey, Joao Dias, and Simon Peyton Jones./ /(2010)/ for more details. category: Compilers/Interpreters author: Norman Ramsey, Joao Dias, Simon Marlow and Simon Peyton Jones exposed: True exposed-modules: Compiler.Hoopl Compiler.Hoopl.Internals Compiler.Hoopl.Wrappers Compiler.Hoopl.Passes.Dominator Compiler.Hoopl.Passes.DList hidden-modules: Compiler.Hoopl.Checkpoint Compiler.Hoopl.Collections Compiler.Hoopl.Combinators Compiler.Hoopl.Dataflow Compiler.Hoopl.Debug Compiler.Hoopl.Block Compiler.Hoopl.Graph Compiler.Hoopl.Label Compiler.Hoopl.MkGraph Compiler.Hoopl.Fuel Compiler.Hoopl.Pointed Compiler.Hoopl.Shape Compiler.Hoopl.Show Compiler.Hoopl.Unique Compiler.Hoopl.XUtil trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/hoopl-3.10.0.1 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/hoopl-3.10.0.1 hs-libraries: HShoopl-3.10.0.1 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 containers-0.5.5.1-d4bd887fb97aa3a46cbadc13709b7653 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/hoopl-3.10.0.1/hoopl.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/hoopl-3.10.0.1 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: bin-package-db version: 0.0.0.0 id: bin-package-db-0.0.0.0-0f3da03684207f2dc4dce793df1db62e license: BSD3 copyright: maintainer: ghc-devs@haskell.org stability: homepage: package-url: synopsis: A binary format for the package database description: category: author: exposed: True exposed-modules: Distribution.InstalledPackageInfo.Binary hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/bin-package-db-0.0.0.0 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/bin-package-db-0.0.0.0 hs-libraries: HSbin-package-db-0.0.0.0 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: Cabal-1.18.1.5-6478013104bde01737bfd67d34bbee0a base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 binary-0.7.1.0-f867dbbb69966feb9f5c4ef7695a70a5 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/bin-package-db-0.0.0.0/bin-package-db.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/bin-package-db-0.0.0.0 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: binary version: 0.7.1.0 id: binary-0.7.1.0-f867dbbb69966feb9f5c4ef7695a70a5 license: BSD3 copyright: maintainer: Lennart Kolmodin, Don Stewart stability: provisional homepage: https://github.com/kolmodin/binary package-url: synopsis: Binary serialisation for Haskell values using lazy ByteStrings description: Efficient, pure binary serialisation using lazy ByteStrings. Haskell values may be encoded to and from binary formats, written to disk as binary, or sent over the network. The format used can be automatically generated, or you can choose to implement a custom format if needed. Serialisation speeds of over 1 G\/sec have been observed, so this library should be suitable for high performance scenarios. category: Data, Parsing author: Lennart Kolmodin exposed: True exposed-modules: Data.Binary Data.Binary.Put Data.Binary.Get Data.Binary.Get.Internal Data.Binary.Builder Data.Binary.Builder.Internal hidden-modules: Data.Binary.Builder.Base Data.Binary.Class Data.Binary.Generic trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/binary-0.7.1.0 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/binary-0.7.1.0 hs-libraries: HSbinary-0.7.1.0 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: array-0.5.0.0-470385a50d2b78598af85cfe9d988e1b base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 bytestring-0.10.4.0-d6f1d17d717e8652498cab8269a0acd5 containers-0.5.5.1-d4bd887fb97aa3a46cbadc13709b7653 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/binary-0.7.1.0/binary.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/binary-0.7.1.0 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: Cabal version: 1.18.1.5 id: Cabal-1.18.1.5-6478013104bde01737bfd67d34bbee0a license: BSD3 copyright: 2003-2006, Isaac Jones 2005-2011, Duncan Coutts maintainer: cabal-devel@haskell.org stability: homepage: http://www.haskell.org/cabal/ package-url: synopsis: A framework for packaging Haskell software description: The Haskell Common Architecture for Building Applications and Libraries: a framework defining a common interface for authors to more easily build their Haskell applications in a portable way. . The Haskell Cabal is part of a larger infrastructure for distributing, organizing, and cataloging Haskell libraries and tools. category: Distribution author: Isaac Jones Duncan Coutts exposed: True exposed-modules: Distribution.Compat.Environment Distribution.Compat.Exception Distribution.Compat.ReadP Distribution.Compiler Distribution.InstalledPackageInfo Distribution.License Distribution.Make Distribution.ModuleName Distribution.Package Distribution.PackageDescription Distribution.PackageDescription.Check Distribution.PackageDescription.Configuration Distribution.PackageDescription.Parse Distribution.PackageDescription.PrettyPrint Distribution.PackageDescription.Utils Distribution.ParseUtils Distribution.ReadE Distribution.Simple Distribution.Simple.Bench Distribution.Simple.Build Distribution.Simple.Build.Macros Distribution.Simple.Build.PathsModule Distribution.Simple.BuildPaths Distribution.Simple.BuildTarget Distribution.Simple.CCompiler Distribution.Simple.Command Distribution.Simple.Compiler Distribution.Simple.Configure Distribution.Simple.GHC Distribution.Simple.Haddock Distribution.Simple.Hpc Distribution.Simple.Hugs Distribution.Simple.Install Distribution.Simple.InstallDirs Distribution.Simple.JHC Distribution.Simple.LHC Distribution.Simple.LocalBuildInfo Distribution.Simple.NHC Distribution.Simple.PackageIndex Distribution.Simple.PreProcess Distribution.Simple.PreProcess.Unlit Distribution.Simple.Program Distribution.Simple.Program.Ar Distribution.Simple.Program.Builtin Distribution.Simple.Program.Db Distribution.Simple.Program.Find Distribution.Simple.Program.GHC Distribution.Simple.Program.HcPkg Distribution.Simple.Program.Hpc Distribution.Simple.Program.Ld Distribution.Simple.Program.Run Distribution.Simple.Program.Script Distribution.Simple.Program.Types Distribution.Simple.Register Distribution.Simple.Setup Distribution.Simple.SrcDist Distribution.Simple.Test Distribution.Simple.UHC Distribution.Simple.UserHooks Distribution.Simple.Utils Distribution.System Distribution.TestSuite Distribution.Text Distribution.Verbosity Distribution.Version Language.Haskell.Extension hidden-modules: Distribution.Compat.CopyFile Distribution.Compat.TempFile Distribution.GetOpt Distribution.Simple.GHC.IPI641 Distribution.Simple.GHC.IPI642 Paths_Cabal trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/Cabal-1.18.1.5 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/Cabal-1.18.1.5 hs-libraries: HSCabal-1.18.1.5 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: array-0.5.0.0-470385a50d2b78598af85cfe9d988e1b base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 bytestring-0.10.4.0-d6f1d17d717e8652498cab8269a0acd5 containers-0.5.5.1-d4bd887fb97aa3a46cbadc13709b7653 deepseq-1.3.0.2-63a1ab91b7017a28bb5d04cb1b5d2d02 directory-1.2.1.0-07cd1f59e3c6cac5e3e180019c59a115 filepath-1.3.0.2-25a474a9272ae6260626ce0d70ad1cab pretty-1.1.1.1-0984f47ffe93ef3983c80b96280f1c3a process-1.2.0.0-06c3215a79834ce4886ae686a0f81122 time-1.4.2-9b3076800c33f8382c38628f35717951 unix-2.7.0.1-f8658ba9ec1c4fba8a371a8e0f42ec6c hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/Cabal-1.18.1.5/Cabal.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/Cabal-1.18.1.5 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: template-haskell version: 2.9.0.0 id: template-haskell-2.9.0.0-6d27c2b362b15abb1822f2f34b9ae7f9 license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: package-url: synopsis: Support library for Template Haskell description: This package provides modules containing facilities for manipulating Haskell source code using Template Haskell. . See for more information. category: Template Haskell author: exposed: True exposed-modules: Language.Haskell.TH Language.Haskell.TH.Lib Language.Haskell.TH.Ppr Language.Haskell.TH.PprLib Language.Haskell.TH.Quote Language.Haskell.TH.Syntax hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/template-haskell-2.9.0.0 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/template-haskell-2.9.0.0 hs-libraries: HStemplate-haskell-2.9.0.0 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 containers-0.5.5.1-d4bd887fb97aa3a46cbadc13709b7653 pretty-1.1.1.1-0984f47ffe93ef3983c80b96280f1c3a hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/template-haskell-2.9.0.0/template-haskell.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/template-haskell-2.9.0.0 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: pretty version: 1.1.1.1 id: pretty-1.1.1.1-0984f47ffe93ef3983c80b96280f1c3a license: BSD3 copyright: maintainer: David Terei stability: Stable homepage: http://github.com/haskell/pretty package-url: synopsis: Pretty-printing library description: This package contains a pretty-printing library, a set of API's that provides a way to easily print out text in a consistent format of your choosing. This is useful for compilers and related tools. . This library was originally designed by John Hughes's and has since been heavily modified by Simon Peyton Jones. category: Text author: exposed: True exposed-modules: Text.PrettyPrint Text.PrettyPrint.HughesPJ hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/pretty-1.1.1.1 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/pretty-1.1.1.1 hs-libraries: HSpretty-1.1.1.1 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/pretty-1.1.1.1/pretty.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/pretty-1.1.1.1 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: hpc version: 0.6.0.1 id: hpc-0.6.0.1-cca17f12dab542e09c423a74a4590c5d license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: package-url: synopsis: Code Coverage Library for Haskell description: This package provides the code coverage library for Haskell. . See for more information. category: Control author: Andy Gill exposed: True exposed-modules: Trace.Hpc.Util Trace.Hpc.Mix Trace.Hpc.Tix Trace.Hpc.Reflect hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/hpc-0.6.0.1 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/hpc-0.6.0.1 hs-libraries: HShpc-0.6.0.1 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 containers-0.5.5.1-d4bd887fb97aa3a46cbadc13709b7653 directory-1.2.1.0-07cd1f59e3c6cac5e3e180019c59a115 time-1.4.2-9b3076800c33f8382c38628f35717951 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/hpc-0.6.0.1/hpc.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/hpc-0.6.0.1 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: process version: 1.2.0.0 id: process-1.2.0.0-06c3215a79834ce4886ae686a0f81122 license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: package-url: synopsis: Process libraries description: This package contains libraries for dealing with system processes. category: System author: exposed: True exposed-modules: System.Cmd System.Process System.Process.Internals hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/process-1.2.0.0 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/process-1.2.0.0 hs-libraries: HSprocess-1.2.0.0 extra-libraries: extra-ghci-libraries: include-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/process-1.2.0.0/include includes: runProcess.h depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 deepseq-1.3.0.2-63a1ab91b7017a28bb5d04cb1b5d2d02 directory-1.2.1.0-07cd1f59e3c6cac5e3e180019c59a115 filepath-1.3.0.2-25a474a9272ae6260626ce0d70ad1cab unix-2.7.0.1-f8658ba9ec1c4fba8a371a8e0f42ec6c hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/process-1.2.0.0/process.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/process-1.2.0.0 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: directory version: 1.2.1.0 id: directory-1.2.1.0-07cd1f59e3c6cac5e3e180019c59a115 license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: package-url: synopsis: library for directory handling description: This package provides a library for handling directories. category: System author: exposed: True exposed-modules: System.Directory hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/directory-1.2.1.0 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/directory-1.2.1.0 hs-libraries: HSdirectory-1.2.1.0 extra-libraries: extra-ghci-libraries: include-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/directory-1.2.1.0/include includes: HsDirectory.h depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 filepath-1.3.0.2-25a474a9272ae6260626ce0d70ad1cab time-1.4.2-9b3076800c33f8382c38628f35717951 unix-2.7.0.1-f8658ba9ec1c4fba8a371a8e0f42ec6c hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/directory-1.2.1.0/directory.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/directory-1.2.1.0 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: unix version: 2.7.0.1 id: unix-2.7.0.1-f8658ba9ec1c4fba8a371a8e0f42ec6c license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: package-url: synopsis: POSIX functionality description: This package gives you access to the set of operating system services standardised by POSIX 1003.1b (or the IEEE Portable Operating System Interface for Computing Environments - IEEE Std. 1003.1). . The package is not supported under Windows (except under Cygwin). category: System author: exposed: True exposed-modules: System.Posix System.Posix.ByteString System.Posix.Error System.Posix.Resource System.Posix.Time System.Posix.Unistd System.Posix.User System.Posix.Signals System.Posix.Signals.Exts System.Posix.Semaphore System.Posix.SharedMem System.Posix.ByteString.FilePath System.Posix.Directory System.Posix.Directory.ByteString System.Posix.DynamicLinker.Module System.Posix.DynamicLinker.Module.ByteString System.Posix.DynamicLinker.Prim System.Posix.DynamicLinker.ByteString System.Posix.DynamicLinker System.Posix.Files System.Posix.Files.ByteString System.Posix.IO System.Posix.IO.ByteString System.Posix.Env System.Posix.Env.ByteString System.Posix.Process System.Posix.Process.Internals System.Posix.Process.ByteString System.Posix.Temp System.Posix.Temp.ByteString System.Posix.Terminal System.Posix.Terminal.ByteString hidden-modules: System.Posix.Directory.Common System.Posix.DynamicLinker.Common System.Posix.Files.Common System.Posix.IO.Common System.Posix.Process.Common System.Posix.Terminal.Common trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/unix-2.7.0.1 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/unix-2.7.0.1 hs-libraries: HSunix-2.7.0.1 extra-libraries: rt util dl pthread extra-ghci-libraries: include-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/unix-2.7.0.1/include includes: HsUnix.h execvpe.h depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 bytestring-0.10.4.0-d6f1d17d717e8652498cab8269a0acd5 time-1.4.2-9b3076800c33f8382c38628f35717951 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/unix-2.7.0.1/unix.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/unix-2.7.0.1 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: time version: 1.4.2 id: time-1.4.2-9b3076800c33f8382c38628f35717951 license: BSD3 copyright: maintainer: stability: stable homepage: http://semantic.org/TimeLib/ package-url: synopsis: A time library description: A time library category: System author: Ashley Yakeley exposed: True exposed-modules: Data.Time.Calendar Data.Time.Calendar.MonthDay Data.Time.Calendar.OrdinalDate Data.Time.Calendar.WeekDate Data.Time.Calendar.Julian Data.Time.Calendar.Easter Data.Time.Clock Data.Time.Clock.POSIX Data.Time.Clock.TAI Data.Time.LocalTime Data.Time.Format Data.Time hidden-modules: Data.Time.Calendar.Private Data.Time.Calendar.Days Data.Time.Calendar.Gregorian Data.Time.Calendar.JulianYearDay Data.Time.Clock.Scale Data.Time.Clock.UTC Data.Time.Clock.CTimeval Data.Time.Clock.UTCDiff Data.Time.LocalTime.TimeZone Data.Time.LocalTime.TimeOfDay Data.Time.LocalTime.LocalTime Data.Time.Format.Parse trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/time-1.4.2 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/time-1.4.2 hs-libraries: HStime-1.4.2 extra-libraries: extra-ghci-libraries: include-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/time-1.4.2/include includes: depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 deepseq-1.3.0.2-63a1ab91b7017a28bb5d04cb1b5d2d02 old-locale-1.0.0.6-50b0125c49f76af85dc7aa22975cdc34 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/time-1.4.2/time.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/time-1.4.2 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: old-locale version: 1.0.0.6 id: old-locale-1.0.0.6-50b0125c49f76af85dc7aa22975cdc34 license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: package-url: synopsis: locale library description: This package provides the ability to adapt to locale conventions such as date and time formats. category: System author: exposed: True exposed-modules: System.Locale hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/old-locale-1.0.0.6 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/old-locale-1.0.0.6 hs-libraries: HSold-locale-1.0.0.6 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/old-locale-1.0.0.6/old-locale.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/old-locale-1.0.0.6 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: containers version: 0.5.5.1 id: containers-0.5.5.1-d4bd887fb97aa3a46cbadc13709b7653 license: BSD3 copyright: maintainer: fox@ucw.cz stability: homepage: package-url: synopsis: Assorted concrete container types description: This package contains efficient general-purpose implementations of various basic immutable container types. The declared cost of each operation is either worst-case or amortized, but remains valid even if structures are shared. category: Data Structures author: exposed: True exposed-modules: Data.IntMap Data.IntMap.Lazy Data.IntMap.Strict Data.IntSet Data.Map Data.Map.Lazy Data.Map.Strict Data.Set Data.Graph Data.Sequence Data.Tree hidden-modules: Data.BitUtil Data.IntMap.Base Data.IntSet.Base Data.Map.Base Data.Set.Base Data.StrictPair trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/containers-0.5.5.1 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/containers-0.5.5.1 hs-libraries: HScontainers-0.5.5.1 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: array-0.5.0.0-470385a50d2b78598af85cfe9d988e1b base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 deepseq-1.3.0.2-63a1ab91b7017a28bb5d04cb1b5d2d02 ghc-prim-0.3.1.0-a24f9c14c632d75b683d0f93283aea37 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/containers-0.5.5.1/containers.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/containers-0.5.5.1 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: bytestring version: 0.10.4.0 id: bytestring-0.10.4.0-d6f1d17d717e8652498cab8269a0acd5 license: BSD3 copyright: Copyright (c) Don Stewart 2005-2009, (c) Duncan Coutts 2006-2013, (c) David Roundy 2003-2005, (c) Jasper Van der Jeugt 2010, (c) Simon Meier 2010-2013. maintainer: Don Stewart , Duncan Coutts stability: homepage: https://github.com/haskell/bytestring package-url: synopsis: Fast, compact, strict and lazy byte strings with a list interface description: An efficient compact, immutable byte string type (both strict and lazy) suitable for binary or 8-bit character data. . The 'ByteString' type represents sequences of bytes or 8-bit characters. It is suitable for high performance use, both in terms of large data quantities, or high speed requirements. The 'ByteString' functions follow the same style as Haskell\'s ordinary lists, so it is easy to convert code from using 'String' to 'ByteString'. . Two 'ByteString' variants are provided: . * Strict 'ByteString's keep the string as a single large array. This makes them convenient for passing data between C and Haskell. . * Lazy 'ByteString's use a lazy list of strict chunks which makes it suitable for I\/O streaming tasks. . The @Char8@ modules provide a character-based view of the same underlying 'ByteString' types. This makes it convenient to handle mixed binary and 8-bit character content (which is common in many file formats and network protocols). . The 'Builder' module provides an efficient way to build up 'ByteString's in an ad-hoc way by repeated concatenation. This is ideal for fast serialisation or pretty printing. . There is also a 'ShortByteString' type which has a lower memory overhead and can can be converted to or from a 'ByteString', but supports very few other operations. It is suitable for keeping many short strings in memory. . 'ByteString's are not designed for Unicode. For Unicode strings you should use the 'Text' type from the @text@ package. . These modules are intended to be imported qualified, to avoid name clashes with "Prelude" functions, e.g. . > import qualified Data.ByteString as BS category: Data author: Don Stewart, Duncan Coutts exposed: True exposed-modules: Data.ByteString Data.ByteString.Char8 Data.ByteString.Unsafe Data.ByteString.Internal Data.ByteString.Lazy Data.ByteString.Lazy.Char8 Data.ByteString.Lazy.Internal Data.ByteString.Short Data.ByteString.Short.Internal Data.ByteString.Builder Data.ByteString.Builder.Extra Data.ByteString.Builder.Prim Data.ByteString.Builder.Internal Data.ByteString.Builder.Prim.Internal Data.ByteString.Lazy.Builder Data.ByteString.Lazy.Builder.Extras Data.ByteString.Lazy.Builder.ASCII hidden-modules: Data.ByteString.Builder.ASCII Data.ByteString.Builder.Prim.Binary Data.ByteString.Builder.Prim.ASCII Data.ByteString.Builder.Prim.Internal.Floating Data.ByteString.Builder.Prim.Internal.UncheckedShifts Data.ByteString.Builder.Prim.Internal.Base16 trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/bytestring-0.10.4.0 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/bytestring-0.10.4.0 hs-libraries: HSbytestring-0.10.4.0 extra-libraries: extra-ghci-libraries: include-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/bytestring-0.10.4.0/include includes: fpstring.h depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 deepseq-1.3.0.2-63a1ab91b7017a28bb5d04cb1b5d2d02 ghc-prim-0.3.1.0-a24f9c14c632d75b683d0f93283aea37 integer-gmp-0.5.1.0-26579559b3647acf4f01d5edd9491a46 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/bytestring-0.10.4.0/bytestring.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/bytestring-0.10.4.0 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: deepseq version: 1.3.0.2 id: deepseq-1.3.0.2-63a1ab91b7017a28bb5d04cb1b5d2d02 license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: package-url: synopsis: Deep evaluation of data structures description: This package provides methods for fully evaluating data structures (\"deep evaluation\"). Deep evaluation is often used for adding strictness to a program, e.g. in order to force pending exceptions, remove space leaks, or force lazy I/O to happen. It is also useful in parallel programs, to ensure pending work does not migrate to the wrong thread. . The primary use of this package is via the 'deepseq' function, a \"deep\" version of 'seq'. It is implemented on top of an 'NFData' typeclass (\"Normal Form Data\", data structures with no unevaluated components) which defines strategies for fully evaluating different data types. . If you want to automatically derive 'NFData' instances via the "GHC.Generics" facility, there is a companion package which builds on top of this package. category: Control author: exposed: True exposed-modules: Control.DeepSeq hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/deepseq-1.3.0.2 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/deepseq-1.3.0.2 hs-libraries: HSdeepseq-1.3.0.2 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: array-0.5.0.0-470385a50d2b78598af85cfe9d988e1b base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/deepseq-1.3.0.2/deepseq.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/deepseq-1.3.0.2 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: array version: 0.5.0.0 id: array-0.5.0.0-470385a50d2b78598af85cfe9d988e1b license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: package-url: synopsis: Mutable and immutable arrays description: In addition to providing the "Data.Array" module , this package also defines the classes 'IArray' of immutable arrays and 'MArray' of arrays mutable within appropriate monads, as well as some instances of these classes. category: Data Structures author: exposed: True exposed-modules: Data.Array Data.Array.Base Data.Array.IArray Data.Array.IO Data.Array.IO.Safe Data.Array.IO.Internals Data.Array.MArray Data.Array.MArray.Safe Data.Array.ST Data.Array.ST.Safe Data.Array.Storable Data.Array.Storable.Safe Data.Array.Storable.Internals Data.Array.Unboxed Data.Array.Unsafe hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/array-0.5.0.0 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/array-0.5.0.0 hs-libraries: HSarray-0.5.0.0 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/array-0.5.0.0/array.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/array-0.5.0.0 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: filepath version: 1.3.0.2 id: filepath-1.3.0.2-25a474a9272ae6260626ce0d70ad1cab license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: http://www-users.cs.york.ac.uk/~ndm/filepath/ package-url: synopsis: Library for manipulating FilePaths in a cross platform way. description: A library for 'FilePath' manipulations, using Posix or Windows filepaths depending on the platform. . Both "System.FilePath.Posix" and "System.FilePath.Windows" provide the same interface. See either for examples and a list of the available functions. category: System author: Neil Mitchell exposed: True exposed-modules: System.FilePath System.FilePath.Posix System.FilePath.Windows hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/filepath-1.3.0.2 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/filepath-1.3.0.2 hs-libraries: HSfilepath-1.3.0.2 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/filepath-1.3.0.2/filepath.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/filepath-1.3.0.2 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: base version: 4.7.0.2 id: base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1 license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: package-url: synopsis: Basic libraries description: This package contains the "Prelude" and its support libraries, and a large collection of useful libraries ranging from data structures to parsing combinators and debugging utilities. category: Prelude author: exposed: True exposed-modules: Control.Applicative Control.Arrow Control.Category Control.Concurrent Control.Concurrent.Chan Control.Concurrent.MVar Control.Concurrent.QSem Control.Concurrent.QSemN Control.Exception Control.Exception.Base Control.Monad Control.Monad.Fix Control.Monad.Instances Control.Monad.ST Control.Monad.ST.Lazy Control.Monad.ST.Lazy.Safe Control.Monad.ST.Lazy.Unsafe Control.Monad.ST.Safe Control.Monad.ST.Strict Control.Monad.ST.Unsafe Control.Monad.Zip Data.Bits Data.Bool Data.Char Data.Coerce Data.Complex Data.Data Data.Dynamic Data.Either Data.Eq Data.Fixed Data.Foldable Data.Function Data.Functor Data.IORef Data.Int Data.Ix Data.List Data.Maybe Data.Monoid Data.OldTypeable Data.OldTypeable.Internal Data.Ord Data.Proxy Data.Ratio Data.STRef Data.STRef.Lazy Data.STRef.Strict Data.String Data.Traversable Data.Tuple Data.Type.Bool Data.Type.Coercion Data.Type.Equality Data.Typeable Data.Typeable.Internal Data.Unique Data.Version Data.Word Debug.Trace Foreign Foreign.C Foreign.C.Error Foreign.C.String Foreign.C.Types Foreign.Concurrent Foreign.ForeignPtr Foreign.ForeignPtr.Safe Foreign.ForeignPtr.Unsafe Foreign.Marshal Foreign.Marshal.Alloc Foreign.Marshal.Array Foreign.Marshal.Error Foreign.Marshal.Pool Foreign.Marshal.Safe Foreign.Marshal.Unsafe Foreign.Marshal.Utils Foreign.Ptr Foreign.Safe Foreign.StablePtr Foreign.Storable GHC.Arr GHC.Base GHC.Char GHC.Conc GHC.Conc.IO GHC.Conc.Signal GHC.Conc.Sync GHC.ConsoleHandler GHC.Constants GHC.Desugar GHC.Enum GHC.Environment GHC.Err GHC.Exception GHC.Exts GHC.Fingerprint GHC.Fingerprint.Type GHC.Float GHC.Float.ConversionUtils GHC.Float.RealFracMethods GHC.Foreign GHC.ForeignPtr GHC.GHCi GHC.Generics GHC.IO GHC.IO.Buffer GHC.IO.BufferedIO GHC.IO.Device GHC.IO.Encoding GHC.IO.Encoding.CodePage GHC.IO.Encoding.Failure GHC.IO.Encoding.Iconv GHC.IO.Encoding.Latin1 GHC.IO.Encoding.Types GHC.IO.Encoding.UTF16 GHC.IO.Encoding.UTF32 GHC.IO.Encoding.UTF8 GHC.IO.Exception GHC.IO.FD GHC.IO.Handle GHC.IO.Handle.FD GHC.IO.Handle.Internals GHC.IO.Handle.Text GHC.IO.Handle.Types GHC.IO.IOMode GHC.IOArray GHC.IORef GHC.IP GHC.Int GHC.List GHC.MVar GHC.Num GHC.PArr GHC.Pack GHC.Profiling GHC.Ptr GHC.Read GHC.Real GHC.ST GHC.STRef GHC.Show GHC.Stable GHC.Stack GHC.Stats GHC.Storable GHC.TopHandler GHC.TypeLits GHC.Unicode GHC.Weak GHC.Word Numeric Prelude System.CPUTime System.Console.GetOpt System.Environment System.Exit System.IO System.IO.Error System.IO.Unsafe System.Info System.Mem System.Mem.StableName System.Mem.Weak System.Posix.Internals System.Posix.Types System.Timeout Text.ParserCombinators.ReadP Text.ParserCombinators.ReadPrec Text.Printf Text.Read Text.Read.Lex Text.Show Text.Show.Functions Unsafe.Coerce GHC.Event hidden-modules: Control.Monad.ST.Imp Control.Monad.ST.Lazy.Imp Foreign.ForeignPtr.Imp System.Environment.ExecutablePath GHC.Event.Arr GHC.Event.Array GHC.Event.Clock GHC.Event.Control GHC.Event.EPoll GHC.Event.IntTable GHC.Event.Internal GHC.Event.KQueue GHC.Event.Manager GHC.Event.PSQ GHC.Event.Poll GHC.Event.Thread GHC.Event.TimerManager GHC.Event.Unique trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/base-4.7.0.2 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/base-4.7.0.2 hs-libraries: HSbase-4.7.0.2 extra-libraries: extra-ghci-libraries: include-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/base-4.7.0.2/include includes: HsBase.h depends: ghc-prim-0.3.1.0-a24f9c14c632d75b683d0f93283aea37 integer-gmp-0.5.1.0-26579559b3647acf4f01d5edd9491a46 builtin_rts hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/base-4.7.0.2/base.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/base-4.7.0.2 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: integer-gmp version: 0.5.1.0 id: integer-gmp-0.5.1.0-26579559b3647acf4f01d5edd9491a46 license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: package-url: synopsis: Integer library based on GMP description: This package provides the low-level implementation of the standard 'Integer' type based on the . . This package provides access to the internal representation of 'Integer' as well as primitive operations with no proper error handling, and should only be used directly with the utmost care. . For more details about the design of @integer-gmp@, see . category: Numerical author: exposed: True exposed-modules: GHC.Integer GHC.Integer.GMP.Internals GHC.Integer.GMP.Prim GHC.Integer.Logarithms GHC.Integer.Logarithms.Internals hidden-modules: GHC.Integer.Type trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/integer-gmp-0.5.1.0 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/integer-gmp-0.5.1.0 hs-libraries: HSinteger-gmp-0.5.1.0 extra-libraries: gmp extra-ghci-libraries: include-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/integer-gmp-0.5.1.0/include includes: depends: ghc-prim-0.3.1.0-a24f9c14c632d75b683d0f93283aea37 hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/integer-gmp-0.5.1.0/integer-gmp.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/integer-gmp-0.5.1.0 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: ghc-prim version: 0.3.1.0 id: ghc-prim-0.3.1.0-a24f9c14c632d75b683d0f93283aea37 license: BSD3 copyright: maintainer: libraries@haskell.org stability: homepage: package-url: synopsis: GHC primitives description: GHC primitives. category: GHC author: exposed: True exposed-modules: GHC.CString GHC.Classes GHC.Debug GHC.IntWord64 GHC.Magic GHC.PrimopWrappers GHC.Tuple GHC.Types GHC.Prim hidden-modules: trusted: False import-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/ghc-prim-0.3.1.0 library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/ghc-prim-0.3.1.0 hs-libraries: HSghc-prim-0.3.1.0 extra-libraries: extra-ghci-libraries: include-dirs: includes: depends: builtin_rts hugs-options: cc-options: ld-options: framework-dirs: frameworks: haddock-interfaces: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/ghc-prim-0.3.1.0/ghc-prim.haddock haddock-html: /opt/ghc/7.8.4/share/doc/ghc/html/libraries/ghc-prim-0.3.1.0 pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" --- name: rts version: 1.0 id: builtin_rts license: BSD3 copyright: maintainer: glasgow-haskell-users@haskell.org stability: homepage: package-url: synopsis: description: category: author: exposed: True exposed-modules: hidden-modules: trusted: False import-dirs: library-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/rts-1.0 hs-libraries: HSrts Cffi extra-libraries: m rt dl extra-ghci-libraries: include-dirs: /opt/ghc/7.8.4/lib/ghc-7.8.4/include includes: Stg.h depends: hugs-options: cc-options: ld-options: "-Wl,-u,ghczmprim_GHCziTypes_Izh_static_info" "-Wl,-u,ghczmprim_GHCziTypes_Czh_static_info" "-Wl,-u,ghczmprim_GHCziTypes_Fzh_static_info" "-Wl,-u,ghczmprim_GHCziTypes_Dzh_static_info" "-Wl,-u,base_GHCziPtr_Ptr_static_info" "-Wl,-u,ghczmprim_GHCziTypes_Wzh_static_info" "-Wl,-u,base_GHCziInt_I8zh_static_info" "-Wl,-u,base_GHCziInt_I16zh_static_info" "-Wl,-u,base_GHCziInt_I32zh_static_info" "-Wl,-u,base_GHCziInt_I64zh_static_info" "-Wl,-u,base_GHCziWord_W8zh_static_info" "-Wl,-u,base_GHCziWord_W16zh_static_info" "-Wl,-u,base_GHCziWord_W32zh_static_info" "-Wl,-u,base_GHCziWord_W64zh_static_info" "-Wl,-u,base_GHCziStable_StablePtr_static_info" "-Wl,-u,ghczmprim_GHCziTypes_Izh_con_info" "-Wl,-u,ghczmprim_GHCziTypes_Czh_con_info" "-Wl,-u,ghczmprim_GHCziTypes_Fzh_con_info" "-Wl,-u,ghczmprim_GHCziTypes_Dzh_con_info" "-Wl,-u,base_GHCziPtr_Ptr_con_info" "-Wl,-u,base_GHCziPtr_FunPtr_con_info" "-Wl,-u,base_GHCziStable_StablePtr_con_info" "-Wl,-u,ghczmprim_GHCziTypes_False_closure" "-Wl,-u,ghczmprim_GHCziTypes_True_closure" "-Wl,-u,base_GHCziPack_unpackCString_closure" "-Wl,-u,base_GHCziIOziException_stackOverflow_closure" "-Wl,-u,base_GHCziIOziException_heapOverflow_closure" "-Wl,-u,base_ControlziExceptionziBase_nonTermination_closure" "-Wl,-u,base_GHCziIOziException_blockedIndefinitelyOnMVar_closure" "-Wl,-u,base_GHCziIOziException_blockedIndefinitelyOnSTM_closure" "-Wl,-u,base_ControlziExceptionziBase_nestedAtomically_closure" "-Wl,-u,base_GHCziWeak_runFinalizzerBatch_closure" "-Wl,-u,base_GHCziTopHandler_flushStdHandles_closure" "-Wl,-u,base_GHCziTopHandler_runIO_closure" "-Wl,-u,base_GHCziTopHandler_runNonIO_closure" "-Wl,-u,base_GHCziConcziIO_ensureIOManagerIsRunning_closure" "-Wl,-u,base_GHCziConcziIO_ioManagerCapabilitiesChanged_closure" "-Wl,-u,base_GHCziConcziSync_runSparks_closure" "-Wl,-u,base_GHCziConcziSignal_runHandlersPtr_closure" framework-dirs: frameworks: haddock-interfaces: haddock-html: pkgroot: "/opt/ghc/7.8.4/lib/ghc-7.8.4" stack-1.5.1/test/package-dump/ghc-7.8.4-osx.txt0000644000000000000000000000625712546477354017146 0ustar0000000000000000name: hmatrix version: 0.16.1.5 id: hmatrix-0.16.1.5-12d5d21f26aa98774cdd8edbc343fbfe license: BSD3 copyright: maintainer: Alberto Ruiz stability: provisional homepage: https://github.com/albertoruiz/hmatrix package-url: synopsis: Numeric Linear Algebra description: Linear algebra based on BLAS and LAPACK. . The package is organized as follows: . ["Numeric.LinearAlgebra.HMatrix"] Starting point and recommended import module for most applications. . ["Numeric.LinearAlgebra.Static"] Experimental alternative interface. . ["Numeric.LinearAlgebra.Devel"] Tools for extending the library. . (Other modules are exposed with hidden documentation for backwards compatibility.) . Code examples: category: Math author: Alberto Ruiz exposed: True exposed-modules: Data.Packed Data.Packed.Vector Data.Packed.Matrix Data.Packed.Foreign Data.Packed.ST Data.Packed.Development Numeric.LinearAlgebra Numeric.LinearAlgebra.LAPACK Numeric.LinearAlgebra.Algorithms Numeric.Container Numeric.LinearAlgebra.Util Numeric.LinearAlgebra.Devel Numeric.LinearAlgebra.Data Numeric.LinearAlgebra.HMatrix Numeric.LinearAlgebra.Static hidden-modules: Data.Packed.Internal Data.Packed.Internal.Common Data.Packed.Internal.Signatures Data.Packed.Internal.Vector Data.Packed.Internal.Matrix Data.Packed.IO Numeric.Chain Numeric.Vectorized Numeric.Vector Numeric.Matrix Data.Packed.Internal.Numeric Data.Packed.Numeric Numeric.LinearAlgebra.Util.Convolution Numeric.LinearAlgebra.Util.CG Numeric.LinearAlgebra.Random Numeric.Conversion Numeric.Sparse Numeric.LinearAlgebra.Static.Internal trusted: False import-dirs: /Users/alexbiehl/.stack/snapshots/x86_64-osx/lts-2.13/7.8.4/lib/x86_64-osx-ghc-7.8.4/hmatrix-0.16.1.5 library-dirs: /Users/alexbiehl/.stack/snapshots/x86_64-osx/lts-2.13/7.8.4/lib/x86_64-osx-ghc-7.8.4/hmatrix-0.16.1.5 /opt/local/lib/ /usr/local/lib/ "C:/Program Files/Example/" hs-libraries: HShmatrix-0.16.1.5 extra-libraries: blas lapack extra-ghci-libraries: include-dirs: /opt/local/include/ /usr/local/include/ includes: depends: array-0.5.0.0-470385a50d2b78598af85cfe9d988e1b base-4.7.0.2-918c7ac27f65a87103264a9f51652d63 binary-0.7.1.0-108d06eea2ef05e517f9c1facf10f63c bytestring-0.10.4.0-78bc8f2c724c765c78c004a84acf6cc3 deepseq-1.3.0.2-0ddc77716bd2515426e1ba39f6788a4f random-1.1-822c19b7507b6ac1aaa4c66731e775ae split-0.2.2-34cfb851cc3784e22bfae7a7bddda9c5 storable-complex-0.2.2-e962c368d58acc1f5b41d41edc93da72 vector-0.10.12.3-f4222db607fd5fdd7545d3e82419b307 hugs-options: cc-options: ld-options: framework-dirs: frameworks: Accelerate haddock-interfaces: /Users/alexbiehl/.stack/snapshots/x86_64-osx/lts-2.13/7.8.4/doc/html/hmatrix.haddock haddock-html: /Users/alexbiehl/.stack/snapshots/x86_64-osx/lts-2.13/7.8.4/doc/html stack-1.5.1/test/package-dump/ghc-7.10.txt0000644000000000000000000015720712546477354016250 0ustar0000000000000000name: ghc version: 7.10.1 id: ghc-7.10.1-325809317787a897b7a97d646ceaa3a3 key: ghc_EMlWrQ42XY0BNVbSrKixqY license: BSD3 maintainer: glasgow-haskell-users@haskell.org homepage: http://www.haskell.org/ghc/ synopsis: The GHC API description: GHC's functionality can be useful for more things than just compiling Haskell programs. Important use cases are programs that analyse (and perhaps transform) Haskell code. Others include loading Haskell code dynamically in a GHCi-like manner. For this reason, a lot of GHC's functionality is made available through this package. category: Development author: The GHC Team exposed: False exposed-modules: Avail BasicTypes ConLike DataCon PatSyn Demand Debug Exception GhcMonad Hooks Id IdInfo Lexeme Literal Llvm Llvm.AbsSyn Llvm.MetaData Llvm.PpLlvm Llvm.Types LlvmCodeGen LlvmCodeGen.Base LlvmCodeGen.CodeGen LlvmCodeGen.Data LlvmCodeGen.Ppr LlvmCodeGen.Regs LlvmMangler MkId Module Name NameEnv NameSet OccName RdrName SrcLoc UniqSupply Unique Var VarEnv VarSet UnVarGraph BlockId CLabel Cmm CmmBuildInfoTables CmmPipeline CmmCallConv CmmCommonBlockElim CmmContFlowOpt CmmExpr CmmInfo CmmLex CmmLint CmmLive CmmMachOp CmmNode CmmOpt CmmParse CmmProcPoint CmmSink CmmType CmmUtils CmmLayoutStack MkGraph PprBase PprC PprCmm PprCmmDecl PprCmmExpr Bitmap CodeGen.Platform CodeGen.Platform.ARM CodeGen.Platform.ARM64 CodeGen.Platform.NoRegs CodeGen.Platform.PPC CodeGen.Platform.PPC_Darwin CodeGen.Platform.SPARC CodeGen.Platform.X86 CodeGen.Platform.X86_64 CgUtils StgCmm StgCmmBind StgCmmClosure StgCmmCon StgCmmEnv StgCmmExpr StgCmmForeign StgCmmHeap StgCmmHpc StgCmmArgRep StgCmmLayout StgCmmMonad StgCmmPrim StgCmmProf StgCmmTicky StgCmmUtils StgCmmExtCode SMRep CoreArity CoreFVs CoreLint CorePrep CoreSubst CoreSyn TrieMap CoreTidy CoreUnfold CoreUtils MkCore PprCore Check Coverage Desugar DsArrows DsBinds DsCCall DsExpr DsForeign DsGRHSs DsListComp DsMonad DsUtils Match MatchCon MatchLit HsBinds HsDecls HsDoc HsExpr HsImpExp HsLit PlaceHolder HsPat HsSyn HsTypes HsUtils BinIface BuildTyCl IfaceEnv IfaceSyn IfaceType LoadIface MkIface TcIface FlagChecker Annotations BreakArray CmdLineParser CodeOutput Config Constants DriverMkDepend DriverPhases PipelineMonad DriverPipeline DynFlags ErrUtils Finder GHC GhcMake GhcPlugins DynamicLoading HeaderInfo HscMain HscStats HscTypes InteractiveEval InteractiveEvalTypes PackageConfig Packages PlatformConstants Plugins TcPluginM PprTyThing StaticFlags StaticPtrTable SysTools TidyPgm Ctype HaddockUtils Lexer OptCoercion Parser RdrHsSyn ApiAnnotation ForeignCall PrelInfo PrelNames PrelRules PrimOp TysPrim TysWiredIn CostCentre ProfInit SCCfinal RnBinds RnEnv RnExpr RnHsDoc RnNames RnPat RnSource RnSplice RnTypes CoreMonad CSE FloatIn FloatOut LiberateCase OccurAnal SAT SetLevels SimplCore SimplEnv SimplMonad SimplUtils Simplify SimplStg StgStats UnariseStg Rules SpecConstr Specialise CoreToStg StgLint StgSyn CallArity DmdAnal WorkWrap WwLib FamInst Inst TcAnnotations TcArrows TcBinds TcClassDcl TcDefaults TcDeriv TcEnv TcExpr TcForeign TcGenDeriv TcGenGenerics TcHsSyn TcHsType TcInstDcls TcMType TcValidity TcMatches TcPat TcPatSyn TcRnDriver TcRnMonad TcRnTypes TcRules TcSimplify TcErrors TcTyClsDecls TcTyDecls TcType TcEvidence TcUnify TcInteract TcCanonical TcFlatten TcSMonad TcTypeNats TcSplice Class Coercion FamInstEnv FunDeps InstEnv TyCon CoAxiom Kind Type TypeRep Unify Bag Binary BooleanFormula BufWrite Digraph Encoding FastBool FastFunctions FastMutInt FastString FastTypes Fingerprint FiniteMap GraphBase GraphColor GraphOps GraphPpr IOEnv ListSetOps Maybes MonadUtils OrdList Outputable Pair Panic Pretty Serialized State Stream StringBuffer UniqFM UniqSet Util ExtsCompat46 Vectorise.Builtins.Base Vectorise.Builtins.Initialise Vectorise.Builtins Vectorise.Monad.Base Vectorise.Monad.Naming Vectorise.Monad.Local Vectorise.Monad.Global Vectorise.Monad.InstEnv Vectorise.Monad Vectorise.Utils.Base Vectorise.Utils.Closure Vectorise.Utils.Hoisting Vectorise.Utils.PADict Vectorise.Utils.Poly Vectorise.Utils Vectorise.Generic.Description Vectorise.Generic.PAMethods Vectorise.Generic.PADict Vectorise.Generic.PData Vectorise.Type.Env Vectorise.Type.Type Vectorise.Type.TyConDecl Vectorise.Type.Classify Vectorise.Convert Vectorise.Vect Vectorise.Var Vectorise.Env Vectorise.Exp Vectorise Hoopl.Dataflow Hoopl AsmCodeGen TargetReg NCGMonad Instruction Size Reg RegClass PIC Platform CPrim X86.Regs X86.RegInfo X86.Instr X86.Cond X86.Ppr X86.CodeGen PPC.Regs PPC.RegInfo PPC.Instr PPC.Cond PPC.Ppr PPC.CodeGen SPARC.Base SPARC.Regs SPARC.Imm SPARC.AddrMode SPARC.Cond SPARC.Instr SPARC.Stack SPARC.ShortcutJump SPARC.Ppr SPARC.CodeGen SPARC.CodeGen.Amode SPARC.CodeGen.Base SPARC.CodeGen.CondCode SPARC.CodeGen.Gen32 SPARC.CodeGen.Gen64 SPARC.CodeGen.Sanity SPARC.CodeGen.Expand RegAlloc.Liveness RegAlloc.Graph.Main RegAlloc.Graph.Stats RegAlloc.Graph.ArchBase RegAlloc.Graph.ArchX86 RegAlloc.Graph.Coalesce RegAlloc.Graph.Spill RegAlloc.Graph.SpillClean RegAlloc.Graph.SpillCost RegAlloc.Graph.TrivColorable RegAlloc.Linear.Main RegAlloc.Linear.JoinToTargets RegAlloc.Linear.State RegAlloc.Linear.Stats RegAlloc.Linear.FreeRegs RegAlloc.Linear.StackMap RegAlloc.Linear.Base RegAlloc.Linear.X86.FreeRegs RegAlloc.Linear.X86_64.FreeRegs RegAlloc.Linear.PPC.FreeRegs RegAlloc.Linear.SPARC.FreeRegs Dwarf Dwarf.Types Dwarf.Constants DsMeta Convert ByteCodeAsm ByteCodeGen ByteCodeInstr ByteCodeItbls ByteCodeLink Debugger LibFFI Linker ObjLink RtClosureInspect DebuggerUtils trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/ghc_EMlWrQ42XY0BNVbSrKixqY library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/ghc_EMlWrQ42XY0BNVbSrKixqY data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/ghc-7.10.1 hs-libraries: HSghc-7.10.1-EMlWrQ42XY0BNVbSrKixqY include-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/ghc_EMlWrQ42XY0BNVbSrKixqY/include depends: array-0.5.1.0-e29cdbe82692341ebb7ce6e2798294f9 base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a bin-package-db-0.0.0.0-708fc7d634a370b311371a5bcde40b62 bytestring-0.10.6.0-0909f8f31271f3d75749190bf2ee35db containers-0.5.6.2-2114032c163425cc264e6e1169dc2f6d directory-1.2.2.0-b4959b472d9eee380c6b32291ade29e0 filepath-1.4.0.0-40d643aa87258c186441a1f8f3e13ca6 hoopl-3.10.0.2-8c8dfc4c3140e5f7c982da224c3cb1f0 hpc-0.6.0.2-ac9064885aa8cb08a93314222939ead4 process-1.2.3.0-3b1e9bca6ac38225806ff7bbf3f845b1 template-haskell-2.10.0.0-e895139a0ffff267d412e3d0191ce93b time-1.5.0.1-e17a9220d438435579d2914e90774246 transformers-0.4.2.0-c1a7bb855a176fe475d7b665301cd48f unix-2.7.1.0-e5915eb989e568b732bc7286b0d0817f haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/ghc-7.10.1/ghc.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/ghc-7.10.1 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: haskeline version: 0.7.2.1 id: haskeline-0.7.2.1-a646e1ddf1a755ca5b5775dcb2ef8d8b key: haske_IlDhIe25uAn0WJY379Nu1M license: BSD3 copyright: (c) Judah Jacobson maintainer: Judah Jacobson stability: Experimental homepage: http://trac.haskell.org/haskeline synopsis: A command-line interface for user input, written in Haskell. description: Haskeline provides a user interface for line input in command-line programs. This library is similar in purpose to readline, but since it is written in Haskell it is (hopefully) more easily used in other Haskell programs. . Haskeline runs both on POSIX-compatible systems and on Windows. category: User Interfaces author: Judah Jacobson exposed: True exposed-modules: System.Console.Haskeline System.Console.Haskeline.Completion System.Console.Haskeline.MonadException System.Console.Haskeline.History System.Console.Haskeline.IO hidden-modules: System.Console.Haskeline.Backend System.Console.Haskeline.Backend.WCWidth System.Console.Haskeline.Command System.Console.Haskeline.Command.Completion System.Console.Haskeline.Command.History System.Console.Haskeline.Command.KillRing System.Console.Haskeline.Directory System.Console.Haskeline.Emacs System.Console.Haskeline.InputT System.Console.Haskeline.Key System.Console.Haskeline.LineState System.Console.Haskeline.Monads System.Console.Haskeline.Prefs System.Console.Haskeline.RunCommand System.Console.Haskeline.Term System.Console.Haskeline.Command.Undo System.Console.Haskeline.Vi System.Console.Haskeline.Recover System.Console.Haskeline.Backend.Posix System.Console.Haskeline.Backend.Posix.Encoder System.Console.Haskeline.Backend.DumbTerm System.Console.Haskeline.Backend.Terminfo trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/haske_IlDhIe25uAn0WJY379Nu1M library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/haske_IlDhIe25uAn0WJY379Nu1M data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/haskeline-0.7.2.1 hs-libraries: HShaskeline-0.7.2.1-IlDhIe25uAn0WJY379Nu1M depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a bytestring-0.10.6.0-0909f8f31271f3d75749190bf2ee35db containers-0.5.6.2-2114032c163425cc264e6e1169dc2f6d directory-1.2.2.0-b4959b472d9eee380c6b32291ade29e0 filepath-1.4.0.0-40d643aa87258c186441a1f8f3e13ca6 terminfo-0.4.0.1-75199801b414a3f4c9de438be2a4e967 transformers-0.4.2.0-c1a7bb855a176fe475d7b665301cd48f unix-2.7.1.0-e5915eb989e568b732bc7286b0d0817f haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/haskeline-0.7.2.1/haskeline.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/haskeline-0.7.2.1 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: terminfo version: 0.4.0.1 id: terminfo-0.4.0.1-75199801b414a3f4c9de438be2a4e967 key: termi_7qZwBlx3clR8sTBilJl253 license: BSD3 copyright: (c) Judah Jacobson maintainer: Judah Jacobson stability: Stable homepage: https://github.com/judah/terminfo synopsis: Haskell bindings to the terminfo library. description: This library provides an interface to the terminfo database (via bindings to the curses library). allows POSIX systems to interact with a variety of terminals using a standard set of capabilities. category: User Interfaces author: Judah Jacobson exposed: True exposed-modules: System.Console.Terminfo System.Console.Terminfo.Base System.Console.Terminfo.Cursor System.Console.Terminfo.Color System.Console.Terminfo.Edit System.Console.Terminfo.Effects System.Console.Terminfo.Keys trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/termi_7qZwBlx3clR8sTBilJl253 library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/termi_7qZwBlx3clR8sTBilJl253 data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/terminfo-0.4.0.1 hs-libraries: HSterminfo-0.4.0.1-7qZwBlx3clR8sTBilJl253 extra-libraries: tinfo includes: ncurses.h term.h depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/terminfo-0.4.0.1/terminfo.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/terminfo-0.4.0.1 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: xhtml version: 3000.2.1 id: xhtml-3000.2.1-7de0560ea74b173b7313fc2303cc6c58 key: xhtml_0mVDYvYGgNUBWShvlDofr1 license: BSD3 copyright: Bjorn Bringert 2004-2006, Andy Gill and the Oregon Graduate Institute of Science and Technology, 1999-2001 maintainer: Chris Dornan stability: Stable homepage: https://github.com/haskell/xhtml synopsis: An XHTML combinator library description: This package provides combinators for producing XHTML 1.0, including the Strict, Transitional and Frameset variants. category: Web, XML, Pretty Printer author: Bjorn Bringert exposed: True exposed-modules: Text.XHtml Text.XHtml.Frameset Text.XHtml.Strict Text.XHtml.Transitional Text.XHtml.Debug Text.XHtml.Table hidden-modules: Text.XHtml.Strict.Attributes Text.XHtml.Strict.Elements Text.XHtml.Frameset.Attributes Text.XHtml.Frameset.Elements Text.XHtml.Transitional.Attributes Text.XHtml.Transitional.Elements Text.XHtml.BlockTable Text.XHtml.Extras Text.XHtml.Internals trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/xhtml_0mVDYvYGgNUBWShvlDofr1 library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/xhtml_0mVDYvYGgNUBWShvlDofr1 data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/xhtml-3000.2.1 hs-libraries: HSxhtml-3000.2.1-0mVDYvYGgNUBWShvlDofr1 depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/xhtml-3000.2.1/xhtml.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/xhtml-3000.2.1 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: transformers version: 0.4.2.0 id: transformers-0.4.2.0-c1a7bb855a176fe475d7b665301cd48f key: trans_ALYlebOVzVI4kxbFX5SGhm license: BSD3 maintainer: Ross Paterson synopsis: Concrete functor and monad transformers description: A portable library of functor and monad transformers, inspired by the paper \"Functional Programming with Overloading and Higher-Order Polymorphism\", by Mark P Jones, in /Advanced School of Functional Programming/, 1995 (). . This package contains: . * the monad transformer class (in "Control.Monad.Trans.Class") and IO monad class (in "Control.Monad.IO.Class") . * concrete functor and monad transformers, each with associated operations and functions to lift operations associated with other transformers. . The package can be used on its own in portable Haskell code, in which case operations need to be manually lifted through transformer stacks (see "Control.Monad.Trans.Class" for some examples). Alternatively, it can be used with the non-portable monad classes in the @mtl@ or @monads-tf@ packages, which automatically lift operations introduced by monad transformers through other transformers. category: Control author: Andy Gill, Ross Paterson exposed: True exposed-modules: Control.Applicative.Backwards Control.Applicative.Lift Control.Monad.IO.Class Control.Monad.Signatures Control.Monad.Trans.Class Control.Monad.Trans.Cont Control.Monad.Trans.Except Control.Monad.Trans.Error Control.Monad.Trans.Identity Control.Monad.Trans.List Control.Monad.Trans.Maybe Control.Monad.Trans.Reader Control.Monad.Trans.RWS Control.Monad.Trans.RWS.Lazy Control.Monad.Trans.RWS.Strict Control.Monad.Trans.State Control.Monad.Trans.State.Lazy Control.Monad.Trans.State.Strict Control.Monad.Trans.Writer Control.Monad.Trans.Writer.Lazy Control.Monad.Trans.Writer.Strict Data.Functor.Classes Data.Functor.Compose Data.Functor.Constant Data.Functor.Product Data.Functor.Reverse Data.Functor.Sum trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/trans_ALYlebOVzVI4kxbFX5SGhm library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/trans_ALYlebOVzVI4kxbFX5SGhm data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/transformers-0.4.2.0 hs-libraries: HStransformers-0.4.2.0-ALYlebOVzVI4kxbFX5SGhm depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/transformers-0.4.2.0/transformers.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/transformers-0.4.2.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: hoopl version: 3.10.0.2 id: hoopl-3.10.0.2-8c8dfc4c3140e5f7c982da224c3cb1f0 key: hoopl_JxODiSRz1e84NbH6nnZuUk license: BSD3 maintainer: nr@cs.tufts.edu homepage: http://ghc.cs.tufts.edu/hoopl/ synopsis: A library to support dataflow analysis and optimization description: Higher-order optimization library . See /Norman Ramsey, Joao Dias, and Simon Peyton Jones./ /(2010)/ for more details. category: Compilers/Interpreters author: Norman Ramsey, Joao Dias, Simon Marlow and Simon Peyton Jones exposed: True exposed-modules: Compiler.Hoopl Compiler.Hoopl.Internals Compiler.Hoopl.Wrappers Compiler.Hoopl.Passes.Dominator Compiler.Hoopl.Passes.DList hidden-modules: Compiler.Hoopl.Checkpoint Compiler.Hoopl.Collections Compiler.Hoopl.Combinators Compiler.Hoopl.Dataflow Compiler.Hoopl.Debug Compiler.Hoopl.Block Compiler.Hoopl.Graph Compiler.Hoopl.Label Compiler.Hoopl.MkGraph Compiler.Hoopl.Fuel Compiler.Hoopl.Pointed Compiler.Hoopl.Shape Compiler.Hoopl.Show Compiler.Hoopl.Unique Compiler.Hoopl.XUtil trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/hoopl_JxODiSRz1e84NbH6nnZuUk library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/hoopl_JxODiSRz1e84NbH6nnZuUk data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/hoopl-3.10.0.2 hs-libraries: HShoopl-3.10.0.2-JxODiSRz1e84NbH6nnZuUk depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a containers-0.5.6.2-2114032c163425cc264e6e1169dc2f6d haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/hoopl-3.10.0.2/hoopl.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/hoopl-3.10.0.2 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: bin-package-db version: 0.0.0.0 id: bin-package-db-0.0.0.0-708fc7d634a370b311371a5bcde40b62 key: binpa_JNoexmBMuO8C771QaIy3YN license: BSD3 maintainer: ghc-devs@haskell.org synopsis: The GHC compiler's view of the GHC package database format description: This library is shared between GHC and ghc-pkg and is used by GHC to read package databases. . It only deals with the subset of the package database that the compiler cares about: modules paths etc and not package metadata like description, authors etc. It is thus not a library interface to ghc-pkg and is *not* suitable for modifying GHC package databases. . The package database format and this library are constructed in such a way that while ghc-pkg depends on Cabal, the GHC library and program do not have to depend on Cabal. exposed: True exposed-modules: GHC.PackageDb trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/binpa_JNoexmBMuO8C771QaIy3YN library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/binpa_JNoexmBMuO8C771QaIy3YN data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/bin-package-db-0.0.0.0 hs-libraries: HSbin-package-db-0.0.0.0-JNoexmBMuO8C771QaIy3YN depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a binary-0.7.3.0-0f543654a1ae447e0d4d0bbfc1bb704e bytestring-0.10.6.0-0909f8f31271f3d75749190bf2ee35db directory-1.2.2.0-b4959b472d9eee380c6b32291ade29e0 filepath-1.4.0.0-40d643aa87258c186441a1f8f3e13ca6 haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/bin-package-db-0.0.0.0/bin-package-db.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/bin-package-db-0.0.0.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: Cabal version: 1.22.2.0 id: Cabal-1.22.2.0-9f7cae2e98cca225e3d159c1e1bc773c key: Cabal_HWT8QvVfJLn2ubvobpycJY license: BSD3 copyright: 2003-2006, Isaac Jones 2005-2011, Duncan Coutts maintainer: cabal-devel@haskell.org homepage: http://www.haskell.org/cabal/ synopsis: A framework for packaging Haskell software description: The Haskell Common Architecture for Building Applications and Libraries: a framework defining a common interface for authors to more easily build their Haskell applications in a portable way. . The Haskell Cabal is part of a larger infrastructure for distributing, organizing, and cataloging Haskell libraries and tools. category: Distribution author: Isaac Jones Duncan Coutts exposed: True exposed-modules: Distribution.Compat.CreatePipe Distribution.Compat.Environment Distribution.Compat.Exception Distribution.Compat.ReadP Distribution.Compiler Distribution.InstalledPackageInfo Distribution.License Distribution.Make Distribution.ModuleName Distribution.Package Distribution.PackageDescription Distribution.PackageDescription.Check Distribution.PackageDescription.Configuration Distribution.PackageDescription.Parse Distribution.PackageDescription.PrettyPrint Distribution.PackageDescription.Utils Distribution.ParseUtils Distribution.ReadE Distribution.Simple Distribution.Simple.Bench Distribution.Simple.Build Distribution.Simple.Build.Macros Distribution.Simple.Build.PathsModule Distribution.Simple.BuildPaths Distribution.Simple.BuildTarget Distribution.Simple.CCompiler Distribution.Simple.Command Distribution.Simple.Compiler Distribution.Simple.Configure Distribution.Simple.GHC Distribution.Simple.GHCJS Distribution.Simple.Haddock Distribution.Simple.HaskellSuite Distribution.Simple.Hpc Distribution.Simple.Install Distribution.Simple.InstallDirs Distribution.Simple.JHC Distribution.Simple.LHC Distribution.Simple.LocalBuildInfo Distribution.Simple.PackageIndex Distribution.Simple.PreProcess Distribution.Simple.PreProcess.Unlit Distribution.Simple.Program Distribution.Simple.Program.Ar Distribution.Simple.Program.Builtin Distribution.Simple.Program.Db Distribution.Simple.Program.Find Distribution.Simple.Program.GHC Distribution.Simple.Program.HcPkg Distribution.Simple.Program.Hpc Distribution.Simple.Program.Ld Distribution.Simple.Program.Run Distribution.Simple.Program.Script Distribution.Simple.Program.Strip Distribution.Simple.Program.Types Distribution.Simple.Register Distribution.Simple.Setup Distribution.Simple.SrcDist Distribution.Simple.Test Distribution.Simple.Test.ExeV10 Distribution.Simple.Test.LibV09 Distribution.Simple.Test.Log Distribution.Simple.UHC Distribution.Simple.UserHooks Distribution.Simple.Utils Distribution.System Distribution.TestSuite Distribution.Text Distribution.Utils.NubList Distribution.Verbosity Distribution.Version Language.Haskell.Extension hidden-modules: Distribution.Compat.Binary Distribution.Compat.CopyFile Distribution.Compat.TempFile Distribution.GetOpt Distribution.Simple.GHC.Internal Distribution.Simple.GHC.IPI641 Distribution.Simple.GHC.IPI642 Distribution.Simple.GHC.ImplInfo Paths_Cabal trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/Cabal_HWT8QvVfJLn2ubvobpycJY library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/Cabal_HWT8QvVfJLn2ubvobpycJY data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/Cabal-1.22.2.0 hs-libraries: HSCabal-1.22.2.0-HWT8QvVfJLn2ubvobpycJY depends: array-0.5.1.0-e29cdbe82692341ebb7ce6e2798294f9 base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a binary-0.7.3.0-0f543654a1ae447e0d4d0bbfc1bb704e bytestring-0.10.6.0-0909f8f31271f3d75749190bf2ee35db containers-0.5.6.2-2114032c163425cc264e6e1169dc2f6d deepseq-1.4.1.1-c1376f846fa170f2cc2cb2e57b203339 directory-1.2.2.0-b4959b472d9eee380c6b32291ade29e0 filepath-1.4.0.0-40d643aa87258c186441a1f8f3e13ca6 pretty-1.1.2.0-0d4e1eca3b0cfcebe20b9405f7bdaca9 process-1.2.3.0-3b1e9bca6ac38225806ff7bbf3f845b1 time-1.5.0.1-e17a9220d438435579d2914e90774246 unix-2.7.1.0-e5915eb989e568b732bc7286b0d0817f haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/Cabal-1.22.2.0/Cabal.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/Cabal-1.22.2.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: binary version: 0.7.3.0 id: binary-0.7.3.0-0f543654a1ae447e0d4d0bbfc1bb704e key: binar_EKE3c9Lmxb3DQpU0fPtru6 license: BSD3 maintainer: Lennart Kolmodin, Don Stewart stability: provisional homepage: https://github.com/kolmodin/binary synopsis: Binary serialisation for Haskell values using lazy ByteStrings description: Efficient, pure binary serialisation using lazy ByteStrings. Haskell values may be encoded to and from binary formats, written to disk as binary, or sent over the network. The format used can be automatically generated, or you can choose to implement a custom format if needed. Serialisation speeds of over 1 G\/sec have been observed, so this library should be suitable for high performance scenarios. category: Data, Parsing author: Lennart Kolmodin exposed: True exposed-modules: Data.Binary Data.Binary.Put Data.Binary.Get Data.Binary.Get.Internal Data.Binary.Builder Data.Binary.Builder.Internal hidden-modules: Data.Binary.Builder.Base Data.Binary.Class Data.Binary.Generic trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/binar_EKE3c9Lmxb3DQpU0fPtru6 library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/binar_EKE3c9Lmxb3DQpU0fPtru6 data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/binary-0.7.3.0 hs-libraries: HSbinary-0.7.3.0-EKE3c9Lmxb3DQpU0fPtru6 depends: array-0.5.1.0-e29cdbe82692341ebb7ce6e2798294f9 base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a bytestring-0.10.6.0-0909f8f31271f3d75749190bf2ee35db containers-0.5.6.2-2114032c163425cc264e6e1169dc2f6d haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/binary-0.7.3.0/binary.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/binary-0.7.3.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: template-haskell version: 2.10.0.0 id: template-haskell-2.10.0.0-e895139a0ffff267d412e3d0191ce93b key: templ_BVMCZyLwIlfGfcqqzyUAI8 license: BSD3 maintainer: libraries@haskell.org synopsis: Support library for Template Haskell description: This package provides modules containing facilities for manipulating Haskell source code using Template Haskell. . See for more information. category: Template Haskell exposed: True exposed-modules: Language.Haskell.TH Language.Haskell.TH.Lib Language.Haskell.TH.Ppr Language.Haskell.TH.PprLib Language.Haskell.TH.Quote Language.Haskell.TH.Syntax hidden-modules: Language.Haskell.TH.Lib.Map trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/templ_BVMCZyLwIlfGfcqqzyUAI8 library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/templ_BVMCZyLwIlfGfcqqzyUAI8 data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/template-haskell-2.10.0.0 hs-libraries: HStemplate-haskell-2.10.0.0-BVMCZyLwIlfGfcqqzyUAI8 depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a pretty-1.1.2.0-0d4e1eca3b0cfcebe20b9405f7bdaca9 haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/template-haskell-2.10.0.0/template-haskell.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/template-haskell-2.10.0.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: pretty version: 1.1.2.0 id: pretty-1.1.2.0-0d4e1eca3b0cfcebe20b9405f7bdaca9 key: prett_7jIfj8VCGFf1WS0tIQ1XSZ license: BSD3 maintainer: David Terei stability: Stable homepage: http://github.com/haskell/pretty synopsis: Pretty-printing library description: This package contains a pretty-printing library, a set of API's that provides a way to easily print out text in a consistent format of your choosing. This is useful for compilers and related tools. . This library was originally designed by John Hughes's and has since been heavily modified by Simon Peyton Jones. category: Text exposed: True exposed-modules: Text.PrettyPrint Text.PrettyPrint.HughesPJ Text.PrettyPrint.HughesPJClass trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/prett_7jIfj8VCGFf1WS0tIQ1XSZ library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/prett_7jIfj8VCGFf1WS0tIQ1XSZ data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/pretty-1.1.2.0 hs-libraries: HSpretty-1.1.2.0-7jIfj8VCGFf1WS0tIQ1XSZ depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a deepseq-1.4.1.1-c1376f846fa170f2cc2cb2e57b203339 ghc-prim-0.4.0.0-7c945cc0c41d3b7b70f3edd125671166 haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/pretty-1.1.2.0/pretty.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/pretty-1.1.2.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: hpc version: 0.6.0.2 id: hpc-0.6.0.2-ac9064885aa8cb08a93314222939ead4 key: hpc_CmUUQl5bURfBueJrdYfNs3 license: BSD3 maintainer: ghc-devs@haskell.org synopsis: Code Coverage Library for Haskell description: This package provides the code coverage library for Haskell. . See for more information. category: Control author: Andy Gill exposed: True exposed-modules: Trace.Hpc.Util Trace.Hpc.Mix Trace.Hpc.Tix Trace.Hpc.Reflect trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/hpc_CmUUQl5bURfBueJrdYfNs3 library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/hpc_CmUUQl5bURfBueJrdYfNs3 data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/hpc-0.6.0.2 hs-libraries: HShpc-0.6.0.2-CmUUQl5bURfBueJrdYfNs3 depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a containers-0.5.6.2-2114032c163425cc264e6e1169dc2f6d directory-1.2.2.0-b4959b472d9eee380c6b32291ade29e0 filepath-1.4.0.0-40d643aa87258c186441a1f8f3e13ca6 time-1.5.0.1-e17a9220d438435579d2914e90774246 haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/hpc-0.6.0.2/hpc.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/hpc-0.6.0.2 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: process version: 1.2.3.0 id: process-1.2.3.0-3b1e9bca6ac38225806ff7bbf3f845b1 key: proce_0hwN3CTKynhHQqQkChnSdH license: BSD3 maintainer: libraries@haskell.org synopsis: Process libraries description: This package contains libraries for dealing with system processes. category: System exposed: True exposed-modules: System.Cmd System.Process System.Process.Internals trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/proce_0hwN3CTKynhHQqQkChnSdH library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/proce_0hwN3CTKynhHQqQkChnSdH data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/process-1.2.3.0 hs-libraries: HSprocess-1.2.3.0-0hwN3CTKynhHQqQkChnSdH include-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/proce_0hwN3CTKynhHQqQkChnSdH/include includes: runProcess.h depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a deepseq-1.4.1.1-c1376f846fa170f2cc2cb2e57b203339 directory-1.2.2.0-b4959b472d9eee380c6b32291ade29e0 filepath-1.4.0.0-40d643aa87258c186441a1f8f3e13ca6 unix-2.7.1.0-e5915eb989e568b732bc7286b0d0817f haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/process-1.2.3.0/process.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/process-1.2.3.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: directory version: 1.2.2.0 id: directory-1.2.2.0-b4959b472d9eee380c6b32291ade29e0 key: direc_3TcTyYedch32o1zTH2MR00 license: BSD3 maintainer: libraries@haskell.org synopsis: Platform-agnostic library for filesystem operations description: This library provides a basic set of operations for manipulating files and directories in a portable way. category: System exposed: True exposed-modules: System.Directory trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/direc_3TcTyYedch32o1zTH2MR00 library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/direc_3TcTyYedch32o1zTH2MR00 data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/directory-1.2.2.0 hs-libraries: HSdirectory-1.2.2.0-3TcTyYedch32o1zTH2MR00 include-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/direc_3TcTyYedch32o1zTH2MR00/include includes: HsDirectory.h depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a filepath-1.4.0.0-40d643aa87258c186441a1f8f3e13ca6 time-1.5.0.1-e17a9220d438435579d2914e90774246 unix-2.7.1.0-e5915eb989e568b732bc7286b0d0817f haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/directory-1.2.2.0/directory.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/directory-1.2.2.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: unix version: 2.7.1.0 id: unix-2.7.1.0-e5915eb989e568b732bc7286b0d0817f key: unix_G4Yo1pNtYrk8nCq1cx8P9d license: BSD3 maintainer: libraries@haskell.org homepage: https://github.com/haskell/unix synopsis: POSIX functionality description: This package gives you access to the set of operating system services standardised by POSIX 1003.1b (or the IEEE Portable Operating System Interface for Computing Environments - IEEE Std. 1003.1). . The package is not supported under Windows (except under Cygwin). category: System exposed: True exposed-modules: System.Posix System.Posix.ByteString System.Posix.Error System.Posix.Resource System.Posix.Time System.Posix.Unistd System.Posix.User System.Posix.Signals System.Posix.Signals.Exts System.Posix.Semaphore System.Posix.SharedMem System.Posix.ByteString.FilePath System.Posix.Directory System.Posix.Directory.ByteString System.Posix.DynamicLinker.Module System.Posix.DynamicLinker.Module.ByteString System.Posix.DynamicLinker.Prim System.Posix.DynamicLinker.ByteString System.Posix.DynamicLinker System.Posix.Files System.Posix.Files.ByteString System.Posix.IO System.Posix.IO.ByteString System.Posix.Env System.Posix.Env.ByteString System.Posix.Fcntl System.Posix.Process System.Posix.Process.Internals System.Posix.Process.ByteString System.Posix.Temp System.Posix.Temp.ByteString System.Posix.Terminal System.Posix.Terminal.ByteString hidden-modules: System.Posix.Directory.Common System.Posix.DynamicLinker.Common System.Posix.Files.Common System.Posix.IO.Common System.Posix.Process.Common System.Posix.Terminal.Common trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/unix_G4Yo1pNtYrk8nCq1cx8P9d library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/unix_G4Yo1pNtYrk8nCq1cx8P9d data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/unix-2.7.1.0 hs-libraries: HSunix-2.7.1.0-G4Yo1pNtYrk8nCq1cx8P9d extra-libraries: rt util dl pthread include-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/unix_G4Yo1pNtYrk8nCq1cx8P9d/include includes: HsUnix.h execvpe.h depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a bytestring-0.10.6.0-0909f8f31271f3d75749190bf2ee35db time-1.5.0.1-e17a9220d438435579d2914e90774246 haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/unix-2.7.1.0/unix.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/unix-2.7.1.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: time version: 1.5.0.1 id: time-1.5.0.1-e17a9220d438435579d2914e90774246 key: time_Hh2clZW6in4HpYHx5bLtb7 license: BSD3 maintainer: stability: stable homepage: https://github.com/haskell/time synopsis: A time library description: A time library category: System author: Ashley Yakeley exposed: True exposed-modules: Data.Time.Calendar Data.Time.Calendar.MonthDay Data.Time.Calendar.OrdinalDate Data.Time.Calendar.WeekDate Data.Time.Calendar.Julian Data.Time.Calendar.Easter Data.Time.Clock Data.Time.Clock.POSIX Data.Time.Clock.TAI Data.Time.LocalTime Data.Time.Format Data.Time hidden-modules: Data.Time.Calendar.Private Data.Time.Calendar.Days Data.Time.Calendar.Gregorian Data.Time.Calendar.JulianYearDay Data.Time.Clock.Scale Data.Time.Clock.UTC Data.Time.Clock.CTimeval Data.Time.Clock.UTCDiff Data.Time.LocalTime.TimeZone Data.Time.LocalTime.TimeOfDay Data.Time.LocalTime.LocalTime Data.Time.Format.Parse Data.Time.Format.Locale trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/time_Hh2clZW6in4HpYHx5bLtb7 library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/time_Hh2clZW6in4HpYHx5bLtb7 data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/time-1.5.0.1 hs-libraries: HStime-1.5.0.1-Hh2clZW6in4HpYHx5bLtb7 include-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/time_Hh2clZW6in4HpYHx5bLtb7/include depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a deepseq-1.4.1.1-c1376f846fa170f2cc2cb2e57b203339 haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/time-1.5.0.1/time.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/time-1.5.0.1 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: containers version: 0.5.6.2 id: containers-0.5.6.2-2114032c163425cc264e6e1169dc2f6d key: conta_47ajk3tbda43DFWyeF3oHQ license: BSD3 maintainer: fox@ucw.cz synopsis: Assorted concrete container types description: This package contains efficient general-purpose implementations of various basic immutable container types. The declared cost of each operation is either worst-case or amortized, but remains valid even if structures are shared. category: Data Structures exposed: True exposed-modules: Data.IntMap Data.IntMap.Lazy Data.IntMap.Strict Data.IntSet Data.Map Data.Map.Lazy Data.Map.Strict Data.Set Data.Graph Data.Sequence Data.Tree hidden-modules: Data.IntMap.Base Data.IntSet.Base Data.Map.Base Data.Set.Base Data.Utils.BitUtil Data.Utils.StrictFold Data.Utils.StrictPair trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/conta_47ajk3tbda43DFWyeF3oHQ library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/conta_47ajk3tbda43DFWyeF3oHQ data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/containers-0.5.6.2 hs-libraries: HScontainers-0.5.6.2-47ajk3tbda43DFWyeF3oHQ depends: array-0.5.1.0-e29cdbe82692341ebb7ce6e2798294f9 base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a deepseq-1.4.1.1-c1376f846fa170f2cc2cb2e57b203339 ghc-prim-0.4.0.0-7c945cc0c41d3b7b70f3edd125671166 haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/containers-0.5.6.2/containers.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/containers-0.5.6.2 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: bytestring version: 0.10.6.0 id: bytestring-0.10.6.0-0909f8f31271f3d75749190bf2ee35db key: bytes_6vj5EoliHgNHISHCVCb069 license: BSD3 copyright: Copyright (c) Don Stewart 2005-2009, (c) Duncan Coutts 2006-2015, (c) David Roundy 2003-2005, (c) Jasper Van der Jeugt 2010, (c) Simon Meier 2010-2013. maintainer: Duncan Coutts homepage: https://github.com/haskell/bytestring synopsis: Fast, compact, strict and lazy byte strings with a list interface description: An efficient compact, immutable byte string type (both strict and lazy) suitable for binary or 8-bit character data. . The 'ByteString' type represents sequences of bytes or 8-bit characters. It is suitable for high performance use, both in terms of large data quantities, or high speed requirements. The 'ByteString' functions follow the same style as Haskell\'s ordinary lists, so it is easy to convert code from using 'String' to 'ByteString'. . Two 'ByteString' variants are provided: . * Strict 'ByteString's keep the string as a single large array. This makes them convenient for passing data between C and Haskell. . * Lazy 'ByteString's use a lazy list of strict chunks which makes it suitable for I\/O streaming tasks. . The @Char8@ modules provide a character-based view of the same underlying 'ByteString' types. This makes it convenient to handle mixed binary and 8-bit character content (which is common in many file formats and network protocols). . The 'Builder' module provides an efficient way to build up 'ByteString's in an ad-hoc way by repeated concatenation. This is ideal for fast serialisation or pretty printing. . There is also a 'ShortByteString' type which has a lower memory overhead and can can be converted to or from a 'ByteString', but supports very few other operations. It is suitable for keeping many short strings in memory. . 'ByteString's are not designed for Unicode. For Unicode strings you should use the 'Text' type from the @text@ package. . These modules are intended to be imported qualified, to avoid name clashes with "Prelude" functions, e.g. . > import qualified Data.ByteString as BS category: Data author: Don Stewart, Duncan Coutts exposed: True exposed-modules: Data.ByteString Data.ByteString.Char8 Data.ByteString.Unsafe Data.ByteString.Internal Data.ByteString.Lazy Data.ByteString.Lazy.Char8 Data.ByteString.Lazy.Internal Data.ByteString.Short Data.ByteString.Short.Internal Data.ByteString.Builder Data.ByteString.Builder.Extra Data.ByteString.Builder.Prim Data.ByteString.Builder.Internal Data.ByteString.Builder.Prim.Internal Data.ByteString.Lazy.Builder Data.ByteString.Lazy.Builder.Extras Data.ByteString.Lazy.Builder.ASCII hidden-modules: Data.ByteString.Builder.ASCII Data.ByteString.Builder.Prim.Binary Data.ByteString.Builder.Prim.ASCII Data.ByteString.Builder.Prim.Internal.Floating Data.ByteString.Builder.Prim.Internal.UncheckedShifts Data.ByteString.Builder.Prim.Internal.Base16 trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/bytes_6vj5EoliHgNHISHCVCb069 library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/bytes_6vj5EoliHgNHISHCVCb069 data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/bytestring-0.10.6.0 hs-libraries: HSbytestring-0.10.6.0-6vj5EoliHgNHISHCVCb069 include-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/bytes_6vj5EoliHgNHISHCVCb069/include includes: fpstring.h depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a deepseq-1.4.1.1-c1376f846fa170f2cc2cb2e57b203339 ghc-prim-0.4.0.0-7c945cc0c41d3b7b70f3edd125671166 integer-gmp-1.0.0.0-3c947e5fb6dca14804d9b2793c521b67 haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/bytestring-0.10.6.0/bytestring.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/bytestring-0.10.6.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: deepseq version: 1.4.1.1 id: deepseq-1.4.1.1-c1376f846fa170f2cc2cb2e57b203339 key: deeps_FpR4obOZALU1lutWnrBldi license: BSD3 maintainer: libraries@haskell.org synopsis: Deep evaluation of data structures description: This package provides methods for fully evaluating data structures (\"deep evaluation\"). Deep evaluation is often used for adding strictness to a program, e.g. in order to force pending exceptions, remove space leaks, or force lazy I/O to happen. It is also useful in parallel programs, to ensure pending work does not migrate to the wrong thread. . The primary use of this package is via the 'deepseq' function, a \"deep\" version of 'seq'. It is implemented on top of an 'NFData' typeclass (\"Normal Form Data\", data structures with no unevaluated components) which defines strategies for fully evaluating different data types. category: Control exposed: True exposed-modules: Control.DeepSeq trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/deeps_FpR4obOZALU1lutWnrBldi library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/deeps_FpR4obOZALU1lutWnrBldi data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/deepseq-1.4.1.1 hs-libraries: HSdeepseq-1.4.1.1-FpR4obOZALU1lutWnrBldi depends: array-0.5.1.0-e29cdbe82692341ebb7ce6e2798294f9 base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/deepseq-1.4.1.1/deepseq.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/deepseq-1.4.1.1 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: array version: 0.5.1.0 id: array-0.5.1.0-e29cdbe82692341ebb7ce6e2798294f9 key: array_FaHmcBFfuRM8kmZLEY8D5S license: BSD3 maintainer: libraries@haskell.org synopsis: Mutable and immutable arrays description: In addition to providing the "Data.Array" module , this package also defines the classes 'IArray' of immutable arrays and 'MArray' of arrays mutable within appropriate monads, as well as some instances of these classes. category: Data Structures exposed: True exposed-modules: Data.Array Data.Array.Base Data.Array.IArray Data.Array.IO Data.Array.IO.Safe Data.Array.IO.Internals Data.Array.MArray Data.Array.MArray.Safe Data.Array.ST Data.Array.ST.Safe Data.Array.Storable Data.Array.Storable.Safe Data.Array.Storable.Internals Data.Array.Unboxed Data.Array.Unsafe trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/array_FaHmcBFfuRM8kmZLEY8D5S library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/array_FaHmcBFfuRM8kmZLEY8D5S data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/array-0.5.1.0 hs-libraries: HSarray-0.5.1.0-FaHmcBFfuRM8kmZLEY8D5S depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/array-0.5.1.0/array.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/array-0.5.1.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: filepath version: 1.4.0.0 id: filepath-1.4.0.0-40d643aa87258c186441a1f8f3e13ca6 key: filep_5HhyRonfEZoDO205Wm9E4h license: BSD3 copyright: Neil Mitchell 2005-2015 maintainer: Neil Mitchell homepage: https://github.com/haskell/filepath#readme synopsis: Library for manipulating FilePaths in a cross platform way. description: This package provides functionality for manipulating @FilePath@ values, and is shipped with both and the . It provides three modules: . * "System.FilePath.Posix" manipulates POSIX\/Linux style @FilePath@ values (with @\/@ as the path separator). . * "System.FilePath.Windows" manipulates Windows style @FilePath@ values (with either @\\@ or @\/@ as the path separator, and deals with drives). . * "System.FilePath" is an alias for the module appropriate to your platform. . All three modules provide the same API, and the same documentation (calling out differences in the different variants). category: System author: Neil Mitchell exposed: True exposed-modules: System.FilePath System.FilePath.Posix System.FilePath.Windows trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/filep_5HhyRonfEZoDO205Wm9E4h library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/filep_5HhyRonfEZoDO205Wm9E4h data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/filepath-1.4.0.0 hs-libraries: HSfilepath-1.4.0.0-5HhyRonfEZoDO205Wm9E4h depends: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/filepath-1.4.0.0/filepath.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/filepath-1.4.0.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: base version: 4.8.0.0 id: base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a key: base_I5BErHzyOm07EBNpKBEeUv license: BSD3 maintainer: libraries@haskell.org synopsis: Basic libraries description: This package contains the "Prelude" and its support libraries, and a large collection of useful libraries ranging from data structures to parsing combinators and debugging utilities. category: Prelude exposed: True exposed-modules: Control.Applicative Control.Arrow Control.Category Control.Concurrent Control.Concurrent.Chan Control.Concurrent.MVar Control.Concurrent.QSem Control.Concurrent.QSemN Control.Exception Control.Exception.Base Control.Monad Control.Monad.Fix Control.Monad.Instances Control.Monad.ST Control.Monad.ST.Lazy Control.Monad.ST.Lazy.Safe Control.Monad.ST.Lazy.Unsafe Control.Monad.ST.Safe Control.Monad.ST.Strict Control.Monad.ST.Unsafe Control.Monad.Zip Data.Bifunctor Data.Bits Data.Bool Data.Char Data.Coerce Data.Complex Data.Data Data.Dynamic Data.Either Data.Eq Data.Fixed Data.Foldable Data.Function Data.Functor Data.Functor.Identity Data.IORef Data.Int Data.Ix Data.List Data.Maybe Data.Monoid Data.Ord Data.Proxy Data.Ratio Data.STRef Data.STRef.Lazy Data.STRef.Strict Data.String Data.Traversable Data.Tuple Data.Type.Bool Data.Type.Coercion Data.Type.Equality Data.Typeable Data.Typeable.Internal Data.Unique Data.Version Data.Void Data.Word Debug.Trace Foreign Foreign.C Foreign.C.Error Foreign.C.String Foreign.C.Types Foreign.Concurrent Foreign.ForeignPtr Foreign.ForeignPtr.Safe Foreign.ForeignPtr.Unsafe Foreign.Marshal Foreign.Marshal.Alloc Foreign.Marshal.Array Foreign.Marshal.Error Foreign.Marshal.Pool Foreign.Marshal.Safe Foreign.Marshal.Unsafe Foreign.Marshal.Utils Foreign.Ptr Foreign.Safe Foreign.StablePtr Foreign.Storable GHC.Arr GHC.Base GHC.Char GHC.Conc GHC.Conc.IO GHC.Conc.Signal GHC.Conc.Sync GHC.ConsoleHandler GHC.Constants GHC.Desugar GHC.Enum GHC.Environment GHC.Err GHC.Exception GHC.Exts GHC.Fingerprint GHC.Fingerprint.Type GHC.Float GHC.Float.ConversionUtils GHC.Float.RealFracMethods GHC.Foreign GHC.ForeignPtr GHC.GHCi GHC.Generics GHC.IO GHC.IO.Buffer GHC.IO.BufferedIO GHC.IO.Device GHC.IO.Encoding GHC.IO.Encoding.CodePage GHC.IO.Encoding.Failure GHC.IO.Encoding.Iconv GHC.IO.Encoding.Latin1 GHC.IO.Encoding.Types GHC.IO.Encoding.UTF16 GHC.IO.Encoding.UTF32 GHC.IO.Encoding.UTF8 GHC.IO.Exception GHC.IO.FD GHC.IO.Handle GHC.IO.Handle.FD GHC.IO.Handle.Internals GHC.IO.Handle.Text GHC.IO.Handle.Types GHC.IO.IOMode GHC.IOArray GHC.IORef GHC.IP GHC.Int GHC.List GHC.MVar GHC.Natural GHC.Num GHC.OldList GHC.PArr GHC.Pack GHC.Profiling GHC.Ptr GHC.Read GHC.Real GHC.RTS.Flags GHC.ST GHC.StaticPtr GHC.STRef GHC.Show GHC.Stable GHC.Stack GHC.Stats GHC.Storable GHC.TopHandler GHC.TypeLits GHC.Unicode GHC.Weak GHC.Word Numeric Numeric.Natural Prelude System.CPUTime System.Console.GetOpt System.Environment System.Exit System.IO System.IO.Error System.IO.Unsafe System.Info System.Mem System.Mem.StableName System.Mem.Weak System.Posix.Internals System.Posix.Types System.Timeout Text.ParserCombinators.ReadP Text.ParserCombinators.ReadPrec Text.Printf Text.Read Text.Read.Lex Text.Show Text.Show.Functions Unsafe.Coerce GHC.Event hidden-modules: Control.Monad.ST.Imp Control.Monad.ST.Lazy.Imp Data.OldList Foreign.ForeignPtr.Imp System.Environment.ExecutablePath GHC.Event.Arr GHC.Event.Array GHC.Event.Clock GHC.Event.Control GHC.Event.EPoll GHC.Event.IntTable GHC.Event.Internal GHC.Event.KQueue GHC.Event.Manager GHC.Event.PSQ GHC.Event.Poll GHC.Event.Thread GHC.Event.TimerManager GHC.Event.Unique trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/base_I5BErHzyOm07EBNpKBEeUv library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/base_I5BErHzyOm07EBNpKBEeUv data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/base-4.8.0.0 hs-libraries: HSbase-4.8.0.0-I5BErHzyOm07EBNpKBEeUv include-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/base_I5BErHzyOm07EBNpKBEeUv/include includes: HsBase.h depends: builtin_rts ghc-prim-0.4.0.0-7c945cc0c41d3b7b70f3edd125671166 integer-gmp-1.0.0.0-3c947e5fb6dca14804d9b2793c521b67 haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/base-4.8.0.0/base.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/base-4.8.0.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: integer-gmp version: 1.0.0.0 id: integer-gmp-1.0.0.0-3c947e5fb6dca14804d9b2793c521b67 key: integ_2aU3IZNMF9a7mQ0OzsZ0dS license: BSD3 maintainer: hvr@gnu.org synopsis: Integer library based on GMP category: Numeric, Algebra author: Herbert Valerio Riedel exposed: True exposed-modules: GHC.Integer GHC.Integer.Logarithms GHC.Integer.Logarithms.Internals GHC.Integer.GMP.Internals hidden-modules: GHC.Integer.Type trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/integ_2aU3IZNMF9a7mQ0OzsZ0dS library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/integ_2aU3IZNMF9a7mQ0OzsZ0dS data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/integer-gmp-1.0.0.0 hs-libraries: HSinteger-gmp-1.0.0.0-2aU3IZNMF9a7mQ0OzsZ0dS extra-libraries: gmp include-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/integ_2aU3IZNMF9a7mQ0OzsZ0dS/include depends: ghc-prim-0.4.0.0-7c945cc0c41d3b7b70f3edd125671166 haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/integer-gmp-1.0.0.0/integer-gmp.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/integer-gmp-1.0.0.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: ghc-prim version: 0.4.0.0 id: ghc-prim-0.4.0.0-7c945cc0c41d3b7b70f3edd125671166 key: ghcpr_8TmvWUcS1U1IKHT0levwg3 license: BSD3 maintainer: libraries@haskell.org synopsis: GHC primitives description: GHC primitives. category: GHC exposed: True exposed-modules: GHC.CString GHC.Classes GHC.Debug GHC.IntWord64 GHC.Magic GHC.PrimopWrappers GHC.Tuple GHC.Types GHC.Prim trusted: False import-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/ghcpr_8TmvWUcS1U1IKHT0levwg3 library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/ghcpr_8TmvWUcS1U1IKHT0levwg3 data-dir: /opt/ghc/7.10.1/share/x86_64-linux-ghc-7.10.1/ghc-prim-0.4.0.0 hs-libraries: HSghc-prim-0.4.0.0-8TmvWUcS1U1IKHT0levwg3 depends: builtin_rts haddock-interfaces: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/ghc-prim-0.4.0.0/ghc-prim.haddock haddock-html: /opt/ghc/7.10.1/share/doc/ghc/html/libraries/ghc-prim-0.4.0.0 pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" --- name: rts version: 1.0 id: builtin_rts key: rts license: BSD3 maintainer: glasgow-haskell-users@haskell.org exposed: True trusted: False library-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/rts hs-libraries: HSrts Cffi extra-libraries: m rt dl include-dirs: /opt/ghc/7.10.1/lib/ghc-7.10.1/include includes: Stg.h ld-options: "-Wl,-u,ghczmprim_GHCziTypes_Izh_static_info" "-Wl,-u,ghczmprim_GHCziTypes_Czh_static_info" "-Wl,-u,ghczmprim_GHCziTypes_Fzh_static_info" "-Wl,-u,ghczmprim_GHCziTypes_Dzh_static_info" "-Wl,-u,base_GHCziPtr_Ptr_static_info" "-Wl,-u,ghczmprim_GHCziTypes_Wzh_static_info" "-Wl,-u,base_GHCziInt_I8zh_static_info" "-Wl,-u,base_GHCziInt_I16zh_static_info" "-Wl,-u,base_GHCziInt_I32zh_static_info" "-Wl,-u,base_GHCziInt_I64zh_static_info" "-Wl,-u,base_GHCziWord_W8zh_static_info" "-Wl,-u,base_GHCziWord_W16zh_static_info" "-Wl,-u,base_GHCziWord_W32zh_static_info" "-Wl,-u,base_GHCziWord_W64zh_static_info" "-Wl,-u,base_GHCziStable_StablePtr_static_info" "-Wl,-u,ghczmprim_GHCziTypes_Izh_con_info" "-Wl,-u,ghczmprim_GHCziTypes_Czh_con_info" "-Wl,-u,ghczmprim_GHCziTypes_Fzh_con_info" "-Wl,-u,ghczmprim_GHCziTypes_Dzh_con_info" "-Wl,-u,base_GHCziPtr_Ptr_con_info" "-Wl,-u,base_GHCziPtr_FunPtr_con_info" "-Wl,-u,base_GHCziStable_StablePtr_con_info" "-Wl,-u,ghczmprim_GHCziTypes_False_closure" "-Wl,-u,ghczmprim_GHCziTypes_True_closure" "-Wl,-u,base_GHCziPack_unpackCString_closure" "-Wl,-u,base_GHCziIOziException_stackOverflow_closure" "-Wl,-u,base_GHCziIOziException_heapOverflow_closure" "-Wl,-u,base_ControlziExceptionziBase_nonTermination_closure" "-Wl,-u,base_GHCziIOziException_blockedIndefinitelyOnMVar_closure" "-Wl,-u,base_GHCziIOziException_blockedIndefinitelyOnSTM_closure" "-Wl,-u,base_GHCziIOziException_allocationLimitExceeded_closure" "-Wl,-u,base_ControlziExceptionziBase_nestedAtomically_closure" "-Wl,-u,base_GHCziEventziThread_blockedOnBadFD_closure" "-Wl,-u,base_GHCziWeak_runFinalizzerBatch_closure" "-Wl,-u,base_GHCziTopHandler_flushStdHandles_closure" "-Wl,-u,base_GHCziTopHandler_runIO_closure" "-Wl,-u,base_GHCziTopHandler_runNonIO_closure" "-Wl,-u,base_GHCziConcziIO_ensureIOManagerIsRunning_closure" "-Wl,-u,base_GHCziConcziIO_ioManagerCapabilitiesChanged_closure" "-Wl,-u,base_GHCziConcziSync_runSparks_closure" "-Wl,-u,base_GHCziConcziSignal_runHandlersPtr_closure" pkgroot: "/opt/ghc/7.10.1/lib/ghc-7.10.1" stack-1.5.1/stack.yaml0000644000000000000000000000064713140560214013005 0ustar0000000000000000resolver: lts-8.22 # docker: # enable: true # repo: fpco/stack-full # image: # containers: # - base: "fpco/stack-base" # see ./etc/docker/stack-base/Dockerfile # name: "fpco/stack-test" nix: # --nix on the command-line to enable. enable: false packages: - zlib flags: stack: hide-dependency-versions: true mintty: win32-2-5: false extra-deps: - mintty-0.1.1 - unicode-transforms-0.3.2