diff --git a/Cabal/Distribution/Simple/Configure.hs b/Cabal/Distribution/Simple/Configure.hs index a7339748eb171c863b89e88ef4fc9e046c057c5b..69582cb392b0d8de8221eda6536b183889a1210f 100644 --- a/Cabal/Distribution/Simple/Configure.hs +++ b/Cabal/Distribution/Simple/Configure.hs @@ -1,4 +1,6 @@ +{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE OverloadedStrings #-} + ----------------------------------------------------------------------------- -- | -- Module : Distribution.Simple.Configure @@ -24,6 +26,7 @@ module Distribution.Simple.Configure (configure, writePersistBuildConfig, + getConfigStateFile, getPersistBuildConfig, checkPersistBuildConfigOutdated, tryGetPersistBuildConfig, @@ -35,9 +38,7 @@ module Distribution.Simple.Configure (configure, ccLdOptionsBuildInfo, checkForeignDeps, interpretPackageDbFlags, - - ConfigStateFileErrorType(..), - ConfigStateFileError, + ConfigStateFileError(..), tryGetConfigStateFile, platformDefines, ) @@ -116,9 +117,11 @@ import qualified Distribution.Simple.HaskellSuite as HaskellSuite -- Prefer the more generic Data.Traversable.mapM to Prelude.mapM import Prelude hiding ( mapM ) +import Control.Exception + ( ErrorCall(..), Exception, evaluate, throw, throwIO, try ) import Control.Monad ( liftM, when, unless, foldM, filterM ) -import Data.Binary ( Binary, decodeOrFail, encode ) +import Data.Binary ( decodeOrFail, encode ) import Data.ByteString.Lazy (ByteString) import qualified Data.ByteString.Lazy.Char8 as BS import Data.List @@ -134,6 +137,7 @@ import qualified Data.Map as Map import Data.Map (Map) import Data.Traversable ( mapM ) +import Data.Typeable import System.Directory ( doesFileExist, createDirectoryIfMissing, getTemporaryDirectory ) import System.FilePath @@ -149,83 +153,85 @@ import Text.PrettyPrint , quotes, punctuate, nest, sep, hsep ) import Distribution.Compat.Exception ( catchExit, catchIO ) -data ConfigStateFileErrorType = ConfigStateFileCantParse - | ConfigStateFileMissing - | ConfigStateFileBadVersion - deriving Eq -type ConfigStateFileError = (String, ConfigStateFileErrorType) - -tryGetConfigStateFile :: (Binary a) => FilePath - -> IO (Either ConfigStateFileError a) -tryGetConfigStateFile filename = do - exists <- doesFileExist filename - if not exists - then return missing - else liftM decodeBody decodeHeader - where - decodeBody :: Binary a => Either ConfigStateFileError BS.ByteString - -> Either ConfigStateFileError a - decodeBody (Left err) = Left err - decodeBody (Right body) = - case decodeOrFail body of - Left _ -> cantParseBody - Right (_, _, x) -> Right x - - decodeHeader :: IO (Either ConfigStateFileError BS.ByteString) - decodeHeader = do - (header, body) <- liftM (BS.span $ (/=) '\n') $ BS.readFile filename - return $ case parseHeader header of - Nothing -> cantParseHeader - Just (cabalId, compId) - | (cabalId /= currentCabalId) || (compId /= currentCompilerId) -> - badVersion cabalId compId - | otherwise -> Right $ BS.tail body - - missing = Left ( "Run the 'configure' command first." - , ConfigStateFileMissing ) - cantParseHeader = Left - ( "Saved package config file header is corrupt." +data ConfigStateFileError + = ConfigStateFileNoHeader + | ConfigStateFileBadHeader + | ConfigStateFileNoParse + | ConfigStateFileMissing + | ConfigStateFileBadVersion PackageIdentifier PackageIdentifier (Either ConfigStateFileError LocalBuildInfo) + deriving (Typeable) + +instance Show ConfigStateFileError where + show ConfigStateFileNoHeader = + "Saved package config file header is missing. " + ++ "Try re-running the 'configure' command." + show ConfigStateFileBadHeader = + "Saved package config file header is corrupt. " ++ "Try re-running the 'configure' command." - , ConfigStateFileCantParse - ) - cantParseBody = Left - ( "Saved package config file body is corrupt." + show ConfigStateFileNoParse = + "Saved package config file body is corrupt. " ++ "Try re-running the 'configure' command." - , ConfigStateFileCantParse - ) - badVersion cabalId compId - = Left ( "You need to re-run the 'configure' command. " - ++ "The version of Cabal being used has changed (was " - ++ display cabalId ++ ", now " - ++ display currentCabalId ++ ")." - ++ badcompiler compId - , ConfigStateFileBadVersion ) - badcompiler compId | compId == currentCompilerId = "" - | otherwise - = " Additionally the compiler is different (was " - ++ display compId ++ ", now " - ++ display currentCompilerId - ++ ") which is probably the cause of the problem." + show ConfigStateFileMissing = "Run the 'configure' command first." + show (ConfigStateFileBadVersion oldCabal oldCompiler _) = + "You need to re-run the 'configure' command. " + ++ "The version of Cabal being used has changed (was " + ++ display oldCabal ++ ", now " + ++ display currentCabalId ++ ")." + ++ badCompiler + where + badCompiler + | oldCompiler == currentCompilerId = "" + | otherwise = + " Additionally the compiler is different (was " + ++ display oldCompiler ++ ", now " + ++ display currentCompilerId + ++ ") which is probably the cause of the problem." + +instance Exception ConfigStateFileError + +getConfigStateFile :: FilePath -> IO LocalBuildInfo +getConfigStateFile filename = do + exists <- doesFileExist filename + unless exists $ throwIO ConfigStateFileMissing + + (header, body) <- liftM (BS.span $ (/=) '\n') $ BS.readFile filename + + headerParseResult <- try $ evaluate $ parseHeader header + let (cabalId, compId) = + case headerParseResult of + Left (ErrorCall _) -> throw ConfigStateFileBadHeader + Right x -> x + + let getStoredValue = evaluate $ + case decodeOrFail (BS.tail body) of + Left _ -> throw ConfigStateFileNoParse + Right (_, _, x) -> x + deferErrorIfBadVersion act + | cabalId /= currentCabalId || compId /= currentCompilerId = do + eResult <- try act + throw $ ConfigStateFileBadVersion cabalId compId eResult + | otherwise = act + deferErrorIfBadVersion getStoredValue + +tryGetConfigStateFile :: FilePath + -> IO (Either ConfigStateFileError LocalBuildInfo) +tryGetConfigStateFile = try . getConfigStateFile -- |Try to read the 'localBuildInfoFile'. tryGetPersistBuildConfig :: FilePath - -> IO (Either ConfigStateFileError LocalBuildInfo) -tryGetPersistBuildConfig distPref - = tryGetConfigStateFile (localBuildInfoFile distPref) + -> IO (Either ConfigStateFileError LocalBuildInfo) +tryGetPersistBuildConfig = try . getPersistBuildConfig --- |Read the 'localBuildInfoFile'. Error if it doesn't exist. Also --- fail if the file containing LocalBuildInfo is older than the .cabal --- file, indicating that a re-configure is required. +-- | Read the 'localBuildInfoFile'. Throw an exception if the file is +-- missing, if the file cannot be read, or if the file was created by an older +-- version of Cabal. getPersistBuildConfig :: FilePath -> IO LocalBuildInfo -getPersistBuildConfig distPref = do - lbi <- tryGetPersistBuildConfig distPref - either (die . fst) return lbi +getPersistBuildConfig = getConfigStateFile . localBuildInfoFile -- |Try to read the 'localBuildInfoFile'. maybeGetPersistBuildConfig :: FilePath -> IO (Maybe LocalBuildInfo) -maybeGetPersistBuildConfig distPref = do - lbi <- tryGetPersistBuildConfig distPref - return $ either (const Nothing) Just lbi +maybeGetPersistBuildConfig = + liftM (either (const Nothing) Just) . tryGetPersistBuildConfig -- |After running configure, output the 'LocalBuildInfo' to the -- 'localBuildInfoFile'. @@ -244,14 +250,15 @@ currentCompilerId :: PackageIdentifier currentCompilerId = PackageIdentifier (PackageName System.Info.compilerName) System.Info.compilerVersion -parseHeader :: ByteString -> Maybe (PackageIdentifier, PackageIdentifier) +parseHeader :: ByteString -> (PackageIdentifier, PackageIdentifier) parseHeader header = case BS.words header of - ["Saved", "package", "config", "for", pkgId, "written", "by", cabalId, "using", compId] -> do - _ <- simpleParse (BS.unpack pkgId) :: Maybe PackageIdentifier - cabalId' <- simpleParse (BS.unpack cabalId) - compId' <- simpleParse (BS.unpack compId) - return (cabalId', compId') - _ -> Nothing + ["Saved", "package", "config", "for", pkgId, "written", "by", cabalId, "using", compId] -> + fromMaybe (throw ConfigStateFileBadHeader) $ do + _ <- simpleParse (BS.unpack pkgId) :: Maybe PackageIdentifier + cabalId' <- simpleParse (BS.unpack cabalId) + compId' <- simpleParse (BS.unpack compId) + return (cabalId', compId') + _ -> throw ConfigStateFileNoHeader showHeader :: PackageIdentifier -> ByteString showHeader pkgId = BS.unwords diff --git a/Cabal/tests/PackageTests.hs b/Cabal/tests/PackageTests.hs index b3f50705d2543ecb56eb68b5246a7534f6fcc143..b2a599146ac5e756f6da786290556e36261ea8fb 100644 --- a/Cabal/tests/PackageTests.hs +++ b/Cabal/tests/PackageTests.hs @@ -36,21 +36,21 @@ import PackageTests.TestSuiteExeV10.Check import PackageTests.OrderFlags.Check import PackageTests.ReexportedModules.Check -import Distribution.Package (PackageIdentifier) +import Distribution.Simple.Configure + ( ConfigStateFileError(..), getConfigStateFile ) import Distribution.Simple.LocalBuildInfo (LocalBuildInfo(..)) import Distribution.Simple.Program.Types (programPath) -import Distribution.Simple.Program.Builtin (ghcProgram, ghcPkgProgram, - haddockProgram) +import Distribution.Simple.Program.Builtin + ( ghcProgram, ghcPkgProgram, haddockProgram ) import Distribution.Simple.Program.Db (requireProgram) -import Distribution.Simple.Utils (cabalVersion, die) +import Distribution.Simple.Utils (cabalVersion) import Distribution.Text (display) import Distribution.Verbosity (normal) import Distribution.Version (Version(Version)) -import Data.Binary (Binary, decodeOrFail) -import qualified Data.ByteString.Lazy as BS -import System.Directory (doesFileExist, getCurrentDirectory, - setCurrentDirectory) +import Control.Exception (try, throw) +import System.Directory + ( getCurrentDirectory, setCurrentDirectory ) import System.FilePath ((</>)) import System.IO (BufferMode(NoBuffering), hSetBuffering, stdout) import Test.Framework (Test, TestName, defaultMain, testGroup) @@ -169,30 +169,9 @@ main = do -- we run Cabal's own test suite, due to bootstrapping issues. getPersistBuildConfig_ :: FilePath -> IO LocalBuildInfo getPersistBuildConfig_ filename = do - exists <- doesFileExist filename - if not exists - then die "Run the 'configure' command first." - else decodeBinHeader >>= decodeBody - - where - decodeB :: Binary a => BS.ByteString -> Either String (BS.ByteString, a) - decodeB str = either (const cantParse) return $ do - (next, _, x) <- decodeOrFail str - return (next, x) - - decodeBody :: Either String BS.ByteString -> IO LocalBuildInfo - decodeBody (Left msg) = die msg - decodeBody (Right body) = either die (return . snd) $ decodeB body - - decodeBinHeader :: IO (Either String BS.ByteString) - decodeBinHeader = do - pbc <- BS.readFile filename - return $ do - (body, _) <- decodeB pbc :: Either String ( BS.ByteString - , ( PackageIdentifier - , PackageIdentifier ) - ) - return body - - cantParse = Left $ "Saved package config file seems to be corrupt. " - ++ "Try re-running the 'configure' command." + eLBI <- try $ getConfigStateFile filename + case eLBI of + Left (ConfigStateFileBadVersion _ _ (Right lbi)) -> return lbi + Left (ConfigStateFileBadVersion _ _ (Left err)) -> throw err + Left err -> throw err + Right lbi -> return lbi diff --git a/cabal-install/Main.hs b/cabal-install/Main.hs index ea04c9b76edfdd9cb7d151f68b924366d385c64e..e2c29940cd4f9eb591b090e0e5d221d47cfaeb42 100644 --- a/cabal-install/Main.hs +++ b/cabal-install/Main.hs @@ -125,7 +125,7 @@ import Distribution.Simple.Compiler ( Compiler(..) ) import Distribution.Simple.Configure ( checkPersistBuildConfigOutdated, configCompilerAuxEx - , ConfigStateFileErrorType(..), localBuildInfoFile + , ConfigStateFileError(..), localBuildInfoFile , getPersistBuildConfig, tryGetPersistBuildConfig ) import qualified Distribution.Simple.LocalBuildInfo as LBI import Distribution.Simple.Program (defaultProgramConfiguration) @@ -468,8 +468,8 @@ reconfigure verbosity distPref addConfigFlags extraArgs globalFlags skipAddSourceDepsCheck numJobsFlag checkFlags = do eLbi <- tryGetPersistBuildConfig distPref case eLbi of - Left (err, errCode) -> onNoBuildConfig err errCode - Right lbi -> onBuildConfig lbi + Left err -> onNoBuildConfig err + Right lbi -> onBuildConfig lbi where @@ -477,17 +477,16 @@ reconfigure verbosity distPref addConfigFlags extraArgs globalFlags -- -- If we're in a sandbox: add-source deps don't have to be reinstalled -- (since we don't know the compiler & platform). - onNoBuildConfig :: String -> ConfigStateFileErrorType - -> IO (UseSandbox, SavedConfig) - onNoBuildConfig err errCode = do - let msg = case errCode of - ConfigStateFileMissing -> "Package has never been configured." - ConfigStateFileCantParse -> "Saved package config file seems " - ++ "to be corrupt." - ConfigStateFileBadVersion -> err - case errCode of - ConfigStateFileBadVersion -> info verbosity msg - _ -> do + onNoBuildConfig :: ConfigStateFileError -> IO (UseSandbox, SavedConfig) + onNoBuildConfig err = do + let msg = case err of + ConfigStateFileMissing -> "Package has never been configured." + ConfigStateFileNoParse -> "Saved package config file seems " + ++ "to be corrupt." + _ -> show err + case err of + ConfigStateFileBadVersion _ _ _ -> info verbosity msg + _ -> do notice verbosity $ msg ++ " Configuring with default flags." ++ configureManually configureAction (defaultFlags, defaultConfigExFlags)