@@ -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)
@@ -61,6 +62,13 @@ data PerformBuild = PerformBuild
61 , pbJobs :: Int
62 , pbJobs :: Int
62 , pbGlobalInstall :: Bool
63 , pbGlobalInstall :: Bool
63 -- ^ Register packages in the global database
64 -- ^ Register packages in the global database
65+ , pbEnableTests :: Bool
66+ , pbEnableHaddock :: Bool
67+ , pbEnableLibProfiling :: Bool
68+ , pbEnableExecDyn :: Bool
70+ , pbAllowNewer :: Bool
71+ -- ^ Pass --allow-newer to cabal configure
66data PackageInfo = PackageInfo
74data PackageInfo = PackageInfo
@@ -83,7 +91,9 @@ waitForDeps toolMap packageMap activeComps bp pi action = do
83 case lookup exe toolMap >>= fromNullable . map checkPackage . setToList of
91 case lookup exe toolMap >>= fromNullable . map checkPackage . setToList of
85 | isCoreExe exe -> return ()
93 | isCoreExe exe -> return ()
94+ -- https://github.com/jgm/zip-archive/issues/23
86− | otherwise -> throwSTM $ ToolMissing exe
95+ -- | otherwise -> throwSTM $ ToolMissing exe
96+ | otherwise -> return ()
87 Just packages -> ofoldl1' (<|>) packages
97 Just packages -> ofoldl1' (<|>) packages
@@ -126,6 +136,10 @@ pbLibDir pb = pbInstallDest pb </> "lib"
126pbDataDir pb = pbInstallDest pb </> "share"
136pbDataDir pb = pbInstallDest pb </> "share"
127pbDocDir 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"
129performBuild :: PerformBuild -> IO [Text]
143performBuild :: PerformBuild -> IO [Text]
130performBuild pb = do
144performBuild pb = do
131 cwd <- getWorkingDirectory
145 cwd <- getWorkingDirectory
@@ -153,12 +167,13 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
153 $ \ClosedStream Inherited Inherited -> return ()
167 $ \ClosedStream Inherited Inherited -> return ()
155 let removeTree' fp = whenM (isDirectory fp) (removeTree fp)
169 let removeTree' fp = whenM (isDirectory fp) (removeTree fp)
156− mapM_ removeTree' [pbInstallDest, pbLogDir]
170+ removeTree' pbLogDir
158− forM_ (pbDatabase pb) $ \db -> do
172+ forM_ (pbDatabase pb) $ \db ->
173+ unlessM (isFile $ db </> "package.cache") $ do
159− createTree $ parent db
174+ createTree $ parent db
160− withCheckedProcess (proc "ghc-pkg" ["init", fpToString db])
175+ withCheckedProcess (proc "ghc-pkg" ["init", fpToString db])
161− $ \ClosedStream Inherited Inherited -> return ()
176+ $ \ClosedStream Inherited Inherited -> return ()
162 pbLog $ encodeUtf8 "Copying built-in Haddocks\n"
177 pbLog $ encodeUtf8 "Copying built-in Haddocks\n"
163 copyBuiltInHaddocks (pbDocDir pb)
178 copyBuiltInHaddocks (pbDocDir pb)
@@ -178,7 +193,14 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
178 env <- getEnvironment
193 env <- getEnvironment
179 haddockFiles <- newTVarIO mempty
194 haddockFiles <- newTVarIO mempty
196+ registeredPackages <- setupPackageDatabase
200+ (ppVersion <$> bpPackages pbPlan)
181− forM_ packageMap $ \pi -> void $ async $ singleBuild pb SingleBuild
202+ forM_ packageMap $ \pi -> void $ async $ singleBuild pb registeredPackages
182 { sbSem = sem
204 { sbSem = sem
183 , sbErrsVar = errsVar
205 , sbErrsVar = errsVar
184 , sbWarningsVar = warningsVar
206 , sbWarningsVar = warningsVar
@@ -192,7 +214,7 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
193 (\db -> (("HASKELL_PACKAGE_SANDBOX", fpToString db):))
215 (\db -> (("HASKELL_PACKAGE_SANDBOX", fpToString db):))
194 (pbDatabase pb)
216 (pbDatabase pb)
195− (map fixEnv env)
217+ (filter allowedEnv $ map fixEnv env)
196 , sbHaddockFiles = haddockFiles
218 , sbHaddockFiles = haddockFiles
@@ -210,6 +232,8 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
210 | toUpper p == "PATH" = (p, fpToString (pbBinDir pb) ++ pathSep : x)
232 | toUpper p == "PATH" = (p, fpToString (pbBinDir pb) ++ pathSep : x)
211 | otherwise = (p, x)
233 | otherwise = (p, x)
235+ allowedEnv (k, _) = k `notMember` bannedEnvs
213 -- | Separate for the PATH environment variable
237 -- | Separate for the PATH environment variable
214 pathSep :: Char
238 pathSep :: Char
215#ifdef mingw32_HOST_OS
239#ifdef mingw32_HOST_OS
@@ -218,6 +242,12 @@ performBuild' pb@PerformBuild {..} = withBuildDir $ \builddir -> do
218 pathSep = ':'
242 pathSep = ':'
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"
221data SingleBuild = SingleBuild
251data SingleBuild = SingleBuild
222 { sbSem :: TSem
252 { sbSem :: TSem
223 , sbErrsVar :: TVar (Map PackageName BuildFailure)
253 , sbErrsVar :: TVar (Map PackageName BuildFailure)
@@ -232,8 +262,10 @@ data SingleBuild = SingleBuild
232 , sbHaddockFiles :: TVar (Map Text FilePath) -- ^ package-version, .haddock file
262 , sbHaddockFiles :: TVar (Map Text FilePath) -- ^ package-version, .haddock file
235−singleBuild :: PerformBuild -> SingleBuild -> IO ()
265+singleBuild :: PerformBuild
266+ -> Set PackageName -- ^ registered packages
267+ -> SingleBuild -> IO ()
236−singleBuild pb@PerformBuild {..} SingleBuild {..} =
268+singleBuild pb@PerformBuild {..} registeredPackages SingleBuild {..} =
237 withCounter sbActive
269 withCounter sbActive
238 $ handle updateErrs
270 $ handle updateErrs
239 $ (`finally` void (atomically $ tryPutTMVar (piResult sbPackageInfo) False))
271 $ (`finally` void (atomically $ tryPutTMVar (piResult sbPackageInfo) False))
@@ -245,11 +277,13 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
245 let wfd comps =
277 let wfd comps =
246 waitForDeps sbToolMap sbPackageMap comps pbPlan sbPackageInfo
278 waitForDeps sbToolMap sbPackageMap comps pbPlan sbPackageInfo
247 . withTSem sbSem
279 . withTSem sbSem
248− wfd libComps buildLibrary
280+ withUnpacked <- wfd libComps buildLibrary
250− wfd testComps runTests
282+ wfd testComps (runTests withUnpacked)
284+ pname = piName sbPackageInfo
285+ pident = PackageIdentifier pname (ppVersion $ piPlan sbPackageInfo)
252− name = display $ piName sbPackageInfo
286+ name = display pname
253 namever = concat
287 namever = concat
@@ -290,6 +324,7 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
290 withBinaryFile (fpToString fp) WriteMode inner'
324 withBinaryFile (fpToString fp) WriteMode inner'
292 configArgs = ($ []) $ execWriter $ do
326 configArgs = ($ []) $ execWriter $ do
327+ when pbAllowNewer $ tell' "--allow-newer"
293 tell' "--package-db=clear"
328 tell' "--package-db=clear"
294 tell' "--package-db=global"
329 tell' "--package-db=global"
295 forM_ (pbDatabase pb) $ \db -> tell' $ "--package-db=" ++ fpToText db
330 forM_ (pbDatabase pb) $ \db -> tell' $ "--package-db=" ++ fpToText db
@@ -298,6 +333,9 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
298 tell' $ "--datadir=" ++ fpToText (pbDataDir pb)
333 tell' $ "--datadir=" ++ fpToText (pbDataDir pb)
299 tell' $ "--docdir=" ++ fpToText (pbDocDir pb)
334 tell' $ "--docdir=" ++ fpToText (pbDocDir pb)
300 tell' $ "--flags=" ++ flags
335 tell' $ "--flags=" ++ flags
336+ when (pbEnableLibProfiling && pcEnableLibProfile) $
337+ tell' "--enable-library-profiling"
338+ when pbEnableExecDyn $ tell' "--enable-executable-dynamic"
302 tell' x = tell (x:)
340 tell' x = tell (x:)
@@ -312,20 +350,39 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
312 PackageConstraints {..} = ppConstraints $ piPlan sbPackageInfo
350 PackageConstraints {..} = ppConstraints $ piPlan sbPackageInfo
314 buildLibrary = wf libOut $ \outH -> do
352 buildLibrary = wf libOut $ \outH -> do
353+ let run a b = do when pbVerbose $ log' (unwords (a : b))
315− let run = runChild outH
354+ runChild outH a b
356+ isUnpacked <- newIORef False
357+ let withUnpacked inner = do
358+ unlessM (readIORef isUnpacked) $ do
316− log' $ "Unpacking " ++ namever
359+ log' $ "Unpacking " ++ namever
317− runParent outH "cabal" ["unpack", namever]
360+ runParent outH "cabal" ["unpack", namever]
318−
361+ writeIORef isUnpacked True
364+ isConfiged <- newIORef False
365+ let withConfiged inner = withUnpacked $ do
366+ unlessM (readIORef isConfiged) $ do
319− log' $ "Configuring " ++ namever
367+ log' $ "Configuring " ++ namever
320− run "cabal" $ "configure" : configArgs
368+ run "cabal" $ "configure" : configArgs
321−
369+ writeIORef isConfiged True
372+ prevBuildResult <- getPreviousResult pb Build pident
373+ unless (prevBuildResult == PRSuccess) $ withConfiged $ do
374+ assert (pname `notMember` registeredPackages) $ do
375+ deletePreviousResults pb pident
322− log' $ "Building " ++ namever
377+ log' $ "Building " ++ namever
323− run "cabal" ["build"]
378+ run "cabal" ["build"]
325− log' $ "Copying/registering " ++ namever
380+ log' $ "Copying/registering " ++ namever
326− run "cabal" ["copy"]
381+ run "cabal" ["copy"]
327− withMVar sbRegisterMutex $ const $
382+ withMVar sbRegisterMutex $ const $
328− run "cabal" ["register"]
383+ run "cabal" ["register"]
385+ savePreviousResult pb Build pident True
330 -- Even if the tests later fail, we can allow other libraries to build
387 -- Even if the tests later fail, we can allow other libraries to build
331 -- on top of our successful results
388 -- on top of our successful results
@@ -335,7 +392,11 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
335 -- dependency's haddocks before this finishes
392 -- dependency's haddocks before this finishes
336 atomically $ putTMVar (piResult sbPackageInfo) True
393 atomically $ putTMVar (piResult sbPackageInfo) True
395+ prevHaddockResult <- getPreviousResult pb Haddock pident
396+ let needHaddock = pbEnableHaddock
397+ && checkPrevResult prevHaddockResult pcHaddocks
338− when (pcHaddocks /= Don'tBuild && not (null $ sdModules $ ppDesc $ piPlan sbPackageInfo)) $ do
398+ && not (null $ sdModules $ ppDesc $ piPlan sbPackageInfo)
399+ when needHaddock $ withConfiged $ do
339 log' $ "Haddocks " ++ namever
400 log' $ "Haddocks " ++ namever
340 hfs <- readTVarIO sbHaddockFiles
401 hfs <- readTVarIO sbHaddockFiles
341 let hfsOpts = flip map (mapToList hfs) $ \(pkgVer, hf) -> concat
402 let hfsOpts = flip map (mapToList hfs) $ \(pkgVer, hf) -> concat
@@ -370,15 +431,21 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
370 $ modifyTVar sbHaddockFiles
431 $ modifyTVar sbHaddockFiles
371 $ insertMap namever newPath
432 $ insertMap namever newPath
434+ savePreviousResult pb Haddock pident $ either (const False) (const True) eres
373 case (eres, pcHaddocks) of
435 case (eres, pcHaddocks) of
374 (Left e, ExpectSuccess) -> throwM e
436 (Left e, ExpectSuccess) -> throwM e
375 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected Haddock success"
437 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected Haddock success"
376 _ -> return ()
438 _ -> return ()
378− runTests = wf testOut $ \outH -> do
442+ runTests withUnpacked = wf testOut $ \outH -> do
379 let run = runChild outH
443 let run = runChild outH
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
382 log' $ "Test configure " ++ namever
449 log' $ "Test configure " ++ namever
383 run "cabal" $ "configure" : "--enable-tests" : configArgs
450 run "cabal" $ "configure" : "--enable-tests" : configArgs
@@ -389,6 +456,7 @@ singleBuild pb@PerformBuild {..} SingleBuild {..} =
389 log' $ "Test run " ++ namever
456 log' $ "Test run " ++ namever
390 run "cabal" ["test", "--log=" ++ fpToText testRunOut]
457 run "cabal" ["test", "--log=" ++ fpToText testRunOut]
459+ savePreviousResult pb Test pident $ either (const False) (const True) eres
392 case (eres, pcTests) of
460 case (eres, pcTests) of
393 (Left e, ExpectSuccess) -> throwM e
461 (Left e, ExpectSuccess) -> throwM e
394 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected test success"
462 (Right (), ExpectFailure) -> warn $ namever ++ ": unexpected test success"
@@ -421,3 +489,52 @@ copyBuiltInHaddocks docdir = do
421 src <- canonicalizePath
489 src <- canonicalizePath
422 (parent (fpFromString ghc) </> "../share/doc/ghc/html/libraries")
490 (parent (fpFromString ghc) </> "../share/doc/ghc/html/libraries")
423 copyDir src docdir
491 copyDir src docdir
493+------------- Previous results
495+-- | The previous actions that can be run
496+data ResultType = Build | Haddock | Test
497+ deriving (Show, Enum, Eq, Ord, Bounded, Read)
499+-- | The result generated on a previous run
500+data PrevResult = PRNoResult | PRSuccess | PRFailure
501+ deriving (Show, Enum, Eq, Ord, Bounded, Read)
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
511+withPRPath :: PerformBuild -> ResultType -> PackageIdentifier -> (FilePath -> IO a) -> IO a
512+withPRPath pb rt ident inner = do
513+ createTree $ parent fp
516+ fp = pbPrevResDir pb </> fpFromString (show rt) </> fpFromText (display ident)
518+successBS, failureBS :: ByteString
519+successBS = "success"
520+failureBS = "failure"
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
527+ | bs == successBS -> PRSuccess
528+ | bs == failureBS -> PRFailure
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
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