1-- | Perform an actual build, generate a binary package database and a
2-- documentation directory in the process.
4{-# LANGUAGE DeriveDataTypeable #-}
5{-# LANGUAGE RecordWildCards #-}
6{-# LANGUAGE NoImplicitPrelude #-}
7{-# LANGUAGE OverloadedStrings #-}
8module Stackage.PerformBuild
15import Stackage.BuildConstraints
16import Stackage.PackageDescription
17import Stackage.BuildPlan
18import Stackage.Prelude hiding (pi)
19import qualified Data.Map as Map
20import Control.Concurrent.STM.TSem
21import Control.Monad.Writer.Strict (execWriter, tell)
22import Data.NonNull (fromNullable)
23import Control.Concurrent.Async (async)
24import System.IO.Temp (withSystemTempDirectory)
25import Filesystem (createTree, removeTree, isDirectory, rename, canonicalizePath, getWorkingDirectory)
26import System.IO (withBinaryFile, IOMode (WriteMode))
27import Filesystem.Path (parent)
28import qualified Filesystem.Path as F
29import System.Environment (getEnvironment)
30import System.Directory (findExecutable)
32data BuildException = BuildException (Map PackageName BuildFailure) [Text]
34instance Exception BuildException
35instance Show BuildException where
36 show (BuildException m warnings) =
37 unlines $ map go (mapToList m) ++ map unpack warnings
39 go (PackageName name, bf) = concat
45data BuildFailure = DependencyFailed PackageName
46 | DependencyMissing PackageName
49 | BuildFailureException SomeException
50 deriving (Show, Typeable)
51instance Exception BuildFailure
53data PerformBuild = PerformBuild
55 , pbInstallDest :: FilePath
56 , pbLog :: ByteString -> IO ()
57 , pbLogDir :: FilePath
59 , pbGlobalInstall :: Bool
60 -- ^ Register packages in the global database
63data PackageInfo = PackageInfo
64 { piPlan :: PackagePlan
65 , piName :: PackageName
66 , piResult :: TMVar Bool
69waitForDeps :: Map ExeName (Set PackageName)
70 -> Map PackageName PackageInfo
76waitForDeps toolMap packageMap activeComps bp pi action = do
78 mapM_ checkPackage $ Map.keys $ filterUnused $ sdPackages $ ppDesc $ piPlan pi
79 forM_ (Map.keys $ filterUnused $ sdTools $ ppDesc $ piPlan pi) $ \exe -> do
80 case lookup exe toolMap >>= fromNullable . map checkPackage . setToList of
82 | isCoreExe exe -> return ()
83 | otherwise -> throwSTM $ ToolMissing exe
84 Just packages -> ofoldl1' (<|>) packages
87 filterUnused :: Ord key => Map key DepInfo -> Map key DepInfo
89 mapFromList . filter (go . snd) . mapToList
91 go = not . null . intersection activeComps . diComponents
93 checkPackage package | package == piName pi = return ()
94 checkPackage package =
95 case lookup package packageMap of
97 | isCore package -> return ()
98 | otherwise -> throwSTM $ DependencyMissing package
100 res <- readTMVar $ piResult dep
101 unless res $ throwSTM $ DependencyFailed package
103 isCore = (`member` siCorePackages (bpSystemInfo bp))
104 isCoreExe = (`member` siCoreExecutables (bpSystemInfo bp))
106withCounter counter = bracket_
107 (atomically $ modifyTVar counter (+ 1))
108 (atomically $ modifyTVar counter (subtract 1))
110withTSem sem = bracket_ (atomically $ waitTSem sem) (atomically $ signalTSem sem)
113 | pbGlobalInstall pb = Nothing
114 | otherwise = Just $ pbInstallDest pb </> "pkgdb"
115pbBinDir pb = pbInstallDest pb </> "bin"
116pbLibDir pb = pbInstallDest pb </> "lib"
117pbDataDir pb = pbInstallDest pb </> "share"
118pbDocDir pb = pbInstallDest pb </> "doc"
120performBuild :: PerformBuild -> IO [Text]
122 cwd <- getWorkingDirectory
124 { pbInstallDest = cwd </> pbInstallDest pb
125 , pbLogDir = cwd </> pbLogDir pb
128performBuild' :: PerformBuild -> IO [Text]
129performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
130 let removeTree' fp = whenM (isDirectory fp) (removeTree fp)
131 mapM_ removeTree' [pbInstallDest, pbLogDir]
133 forM_ (pbDatabase pb) $ \db -> do
134 createTree $ parent db
135 withCheckedProcess (proc "ghc-pkg" ["init", fpToString db])
136 $ \ClosedStream Inherited Inherited -> return ()
137 pbLog $ encodeUtf8 "Copying built-in Haddocks\n"
138 copyBuiltInHaddocks (pbDocDir pb)
140 sem <- atomically $ newTSem pbJobs
141 active <- newTVarIO (0 :: Int)
142 let toolMap = makeToolMap $ bpPackages pbPlan
143 packageMap <- fmap fold $ forM (mapToList $ bpPackages pbPlan)
144 $ \(name, plan) -> do
147 piResult <- newEmptyTMVarIO
148 return $ singletonMap name PackageInfo {..}
150 errsVar <- newTVarIO mempty
151 warningsVar <- newTVarIO id
153 env <- getEnvironment
154 haddockFiles <- newTVarIO mempty
156 forM_ packageMap $ \pi -> void $ async $ singleBuild pb SingleBuild
158 , sbErrsVar = errsVar
159 , sbWarningsVar = warningsVar
161 , sbToolMap = toolMap
162 , sbPackageMap = packageMap
163 , sbBuildDir = builddir
165 , sbRegisterMutex = mutex
166 , sbModifiedEnv = maybe
168 (\db -> (("HASKELL_PACKAGE_SANDBOX", fpToString db):))
171 , sbHaddockFiles = haddockFiles
174 void $ tryAny $ atomically $ readTVar active >>= checkSTM . (== 0)
176 warnings <- ($ []) <$> readTVarIO warningsVar
177 errs <- readTVarIO errsVar
178 when (not $ null errs) $ throwM $ BuildException errs warnings
181 withBuildDir f = withSystemTempDirectory "stackage-build" (f . fpFromString)
184 -- Thank you Windows having case-insensitive environment variables...
185 | toUpper p == "PATH" = (p, fpToString (pbBinDir pb) ++ pathSep : x)
188 -- | Separate for the PATH environment variable
190#ifdef mingw32_HOST_OS
196data SingleBuild = SingleBuild
198 , sbErrsVar :: TVar (Map PackageName BuildFailure)
199 , sbWarningsVar :: TVar ([Text] -> [Text])
200 , sbActive :: TVar Int
201 , sbToolMap :: Map ExeName (Set PackageName)
202 , sbPackageMap :: Map PackageName PackageInfo
203 , sbBuildDir :: FilePath
204 , sbPackageInfo :: PackageInfo
205 , sbRegisterMutex :: MVar ()
206 , sbModifiedEnv :: [(String, String)]
207 , sbHaddockFiles :: TVar (Map Text FilePath) -- ^ package-version, .haddock file
210singleBuild :: PerformBuild -> SingleBuild -> IO ()
211singleBuild pb@PerformBuild {..} SingleBuild {..} =
214 $ (`finally` void (atomically $ tryPutTMVar (piResult sbPackageInfo) False))
217 libComps = setFromList [CompLibrary, CompExecutable]
218 testComps = insertSet CompTestSuite libComps
221 waitForDeps sbToolMap sbPackageMap comps pbPlan sbPackageInfo
223 wfd libComps buildLibrary
225 wfd testComps runTests
227 name = display $ piName sbPackageInfo
231 , display $ ppVersion $ piPlan sbPackageInfo
234 runIn wdir outH cmd args =
235 withCheckedProcess cp $ \ClosedStream UseProvidedHandle UseProvidedHandle ->
238 cp = (proc (unpack $ asText cmd) (map (unpack . asText) args))
239 { cwd = Just $ fpToString wdir
240 , std_out = UseHandle outH
241 , std_err = UseHandle outH
242 , env = Just sbModifiedEnv
244 runParent = runIn sbBuildDir
245 runChild = runIn childDir
246 childDir = sbBuildDir </> fpFromText namever
249 i <- readTVarIO sbActive
250 errs <- readTVarIO sbErrsVar
251 pbLog $ encodeUtf8 $ concat
256 , tshow $ length errs
259 libOut = pbLogDir </> fpFromText namever </> "build.out"
260 testOut = pbLogDir </> fpFromText namever </> "test.out"
261 testRunOut = pbLogDir </> fpFromText namever </> "test-run.out"
264 createTree $ parent fp
265 withBinaryFile (fpToString fp) WriteMode inner
267 configArgs = ($ []) $ execWriter $ do
268 tell' "--package-db=clear"
269 tell' "--package-db=global"
270 forM_ (pbDatabase pb) $ \db -> tell' $ "--package-db=" ++ fpToText db
271 tell' $ "--libdir=" ++ fpToText (pbLibDir pb)
272 tell' $ "--bindir=" ++ fpToText (pbBinDir pb)
273 tell' $ "--datadir=" ++ fpToText (pbDataDir pb)
274 tell' $ "--docdir=" ++ fpToText (pbDocDir pb)
275 tell' $ "--flags=" ++ flags
280 flags = unwords $ map go $ mapToList pcFlagOverrides
282 go (name, isOn) = concat
283 [ if isOn then "" else "-"
287 PackageConstraints {..} = ppConstraints $ piPlan sbPackageInfo
289 buildLibrary = wf libOut $ \outH -> do
290 let run = runChild outH
291 log' $ "Unpacking " ++ namever
292 runParent outH "cabal" ["unpack", namever]
294 log' $ "Configuring " ++ namever
295 run "cabal" $ "configure" : configArgs
297 log' $ "Building " ++ namever
298 run "cabal" ["build"]
300 log' $ "Copying/registering " ++ namever
302 withMVar sbRegisterMutex $ const $
303 run "cabal" ["register"]
305 -- Even if the tests later fail, we can allow other libraries to build
306 -- on top of our successful results
308 -- FIXME do we need to wait to do this until after Haddocks build?
309 -- otherwise, we could have a race condition and try to build a
310 -- dependency's haddocks before this finishes
311 atomically $ putTMVar (piResult sbPackageInfo) True
313 when (pcHaddocks /= Don'tBuild && not (null $ sdModules $ ppDesc $ piPlan sbPackageInfo)) $ do
314 log' $ "Haddocks " ++ namever
315 hfs <- readTVarIO sbHaddockFiles
316 let hfsOpts = flip map (mapToList hfs) $ \(pkgVer, hf) -> concat
317 [ "--haddock-options=--read-interface="
324 : "--hyperlink-source"
327 : "--html-location=../$pkg-$version/"
330 eres <- tryAny $ run "cabal" args
332 forM_ eres $ \() -> do
334 (childDir </> "dist" </> "doc" </> "html" </> fpFromText name)
335 (pbDocDir pb </> fpFromText namever)
340 </> fpFromText namever
341 </> fpFromText name <.> "haddock"
343 Left e -> warn $ tshow e
344 Right newPath -> atomically
345 $ modifyTVar sbHaddockFiles
346 $ insertMap namever newPath
348 case (eres, pcHaddocks) of
349 (Left e, ExpectSuccess) -> throwM e
350 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected Haddock success"
353 runTests = wf testOut $ \outH -> do
354 let run = runChild outH
356 when (pcTests /= Don'tBuild) $ do
357 log' $ "Test configure " ++ namever
358 run "cabal" $ "configure" : "--enable-tests" : configArgs
361 log' $ "Test build " ++ namever
362 run "cabal" ["build"]
364 log' $ "Test run " ++ namever
365 run "cabal" ["test", "--log=" ++ fpToText testRunOut]
367 case (eres, pcTests) of
368 (Left e, ExpectSuccess) -> throwM e
369 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected test success"
372 warn t = atomically $ modifyTVar sbWarningsVar (. (t:))
376 [ display (piName sbPackageInfo)
380 atomically $ modifyTVar sbErrsVar $ insertMap (piName sbPackageInfo) exc'
383 case fromException exc of
385 Nothing -> BuildFailureException exc
387renameOrCopy :: FilePath -> FilePath -> IO ()
388renameOrCopy src dest = rename src dest `catchIO` \_ -> copyDir src dest
390copyDir :: FilePath -> FilePath -> IO ()
392 runResourceT $ sourceDirectoryDeep False src $$ mapM_C go
395 go fp = forM_ (F.stripPrefix src' fp) $ \suffix -> do
396 let dest' = dest </> suffix
397 liftIO $ createTree $ parent dest'
398 sourceFile fp $$ (sinkFile dest' :: Sink ByteString (ResourceT IO) ())
400copyBuiltInHaddocks :: FilePath -> IO ()
401copyBuiltInHaddocks docdir = do
402 mghc <- findExecutable "ghc"
404 Nothing -> error "GHC not found on PATH"
406 src <- canonicalizePath
407 (parent (fpFromString ghc) </> "../share/doc/ghc/html/libraries")
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list