packages feed

cabal-rpm-0.8.11: src/PackageUtils.hs

-- |
-- Module      :  PackageUtils
-- Copyright   :  (C) 2013-2014  Jens Petersen
--
-- Maintainer  :  Jens Petersen <petersen@fedoraproject.org>
-- Stability   :  alpha
-- Portability :  portable
--
-- Explanation: functions related to Cabal dependency generation.

-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.

module PackageUtils (
  isScmDir,
  missingPackages,
  notInstalled,
  packageName,
  packageVersion,
  simplePackageDescription
    ) where

import Dependencies (packageDependencies)
import FileUtils (fileWithExtension, fileWithExtension_,
                  getDirectoryContents_, mktempdir)
import Setup (RpmFlags (..))
import SysCmd (runSystem, tryReadProcess, systemBool, (+-+))

import Control.Applicative ((<$>))
import Control.Monad    (filterM, liftM)

import Data.Char (isAlphaNum)
import Data.List (isSuffixOf, stripPrefix)
import Data.Maybe (fromMaybe)
import Data.Version     (showVersion)

import Distribution.Compiler (CompilerFlavor (..))
import Distribution.Package  (PackageIdentifier (..),
                              PackageName (..))
import Distribution.PackageDescription (PackageDescription (..))
import Distribution.PackageDescription.Configuration (finalizePackageDescription)
import Distribution.PackageDescription.Parse (readPackageDescription)

import Distribution.Simple.Compiler (Compiler (..))
import Distribution.Simple.Configure (configCompiler)
import Distribution.Simple.Program   (defaultProgramConfiguration)
import Distribution.Simple.Utils (die, findPackageDesc)

import Distribution.System (Platform (..), buildArch, buildOS)
import Distribution.Verbosity (Verbosity)

import System.Directory (doesDirectoryExist, doesFileExist,
                         getCurrentDirectory, setCurrentDirectory)
import System.FilePath ((</>), takeExtension)


-- returns path to .cabal file and possibly tmpdir to be removed
findCabalFile :: Verbosity -> FilePath -> IO (FilePath, Maybe FilePath)
findCabalFile vb path = do
  isdir <- doesDirectoryExist path
  if isdir
    then do
      cblfile <- fileWithExtension_ path ".cabal"
      if cblfile
        then do
          file <- findPackageDesc path
          return (file, Nothing)
        else do
          spcfile <- fileWithExtension path ".spec"
          maybe (die "Cannot determine package in dir.")
            (cabalFromSpec vb) spcfile
    else do
      isfile <- doesFileExist path
      if not isfile
        then if isPackageId path
             then tryUnpack path
             else error $ path ++ ": No such file or directory"
        else if takeExtension path == ".cabal"
             then return (path, Nothing)
             else if takeExtension path == ".spec"
                  then cabalFromSpec vb path
                  else if ".tar.gz" `isSuffixOf` path
                       then do
                         tmpdir <- mktempdir
                         runSystem $ "tar zxf " ++ path ++ " -C " ++ tmpdir ++ " *.cabal"
                         subdir <- getDirectoryContents_ tmpdir
                         file <- findPackageDesc $ tmpdir ++ "/" ++ head subdir
                         return (file, Just tmpdir)
                       else error $ path ++ ": file should be a .cabal, .spec or .tar.gz file."
  where
    isPackageId :: String -> Bool
    isPackageId (c:cs) | isAlphaNum c =
      all (\d -> isAlphaNum  d || d `elem` "-." ) cs
    isPackageId _ = False

simplePackageDescription :: FilePath -> RpmFlags
                         -> IO (FilePath, PackageDescription, Maybe FilePath)
simplePackageDescription path opts = do
  let verbose = rpmVerbosity opts
  (cabalPath, mtmp) <- findCabalFile verbose path
  genPkgDesc <- readPackageDescription verbose cabalPath
  (compiler, _) <- configCompiler (Just GHC) Nothing Nothing
                   defaultProgramConfiguration verbose
  case finalizePackageDescription (rpmConfigurationsFlags opts)
       (const True) (Platform buildArch buildOS) (compilerId compiler)
       [] genPkgDesc of
    Left e -> die $ "finalize failed: " ++ show e
    Right (pd, _) -> return (cabalPath, pd, mtmp)

cabalFromSpec :: Verbosity -> FilePath -> IO (FilePath, Maybe FilePath)
cabalFromSpec vrb spcfile = do
  -- no rpmspec command in RHEL 5 and 6
  namever <- removePrefix "ghc-" <$> tryReadProcess "rpm" ["-q", "--qf", "%{name}-%{version}\n", "--specfile", spcfile]
  findCabalFile vrb (head $ lines namever)
  where
    removePrefix :: String -> String-> String
    removePrefix pref str =
      fromMaybe str (stripPrefix pref str)

tryUnpack :: String -> IO (FilePath, Maybe FilePath)
tryUnpack pkg = do
  pkgver <- if '.' `elem` pkg then return pkg
            else do
              contains_pkg <- tryReadProcess "cabal" ["list", "--simple-output", pkg]
              let pkgs = filter ((== pkg) . takeWhile (not . (== ' '))) $ lines contains_pkg
              if null pkgs
                then error $ pkg ++ " hackage not found"
                else return $ map (\c -> if c == ' ' then '-' else c) $ last pkgs
  isdir <- doesDirectoryExist pkgver
  if isdir
    then do
    pth <-findPackageDesc pkgver
    return (pth, Nothing)
    else do
    cwd <- getCurrentDirectory
    tmpdir <- mktempdir
    setCurrentDirectory tmpdir
    runSystem $ "cabal unpack -v0 " ++ pkgver
    pth <- findPackageDesc pkgver
    setCurrentDirectory cwd
    return (tmpdir ++ "/" ++ pth, Just tmpdir)

packageName :: PackageIdentifier -> String
packageName pkg = name
  where PackageName name = pkgName pkg

packageVersion :: PackageIdentifier -> String
packageVersion = showVersion . pkgVersion

(<||>) :: IO Bool -> IO Bool -> IO Bool
(<||>) f s = do
  one <- f
  if one then return True else s

isScmDir :: FilePath -> IO Bool
isScmDir dir =
  doesDirectoryExist (dir </> ".git") <||> doesDirectoryExist (dir </> "_darcs")

notInstalled :: String -> IO Bool
notInstalled br =
  liftM not $ systemBool $ "rpm -q --whatprovides" +-+ shellQuote br
  where
    shellQuote :: String -> String
    shellQuote (c:cs) = (if c `elem` "()" then (['\\', c] ++) else (c:)) (shellQuote cs)
    shellQuote "" = ""

missingPackages :: PackageDescription -> String -> IO [String]
missingPackages pkgDesc name = do
  (deps, tools, clibs, pkgcfgs, _) <- packageDependencies pkgDesc name
  filterM notInstalled $ deps ++ ["ghc-Cabal-devel", "ghc-rpm-macros"] ++ tools ++ clibs ++ pkgcfgs