stack-0.0.2: src/Stack/Build.hs
{-# 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
-}
--}