@@ -11,6 +11,7 @@ import Distribution.Client.Cron (cron, rethrowSignalsAsExceptions,
1111
1212import Distribution.Package
1313import Distribution.Text
14+ import qualified Text.PrettyPrint as Disp
1415import Distribution.Verbosity
1516import Distribution.Simple.Utils hiding (intercalate )
1617import Distribution.Version (Version (.. ))
@@ -19,10 +20,12 @@ import Data.List
1920import Data.Maybe
2021import Data.IORef
2122import Data.Time
23+ import Control.Applicative ((<$>) , (<*>) )
2224import Control.Exception
2325import Control.Monad
2426import Control.Monad.Trans
2527import qualified Data.ByteString.Lazy as BS
28+ import qualified Data.Map as M
2629import qualified Data.Set as S
2730
2831import qualified Codec.Compression.GZip as GZip
@@ -55,7 +58,9 @@ data BuildOpts = BuildOpts {
5558 bo_dryRun :: Bool ,
5659 bo_prune :: Bool ,
5760 bo_username :: Maybe String ,
58- bo_password :: Maybe String
61+ bo_password :: Maybe String ,
62+ bo_buildAttempts :: Int
63+ -- ^ how many times to attempt to rebuild a failing package
5964 }
6065
6166data BuildConfig = BuildConfig {
@@ -134,6 +139,23 @@ initialise opts uri auxUris
134139 where
135140 readMissingOpt prompt = maybe (putStrLn prompt >> getLine ) return
136141
142+
143+ -- | Parse the @00-index.cache@ file of the available package repositories.
144+ parseRepositoryIndices :: IO (S. Set PackageIdentifier )
145+ parseRepositoryIndices = do
146+ cabalDir <- getAppUserDataDirectory " cabal/packages"
147+ cacheDirs <- listDirectory cabalDir
148+ indexFiles <- filterM doesFileExist $ map (\ dir -> cabalDir </> dir </> " 00-index.cache" ) cacheDirs
149+ S. unions <$> mapM readCache indexFiles
150+ where
151+ readCache fname =
152+ S. fromList . mapMaybe parseLine . lines <$> readFile fname
153+ parseLine line
154+ | " pkg:" : name : ver : _ <- words line
155+ = PackageIdentifier <$> simpleParse name <*> simpleParse ver
156+ | otherwise
157+ = Nothing
158+
137159writeConfig :: BuildOpts -> BuildConfig -> IO ()
138160writeConfig opts BuildConfig {
139161 bc_srcURI = uri,
@@ -388,6 +410,11 @@ getDocumentationStats verbosity config didFail = do
388410buildOnce :: BuildOpts -> [PackageId ] -> IO ()
389411buildOnce opts pkgs = keepGoing $ do
390412 config <- readConfig opts
413+ -- Due to caching sometimes the package repository state may lag behind the
414+ -- documentation index. Consequently, we make sure that the packages we are
415+ -- going to build actually appear in the repository before building. See
416+ -- #543.
417+ repoIndex <- parseRepositoryIndices
391418
392419 notice verbosity " Initialising"
393420 (has_failed, mark_as_failed, persist_failed) <- mkPackageFailed opts
@@ -406,6 +433,7 @@ buildOnce opts pkgs = keepGoing $ do
406433 -- Find those files *not* marked as having documentation in our cache
407434 let toBuild :: [DocInfo ]
408435 toBuild = filter shouldBuild
436+ . filter (flip S. member repoIndex . docInfoPackage)
409437 . latestFirst
410438 . map (sortBy (flip (comparing docInfoPackageVersion)))
411439 . groupBy (equating docInfoPackageName)
@@ -486,32 +514,44 @@ buildOnce opts pkgs = keepGoing $ do
486514 unless (update_ec == ExitSuccess ) $
487515 die " Could not 'cabal update' from specified server"
488516
489-
490- -- Builds a little memoised function that can tell us whether a
491- -- particular package failed to build its documentation
517+ -- | Builds a little memoised function that can tell us whether a
518+ -- particular package failed to build its documentation, a function to mark a
519+ -- package as having failed, and a function to write the final failed list back
520+ -- to disk.
492521mkPackageFailed :: BuildOpts
493522 -> IO (PackageId -> IO Bool , PackageId -> IO () , IO () )
494523mkPackageFailed opts = do
495524 init_failed <- readFailedCache (bo_stateDir opts)
496525 cache_var <- newIORef init_failed
497526
498527 let mark_as_failed pkg_id = atomicModifyIORef cache_var $ \ already_failed ->
499- (S. insert pkg_id already_failed, () )
500- has_failed pkg_id = liftM (pkg_id `S.member` ) $ readIORef cache_var
528+ (M. insertWith (+) pkg_id 1 already_failed, () )
529+ has_failed pkg_id = f <$> readIORef cache_var
530+ where f cache = M. findWithDefault 0 pkg_id cache > bo_buildAttempts opts
501531 persist = readIORef cache_var >>= writeFailedCache (bo_stateDir opts)
502532
503533 return (has_failed, mark_as_failed, persist)
504534 where
505- readFailedCache :: FilePath -> IO (S. Set PackageId )
535+ readFailedCache :: FilePath -> IO (M. Map PackageId Int )
506536 readFailedCache cache_dir = do
507537 pkgstrs <- handleDoesNotExist [] $ liftM lines $ readFile (cache_dir </> " failed" )
508- case validatePackageIds pkgstrs of
538+ let (pkgids, attempts) = unzip $ map (parseLine . words ) pkgstrs
539+ where
540+ parseLine [pkg_id] = (pkg_id, 1 )
541+ parseLine [pkg_id, attempts']
542+ | [(n,_)] <- reads attempts' = (pkg_id, n)
543+ | otherwise = (pkg_id, 1 )
544+ parseLine other = error $ " failed to parse failed list line: " ++ show other
545+ case validatePackageIds pkgids of
509546 Left theError -> die theError
510- Right pkgs -> return (S . fromList pkgs)
547+ Right pkgs -> return (M . fromList $ zip pkgs attempts )
511548
512- writeFailedCache :: FilePath -> S. Set PackageId -> IO ()
549+ writeFailedCache :: FilePath -> M. Map PackageId Int -> IO ()
513550 writeFailedCache cache_dir pkgs =
514- writeFile (cache_dir </> " failed" ) $ unlines $ map display $ S. toList pkgs
551+ writeFile (cache_dir </> " failed" )
552+ $ unlines
553+ $ map (\ (pkgid,n) -> show $ disp pkgid Disp. <+> disp n)
554+ $ M. assocs pkgs
515555
516556
517557-- | Build documentation and return @(Just tgz)@ for the built tgz file
@@ -778,7 +818,8 @@ data BuildFlags = BuildFlags {
778818 flagInterval :: Maybe String ,
779819 flagPrune :: Bool ,
780820 flagUsername :: Maybe String ,
781- flagPassword :: Maybe String
821+ flagPassword :: Maybe String ,
822+ flagBuildAttempts :: Maybe Int
782823}
783824
784825emptyBuildFlags :: BuildFlags
@@ -795,6 +836,7 @@ emptyBuildFlags = BuildFlags {
795836 , flagPrune = False
796837 , flagUsername = Nothing
797838 , flagPassword = Nothing
839+ , flagBuildAttempts = Nothing
798840 }
799841
800842buildFlagDescrs :: [OptDescr (BuildFlags -> BuildFlags )]
@@ -848,6 +890,12 @@ buildFlagDescrs =
848890 , Option [] [" init-password" ]
849891 (ReqArg (\ passwd opts -> opts { flagPassword = Just passwd }) " PASSWORD" )
850892 " The password of the Hackage user to run the build as (used with init)"
893+
894+ , Option [] [" build-attempts" ]
895+ (ReqArg (\ attempts opts -> case reads attempts of
896+ [(attempts', " " )] -> opts { flagBuildAttempts = Just attempts' }
897+ _ -> error " Can't parse attempt count" ) " ATTEMPTS" )
898+ " How many times to attempt to build a package before giving up"
851899 ]
852900
853901validateOpts :: [String ] -> IO (Mode , BuildOpts )
@@ -869,7 +917,8 @@ validateOpts args = do
869917 bo_dryRun = flagDryRun flags,
870918 bo_prune = flagPrune flags,
871919 bo_username = flagUsername flags,
872- bo_password = flagPassword flags
920+ bo_password = flagPassword flags,
921+ bo_buildAttempts = fromMaybe 10 $ flagBuildAttempts flags
873922 }
874923
875924 mode = case args' of
0 commit comments