1 {-# LANGUAGE ScopedTypeVariables #-}
3 -----------------------------------------------------------------------------
5 -----------------------------------------------------------------------------
8 -- Module : Distribution.Client.List
9 -- Copyright : (c) David Himmelstrup 2005
10 -- Duncan Coutts 2008-2011
13 -- Maintainer : cabal-devel@haskell.org
15 -- Search for and print information about packages
16 module Distribution
.Client
.List
21 import Distribution
.Client
.Compat
.Prelude
24 import qualified Distribution
.InstalledPackageInfo
as Installed
25 import Distribution
.License
(License
)
26 import Distribution
.ModuleName
(ModuleName
)
27 import Distribution
.Package
34 import Distribution
.PackageDescription
38 import qualified Distribution
.PackageDescription
as Source
39 import Distribution
.PackageDescription
.Configuration
40 ( flattenPackageDescription
42 import Distribution
.Types
.Dependency
43 import Distribution
.Types
.UnqualComponentName
45 import Distribution
.Simple
.Compiler
46 import Distribution
.Simple
.PackageIndex
(InstalledPackageIndex
)
47 import qualified Distribution
.Simple
.PackageIndex
as InstalledPackageIndex
48 import Distribution
.Simple
.Program
(ProgramDb
)
49 import Distribution
.Simple
.Setup
(fromFlag
, fromFlagOrDefault
)
50 import Distribution
.Simple
.Utils
55 import Distribution
.Version
59 , intersectVersionRanges
61 , simplifyVersionRange
66 import qualified Distribution
.SPDX
as SPDX
68 import Distribution
.Solver
.Types
.PackageConstraint
69 import qualified Distribution
.Solver
.Types
.PackageIndex
as PackageIndex
70 import Distribution
.Solver
.Types
.SourcePackage
72 import Distribution
.Client
.FetchUtils
75 import Distribution
.Client
.IndexUtils
as IndexUtils
76 ( getInstalledPackages
79 import Distribution
.Client
.Setup
85 import Distribution
.Client
.Targets
89 import Distribution
.Client
.Types
90 ( PackageSpecifier
(..)
91 , SourcePackageDb
(..)
92 , UnresolvedSourcePackage
94 import Distribution
.Client
.Utils
99 import Control
.Exception
102 import Data
.Bits
((.|
.))
106 import qualified Data
.List
as L
107 import Data
.List
.NonEmpty
(groupBy)
108 import qualified Data
.Map
as Map
112 import Data
.Tree
as Tree
113 import System
.Directory
116 import Text
.PrettyPrint
130 import qualified Text
.PrettyPrint
as Disp
132 import Distribution
.Client
.Errors
133 import Distribution
.Utils
.ShortText
(ShortText
)
134 import qualified Distribution
.Utils
.ShortText
as ShortText
135 import qualified Text
.Regex
.Base
as Regex
136 import qualified Text
.Regex
.Posix
.String as Regex
138 -- | Return a list of packages matching given search strings.
143 -> Maybe (Compiler
, ProgramDb
)
146 -> IO [PackageDisplayInfo
]
147 getPkgList verbosity packageDBs repoCtxt mcompprogdb listFlags pats
= do
148 installedPkgIndex
<- for mcompprogdb
$ \(comp
, progdb
) ->
149 getInstalledPackages verbosity comp packageDBs progdb
150 sourcePkgDb
<- getSourcePackages verbosity repoCtxt
152 regexps
<- for pats
$ \pat
-> do
153 e
<- Regex
.compile compOption Regex
.execBlank pat
156 Left err
-> dieWithException verbosity
$ GetPkgList pat err
158 let sourcePkgIndex
= packageIndex sourcePkgDb
162 (Map
.lookup name
(packagePreferences sourcePkgDb
))
165 :: [(PackageName
, [Installed
.InstalledPackageInfo
], [UnresolvedSourcePackage
])]
167 let matchingInstalled
= maybe [] (matchingPackages InstalledPackageIndex
.searchWithPredicate regexps
) installedPkgIndex
168 matchingSource
= matchingPackages
(\idx n
-> concatMap snd (PackageIndex
.searchWithPredicate idx n
)) regexps sourcePkgIndex
169 in mergePackages matchingInstalled matchingSource
172 :: [(PackageName
, [Installed
.InstalledPackageInfo
], [UnresolvedSourcePackage
])]
174 -- gather info for all packages
177 (maybe [] InstalledPackageIndex
.allPackages installedPkgIndex
)
178 (PackageIndex
.allPackages sourcePkgIndex
)
179 -- gather info for packages matching search term
180 |
otherwise = pkgsInfoMatching
182 matches
:: [PackageDisplayInfo
]
190 |
(pkgname
, installedPkgs
, sourcePkgs
) <- pkgsInfo
191 , not onlyInstalled ||
not (null installedPkgs
)
192 , let pref
= prefs pkgname
193 selectedPkg
= latestWithPref pref sourcePkgs
197 onlyInstalled
= fromFlagOrDefault
False (listInstalled listFlags
)
198 caseInsensitive
= fromFlagOrDefault
True (listCaseInsensitive listFlags
)
201 | caseInsensitive
= Regex
.compExtended
.|
. Regex
.compIgnoreCase
202 |
otherwise = Regex
.compExtended
204 matchingPackages search regexps
index =
207 , pkg
<- search
index (Regex
.matchTest re
)
210 -- | Show information about packages.
215 -> Maybe (Compiler
, ProgramDb
)
219 list verbosity packageDBs repos mcompProgdb listFlags pats
= do
220 matches
<- getPkgList verbosity packageDBs repos mcompProgdb listFlags pats
226 [ prettyShow
(pkgName pkg
) ++ " " ++ prettyShow version
230 then installedVersions pkg
233 installedVersions pkg
234 ++ sourceVersions pkg
236 else -- Note: this only works because for 'list', one cannot currently
237 -- specify any version constraints, so listing all installed
238 -- and source ones works.
241 then notice verbosity
"No matches found."
242 else putStr $ unlines (map showPackageSummaryInfo matches
)
244 onlyInstalled
= fromFlag
(listInstalled listFlags
)
245 simpleOutput
= fromFlag
(listSimpleOutput listFlags
)
257 info verbosity _ _ _ _ _ _
[] =
258 notice verbosity
"No packages requested. Nothing to do."
268 installedPkgIndex
<- getInstalledPackages verbosity comp packageDBs progdb
269 sourcePkgDb
<- getSourcePackages verbosity repoCtxt
270 let sourcePkgIndex
= packageIndex sourcePkgDb
274 (Map
.lookup name
(packagePreferences sourcePkgDb
))
276 -- Users may specify names of packages that are only installed, not
277 -- just available source packages, so we must resolve targets using
278 -- the combination of installed and source packages.
280 PackageIndex
.fromList
$
283 (InstalledPackageIndex
.allPackages installedPkgIndex
)
286 (PackageIndex
.allPackages sourcePkgIndex
)
298 either (dieWithException verbosity
) return $
304 updateFileSystemPackageDetails pkginfo
305 | pkgSpecifier
<- pkgSpecifiers
308 putStr $ unlines (map showPackageDetailedInfo pkgsinfo
)
311 :: (PackageName
-> VersionRange
)
312 -> InstalledPackageIndex
313 -> PackageIndex
.PackageIndex UnresolvedSourcePackage
314 -> PackageSpecifier UnresolvedSourcePackage
315 -> Either CabalInstallException PackageDisplayInfo
320 (NamedPackage name props
)
321 |
null (selectedInstalledPkgs
) && null (selectedSourcePkgs
) =
322 Left
$ GatherPkgInfo name
(simplifyVersionRange verConstraint
)
332 (pref
, installedPkgs
, sourcePkgs
) =
333 sourcePkgsInfo prefs name installedPkgIndex sourcePkgIndex
335 selectedInstalledPkgs
=
336 InstalledPackageIndex
.lookupDependency
341 PackageIndex
.lookupDependency
345 selectedSourcePkg
' = latestWithPref pref selectedSourcePkgs
347 -- display a specific package version if the user
348 -- supplied a non-trivial version constraint
349 showPkgVersion
= not (null verConstraints
)
350 verConstraint
= foldr intersectVersionRanges anyVersion verConstraints
351 verConstraints
= [vr | PackagePropertyVersion vr
<- props
]
356 (SpecificSourcePackage pkg
) =
365 name
= packageName pkg
366 selectedPkg
= Just pkg
367 (pref
, installedPkgs
, sourcePkgs
) =
368 sourcePkgsInfo prefs name installedPkgIndex sourcePkgIndex
371 :: (PackageName
-> VersionRange
)
373 -> InstalledPackageIndex
374 -> PackageIndex
.PackageIndex UnresolvedSourcePackage
375 -> (VersionRange
, [Installed
.InstalledPackageInfo
], [UnresolvedSourcePackage
])
376 sourcePkgsInfo prefs name installedPkgIndex sourcePkgIndex
=
377 (pref
, installedPkgs
, sourcePkgs
)
383 ( InstalledPackageIndex
.lookupPackageName
387 sourcePkgs
= PackageIndex
.lookupPackageName sourcePkgIndex name
389 -- | The info that we can display for each package. It is information per
390 -- package name and covers all installed and available versions.
391 data PackageDisplayInfo
= PackageDisplayInfo
392 { pkgName
:: PackageName
393 , selectedVersion
:: Maybe Version
394 , selectedSourcePkg
:: Maybe UnresolvedSourcePackage
395 , installedVersions
:: [Version
]
396 , sourceVersions
:: [Version
]
397 , preferredVersions
:: VersionRange
398 , homepage
:: ShortText
399 , bugReports
:: ShortText
400 , sourceRepo
:: String -- TODO
401 , synopsis
:: ShortText
402 , description
:: ShortText
403 , category
:: ShortText
404 , license
:: Either SPDX
.License License
405 , author
:: ShortText
406 , maintainer
:: ShortText
407 , dependencies
:: [ExtDependency
]
408 , flags
:: [PackageFlag
]
411 , executables
:: [UnqualComponentName
]
412 , modules
:: [ModuleName
]
413 , haddockHtml
:: FilePath
414 , haveTarball
:: Bool
417 -- | Covers source dependencies and installed dependencies in
420 = SourceDependency Dependency
421 | InstalledDependency UnitId
423 showPackageSummaryInfo
:: PackageDisplayInfo
-> String
424 showPackageSummaryInfo pkginfo
=
425 renderStyle
(style{lineLength
= 80, ribbonsPerLine
= 1}) $
427 <+> pretty
(pkgName pkginfo
)
431 [ maybeShowST
(synopsis pkginfo
) "Synopsis:" reflowParagraphs
432 , text
"Default available version:"
433 <+> case selectedSourcePkg pkginfo
of
434 Nothing
-> text
"[ Not available from any configured repository ]"
435 Just pkg
-> pretty
(packageVersion pkg
)
436 , text
"Installed versions:"
437 <+> case installedVersions pkginfo
of
439 | hasLib pkginfo
-> text
"[ Not installed ]"
440 |
otherwise -> text
"[ Unknown ]"
444 (preferredVersions pkginfo
)
446 , maybeShowST
(homepage pkginfo
) "Homepage:" text
447 , text
"License: " <+> either pretty pretty
(license pkginfo
)
453 | ShortText
.null l
= Disp
.empty
454 |
otherwise = text s
<+> f
(ShortText
.fromShortText l
)
456 showPackageDetailedInfo
:: PackageDisplayInfo
-> String
457 showPackageDetailedInfo pkginfo
=
458 renderStyle
(style{lineLength
= 80, ribbonsPerLine
= 1}) $
460 <+> pretty
(pkgName pkginfo
)
461 <<>> maybe Disp
.empty (\v -> char
'-' Disp
.<> pretty v
) (selectedVersion pkginfo
)
462 <+> text
(replicate (16 - length (prettyShow
(pkgName pkginfo
))) ' ')
467 [ entryST
"Synopsis" synopsis hideIfNull reflowParagraphs
471 (altText
null "[ Not available from server ]")
472 (dispTopVersions
9 (preferredVersions pkginfo
))
479 then "[ Not installed ]"
483 (dispTopVersions
4 (preferredVersions pkginfo
))
484 , entryST
"Homepage" homepage orNotSpecified text
485 , entryST
"Bug reports" bugReports orNotSpecified text
486 , entryST
"Description" description hideIfNull reflowParagraphs
487 , entryST
"Category" category hideIfNull text
488 , entry
"License" license alwaysShow
(either pretty pretty
)
489 , entryST
"Author" author hideIfNull reflowLines
490 , entryST
"Maintainer" maintainer hideIfNull reflowLines
491 , entry
"Source repo" sourceRepo orNotSpecified text
492 , entry
"Executables" executables hideIfNull
(commaSep pretty
)
493 , entry
"Flags" flags hideIfNull
(commaSep dispFlag
)
494 , entry
"Dependencies" dependencies hideIfNull
(commaSep dispExtDep
)
495 , entry
"Documentation" haddockHtml showIfInstalled text
496 , entry
"Cached" haveTarball alwaysShow dispYesNo
497 , if not (hasLib pkginfo
)
499 else text
"Modules:" $+$ nest
4 (vcat
(map pretty
. sort . modules
$ pkginfo
))
504 entry fname field cond format
= case cond
(field pkginfo
) of
505 Nothing
-> label
<+> format
(field pkginfo
)
506 Just Nothing
-> mempty
507 Just
(Just other
) -> label
<+> text other
509 label
= text fname Disp
.<> char
':' Disp
.<> padding
510 padding
= text
(replicate (13 - length fname
) ' ')
512 entryST fname field
= entry fname
(ShortText
.fromShortText
. field
)
516 replace msg
= Just
(Just msg
)
518 alwaysShow
= const normal
519 hideIfNull v
= if null v
then hide
else normal
521 |
not isInstalled
= hide
522 |
null v
= replace
"[ Not installed ]"
524 altText nul msg v
= if nul v
then replace msg
else normal
525 orNotSpecified
= altText
null "[ Not specified ]"
527 commaSep f
= Disp
.fsep
. Disp
.punctuate
(Disp
.char
',') . map f
528 dispFlag
= text
. unFlagName
. flagName
529 dispYesNo
True = text
"Yes"
530 dispYesNo
False = text
"No"
532 dispExtDep
(SourceDependency dep
) = pretty dep
533 dispExtDep
(InstalledDependency dep
) = pretty dep
535 isInstalled
= not (null (installedVersions pkginfo
))
536 hasExes
= length (executables pkginfo
) >= 2
537 -- TODO: exclude non-buildable exes
539 | hasLib pkginfo
&& hasExes
= text
"programs and library"
540 | hasLib pkginfo
&& hasExe pkginfo
= text
"program and library"
541 | hasLib pkginfo
= text
"library"
542 | hasExes
= text
"programs"
543 | hasExe pkginfo
= text
"program"
546 reflowParagraphs
:: String -> Doc
549 . intersperse (text
"") -- re-insert blank lines
550 . map (fsep
. map text
. concatMap words) -- reflow paragraphs
552 . L
.groupBy (\x y
-> "" `
notElem`
[x
, y
]) -- break on blank lines
555 reflowLines
:: String -> Doc
556 reflowLines
= vcat
. map text
. lines
558 -- | We get the 'PackageDisplayInfo' by combining the info for the installed
559 -- and available versions of a package.
561 -- * We're building info about a various versions of a single named package so
562 -- the input package info records are all supposed to refer to the same
566 -> [Installed
.InstalledPackageInfo
]
567 -> [UnresolvedSourcePackage
]
568 -> Maybe UnresolvedSourcePackage
570 -> PackageDisplayInfo
571 mergePackageInfo versionPref installedPkgs sourcePkgs selectedPkg showVer
=
572 assert
(length installedPkgs
+ length sourcePkgs
> 0) $
582 then fmap packageVersion selectedPkg
584 , selectedSourcePkg
= sourceSelected
585 , installedVersions
= map packageVersion installedPkgs
586 , sourceVersions
= map packageVersion sourcePkgs
587 , preferredVersions
= versionPref
612 , bugReports
= maybe mempty Source
.bugReports source
617 ( uncons Nothing Source
.repoLocation
618 . sortBy (comparing Source
.repoKind
)
622 , -- TODO: installed package info is missing synopsis
623 synopsis
= maybe mempty Source
.synopsis source
628 Installed
.description
636 , flags
= maybe [] Source
.genPackageFlags sourceGeneric
639 ||
maybe False (isJust . Source
.condLibrary
) sourceGeneric
640 , hasExe
= maybe False (not . null . Source
.condExecutables
) sourceGeneric
641 , executables
= map fst (maybe [] Source
.condExecutables sourceGeneric
)
644 (map Installed
.exposedName
. Installed
.exposedModules
)
646 -- NB: only for the PUBLIC library
647 (concatMap getListOfExposedModules
. maybeToList . Source
.library
)
651 ( map (SourceDependency
. simplifyDependency
)
652 . Source
.allBuildDepends
655 (map InstalledDependency
. Installed
.depends
)
660 . fmap (listToMaybe . Installed
.haddockHTMLs
)
662 , haveTarball
= False
665 combine f x g y
= fromJust (fmap f x `mplus`
fmap g y
)
666 installed
:: Maybe Installed
.InstalledPackageInfo
667 installed
= latestWithPref versionPref installedPkgs
669 getListOfExposedModules lib
=
670 Source
.exposedModules lib
672 Source
.moduleReexportName
673 (Source
.reexportedModules lib
)
676 |
isJust selectedPkg
= selectedPkg
677 |
otherwise = latestWithPref versionPref sourcePkgs
678 sourceGeneric
= fmap srcpkgDescription sourceSelected
679 source
= fmap flattenPackageDescription sourceGeneric
681 uncons
:: b
-> (a
-> b
) -> [a
] -> b
683 uncons _ f
(x
: _
) = f x
685 -- | Not all the info is pure. We have to check if the docs really are
686 -- installed, because the registered package info lies. Similarly we have to
687 -- check if the tarball has indeed been fetched.
688 updateFileSystemPackageDetails
:: PackageDisplayInfo
-> IO PackageDisplayInfo
689 updateFileSystemPackageDetails pkginfo
= do
693 (isFetched
. srcpkgSource
)
694 (selectedSourcePkg pkginfo
)
695 docsExist
<- doesDirectoryExist (haddockHtml pkginfo
)
698 { haveTarball
= fetched
699 , haddockHtml
= if docsExist
then haddockHtml pkginfo
else ""
702 latestWithPref
:: Package pkg
=> VersionRange
-> [pkg
] -> Maybe pkg
703 latestWithPref _
[] = Nothing
704 latestWithPref pref pkgs
= Just
(maximumBy (comparing prefThenVersion
) pkgs
)
706 prefThenVersion pkg
=
707 let ver
= packageVersion pkg
708 in (withinRange ver pref
, ver
)
710 -- | Rearrange installed and source packages into groups referring to the
711 -- same package by name. In the result pairs, the lists are guaranteed to not
714 :: [Installed
.InstalledPackageInfo
]
715 -> [UnresolvedSourcePackage
]
717 , [Installed
.InstalledPackageInfo
]
718 , [UnresolvedSourcePackage
]
721 mergePackages installedPkgs sourcePkgs
=
724 (\i a
-> fst i `
compare`
fst a
)
725 (groupOn packageName installedPkgs
)
726 (groupOn packageName sourcePkgs
)
728 collect
(OnlyInLeft
(name
, is
)) = (name
, is
, [])
729 collect
(InBoth
(_
, is
) (name
, as)) = (name
, is
, as)
730 collect
(OnlyInRight
(name
, as)) = (name
, [], as)
732 groupOn
:: Ord key
=> (a
-> key
) -> [a
] -> [(key
, [a
])]
734 map (\xs
-> (key
(head xs
), toList xs
))
735 . groupBy (equating key
)
736 . sortBy (comparing key
)
738 dispTopVersions
:: Int -> VersionRange
-> [Version
] -> Doc
739 dispTopVersions n pref vs
=
741 . Disp
.punctuate
(Disp
.char
',')
742 . map (\ver
-> if ispref ver
then pretty ver
else parens
(pretty ver
))
745 . interestingVersions ispref
750 ispref ver
= withinRange ver pref
751 extra
= length vs
- n
753 | extra
<= 0 = Disp
.empty
757 <+> Disp
.int
(length vs
- n
)
759 then Disp
.text
"other"
760 else Disp
.text
"others"
762 -- | Reorder a bunch of versions to put the most interesting / significant
763 -- versions first. A preferred version range is taken into account.
765 -- This may be used in a user interface to select a small number of versions
766 -- to present to the user, e.g.
768 -- > let selectVersions = sort . take 5 . interestingVersions pref
769 interestingVersions
:: (Version
-> Bool) -> [Version
] -> [Version
]
770 interestingVersions pref
=
771 map (mkVersion
. fst)
776 . reorderTree
(\(Node
(v
, _
) _
) -> pref
(mkVersion v
))
779 . map (or0
. versionNumbers
)
782 or0
(x
: xs
) = x
:| xs
784 swizzleTree
= unfoldTree
(spine
[])
786 spine ts
' (Node x
[]) = (x
, ts
')
787 spine ts
' (Node x
(t
: ts
)) = spine
(Node x ts
: ts
') t
789 reorderTree _
(Node x
[]) = Node x
[]
790 reorderTree p
(Node x ts
) = Node x
(ts
' ++ ts
'')
792 (ts
', ts
'') = partition p
(map (reorderTree p
) ts
)
794 reverseTree
(Node x cs
) = Node x
(reverse (map reverseTree cs
))
796 mkTree
:: forall a
. Eq a
=> [NonEmpty a
] -> Tree
([a
], Bool)
797 mkTree xs
= unfoldTree step
(False, [], xs
)
799 step
:: (Bool, [a
], [NonEmpty a
]) -> (([a
], Bool), [(Bool, [a
], [NonEmpty a
])])
800 step
(node
, ns
, vs
) =
802 , [ (any null vs
', n
: ns
, mapMaybe nonEmpty
(toList vs
'))
803 |
(n
, vs
') <- groups vs
807 groups
:: [NonEmpty a
] -> [(a
, NonEmpty
[a
])]
809 map (\g
-> (head (head g
), fmap tail g
))
810 . groupBy (equating
head)