cabal-install-3.0.0.0: Distribution/Client/Outdated.hs
{-# LANGUAGE CPP #-}
-----------------------------------------------------------------------------
-- |
-- Module : Distribution.Client.Outdated
-- Maintainer : cabal-devel@haskell.org
-- Portability : portable
--
-- Implementation of the 'outdated' command. Checks for outdated
-- dependencies in the package description file or freeze file.
-----------------------------------------------------------------------------
module Distribution.Client.Outdated ( outdated
, ListOutdatedSettings(..), listOutdated )
where
import Prelude ()
import Distribution.Client.Config
import Distribution.Client.IndexUtils as IndexUtils
import Distribution.Client.Compat.Prelude
import Distribution.Client.ProjectConfig
import Distribution.Client.DistDirLayout
import Distribution.Client.RebuildMonad
import Distribution.Client.Setup hiding (quiet)
import Distribution.Client.Targets
import Distribution.Client.Types
import Distribution.Solver.Types.PackageConstraint
import Distribution.Solver.Types.PackageIndex
import Distribution.Client.Sandbox.PackageEnvironment
import Distribution.Package (PackageName, packageVersion)
import Distribution.PackageDescription (allBuildDepends)
import Distribution.PackageDescription.Configuration (finalizePD)
import Distribution.Simple.Compiler (Compiler, compilerInfo)
import Distribution.Simple.Setup
(fromFlagOrDefault, flagToMaybe)
import Distribution.Simple.Utils
(die', notice, debug, tryFindPackageDesc)
import Distribution.System (Platform)
import Distribution.Deprecated.Text (display)
import Distribution.Types.ComponentRequestedSpec
(ComponentRequestedSpec(..))
import Distribution.Types.Dependency
(Dependency(..), depPkgName, simplifyDependency)
import Distribution.Verbosity (Verbosity, silent)
import Distribution.Version
(Version, VersionRange, LowerBound(..), UpperBound(..)
,asVersionIntervals, majorBoundVersion)
import Distribution.PackageDescription.Parsec
(readGenericPackageDescription)
import qualified Data.Set as S
import System.Directory (getCurrentDirectory)
import System.Exit (exitFailure)
import Control.Exception (throwIO)
-- | Entry point for the 'outdated' command.
outdated :: Verbosity -> OutdatedFlags -> RepoContext
-> Compiler -> Platform
-> IO ()
outdated verbosity0 outdatedFlags repoContext comp platform = do
let freezeFile = fromFlagOrDefault False (outdatedFreezeFile outdatedFlags)
newFreezeFile = fromFlagOrDefault False
(outdatedNewFreezeFile outdatedFlags)
mprojectFile = flagToMaybe
(outdatedProjectFile outdatedFlags)
simpleOutput = fromFlagOrDefault False
(outdatedSimpleOutput outdatedFlags)
quiet = fromFlagOrDefault False (outdatedQuiet outdatedFlags)
exitCode = fromFlagOrDefault quiet (outdatedExitCode outdatedFlags)
ignorePred = let ignoreSet = S.fromList (outdatedIgnore outdatedFlags)
in \pkgname -> pkgname `S.member` ignoreSet
minorPred = case outdatedMinor outdatedFlags of
Nothing -> const False
Just IgnoreMajorVersionBumpsNone -> const False
Just IgnoreMajorVersionBumpsAll -> const True
Just (IgnoreMajorVersionBumpsSome pkgs) ->
let minorSet = S.fromList pkgs
in \pkgname -> pkgname `S.member` minorSet
verbosity = if quiet then silent else verbosity0
when (not newFreezeFile && isJust mprojectFile) $
die' verbosity $
"--project-file must only be used with --v2-freeze-file."
sourcePkgDb <- IndexUtils.getSourcePackages verbosity repoContext
let pkgIndex = packageIndex sourcePkgDb
deps <- if freezeFile
then depsFromFreezeFile verbosity
else if newFreezeFile
then depsFromNewFreezeFile verbosity mprojectFile
else depsFromPkgDesc verbosity comp platform
debug verbosity $ "Dependencies loaded: "
++ (intercalate ", " $ map display deps)
let outdatedDeps = listOutdated deps pkgIndex
(ListOutdatedSettings ignorePred minorPred)
when (not quiet) $
showResult verbosity outdatedDeps simpleOutput
if (exitCode && (not . null $ outdatedDeps))
then exitFailure
else return ()
-- | Print either the list of all outdated dependencies, or a message
-- that there are none.
showResult :: Verbosity -> [(Dependency,Version)] -> Bool -> IO ()
showResult verbosity outdatedDeps simpleOutput =
if (not . null $ outdatedDeps)
then
do when (not simpleOutput) $
notice verbosity "Outdated dependencies:"
for_ outdatedDeps $ \(d@(Dependency pn _ _), v) ->
let outdatedDep = if simpleOutput then display pn
else display d ++ " (latest: " ++ display v ++ ")"
in notice verbosity outdatedDep
else notice verbosity "All dependencies are up to date."
-- | Convert a list of 'UserConstraint's to a 'Dependency' list.
userConstraintsToDependencies :: [UserConstraint] -> [Dependency]
userConstraintsToDependencies ucnstrs =
mapMaybe (packageConstraintToDependency . userToPackageConstraint) ucnstrs
-- | Read the list of dependencies from the freeze file.
depsFromFreezeFile :: Verbosity -> IO [Dependency]
depsFromFreezeFile verbosity = do
cwd <- getCurrentDirectory
userConfig <- loadUserConfig verbosity cwd Nothing
let ucnstrs = map fst . configExConstraints . savedConfigureExFlags $
userConfig
deps = userConstraintsToDependencies ucnstrs
debug verbosity "Reading the list of dependencies from the freeze file"
return deps
-- | Read the list of dependencies from the new-style freeze file.
depsFromNewFreezeFile :: Verbosity -> Maybe FilePath -> IO [Dependency]
depsFromNewFreezeFile verbosity mprojectFile = do
projectRoot <- either throwIO return =<<
findProjectRoot Nothing mprojectFile
let distDirLayout = defaultDistDirLayout projectRoot
{- TODO: Support dist dir override -} Nothing
projectConfig <- runRebuild (distProjectRootDirectory distDirLayout) $
readProjectLocalFreezeConfig verbosity distDirLayout
let ucnstrs = map fst . projectConfigConstraints . projectConfigShared
$ projectConfig
deps = userConstraintsToDependencies ucnstrs
debug verbosity $
"Reading the list of dependencies from the new-style freeze file " ++ distProjectFile distDirLayout "freeze"
return deps
-- | Read the list of dependencies from the package description.
depsFromPkgDesc :: Verbosity -> Compiler -> Platform -> IO [Dependency]
depsFromPkgDesc verbosity comp platform = do
cwd <- getCurrentDirectory
path <- tryFindPackageDesc verbosity cwd
gpd <- readGenericPackageDescription verbosity path
let cinfo = compilerInfo comp
epd = finalizePD mempty (ComponentRequestedSpec True True)
(const True) platform cinfo [] gpd
case epd of
Left _ -> die' verbosity "finalizePD failed"
Right (pd, _) -> do
let bd = allBuildDepends pd
debug verbosity
"Reading the list of dependencies from the package description"
return bd
-- | Various knobs for customising the behaviour of 'listOutdated'.
data ListOutdatedSettings = ListOutdatedSettings {
-- | Should this package be ignored?
listOutdatedIgnorePred :: PackageName -> Bool,
-- | Should major version bumps should be ignored for this package?
listOutdatedMinorPred :: PackageName -> Bool
}
-- | Find all outdated dependencies.
listOutdated :: [Dependency]
-> PackageIndex UnresolvedSourcePackage
-> ListOutdatedSettings
-> [(Dependency, Version)]
listOutdated deps pkgIndex (ListOutdatedSettings ignorePred minorPred) =
mapMaybe isOutdated $ map simplifyDependency deps
where
isOutdated :: Dependency -> Maybe (Dependency, Version)
isOutdated dep@(Dependency pname vr _)
| ignorePred (depPkgName dep) = Nothing
| otherwise =
let this = map packageVersion $ lookupDependency pkgIndex pname vr
latest = lookupLatest dep
in (\v -> (dep, v)) `fmap` isOutdated' this latest
isOutdated' :: [Version] -> [Version] -> Maybe Version
isOutdated' [] _ = Nothing
isOutdated' _ [] = Nothing
isOutdated' this latest =
let this' = maximum this
latest' = maximum latest
in if this' < latest' then Just latest' else Nothing
lookupLatest :: Dependency -> [Version]
lookupLatest dep@(Dependency pname vr _)
| minorPred (depPkgName dep) =
map packageVersion $ lookupDependency pkgIndex pname (relaxMinor vr)
| otherwise =
map packageVersion $ lookupPackageName pkgIndex (depPkgName dep)
relaxMinor :: VersionRange -> VersionRange
relaxMinor vr =
let vis = asVersionIntervals vr
(LowerBound v0 _,upper) = last vis
in case upper of
NoUpperBound -> vr
UpperBound _v1 _ -> majorBoundVersion v0