@@ -19,17 +19,18 @@ 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)
30import System.Environment (getEnvironment)
31import System.Environment (getEnvironment)
31import System.IO (IOMode (WriteMode),
32import System.IO (IOMode (WriteMode),
32− withBinaryFile)
33+ openBinaryFile)
33import System.IO.Temp (withSystemTempDirectory)
34import System.IO.Temp (withSystemTempDirectory)
35data BuildException = BuildException (Map PackageName BuildFailure) [Text]
36data BuildException = BuildException (Map PackageName BuildFailure) [Text]
@@ -62,7 +63,9 @@ data PerformBuild = PerformBuild
62 , pbGlobalInstall :: Bool
63 , pbGlobalInstall :: Bool
63 -- ^ Register packages in the global database
64 -- ^ Register packages in the global database
64 , pbEnableTests :: Bool
65 , pbEnableTests :: Bool
66+ , pbEnableHaddock :: Bool
65 , pbEnableLibProfiling :: Bool
67 , pbEnableLibProfiling :: Bool
68+ , pbEnableExecDyn :: Bool
66 , pbVerbose :: Bool
69 , pbVerbose :: Bool
67 , pbAllowNewer :: Bool
70 , pbAllowNewer :: Bool
68 -- ^ Pass --allow-newer to cabal configure
71 -- ^ Pass --allow-newer to cabal configure
@@ -89,7 +92,7 @@ waitForDeps toolMap packageMap activeComps bp pi action = do
90 | isCoreExe exe -> return ()
93 | isCoreExe exe -> return ()
91 -- https://github.com/jgm/zip-archive/issues/23
94 -- https://github.com/jgm/zip-archive/issues/23
92− -- | otherwise -> throwSTM $ ToolMissing exe
95+ -- - | otherwise -> throwSTM $ ToolMissing exe
93 | otherwise -> return ()
96 | otherwise -> return ()
94 Just packages -> ofoldl1' (<|>) packages
97 Just packages -> ofoldl1' (<|>) packages
@@ -133,6 +136,10 @@ pbLibDir pb = pbInstallDest pb </> "lib"
133pbDataDir pb = pbInstallDest pb </> "share"
136pbDataDir pb = pbInstallDest pb </> "share"
134pbDocDir 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"
136performBuild :: PerformBuild -> IO [Text]
143performBuild :: PerformBuild -> IO [Text]
137performBuild pb = do
144performBuild pb = do
138 cwd <- getWorkingDirectory
145 cwd <- getWorkingDirectory
@@ -160,12 +167,13 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
160 $ \ClosedStream Inherited Inherited -> return ()
167 $ \ClosedStream Inherited Inherited -> return ()
162 let removeTree' fp = whenM (isDirectory fp) (removeTree fp)
169 let removeTree' fp = whenM (isDirectory fp) (removeTree fp)
163− mapM_ removeTree' [pbInstallDest, pbLogDir]
170+ removeTree' pbLogDir
165− forM_ (pbDatabase pb) $ \db -> do
172+ forM_ (pbDatabase pb) $ \db ->
173+ unlessM (isFile $ db </> "package.cache") $ do
166− createTree $ parent db
174+ createTree $ parent db
167− withCheckedProcess (proc "ghc-pkg" ["init", fpToString db])
175+ withCheckedProcess (proc "ghc-pkg" ["init", fpToString db])
168− $ \ClosedStream Inherited Inherited -> return ()
176+ $ \ClosedStream Inherited Inherited -> return ()
169 pbLog $ encodeUtf8 "Copying built-in Haddocks\n"
177 pbLog $ encodeUtf8 "Copying built-in Haddocks\n"
170 copyBuiltInHaddocks (pbDocDir pb)
178 copyBuiltInHaddocks (pbDocDir pb)
171 pbLog $ encodeUtf8 "Finished copying built-in Haddocks\n"
179 pbLog $ encodeUtf8 "Finished copying built-in Haddocks\n"
@@ -186,7 +194,15 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
186 env <- getEnvironment
194 env <- getEnvironment
187 haddockFiles <- newTVarIO mempty
195 haddockFiles <- newTVarIO mempty
197+ registeredPackages <- setupPackageDatabase
201+ (ppVersion <$> bpPackages pbPlan)
202+ (deletePreviousResults pb)
189− forM_ packageMap $ \pi -> void $ async $ singleBuild pb SingleBuild
204+ forM_ packageMap $ \pi -> void $ async $ singleBuild pb registeredPackages
190 { sbSem = sem
206 { sbSem = sem
191 , sbErrsVar = errsVar
207 , sbErrsVar = errsVar
192 , sbWarningsVar = warningsVar
208 , sbWarningsVar = warningsVar
@@ -248,8 +264,10 @@ data SingleBuild = SingleBuild
248 , sbHaddockFiles :: TVar (Map Text FilePath) -- ^ package-version, .haddock file
264 , sbHaddockFiles :: TVar (Map Text FilePath) -- ^ package-version, .haddock file
251−singleBuild :: PerformBuild -> SingleBuild -> IO ()
267+singleBuild :: PerformBuild
268+ -> Set PackageName -- ^ registered packages
269+ -> SingleBuild -> IO ()
252−singleBuild pb@PerformBuild {..} SingleBuild {..} =
270+singleBuild pb@PerformBuild {..} registeredPackages SingleBuild {..} =
253 withCounter sbActive
271 withCounter sbActive
254 $ handle updateErrs
272 $ handle updateErrs
255 $ (`finally` void (atomically $ tryPutTMVar (piResult sbPackageInfo) False))
273 $ (`finally` void (atomically $ tryPutTMVar (piResult sbPackageInfo) False))
@@ -261,22 +279,25 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
261 let wfd comps =
279 let wfd comps =
262 waitForDeps sbToolMap sbPackageMap comps pbPlan sbPackageInfo
280 waitForDeps sbToolMap sbPackageMap comps pbPlan sbPackageInfo
263 . withTSem sbSem
281 . withTSem sbSem
264− wfd libComps buildLibrary
282+ withUnpacked <- wfd libComps buildLibrary
266− wfd testComps runTests
284+ wfd testComps (runTests withUnpacked)
286+ pname = piName sbPackageInfo
287+ pident = PackageIdentifier pname (ppVersion $ piPlan sbPackageInfo)
268− name = display $ piName sbPackageInfo
288+ name = display pname
269 namever = concat
289 namever = concat
272 , display $ ppVersion $ piPlan sbPackageInfo
292 , display $ ppVersion $ piPlan sbPackageInfo
275− runIn wdir outH cmd args =
295+ runIn wdir getOutH cmd args = do
276− withCheckedProcess cp $ \ClosedStream UseProvidedHandle UseProvidedHandle ->
297+ withCheckedProcess (cp outH) $ \ClosedStream UseProvidedHandle UseProvidedHandle ->
277 (return () :: IO ())
298 (return () :: IO ())
279− cp = (proc (unpack $ asText cmd) (map (unpack . asText) args))
300+ cp outH = (proc (unpack $ asText cmd) (map (unpack . asText) args))
280 { cwd = Just $ fpToString wdir
301 { cwd = Just $ fpToString wdir
281 , std_out = UseHandle outH
302 , std_out = UseHandle outH
282 , std_err = UseHandle outH
303 , std_err = UseHandle outH
@@ -302,8 +323,21 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
302 testRunOut = pbLogDir </> fpFromText namever </> "test-run.out"
323 testRunOut = pbLogDir </> fpFromText namever </> "test-run.out"
304 wf fp inner' = do
325 wf fp inner' = do
326+ ref <- newIORef Nothing
334+ Nothing -> mask_ $ do
305− createTree $ parent fp
335+ createTree $ parent fp
306− withBinaryFile (fpToString fp) WriteMode inner'
336+ h <- openBinaryFile (fpToString fp) WriteMode
337+ writeIORef ref $ Just h
340+ inner' getH `finally` cleanup
308 configArgs = ($ []) $ execWriter $ do
342 configArgs = ($ []) $ execWriter $ do
309 when pbAllowNewer $ tell' "--allow-newer"
343 when pbAllowNewer $ tell' "--allow-newer"
@@ -317,6 +351,7 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
317 tell' $ "--flags=" ++ flags
351 tell' $ "--flags=" ++ flags
318 when (pbEnableLibProfiling && pcEnableLibProfile) $
352 when (pbEnableLibProfiling && pcEnableLibProfile) $
319 tell' "--enable-library-profiling"
353 tell' "--enable-library-profiling"
354+ when pbEnableExecDyn $ tell' "--enable-executable-dynamic"
321 tell' x = tell (x:)
356 tell' x = tell (x:)
@@ -330,22 +365,40 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
331 PackageConstraints {..} = ppConstraints $ piPlan sbPackageInfo
366 PackageConstraints {..} = ppConstraints $ piPlan sbPackageInfo
333− buildLibrary = wf libOut $ \outH -> do
368+ buildLibrary = wf libOut $ \getOutH -> do
334 let run a b = do when pbVerbose $ log' (unwords (a : b))
369 let run a b = do when pbVerbose $ log' (unwords (a : b))
335− runChild outH a b
370+ runChild getOutH a b
372+ isUnpacked <- newIORef False
373+ let withUnpacked inner = do
374+ unlessM (readIORef isUnpacked) $ do
336− log' $ "Unpacking " ++ namever
375+ log' $ "Unpacking " ++ namever
337− runParent outH "cabal" ["unpack", namever]
376+ runParent getOutH "cabal" ["unpack", namever]
338−
377+ writeIORef isUnpacked True
380+ isConfiged <- newIORef False
381+ let withConfiged inner = withUnpacked $ do
382+ unlessM (readIORef isConfiged) $ do
339− log' $ "Configuring " ++ namever
383+ log' $ "Configuring " ++ namever
340− run "cabal" $ "configure" : configArgs
384+ run "cabal" $ "configure" : configArgs
341−
385+ writeIORef isConfiged True
388+ prevBuildResult <- getPreviousResult pb Build pident
389+ unless (prevBuildResult == PRSuccess) $ withConfiged $ do
390+ assert (pname `notMember` registeredPackages) $ do
391+ deletePreviousResults pb pident
342− log' $ "Building " ++ namever
393+ log' $ "Building " ++ namever
343− run "cabal" ["build"]
394+ run "cabal" ["build"]
345− log' $ "Copying/registering " ++ namever
396+ log' $ "Copying/registering " ++ namever
346− run "cabal" ["copy"]
397+ run "cabal" ["copy"]
347− withMVar sbRegisterMutex $ const $
398+ withMVar sbRegisterMutex $ const $
348− run "cabal" ["register"]
399+ run "cabal" ["register"]
401+ savePreviousResult pb Build pident True
350 -- Even if the tests later fail, we can allow other libraries to build
403 -- Even if the tests later fail, we can allow other libraries to build
351 -- on top of our successful results
404 -- on top of our successful results
@@ -355,7 +408,11 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
355 -- dependency's haddocks before this finishes
408 -- dependency's haddocks before this finishes
356 atomically $ putTMVar (piResult sbPackageInfo) True
409 atomically $ putTMVar (piResult sbPackageInfo) True
411+ prevHaddockResult <- getPreviousResult pb Haddock pident
412+ let needHaddock = pbEnableHaddock
413+ && checkPrevResult prevHaddockResult pcHaddocks
358− when (pcHaddocks /= Don'tBuild && not (null $ sdModules $ ppDesc $ piPlan sbPackageInfo)) $ do
414+ && not (null $ sdModules $ ppDesc $ piPlan sbPackageInfo)
415+ when needHaddock $ withConfiged $ do
359 log' $ "Haddocks " ++ namever
416 log' $ "Haddocks " ++ namever
360 hfs <- readTVarIO sbHaddockFiles
417 hfs <- readTVarIO sbHaddockFiles
361 let hfsOpts = flip map (mapToList hfs) $ \(pkgVer, hf) -> concat
418 let hfsOpts = flip map (mapToList hfs) $ \(pkgVer, hf) -> concat
@@ -389,15 +446,21 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
389 $ modifyTVar sbHaddockFiles
446 $ modifyTVar sbHaddockFiles
390 $ insertMap namever newPath
447 $ insertMap namever newPath
449+ savePreviousResult pb Haddock pident $ either (const False) (const True) eres
392 case (eres, pcHaddocks) of
450 case (eres, pcHaddocks) of
393 (Left e, ExpectSuccess) -> throwM e
451 (Left e, ExpectSuccess) -> throwM e
394 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected Haddock success"
452 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected Haddock success"
395 _ -> return ()
453 _ -> return ()
397− runTests = wf testOut $ \outH -> do
457+ runTests withUnpacked = wf testOut $ \getOutH -> do
398− let run = runChild outH
458+ let run = runChild getOutH
460+ prevTestResult <- getPreviousResult pb Test pident
400− when (pbEnableTests && pcTests /= Don'tBuild) $ do
461+ let needTest = pbEnableTests
462+ && checkPrevResult prevTestResult pcTests
463+ when needTest $ withUnpacked $ do
401 log' $ "Test configure " ++ namever
464 log' $ "Test configure " ++ namever
402 run "cabal" $ "configure" : "--enable-tests" : configArgs
465 run "cabal" $ "configure" : "--enable-tests" : configArgs
@@ -408,6 +471,7 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
408 log' $ "Test run " ++ namever
471 log' $ "Test run " ++ namever
409 run "cabal" ["test", "--log=" ++ fpToText testRunOut]
472 run "cabal" ["test", "--log=" ++ fpToText testRunOut]
474+ savePreviousResult pb Test pident $ either (const False) (const True) eres
411 case (eres, pcTests) of
475 case (eres, pcTests) of
412 (Left e, ExpectSuccess) -> throwM e
476 (Left e, ExpectSuccess) -> throwM e
413 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected test success"
477 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected test success"
@@ -434,16 +498,6 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
434renameOrCopy :: FilePath -> FilePath -> IO ()
498renameOrCopy :: FilePath -> FilePath -> IO ()
435renameOrCopy src dest = rename src dest `catchIO` \_ -> copyDir src dest
499renameOrCopy src dest = rename src dest `catchIO` \_ -> copyDir src dest
437−copyDir :: FilePath -> FilePath -> IO ()
439− runResourceT $ sourceDirectoryDeep False src $$ mapM_C go
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) ())
447copyBuiltInHaddocks :: FilePath -> IO ()
501copyBuiltInHaddocks :: FilePath -> IO ()
448copyBuiltInHaddocks docdir = do
502copyBuiltInHaddocks docdir = do
449 mghc <- findExecutable "ghc"
503 mghc <- findExecutable "ghc"
@@ -453,3 +507,52 @@ copyBuiltInHaddocks docdir = do
453 src <- canonicalizePath
507 src <- canonicalizePath
454 (parent (fpFromString ghc) </> "../share/doc/ghc/html/libraries")
508 (parent (fpFromString ghc) </> "../share/doc/ghc/html/libraries")
455 copyDir src docdir
509 copyDir src docdir
511+------------- Previous results
513+-- | The previous actions that can be run
514+data ResultType = Build | Haddock | Test
515+ deriving (Show, Enum, Eq, Ord, Bounded, Read)
517+-- | The result generated on a previous run
518+data PrevResult = PRNoResult | PRSuccess | PRFailure
519+ deriving (Show, Enum, Eq, Ord, Bounded, Read)
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
529+withPRPath :: PerformBuild -> ResultType -> PackageIdentifier -> (FilePath -> IO a) -> IO a
530+withPRPath pb rt ident inner = do
531+ createTree $ parent fp
534+ fp = pbPrevResDir pb </> fpFromString (show rt) </> fpFromText (display ident)
536+successBS, failureBS :: ByteString
537+successBS = "success"
538+failureBS = "failure"
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
545+ | bs == successBS -> PRSuccess
546+ | bs == failureBS -> PRFailure
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
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