cabal-debian-4.24.8: src/Debian/Debianize/Bundled.hs
-- | Determine whether a specific version of a Haskell package is
-- bundled with into this particular version of the given compiler.
{-# LANGUAGE FlexibleContexts, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell #-}
module Debian.Debianize.Bundled
( builtIn
-- * Utilities
, aptCacheShowPkg
, aptCacheProvides
, aptCacheDepends
, aptCacheConflicts
, aptVersions
) where
import Control.DeepSeq (force)
import Control.Exception (SomeException, try)
import Data.Char (toLower)
import Data.Function (on)
import Data.Function.Memoize (memoize2, memoize3)
import Data.List (isPrefixOf, sortBy)
import Data.Map as Map (Map)
import Data.Maybe (listToMaybe, mapMaybe)
import Data.Version (parseVersion, Version(..))
import Debian.Debianize.VersionSplits (cabalFromDebian', DebBase(DebBase), VersionSplits)
import Debian.GHC ()
import Debian.Relation (BinPkgName(..))
import Debian.Relation.ByteString ()
import Debian.Version (DebianVersion, parseDebianVersion, prettyDebianVersion)
import Distribution.Package (PackageIdentifier(..), PackageName(..))
import Distribution.Simple.Compiler (CompilerFlavor(..))
import System.IO.Unsafe (unsafePerformIO)
import System.Process (readProcess)
import System.Unix.Chroot (useEnv)
import Text.ParserCombinators.ReadP (readP_to_S)
import Text.Regex.TDFA ((=~))
-- | Find out what version, if any, of a cabal library is built into
-- the newest version of haskell compiler hc in environment root. For
-- GHC this is done by looking at what virtual packages debian package
-- provides. I have modified the ghcjs packaging to generate the
-- required virtual packages in the Provides line. For other
-- compilers it maybe be unimplemented.
builtIn :: Map PackageName VersionSplits -> CompilerFlavor -> FilePath -> PackageName -> Maybe Version
builtIn splits hc root lib = do
f $ builtIn' splits hc root
where
f :: (DebianVersion, [PackageIdentifier]) -> Maybe Version
f (hcv, ids) = case map pkgVersion (filter (\ i -> pkgName i == lib) ids) of
[] -> Nothing
[v] -> Just v
vs -> error $ show hc ++ "-" ++ show hcv ++ " in " ++ show root ++ " provides multiple versions of " ++ show lib ++ ": " ++ show vs
-- | Ok, lets see if we can infer the built in packages from the
-- Provides field returned by apt-cache.
builtIn' :: Map PackageName VersionSplits -> CompilerFlavor -> FilePath -> (DebianVersion, [PackageIdentifier])
builtIn' splits hc root = do
-- Find out what library versions are provided by the latest version
-- of the compiler.
case aptCacheProvides root hcname of
[] -> error $ "No versions of " ++ show hc ++ " (" ++ show hcname ++ ") in " ++ show root
((v, pids) : _) -> (v, mapMaybe (parsePackageID . unBinPkgName) pids)
where
BinPkgName hcname = BinPkgName (map toLower (show hc))
-- The virtual package id, which appears in the Provides
-- line for the compiler package, is generated by the
-- function package_id_to_virtual_package in Dh_Haskell.sh.
-- It consists of the library's debian package name and the
-- first five characters of the checksum.
-- parsePID "libghc-unix-dev-2.7.0.1-2a456" -> Just (PackageIdentifier "unix" (Version [2,7,0,1] []))
parsePackageID :: String -> Maybe PackageIdentifier
parsePackageID s =
case s =~ ("lib" ++ hcname ++ "-(.*)-dev-([0-9.]*)-.....$") :: (String, String, String, [String]) of
(_, _, _, [base, vs]) -> case listToMaybe (map fst $ filter ((== "") . snd) $ readP_to_S parseVersion $ vs) of
Just v -> Just (cabalFromDebian' splits (DebBase base) v)
Nothing -> Nothing
_ -> Nothing
aptCacheShowPkg :: FilePath -> String -> Either SomeException String
aptCacheShowPkg =
memoize2 (\ root hcname -> unsafePerformIO (try (chroot root (readProcess "apt-cache" ["showpkg", hcname] ""))))
where
chroot "/" = id
chroot root = useEnv root (return . force)
aptCacheProvides :: FilePath -> String -> [(DebianVersion, [BinPkgName])]
aptCacheProvides root hcname =
let lns = lines . either (\ (e :: SomeException) -> error $ "builtIn: " ++ show e) id $ aptCacheShowPkg root hcname
hcs = map words $ takeBetween (isPrefixOf "Provides:") (isPrefixOf "Reverse Provides:") lns
hcs' = reverse . sortBy (compare `on` fst) . map doHCVersion $ hcs in
hcs'
where
doHCVersion :: [String] -> (DebianVersion, [BinPkgName])
doHCVersion (versionString : "-" : deps) = (parseDebianVersion versionString, map BinPkgName deps)
doHCVersion x = error $ "Unexpected output from apt-cache: " ++ show x
aptCacheDepends :: FilePath -> String -> String -> Either SomeException String
aptCacheDepends =
memoize3 (\ root hcname ver -> unsafePerformIO (try (chroot root (readProcess "apt-cache" ["depends", hcname ++ "=" ++ ver] ""))))
where
chroot "/" = id
chroot root = useEnv root (return . force)
aptCacheConflicts :: FilePath -> String -> DebianVersion -> [BinPkgName]
aptCacheConflicts root hcname ver =
either (\ _ -> []) (mapMaybe doLine . lines) (aptCacheDepends root hcname (show (prettyDebianVersion ver)))
where
doLine s = case s =~ "^[ ]*Conflicts:[ ]*<(.*)>$" :: (String, String, String, [String]) of
(_, _, _, [name]) -> Just (BinPkgName name)
_ -> Nothing
aptVersions :: FilePath -> String -> [DebianVersion]
aptVersions root hcname =
either (\ _ -> []) (map parseDebianVersion . filter (/= "") . map (takeWhile (/= ' ')) . takeWhile (not . isPrefixOf "Reverse Depends:") . drop 1 . dropWhile (not . isPrefixOf "Versions:") . lines) (aptCacheShowPkg root hcname)
takeBetween :: (a -> Bool) -> (a -> Bool) -> [a] -> [a]
takeBetween startPred endPred = takeWhile (not . endPred) . dropWhile startPred . dropWhile (not . startPred)