module-management-0.20.2: testdata/debian/Debian/Repo/State/PackageIndex.hs
{-# LANGUAGE FlexibleInstances, OverloadedStrings, PackageImports, ScopedTypeVariables, TemplateHaskell #-}
{-# OPTIONS -Wall -fno-warn-orphans #-}
module Debian.Repo.State.PackageIndex
( binaryPackagesFromSources
, sourcePackagesFromSources
) where
import Control.Monad.Trans (MonadIO(liftIO))
import Data.List (intercalate)
import Data.List as List (map, partition)
import Data.Maybe (catMaybes)
import qualified Data.Text as T (Text, unpack)
import Debian.Arch (Arch, Arch(..), prettyArch)
import Debian.Control (ControlFunctions(stripWS), formatParagraph)
import qualified Debian.Control.Text as B (Control'(Control), ControlFunctions(lookupP), ControlFunctions(parseControlFromHandle), Field, Field'(Field), fieldValue, Paragraph)
import Debian.Pretty (ppDisplay)
import qualified Debian.Relation.Text as B (ParseRelations(..), Relations)
import Debian.Release (ReleaseName(..), releaseName', sectionName')
import Debian.Repo.EnvPath (EnvRoot(rootPath))
import Debian.Repo.Internal.Repos (MonadRepos)
import Debian.Repo.PackageID (makeBinaryPackageID, makeSourcePackageID)
import Debian.Repo.PackageIndex (BinaryPackage, BinaryPackage(..), PackageIndex(..), PackageIndex(packageIndexArch, packageIndexComponent), packageIndexPath, SourceControl(..), SourceFileSpec(SourceFileSpec), SourcePackage(..), SourcePackage(sourcePackageID))
--import Debian.Repo.Prelude (symbol)
import Debian.Repo.Release (Release(releaseName))
import Debian.Repo.Repo (Repo(repoKey, repoReleaseInfo), RepoKey, repoKeyURI)
import Debian.Repo.Slice (binarySlices, Slice(sliceRepoKey, sliceSource), SliceList(slices), sourceSlices)
import Debian.Repo.State.Repository (foldRepository)
import Debian.Sources (DebSource(sourceDist, sourceType), SourceType(Deb, DebSrc))
import Debian.URI (URI(uriScheme), uriToString')
import Debian.Version (parseDebianVersion)
import Network.URI (escapeURIString, URI(uriAuthority, uriPath), URIAuth(uriPort, uriRegName, uriUserInfo))
import qualified System.IO as IO (hClose, IOMode(ReadMode), openBinaryFile)
--import System.IO.Unsafe (unsafeInterleaveIO)
--import System.Process.Progress (qBracket, quieter)
-- |Return a list of the index files that contain the packages of a
-- slice.
sliceIndexes :: MonadRepos m => Arch -> Slice -> m [(RepoKey, Release, PackageIndex)]
sliceIndexes arch slice =
foldRepository f f (sliceRepoKey slice)
where
f repo =
case (sourceDist (sliceSource slice)) of
Left exact -> error $ "Can't handle exact path in sources.list: " ++ exact
Right (release, sections) -> return $ map (makeIndex repo release) sections
makeIndex repo release section =
(repoKey repo,
findReleaseInfo repo release,
PackageIndex { packageIndexComponent = section
, packageIndexArch = case (sourceType (sliceSource slice)) of
DebSrc -> Source
Deb -> arch })
findReleaseInfo repo release =
case filter ((==) release . releaseName) (repoReleaseInfo repo) of
[x] -> x
[] -> error $ ("sliceIndexes: Invalid release name: " ++ releaseName' release ++
"\n You may need to remove ~/.autobuilder/repoCache." ++
"\n Available: " ++ (show . map releaseName . repoReleaseInfo $ repo)) ++
"\n repoKey: " ++ show (repoKey repo) ++
"\n repoReleaseInfo: " ++ show (repoReleaseInfo repo) ++
"\n slice: " ++ show slice
xs -> error $ "Internal error 5 - multiple releases named " ++ releaseName' release ++ "\n" ++ show xs
data UpdateError
= Changed ReleaseName FilePath SliceList SliceList
| Missing ReleaseName FilePath
| Flushed
instance Show UpdateError where
show (Changed r p l1 l2) = unwords ["Changed", show r, show p, ppDisplay l1, ppDisplay l2]
show (Missing r p) = unwords ["Missing", show r, show p]
show Flushed = "Flushed"
sourcePackagesFromSources :: MonadRepos m => EnvRoot -> Arch -> SliceList -> m [SourcePackage]
sourcePackagesFromSources root arch sources = do
indexes <- mapM (sliceIndexes arch) (slices . sourceSlices $ sources) >>= return . concat
mapM (\ (repo, rel, index) -> sourcePackagesOfIndex root arch repo rel index) indexes >>= return . concat
-- FIXME: assuming the index is part of the cache
sourcePackagesOfIndex :: MonadRepos m => EnvRoot -> Arch -> RepoKey -> Release -> PackageIndex -> m [SourcePackage]
sourcePackagesOfIndex root arch repo release index =
-- quieter 2 $ qBracket ($(symbol 'sourcePackagesOfIndex) ++ " " ++ path) $
do -- unsafeInterleaveIO makes the package index file reads
-- asynchronous, not sure what the performance implications
-- are. Anyway, this is now only called on demand, so the
-- unsafeInterleaveIO is probably moot.
paragraphs <- liftIO $ {-unsafeInterleaveIO-} (readParagraphs path)
let packages = List.map (toSourcePackage index) paragraphs
return packages
where
path = rootPath root ++ suff
suff = indexCacheFile arch repo release index
toSourcePackage :: PackageIndex -> B.Paragraph -> SourcePackage
toSourcePackage index package =
case (B.fieldValue "Directory" package,
B.fieldValue "Files" package,
B.fieldValue "Package" package,
maybe Nothing (Just . parseDebianVersion . T.unpack) (B.fieldValue "Version" package)) of
(Just directory, Just files, Just name, Just version) ->
case (parseSourcesFileList files, parseSourceParagraph package) of
(Right files', Right para) ->
SourcePackage
{ sourcePackageID = makeSourcePackageID (T.unpack name) version
, sourceParagraph = package
, sourceControl = para
, sourceDirectory = T.unpack directory
, sourcePackageFiles = files' }
(Left messages, _) -> error $ "Invalid file list: " ++ show messages
(_, Left messages) -> error $ "Error in source paragraph\n package=" ++ show package ++ "\n index=" ++ show index ++ "\n messages:\n " ++ intercalate "\n " messages
x -> error $ "Missing info in source package control information in " ++ show index ++ " -> " ++ show x ++ " :\n" ++ T.unpack (formatParagraph package)
where
-- Parse the list of files in a paragraph of a Sources index.
parseSourcesFileList :: T.Text -> Either [String] [SourceFileSpec]
parseSourcesFileList text =
merge . catMaybes . List.map parseSourcesFiles . lines . T.unpack $ text
parseSourcesFiles line =
case words line of
[md5sum, size, name] -> Just (Right (SourceFileSpec md5sum (read size) name))
[] -> Nothing
_ -> Just (Left ("Invalid line in Files list: '" ++ show line ++ "'"))
merge x = case partition (either (const True) (const False)) x of
(a, []) -> Left . catMaybes . List.map (either Just (const Nothing )) $ a
(_, a) -> Right . catMaybes . List.map (either (const Nothing) Just) $ a
parseSourceParagraph :: B.Paragraph -> Either [String] SourceControl
parseSourceParagraph p =
-- Look up the required fields
case (B.fieldValue "Package" p,
B.fieldValue "Maintainer" p) of
(Just source', Just maintainer') ->
-- The optional fields can be parsed as pure values
Right (SourceControl
{ source = source'
, maintainer = maintainer'
, uploaders = maybe [] (: []) $ B.fieldValue "Uploaders" p
, packageSection = fmap stripWS $ B.fieldValue "Section" p
, packagePriority = fmap stripWS $ B.fieldValue "Priority" p
, buildDepends = maybe [] (: []) $ B.fieldValue "Build-Depends" p
, buildDependsIndep = maybe [] (: []) $ B.fieldValue "Build-Depends-Indep" p
, buildConflicts = maybe [] (: []) $ B.fieldValue "Build-Conflicts" p
, buildConflictsIndep = maybe [] (: []) $ B.fieldValue "Build-Conflicts-Indep" p
, standardsVersion = fmap stripWS $ B.fieldValue "Standards-Version" p
, homepage = fmap stripWS $ B.fieldValue "Homepage" p })
_x -> Left ["parseSourceParagraph - One or more required fields (Package, Maintainer, Standards-Version) missing: " ++ show p]
binaryPackagesFromSources :: MonadRepos m => EnvRoot -> Arch -> SliceList -> m [BinaryPackage]
binaryPackagesFromSources root arch sources = do
indexes <- mapM (sliceIndexes arch) (slices . binarySlices $ sources) >>= return . concat
mapM (\ (repo, rel, index) -> binaryPackagesOfIndex root arch repo rel index) indexes >>= return . concat
-- FIXME: assuming the index is part of the cache
binaryPackagesOfIndex :: MonadRepos m => EnvRoot -> Arch -> RepoKey -> Release -> PackageIndex -> m [BinaryPackage]
binaryPackagesOfIndex root arch repo release index =
-- quieter 2 $ qBracket ($(symbol 'binaryPackagesOfIndex) ++ ": " ++ path) $
do paragraphs <- liftIO $ {-unsafeInterleaveIO-} (readParagraphs path)
let packages = List.map (toBinaryPackage release index) paragraphs
return packages
where
suff = indexCacheFile arch repo release index
path = rootPath root ++ suff
toBinaryPackage :: Release -> PackageIndex -> B.Paragraph -> BinaryPackage
toBinaryPackage release index p =
case (B.fieldValue "Package" p, B.fieldValue "Version" p) of
(Just name, Just version) ->
BinaryPackage
{ packageID =
makeBinaryPackageID (T.unpack name) (parseDebianVersion (T.unpack version))
, packageInfo = p
, pDepends = tryParseRel $ B.lookupP "Depends" p
, pPreDepends = tryParseRel $ B.lookupP "Pre-Depends" p
, pConflicts = tryParseRel $ B.lookupP "Conflicts" p
, pReplaces = tryParseRel $ B.lookupP "Replaces" p
, pProvides = tryParseRel $ B.lookupP "Provides" p
}
_ -> error ("Invalid data in source index:\n " ++ packageIndexPath release index)
tryParseRel :: Maybe B.Field -> B.Relations
tryParseRel (Just (B.Field (_, relStr))) = either (error . show) id (B.parseRelations relStr)
tryParseRel _ = []
readParagraphs :: FilePath -> IO [B.Paragraph]
readParagraphs path =
do --IO.hPutStrLn IO.stderr ("OSImage.paragraphsFromFile " ++ path) -- Debugging output
h <- IO.openBinaryFile path IO.ReadMode
B.Control paragraphs <- B.parseControlFromHandle path h >>= return . (either (error . show) id)
IO.hClose h
--IO.hPutStrLn IO.stderr ("OSImage.paragraphsFromFile " ++ path ++ " done.") -- Debugging output
return paragraphs
indexCacheFile :: Arch -> RepoKey -> Release -> PackageIndex -> FilePath
indexCacheFile arch repo release index =
case (arch, packageIndexArch index) of
(Binary _ _, Source) -> indexPrefix repo release index ++ "_source_Sources"
(Binary _ _, indexArch@(Binary _ _)) -> indexPrefix repo release index ++ "_binary-" ++ show (prettyArch indexArch) ++ "_Packages"
(x, _) -> error $ "Invalid build architecture: " ++ show x
indexPrefix :: RepoKey -> Release -> PackageIndex -> FilePath
indexPrefix repo release index =
(escapeURIString (/= '@') ("/var/lib/apt/lists/" ++ uriText +?+ "dists_") ++
releaseName' distro ++ "_" ++ (sectionName' $ section))
where
section = packageIndexComponent index
uri = repoKeyURI repo
distro = releaseName $ release
scheme = uriScheme uri
auth = uriAuthority uri
path = uriPath uri
userpass = maybe "" uriUserInfo auth
reg = maybeOfString $ maybe "" uriRegName auth
port = maybe "" uriPort auth
(user, pass) = break (== ':') userpass
user' = maybeOfString user
pass' = maybeOfString pass
uriText = prefix scheme user' pass' reg port path
-- If user is given and password is not, the user name is
-- added to the file name. Otherwise it is not. Really.
prefix "http:" (Just user'') Nothing (Just host) port' path' =
user'' ++ host ++ port' ++ escape path'
prefix "http:" _ _ (Just host) port' path' =
host ++ port' ++ escape path'
prefix "ftp:" _ _ (Just host) _ path' =
host ++ escape path'
prefix "file:" Nothing Nothing Nothing "" path' =
escape path'
prefix "ssh:" (Just user'') Nothing (Just host) port' path' =
user'' ++ host ++ port' ++ escape path'
prefix "ssh" _ _ (Just host) port' path' =
host ++ port' ++ escape path'
prefix _ _ _ _ _ _ = error ("invalid repo URI: " ++ (uriToString' . repoKeyURI $ repo))
maybeOfString "" = Nothing
maybeOfString s = Just s
escape s = intercalate "_" (wordsBy (== '/') s)
wordsBy :: Eq a => (a -> Bool) -> [a] -> [[a]]
wordsBy p s =
case (break p s) of
(s', []) -> [s']
(h, t) -> h : wordsBy p (drop 1 t)
(+?+) :: String -> String -> String
(+?+) a ('_' : b) = a +?+ b
(+?+) "" b = b
(+?+) a b =
case last a of
'_' -> (init a) +?+ b
_ -> a ++ "_" ++ b