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"]
}