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
.PackageVersionConstraint
133 ( PackageVersionConstraint
(..)
134 , simplifyPackageVersionConstraint
136 import Distribution
.Utils
.NubList
139 import Distribution
.Verbosity
143 import Distribution
.Version
147 , VersionInterval
(..)
153 import qualified Data
.Set
as S
154 import Distribution
.Client
.Errors
155 import System
.Directory
157 , getCurrentDirectory
160 -------------------------------------------------------------------------------
162 -------------------------------------------------------------------------------
164 outdatedCommand
:: CommandUI
(ProjectFlags
, OutdatedFlags
)
167 { commandName
= "outdated"
168 , commandSynopsis
= "Check for outdated dependencies."
169 , commandDescription
= Just
$ \_
->
171 "Checks for outdated dependencies in the package description file "
173 , commandNotes
= Nothing
174 , commandUsage
= \pname
->
175 "Usage: " ++ pname
++ " outdated [FLAGS] [PACKAGES]\n"
176 , commandDefaultFlags
= (defaultProjectFlags
, defaultOutdatedFlags
)
177 , commandOptions
= \showOrParseArgs
->
180 (removeIgnoreProjectOption
(projectFlagsOptions showOrParseArgs
))
181 ++ map (liftOptionL _2
) (outdatedOptions showOrParseArgs
)
184 -------------------------------------------------------------------------------
186 -------------------------------------------------------------------------------
188 data IgnoreMajorVersionBumps
189 = IgnoreMajorVersionBumpsNone
190 | IgnoreMajorVersionBumpsAll
191 | IgnoreMajorVersionBumpsSome
[PackageName
]
193 instance Monoid IgnoreMajorVersionBumps
where
194 mempty
= IgnoreMajorVersionBumpsNone
197 instance Semigroup IgnoreMajorVersionBumps
where
198 IgnoreMajorVersionBumpsNone
<> r
= r
199 l
@IgnoreMajorVersionBumpsAll
<> _
= l
200 l
@(IgnoreMajorVersionBumpsSome _
) <> IgnoreMajorVersionBumpsNone
= l
201 (IgnoreMajorVersionBumpsSome _
) <> r
@IgnoreMajorVersionBumpsAll
= r
202 (IgnoreMajorVersionBumpsSome a
) <> (IgnoreMajorVersionBumpsSome b
) =
203 IgnoreMajorVersionBumpsSome
(a
++ b
)
205 data OutdatedFlags
= OutdatedFlags
206 { outdatedVerbosity
:: Flag Verbosity
207 , outdatedFreezeFile
:: Flag
Bool
208 , outdatedNewFreezeFile
:: Flag
Bool
209 , outdatedSimpleOutput
:: Flag
Bool
210 , outdatedExitCode
:: Flag
Bool
211 , outdatedQuiet
:: Flag
Bool
212 , outdatedIgnore
:: [PackageName
]
213 , outdatedMinor
:: Maybe IgnoreMajorVersionBumps
216 defaultOutdatedFlags
:: OutdatedFlags
217 defaultOutdatedFlags
=
219 { outdatedVerbosity
= toFlag normal
220 , outdatedFreezeFile
= mempty
221 , outdatedNewFreezeFile
= mempty
222 , outdatedSimpleOutput
= mempty
223 , outdatedExitCode
= mempty
224 , outdatedQuiet
= mempty
225 , outdatedIgnore
= mempty
226 , outdatedMinor
= mempty
229 outdatedOptions
:: ShowOrParseArgs
-> [OptionField OutdatedFlags
]
230 outdatedOptions _showOrParseArgs
=
233 (\v flags
-> flags
{outdatedVerbosity
= v
})
236 ["freeze-file", "v1-freeze-file"]
237 "Act on the freeze file"
239 (\v flags
-> flags
{outdatedFreezeFile
= v
})
243 ["v2-freeze-file", "new-freeze-file"]
244 "Act on the new-style freeze file (default: cabal.project.freeze)"
245 outdatedNewFreezeFile
246 (\v flags
-> flags
{outdatedNewFreezeFile
= v
})
251 "Only print names of outdated dependencies, one per line"
253 (\v flags
-> flags
{outdatedSimpleOutput
= v
})
258 "Exit with non-zero when there are outdated dependencies"
260 (\v flags
-> flags
{outdatedExitCode
= v
})
265 "Don't print any output. Implies '--exit-code' and '-v0'"
267 (\v flags
-> flags
{outdatedQuiet
= v
})
274 (\v flags
-> flags
{outdatedIgnore
= v
})
275 (reqArg
"PKGS" pkgNameListParser
(map prettyShow
))
279 "Ignore major version bumps for these packages"
281 (\v flags
-> flags
{outdatedMinor
= v
})
284 ignoreMajorVersionBumpsParser
285 ("", Just IgnoreMajorVersionBumpsAll
)
286 ignoreMajorVersionBumpsPrinter
290 ignoreMajorVersionBumpsPrinter
291 :: Maybe IgnoreMajorVersionBumps
293 ignoreMajorVersionBumpsPrinter Nothing
= []
294 ignoreMajorVersionBumpsPrinter
(Just IgnoreMajorVersionBumpsNone
) = []
295 ignoreMajorVersionBumpsPrinter
(Just IgnoreMajorVersionBumpsAll
) = [Nothing
]
296 ignoreMajorVersionBumpsPrinter
(Just
(IgnoreMajorVersionBumpsSome pkgs
)) =
297 map (Just
. prettyShow
) pkgs
299 ignoreMajorVersionBumpsParser
=
300 (Just
. IgnoreMajorVersionBumpsSome
) `
fmap` pkgNameListParser
304 ("Couldn't parse the list of package names: " ++)
305 (fmap toList
(P
.sepByNonEmpty parsec
(P
.char
',')))
307 -------------------------------------------------------------------------------
309 -------------------------------------------------------------------------------
311 -- | Entry point for the 'outdated' command.
312 outdatedAction
:: (ProjectFlags
, OutdatedFlags
) -> [String] -> GlobalFlags
-> IO ()
313 outdatedAction
(ProjectFlags
{flagProjectDir
, flagProjectFile
}, OutdatedFlags
{..}) _targetStrings globalFlags
= do
314 config
<- loadConfigOrSandboxConfig verbosity globalFlags
315 let globalFlags
' = savedGlobalFlags config `mappend` globalFlags
316 configFlags
= savedConfigureFlags config
317 withRepoContext verbosity globalFlags
' $ \repoContext
-> do
318 when (not newFreezeFile
&& (isJust mprojectDir ||
isJust mprojectFile
)) $
319 dieWithException verbosity OutdatedAction
321 sourcePkgDb
<- IndexUtils
.getSourcePackages verbosity repoContext
322 (comp
, platform
, _progdb
) <- configCompilerAux
' configFlags
325 then depsFromFreezeFile verbosity
332 (fromNubList
. globalProgPathExtra
$ globalFlags
)
333 (flagToMaybe
. globalHttpTransport
$ globalFlags
)
334 depsFromNewFreezeFile verbosity httpTransport comp platform mprojectDir mprojectFile
336 depsFromPkgDesc verbosity comp platform
338 "Dependencies loaded: "
339 ++ intercalate
", " (map prettyShow deps
)
344 (ListOutdatedSettings ignorePred minorPred
)
346 showResult verbosity outdatedDeps simpleOutput
347 if exitCode
&& (not . null $ outdatedDeps
)
354 else fromFlagOrDefault normal outdatedVerbosity
355 freezeFile
= fromFlagOrDefault
False outdatedFreezeFile
356 newFreezeFile
= fromFlagOrDefault
False outdatedNewFreezeFile
357 mprojectDir
= flagToMaybe flagProjectDir
358 mprojectFile
= flagToMaybe flagProjectFile
359 simpleOutput
= fromFlagOrDefault
False outdatedSimpleOutput
360 quiet
= fromFlagOrDefault
False outdatedQuiet
361 exitCode
= fromFlagOrDefault quiet outdatedExitCode
363 let ignoreSet
= S
.fromList outdatedIgnore
364 in \pkgname
-> pkgname `S
.member` ignoreSet
365 minorPred
= case outdatedMinor
of
366 Nothing
-> const False
367 Just IgnoreMajorVersionBumpsNone
-> const False
368 Just IgnoreMajorVersionBumpsAll
-> const True
369 Just
(IgnoreMajorVersionBumpsSome pkgs
) ->
370 let minorSet
= S
.fromList pkgs
371 in \pkgname
-> pkgname `S
.member` minorSet
373 -- | Print either the list of all outdated dependencies, or a message
374 -- that there are none.
375 showResult
:: Verbosity
-> [(PackageVersionConstraint
, Version
)] -> Bool -> IO ()
376 showResult verbosity outdatedDeps simpleOutput
=
377 if not . null $ outdatedDeps
379 when (not simpleOutput
) $
380 notice verbosity
"Outdated dependencies:"
381 for_ outdatedDeps
$ \(d
@(PackageVersionConstraint pn _
), v
) ->
385 else prettyShow d
++ " (latest: " ++ prettyShow v
++ ")"
386 in notice verbosity outdatedDep
387 else notice verbosity
"All dependencies are up to date."
389 -- | Convert a list of 'UserConstraint's to a 'Dependency' list.
390 userConstraintsToDependencies
:: [UserConstraint
] -> [PackageVersionConstraint
]
391 userConstraintsToDependencies ucnstrs
=
392 mapMaybe (packageConstraintToDependency
. userToPackageConstraint
) ucnstrs
394 -- | Read the list of dependencies from the freeze file.
395 depsFromFreezeFile
:: Verbosity
-> IO [PackageVersionConstraint
]
396 depsFromFreezeFile verbosity
= do
397 cwd
<- getCurrentDirectory
398 userConfig
<- loadUserConfig verbosity cwd Nothing
400 map fst . configExConstraints
. savedConfigureExFlags
$
402 deps
= userConstraintsToDependencies ucnstrs
403 debug verbosity
"Reading the list of dependencies from the freeze file"
406 -- | Read the list of dependencies from the new-style freeze file.
407 depsFromNewFreezeFile
:: Verbosity
-> HttpTransport
-> Compiler
-> Platform
-> Maybe FilePath -> Maybe FilePath -> IO [PackageVersionConstraint
]
408 depsFromNewFreezeFile verbosity httpTransport compiler
(Platform arch os
) mprojectDir mprojectFile
= do
410 either throwIO
return
411 =<< findProjectRoot verbosity mprojectDir mprojectFile
415 {- TODO: Support dist dir override -} Nothing
417 projectConfig
<- runRebuild
(distProjectRootDirectory distDirLayout
) $ do
418 pcs
<- readProjectLocalFreezeConfig verbosity httpTransport distDirLayout
419 pure
$ instantiateProjectConfigSkeletonWithCompiler os arch
(compilerInfo compiler
) mempty pcs
421 map fst . projectConfigConstraints
. projectConfigShared
$
423 deps
= userConstraintsToDependencies ucnstrs
424 freezeFile
= distProjectFile distDirLayout
"freeze"
425 freezeFileExists
<- doesFileExist freezeFile
427 unless freezeFileExists
$
428 dieWithException verbosity
$
429 FreezeFileExistsErr freezeFile
432 "Reading the list of dependencies from the new-style freeze file " ++ freezeFile
435 -- | Read the list of dependencies from the package description.
436 depsFromPkgDesc
:: Verbosity
-> Compiler
-> Platform
-> IO [PackageVersionConstraint
]
437 depsFromPkgDesc verbosity comp platform
= do
438 cwd
<- getCurrentDirectory
439 path
<- tryFindPackageDesc verbosity cwd
440 gpd
<- readGenericPackageDescription verbosity path
441 let cinfo
= compilerInfo comp
445 (ComponentRequestedSpec
True True)
452 Left _
-> dieWithException verbosity FinalizePDFailed
454 let bd
= allBuildDepends pd
457 "Reading the list of dependencies from the package description"
458 return $ map toPVC bd
460 toPVC
(Dependency pn vr _
) = PackageVersionConstraint pn vr
462 -- | Various knobs for customising the behaviour of 'listOutdated'.
463 data ListOutdatedSettings
= ListOutdatedSettings
464 { listOutdatedIgnorePred
:: PackageName
-> Bool
465 -- ^ Should this package be ignored?
466 , listOutdatedMinorPred
:: PackageName
-> Bool
467 -- ^ Should major version bumps be ignored for this package?
470 -- | Find all outdated dependencies.
472 :: [PackageVersionConstraint
]
474 -> ListOutdatedSettings
475 -> [(PackageVersionConstraint
, Version
)]
476 listOutdated deps sourceDb
(ListOutdatedSettings ignorePred minorPred
) =
477 mapMaybe isOutdated
$ map simplifyPackageVersionConstraint deps
479 isOutdated
:: PackageVersionConstraint
-> Maybe (PackageVersionConstraint
, Version
)
480 isOutdated dep
@(PackageVersionConstraint pname vr
)
481 | ignorePred pname
= Nothing
483 let this
= map packageVersion
$ SourcePackageDb
.lookupDependency sourceDb pname vr
484 latest
= lookupLatest dep
485 in (\v -> (dep
, v
)) `
fmap` isOutdated
' this latest
487 isOutdated
' :: [Version
] -> [Version
] -> Maybe Version
488 isOutdated
' [] _
= Nothing
489 isOutdated
' _
[] = Nothing
490 isOutdated
' this latest
=
491 let this
' = maximum this
492 latest
' = maximum latest
493 in if this
' < latest
' then Just latest
' else Nothing
495 lookupLatest
:: PackageVersionConstraint
-> [Version
]
496 lookupLatest
(PackageVersionConstraint pname vr
)
498 map packageVersion
$ SourcePackageDb
.lookupDependency sourceDb pname
(relaxMinor vr
)
500 map packageVersion
$ SourcePackageDb
.lookupPackageName sourceDb pname
502 relaxMinor
:: VersionRange
-> VersionRange
504 let vis
= asVersionIntervals vr
505 in maybe vr relax
(safeLast vis
)
507 relax
(VersionInterval
(LowerBound v0 _
) upper
) =
510 UpperBound _v1 _
-> majorBoundVersion v0