@@ -19,17 +19,18 @@ import qualified Data.Map as Map
1919import Data.NonNull (fromNullable)
2020import Filesystem (canonicalizePath, createTree,
2121 getWorkingDirectory, isDirectory,
22− removeTree, rename)
22+ removeTree, rename, isFile, removeFile)
2323import Filesystem.Path (parent)
2424import qualified Filesystem.Path as F
2525import Stackage.BuildConstraints
2626import Stackage.BuildPlan
27+import Stackage.GhcPkg
2728import Stackage.PackageDescription
2829import Stackage.Prelude hiding (pi)
2930import System.Directory (findExecutable)
3031import System.Environment (getEnvironment)
3132import System.IO (IOMode (WriteMode),
32− withBinaryFile)
33+ openBinaryFile)
3334import System.IO.Temp (withSystemTempDirectory)
3435
3536data BuildException = BuildException (Map PackageName BuildFailure) [Text]
@@ -62,7 +63,9 @@ data PerformBuild = PerformBuild
6263 , pbGlobalInstall :: Bool
6364 -- ^ Register packages in the global database
6465 , pbEnableTests :: Bool
66+ , pbEnableHaddock :: Bool
6567 , pbEnableLibProfiling :: Bool
68+ , pbEnableExecDyn :: Bool
6669 , pbVerbose :: Bool
6770 , pbAllowNewer :: Bool
6871 -- ^ Pass --allow-newer to cabal configure
@@ -89,7 +92,7 @@ waitForDeps toolMap packageMap activeComps bp pi action = do
8992 Nothing
9093 | isCoreExe exe -> return ()
9194 -- https://github.com/jgm/zip-archive/issues/23
92− -- | otherwise -> throwSTM $ ToolMissing exe
95+ -- - | otherwise -> throwSTM $ ToolMissing exe
9396 | otherwise -> return ()
9497 Just packages -> ofoldl1' (<|>) packages
9598 action
@@ -133,6 +136,10 @@ pbLibDir pb = pbInstallDest pb </> "lib"
133136pbDataDir pb = pbInstallDest pb </> "share"
134137pbDocDir pb = pbInstallDest pb </> "doc"
135138
139+-- | Directory keeping previous result info
140+pbPrevResDir :: PerformBuild -> FilePath
141+pbPrevResDir pb = pbInstallDest pb </> "prevres"
142+
136143performBuild :: PerformBuild -> IO [Text]
137144performBuild pb = do
138145 cwd <- getWorkingDirectory
@@ -160,12 +167,13 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
160167 $ \ClosedStream Inherited Inherited -> return ()
161168
162169 let removeTree' fp = whenM (isDirectory fp) (removeTree fp)
163− mapM_ removeTree' [pbInstallDest, pbLogDir]
170+ removeTree' pbLogDir
164171
165− forM_ (pbDatabase pb) $ \db -> do
166− createTree $ parent db
167− withCheckedProcess (proc "ghc-pkg" ["init", fpToString db])
168− $ \ClosedStream Inherited Inherited -> return ()
172+ forM_ (pbDatabase pb) $ \db ->
173+ unlessM (isFile $ db </> "package.cache") $ do
174+ createTree $ parent db
175+ withCheckedProcess (proc "ghc-pkg" ["init", fpToString db])
176+ $ \ClosedStream Inherited Inherited -> return ()
169177 pbLog $ encodeUtf8 "Copying built-in Haddocks\n"
170178 copyBuiltInHaddocks (pbDocDir pb)
171179 pbLog $ encodeUtf8 "Finished copying built-in Haddocks\n"
@@ -186,7 +194,15 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
186194 env <- getEnvironment
187195 haddockFiles <- newTVarIO mempty
188196
189− forM_ packageMap $ \pi -> void $ async $ singleBuild pb SingleBuild
197+ registeredPackages <- setupPackageDatabase
198+ (pbDatabase pb)
199+ (pbDocDir pb)
200+ pbLog
201+ (ppVersion <$> bpPackages pbPlan)
202+ (deletePreviousResults pb)
203+
204+ forM_ packageMap $ \pi -> void $ async $ singleBuild pb registeredPackages
205+ SingleBuild
190206 { sbSem = sem
191207 , sbErrsVar = errsVar
192208 , sbWarningsVar = warningsVar
@@ -248,8 +264,10 @@ data SingleBuild = SingleBuild
248264 , sbHaddockFiles :: TVar (Map Text FilePath) -- ^ package-version, .haddock file
249265 }
250266
251−singleBuild :: PerformBuild -> SingleBuild -> IO ()
252−singleBuild pb@PerformBuild {..} SingleBuild {..} =
267+singleBuild :: PerformBuild
268+ -> Set PackageName -- ^ registered packages
269+ -> SingleBuild -> IO ()
270+singleBuild pb@PerformBuild {..} registeredPackages SingleBuild {..} =
253271 withCounter sbActive
254272 $ handle updateErrs
255273 $ (`finally` void (atomically $ tryPutTMVar (piResult sbPackageInfo) False))
@@ -261,22 +279,25 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
261279 let wfd comps =
262280 waitForDeps sbToolMap sbPackageMap comps pbPlan sbPackageInfo
263281 . withTSem sbSem
264− wfd libComps buildLibrary
282+ withUnpacked <- wfd libComps buildLibrary
265283
266− wfd testComps runTests
284+ wfd testComps (runTests withUnpacked)
267285
268− name = display $ piName sbPackageInfo
286+ pname = piName sbPackageInfo
287+ pident = PackageIdentifier pname (ppVersion $ piPlan sbPackageInfo)
288+ name = display pname
269289 namever = concat
270290 [ name
271291 , "-"
272292 , display $ ppVersion $ piPlan sbPackageInfo
273293 ]
274294
275− runIn wdir outH cmd args =
276− withCheckedProcess cp $ \ClosedStream UseProvidedHandle UseProvidedHandle ->
295+ runIn wdir getOutH cmd args = do
296+ outH <- getOutH
297+ withCheckedProcess (cp outH) $ \ClosedStream UseProvidedHandle UseProvidedHandle ->
277298 (return () :: IO ())
278299 where
279− cp = (proc (unpack $ asText cmd) (map (unpack . asText) args))
300+ cp outH = (proc (unpack $ asText cmd) (map (unpack . asText) args))
280301 { cwd = Just $ fpToString wdir
281302 , std_out = UseHandle outH
282303 , std_err = UseHandle outH
@@ -302,8 +323,21 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
302323 testRunOut = pbLogDir </> fpFromText namever </> "test-run.out"
303324
304325 wf fp inner' = do
305− createTree $ parent fp
306− withBinaryFile (fpToString fp) WriteMode inner'
326+ ref <- newIORef Nothing
327+ let cleanup = do
328+ mh <- readIORef ref
329+ forM_ mh hClose
330+ getH = do
331+ mh <- readIORef ref
332+ case mh of
333+ Just h -> return h
334+ Nothing -> mask_ $ do
335+ createTree $ parent fp
336+ h <- openBinaryFile (fpToString fp) WriteMode
337+ writeIORef ref $ Just h
338+ return h
339+
340+ inner' getH `finally` cleanup
307341
308342 configArgs = ($ []) $ execWriter $ do
309343 when pbAllowNewer $ tell' "--allow-newer"
@@ -317,6 +351,7 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
317351 tell' $ "--flags=" ++ flags
318352 when (pbEnableLibProfiling && pcEnableLibProfile) $
319353 tell' "--enable-library-profiling"
354+ when pbEnableExecDyn $ tell' "--enable-executable-dynamic"
320355 where
321356 tell' x = tell (x:)
322357
@@ -330,22 +365,40 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
330365
331366 PackageConstraints {..} = ppConstraints $ piPlan sbPackageInfo
332367
333− buildLibrary = wf libOut $ \outH -> do
368+ buildLibrary = wf libOut $ \getOutH -> do
334369 let run a b = do when pbVerbose $ log' (unwords (a : b))
335− runChild outH a b
336− log' $ "Unpacking " ++ namever
337− runParent outH "cabal" ["unpack", namever]
338−
339− log' $ "Configuring " ++ namever
340− run "cabal" $ "configure" : configArgs
341−
342− log' $ "Building " ++ namever
343− run "cabal" ["build"]
344−
345− log' $ "Copying/registering " ++ namever
346− run "cabal" ["copy"]
347− withMVar sbRegisterMutex $ const $
348− run "cabal" ["register"]
370+ runChild getOutH a b
371+
372+ isUnpacked <- newIORef False
373+ let withUnpacked inner = do
374+ unlessM (readIORef isUnpacked) $ do
375+ log' $ "Unpacking " ++ namever
376+ runParent getOutH "cabal" ["unpack", namever]
377+ writeIORef isUnpacked True
378+ inner
379+
380+ isConfiged <- newIORef False
381+ let withConfiged inner = withUnpacked $ do
382+ unlessM (readIORef isConfiged) $ do
383+ log' $ "Configuring " ++ namever
384+ run "cabal" $ "configure" : configArgs
385+ writeIORef isConfiged True
386+ inner
387+
388+ prevBuildResult <- getPreviousResult pb Build pident
389+ unless (prevBuildResult == PRSuccess) $ withConfiged $ do
390+ assert (pname `notMember` registeredPackages) $ do
391+ deletePreviousResults pb pident
392+
393+ log' $ "Building " ++ namever
394+ run "cabal" ["build"]
395+
396+ log' $ "Copying/registering " ++ namever
397+ run "cabal" ["copy"]
398+ withMVar sbRegisterMutex $ const $
399+ run "cabal" ["register"]
400+
401+ savePreviousResult pb Build pident True
349402
350403 -- Even if the tests later fail, we can allow other libraries to build
351404 -- on top of our successful results
@@ -355,7 +408,11 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
355408 -- dependency's haddocks before this finishes
356409 atomically $ putTMVar (piResult sbPackageInfo) True
357410
358− when (pcHaddocks /= Don'tBuild && not (null $ sdModules $ ppDesc $ piPlan sbPackageInfo)) $ do
411+ prevHaddockResult <- getPreviousResult pb Haddock pident
412+ let needHaddock = pbEnableHaddock
413+ && checkPrevResult prevHaddockResult pcHaddocks
414+ && not (null $ sdModules $ ppDesc $ piPlan sbPackageInfo)
415+ when needHaddock $ withConfiged $ do
359416 log' $ "Haddocks " ++ namever
360417 hfs <- readTVarIO sbHaddockFiles
361418 let hfsOpts = flip map (mapToList hfs) $ \(pkgVer, hf) -> concat
@@ -389,15 +446,21 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
389446 $ modifyTVar sbHaddockFiles
390447 $ insertMap namever newPath
391448
449+ savePreviousResult pb Haddock pident $ either (const False) (const True) eres
392450 case (eres, pcHaddocks) of
393451 (Left e, ExpectSuccess) -> throwM e
394452 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected Haddock success"
395453 _ -> return ()
396454
397− runTests = wf testOut $ \outH -> do
398− let run = runChild outH
455+ return withUnpacked
456+
457+ runTests withUnpacked = wf testOut $ \getOutH -> do
458+ let run = runChild getOutH
399459
400− when (pbEnableTests && pcTests /= Don'tBuild) $ do
460+ prevTestResult <- getPreviousResult pb Test pident
461+ let needTest = pbEnableTests
462+ && checkPrevResult prevTestResult pcTests
463+ when needTest $ withUnpacked $ do
401464 log' $ "Test configure " ++ namever
402465 run "cabal" $ "configure" : "--enable-tests" : configArgs
403466
@@ -408,6 +471,7 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
408471 log' $ "Test run " ++ namever
409472 run "cabal" ["test", "--log=" ++ fpToText testRunOut]
410473
474+ savePreviousResult pb Test pident $ either (const False) (const True) eres
411475 case (eres, pcTests) of
412476 (Left e, ExpectSuccess) -> throwM e
413477 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected test success"
@@ -434,16 +498,6 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
434498renameOrCopy :: FilePath -> FilePath -> IO ()
435499renameOrCopy src dest = rename src dest `catchIO` \_ -> copyDir src dest
436500
437−copyDir :: FilePath -> FilePath -> IO ()
438−copyDir src dest =
439− runResourceT $ sourceDirectoryDeep False src $$ mapM_C go
440− where
441− src' = src </> ""
442− go fp = forM_ (F.stripPrefix src' fp) $ \suffix -> do
443− let dest' = dest </> suffix
444− liftIO $ createTree $ parent dest'
445− sourceFile fp $$ (sinkFile dest' :: Sink ByteString (ResourceT IO) ())
446−
447501copyBuiltInHaddocks :: FilePath -> IO ()
448502copyBuiltInHaddocks docdir = do
449503 mghc <- findExecutable "ghc"
@@ -453,3 +507,52 @@ copyBuiltInHaddocks docdir = do
453507 src <- canonicalizePath
454508 (parent (fpFromString ghc) </> "../share/doc/ghc/html/libraries")
455509 copyDir src docdir
510+
511+------------- Previous results
512+
513+-- | The previous actions that can be run
514+data ResultType = Build | Haddock | Test
515+ deriving (Show, Enum, Eq, Ord, Bounded, Read)
516+
517+-- | The result generated on a previous run
518+data PrevResult = PRNoResult | PRSuccess | PRFailure
519+ deriving (Show, Enum, Eq, Ord, Bounded, Read)
520+
521+-- | Check if we should rerun based on a PrevResult and the expected status
522+checkPrevResult :: PrevResult -> TestState -> Bool
523+checkPrevResult _ Don'tBuild = False
524+checkPrevResult PRNoResult _ = True
525+checkPrevResult PRSuccess _ = False
526+checkPrevResult PRFailure ExpectSuccess = True
527+checkPrevResult PRFailure _ = False
528+
529+withPRPath :: PerformBuild -> ResultType -> PackageIdentifier -> (FilePath -> IO a) -> IO a
530+withPRPath pb rt ident inner = do
531+ createTree $ parent fp
532+ inner fp
533+ where
534+ fp = pbPrevResDir pb </> fpFromString (show rt) </> fpFromText (display ident)
535+
536+successBS, failureBS :: ByteString
537+successBS = "success"
538+failureBS = "failure"
539+
540+getPreviousResult :: PerformBuild -> ResultType -> PackageIdentifier -> IO PrevResult
541+getPreviousResult w x y = withPRPath w x y $ \fp -> do
542+ eres <- tryIO $ readFile fp
543+ return $ case eres of
544+ Right bs
545+ | bs == successBS -> PRSuccess
546+ | bs == failureBS -> PRFailure
547+ _ -> PRNoResult
548+
549+savePreviousResult :: PerformBuild -> ResultType -> PackageIdentifier -> Bool -> IO ()
550+savePreviousResult pb rt ident res =
551+ withPRPath pb rt ident $ \fp -> writeFile fp $
552+ if res then successBS else failureBS
553+
554+deletePreviousResults :: PerformBuild -> PackageIdentifier -> IO ()
555+deletePreviousResults pb name =
556+ forM_ [minBound..maxBound] $ \rt ->
557+ withPRPath pb rt name $ \fp ->
558+ void $ tryIO $ removeFile fp