@@ -19,11 +19,12 @@ import qualified Data.Map as Map
19import Data.NonNull (fromNullable)
19import Data.NonNull (fromNullable)
20import Filesystem (canonicalizePath, createTree,
20import Filesystem (canonicalizePath, createTree,
21 getWorkingDirectory, isDirectory,
21 getWorkingDirectory, isDirectory,
22− removeTree, rename)
22+ removeTree, rename, isFile, removeFile)
23import Filesystem.Path (parent)
23import Filesystem.Path (parent)
24import qualified Filesystem.Path as F
24import qualified Filesystem.Path as F
25import Stackage.BuildConstraints
25import Stackage.BuildConstraints
26import Stackage.BuildPlan
26import Stackage.BuildPlan
27+import Stackage.GhcPkg
27import Stackage.PackageDescription
28import Stackage.PackageDescription
28import Stackage.Prelude hiding (pi)
29import Stackage.Prelude hiding (pi)
29import System.Directory (findExecutable)
30import System.Directory (findExecutable)
@@ -135,6 +136,10 @@ pbLibDir pb = pbInstallDest pb </> "lib"
135pbDataDir pb = pbInstallDest pb </> "share"
136pbDataDir pb = pbInstallDest pb </> "share"
136pbDocDir pb = pbInstallDest pb </> "doc"
137pbDocDir pb = pbInstallDest pb </> "doc"
139+-- | Directory keeping previous result info
140+pbPrevResDir :: PerformBuild -> FilePath
141+pbPrevResDir pb = pbInstallDest pb </> "prevres"
138performBuild :: PerformBuild -> IO [Text]
143performBuild :: PerformBuild -> IO [Text]
139performBuild pb = do
144performBuild pb = do
140 cwd <- getWorkingDirectory
145 cwd <- getWorkingDirectory
@@ -162,12 +167,13 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
162 $ \ClosedStream Inherited Inherited -> return ()
167 $ \ClosedStream Inherited Inherited -> return ()
164 let removeTree' fp = whenM (isDirectory fp) (removeTree fp)
169 let removeTree' fp = whenM (isDirectory fp) (removeTree fp)
165− mapM_ removeTree' [pbInstallDest, pbLogDir]
170+ removeTree' pbLogDir
167− forM_ (pbDatabase pb) $ \db -> do
172+ forM_ (pbDatabase pb) $ \db ->
173+ unlessM (isFile $ db </> "package.cache") $ do
168− createTree $ parent db
174+ createTree $ parent db
169− withCheckedProcess (proc "ghc-pkg" ["init", fpToString db])
175+ withCheckedProcess (proc "ghc-pkg" ["init", fpToString db])
170− $ \ClosedStream Inherited Inherited -> return ()
176+ $ \ClosedStream Inherited Inherited -> return ()
171 pbLog $ encodeUtf8 "Copying built-in Haddocks\n"
177 pbLog $ encodeUtf8 "Copying built-in Haddocks\n"
172 copyBuiltInHaddocks (pbDocDir pb)
178 copyBuiltInHaddocks (pbDocDir pb)
@@ -187,7 +193,15 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
187 env <- getEnvironment
193 env <- getEnvironment
188 haddockFiles <- newTVarIO mempty
194 haddockFiles <- newTVarIO mempty
196+ registeredPackages <- setupPackageDatabase
200+ (ppVersion <$> bpPackages pbPlan)
201+ (deletePreviousResults pb)
190− forM_ packageMap $ \pi -> void $ async $ singleBuild pb SingleBuild
203+ forM_ packageMap $ \pi -> void $ async $ singleBuild pb registeredPackages
191 { sbSem = sem
205 { sbSem = sem
192 , sbErrsVar = errsVar
206 , sbErrsVar = errsVar
193 , sbWarningsVar = warningsVar
207 , sbWarningsVar = warningsVar
@@ -249,8 +263,10 @@ data SingleBuild = SingleBuild
249 , sbHaddockFiles :: TVar (Map Text FilePath) -- ^ package-version, .haddock file
263 , sbHaddockFiles :: TVar (Map Text FilePath) -- ^ package-version, .haddock file
252−singleBuild :: PerformBuild -> SingleBuild -> IO ()
266+singleBuild :: PerformBuild
267+ -> Set PackageName -- ^ registered packages
268+ -> SingleBuild -> IO ()
253−singleBuild pb@PerformBuild {..} SingleBuild {..} =
269+singleBuild pb@PerformBuild {..} registeredPackages SingleBuild {..} =
254 withCounter sbActive
270 withCounter sbActive
255 $ handle updateErrs
271 $ handle updateErrs
256 $ (`finally` void (atomically $ tryPutTMVar (piResult sbPackageInfo) False))
272 $ (`finally` void (atomically $ tryPutTMVar (piResult sbPackageInfo) False))
@@ -262,11 +278,13 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
262 let wfd comps =
278 let wfd comps =
263 waitForDeps sbToolMap sbPackageMap comps pbPlan sbPackageInfo
279 waitForDeps sbToolMap sbPackageMap comps pbPlan sbPackageInfo
264 . withTSem sbSem
280 . withTSem sbSem
265− wfd libComps buildLibrary
281+ withUnpacked <- wfd libComps buildLibrary
267− wfd testComps runTests
283+ wfd testComps (runTests withUnpacked)
285+ pname = piName sbPackageInfo
286+ pident = PackageIdentifier pname (ppVersion $ piPlan sbPackageInfo)
269− name = display $ piName sbPackageInfo
287+ name = display pname
270 namever = concat
288 namever = concat
@@ -335,19 +353,37 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
335 buildLibrary = wf libOut $ \outH -> do
353 buildLibrary = wf libOut $ \outH -> do
336 let run a b = do when pbVerbose $ log' (unwords (a : b))
354 let run a b = do when pbVerbose $ log' (unwords (a : b))
337 runChild outH a b
355 runChild outH a b
338− log' $ "Unpacking " ++ namever
339− runParent outH "cabal" ["unpack", namever]
341− log' $ "Configuring " ++ namever
342− run "cabal" $ "configure" : configArgs
357+ isUnpacked <- newIORef False
358+ let withUnpacked inner = do
359+ unlessM (readIORef isUnpacked) $ do
360+ log' $ "Unpacking " ++ namever
361+ runParent outH "cabal" ["unpack", namever]
362+ writeIORef isUnpacked True
365+ isConfiged <- newIORef False
366+ let withConfiged inner = withUnpacked $ do
367+ unlessM (readIORef isConfiged) $ do
368+ log' $ "Configuring " ++ namever
369+ run "cabal" $ "configure" : configArgs
370+ writeIORef isConfiged True
373+ prevBuildResult <- getPreviousResult pb Build pident
374+ unless (prevBuildResult == PRSuccess) $ withConfiged $ do
375+ assert (pname `notMember` registeredPackages) $ do
376+ deletePreviousResults pb pident
344− log' $ "Building " ++ namever
378+ log' $ "Building " ++ namever
345− run "cabal" ["build"]
379+ run "cabal" ["build"]
347− log' $ "Copying/registering " ++ namever
381+ log' $ "Copying/registering " ++ namever
348− run "cabal" ["copy"]
382+ run "cabal" ["copy"]
349− withMVar sbRegisterMutex $ const $
383+ withMVar sbRegisterMutex $ const $
350− run "cabal" ["register"]
384+ run "cabal" ["register"]
386+ savePreviousResult pb Build pident True
352 -- Even if the tests later fail, we can allow other libraries to build
388 -- Even if the tests later fail, we can allow other libraries to build
353 -- on top of our successful results
389 -- on top of our successful results
@@ -357,7 +393,11 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
357 -- dependency's haddocks before this finishes
393 -- dependency's haddocks before this finishes
358 atomically $ putTMVar (piResult sbPackageInfo) True
394 atomically $ putTMVar (piResult sbPackageInfo) True
396+ prevHaddockResult <- getPreviousResult pb Haddock pident
397+ let needHaddock = pbEnableHaddock
398+ && checkPrevResult prevHaddockResult pcHaddocks
360− when (pbEnableHaddock && pcHaddocks /= Don'tBuild && not (null $ sdModules $ ppDesc $ piPlan sbPackageInfo)) $ do
399+ && not (null $ sdModules $ ppDesc $ piPlan sbPackageInfo)
400+ when needHaddock $ withConfiged $ do
361 log' $ "Haddocks " ++ namever
401 log' $ "Haddocks " ++ namever
362 hfs <- readTVarIO sbHaddockFiles
402 hfs <- readTVarIO sbHaddockFiles
363 let hfsOpts = flip map (mapToList hfs) $ \(pkgVer, hf) -> concat
403 let hfsOpts = flip map (mapToList hfs) $ \(pkgVer, hf) -> concat
@@ -392,15 +432,21 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
392 $ modifyTVar sbHaddockFiles
432 $ modifyTVar sbHaddockFiles
393 $ insertMap namever newPath
433 $ insertMap namever newPath
435+ savePreviousResult pb Haddock pident $ either (const False) (const True) eres
395 case (eres, pcHaddocks) of
436 case (eres, pcHaddocks) of
396 (Left e, ExpectSuccess) -> throwM e
437 (Left e, ExpectSuccess) -> throwM e
397 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected Haddock success"
438 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected Haddock success"
398 _ -> return ()
439 _ -> return ()
400− runTests = wf testOut $ \outH -> do
443+ runTests withUnpacked = wf testOut $ \outH -> do
401 let run = runChild outH
444 let run = runChild outH
446+ prevTestResult <- getPreviousResult pb Test pident
403− when (pbEnableTests && pcTests /= Don'tBuild) $ do
447+ let needTest = pbEnableTests
448+ && checkPrevResult prevTestResult pcTests
449+ when needTest $ withUnpacked $ do
404 log' $ "Test configure " ++ namever
450 log' $ "Test configure " ++ namever
405 run "cabal" $ "configure" : "--enable-tests" : configArgs
451 run "cabal" $ "configure" : "--enable-tests" : configArgs
@@ -411,6 +457,7 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
411 log' $ "Test run " ++ namever
457 log' $ "Test run " ++ namever
412 run "cabal" ["test", "--log=" ++ fpToText testRunOut]
458 run "cabal" ["test", "--log=" ++ fpToText testRunOut]
460+ savePreviousResult pb Test pident $ either (const False) (const True) eres
414 case (eres, pcTests) of
461 case (eres, pcTests) of
415 (Left e, ExpectSuccess) -> throwM e
462 (Left e, ExpectSuccess) -> throwM e
416 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected test success"
463 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected test success"
@@ -453,3 +500,52 @@ copyBuiltInHaddocks docdir = do
453 src <- canonicalizePath
500 src <- canonicalizePath
454 (parent (fpFromString ghc) </> "../share/doc/ghc/html/libraries")
501 (parent (fpFromString ghc) </> "../share/doc/ghc/html/libraries")
455 copyDir src docdir
502 copyDir src docdir
504+------------- Previous results
506+-- | The previous actions that can be run
507+data ResultType = Build | Haddock | Test
508+ deriving (Show, Enum, Eq, Ord, Bounded, Read)
510+-- | The result generated on a previous run
511+data PrevResult = PRNoResult | PRSuccess | PRFailure
512+ deriving (Show, Enum, Eq, Ord, Bounded, Read)
514+-- | Check if we should rerun based on a PrevResult and the expected status
515+checkPrevResult :: PrevResult -> TestState -> Bool
516+checkPrevResult _ Don'tBuild = False
517+checkPrevResult PRNoResult _ = True
518+checkPrevResult PRSuccess _ = False
519+checkPrevResult PRFailure ExpectSuccess = True
520+checkPrevResult PRFailure _ = False
522+withPRPath :: PerformBuild -> ResultType -> PackageIdentifier -> (FilePath -> IO a) -> IO a
523+withPRPath pb rt ident inner = do
524+ createTree $ parent fp
527+ fp = pbPrevResDir pb </> fpFromString (show rt) </> fpFromText (display ident)
529+successBS, failureBS :: ByteString
530+successBS = "success"
531+failureBS = "failure"
533+getPreviousResult :: PerformBuild -> ResultType -> PackageIdentifier -> IO PrevResult
534+getPreviousResult w x y = withPRPath w x y $ \fp -> do
535+ eres <- tryIO $ readFile fp
536+ return $ case eres of
538+ | bs == successBS -> PRSuccess
539+ | bs == failureBS -> PRFailure
542+savePreviousResult :: PerformBuild -> ResultType -> PackageIdentifier -> Bool -> IO ()
543+savePreviousResult pb rt ident res =
544+ withPRPath pb rt ident $ \fp -> writeFile fp $
545+ if res then successBS else failureBS
547+deletePreviousResults :: PerformBuild -> PackageIdentifier -> IO ()
548+deletePreviousResults pb name =
549+ forM_ [minBound..maxBound] $ \rt ->
550+ withPRPath pb rt name $ \fp ->
551+ void $ tryIO $ removeFile fp