{- - 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 Add import Distribution.PackageDescription.Parse import Distribution.Verbosity -- import PkgDB as DB import Util.Misc import Control.Monad import Control.Monad.Error 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 System.Directory import System.Exit import System.FilePath 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 s <> char '"' -- {{{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) shVarNewValue (ShVar n _) v = ShVar n v shVarAppendValue (ShVar n v1) v2 = ShVar n (v1 `mappend` v2) shVarValue (ShVar _ v) = v instance Pretty a => Pretty (ShVar a) where pretty (ShVar n v) = text n <> char '=' <> pretty v -- {{{1 ArchPkg -- TODO: flags 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 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 } 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 "pkgver" (Version [] []) , apShPkgRel = ShVar "pkgrel" "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}/${pkgver}/${_hkgname}-${pkgver}.tar.gz"]) , 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"]) } -- {{{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 , apShPkgDesc = pkgDesc , apShUrl = url , apShLicence = pkgLicense , apShMakeDepends = makeDepends , apShDepends = depends , apShSource = source , apShInstall = install , apShSha256Sums = sha256sums }) = vsep [ text "# custom variables" , pretty hkgName , maybe empty (\ fn -> pretty $ ShVar "_licensefile" fn) licenseFile , empty, text "# PKGBUILD options/directives" , pretty pkgName , pretty pkgVer , pretty pkgRel , pretty pkgDesc , pretty url , pretty pkgLicense , text "arch=('i686' 'x86_64')" , pretty makeDepends , pretty depends , text "options=('strip')" , pretty source , maybe empty pretty install , pretty sha256sums , empty, text "# PKGBUILD functions" , if hasLib then libBuildFunction else exeBuildFunction , empty , if hasLib then libPackageFunction else exePackageFunction , empty ] where libBuildFunction = text "build() {" <> nest 4 (empty <$> text "cd ${srcdir}/${_hkgname}-${pkgver}" <$> maybe empty (\ _ -> text $ "patch " ++ shVarValue hkgName ++ ".cabal ${srcdir}/cabal.patch ") cabalPatchFile <$> maybe empty (\ _ -> text $ "patch -p4 < ${srcdir}/source.patch") buildPatchFile <$> nest 4 (text "runhaskell Setup configure -O -p --enable-split-objs --enable-shared \\" <$> text "--prefix=/usr --docdir=/usr/share/doc/${pkgname} \\" <$> text "--libsubdir=\\$compiler/site-local/\\$pkgid") <$> text "runhaskell Setup build" <$> text "runhaskell Setup haddock" <$> 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}-${pkgver}" <$> maybe empty (\ _ -> text $ "patch " ++ shVarValue hkgName ++ ".cabal ${srcdir}/cabal.patch ") cabalPatchFile <$> maybe empty (\ _ -> text $ "patch -p4 < ${srcdir}/source.patch") buildPatchFile <$> text "runhaskell Setup configure -O --prefix=/usr --docdir=/usr/share/doc/${pkgname}" <$> text "runhaskell Setup build" ) <$> char '}' libPackageFunction = text "package() {" <> nest 4 (empty <$> text "cd ${srcdir}/${_hkgname}-${pkgver}" <$> 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}-${pkgver}" <$> 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, postRemoveFunction , empty ] where postInstallFunction = text "post_install() {" <> nest 4 (empty <$> text "${HS_DIR}/register.sh" <$> text "(cd usr/share/doc/ghc/html/libraries; ./gen_contents_index)") <$> char '}' preUpgradeFunction = text "pre_upgrade() {" <> nest 4 (empty <$> text "${HS_DIR}/unregister.sh") <$> char '}' postUpgradeFunction = text "post_upgrade() {" <> nest 4 (empty <$> text "${HS_DIR}/register.sh" <$> text "(cd usr/share/doc/ghc/html/libraries; ./gen_contents_index)") <$> char '}' preRemoveFunction = text "pre_remove() {" <> nest 4 (empty <$> text "${HS_DIR}/unregister.sh") <$> char '}' postRemoveFunction = text "post_remove() {" <> nest 4 (empty <$> text "(cd usr/share/doc/ghc/html/libraries; ./gen_contents_index)") <$> char '}' -- {{{1 extra instances instance Pretty Version where pretty (Version b _) = encloseSep empty empty (char '.') (map pretty b) -- {{{1 translate -- TODO: -- • add flags -- • translation of extraLibDepends-libs to Arch packages translate db pd = let ap = baseArchPkg (PackageName hkgName) = packageName pd pkgVer = packageVersion pd pkgRel = maybe "1" pkgRelease (lookupPkg db hkgName) hasLib = maybe False (const True) (library pd) licFn = let l = licenseFile pd in if null l then Nothing else Just 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] ++ calcExactDeps db pd depends = if hasLib then [ghcVersionDep] ++ 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 , 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 } -- 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 = let n = (\ (P.PackageName n) -> n ) (P.packageName pd) remPkgs = (map DB.pkgName (filter isGhcPkg db)) ++ ghcPkgs ++ [n] deps = filter (not . flip elem remPkgs) (map depName (buildDepends pd)) lookupPkgVer = display . DB.pkgVersion . fromJust . lookupPkg db depString n = let pkg = fromJust $ lookupPkg db n name = map toLower $ DB.pkgName pkg ver = display $ DB.pkgVersion pkg rel = pkgRelease pkg in "haskell-" ++ name ++ "=" ++ ver ++ "-" ++ rel in map depString deps -- {{{2 ghcPkgs -- libraries included in GHC, but not marked as provided by the Arch package ghcPkgs = ["base", "bin-package-db", "ffi", "ghc", "ghc-binary", "ghc-prim", "haskell2010", "integer-gmp", "rts"] -- {{{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 "cabal.patch") cabalPatch, 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 tmpDir = let hashes = map (filter (`elem` "1234567890abcdef")) . lines . drop 11 pkgbuildFn = tmpDir "PKGBUILD" pkgbuildPatch = apPkgbuildPatch ap in do liftIO $ copyPatches tmpDir ap liftIO $ writeFile pkgbuildFn (show $ pretty ap) maybe (return ()) (\ pfn -> (applyPatch pkgbuildFn pfn) >> return ()) pkgbuildPatch (ec, out, er) <- liftIO $ withWorkingDirectory tmpDir (readProcessWithExitCode "makepkg" ["-g"] "") case ec of ExitFailure _ -> do -- hPutStrLn stderr er throwError $ "makepkg: error while calculating the source hashes for " ++ (apHkgName ap) ExitSuccess -> do if "sha256sums=(" `isPrefixOf` out then return ap { apShSha256Sums = shVarNewValue (apShSha256Sums ap) (ShArray $ hashes out) } else return ap