Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 62 additions & 13 deletions BuildClient.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Distribution.Client.Cron (cron, rethrowSignalsAsExceptions,

import Distribution.Package
import Distribution.Text
import qualified Text.PrettyPrint as Disp
import Distribution.Verbosity
import Distribution.Simple.Utils hiding (intercalate)
import Distribution.Version (Version(..))
Expand All @@ -19,10 +20,12 @@ import Data.List
import Data.Maybe
import Data.IORef
import Data.Time
import Control.Applicative ((<$>), (<*>))
import Control.Exception
import Control.Monad
import Control.Monad.Trans
import qualified Data.ByteString.Lazy as BS
import qualified Data.Map as M
import qualified Data.Set as S

import qualified Codec.Compression.GZip as GZip
Expand Down Expand Up @@ -55,7 +58,9 @@ data BuildOpts = BuildOpts {
bo_dryRun :: Bool,
bo_prune :: Bool,
bo_username :: Maybe String,
bo_password :: Maybe String
bo_password :: Maybe String,
bo_buildAttempts :: Int
-- ^ how many times to attempt to rebuild a failing package
}

data BuildConfig = BuildConfig {
Expand Down Expand Up @@ -134,6 +139,23 @@ initialise opts uri auxUris
where
readMissingOpt prompt = maybe (putStrLn prompt >> getLine) return


-- | Parse the @00-index.cache@ file of the available package repositories.
parseRepositoryIndices :: IO (S.Set PackageIdentifier)
parseRepositoryIndices = do

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this will likely break for secure repos as the filename and on-disk format for 01-index.* changed

In future cabal versions we should extend or add a cabal list variant that dumps the required information (including additional meta-data such as revision number and/or last-revision-timestamp, and maybe also repository provenance) so that we don't need to understand the .cache format

cabalDir <- getAppUserDataDirectory "cabal/packages"
cacheDirs <- listDirectory cabalDir
indexFiles <- filterM doesFileExist $ map (\dir -> cabalDir </> dir </> "00-index.cache") cacheDirs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This format is actually private to cabal, and does change (it's already changed in cabal-install-1.25). On the other hand the 00-index.tar or the 01-index.tar is a stable format. You can use the code from the mirror client (in the same repo) for this.

@hvr hvr Oct 23, 2016

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otoh, the on-disk format for 00-index.cache is supposed to remain backward compatible, otherwise different cabal clients would step on each other's toe ... For 01-index.cache it's a different story though :-)

S.unions <$> mapM readCache indexFiles
where
readCache fname =
S.fromList . mapMaybe parseLine . lines <$> readFile fname
parseLine line
| "pkg:" : name : ver : _ <- words line
= PackageIdentifier <$> simpleParse name <*> simpleParse ver
| otherwise
= Nothing

writeConfig :: BuildOpts -> BuildConfig -> IO ()
writeConfig opts BuildConfig {
bc_srcURI = uri,
Expand Down Expand Up @@ -388,6 +410,11 @@ getDocumentationStats verbosity config didFail = do
buildOnce :: BuildOpts -> [PackageId] -> IO ()
buildOnce opts pkgs = keepGoing $ do
config <- readConfig opts
-- Due to caching sometimes the package repository state may lag behind the
-- documentation index. Consequently, we make sure that the packages we are
-- going to build actually appear in the repository before building. See
-- #543.
repoIndex <- parseRepositoryIndices

notice verbosity "Initialising"
(has_failed, mark_as_failed, persist_failed) <- mkPackageFailed opts
Expand All @@ -406,6 +433,7 @@ buildOnce opts pkgs = keepGoing $ do
-- Find those files *not* marked as having documentation in our cache
let toBuild :: [DocInfo]
toBuild = filter shouldBuild
. filter (flip S.member repoIndex . docInfoPackage)
. latestFirst
. map (sortBy (flip (comparing docInfoPackageVersion)))
. groupBy (equating docInfoPackageName)
Expand Down Expand Up @@ -486,32 +514,44 @@ buildOnce opts pkgs = keepGoing $ do
unless (update_ec == ExitSuccess) $
die "Could not 'cabal update' from specified server"


-- Builds a little memoised function that can tell us whether a
-- particular package failed to build its documentation
-- | Builds a little memoised function that can tell us whether a
-- particular package failed to build its documentation, a function to mark a
-- package as having failed, and a function to write the final failed list back
-- to disk.
mkPackageFailed :: BuildOpts
-> IO (PackageId -> IO Bool, PackageId -> IO (), IO ())
mkPackageFailed opts = do
init_failed <- readFailedCache (bo_stateDir opts)
cache_var <- newIORef init_failed

let mark_as_failed pkg_id = atomicModifyIORef cache_var $ \already_failed ->
(S.insert pkg_id already_failed, ())
has_failed pkg_id = liftM (pkg_id `S.member`) $ readIORef cache_var
(M.insertWith (+) pkg_id 1 already_failed, ())
has_failed pkg_id = f <$> readIORef cache_var
where f cache = M.findWithDefault 0 pkg_id cache > bo_buildAttempts opts
persist = readIORef cache_var >>= writeFailedCache (bo_stateDir opts)

return (has_failed, mark_as_failed, persist)
where
readFailedCache :: FilePath -> IO (S.Set PackageId)
readFailedCache :: FilePath -> IO (M.Map PackageId Int)
readFailedCache cache_dir = do
pkgstrs <- handleDoesNotExist [] $ liftM lines $ readFile (cache_dir </> "failed")
case validatePackageIds pkgstrs of
let (pkgids, attempts) = unzip $ map (parseLine . words) pkgstrs
where
parseLine [pkg_id] = (pkg_id, 1)
parseLine [pkg_id, attempts']
| [(n,_)] <- reads attempts' = (pkg_id, n)
| otherwise = (pkg_id, 1)
parseLine other = error $ "failed to parse failed list line: "++show other
case validatePackageIds pkgids of
Left theError -> die theError
Right pkgs -> return (S.fromList pkgs)
Right pkgs -> return (M.fromList $ zip pkgs attempts)

writeFailedCache :: FilePath -> S.Set PackageId -> IO ()
writeFailedCache :: FilePath -> M.Map PackageId Int -> IO ()
writeFailedCache cache_dir pkgs =
writeFile (cache_dir </> "failed") $ unlines $ map display $ S.toList pkgs
writeFile (cache_dir </> "failed")
$ unlines
$ map (\(pkgid,n) -> show $ disp pkgid Disp.<+> disp n)
$ M.assocs pkgs


-- | Build documentation and return @(Just tgz)@ for the built tgz file
Expand Down Expand Up @@ -778,7 +818,8 @@ data BuildFlags = BuildFlags {
flagInterval :: Maybe String,
flagPrune :: Bool,
flagUsername :: Maybe String,
flagPassword :: Maybe String
flagPassword :: Maybe String,
flagBuildAttempts :: Maybe Int
}

emptyBuildFlags :: BuildFlags
Expand All @@ -795,6 +836,7 @@ emptyBuildFlags = BuildFlags {
, flagPrune = False
, flagUsername = Nothing
, flagPassword = Nothing
, flagBuildAttempts = Nothing
}

buildFlagDescrs :: [OptDescr (BuildFlags -> BuildFlags)]
Expand Down Expand Up @@ -848,6 +890,12 @@ buildFlagDescrs =
, Option [] ["init-password"]
(ReqArg (\passwd opts -> opts { flagPassword = Just passwd }) "PASSWORD")
"The password of the Hackage user to run the build as (used with init)"

, Option [] ["build-attempts"]
(ReqArg (\attempts opts -> case reads attempts of
[(attempts', "")] -> opts { flagBuildAttempts = Just attempts' }
_ -> error "Can't parse attempt count") "ATTEMPTS")
"How many times to attempt to build a package before giving up"
]

validateOpts :: [String] -> IO (Mode, BuildOpts)
Expand All @@ -869,7 +917,8 @@ validateOpts args = do
bo_dryRun = flagDryRun flags,
bo_prune = flagPrune flags,
bo_username = flagUsername flags,
bo_password = flagPassword flags
bo_password = flagPassword flags,
bo_buildAttempts = fromMaybe 10 $ flagBuildAttempts flags

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems a little high as a default, no?

}

mode = case args' of
Expand Down
2 changes: 1 addition & 1 deletion hackage-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ executable hackage-build
build-depends:
base,
containers, array, vector, bytestring, text, pretty,
filepath, directory, process >= 1.0,
filepath, directory >= 1.2.5, process >= 1.0,
time,
time-locale-compat >= 0.1.0.1,
tar, zlib,
Expand Down