cabal-install-2.0.0.1: Distribution/Client/SetupWrapper.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
-----------------------------------------------------------------------------
-- |
-- Module : Distribution.Client.SetupWrapper
-- Copyright : (c) The University of Glasgow 2006,
-- Duncan Coutts 2008
--
-- Maintainer : cabal-devel@haskell.org
-- Stability : alpha
-- Portability : portable
--
-- An interface to building and installing Cabal packages.
-- If the @Built-Type@ field is specified as something other than
-- 'Custom', and the current version of Cabal is acceptable, this performs
-- setup actions directly. Otherwise it builds the setup script and
-- runs it with the given arguments.
module Distribution.Client.SetupWrapper (
getSetup, runSetup, runSetupCommand, setupWrapper,
SetupScriptOptions(..),
defaultSetupScriptOptions,
) where
import Prelude ()
import Distribution.Client.Compat.Prelude
import qualified Distribution.Make as Make
import qualified Distribution.Simple as Simple
import Distribution.Version
( Version, mkVersion, versionNumbers, VersionRange, anyVersion
, intersectVersionRanges, orLaterVersion
, withinRange )
import qualified Distribution.Backpack as Backpack
import Distribution.Package
( newSimpleUnitId, unsafeMkDefUnitId, ComponentId, PackageId, mkPackageName
, PackageIdentifier(..), packageVersion, packageName )
import Distribution.Types.Dependency
import Distribution.PackageDescription
( GenericPackageDescription(packageDescription)
, PackageDescription(..), specVersion
, BuildType(..), knownBuildTypes, defaultRenaming )
#ifdef CABAL_PARSEC
import Distribution.PackageDescription.Parsec
( readGenericPackageDescription )
#else
import Distribution.PackageDescription.Parse
( readGenericPackageDescription )
#endif
import Distribution.Simple.Configure
( configCompilerEx )
import Distribution.Compiler
( buildCompilerId, CompilerFlavor(GHC, GHCJS) )
import Distribution.Simple.Compiler
( Compiler(compilerId), compilerFlavor, PackageDB(..), PackageDBStack )
import Distribution.Simple.PreProcess
( runSimplePreProcessor, ppUnlit )
import Distribution.Simple.Build.Macros
( generatePackageVersionMacros )
import Distribution.Simple.Program
( ProgramDb, emptyProgramDb
, getProgramSearchPath, getDbProgramOutput, runDbProgram, ghcProgram
, ghcjsProgram )
import Distribution.Simple.Program.Find
( programSearchPathAsPATHVar, ProgramSearchPathEntry(ProgramSearchPathDir) )
import Distribution.Simple.Program.Run
( getEffectiveEnvironment )
import qualified Distribution.Simple.Program.Strip as Strip
import Distribution.Simple.BuildPaths
( defaultDistPref, exeExtension )
import Distribution.Simple.Command
( CommandUI(..), commandShowOptions )
import Distribution.Simple.Program.GHC
( GhcMode(..), GhcOptions(..), renderGhcOptions )
import qualified Distribution.Simple.PackageIndex as PackageIndex
import Distribution.Simple.PackageIndex (InstalledPackageIndex)
import qualified Distribution.InstalledPackageInfo as IPI
import Distribution.Client.Types
import Distribution.Client.Config
( defaultCabalDir )
import Distribution.Client.IndexUtils
( getInstalledPackages )
import Distribution.Client.JobControl
( Lock, criticalSection )
import Distribution.Simple.Setup
( Flag(..) )
import Distribution.Simple.Utils
( die', debug, info, infoNoWrap, cabalVersion, tryFindPackageDesc, comparing
, createDirectoryIfMissingVerbose, installExecutableFile
, copyFileVerbose, rewriteFileEx )
import Distribution.Client.Utils
( inDir, tryCanonicalizePath, withExtraPathEnv
, existsAndIsMoreRecentThan, moreRecentFile, withEnv
#ifdef mingw32_HOST_OS
, canonicalizePathNoThrow
#endif
)
import Distribution.ReadE
import Distribution.System ( Platform(..), buildPlatform )
import Distribution.Text
( display )
import Distribution.Utils.NubList
( toNubListR )
import Distribution.Verbosity
import Distribution.Compat.Exception
( catchIO )
import Distribution.Compat.Stack
import System.Directory ( doesFileExist )
import System.FilePath ( (</>), (<.>) )
import System.IO ( Handle, hPutStr )
import System.Exit ( ExitCode(..), exitWith )
import System.Process ( createProcess, StdStream(..), proc, waitForProcess
, ProcessHandle )
import qualified System.Process as Process
import Data.List ( foldl1' )
import Distribution.Client.Compat.ExecutablePath ( getExecutablePath )
#ifdef mingw32_HOST_OS
import Distribution.Simple.Utils
( withTempDirectory )
import Control.Exception ( bracket )
import System.FilePath ( equalFilePath, takeDirectory )
import System.Directory ( doesDirectoryExist )
import qualified System.Win32 as Win32
#endif
-- | @Setup@ encapsulates the outcome of configuring a setup method to build a
-- particular package.
data Setup = Setup { setupMethod :: SetupMethod
, setupScriptOptions :: SetupScriptOptions
, setupVersion :: Version
, setupBuildType :: BuildType
, setupPackage :: PackageDescription
}
-- | @SetupMethod@ represents one of the methods used to run Cabal commands.
data SetupMethod = InternalMethod
-- ^ run Cabal commands through \"cabal\" in the
-- current process
| SelfExecMethod
-- ^ run Cabal commands through \"cabal\" as a
-- child process
| ExternalMethod FilePath
-- ^ run Cabal commands through a custom \"Setup\" executable
--TODO: The 'setupWrapper' and 'SetupScriptOptions' should be split into two
-- parts: one that has no policy and just does as it's told with all the
-- explicit options, and an optional initial part that applies certain
-- policies (like if we should add the Cabal lib as a dep, and if so which
-- version). This could be structured as an action that returns a fully
-- elaborated 'SetupScriptOptions' containing no remaining policy choices.
--
-- See also the discussion at https://github.com/haskell/cabal/pull/3094
-- | @SetupScriptOptions@ are options used to configure and run 'Setup', as
-- opposed to options given to the Cabal command at runtime.
data SetupScriptOptions = SetupScriptOptions {
-- | The version of the Cabal library to use (if 'useDependenciesExclusive'
-- is not set). A suitable version of the Cabal library must be installed
-- (or for some build-types be the one cabal-install was built with).
--
-- The version found also determines the version of the Cabal specification
-- that we us for talking to the Setup.hs, unless overridden by
-- 'useCabalSpecVersion'.
--
useCabalVersion :: VersionRange,
-- | This is the version of the Cabal specification that we believe that
-- this package uses. This affects the semantics and in particular the
-- Setup command line interface.
--
-- This is similar to 'useCabalVersion' but instead of probing the system
-- for a version of the /Cabal library/ you just say exactly which version
-- of the /spec/ we will use. Using this also avoid adding the Cabal
-- library as an additional dependency, so add it to 'useDependencies'
-- if needed.
--
useCabalSpecVersion :: Maybe Version,
useCompiler :: Maybe Compiler,
usePlatform :: Maybe Platform,
usePackageDB :: PackageDBStack,
usePackageIndex :: Maybe InstalledPackageIndex,
useProgramDb :: ProgramDb,
useDistPref :: FilePath,
useLoggingHandle :: Maybe Handle,
useWorkingDir :: Maybe FilePath,
-- | Extra things to add to PATH when invoking the setup script.
useExtraPathEnv :: [FilePath],
forceExternalSetupMethod :: Bool,
-- | List of dependencies to use when building Setup.hs.
useDependencies :: [(ComponentId, PackageId)],
-- | Is the list of setup dependencies exclusive?
--
-- When this is @False@, if we compile the Setup.hs script we do so with the
-- list in 'useDependencies' but all other packages in the environment are
-- also visible. A suitable version of @Cabal@ library (see
-- 'useCabalVersion') is also added to the list of dependencies, unless
-- 'useDependencies' already contains a Cabal dependency.
--
-- When @True@, only the 'useDependencies' packages are used, with other
-- packages in the environment hidden.
--
-- This feature is here to support the setup stanza in .cabal files that
-- specifies explicit (and exclusive) dependencies, as well as the old
-- style with no dependencies.
useDependenciesExclusive :: Bool,
-- | Should we build the Setup.hs with CPP version macros available?
-- We turn this on when we have a setup stanza in .cabal that declares
-- explicit setup dependencies.
--
useVersionMacros :: Bool,
-- Used only by 'cabal clean' on Windows.
--
-- Note: win32 clean hack
-------------------------
-- On Windows, running './dist/setup/setup clean' doesn't work because the
-- setup script will try to delete itself (which causes it to fail horribly,
-- unlike on Linux). So we have to move the setup exe out of the way first
-- and then delete it manually. This applies only to the external setup
-- method.
useWin32CleanHack :: Bool,
-- Used only when calling setupWrapper from parallel code to serialise
-- access to the setup cache; should be Nothing otherwise.
--
-- Note: setup exe cache
------------------------
-- When we are installing in parallel, we always use the external setup
-- method. Since compiling the setup script each time adds noticeable
-- overhead, we use a shared setup script cache
-- ('~/.cabal/setup-exe-cache'). For each (compiler, platform, Cabal
-- version) combination the cache holds a compiled setup script
-- executable. This only affects the Simple build type; for the Custom,
-- Configure and Make build types we always compile the setup script anew.
setupCacheLock :: Maybe Lock,
-- | Is the task we are going to run an interactive foreground task,
-- or an non-interactive background task? Based on this flag we
-- decide whether or not to delegate ctrl+c to the spawned task
isInteractive :: Bool
}
defaultSetupScriptOptions :: SetupScriptOptions
defaultSetupScriptOptions = SetupScriptOptions {
useCabalVersion = anyVersion,
useCabalSpecVersion = Nothing,
useCompiler = Nothing,
usePlatform = Nothing,
usePackageDB = [GlobalPackageDB, UserPackageDB],
usePackageIndex = Nothing,
useDependencies = [],
useDependenciesExclusive = False,
useVersionMacros = False,
useProgramDb = emptyProgramDb,
useDistPref = defaultDistPref,
useLoggingHandle = Nothing,
useWorkingDir = Nothing,
useExtraPathEnv = [],
useWin32CleanHack = False,
forceExternalSetupMethod = False,
setupCacheLock = Nothing,
isInteractive = False
}
workingDir :: SetupScriptOptions -> FilePath
workingDir options =
case fromMaybe "" (useWorkingDir options) of
[] -> "."
dir -> dir
-- | A @SetupRunner@ implements a 'SetupMethod'.
type SetupRunner = Verbosity
-> SetupScriptOptions
-> BuildType
-> [String]
-> IO ()
-- | Prepare to build a package by configuring a 'SetupMethod'. The returned
-- 'Setup' object identifies the method. The 'SetupScriptOptions' may be changed
-- during the configuration process; the final values are given by
-- 'setupScriptOptions'.
getSetup :: Verbosity
-> SetupScriptOptions
-> Maybe PackageDescription
-> IO Setup
getSetup verbosity options mpkg = do
pkg <- maybe getPkg return mpkg
let options' = options {
useCabalVersion = intersectVersionRanges
(useCabalVersion options)
(orLaterVersion (specVersion pkg))
}
buildType' = fromMaybe Custom (buildType pkg)
checkBuildType buildType'
(version, method, options'') <-
getSetupMethod verbosity options' pkg buildType'
return Setup { setupMethod = method
, setupScriptOptions = options''
, setupVersion = version
, setupBuildType = buildType'
, setupPackage = pkg
}
where
getPkg = tryFindPackageDesc (fromMaybe "." (useWorkingDir options))
>>= readGenericPackageDescription verbosity
>>= return . packageDescription
checkBuildType (UnknownBuildType name) =
die' verbosity $ "The build-type '" ++ name ++ "' is not known. Use one of: "
++ intercalate ", " (map display knownBuildTypes) ++ "."
checkBuildType _ = return ()
-- | Decide if we're going to be able to do a direct internal call to the
-- entry point in the Cabal library or if we're going to have to compile
-- and execute an external Setup.hs script.
--
getSetupMethod
:: Verbosity -> SetupScriptOptions -> PackageDescription -> BuildType
-> IO (Version, SetupMethod, SetupScriptOptions)
getSetupMethod verbosity options pkg buildType'
| buildType' == Custom
|| maybe False (cabalVersion /=) (useCabalSpecVersion options)
|| not (cabalVersion `withinRange` useCabalVersion options) =
getExternalSetupMethod verbosity options pkg buildType'
| isJust (useLoggingHandle options)
-- Forcing is done to use an external process e.g. due to parallel
-- build concerns.
|| forceExternalSetupMethod options =
return (cabalVersion, SelfExecMethod, options)
| otherwise = return (cabalVersion, InternalMethod, options)
runSetupMethod :: WithCallStack (SetupMethod -> SetupRunner)
runSetupMethod InternalMethod = internalSetupMethod
runSetupMethod (ExternalMethod path) = externalSetupMethod path
runSetupMethod SelfExecMethod = selfExecSetupMethod
-- | Run a configured 'Setup' with specific arguments.
runSetup :: Verbosity -> Setup
-> [String] -- ^ command-line arguments
-> IO ()
runSetup verbosity setup args0 = do
let method = setupMethod setup
options = setupScriptOptions setup
bt = setupBuildType setup
args = verbosityHack (setupVersion setup) args0
when (verbosity >= deafening {- avoid test if not debug -} && args /= args0) $
infoNoWrap verbose $
"Applied verbosity hack:\n" ++
" Before: " ++ show args0 ++ "\n" ++
" After: " ++ show args ++ "\n"
runSetupMethod method verbosity options bt args
-- | This is a horrible hack to make sure passing fancy verbosity
-- flags (e.g., @-v'info +callstack'@) doesn't break horribly on
-- old Setup. We can't do it in 'filterConfigureFlags' because
-- verbosity applies to ALL commands.
verbosityHack :: Version -> [String] -> [String]
verbosityHack ver args0
| ver >= mkVersion [1,25] = args0
| otherwise = go args0
where
go (('-':'v':rest) : args)
| Just rest' <- munch rest = ("-v" ++ rest') : go args
go (('-':'-':'v':'e':'r':'b':'o':'s':'e':'=':rest) : args)
| Just rest' <- munch rest = ("--verbose=" ++ rest') : go args
go ("--verbose" : rest : args)
| Just rest' <- munch rest = "--verbose" : rest' : go args
go rest@("--" : _) = rest
go (arg:args) = arg : go args
go [] = []
munch rest =
case runReadE flagToVerbosity rest of
Right v | verboseHasFlags v
-- We could preserve the prefix, but since we're assuming
-- it's Cabal's verbosity flag, we can assume that
-- any format is OK
-> Just (showForCabal (verboseNoFlags v))
_ -> Nothing
-- | Run a command through a configured 'Setup'.
runSetupCommand :: Verbosity -> Setup
-> CommandUI flags -- ^ command definition
-> flags -- ^ command flags
-> [String] -- ^ extra command-line arguments
-> IO ()
runSetupCommand verbosity setup cmd flags extraArgs = do
let args = commandName cmd : commandShowOptions cmd flags ++ extraArgs
runSetup verbosity setup args
-- | Configure a 'Setup' and run a command in one step. The command flags
-- may depend on the Cabal library version in use.
setupWrapper :: Verbosity
-> SetupScriptOptions
-> Maybe PackageDescription
-> CommandUI flags
-> (Version -> flags)
-- ^ produce command flags given the Cabal library version
-> [String]
-> IO ()
setupWrapper verbosity options mpkg cmd flags extraArgs = do
setup <- getSetup verbosity options mpkg
runSetupCommand verbosity setup cmd (flags $ setupVersion setup) extraArgs
-- ------------------------------------------------------------
-- * Internal SetupMethod
-- ------------------------------------------------------------
internalSetupMethod :: SetupRunner
internalSetupMethod verbosity options bt args = do
info verbosity $ "Using internal setup method with build-type " ++ show bt
++ " and args:\n " ++ show args
inDir (useWorkingDir options) $ do
withEnv "HASKELL_DIST_DIR" (useDistPref options) $
withExtraPathEnv (useExtraPathEnv options) $
buildTypeAction bt args
buildTypeAction :: BuildType -> ([String] -> IO ())
buildTypeAction Simple = Simple.defaultMainArgs
buildTypeAction Configure = Simple.defaultMainWithHooksArgs
Simple.autoconfUserHooks
buildTypeAction Make = Make.defaultMainArgs
buildTypeAction Custom = error "buildTypeAction Custom"
buildTypeAction (UnknownBuildType _) = error "buildTypeAction UnknownBuildType"
-- | @runProcess'@ is a version of @runProcess@ where we have
-- the additional option to decide whether or not we should
-- delegate CTRL+C to the spawned process.
runProcess' :: FilePath -- ^ Filename of the executable
-> [String] -- ^ Arguments to pass to executable
-> Maybe FilePath -- ^ Optional path to working directory
-> Maybe [(String, String)] -- ^ Optional environment
-> Maybe Handle -- ^ Handle for @stdin@
-> Maybe Handle -- ^ Handle for @stdout@
-> Maybe Handle -- ^ Handle for @stderr@
-> Bool -- ^ Delegate Ctrl+C ?
-> IO ProcessHandle
runProcess' cmd args mb_cwd mb_env mb_stdin mb_stdout mb_stderr _delegate = do
(_,_,_,ph) <-
createProcess
(proc cmd args){ Process.cwd = mb_cwd
, Process.env = mb_env
, Process.std_in = mbToStd mb_stdin
, Process.std_out = mbToStd mb_stdout
, Process.std_err = mbToStd mb_stderr
#if MIN_VERSION_process(1,2,0)
, Process.delegate_ctlc = _delegate
#endif
}
return ph
where
mbToStd :: Maybe Handle -> StdStream
mbToStd Nothing = Inherit
mbToStd (Just hdl) = UseHandle hdl
-- ------------------------------------------------------------
-- * Self-Exec SetupMethod
-- ------------------------------------------------------------
selfExecSetupMethod :: SetupRunner
selfExecSetupMethod verbosity options bt args0 = do
let args = ["act-as-setup",
"--build-type=" ++ display bt,
"--"] ++ args0
info verbosity $ "Using self-exec internal setup method with build-type "
++ show bt ++ " and args:\n " ++ show args
path <- getExecutablePath
info verbosity $ unwords (path : args)
case useLoggingHandle options of
Nothing -> return ()
Just logHandle -> info verbosity $ "Redirecting build log to "
++ show logHandle
searchpath <- programSearchPathAsPATHVar
(map ProgramSearchPathDir (useExtraPathEnv options) ++
getProgramSearchPath (useProgramDb options))
env <- getEffectiveEnvironment [("PATH", Just searchpath)
,("HASKELL_DIST_DIR", Just (useDistPref options))]
process <- runProcess' path args
(useWorkingDir options) env Nothing
(useLoggingHandle options) (useLoggingHandle options)
(isInteractive options)
exitCode <- waitForProcess process
unless (exitCode == ExitSuccess) $ exitWith exitCode
-- ------------------------------------------------------------
-- * External SetupMethod
-- ------------------------------------------------------------
externalSetupMethod :: WithCallStack (FilePath -> SetupRunner)
externalSetupMethod path verbosity options _ args = do
info verbosity $ unwords (path : args)
case useLoggingHandle options of
Nothing -> return ()
Just logHandle -> info verbosity $ "Redirecting build log to "
++ show logHandle
-- See 'Note: win32 clean hack' above.
#ifdef mingw32_HOST_OS
if useWin32CleanHack options then doWin32CleanHack path else doInvoke path
#else
doInvoke path
#endif
where
doInvoke path' = do
searchpath <- programSearchPathAsPATHVar
(map ProgramSearchPathDir (useExtraPathEnv options) ++
getProgramSearchPath (useProgramDb options))
env <- getEffectiveEnvironment [("PATH", Just searchpath)
,("HASKELL_DIST_DIR", Just (useDistPref options))]
process <- runProcess' path' args
(useWorkingDir options) env Nothing
(useLoggingHandle options) (useLoggingHandle options)
(isInteractive options)
exitCode <- waitForProcess process
unless (exitCode == ExitSuccess) $ exitWith exitCode
#ifdef mingw32_HOST_OS
doWin32CleanHack path' = do
info verbosity $ "Using the Win32 clean hack."
-- Recursively removes the temp dir on exit.
withTempDirectory verbosity (workingDir options) "cabal-tmp" $ \tmpDir ->
bracket (moveOutOfTheWay tmpDir path')
(maybeRestore path')
doInvoke
moveOutOfTheWay tmpDir path' = do
let newPath = tmpDir </> "setup" <.> exeExtension
Win32.moveFile path' newPath
return newPath
maybeRestore oldPath path' = do
let oldPathDir = takeDirectory oldPath
oldPathDirExists <- doesDirectoryExist oldPathDir
-- 'setup clean' didn't complete, 'dist/setup' still exists.
when oldPathDirExists $
Win32.moveFile path' oldPath
#endif
getExternalSetupMethod
:: Verbosity -> SetupScriptOptions -> PackageDescription -> BuildType
-> IO (Version, SetupMethod, SetupScriptOptions)
getExternalSetupMethod verbosity options pkg bt = do
debug verbosity $ "Using external setup method with build-type " ++ show bt
debug verbosity $ "Using explicit dependencies: "
++ show (useDependenciesExclusive options)
createDirectoryIfMissingVerbose verbosity True setupDir
(cabalLibVersion, mCabalLibInstalledPkgId, options') <- cabalLibVersionToUse
debug verbosity $ "Using Cabal library version " ++ display cabalLibVersion
path <- if useCachedSetupExecutable
then getCachedSetupExecutable options'
cabalLibVersion mCabalLibInstalledPkgId
else compileSetupExecutable options'
cabalLibVersion mCabalLibInstalledPkgId False
-- Since useWorkingDir can change the relative path, the path argument must
-- be turned into an absolute path. On some systems, runProcess' will take
-- path as relative to the new working directory instead of the current
-- working directory.
path' <- tryCanonicalizePath path
-- See 'Note: win32 clean hack' above.
#ifdef mingw32_HOST_OS
-- setupProgFile may not exist if we're using a cached program
setupProgFile' <- canonicalizePathNoThrow setupProgFile
let win32CleanHackNeeded = (useWin32CleanHack options)
-- Skip when a cached setup script is used.
&& setupProgFile' `equalFilePath` path'
#else
let win32CleanHackNeeded = False
#endif
let options'' = options' { useWin32CleanHack = win32CleanHackNeeded }
return (cabalLibVersion, ExternalMethod path', options'')
where
setupDir = workingDir options </> useDistPref options </> "setup"
setupVersionFile = setupDir </> "setup" <.> "version"
setupHs = setupDir </> "setup" <.> "hs"
setupProgFile = setupDir </> "setup" <.> exeExtension
platform = fromMaybe buildPlatform (usePlatform options)
useCachedSetupExecutable = (bt == Simple || bt == Configure || bt == Make)
maybeGetInstalledPackages :: SetupScriptOptions -> Compiler
-> ProgramDb -> IO InstalledPackageIndex
maybeGetInstalledPackages options' comp progdb =
case usePackageIndex options' of
Just index -> return index
Nothing -> getInstalledPackages verbosity
comp (usePackageDB options') progdb
-- Choose the version of Cabal to use if the setup script has a dependency on
-- Cabal, and possibly update the setup script options. The version also
-- determines how to filter the flags to Setup.
--
-- We first check whether the dependency solver has specified a Cabal version.
-- If it has, we use the solver's version without looking at the installed
-- package index (See issue #3436). Otherwise, we pick the Cabal version by
-- checking 'useCabalSpecVersion', then the saved version, and finally the
-- versions available in the index.
--
-- The version chosen here must match the one used in 'compileSetupExecutable'
-- (See issue #3433).
cabalLibVersionToUse :: IO (Version, Maybe ComponentId
,SetupScriptOptions)
cabalLibVersionToUse =
case find (isCabalPkgId . snd) (useDependencies options) of
Just (unitId, pkgId) -> do
let version = pkgVersion pkgId
updateSetupScript version bt
writeSetupVersionFile version
return (version, Just unitId, options)
Nothing ->
case useCabalSpecVersion options of
Just version -> do
updateSetupScript version bt
writeSetupVersionFile version
return (version, Nothing, options)
Nothing -> do
savedVer <- savedVersion
case savedVer of
Just version | version `withinRange` useCabalVersion options
-> do updateSetupScript version bt
-- Does the previously compiled setup executable still exist
-- and is it up-to date?
useExisting <- canUseExistingSetup version
if useExisting
then return (version, Nothing, options)
else installedVersion
_ -> installedVersion
where
-- This check duplicates the checks in 'getCachedSetupExecutable' /
-- 'compileSetupExecutable'. Unfortunately, we have to perform it twice
-- because the selected Cabal version may change as a result of this
-- check.
canUseExistingSetup :: Version -> IO Bool
canUseExistingSetup version =
if useCachedSetupExecutable
then do
(_, cachedSetupProgFile) <- cachedSetupDirAndProg options version
doesFileExist cachedSetupProgFile
else
(&&) <$> setupProgFile `existsAndIsMoreRecentThan` setupHs
<*> setupProgFile `existsAndIsMoreRecentThan` setupVersionFile
writeSetupVersionFile :: Version -> IO ()
writeSetupVersionFile version =
writeFile setupVersionFile (show version ++ "\n")
installedVersion :: IO (Version, Maybe InstalledPackageId
,SetupScriptOptions)
installedVersion = do
(comp, progdb, options') <- configureCompiler options
(version, mipkgid, options'') <- installedCabalVersion options' comp progdb
updateSetupScript version bt
writeSetupVersionFile version
return (version, mipkgid, options'')
savedVersion :: IO (Maybe Version)
savedVersion = do
versionString <- readFile setupVersionFile `catchIO` \_ -> return ""
case reads versionString of
[(version,s)] | all isSpace s -> return (Just version)
_ -> return Nothing
-- | Update a Setup.hs script, creating it if necessary.
updateSetupScript :: Version -> BuildType -> IO ()
updateSetupScript _ Custom = do
useHs <- doesFileExist customSetupHs
useLhs <- doesFileExist customSetupLhs
unless (useHs || useLhs) $ die' verbosity
"Using 'build-type: Custom' but there is no Setup.hs or Setup.lhs script."
let src = (if useHs then customSetupHs else customSetupLhs)
srcNewer <- src `moreRecentFile` setupHs
when srcNewer $ if useHs
then copyFileVerbose verbosity src setupHs
else runSimplePreProcessor ppUnlit src setupHs verbosity
where
customSetupHs = workingDir options </> "Setup.hs"
customSetupLhs = workingDir options </> "Setup.lhs"
updateSetupScript cabalLibVersion _ =
rewriteFileEx verbosity setupHs (buildTypeScript cabalLibVersion)
buildTypeScript :: Version -> String
buildTypeScript cabalLibVersion = case bt of
Simple -> "import Distribution.Simple; main = defaultMain\n"
Configure -> "import Distribution.Simple; main = defaultMainWithHooks "
++ if cabalLibVersion >= mkVersion [1,3,10]
then "autoconfUserHooks\n"
else "defaultUserHooks\n"
Make -> "import Distribution.Make; main = defaultMain\n"
Custom -> error "buildTypeScript Custom"
UnknownBuildType _ -> error "buildTypeScript UnknownBuildType"
installedCabalVersion :: SetupScriptOptions -> Compiler -> ProgramDb
-> IO (Version, Maybe InstalledPackageId
,SetupScriptOptions)
installedCabalVersion options' _ _ | packageName pkg == mkPackageName "Cabal"
&& bt == Custom =
return (packageVersion pkg, Nothing, options')
installedCabalVersion options' compiler progdb = do
index <- maybeGetInstalledPackages options' compiler progdb
let cabalDep = Dependency (mkPackageName "Cabal") (useCabalVersion options')
options'' = options' { usePackageIndex = Just index }
case PackageIndex.lookupDependency index cabalDep of
[] -> die' verbosity $ "The package '" ++ display (packageName pkg)
++ "' requires Cabal library version "
++ display (useCabalVersion options)
++ " but no suitable version is installed."
pkgs -> let ipkginfo = head . snd . bestVersion fst $ pkgs
in return (packageVersion ipkginfo
,Just . IPI.installedComponentId $ ipkginfo, options'')
bestVersion :: (a -> Version) -> [a] -> a
bestVersion f = firstMaximumBy (comparing (preference . f))
where
-- Like maximumBy, but picks the first maximum element instead of the
-- last. In general, we expect the preferred version to go first in the
-- list. For the default case, this has the effect of choosing the version
-- installed in the user package DB instead of the global one. See #1463.
--
-- Note: firstMaximumBy could be written as just
-- `maximumBy cmp . reverse`, but the problem is that the behaviour of
-- maximumBy is not fully specified in the case when there is not a single
-- greatest element.
firstMaximumBy :: (a -> a -> Ordering) -> [a] -> a
firstMaximumBy _ [] =
error "Distribution.Client.firstMaximumBy: empty list"
firstMaximumBy cmp xs = foldl1' maxBy xs
where
maxBy x y = case cmp x y of { GT -> x; EQ -> x; LT -> y; }
preference version = (sameVersion, sameMajorVersion
,stableVersion, latestVersion)
where
sameVersion = version == cabalVersion
sameMajorVersion = majorVersion version == majorVersion cabalVersion
majorVersion = take 2 . versionNumbers
stableVersion = case versionNumbers version of
(_:x:_) -> even x
_ -> False
latestVersion = version
configureCompiler :: SetupScriptOptions
-> IO (Compiler, ProgramDb, SetupScriptOptions)
configureCompiler options' = do
(comp, progdb) <- case useCompiler options' of
Just comp -> return (comp, useProgramDb options')
Nothing -> do (comp, _, progdb) <-
configCompilerEx (Just GHC) Nothing Nothing
(useProgramDb options') verbosity
return (comp, progdb)
-- Whenever we need to call configureCompiler, we also need to access the
-- package index, so let's cache it in SetupScriptOptions.
index <- maybeGetInstalledPackages options' comp progdb
return (comp, progdb, options' { useCompiler = Just comp,
usePackageIndex = Just index,
useProgramDb = progdb })
-- | Path to the setup exe cache directory and path to the cached setup
-- executable.
cachedSetupDirAndProg :: SetupScriptOptions -> Version
-> IO (FilePath, FilePath)
cachedSetupDirAndProg options' cabalLibVersion = do
cabalDir <- defaultCabalDir
let setupCacheDir = cabalDir </> "setup-exe-cache"
cachedSetupProgFile = setupCacheDir
</> ("setup-" ++ buildTypeString ++ "-"
++ cabalVersionString ++ "-"
++ platformString ++ "-"
++ compilerVersionString)
<.> exeExtension
return (setupCacheDir, cachedSetupProgFile)
where
buildTypeString = show bt
cabalVersionString = "Cabal-" ++ (display cabalLibVersion)
compilerVersionString = display $
fromMaybe buildCompilerId
(fmap compilerId . useCompiler $ options')
platformString = display platform
-- | Look up the setup executable in the cache; update the cache if the setup
-- executable is not found.
getCachedSetupExecutable :: SetupScriptOptions
-> Version -> Maybe InstalledPackageId
-> IO FilePath
getCachedSetupExecutable options' cabalLibVersion
maybeCabalLibInstalledPkgId = do
(setupCacheDir, cachedSetupProgFile) <-
cachedSetupDirAndProg options' cabalLibVersion
cachedSetupExists <- doesFileExist cachedSetupProgFile
if cachedSetupExists
then debug verbosity $
"Found cached setup executable: " ++ cachedSetupProgFile
else criticalSection' $ do
-- The cache may have been populated while we were waiting.
cachedSetupExists' <- doesFileExist cachedSetupProgFile
if cachedSetupExists'
then debug verbosity $
"Found cached setup executable: " ++ cachedSetupProgFile
else do
debug verbosity $ "Setup executable not found in the cache."
src <- compileSetupExecutable options'
cabalLibVersion maybeCabalLibInstalledPkgId True
createDirectoryIfMissingVerbose verbosity True setupCacheDir
installExecutableFile verbosity src cachedSetupProgFile
-- Do not strip if we're using GHCJS, since the result may be a script
when (maybe True ((/=GHCJS).compilerFlavor) $ useCompiler options') $
Strip.stripExe verbosity platform (useProgramDb options')
cachedSetupProgFile
return cachedSetupProgFile
where
criticalSection' = fromMaybe id
(fmap criticalSection $ setupCacheLock options')
-- | If the Setup.hs is out of date wrt the executable then recompile it.
-- Currently this is GHC/GHCJS only. It should really be generalised.
--
compileSetupExecutable :: SetupScriptOptions
-> Version -> Maybe ComponentId -> Bool
-> IO FilePath
compileSetupExecutable options' cabalLibVersion maybeCabalLibInstalledPkgId
forceCompile = do
setupHsNewer <- setupHs `moreRecentFile` setupProgFile
cabalVersionNewer <- setupVersionFile `moreRecentFile` setupProgFile
let outOfDate = setupHsNewer || cabalVersionNewer
when (outOfDate || forceCompile) $ do
debug verbosity "Setup executable needs to be updated, compiling..."
(compiler, progdb, options'') <- configureCompiler options'
let cabalPkgid = PackageIdentifier (mkPackageName "Cabal") cabalLibVersion
(program, extraOpts)
= case compilerFlavor compiler of
GHCJS -> (ghcjsProgram, ["-build-runner"])
_ -> (ghcProgram, ["-threaded"])
cabalDep = maybe [] (\ipkgid -> [(ipkgid, cabalPkgid)])
maybeCabalLibInstalledPkgId
-- With 'useDependenciesExclusive' we enforce the deps specified,
-- so only the given ones can be used. Otherwise we allow the use
-- of packages in the ambient environment, and add on a dep on the
-- Cabal library (unless 'useDependencies' already contains one).
--
-- With 'useVersionMacros' we use a version CPP macros .h file.
--
-- Both of these options should be enabled for packages that have
-- opted-in and declared a custom-settup stanza.
--
selectedDeps | useDependenciesExclusive options'
= useDependencies options'
| otherwise = useDependencies options' ++
if any (isCabalPkgId . snd) (useDependencies options')
then []
else cabalDep
addRenaming (ipid, _) =
-- Assert 'DefUnitId' invariant
(Backpack.DefiniteUnitId (unsafeMkDefUnitId (newSimpleUnitId ipid)), defaultRenaming)
cppMacrosFile = setupDir </> "setup_macros.h"
ghcOptions = mempty {
-- Respect -v0, but don't crank up verbosity on GHC if
-- Cabal verbosity is requested. For that, use --ghc-option=-v instead!
ghcOptVerbosity = Flag (min verbosity normal)
, ghcOptMode = Flag GhcModeMake
, ghcOptInputFiles = toNubListR [setupHs]
, ghcOptOutputFile = Flag setupProgFile
, ghcOptObjDir = Flag setupDir
, ghcOptHiDir = Flag setupDir
, ghcOptSourcePathClear = Flag True
, ghcOptSourcePath = case bt of
Custom -> toNubListR [workingDir options']
_ -> mempty
, ghcOptPackageDBs = usePackageDB options''
, ghcOptHideAllPackages = Flag (useDependenciesExclusive options')
, ghcOptCabal = Flag (useDependenciesExclusive options')
, ghcOptPackages = toNubListR $ map addRenaming selectedDeps
, ghcOptCppIncludes = toNubListR [ cppMacrosFile
| useVersionMacros options' ]
, ghcOptExtra = toNubListR extraOpts
}
let ghcCmdLine = renderGhcOptions compiler platform ghcOptions
when (useVersionMacros options') $
rewriteFileEx verbosity cppMacrosFile
(generatePackageVersionMacros (map snd selectedDeps))
case useLoggingHandle options of
Nothing -> runDbProgram verbosity program progdb ghcCmdLine
-- If build logging is enabled, redirect compiler output to the log file.
(Just logHandle) -> do output <- getDbProgramOutput verbosity program
progdb ghcCmdLine
hPutStr logHandle output
return setupProgFile
isCabalPkgId :: PackageIdentifier -> Bool
isCabalPkgId (PackageIdentifier pname _) = pname == mkPackageName "Cabal"