@@ -11,6 +11,7 @@ import Distribution.Client.Cron (cron, rethrowSignalsAsExceptions,
11
11
12
12
import Distribution.Package
13
13
import Distribution.Text
14
+ import qualified Text.PrettyPrint as Disp
14
15
import Distribution.Verbosity
15
16
import Distribution.Simple.Utils hiding (intercalate )
16
17
import Distribution.Version (Version (.. ))
@@ -19,10 +20,12 @@ import Data.List
19
20
import Data.Maybe
20
21
import Data.IORef
21
22
import Data.Time
23
+ import Control.Applicative ((<$>) , (<*>) )
22
24
import Control.Exception
23
25
import Control.Monad
24
26
import Control.Monad.Trans
25
27
import qualified Data.ByteString.Lazy as BS
28
+ import qualified Data.Map as M
26
29
import qualified Data.Set as S
27
30
28
31
import qualified Codec.Compression.GZip as GZip
@@ -55,7 +58,9 @@ data BuildOpts = BuildOpts {
55
58
bo_dryRun :: Bool ,
56
59
bo_prune :: Bool ,
57
60
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
59
64
}
60
65
61
66
data BuildConfig = BuildConfig {
@@ -134,6 +139,23 @@ initialise opts uri auxUris
134
139
where
135
140
readMissingOpt prompt = maybe (putStrLn prompt >> getLine ) return
136
141
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
+
137
159
writeConfig :: BuildOpts -> BuildConfig -> IO ()
138
160
writeConfig opts BuildConfig {
139
161
bc_srcURI = uri,
@@ -388,6 +410,11 @@ getDocumentationStats verbosity config didFail = do
388
410
buildOnce :: BuildOpts -> [PackageId ] -> IO ()
389
411
buildOnce opts pkgs = keepGoing $ do
390
412
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
391
418
392
419
notice verbosity " Initialising"
393
420
(has_failed, mark_as_failed, persist_failed) <- mkPackageFailed opts
@@ -406,6 +433,7 @@ buildOnce opts pkgs = keepGoing $ do
406
433
-- Find those files *not* marked as having documentation in our cache
407
434
let toBuild :: [DocInfo ]
408
435
toBuild = filter shouldBuild
436
+ . filter (flip S. member repoIndex . docInfoPackage)
409
437
. latestFirst
410
438
. map (sortBy (flip (comparing docInfoPackageVersion)))
411
439
. groupBy (equating docInfoPackageName)
@@ -486,32 +514,44 @@ buildOnce opts pkgs = keepGoing $ do
486
514
unless (update_ec == ExitSuccess ) $
487
515
die " Could not 'cabal update' from specified server"
488
516
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.
492
521
mkPackageFailed :: BuildOpts
493
522
-> IO (PackageId -> IO Bool , PackageId -> IO () , IO () )
494
523
mkPackageFailed opts = do
495
524
init_failed <- readFailedCache (bo_stateDir opts)
496
525
cache_var <- newIORef init_failed
497
526
498
527
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
501
531
persist = readIORef cache_var >>= writeFailedCache (bo_stateDir opts)
502
532
503
533
return (has_failed, mark_as_failed, persist)
504
534
where
505
- readFailedCache :: FilePath -> IO (S. Set PackageId )
535
+ readFailedCache :: FilePath -> IO (M. Map PackageId Int )
506
536
readFailedCache cache_dir = do
507
537
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
509
546
Left theError -> die theError
510
- Right pkgs -> return (S . fromList pkgs)
547
+ Right pkgs -> return (M . fromList $ zip pkgs attempts )
511
548
512
- writeFailedCache :: FilePath -> S. Set PackageId -> IO ()
549
+ writeFailedCache :: FilePath -> M. Map PackageId Int -> IO ()
513
550
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
515
555
516
556
517
557
-- | Build documentation and return @(Just tgz)@ for the built tgz file
@@ -778,7 +818,8 @@ data BuildFlags = BuildFlags {
778
818
flagInterval :: Maybe String ,
779
819
flagPrune :: Bool ,
780
820
flagUsername :: Maybe String ,
781
- flagPassword :: Maybe String
821
+ flagPassword :: Maybe String ,
822
+ flagBuildAttempts :: Maybe Int
782
823
}
783
824
784
825
emptyBuildFlags :: BuildFlags
@@ -795,6 +836,7 @@ emptyBuildFlags = BuildFlags {
795
836
, flagPrune = False
796
837
, flagUsername = Nothing
797
838
, flagPassword = Nothing
839
+ , flagBuildAttempts = Nothing
798
840
}
799
841
800
842
buildFlagDescrs :: [OptDescr (BuildFlags -> BuildFlags )]
@@ -848,6 +890,12 @@ buildFlagDescrs =
848
890
, Option [] [" init-password" ]
849
891
(ReqArg (\ passwd opts -> opts { flagPassword = Just passwd }) " PASSWORD" )
850
892
" 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"
851
899
]
852
900
853
901
validateOpts :: [String ] -> IO (Mode , BuildOpts )
@@ -869,7 +917,8 @@ validateOpts args = do
869
917
bo_dryRun = flagDryRun flags,
870
918
bo_prune = flagPrune flags,
871
919
bo_username = flagUsername flags,
872
- bo_password = flagPassword flags
920
+ bo_password = flagPassword flags,
921
+ bo_buildAttempts = fromMaybe 10 $ flagBuildAttempts flags
873
922
}
874
923
875
924
mode = case args' of
0 commit comments