{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} -- | Build the project. module Stack.Build ( buildCmd , build , buildLocalTargets , loadPackage , mkBaseConfigOpts , splitObjsWarning ) where import Data.Attoparsec.Args ( EscapingMode (Escaping), parseArgs ) import Data.List ( (\\) ) import Data.List.Extra ( groupSort ) import qualified Data.Map as Map import qualified Data.Set as Set import qualified Data.Text as T -- import qualified Distribution.PackageDescription as C -- import Distribution.Types.Dependency ( Dependency (..), depLibraries ) import Distribution.Version ( mkVersion ) import RIO.NonEmpty ( nonEmpty ) import qualified RIO.NonEmpty as NE import Stack.Build.ConstructPlan ( constructPlan ) import Stack.Build.Execute ( executePlan, preFetch, printPlan ) import Stack.Build.Installed ( getInstalled, toInstallMap ) import Stack.Build.Source ( localDependencies, projectLocalPackages ) import Stack.Build.Target ( NeedTargets (..) ) import Stack.FileWatch ( fileWatch, fileWatchPoll ) import Stack.Package ( buildableExes, resolvePackage ) import Stack.Prelude hiding ( loadPackage ) import Stack.Runners ( ShouldReexec (..), withConfig, withEnvConfig ) import Stack.Setup ( withNewLocalBuildTargets ) import Stack.Types.Build ( Plan (..), Task (..), TaskType (..), taskLocation , taskProvides ) import Stack.Types.Build.Exception ( BuildException (..), BuildPrettyException (..) ) import Stack.Types.BuildConfig ( HasBuildConfig, stackYamlL ) import Stack.Types.BuildOpts ( BuildOpts (..) ) import Stack.Types.BuildOptsCLI ( BuildCommand (..), BuildOptsCLI (..), FileWatchOpts (..) ) import Stack.Types.BuildOptsMonoid ( buildOptsMonoidBenchmarksL, buildOptsMonoidHaddockL , buildOptsMonoidInstallExesL, buildOptsMonoidTestsL ) import Stack.Types.Compiler ( getGhcVersion ) import Stack.Types.CompilerPaths ( cabalVersionL ) import Stack.Types.Config ( Config (..), HasConfig (..), buildOptsL ) import Stack.Types.ConfigureOpts ( BaseConfigOpts (..) ) import Stack.Types.EnvConfig ( EnvConfig (..), HasEnvConfig (..), HasSourceMap , actualCompilerVersionL, installationRootDeps , installationRootLocal, packageDatabaseDeps , packageDatabaseExtra, packageDatabaseLocal ) import Stack.Types.GlobalOpts ( globalOptsBuildOptsMonoidL ) import Stack.Types.NamedComponent ( exeComponents ) import Stack.Types.Package ( InstallLocation (..), LocalPackage (..), Package (..) , PackageConfig (..), lpFiles, lpFilesForComponents ) import Stack.Types.Platform ( HasPlatform (..) ) import Stack.Types.Runner ( Runner, globalOptsL ) import Stack.Types.SourceMap ( SMTargets (..) , SourceMap (..), Target (..) ) import System.Terminal ( fixCodePage ) newtype CabalVersionPrettyException = CabalVersionNotSupported Version deriving (Show, Typeable) instance Pretty CabalVersionPrettyException where pretty (CabalVersionNotSupported cabalVer) = "[S-5973]" <> line <> fillSep [ flow "Stack does not support Cabal versions before 1.24, but \ \version" , fromString $ versionString cabalVer , flow "was found. To fix this, consider updating the snapshot to" , style Shell "lts-7.0" , flow "or later or to" , style Shell "nightly-2016-05-26" , flow "or later." ] instance Exception CabalVersionPrettyException -- | Helper for build and install commands buildCmd :: BuildOptsCLI -> RIO Runner () buildCmd opts = do when (any (("-prof" `elem`) . fromRight [] . parseArgs Escaping) opts.ghcOptions) $ prettyThrowIO GHCProfOptionInvalid local (over globalOptsL modifyGO) $ case opts.fileWatch of FileWatchPoll -> fileWatchPoll (inner . Just) FileWatch -> fileWatch (inner . Just) NoFileWatch -> inner Nothing where inner :: Maybe (Set (Path Abs File) -> IO ()) -> RIO Runner () inner setLocalFiles = withConfig YesReexec $ withEnvConfig NeedTargets opts $ Stack.Build.build setLocalFiles -- Read the build command from the CLI and enable it to run modifyGO = case opts.command of Test -> set (globalOptsBuildOptsMonoidL . buildOptsMonoidTestsL) (Just True) Haddock -> set (globalOptsBuildOptsMonoidL . buildOptsMonoidHaddockL) (Just True) Bench -> set (globalOptsBuildOptsMonoidL . buildOptsMonoidBenchmarksL) (Just True) Install -> set (globalOptsBuildOptsMonoidL . buildOptsMonoidInstallExesL) (Just True) Build -> id -- Default case is just Build -- | 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 :: HasEnvConfig env => Maybe (Set (Path Abs File) -> IO ()) -- ^ callback after discovering all local files -> RIO env () build msetLocalFiles = do mcp <- view $ configL . to (.modifyCodePage) ghcVersion <- view $ actualCompilerVersionL . to getGhcVersion fixCodePage mcp ghcVersion $ do bopts <- view buildOptsL sourceMap <- view $ envConfigL . to (.sourceMap) locals <- projectLocalPackages depsLocals <- localDependencies let allLocals = locals <> depsLocals boptsCli <- view $ envConfigL . to (.buildOptsCLI) -- Set local files, necessary for file watching stackYaml <- view stackYamlL for_ msetLocalFiles $ \setLocalFiles -> do files <- if boptsCli.watchAll then sequence [lpFiles lp | lp <- allLocals] else forM allLocals $ \lp -> do let pn = lp.package.name case Map.lookup pn sourceMap.targets.targets of Nothing -> pure Set.empty Just (TargetAll _) -> lpFiles lp Just (TargetComps components) -> lpFilesForComponents components lp liftIO $ setLocalFiles $ Set.insert stackYaml $ Set.unions files checkComponentsBuildable allLocals installMap <- toInstallMap sourceMap (installedMap, globalDumpPkgs, snapshotDumpPkgs, localDumpPkgs) <- getInstalled installMap baseConfigOpts <- mkBaseConfigOpts boptsCli plan <- constructPlan baseConfigOpts localDumpPkgs loadPackage sourceMap installedMap boptsCli.initialBuildSteps allowLocals <- view $ configL . to (.allowLocals) unless allowLocals $ case justLocals plan of [] -> pure () localsIdents -> throwM $ LocalPackagesPresent localsIdents checkCabalVersion warnAboutSplitObjs bopts warnIfExecutablesWithSameNameCouldBeOverwritten locals plan when bopts.preFetch $ preFetch plan if boptsCli.dryrun then printPlan plan else executePlan boptsCli baseConfigOpts locals globalDumpPkgs snapshotDumpPkgs localDumpPkgs installedMap sourceMap.targets.targets plan buildLocalTargets :: HasEnvConfig env => NonEmpty Text -> RIO env (Either SomeException ()) buildLocalTargets targets = tryAny $ withNewLocalBuildTargets (NE.toList targets) $ build Nothing justLocals :: Plan -> [PackageIdentifier] justLocals = map taskProvides . filter ((== Local) . taskLocation) . Map.elems . (.tasks) checkCabalVersion :: HasEnvConfig env => RIO env () checkCabalVersion = do cabalVer <- view cabalVersionL when (cabalVer < mkVersion [1, 24]) $ prettyThrowM $ CabalVersionNotSupported cabalVer -- | See https://github.com/commercialhaskell/stack/issues/1198. warnIfExecutablesWithSameNameCouldBeOverwritten :: HasTerm env => [LocalPackage] -> Plan -> RIO env () 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 = flow "several executables with the same name:" | otherwise = "executable" exesText pkgs = fillSep $ punctuate "," [ style PkgComponent (fromString $ packageNameString p <> ":" <> T.unpack exe) | p <- pkgs ] prettyWarnL $ [ "Building" , exe_s , exesText toBuild <> "." ] <> [ fillSep [ flow "Only one of them will be available via" , style Shell "stack exec" , flow "or locally installed." ] | length toBuild > 1 ] <> [ fillSep [ flow "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 plan.tasks , TTLocalMutable lp <- [task.taskType] , exe <- (Set.toList . exeComponents . (.components)) lp ] localExes :: Map Text (NonEmpty PackageName) localExes = collect [ (exe, pkg.name) | pkg <- map (.package) locals , exe <- Set.toList (buildableExes pkg) ] collect :: Ord k => [(k, v)] -> Map k (NonEmpty v) collect = Map.mapMaybe nonEmpty . Map.fromDistinctAscList . groupSort warnAboutSplitObjs :: HasTerm env => BuildOpts -> RIO env () warnAboutSplitObjs bopts | bopts.splitObjs = prettyWarnL [ flow "Building with" , style Shell "--split-objs" , flow "is enabled." , flow splitObjsWarning ] warnAboutSplitObjs _ = pure () splitObjsWarning :: String splitObjsWarning = "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 :: (HasEnvConfig env) => BuildOptsCLI -> RIO env BaseConfigOpts mkBaseConfigOpts buildOptsCLI = do buildOpts <- view buildOptsL snapDB <- packageDatabaseDeps localDB <- packageDatabaseLocal snapInstallRoot <- installationRootDeps localInstallRoot <- installationRootLocal extraDBs <- packageDatabaseExtra pure BaseConfigOpts { snapDB , localDB , snapInstallRoot , localInstallRoot , buildOpts , buildOptsCLI , extraDBs } -- | Provide a function for loading package information from the package index loadPackage :: (HasBuildConfig env, HasSourceMap env) => PackageLocationImmutable -> Map FlagName Bool -> [Text] -- ^ GHC options -> [Text] -- ^ Cabal configure options -> RIO env Package loadPackage loc flags ghcOptions cabalConfigOpts = do compilerVersion <- view actualCompilerVersionL platform <- view platformL let pkgConfig = PackageConfig { enableTests = False , enableBenchmarks = False , flags , ghcOptions , cabalConfigOpts , compilerVersion , platform } resolvePackage pkgConfig <$> loadCabalFileImmutable loc checkComponentsBuildable :: MonadThrow m => [LocalPackage] -> m () checkComponentsBuildable lps = unless (null unbuildable) $ prettyThrowM $ SomeTargetsNotBuildable unbuildable where unbuildable = [ (lp.package.name, c) | lp <- lps , c <- Set.toList lp.unbuildable ]