{-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE ViewPatterns #-} -- | Build project(s). module Stack.Build (build ,clean) where import Control.Monad import Control.Monad.Catch (MonadCatch, MonadMask) import Control.Monad.IO.Class import Control.Monad.Logger import Control.Monad.Reader (MonadReader, asks) import Control.Monad.Trans.Resource import Data.Function import Data.Map.Strict (Map) import qualified Data.Map as Map import Network.HTTP.Client.Conduit (HasHttpManager) import Path.IO import Prelude hiding (FilePath, writeFile) import Stack.Build.ConstructPlan import Stack.Build.Execute import Stack.Build.Installed import Stack.Build.Source import Stack.Build.Types import Stack.Constants import Stack.Fetch as Fetch import Stack.GhcPkg import Stack.Package import Stack.Types import Stack.Types.Internal {- EKB TODO: doc generation for stack-doc-server #ifndef mingw32_HOST_OS import System.Posix.Files (createSymbolicLink,removeLink) #endif --} type M env m = (MonadIO m,MonadReader env m,HasHttpManager env,HasBuildConfig env,MonadLogger m,MonadBaseControl IO m,MonadCatch m,MonadMask m,HasLogLevel env,HasEnvConfig env) -- | Build build :: M env m => BuildOpts -> m () build bopts = do menv <- getMinimalEnvOverride (mbp, locals, extraToBuild, sourceMap) <- loadSourceMap bopts (installedMap, locallyRegistered) <- getInstalled menv profiling sourceMap baseConfigOpts <- mkBaseConfigOpts bopts plan <- withLoadPackage menv $ \loadPackage -> constructPlan mbp baseConfigOpts locals extraToBuild locallyRegistered loadPackage sourceMap installedMap if boptsDryrun bopts then printPlan (boptsFinalAction bopts) plan else executePlan menv bopts baseConfigOpts locals plan where profiling = boptsLibProfile bopts || boptsExeProfile bopts -- | Get the @BaseConfigOpts@ necessary for constructing configure options mkBaseConfigOpts :: (MonadIO m, MonadReader env m, HasBuildConfig env, MonadThrow m) => BuildOpts -> m BaseConfigOpts mkBaseConfigOpts bopts = do snapDBPath <- packageDatabaseDeps localDBPath <- packageDatabaseLocal snapInstallRoot <- installationRootDeps localInstallRoot <- installationRootLocal return BaseConfigOpts { bcoSnapDB = snapDBPath , bcoLocalDB = localDBPath , bcoSnapInstallRoot = snapInstallRoot , bcoLocalInstallRoot = localInstallRoot , bcoBuildOpts = bopts } -- | Provide a function for loading package information from the package index withLoadPackage :: M env m => EnvOverride -> ((PackageName -> Version -> Map FlagName Bool -> IO Package) -> m a) -> m a withLoadPackage menv inner = do bconfig <- asks getBuildConfig withCabalLoader menv $ \cabalLoader -> inner $ \name version flags -> do bs <- cabalLoader $ PackageIdentifier name version -- TODO automatically update index the first time this fails readPackageBS (depPackageConfig bconfig flags) bs where -- | Package config to be used for dependencies depPackageConfig :: BuildConfig -> Map FlagName Bool -> PackageConfig depPackageConfig bconfig flags = PackageConfig { packageConfigEnableTests = False , packageConfigEnableBenchmarks = False , packageConfigFlags = flags , packageConfigGhcVersion = bcGhcVersion bconfig , packageConfigPlatform = configPlatform (getConfig bconfig) } -- | Reset the build (remove Shake database and .gen files). clean :: (M env m) => m () clean = do bconfig <- asks getBuildConfig forM_ (Map.keys (bcPackages bconfig)) (distDirFromDir >=> removeTreeIfExists) ---------------------------------------------------------- -- DEAD CODE BELOW HERE ---------------------------------------------------------- {- EKB TODO: doc generation for stack-doc-server (boptsFinalAction bopts == DoHaddock) (buildDocIndex (wanted pwd) docLoc packages mgr logLevel) -} {- EKB TODO: doc generation for stack-doc-server -- | Build the haddock documentation index and contents. buildDocIndex :: (Package -> Wanted) -> Path Abs Dir -> Set Package -> Manager -> LogLevel -> Rules () buildDocIndex wanted docLoc packages mgr logLevel = do runHaddock "--gen-contents" $(mkRelFile "index.html") runHaddock "--gen-index" $(mkRelFile "doc-index.html") combineHoogle where runWithLogging = runStackLoggingT mgr logLevel runHaddock genOpt destFilename = do let destPath = toFilePath (docLoc destFilename) want [destPath] destPath %> \_ -> runWithLogging (do needDeps ifcOpts <- liftIO (fmap concat (mapM toInterfaceOpt (S.toList packages))) runIn docLoc "haddock" mempty (genOpt:ifcOpts) Nothing) toInterfaceOpt package = do let pv = joinPkgVer (packageName package,packageVersion package) srcPath = (toFilePath docLoc) ++ "/" ++ pv ++ "/" ++ packageNameString (packageName package) ++ "." ++ haddockExtension exists <- doesFileExist srcPath return (if exists then ["-i" ,"../" ++ pv ++ "," ++ srcPath] else []) combineHoogle = do let destHoogleDbLoc = hoogleDatabaseFile docLoc destPath = toFilePath destHoogleDbLoc want [destPath] destPath %> \_ -> runWithLogging (do needDeps srcHoogleDbs <- liftIO (fmap concat (mapM toSrcHoogleDb (S.toList packages))) callProcess "hoogle" ("combine" : "-o" : toFilePath destHoogleDbLoc : srcHoogleDbs)) toSrcHoogleDb package = do let srcPath = toFilePath docLoc ++ "/" ++ joinPkgVer (packageName package,packageVersion package) ++ "/" ++ packageNameString (packageName package) ++ "." ++ hoogleDbExtension exists <- doesFileExist srcPath return (if exists then [srcPath] else []) needDeps = need (concatMap (\package -> if wanted package == Wanted then let dir = packageDir package in [toFilePath (builtFileFromDir dir)] else []) (S.toList packages)) #ifndef mingw32_HOST_OS -- | Remove existing links docs for package from @~/.shake/doc@. removeDocLinks :: Path Abs Dir -> Package -> IO () removeDocLinks docLoc package = do createDirectoryIfMissing True (toFilePath docLoc) userDocLs <- fmap (map (toFilePath docLoc ++)) (getDirectoryContents (toFilePath docLoc)) forM_ userDocLs $ \docPath -> do isDir <- doesDirectoryExist docPath when isDir (case breakPkgVer (FilePath.takeFileName docPath) of Just (p,_) -> when (p == packageName package) (removeLink docPath) Nothing -> return ()) -- | Add link for package to @~/.shake/doc@. createDocLinks :: Path Abs Dir -> Package -> IO () createDocLinks docLoc package = do let pkgVer = joinPkgVer (packageName package,(packageVersion package)) pkgVerLoc <- liftIO (parseRelDir pkgVer) let pkgDestDocLoc = docLoc pkgVerLoc pkgDestDocPath = FilePath.dropTrailingPathSeparator (toFilePath pkgDestDocLoc) cabalDocLoc = parent docLoc $(mkRelDir "share/doc/") haddockLocs <- do cabalDocExists <- doesDirectoryExist (toFilePath cabalDocLoc) if cabalDocExists then findFiles cabalDocLoc (\fileLoc -> FilePath.takeExtensions (toFilePath fileLoc) == "." ++ haddockExtension && dirname (parent fileLoc) == $(mkRelDir "html/") && toFilePath (dirname (parent (parent fileLoc))) == (pkgVer ++ "/")) (\dirLoc -> not (isHiddenDir dirLoc) && dirname (parent (parent dirLoc)) /= $(mkRelDir "html/")) else return [] case haddockLocs of [haddockLoc] -> case stripDir (parent docLoc) haddockLoc of Just relHaddockPath -> do let srcRelPathCollapsed = FilePath.takeDirectory (FilePath.dropTrailingPathSeparator (toFilePath relHaddockPath)) {-srcRelPath = "../" ++ srcRelPathCollapsed-} createSymbolicLink (FilePath.dropTrailingPathSeparator srcRelPathCollapsed) pkgDestDocPath Nothing -> return () _ -> return () #endif /* not defined(mingw32_HOST_OS) */ -- | Get @-i@ arguments for haddock for dependencies. haddockInterfaceOpts :: Path Abs Dir -> Package -> Set Package -> IO [String] haddockInterfaceOpts userDocLoc package packages = do mglobalDocLoc <- getGlobalDocPath globalPkgVers <- case mglobalDocLoc of Nothing -> return M.empty Just globalDocLoc -> getDocPackages globalDocLoc let toInterfaceOpt pn = case find (\dpi -> packageName dpi == pn) (S.toList packages) of Nothing -> return (case (M.lookup pn globalPkgVers,mglobalDocLoc) of (Just (v:_),Just globalDocLoc) -> ["-i" ,"../" ++ joinPkgVer (pn,v) ++ "," ++ toFilePath globalDocLoc ++ "/" ++ joinPkgVer (pn,v) ++ "/" ++ packageNameString pn ++ "." ++ haddockExtension] _ -> []) Just dpi -> do let destPath = (toFilePath userDocLoc ++ "/" ++ joinPkgVer (pn,packageVersion dpi) ++ "/" ++ packageNameString pn ++ "." ++ haddockExtension) exists <- doesFileExist destPath return (if exists then ["-i" ,"../" ++ joinPkgVer (pn,packageVersion dpi) ++ "," ++ destPath] else []) --TODO: use not only direct dependencies, but dependencies of dependencies etc. --(e.g. redis-fp doesn't include @text@ in its dependencies which means the 'Text' --datatype isn't linked in its haddocks) fmap concat (mapM toInterfaceOpt (S.toList (packageAllDeps package))) -------------------------------------------------------------------------------- -- Paths {- EKB TODO: doc generation for stack-doc-server -- | Returns true for paths whose last directory component begins with ".". isHiddenDir :: Path b Dir -> Bool isHiddenDir = isPrefixOf "." . toFilePath . dirname -} --}