packages feed

hackport-0.2.10: cabal-install-0.9.5_rc20101226/Distribution/Client/Fetch.hs

-----------------------------------------------------------------------------
-- |
-- Module      :  Distribution.Client.Fetch
-- Copyright   :  (c) David Himmelstrup 2005
-- License     :  BSD-like
--
-- Maintainer  :  lemmih@gmail.com
-- Stability   :  provisional
-- Portability :  portable
--
--
-----------------------------------------------------------------------------
module Distribution.Client.Fetch (

    -- * Commands
    fetch,

    -- * Utilities
    fetchPackage,
    isFetched,
    downloadIndex,
  ) where

import Distribution.Client.Types
         ( UnresolvedDependency (..), AvailablePackage(..)
         , AvailablePackageSource(..), AvailablePackageDb(..)
         , Repo(..), RemoteRepo(..), LocalRepo(..)
         , InstalledPackage )
import Distribution.Client.PackageIndex (PackageIndex)
import Distribution.Client.Dependency as Dependency
         ( resolveDependenciesWithProgress
         , resolveAvailablePackages
         , dependencyConstraints, dependencyTargets
         , PackagesPreference(..), PackagesPreferenceDefault(..)
         , PackagePreference(..) )
import Distribution.Client.Dependency.Types
         ( foldProgress )
import Distribution.Client.IndexUtils as IndexUtils
         ( getAvailablePackages, disambiguateDependencies
         , getInstalledPackages )
import qualified Distribution.Client.InstallPlan as InstallPlan
import Distribution.Client.HttpUtils
         ( downloadURI, isOldHackageURI )
import Distribution.Client.Setup
         ( FetchFlags(..) )

import Distribution.Package
         ( PackageIdentifier, packageId, packageName, packageVersion
         , Dependency(..) )
import qualified Distribution.Client.PackageIndex as PackageIndex
import Distribution.Simple.Compiler
         ( Compiler(compilerId), PackageDBStack )
import Distribution.Simple.Program
         ( ProgramConfiguration )
import Distribution.Simple.Setup
         ( fromFlag )
import Distribution.Simple.Utils
         ( die, notice, info, debug, setupMessage )
import Distribution.System
         ( buildPlatform )
import Distribution.Text
         ( display )
import Distribution.Verbosity
         ( Verbosity )

import qualified Data.Map as Map
import Control.Monad
         ( when, filterM )
import System.Directory
         ( doesFileExist, createDirectoryIfMissing )
import System.FilePath
         ( (</>), (<.>) )
import qualified System.FilePath.Posix as FilePath.Posix
         ( combine, joinPath )
import Network.URI
         ( URI(uriPath) )


-- Downloads a package to [config-dir/packages/package-id] and returns the path to the package.
downloadPackage :: Verbosity -> Repo -> PackageIdentifier -> IO String
downloadPackage _ repo@Repo{ repoKind = Right LocalRepo } pkgid =
  return (packageFile repo pkgid)

downloadPackage verbosity repo@Repo{ repoKind = Left remoteRepo } pkgid = do
  let uri  = packageURI remoteRepo pkgid
      dir  = packageDir       repo pkgid
      path = packageFile      repo pkgid
  debug verbosity $ "GET " ++ show uri
  createDirectoryIfMissing True dir
  downloadURI verbosity uri path
  return path

-- Downloads an index file to [config-dir/packages/serv-id].
downloadIndex :: Verbosity -> RemoteRepo -> FilePath -> IO FilePath
downloadIndex verbosity repo cacheDir = do
  let uri = (remoteRepoURI repo) {
              uriPath = uriPath (remoteRepoURI repo)
                          `FilePath.Posix.combine` "00-index.tar.gz"
            }
      path = cacheDir </> "00-index" <.> "tar.gz"
  createDirectoryIfMissing True cacheDir
  downloadURI verbosity uri path
  return path

-- |Returns @True@ if the package has already been fetched.
isFetched :: AvailablePackage -> IO Bool
isFetched (AvailablePackage pkgid _ source) = case source of
  LocalUnpackedPackage _  -> return True
  LocalTarballPackage  _  -> return True
  RemoteTarballPackage _  -> return False --TODO: ad-hoc download caching
  RepoTarballPackage repo -> doesFileExist (packageFile repo pkgid)

-- |Fetch a package if we don't have it already.
fetchPackage :: Verbosity -> Repo -> PackageIdentifier -> IO String
fetchPackage verbosity repo pkgid = do
  fetched <- doesFileExist (packageFile repo pkgid)
  if fetched
    then do info verbosity $ display pkgid ++ " has already been downloaded."
            return (packageFile repo pkgid)
    else do setupMessage verbosity "Downloading" pkgid
            downloadPackage verbosity repo pkgid

-- |Fetch a list of packages and their dependencies.
fetch :: Verbosity
      -> PackageDBStack
      -> [Repo]
      -> Compiler
      -> ProgramConfiguration
      -> FetchFlags
      -> [UnresolvedDependency]
      -> IO ()
fetch verbosity _ _ _ _ _ [] =
  notice verbosity "No packages requested. Nothing to do."

fetch verbosity packageDBs repos comp conf flags deps = do

  installed <- getInstalledPackages verbosity comp packageDBs conf
  availableDb@(AvailablePackageDb available _)
        <- getAvailablePackages verbosity repos
  deps' <- IndexUtils.disambiguateDependencies available deps

  pkgs <- resolvePackages verbosity
            includeDeps comp
            installed availableDb deps'

  pkgs' <- filterM (fmap not . isFetched) pkgs
  when (null pkgs') $
    notice verbosity $ "No packages need to be fetched. "
                    ++ "All the requested packages are already cached."
  if dryRun
    then notice verbosity $ unlines $
            "The following packages would be fetched:"
          : map (display . packageId) pkgs'
    else sequence_
           [ fetchPackage verbosity repo pkgid
           | (AvailablePackage pkgid _ (RepoTarballPackage repo)) <- pkgs' ]
  where
    includeDeps = fromFlag (fetchDeps flags)
    dryRun      = fromFlag (fetchDryRun flags)


resolvePackages
  :: Verbosity
  -> Bool
  -> Compiler
  -> PackageIndex InstalledPackage
  -> AvailablePackageDb
  -> [UnresolvedDependency]
  -> IO [AvailablePackage]
resolvePackages verbosity includeDependencies comp
  installed (AvailablePackageDb available availablePrefs) deps

  | includeDependencies = do

      notice verbosity "Resolving dependencies..."
      plan <- foldProgress logMsg die return $
                resolveDependenciesWithProgress
                  buildPlatform (compilerId comp)
                  installed' available
                  preferences constraints
                  targets
      --TODO: suggest using --no-deps, unpack or fetch -o
      -- if cannot satisfy deps
      --TODO: add commandline constraint and preference args for fetch

      return (selectPackagesToFetch plan)

  | otherwise = do

    either (die . unlines . map show) return $
      resolveAvailablePackages
        installed   available
        preferences constraints
        targets

  where
    targets     = dependencyTargets     deps
    constraints = dependencyConstraints deps
    preferences = PackagesPreference
                    PreferLatestForSelected
                    [ PackageVersionPreference name ver
                    | (name, ver) <- Map.toList availablePrefs ]

    installed'  = hideGivenDeps deps installed

    -- Hide the packages given on the command line so that the dep resolver
    -- will decide that they need fetching, even if they're already
    -- installed. Sicne we want to get the source packages of things we might
    -- have installed (but not have the sources for).

    -- TODO: to allow for preferences on selecting an available version
    -- corresponding to a package we've got installed, instead of hiding the
    -- installed instances, we should add a constraint on using an installed
    -- instance.
    hideGivenDeps pkgs index =
      foldr PackageIndex.deletePackageName index
        [ name | UnresolvedDependency (Dependency name _) _ <- pkgs ]

    -- The packages we want to fetch are those packages the 'InstallPlan' that
    -- are in the 'InstallPlan.Configured' state.
    selectPackagesToFetch :: InstallPlan.InstallPlan -> [AvailablePackage]
    selectPackagesToFetch plan =
      [ pkg | (InstallPlan.Configured (InstallPlan.ConfiguredPackage pkg _ _))
                 <- InstallPlan.toList plan ]

    logMsg message rest = info verbosity message >> rest


-- |Generate the full path to the locally cached copy of
-- the tarball for a given @PackageIdentifer@.
packageFile :: Repo -> PackageIdentifier -> FilePath
packageFile repo pkgid = packageDir repo pkgid
                     </> display pkgid
                     <.> "tar.gz"

-- |Generate the full path to the directory where the local cached copy of
-- the tarball for a given @PackageIdentifer@ is stored.
packageDir :: Repo -> PackageIdentifier -> FilePath
packageDir repo pkgid = repoLocalDir repo
                    </> display (packageName    pkgid)
                    </> display (packageVersion pkgid)

-- | Generate the URI of the tarball for a given package.
packageURI :: RemoteRepo -> PackageIdentifier -> URI
packageURI repo pkgid | isOldHackageURI (remoteRepoURI repo) =
  (remoteRepoURI repo) {
    uriPath = FilePath.Posix.joinPath
      [uriPath (remoteRepoURI repo)
      ,display (packageName    pkgid)
      ,display (packageVersion pkgid)
      ,display pkgid <.> "tar.gz"]
  }
packageURI repo pkgid =
  (remoteRepoURI repo) {
    uriPath = FilePath.Posix.joinPath
      [uriPath (remoteRepoURI repo)
      ,"package"
      ,display pkgid <.> "tar.gz"]
  }