2 {-# LANGUAGE RecordWildCards #-}
3 {-# LANGUAGE NamedFieldPuns #-}
4 -----------------------------------------------------------------------------
6 -- Module : Distribution.Client.CmdOutdated
7 -- Maintainer : cabal-devel@haskell.org
8 -- Portability : portable
10 -- Implementation of the 'outdated' command. Checks for outdated
11 -- dependencies in the package description file or freeze file.
12 -----------------------------------------------------------------------------
14 module Distribution
.Client
.CmdOutdated
15 ( outdatedCommand
, outdatedAction
16 , ListOutdatedSettings
(..), listOutdated
)
19 import Distribution
.Client
.Compat
.Prelude
20 import Distribution
.Compat
.Lens
24 import Distribution
.Client
.Config
25 ( SavedConfig
(savedGlobalFlags
, savedConfigureFlags
26 , savedConfigureExFlags
) )
27 import Distribution
.Client
.IndexUtils
as IndexUtils
28 import Distribution
.Client
.DistDirLayout
29 ( defaultDistDirLayout
30 , DistDirLayout
(distProjectRootDirectory
, distProjectFile
) )
31 import Distribution
.Client
.ProjectConfig
32 import Distribution
.Client
.ProjectConfig
.Legacy
33 ( instantiateProjectConfigSkeletonWithCompiler
)
34 import Distribution
.Client
.ProjectFlags
35 ( projectFlagsOptions
, ProjectFlags
(..), defaultProjectFlags
36 , removeIgnoreProjectOption
)
37 import Distribution
.Client
.RebuildMonad
39 import Distribution
.Client
.Sandbox
40 ( loadConfigOrSandboxConfig
)
41 import Distribution
.Client
.Setup
42 import Distribution
.Client
.Targets
43 ( userToPackageConstraint
, UserConstraint
)
44 import Distribution
.Client
.Types
.SourcePackageDb
as SourcePackageDb
45 import Distribution
.Solver
.Types
.PackageConstraint
46 ( packageConstraintToDependency
)
47 import Distribution
.Client
.Sandbox
.PackageEnvironment
49 import Distribution
.Utils
.Generic
50 ( safeLast
, wrapText
)
52 import Distribution
.Package
53 ( PackageName
, packageVersion
)
54 import Distribution
.PackageDescription
56 import Distribution
.PackageDescription
.Configuration
58 import Distribution
.Simple
.Compiler
59 ( Compiler
, compilerInfo
)
60 import Distribution
.Simple
.Setup
61 ( optionVerbosity
, trueArg
)
62 import Distribution
.Simple
.Utils
63 ( die
', notice
, debug
, tryFindPackageDesc
)
64 import Distribution
.System
66 import Distribution
.Types
.ComponentRequestedSpec
67 ( ComponentRequestedSpec
(..) )
68 import Distribution
.Types
.Dependency
70 import Distribution
.Verbosity
72 import Distribution
.Version
73 ( Version
, VersionInterval
(..), VersionRange
, LowerBound
(..)
74 , UpperBound
(..) , asVersionIntervals
, majorBoundVersion
)
75 import Distribution
.Types
.PackageVersionConstraint
76 ( PackageVersionConstraint
(..), simplifyPackageVersionConstraint
)
77 import Distribution
.Simple
.Flag
78 ( Flag
(..), flagToMaybe
, fromFlagOrDefault
, toFlag
)
79 import Distribution
.Simple
.Command
80 ( ShowOrParseArgs
, OptionField
, CommandUI
(..), optArg
, option
, reqArg
, liftOptionL
)
81 import Distribution
.Simple
.PackageDescription
82 ( readGenericPackageDescription
)
83 import qualified Distribution
.Compat
.CharParsing
as P
84 import Distribution
.ReadE
86 import Distribution
.Client
.HttpUtils
87 import Distribution
.Utils
.NubList
90 import qualified Data
.Set
as S
91 import System
.Directory
92 ( getCurrentDirectory, doesFileExist )
94 -------------------------------------------------------------------------------
96 -------------------------------------------------------------------------------
98 outdatedCommand
:: CommandUI
(ProjectFlags
, OutdatedFlags
)
99 outdatedCommand
= CommandUI
100 { commandName
= "outdated"
101 , commandSynopsis
= "Check for outdated dependencies."
102 , commandDescription
= Just
$ \_
-> wrapText
$
103 "Checks for outdated dependencies in the package description file "
105 , commandNotes
= Nothing
106 , commandUsage
= \pname
->
107 "Usage: " ++ pname
++ " outdated [FLAGS] [PACKAGES]\n"
108 , commandDefaultFlags
= (defaultProjectFlags
, defaultOutdatedFlags
)
109 , commandOptions
= \showOrParseArgs
->
111 (removeIgnoreProjectOption
(projectFlagsOptions showOrParseArgs
)) ++
112 map (liftOptionL _2
) (outdatedOptions showOrParseArgs
)
115 -------------------------------------------------------------------------------
117 -------------------------------------------------------------------------------
119 data IgnoreMajorVersionBumps
= IgnoreMajorVersionBumpsNone
120 | IgnoreMajorVersionBumpsAll
121 | IgnoreMajorVersionBumpsSome
[PackageName
]
123 instance Monoid IgnoreMajorVersionBumps
where
124 mempty
= IgnoreMajorVersionBumpsNone
127 instance Semigroup IgnoreMajorVersionBumps
where
128 IgnoreMajorVersionBumpsNone
<> r
= r
129 l
@IgnoreMajorVersionBumpsAll
<> _
= l
130 l
@(IgnoreMajorVersionBumpsSome _
) <> IgnoreMajorVersionBumpsNone
= l
131 (IgnoreMajorVersionBumpsSome _
) <> r
@IgnoreMajorVersionBumpsAll
= r
132 (IgnoreMajorVersionBumpsSome a
) <> (IgnoreMajorVersionBumpsSome b
) =
133 IgnoreMajorVersionBumpsSome
(a
++ b
)
135 data OutdatedFlags
= OutdatedFlags
136 { outdatedVerbosity
:: Flag Verbosity
137 , outdatedFreezeFile
:: Flag
Bool
138 , outdatedNewFreezeFile
:: Flag
Bool
139 , outdatedSimpleOutput
:: Flag
Bool
140 , outdatedExitCode
:: Flag
Bool
141 , outdatedQuiet
:: Flag
Bool
142 , outdatedIgnore
:: [PackageName
]
143 , outdatedMinor
:: Maybe IgnoreMajorVersionBumps
146 defaultOutdatedFlags
:: OutdatedFlags
147 defaultOutdatedFlags
= OutdatedFlags
148 { outdatedVerbosity
= toFlag normal
149 , outdatedFreezeFile
= mempty
150 , outdatedNewFreezeFile
= mempty
151 , outdatedSimpleOutput
= mempty
152 , outdatedExitCode
= mempty
153 , outdatedQuiet
= mempty
154 , outdatedIgnore
= mempty
155 , outdatedMinor
= mempty
158 outdatedOptions
:: ShowOrParseArgs
-> [OptionField OutdatedFlags
]
159 outdatedOptions _showOrParseArgs
=
162 (\v flags
-> flags
{outdatedVerbosity
= v
})
163 , option
[] ["freeze-file", "v1-freeze-file"]
164 "Act on the freeze file"
165 outdatedFreezeFile
(\v flags
-> flags
{outdatedFreezeFile
= v
})
167 , option
[] ["v2-freeze-file", "new-freeze-file"]
168 "Act on the new-style freeze file (default: cabal.project.freeze)"
169 outdatedNewFreezeFile
(\v flags
-> flags
{outdatedNewFreezeFile
= v
})
171 , option
[] ["simple-output"]
172 "Only print names of outdated dependencies, one per line"
173 outdatedSimpleOutput
(\v flags
-> flags
{outdatedSimpleOutput
= v
})
175 , option
[] ["exit-code"]
176 "Exit with non-zero when there are outdated dependencies"
177 outdatedExitCode
(\v flags
-> flags
{outdatedExitCode
= v
})
179 , option
['q
'] ["quiet"]
180 "Don't print any output. Implies '--exit-code' and '-v0'"
181 outdatedQuiet
(\v flags
-> flags
{outdatedQuiet
= v
})
183 , option
[] ["ignore"]
185 outdatedIgnore
(\v flags
-> flags
{outdatedIgnore
= v
})
186 (reqArg
"PKGS" pkgNameListParser
(map prettyShow
))
187 , option
[] ["minor"]
188 "Ignore major version bumps for these packages"
189 outdatedMinor
(\v flags
-> flags
{outdatedMinor
= v
})
192 ignoreMajorVersionBumpsParser
193 (Just IgnoreMajorVersionBumpsAll
)
194 ignoreMajorVersionBumpsPrinter
198 ignoreMajorVersionBumpsPrinter
:: Maybe IgnoreMajorVersionBumps
200 ignoreMajorVersionBumpsPrinter Nothing
= []
201 ignoreMajorVersionBumpsPrinter
(Just IgnoreMajorVersionBumpsNone
)= []
202 ignoreMajorVersionBumpsPrinter
(Just IgnoreMajorVersionBumpsAll
) = [Nothing
]
203 ignoreMajorVersionBumpsPrinter
(Just
(IgnoreMajorVersionBumpsSome pkgs
)) =
204 map (Just
. prettyShow
) pkgs
206 ignoreMajorVersionBumpsParser
=
207 (Just
. IgnoreMajorVersionBumpsSome
) `
fmap` pkgNameListParser
209 pkgNameListParser
= parsecToReadE
210 ("Couldn't parse the list of package names: " ++)
211 (fmap toList
(P
.sepByNonEmpty parsec
(P
.char
',')))
213 -------------------------------------------------------------------------------
215 -------------------------------------------------------------------------------
217 -- | Entry point for the 'outdated' command.
218 outdatedAction
:: (ProjectFlags
, OutdatedFlags
) -> [String] -> GlobalFlags
-> IO ()
219 outdatedAction
(ProjectFlags
{flagProjectFileName
}, OutdatedFlags
{..}) _targetStrings globalFlags
= do
220 config
<- loadConfigOrSandboxConfig verbosity globalFlags
221 let globalFlags
' = savedGlobalFlags config `mappend` globalFlags
222 configFlags
= savedConfigureFlags config
223 withRepoContext verbosity globalFlags
' $ \repoContext
-> do
224 when (not newFreezeFile
&& isJust mprojectFile
) $
226 "--project-file must only be used with --v2-freeze-file."
228 sourcePkgDb
<- IndexUtils
.getSourcePackages verbosity repoContext
229 (comp
, platform
, _progdb
) <- configCompilerAux
' configFlags
230 deps
<- if freezeFile
231 then depsFromFreezeFile verbosity
232 else if newFreezeFile
234 httpTransport
<- configureTransport verbosity
235 (fromNubList
. globalProgPathExtra
$ globalFlags
)
236 (flagToMaybe
. globalHttpTransport
$ globalFlags
)
237 depsFromNewFreezeFile verbosity httpTransport comp platform mprojectFile
239 depsFromPkgDesc verbosity comp platform
240 debug verbosity
$ "Dependencies loaded: "
241 ++ intercalate
", " (map prettyShow deps
)
242 let outdatedDeps
= listOutdated deps sourcePkgDb
243 (ListOutdatedSettings ignorePred minorPred
)
245 showResult verbosity outdatedDeps simpleOutput
246 if exitCode
&& (not . null $ outdatedDeps
)
252 else fromFlagOrDefault normal outdatedVerbosity
253 freezeFile
= fromFlagOrDefault
False outdatedFreezeFile
254 newFreezeFile
= fromFlagOrDefault
False outdatedNewFreezeFile
255 mprojectFile
= flagToMaybe flagProjectFileName
256 simpleOutput
= fromFlagOrDefault
False outdatedSimpleOutput
257 quiet
= fromFlagOrDefault
False outdatedQuiet
258 exitCode
= fromFlagOrDefault quiet outdatedExitCode
259 ignorePred
= let ignoreSet
= S
.fromList outdatedIgnore
260 in \pkgname
-> pkgname `S
.member` ignoreSet
261 minorPred
= case outdatedMinor
of
262 Nothing
-> const False
263 Just IgnoreMajorVersionBumpsNone
-> const False
264 Just IgnoreMajorVersionBumpsAll
-> const True
265 Just
(IgnoreMajorVersionBumpsSome pkgs
) ->
266 let minorSet
= S
.fromList pkgs
267 in \pkgname
-> pkgname `S
.member` minorSet
270 -- | Print either the list of all outdated dependencies, or a message
271 -- that there are none.
272 showResult
:: Verbosity
-> [(PackageVersionConstraint
,Version
)] -> Bool -> IO ()
273 showResult verbosity outdatedDeps simpleOutput
=
274 if not . null $ outdatedDeps
276 do when (not simpleOutput
) $
277 notice verbosity
"Outdated dependencies:"
278 for_ outdatedDeps
$ \(d
@(PackageVersionConstraint pn _
), v
) ->
279 let outdatedDep
= if simpleOutput
then prettyShow pn
280 else prettyShow d
++ " (latest: " ++ prettyShow v
++ ")"
281 in notice verbosity outdatedDep
282 else notice verbosity
"All dependencies are up to date."
284 -- | Convert a list of 'UserConstraint's to a 'Dependency' list.
285 userConstraintsToDependencies
:: [UserConstraint
] -> [PackageVersionConstraint
]
286 userConstraintsToDependencies ucnstrs
=
287 mapMaybe (packageConstraintToDependency
. userToPackageConstraint
) ucnstrs
289 -- | Read the list of dependencies from the freeze file.
290 depsFromFreezeFile
:: Verbosity
-> IO [PackageVersionConstraint
]
291 depsFromFreezeFile verbosity
= do
292 cwd
<- getCurrentDirectory
293 userConfig
<- loadUserConfig verbosity cwd Nothing
294 let ucnstrs
= map fst . configExConstraints
. savedConfigureExFlags
$
296 deps
= userConstraintsToDependencies ucnstrs
297 debug verbosity
"Reading the list of dependencies from the freeze file"
300 -- | Read the list of dependencies from the new-style freeze file.
301 depsFromNewFreezeFile
:: Verbosity
-> HttpTransport
-> Compiler
-> Platform
-> Maybe FilePath -> IO [PackageVersionConstraint
]
302 depsFromNewFreezeFile verbosity httpTransport compiler
(Platform arch os
) mprojectFile
= do
303 projectRoot
<- either throwIO
return =<<
304 findProjectRoot Nothing mprojectFile
305 let distDirLayout
= defaultDistDirLayout projectRoot
306 {- TODO: Support dist dir override -} Nothing
307 projectConfig
<- runRebuild
(distProjectRootDirectory distDirLayout
) $ do
308 pcs
<- readProjectLocalFreezeConfig verbosity httpTransport distDirLayout
309 pure
$ instantiateProjectConfigSkeletonWithCompiler os arch
(compilerInfo compiler
) mempty pcs
310 let ucnstrs
= map fst . projectConfigConstraints
. projectConfigShared
312 deps
= userConstraintsToDependencies ucnstrs
313 freezeFile
= distProjectFile distDirLayout
"freeze"
314 freezeFileExists
<- doesFileExist freezeFile
316 unless freezeFileExists
$
318 "Couldn't find a freeze file expected at: " ++ freezeFile
++ "\n\n"
319 ++ "We are looking for this file because you supplied '--project-file' or '--v2-freeze-file'. "
320 ++ "When one of these flags is given, we try to read the dependencies from a freeze file. "
321 ++ "If it is undesired behaviour, you should not use these flags, otherwise please generate "
322 ++ "a freeze file via 'cabal freeze'."
324 "Reading the list of dependencies from the new-style freeze file " ++ freezeFile
327 -- | Read the list of dependencies from the package description.
328 depsFromPkgDesc
:: Verbosity
-> Compiler
-> Platform
-> IO [PackageVersionConstraint
]
329 depsFromPkgDesc verbosity comp platform
= do
330 cwd
<- getCurrentDirectory
331 path
<- tryFindPackageDesc verbosity cwd
332 gpd
<- readGenericPackageDescription verbosity path
333 let cinfo
= compilerInfo comp
334 epd
= finalizePD mempty
(ComponentRequestedSpec
True True)
335 (const True) platform cinfo
[] gpd
337 Left _
-> die
' verbosity
"finalizePD failed"
339 let bd
= allBuildDepends pd
341 "Reading the list of dependencies from the package description"
342 return $ map toPVC bd
344 toPVC
(Dependency pn vr _
) = PackageVersionConstraint pn vr
346 -- | Various knobs for customising the behaviour of 'listOutdated'.
347 data ListOutdatedSettings
= ListOutdatedSettings
348 { -- | Should this package be ignored?
349 listOutdatedIgnorePred
:: PackageName
-> Bool
350 , -- | Should major version bumps be ignored for this package?
351 listOutdatedMinorPred
:: PackageName
-> Bool
354 -- | Find all outdated dependencies.
355 listOutdated
:: [PackageVersionConstraint
]
357 -> ListOutdatedSettings
358 -> [(PackageVersionConstraint
, Version
)]
359 listOutdated deps sourceDb
(ListOutdatedSettings ignorePred minorPred
) =
360 mapMaybe isOutdated
$ map simplifyPackageVersionConstraint deps
362 isOutdated
:: PackageVersionConstraint
-> Maybe (PackageVersionConstraint
, Version
)
363 isOutdated dep
@(PackageVersionConstraint pname vr
)
364 | ignorePred pname
= Nothing
366 let this
= map packageVersion
$ SourcePackageDb
.lookupDependency sourceDb pname vr
367 latest
= lookupLatest dep
368 in (\v -> (dep
, v
)) `
fmap` isOutdated
' this latest
370 isOutdated
' :: [Version
] -> [Version
] -> Maybe Version
371 isOutdated
' [] _
= Nothing
372 isOutdated
' _
[] = Nothing
373 isOutdated
' this latest
=
374 let this
' = maximum this
375 latest
' = maximum latest
376 in if this
' < latest
' then Just latest
' else Nothing
378 lookupLatest
:: PackageVersionConstraint
-> [Version
]
379 lookupLatest
(PackageVersionConstraint pname vr
)
381 map packageVersion
$ SourcePackageDb
.lookupDependency sourceDb pname
(relaxMinor vr
)
383 map packageVersion
$ SourcePackageDb
.lookupPackageName sourceDb pname
385 relaxMinor
:: VersionRange
-> VersionRange
387 let vis
= asVersionIntervals vr
388 in maybe vr relax
(safeLast vis
)
389 where relax
(VersionInterval
(LowerBound v0 _
) upper
) =
392 UpperBound _v1 _
-> majorBoundVersion v0