2 {-# LANGUAGE NamedFieldPuns #-}
3 {-# LANGUAGE RecordWildCards #-}
5 -----------------------------------------------------------------------------
7 -----------------------------------------------------------------------------
10 -- Module : Distribution.Client.CmdOutdated
11 -- Maintainer : cabal-devel@haskell.org
12 -- Portability : portable
14 -- Implementation of the 'outdated' command. Checks for outdated
15 -- dependencies in the package description file or freeze file.
16 module Distribution
.Client
.CmdOutdated
19 , ListOutdatedSettings
(..)
24 import Distribution
.Client
.Compat
.Prelude
25 import Distribution
.Compat
.Lens
31 import Distribution
.Client
.Config
33 ( savedConfigureExFlags
38 import Distribution
.Client
.DistDirLayout
39 ( DistDirLayout
(distProjectFile
, distProjectRootDirectory
)
40 , defaultDistDirLayout
42 import Distribution
.Client
.IndexUtils
as IndexUtils
43 import Distribution
.Client
.ProjectConfig
44 import Distribution
.Client
.ProjectConfig
.Legacy
45 ( instantiateProjectConfigSkeletonWithCompiler
47 import Distribution
.Client
.ProjectFlags
51 , removeIgnoreProjectOption
53 import Distribution
.Client
.RebuildMonad
56 import Distribution
.Client
.Sandbox
57 ( loadConfigOrSandboxConfig
59 import Distribution
.Client
.Sandbox
.PackageEnvironment
62 import Distribution
.Client
.Setup
63 import Distribution
.Client
.Targets
65 , userToPackageConstraint
67 import Distribution
.Client
.Types
.SourcePackageDb
as SourcePackageDb
68 import Distribution
.Solver
.Types
.PackageConstraint
69 ( packageConstraintToDependency
71 import Distribution
.Utils
.Generic
76 import Distribution
.Client
.HttpUtils
77 import qualified Distribution
.Compat
.CharParsing
as P
78 import Distribution
.Package
82 import Distribution
.PackageDescription
85 import Distribution
.PackageDescription
.Configuration
88 import Distribution
.ReadE
91 import Distribution
.Simple
.Command
100 import Distribution
.Simple
.Compiler
104 import Distribution
.Simple
.Flag
110 import Distribution
.Simple
.PackageDescription
111 ( readGenericPackageDescription
113 import Distribution
.Simple
.Setup
117 import Distribution
.Simple
.Utils
123 import Distribution
.System
126 import Distribution
.Types
.ComponentRequestedSpec
127 ( ComponentRequestedSpec
(..)
129 import Distribution
.Types
.Dependency
132 import Distribution
.Types
.DependencySatisfaction
133 ( DependencySatisfaction
(..)
135 import Distribution
.Types
.PackageVersionConstraint
136 ( PackageVersionConstraint
(..)
137 , simplifyPackageVersionConstraint
139 import Distribution
.Utils
.NubList
142 import Distribution
.Verbosity
146 import Distribution
.Version
150 , VersionInterval
(..)
156 import qualified Data
.Set
as S
157 import Distribution
.Client
.Errors
158 import Distribution
.Utils
.Path
(relativeSymbolicPath
)
159 import System
.Directory
161 , getCurrentDirectory
164 -------------------------------------------------------------------------------
166 -------------------------------------------------------------------------------
168 outdatedCommand
:: CommandUI
(ProjectFlags
, OutdatedFlags
)
171 { commandName
= "outdated"
172 , commandSynopsis
= "Check for outdated dependencies."
173 , commandDescription
= Just
$ \_
->
175 "Checks for outdated dependencies in the package description file "
177 , commandNotes
= Nothing
178 , commandUsage
= \pname
->
179 "Usage: " ++ pname
++ " outdated [FLAGS] [PACKAGES]\n"
180 , commandDefaultFlags
= (defaultProjectFlags
, defaultOutdatedFlags
)
181 , commandOptions
= \showOrParseArgs
->
184 (removeIgnoreProjectOption
(projectFlagsOptions showOrParseArgs
))
185 ++ map (liftOptionL _2
) (outdatedOptions showOrParseArgs
)
188 -------------------------------------------------------------------------------
190 -------------------------------------------------------------------------------
192 data IgnoreMajorVersionBumps
193 = IgnoreMajorVersionBumpsNone
194 | IgnoreMajorVersionBumpsAll
195 | IgnoreMajorVersionBumpsSome
[PackageName
]
197 instance Monoid IgnoreMajorVersionBumps
where
198 mempty
= IgnoreMajorVersionBumpsNone
201 instance Semigroup IgnoreMajorVersionBumps
where
202 IgnoreMajorVersionBumpsNone
<> r
= r
203 l
@IgnoreMajorVersionBumpsAll
<> _
= l
204 l
@(IgnoreMajorVersionBumpsSome _
) <> IgnoreMajorVersionBumpsNone
= l
205 (IgnoreMajorVersionBumpsSome _
) <> r
@IgnoreMajorVersionBumpsAll
= r
206 (IgnoreMajorVersionBumpsSome a
) <> (IgnoreMajorVersionBumpsSome b
) =
207 IgnoreMajorVersionBumpsSome
(a
++ b
)
209 data OutdatedFlags
= OutdatedFlags
210 { outdatedVerbosity
:: Flag Verbosity
211 , outdatedFreezeFile
:: Flag
Bool
212 , outdatedNewFreezeFile
:: Flag
Bool
213 , outdatedSimpleOutput
:: Flag
Bool
214 , outdatedExitCode
:: Flag
Bool
215 , outdatedQuiet
:: Flag
Bool
216 , outdatedIgnore
:: [PackageName
]
217 , outdatedMinor
:: Maybe IgnoreMajorVersionBumps
220 defaultOutdatedFlags
:: OutdatedFlags
221 defaultOutdatedFlags
=
223 { outdatedVerbosity
= toFlag normal
224 , outdatedFreezeFile
= mempty
225 , outdatedNewFreezeFile
= mempty
226 , outdatedSimpleOutput
= mempty
227 , outdatedExitCode
= mempty
228 , outdatedQuiet
= mempty
229 , outdatedIgnore
= mempty
230 , outdatedMinor
= mempty
233 outdatedOptions
:: ShowOrParseArgs
-> [OptionField OutdatedFlags
]
234 outdatedOptions _showOrParseArgs
=
237 (\v flags
-> flags
{outdatedVerbosity
= v
})
240 ["freeze-file", "v1-freeze-file"]
241 "Act on the freeze file"
243 (\v flags
-> flags
{outdatedFreezeFile
= v
})
247 ["v2-freeze-file", "new-freeze-file"]
248 "Act on the new-style freeze file (default: cabal.project.freeze)"
249 outdatedNewFreezeFile
250 (\v flags
-> flags
{outdatedNewFreezeFile
= v
})
255 "Only print names of outdated dependencies, one per line"
257 (\v flags
-> flags
{outdatedSimpleOutput
= v
})
262 "Exit with non-zero when there are outdated dependencies"
264 (\v flags
-> flags
{outdatedExitCode
= v
})
269 "Don't print any output. Implies '--exit-code' and '-v0'"
271 (\v flags
-> flags
{outdatedQuiet
= v
})
278 (\v flags
-> flags
{outdatedIgnore
= v
})
279 (reqArg
"PKGS" pkgNameListParser
(map prettyShow
))
283 "Ignore major version bumps for these packages"
285 (\v flags
-> flags
{outdatedMinor
= v
})
288 ignoreMajorVersionBumpsParser
289 ("", Just IgnoreMajorVersionBumpsAll
)
290 ignoreMajorVersionBumpsPrinter
294 ignoreMajorVersionBumpsPrinter
295 :: Maybe IgnoreMajorVersionBumps
297 ignoreMajorVersionBumpsPrinter Nothing
= []
298 ignoreMajorVersionBumpsPrinter
(Just IgnoreMajorVersionBumpsNone
) = []
299 ignoreMajorVersionBumpsPrinter
(Just IgnoreMajorVersionBumpsAll
) = [Nothing
]
300 ignoreMajorVersionBumpsPrinter
(Just
(IgnoreMajorVersionBumpsSome pkgs
)) =
301 map (Just
. prettyShow
) pkgs
303 ignoreMajorVersionBumpsParser
=
304 (Just
. IgnoreMajorVersionBumpsSome
) `
fmap` pkgNameListParser
308 ("Couldn't parse the list of package names: " ++)
309 (fmap toList
(P
.sepByNonEmpty parsec
(P
.char
',')))
311 -------------------------------------------------------------------------------
313 -------------------------------------------------------------------------------
315 -- | Entry point for the 'outdated' command.
316 outdatedAction
:: (ProjectFlags
, OutdatedFlags
) -> [String] -> GlobalFlags
-> IO ()
317 outdatedAction
(ProjectFlags
{flagProjectDir
, flagProjectFile
}, OutdatedFlags
{..}) _targetStrings globalFlags
= do
318 config
<- loadConfigOrSandboxConfig verbosity globalFlags
319 let globalFlags
' = savedGlobalFlags config `mappend` globalFlags
320 configFlags
= savedConfigureFlags config
321 withRepoContext verbosity globalFlags
' $ \repoContext
-> do
322 when (not newFreezeFile
&& (isJust mprojectDir ||
isJust mprojectFile
)) $
323 dieWithException verbosity OutdatedAction
325 sourcePkgDb
<- IndexUtils
.getSourcePackages verbosity repoContext
326 (comp
, platform
, _progdb
) <- configCompilerAux
' configFlags
329 then depsFromFreezeFile verbosity
336 (fromNubList
. globalProgPathExtra
$ globalFlags
)
337 (flagToMaybe
. globalHttpTransport
$ globalFlags
)
338 depsFromNewFreezeFile verbosity httpTransport comp platform mprojectDir mprojectFile
340 depsFromPkgDesc verbosity comp platform
342 "Dependencies loaded: "
343 ++ intercalate
", " (map prettyShow deps
)
348 (ListOutdatedSettings ignorePred minorPred
)
350 showResult verbosity outdatedDeps simpleOutput
351 if exitCode
&& (not . null $ outdatedDeps
)
358 else fromFlagOrDefault normal outdatedVerbosity
359 freezeFile
= fromFlagOrDefault
False outdatedFreezeFile
360 newFreezeFile
= fromFlagOrDefault
False outdatedNewFreezeFile
361 mprojectDir
= flagToMaybe flagProjectDir
362 mprojectFile
= flagToMaybe flagProjectFile
363 simpleOutput
= fromFlagOrDefault
False outdatedSimpleOutput
364 quiet
= fromFlagOrDefault
False outdatedQuiet
365 exitCode
= fromFlagOrDefault quiet outdatedExitCode
367 let ignoreSet
= S
.fromList outdatedIgnore
368 in \pkgname
-> pkgname `S
.member` ignoreSet
369 minorPred
= case outdatedMinor
of
370 Nothing
-> const False
371 Just IgnoreMajorVersionBumpsNone
-> const False
372 Just IgnoreMajorVersionBumpsAll
-> const True
373 Just
(IgnoreMajorVersionBumpsSome pkgs
) ->
374 let minorSet
= S
.fromList pkgs
375 in \pkgname
-> pkgname `S
.member` minorSet
377 -- | Print either the list of all outdated dependencies, or a message
378 -- that there are none.
379 showResult
:: Verbosity
-> [(PackageVersionConstraint
, Version
)] -> Bool -> IO ()
380 showResult verbosity outdatedDeps simpleOutput
=
381 if not . null $ outdatedDeps
383 when (not simpleOutput
) $
384 notice verbosity
"Outdated dependencies:"
385 for_ outdatedDeps
$ \(d
@(PackageVersionConstraint pn _
), v
) ->
389 else prettyShow d
++ " (latest: " ++ prettyShow v
++ ")"
390 in notice verbosity outdatedDep
391 else notice verbosity
"All dependencies are up to date."
393 -- | Convert a list of 'UserConstraint's to a 'Dependency' list.
394 userConstraintsToDependencies
:: [UserConstraint
] -> [PackageVersionConstraint
]
395 userConstraintsToDependencies ucnstrs
=
396 mapMaybe (packageConstraintToDependency
. userToPackageConstraint
) ucnstrs
398 -- | Read the list of dependencies from the freeze file.
399 depsFromFreezeFile
:: Verbosity
-> IO [PackageVersionConstraint
]
400 depsFromFreezeFile verbosity
= do
401 cwd
<- getCurrentDirectory
402 userConfig
<- loadUserConfig verbosity cwd Nothing
404 map fst . configExConstraints
. savedConfigureExFlags
$
406 deps
= userConstraintsToDependencies ucnstrs
407 debug verbosity
"Reading the list of dependencies from the freeze file"
410 -- | Read the list of dependencies from the new-style freeze file.
411 depsFromNewFreezeFile
:: Verbosity
-> HttpTransport
-> Compiler
-> Platform
-> Maybe FilePath -> Maybe FilePath -> IO [PackageVersionConstraint
]
412 depsFromNewFreezeFile verbosity httpTransport compiler
(Platform arch os
) mprojectDir mprojectFile
= do
414 either throwIO
return
415 =<< findProjectRoot verbosity mprojectDir mprojectFile
419 {- TODO: Support dist dir override -} Nothing
421 projectConfig
<- runRebuild
(distProjectRootDirectory distDirLayout
) $ do
422 pcs
<- readProjectLocalFreezeConfig verbosity httpTransport distDirLayout
423 pure
$ instantiateProjectConfigSkeletonWithCompiler os arch
(compilerInfo compiler
) mempty pcs
425 map fst . projectConfigConstraints
. projectConfigShared
$
427 deps
= userConstraintsToDependencies ucnstrs
428 freezeFile
= distProjectFile distDirLayout
"freeze"
429 freezeFileExists
<- doesFileExist freezeFile
431 unless freezeFileExists
$
432 dieWithException verbosity
$
433 FreezeFileExistsErr freezeFile
436 "Reading the list of dependencies from the new-style freeze file " ++ freezeFile
439 -- | Read the list of dependencies from the package description.
440 depsFromPkgDesc
:: Verbosity
-> Compiler
-> Platform
-> IO [PackageVersionConstraint
]
441 depsFromPkgDesc verbosity comp platform
= do
442 path
<- tryFindPackageDesc verbosity Nothing
443 gpd
<- readGenericPackageDescription verbosity Nothing
(relativeSymbolicPath path
)
444 let cinfo
= compilerInfo comp
448 (ComponentRequestedSpec
True True)
455 Left _
-> dieWithException verbosity FinalizePDFailed
457 let bd
= allBuildDepends pd
460 "Reading the list of dependencies from the package description"
461 return $ map toPVC bd
463 toPVC
(Dependency pn vr _
) = PackageVersionConstraint pn vr
465 -- | Various knobs for customising the behaviour of 'listOutdated'.
466 data ListOutdatedSettings
= ListOutdatedSettings
467 { listOutdatedIgnorePred
:: PackageName
-> Bool
468 -- ^ Should this package be ignored?
469 , listOutdatedMinorPred
:: PackageName
-> Bool
470 -- ^ Should major version bumps be ignored for this package?
473 -- | Find all outdated dependencies.
475 :: [PackageVersionConstraint
]
477 -> ListOutdatedSettings
478 -> [(PackageVersionConstraint
, Version
)]
479 listOutdated deps sourceDb
(ListOutdatedSettings ignorePred minorPred
) =
480 mapMaybe isOutdated
$ map simplifyPackageVersionConstraint deps
482 isOutdated
:: PackageVersionConstraint
-> Maybe (PackageVersionConstraint
, Version
)
483 isOutdated dep
@(PackageVersionConstraint pname vr
)
484 | ignorePred pname
= Nothing
486 let this
= map packageVersion
$ SourcePackageDb
.lookupDependency sourceDb pname vr
487 latest
= lookupLatest dep
488 in (\v -> (dep
, v
)) `
fmap` isOutdated
' this latest
490 isOutdated
' :: [Version
] -> [Version
] -> Maybe Version
491 isOutdated
' [] _
= Nothing
492 isOutdated
' _
[] = Nothing
493 isOutdated
' this latest
=
494 let this
' = maximum this
495 latest
' = maximum latest
496 in if this
' < latest
' then Just latest
' else Nothing
498 lookupLatest
:: PackageVersionConstraint
-> [Version
]
499 lookupLatest
(PackageVersionConstraint pname vr
)
501 map packageVersion
$ SourcePackageDb
.lookupDependency sourceDb pname
(relaxMinor vr
)
503 map packageVersion
$ SourcePackageDb
.lookupPackageName sourceDb pname
505 relaxMinor
:: VersionRange
-> VersionRange
507 let vis
= asVersionIntervals vr
508 in maybe vr relax
(safeLast vis
)
510 relax
(VersionInterval
(LowerBound v0 _
) upper
) =
513 UpperBound _v1 _
-> majorBoundVersion v0