packages feed

cabal-install-0.4.0: Hackage/Install.hs

-----------------------------------------------------------------------------
-- |
-- Module      :  Hackage.Install
-- Copyright   :  (c) David Himmelstrup 2005
-- License     :  BSD-like
--
-- Maintainer  :  lemmih@gmail.com
-- Stability   :  provisional
-- Portability :  portable
--
-- High level interface to package installation.
-----------------------------------------------------------------------------
module Hackage.Install
    ( install
    ) where

import Control.Exception (bracket_)
import Control.Monad (when)
import System.Directory (getTemporaryDirectory, createDirectoryIfMissing
                        ,removeDirectoryRecursive, doesFileExist)
import System.FilePath ((</>),(<.>))

import Text.Printf (printf)


import Hackage.Config (message)
import Hackage.Dependency (resolveDependencies, resolveDependenciesLocal, packagesToInstall)
import Hackage.Fetch (fetchPackage)
import Hackage.Tar (extractTarGzFile)
import Hackage.Types (ConfigFlags(..), UnresolvedDependency(..)
                     , PkgInfo(..))
import Hackage.Utils

import Distribution.Simple.Compiler (Compiler(..))
import Distribution.Simple.InstallDirs (InstallDirs(..), absoluteInstallDirs)
import Distribution.Simple.Program (ProgramConfiguration)
import Distribution.Simple.SetupWrapper (setupWrapper)
import Distribution.Simple.Setup (CopyDest(..))
import Distribution.Simple.Utils (defaultPackageDesc)
import Distribution.Package (showPackageId, PackageIdentifier(..))
import Distribution.PackageDescription (packageDescription, readPackageDescription, package)
import Distribution.Verbosity




-- |Installs the packages needed to satisfy a list of dependencies.
install :: ConfigFlags -> Compiler -> ProgramConfiguration -> [String] -> [UnresolvedDependency] -> IO ()
install cfg comp conf globalArgs deps
    | null deps = installLocalPackage cfg comp conf globalArgs
    | otherwise = installRepoPackages cfg comp conf globalArgs deps

-- | Install the unpacked package in the current directory, and all its dependencies.
installLocalPackage :: ConfigFlags -> Compiler -> ProgramConfiguration -> [String] -> IO ()
installLocalPackage cfg comp conf globalArgs =
   do cabalFile <- defaultPackageDesc (configVerbose cfg)
      desc <- readPackageDescription (configVerbose cfg) cabalFile
      resolvedDeps <- resolveDependenciesLocal cfg comp conf desc globalArgs
      case packagesToInstall resolvedDeps of
        Left missing -> fail $ "Unresolved dependencies: " ++ showDependencies missing
        Right pkgs   -> installPackages cfg comp globalArgs pkgs
      let pkgId = package (packageDescription desc)
      installUnpackedPkg cfg comp globalArgs pkgId [] Nothing

installRepoPackages :: ConfigFlags -> Compiler -> ProgramConfiguration -> [String] -> [UnresolvedDependency] -> IO ()
installRepoPackages cfg comp conf globalArgs deps =
    do resolvedDeps <- resolveDependencies cfg comp conf deps
       case packagesToInstall resolvedDeps of
         Left missing -> fail $ "Unresolved dependencies: " ++ showDependencies missing
         Right []     -> message cfg normal "All requested packages already installed. Nothing to do."
         Right pkgs   -> installPackages cfg comp globalArgs pkgs

-- Attach the correct prefix flag to configure commands,
-- correct --user flag to install commands and no options to other commands.
mkPkgOps :: ConfigFlags -> Compiler -> PackageIdentifier -> String -> [String] -> [String]
mkPkgOps cfg comp pkgId cmd ops = verbosity ++
  case cmd of
    "configure" -> user ++ hcPath ++ hcPkgPath ++ installDirFlags installDirs ++ ops
    "install"   -> user
    _ -> []
 where verbosity = ["-v" ++ showForCabal (configVerbose cfg)]
       user = if configUserInstall cfg then ["--user"] else []
       hcPath    = maybe [] (\path -> ["--with-compiler=" ++ path]) (configCompilerPath cfg)
       hcPkgPath = maybe [] (\path -> ["--with-hc-pkg="   ++ path]) (configHcPkgPath    cfg)
       installDirTemplates | configUserInstall cfg = configUserInstallDirs cfg
                           | otherwise             = configGlobalInstallDirs cfg
       installDirs = absoluteInstallDirs pkgId (compilerId comp) NoCopyDest installDirTemplates

installDirFlags :: InstallDirs FilePath -> [String]
installDirFlags dirs =
    [flag "prefix" prefix,
     flag "bindir" bindir,
     flag "libdir" libdir,
--     flag "dynlibdir" dynlibdir, -- not accepted as argument by cabal?
     flag "libexecdir" libexecdir,
--     flag "progdir" progdir, -- not accepted as argument by cabal?
--     flag "includedir" includedir, -- not accepted as argument by cabal?
     flag "datadir" datadir,
     flag "docdir" docdir,
     flag "htmldir" htmldir]
  where flag s f = "--" ++ s ++ "=" ++ f dirs

installPackages :: ConfigFlags
                -> Compiler
                -> [String] -- ^Options which will be parse to every package.
                -> [(PkgInfo,[String])] -- ^ (Package, list of configure options)
                -> IO ()
installPackages cfg comp globalArgs pkgs =
    mapM_ (installPkg cfg comp globalArgs) pkgs


{-|
  Download, build and install a given package with some given flags.

  The process is divided up in a few steps:

    * The package is downloaded to {config-dir}\/packages\/{pkg-id} (if not already there).

    * The fetched tarball is then moved to a temporary directory (\/tmp on linux) and unpacked.

    * setupWrapper (equivalent to cabal-setup) is called with the options
      \'configure\' and the user specified options, \'--user\'
      if the 'configUser' flag is @True@ and install directory flags depending on 
      @configUserInstallDirs@ or @configGlobalInstallDirs@.

    * setupWrapper \'build\' is called with no options.

    * setupWrapper \'install\' is called with the \'--user\' flag if 'configUserInstall' is @True@.

    * The installation finishes by deleting the unpacked tarball.
-} 
installPkg :: ConfigFlags
           -> Compiler
           -> [String] -- ^Options which will be parse to every package.
           -> (PkgInfo,[String]) -- ^(Package, list of configure options)
           -> IO ()
installPkg cfg comp globalArgs (pkg,opts)
    = do pkgPath <- fetchPackage cfg pkg
         tmp <- getTemporaryDirectory
         let p = pkgInfoId pkg
             tmpDirPath = tmp </> printf "TMP%sTMP" (showPackageId p)
             path = tmpDirPath </> showPackageId p
         bracket_ (createDirectoryIfMissing True tmpDirPath)
                  (removeDirectoryRecursive tmpDirPath)
                  (do message cfg verbose (printf "Extracting %s to %s..." pkgPath tmpDirPath)
                      extractTarGzFile (Just tmpDirPath) pkgPath
                      let descFilePath = tmpDirPath </> showPackageId p </> pkgName p <.> "cabal"
                      e <- doesFileExist descFilePath
                      when (not e) $ fail $ "Package .cabal file not found: " ++ show descFilePath
                      installUnpackedPkg cfg comp globalArgs p opts (Just path)
                      return ())

installUnpackedPkg :: ConfigFlags -> Compiler 
                   -> [String] -- ^ Arguments for all packages
                   -> PackageIdentifier
                   -> [String] -- ^ Arguments for this package
                   -> Maybe FilePath -- ^ Directory to change to before starting the installation.
                   -> IO ()
installUnpackedPkg cfg comp globalArgs pkgId opts mpath
    = do setup "configure"
         setup "build"
         setup "install"
  where
    setup cmd 
        = do let cmdOps = mkPkgOps cfg comp pkgId cmd (globalArgs++opts)
             message cfg verbose $ 
                     unwords ["setupWrapper", show (cmd:cmdOps), show mpath]
             setupWrapper (cmd:cmdOps) mpath