cblrepo-0.22.0: src/Util/Translation.hs
{-# LANGUAGE FlexibleContexts #-}
{-
- Copyright 2011 Per Magnus Therning
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-}
module Util.Translation where
import PkgDB as DB
import Util.Misc
import Util.Dist
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Except
import qualified Data.ByteString.Lazy as BSL (writeFile)
import Data.Char
import Data.List
import Data.Maybe
import Data.Monoid hiding ((<>))
import Data.Version
import Distribution.Package as P
import Distribution.PackageDescription as PD
import Distribution.Text
import Prelude hiding ( (<$>) )
import System.Directory
import System.Exit
import System.FilePath
import System.IO
import System.Process
import System.Unix.Directory
import Text.PrettyPrint.ANSI.Leijen hiding((</>))
-- {{{1 ShQuotedString
newtype ShQuotedString = ShQuotedString String
deriving (Eq, Show)
instance Pretty ShQuotedString where
pretty (ShQuotedString s) = char '"' <> text quotStr <> char '"'
where
quotStr = shEsc $ unwords $ lines s
-- {{{1 ShArray
newtype ShArray = ShArray [String]
deriving (Eq, Show)
instance Monoid ShArray where
mempty = ShArray []
(ShArray a) `mappend` (ShArray b) = ShArray $ a ++ b
instance Pretty ShArray where
pretty (ShArray a) = encloseSep (char '(') (char ')') (char ' ') (map (\ t -> char '"' <> text t <> char '"') a)
-- {{{1 ShVar
data ShVar a = ShVar String a
deriving (Eq, Show)
{-# ANN shVarNewValue "HLint: ignore Eta reduce" #-}
shVarNewValue (ShVar n _) v = ShVar n v
shVarAppendValue (ShVar n v1) v2 = ShVar n (v1 `mappend` v2)
shVarValue (ShVar _ v) = v
shVarName (ShVar n _) = n
shVarRefName (ShVar n _) = "${" ++ n ++ "}"
instance Pretty a => Pretty (ShVar a) where
pretty (ShVar n v) = text n <> char '=' <> pretty v
-- {{{1 ArchPkg
data ArchPkg = ArchPkg
{ apPkgName :: String
, apHkgName :: String
, apHasLibrary :: Bool
, apLicenseFile :: Maybe FilePath
, apCabalPatch :: Maybe FilePath
, apPkgbuildPatch :: Maybe FilePath
, apInstallPatch :: Maybe FilePath
, apBuildPatch :: Maybe FilePath
-- shell bits
, apShHkgName :: ShVar String
, apShPkgName :: ShVar String
, apShPkgVer :: ShVar Version
, apShPkgRel :: ShVar Int
, apShXRev :: ShVar String
, apShPkgDesc :: ShVar ShQuotedString
, apShUrl :: ShVar ShQuotedString
, apShLicence :: ShVar ShArray
, apShMakeDepends :: ShVar ShArray
, apShDepends :: ShVar ShArray
, apShSource :: ShVar ShArray
, apShInstall :: Maybe (ShVar ShQuotedString)
, apShSha256Sums :: ShVar ShArray
, apFlags :: FlagAssignment
} deriving (Eq, Show)
-- {{{2 baseArchPkg
baseArchPkg = ArchPkg
{ apPkgName = ""
, apHkgName = ""
, apHasLibrary = False
, apLicenseFile = Nothing
, apCabalPatch = Nothing
, apPkgbuildPatch = Nothing
, apInstallPatch = Nothing
, apBuildPatch = Nothing
, apShHkgName = ShVar "_hkgname" ""
, apShPkgName = ShVar "pkgname" ""
, apShPkgVer = ShVar "_ver" (Version [] [])
, apShPkgRel = ShVar "pkgrel" 0
, apShXRev = ShVar "_xrev" "0"
, apShPkgDesc = ShVar "pkgdesc" (ShQuotedString "")
, apShUrl = ShVar "url" (ShQuotedString "http://hackage.haskell.org/package/${_hkgname}")
, apShLicence = ShVar "license" (ShArray [])
, apShMakeDepends = ShVar "makedepends" (ShArray [])
, apShDepends = ShVar "depends" (ShArray [])
, apShSource = ShVar "source" (ShArray ["http://hackage.haskell.org/packages/archive/${_hkgname}/${_ver}/${_hkgname}-${_ver}.tar.gz", "original.cabal"])
, apShInstall = Just $ ShVar "install" (ShQuotedString "${pkgname}.install")
-- this is a trick to make sure that the user's setting for integrity
-- checking in makepkg.conf isn't used, as long as this array contains
-- something non-empty it will overrule
, apShSha256Sums = ShVar "sha256sums" (ShArray ["0"])
, apFlags = []
}
-- {{{2 Pretty instance
instance Pretty ArchPkg where
pretty (ArchPkg
{ apHasLibrary = hasLib
, apLicenseFile = licenseFile
, apCabalPatch = cabalPatchFile
, apBuildPatch = buildPatchFile
, apShHkgName = hkgName
, apShPkgName = pkgName
, apShPkgVer = pkgVer
, apShPkgRel = pkgRel
, apShXRev = xrev
, apShPkgDesc = pkgDesc
, apShUrl = url
, apShLicence = pkgLicense
, apShMakeDepends = makeDepends
, apShDepends = depends
, apShSource = source
, apShInstall = install
, apShSha256Sums = sha256sums
, apFlags = flags
}) = vsep
[ text "# custom variables"
, pretty hkgName
, maybe empty (pretty . ShVar "_licensefile") licenseFile
, pretty pkgVer
, pretty xrev
, empty, text "# PKGBUILD options/directives"
, pretty pkgName
, text "pkgver=${_ver}.x${_xrev}"
, pretty pkgRel
, pretty pkgDesc
, pretty url
, pretty pkgLicense
, text "arch=('i686' 'x86_64')"
, pretty makeDepends
, pretty depends
, text "options=('strip' 'staticlibs')"
, pretty source
, maybe empty pretty install
, pretty sha256sums
, empty, text "# PKGBUILD functions"
, empty
, prepareFunction
, empty
, if hasLib then libBuildFunction else exeBuildFunction
, empty
, if hasLib then libPackageFunction else exePackageFunction
, empty
]
where
prepareFunction = text "prepare() {" <>
nest 4 (empty <$>
text "cd \"${srcdir}/${_hkgname}-${_ver}\"" <$>
text "cp \"${srcdir}/original.cabal\" \"${srcdir}/${_hkgname}-${_ver}/${_hkgname}.cabal\"" <$>
empty <$>
maybe (text "# no source patch") (\ _ ->
text "patch -p4 < \"${srcdir}/source.patch\"")
buildPatchFile
) <$>
char '}'
libBuildFunction = text "build() {" <>
nest 4 (empty <$>
text "cd \"${srcdir}/${_hkgname}-${_ver}\"" <$>
empty <$>
nest 4 (text "runhaskell Setup configure -O --enable-library-profiling --enable-shared \\" <$>
text "--prefix=/usr --docdir=\"/usr/share/doc/${pkgname}\" \\" <$>
text "--libsubdir=\\$compiler/site-local/\\$pkgid" <> confFlags) <$>
text "runhaskell Setup build" <$>
text "runhaskell Setup haddock --hoogle --html" <$>
text "runhaskell Setup register --gen-script" <$>
text "runhaskell Setup unregister --gen-script" <$>
text "sed -i -r -e \"s|ghc-pkg.*unregister[^ ]* |&'--force' |\" unregister.sh"
) <$>
char '}'
exeBuildFunction = text "build() {" <>
nest 4 (empty <$> text "cd \"${srcdir}/${_hkgname}-${_ver}\"" <$>
empty <$>
text "runhaskell Setup configure -O --prefix=/usr --docdir=\"/usr/share/doc/${pkgname}\"" <> confFlags <$>
text "runhaskell Setup build"
) <$>
char '}'
confFlags = if null flags
then empty
else text " \\" <>
nest 4 (empty <$> (hsep . map (uncurry (<>)) $ zip (repeat $ text "-f") (map prettyFlag flags)))
libPackageFunction = text "package() {" <>
nest 4 (empty <$>
text "cd \"${srcdir}/${_hkgname}-${_ver}\"" <$>
empty <$>
text "install -D -m744 register.sh \"${pkgdir}/usr/share/haskell/${pkgname}/register.sh\"" <$>
text "install -m744 unregister.sh \"${pkgdir}/usr/share/haskell/${pkgname}/unregister.sh\"" <$>
text "install -d -m755 \"${pkgdir}/usr/share/doc/ghc/html/libraries\"" <$>
text "ln -s \"/usr/share/doc/${pkgname}/html\" \"${pkgdir}/usr/share/doc/ghc/html/libraries/${_hkgname}\"" <$>
text "runhaskell Setup copy --destdir=\"${pkgdir}\"" <$>
maybe empty (\ _ -> text "install -D -m644 \"${_licensefile}\" \"${pkgdir}/usr/share/licenses/${pkgname}/LICENSE\"" <$>
text "rm -f \"${pkgdir}/usr/share/doc/${pkgname}/${_licensefile}\"") licenseFile
) <$>
char '}'
exePackageFunction = text "package() {" <>
nest 4 (empty <$> text "cd \"${srcdir}/${_hkgname}-${_ver}\"" <$>
text "runhaskell Setup copy --destdir=\"${pkgdir}\""
) <$>
char '}'
-- {{{1 ArchInstall
data ArchInstall = ArchInstall
{ aiShPkgName :: ShVar String
} deriving (Eq, Show)
-- {{{2 baseArchInstall
baseArchInstall = ArchInstall
{ aiShPkgName = ShVar "pkgname" ""
}
-- {{{2 ArchInstall from ArchPackage
aiFromAP (ArchPkg { apShPkgName = pkgName }) =
ArchInstall { aiShPkgName = pkgName }
-- {{{2 Pretty instance
instance Pretty ArchInstall where
pretty (ArchInstall
{ aiShPkgName = pkgName
}) = vsep
[ text "# custom variables"
, pretty pkgName
, pretty (ShVar "HS_DIR" "usr/share/haskell/${pkgname}")
, empty, text "# functions"
, postInstallFunction
, empty, preUpgradeFunction
, empty, postUpgradeFunction
, empty, preRemoveFunction
, empty
]
where
postInstallFunction = text "post_install() {" <>
nest 4 (empty <$> text "${HS_DIR}/register.sh --verbose=0") <$>
char '}'
preUpgradeFunction = text "pre_upgrade() {" <>
nest 4 (empty <$> text "${HS_DIR}/unregister.sh --verbose=0") <$>
char '}'
postUpgradeFunction = text "post_upgrade() {" <>
nest 4 (empty <$> text "${HS_DIR}/register.sh --verbose=0") <$>
char '}'
preRemoveFunction = text "pre_remove() {" <>
nest 4 (empty <$> text "${HS_DIR}/unregister.sh --verbose=0") <$>
char '}'
-- {{{1 extra instances
instance Pretty Version where
pretty (Version b _) = encloseSep empty empty (char '.') (map pretty b)
prettyFlag (FlagName n, True) = text n
prettyFlag (FlagName n, False) = text $ '-' : n
-- {{{1 translate
-- TODO:
-- • translation of extraLibDepends-libs to Arch packages
translate :: Version -> Int -> CblDB -> FlagAssignment -> PackageDescription -> ArchPkg
translate ghcVer ghcRel db fa pd = let
ap = baseArchPkg
(PackageName hkgName) = packageName pd
pkgVer = packageVersion pd
pkgRel = maybe 1 pkgRelease (lookupPkg db hkgName)
xrev = show $ Util.Dist.pkgXRev pd
hasLib = isJust (library pd)
licFn = let l = licenseFiles pd in if null l then Nothing else Just (head l)
archName = (if hasLib then "haskell-" else "") ++ map toLower hkgName
pkgDesc = synopsis pd
url = if null (homepage pd) then "http://hackage.haskell.org/package/${_hkgname}" else homepage pd
lic = display (license pd)
makeDepends = if hasLib then [] else ghcVersionDep ghcVer ghcRel : calcExactDeps db pd
depends = if hasLib then ghcVersionDep ghcVer ghcRel : calcExactDeps db pd else []
extraLibDepends = maybe [] (extraLibs . libBuildInfo) (library pd)
install = if hasLib then apShInstall ap else Nothing
in ap
{ apPkgName = archName
, apHkgName = hkgName
, apHasLibrary = hasLib
, apLicenseFile = licFn
, apShHkgName = shVarNewValue (apShHkgName ap) hkgName
, apShPkgName = shVarNewValue (apShPkgName ap) archName
, apShPkgVer = shVarNewValue (apShPkgVer ap) pkgVer
, apShPkgRel = shVarNewValue (apShPkgRel ap) pkgRel
, apShXRev = shVarNewValue (apShXRev ap) xrev
, apShPkgDesc = shVarNewValue (apShPkgDesc ap) (ShQuotedString pkgDesc)
, apShUrl = shVarNewValue (apShUrl ap) (ShQuotedString url)
, apShLicence = shVarNewValue (apShLicence ap) (ShArray [lic])
, apShMakeDepends = shVarNewValue (apShMakeDepends ap) (ShArray makeDepends)
, apShDepends = shVarNewValue (apShDepends ap) (ShArray $ depends ++ extraLibDepends)
, apShInstall = install
, apFlags = fa
}
-- Calculate exact dependencies based on the package in a CblDB. We assume the
-- same db has been used to finalise the package so it's all right to use
-- fromJust.
-- TODO:
-- • this is most likely too simplistic to create the Arch package names
-- correctly for all possible dependencies
calcExactDeps db pd = map depString deps
where
remPkgs = map DB.pkgName (filter isGhcPkg db) ++ [pkgNameStr pd]
deps = filter (not . (`elem` remPkgs)) (map depName (buildDepends pd))
depString n = "haskell-" ++ name ++ "=" ++ ver ++ ".x" ++ xrev ++ "-" ++ rel
where
pkg = fromJust $ lookupPkg db n
name = map toLower $ DB.pkgName pkg
ver = display $ DB.pkgVersion pkg
xrev = show $ DB.pkgXRev pkg
rel = show $ pkgRelease pkg
-- {{{1 stuff with patches
-- {{{2 addPatches
addPatches patchDir ap = let
hkgName = apHkgName ap
sources = apShSource ap
fi tF fF v = if v then tF else fF
cabalPatchFn = patchDir </> hkgName <.> "cabal"
pkgbuildPatchFn = patchDir </> hkgName <.> "pkgbuild"
installPatchFn = patchDir </> hkgName <.> "install"
buildPatchFn = patchDir </> hkgName <.> "source"
in do
cabalPatch <- doesFileExist cabalPatchFn >>= fi (liftM Just $ canonicalizePath cabalPatchFn) (return Nothing)
pkgBuildPatch <- doesFileExist pkgbuildPatchFn >>= fi (liftM Just $ canonicalizePath pkgbuildPatchFn) (return Nothing)
installPatch <- doesFileExist installPatchFn >>= fi (liftM Just $ canonicalizePath installPatchFn) (return Nothing)
buildPatch <- doesFileExist buildPatchFn >>= fi (liftM Just $ canonicalizePath buildPatchFn) (return Nothing)
let sources' = shVarAppendValue sources
(ShArray $ catMaybes [maybe Nothing (const $ Just "source.patch") buildPatch])
return ap
{ apCabalPatch = cabalPatch
, apPkgbuildPatch = pkgBuildPatch
, apInstallPatch = installPatch
, apBuildPatch = buildPatch
, apShSource = sources'
}
-- {{{2 copyPatches
copyPatches destDir ap = let
cabalPatch = apCabalPatch ap
buildPatch = apBuildPatch ap
in do
maybe (return ()) (\ fn -> copyFile fn (destDir </> "cabal.patch")) cabalPatch
maybe (return ()) (\ fn -> copyFile fn (destDir </> "source.patch")) buildPatch
-- {{{1 addHashes
addHashes ap cbl tmpDir = let
hashes = map (filter (`elem` "1234567890abcdef")) . lines . drop 11
pkgbuildFn = tmpDir </> "PKGBUILD"
installFn = tmpDir </> apPkgName ap <.> "install"
cblFn = tmpDir </> "original.cabal"
pkgbuildPatch = apPkgbuildPatch ap
in do
liftIO $ copyPatches tmpDir ap
liftIO $ withFile pkgbuildFn WriteMode (`hPutDoc` pretty ap)
liftIO $ withFile installFn WriteMode (`hPutDoc` (pretty $ aiFromAP ap))
maybe (return ()) (void . applyPatch pkgbuildFn) pkgbuildPatch
liftIO $ BSL.writeFile cblFn cbl
(ec, out, _) <- liftIO $ withWorkingDirectory tmpDir (readProcessWithExitCode "makepkg" ["-g"] "")
case ec of
ExitFailure _ ->
throwE $ "makepkg: error while calculating the source hashes for " ++ apHkgName ap
ExitSuccess ->
return (if "sha256sums=(" `isPrefixOf` out then replaced else ap)
where
replaced = ap { apShSha256Sums = shVarNewValue (apShSha256Sums ap) (ShArray $ hashes out) }