@@ -19,11 +19,12 @@ 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)
@@ -61,6 +62,13 @@ data PerformBuild = PerformBuild
6162 , pbJobs :: Int
6263 , pbGlobalInstall :: Bool
6364 -- ^ Register packages in the global database
65+ , pbEnableTests :: Bool
66+ , pbEnableHaddock :: Bool
67+ , pbEnableLibProfiling :: Bool
68+ , pbEnableExecDyn :: Bool
69+ , pbVerbose :: Bool
70+ , pbAllowNewer :: Bool
71+ -- ^ Pass --allow-newer to cabal configure
6472 }
6573
6674data PackageInfo = PackageInfo
@@ -83,7 +91,9 @@ waitForDeps toolMap packageMap activeComps bp pi action = do
8391 case lookup exe toolMap >>= fromNullable . map checkPackage . setToList of
8492 Nothing
8593 | isCoreExe exe -> return ()
86− | otherwise -> throwSTM $ ToolMissing exe
94+ -- https://github.com/jgm/zip-archive/issues/23
95+ -- | otherwise -> throwSTM $ ToolMissing exe
96+ | otherwise -> return ()
8797 Just packages -> ofoldl1' (<|>) packages
8898 action
8999 where
@@ -126,6 +136,10 @@ pbLibDir pb = pbInstallDest pb </> "lib"
126136pbDataDir pb = pbInstallDest pb </> "share"
127137pbDocDir pb = pbInstallDest pb </> "doc"
128138
139+-- | Directory keeping previous result info
140+pbPrevResDir :: PerformBuild -> FilePath
141+pbPrevResDir pb = pbInstallDest pb </> "prevres"
142+
129143performBuild :: PerformBuild -> IO [Text]
130144performBuild pb = do
131145 cwd <- getWorkingDirectory
@@ -153,12 +167,13 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
153167 $ \ClosedStream Inherited Inherited -> return ()
154168
155169 let removeTree' fp = whenM (isDirectory fp) (removeTree fp)
156− mapM_ removeTree' [pbInstallDest, pbLogDir]
170+ removeTree' pbLogDir
157171
158− forM_ (pbDatabase pb) $ \db -> do
159− createTree $ parent db
160− withCheckedProcess (proc "ghc-pkg" ["init", fpToString db])
161− $ \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 ()
162177 pbLog $ encodeUtf8 "Copying built-in Haddocks\n"
163178 copyBuiltInHaddocks (pbDocDir pb)
164179
@@ -178,7 +193,14 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
178193 env <- getEnvironment
179194 haddockFiles <- newTVarIO mempty
180195
181− forM_ packageMap $ \pi -> void $ async $ singleBuild pb SingleBuild
196+ registeredPackages <- setupPackageDatabase
197+ (pbDatabase pb)
198+ (pbDocDir pb)
199+ pbLog
200+ (ppVersion <$> bpPackages pbPlan)
201+
202+ forM_ packageMap $ \pi -> void $ async $ singleBuild pb registeredPackages
203+ SingleBuild
182204 { sbSem = sem
183205 , sbErrsVar = errsVar
184206 , sbWarningsVar = warningsVar
@@ -192,7 +214,7 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
192214 id
193215 (\db -> (("HASKELL_PACKAGE_SANDBOX", fpToString db):))
194216 (pbDatabase pb)
195− (map fixEnv env)
217+ (filter allowedEnv $ map fixEnv env)
196218 , sbHaddockFiles = haddockFiles
197219 }
198220
@@ -210,6 +232,8 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
210232 | toUpper p == "PATH" = (p, fpToString (pbBinDir pb) ++ pathSep : x)
211233 | otherwise = (p, x)
212234
235+ allowedEnv (k, _) = k `notMember` bannedEnvs
236+
213237 -- | Separate for the PATH environment variable
214238 pathSep :: Char
215239#ifdef mingw32_HOST_OS
@@ -218,6 +242,12 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
218242 pathSep = ':'
219243#endif
220244
245+-- | Environment variables we don't allow to be passed on to child processes.
246+bannedEnvs :: Set String
247+bannedEnvs = setFromList
248+ [ "STACKAGE_AUTH_TOKEN"
249+ ]
250+
221251data SingleBuild = SingleBuild
222252 { sbSem :: TSem
223253 , sbErrsVar :: TVar (Map PackageName BuildFailure)
@@ -232,8 +262,10 @@ data SingleBuild = SingleBuild
232262 , sbHaddockFiles :: TVar (Map Text FilePath) -- ^ package-version, .haddock file
233263 }
234264
235−singleBuild :: PerformBuild -> SingleBuild -> IO ()
236−singleBuild pb@PerformBuild {..} SingleBuild {..} =
265+singleBuild :: PerformBuild
266+ -> Set PackageName -- ^ registered packages
267+ -> SingleBuild -> IO ()
268+singleBuild pb@PerformBuild {..} registeredPackages SingleBuild {..} =
237269 withCounter sbActive
238270 $ handle updateErrs
239271 $ (`finally` void (atomically $ tryPutTMVar (piResult sbPackageInfo) False))
@@ -245,11 +277,13 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
245277 let wfd comps =
246278 waitForDeps sbToolMap sbPackageMap comps pbPlan sbPackageInfo
247279 . withTSem sbSem
248− wfd libComps buildLibrary
280+ withUnpacked <- wfd libComps buildLibrary
249281
250− wfd testComps runTests
282+ wfd testComps (runTests withUnpacked)
251283
252− name = display $ piName sbPackageInfo
284+ pname = piName sbPackageInfo
285+ pident = PackageIdentifier pname (ppVersion $ piPlan sbPackageInfo)
286+ name = display pname
253287 namever = concat
254288 [ name
255289 , "-"
@@ -290,6 +324,7 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
290324 withBinaryFile (fpToString fp) WriteMode inner'
291325
292326 configArgs = ($ []) $ execWriter $ do
327+ when pbAllowNewer $ tell' "--allow-newer"
293328 tell' "--package-db=clear"
294329 tell' "--package-db=global"
295330 forM_ (pbDatabase pb) $ \db -> tell' $ "--package-db=" ++ fpToText db
@@ -298,6 +333,9 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
298333 tell' $ "--datadir=" ++ fpToText (pbDataDir pb)
299334 tell' $ "--docdir=" ++ fpToText (pbDocDir pb)
300335 tell' $ "--flags=" ++ flags
336+ when (pbEnableLibProfiling && pcEnableLibProfile) $
337+ tell' "--enable-library-profiling"
338+ when pbEnableExecDyn $ tell' "--enable-executable-dynamic"
301339 where
302340 tell' x = tell (x:)
303341
@@ -312,20 +350,39 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
312350 PackageConstraints {..} = ppConstraints $ piPlan sbPackageInfo
313351
314352 buildLibrary = wf libOut $ \outH -> do
315− let run = runChild outH
316− log' $ "Unpacking " ++ namever
317− runParent outH "cabal" ["unpack", namever]
318−
319− log' $ "Configuring " ++ namever
320− run "cabal" $ "configure" : configArgs
321−
322− log' $ "Building " ++ namever
323− run "cabal" ["build"]
324−
325− log' $ "Copying/registering " ++ namever
326− run "cabal" ["copy"]
327− withMVar sbRegisterMutex $ const $
328− run "cabal" ["register"]
353+ let run a b = do when pbVerbose $ log' (unwords (a : b))
354+ runChild outH a b
355+
356+ isUnpacked <- newIORef False
357+ let withUnpacked inner = do
358+ unlessM (readIORef isUnpacked) $ do
359+ log' $ "Unpacking " ++ namever
360+ runParent outH "cabal" ["unpack", namever]
361+ writeIORef isUnpacked True
362+ inner
363+
364+ isConfiged <- newIORef False
365+ let withConfiged inner = withUnpacked $ do
366+ unlessM (readIORef isConfiged) $ do
367+ log' $ "Configuring " ++ namever
368+ run "cabal" $ "configure" : configArgs
369+ writeIORef isConfiged True
370+ inner
371+
372+ prevBuildResult <- getPreviousResult pb Build pident
373+ unless (prevBuildResult == PRSuccess) $ withConfiged $ do
374+ assert (pname `notMember` registeredPackages) $ do
375+ deletePreviousResults pb pident
376+
377+ log' $ "Building " ++ namever
378+ run "cabal" ["build"]
379+
380+ log' $ "Copying/registering " ++ namever
381+ run "cabal" ["copy"]
382+ withMVar sbRegisterMutex $ const $
383+ run "cabal" ["register"]
384+
385+ savePreviousResult pb Build pident True
329386
330387 -- Even if the tests later fail, we can allow other libraries to build
331388 -- on top of our successful results
@@ -335,7 +392,11 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
335392 -- dependency's haddocks before this finishes
336393 atomically $ putTMVar (piResult sbPackageInfo) True
337394
338− when (pcHaddocks /= Don'tBuild && not (null $ sdModules $ ppDesc $ piPlan sbPackageInfo)) $ do
395+ prevHaddockResult <- getPreviousResult pb Haddock pident
396+ let needHaddock = pbEnableHaddock
397+ && checkPrevResult prevHaddockResult pcHaddocks
398+ && not (null $ sdModules $ ppDesc $ piPlan sbPackageInfo)
399+ when needHaddock $ withConfiged $ do
339400 log' $ "Haddocks " ++ namever
340401 hfs <- readTVarIO sbHaddockFiles
341402 let hfsOpts = flip map (mapToList hfs) $ \(pkgVer, hf) -> concat
@@ -370,15 +431,21 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
370431 $ modifyTVar sbHaddockFiles
371432 $ insertMap namever newPath
372433
434+ savePreviousResult pb Haddock pident $ either (const False) (const True) eres
373435 case (eres, pcHaddocks) of
374436 (Left e, ExpectSuccess) -> throwM e
375437 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected Haddock success"
376438 _ -> return ()
377439
378− runTests = wf testOut $ \outH -> do
440+ return withUnpacked
441+
442+ runTests withUnpacked = wf testOut $ \outH -> do
379443 let run = runChild outH
380444
381− when (pcTests /= Don'tBuild) $ do
445+ prevTestResult <- getPreviousResult pb Test pident
446+ let needTest = pbEnableTests
447+ && checkPrevResult prevTestResult pcTests
448+ when needTest $ withUnpacked $ do
382449 log' $ "Test configure " ++ namever
383450 run "cabal" $ "configure" : "--enable-tests" : configArgs
384451
@@ -389,6 +456,7 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
389456 log' $ "Test run " ++ namever
390457 run "cabal" ["test", "--log=" ++ fpToText testRunOut]
391458
459+ savePreviousResult pb Test pident $ either (const False) (const True) eres
392460 case (eres, pcTests) of
393461 (Left e, ExpectSuccess) -> throwM e
394462 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected test success"
@@ -421,3 +489,52 @@ copyBuiltInHaddocks docdir = do
421489 src <- canonicalizePath
422490 (parent (fpFromString ghc) </> "../share/doc/ghc/html/libraries")
423491 copyDir src docdir
492+
493+------------- Previous results
494+
495+-- | The previous actions that can be run
496+data ResultType = Build | Haddock | Test
497+ deriving (Show, Enum, Eq, Ord, Bounded, Read)
498+
499+-- | The result generated on a previous run
500+data PrevResult = PRNoResult | PRSuccess | PRFailure
501+ deriving (Show, Enum, Eq, Ord, Bounded, Read)
502+
503+-- | Check if we should rerun based on a PrevResult and the expected status
504+checkPrevResult :: PrevResult -> TestState -> Bool
505+checkPrevResult _ Don'tBuild = False
506+checkPrevResult PRNoResult _ = True
507+checkPrevResult PRSuccess _ = False
508+checkPrevResult PRFailure ExpectSuccess = True
509+checkPrevResult PRFailure _ = False
510+
511+withPRPath :: PerformBuild -> ResultType -> PackageIdentifier -> (FilePath -> IO a) -> IO a
512+withPRPath pb rt ident inner = do
513+ createTree $ parent fp
514+ inner fp
515+ where
516+ fp = pbPrevResDir pb </> fpFromString (show rt) </> fpFromText (display ident)
517+
518+successBS, failureBS :: ByteString
519+successBS = "success"
520+failureBS = "failure"
521+
522+getPreviousResult :: PerformBuild -> ResultType -> PackageIdentifier -> IO PrevResult
523+getPreviousResult w x y = withPRPath w x y $ \fp -> do
524+ eres <- tryIO $ readFile fp
525+ return $ case eres of
526+ Right bs
527+ | bs == successBS -> PRSuccess
528+ | bs == failureBS -> PRFailure
529+ _ -> PRNoResult
530+
531+savePreviousResult :: PerformBuild -> ResultType -> PackageIdentifier -> Bool -> IO ()
532+savePreviousResult pb rt ident res =
533+ withPRPath pb rt ident $ \fp -> writeFile fp $
534+ if res then successBS else failureBS
535+
536+deletePreviousResults :: PerformBuild -> PackageIdentifier -> IO ()
537+deletePreviousResults pb name =
538+ forM_ [minBound..maxBound] $ \rt ->
539+ withPRPath pb rt name $ \fp ->
540+ void $ tryIO $ removeFile fp