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
49 import Distribution
.Simple
.PackageIndex
(InstalledPackageIndex
)
50 import qualified Distribution
.Simple
.PackageIndex
as InstalledPackageIndex
51 import Distribution
.Simple
.Program
(ProgramDb
)
52 import Distribution
.Simple
.Setup
(fromFlag
, fromFlagOrDefault
)
53 import Distribution
.Simple
.Utils
58 import Distribution
.Version
62 , intersectVersionRanges
64 , simplifyVersionRange
69 import qualified Distribution
.SPDX
as SPDX
71 import Distribution
.Solver
.Types
.PackageConstraint
72 import qualified Distribution
.Solver
.Types
.PackageIndex
as PackageIndex
73 import Distribution
.Solver
.Types
.SourcePackage
75 import Distribution
.Client
.FetchUtils
78 import Distribution
.Client
.IndexUtils
as IndexUtils
79 ( getInstalledPackages
82 import Distribution
.Client
.Setup
88 import Distribution
.Client
.Targets
92 import Distribution
.Client
.Types
93 ( PackageSpecifier
(..)
94 , SourcePackageDb
(..)
95 , UnresolvedSourcePackage
97 import Distribution
.Client
.Utils
102 import Control
.Exception
105 import Data
.Bits
((.|
.))
109 import qualified Data
.List
as L
110 import Data
.List
.NonEmpty
(groupBy)
111 import qualified Data
.Map
as Map
115 import Data
.Tree
as Tree
116 import System
.Directory
119 import Text
.PrettyPrint
133 import qualified Text
.PrettyPrint
as Disp
135 import Distribution
.Client
.Errors
136 import Distribution
.Utils
.ShortText
(ShortText
)
137 import qualified Distribution
.Utils
.ShortText
as ShortText
138 import qualified Text
.Regex
.Base
as Regex
139 import qualified Text
.Regex
.Posix
.String as Regex
141 -- | Return a list of packages matching given search strings.
146 -> Maybe (Compiler
, ProgramDb
)
149 -> IO [PackageDisplayInfo
]
150 getPkgList verbosity packageDBs repoCtxt mcompprogdb listFlags pats
= do
151 installedPkgIndex
<- for mcompprogdb
$ \(comp
, progdb
) ->
152 getInstalledPackages verbosity comp packageDBs progdb
153 sourcePkgDb
<- getSourcePackages verbosity repoCtxt
155 regexps
<- for pats
$ \pat
-> do
156 e
<- Regex
.compile compOption Regex
.execBlank pat
159 Left err
-> dieWithException verbosity
$ GetPkgList pat err
161 let sourcePkgIndex
= packageIndex sourcePkgDb
165 (Map
.lookup name
(packagePreferences sourcePkgDb
))
168 :: [(PackageName
, [Installed
.InstalledPackageInfo
], [UnresolvedSourcePackage
])]
170 let matchingInstalled
= maybe [] (matchingPackages InstalledPackageIndex
.searchWithPredicate regexps
) installedPkgIndex
171 matchingSource
= matchingPackages
(\idx n
-> concatMap snd (PackageIndex
.searchWithPredicate idx n
)) regexps sourcePkgIndex
172 in mergePackages matchingInstalled matchingSource
175 :: [(PackageName
, [Installed
.InstalledPackageInfo
], [UnresolvedSourcePackage
])]
177 -- gather info for all packages
180 (maybe [] InstalledPackageIndex
.allPackages installedPkgIndex
)
181 (PackageIndex
.allPackages sourcePkgIndex
)
182 -- gather info for packages matching search term
183 |
otherwise = pkgsInfoMatching
185 matches
:: [PackageDisplayInfo
]
193 |
(pkgname
, installedPkgs
, sourcePkgs
) <- pkgsInfo
194 , not onlyInstalled ||
not (null installedPkgs
)
195 , let pref
= prefs pkgname
196 selectedPkg
= latestWithPref pref sourcePkgs
200 onlyInstalled
= fromFlagOrDefault
False (listInstalled listFlags
)
201 caseInsensitive
= fromFlagOrDefault
True (listCaseInsensitive listFlags
)
204 | caseInsensitive
= Regex
.compExtended
.|
. Regex
.compIgnoreCase
205 |
otherwise = Regex
.compExtended
207 matchingPackages search regexps
index =
210 , pkg
<- search
index (Regex
.matchTest re
)
213 -- | Show information about packages.
218 -> Maybe (Compiler
, ProgramDb
)
222 list verbosity packageDBs repos mcompProgdb listFlags pats
= do
223 matches
<- getPkgList verbosity packageDBs repos mcompProgdb listFlags pats
229 [ prettyShow
(pkgName pkg
) ++ " " ++ prettyShow version
233 then installedVersions pkg
236 installedVersions pkg
237 ++ sourceVersions pkg
239 else -- Note: this only works because for 'list', one cannot currently
240 -- specify any version constraints, so listing all installed
241 -- and source ones works.
244 then notice verbosity
"No matches found."
245 else putStr $ unlines (map showPackageSummaryInfo matches
)
247 onlyInstalled
= fromFlag
(listInstalled listFlags
)
248 simpleOutput
= fromFlag
(listSimpleOutput listFlags
)
260 info verbosity _ _ _ _ _ _
[] =
261 notice verbosity
"No packages requested. Nothing to do."
271 installedPkgIndex
<- getInstalledPackages verbosity comp packageDBs progdb
272 sourcePkgDb
<- getSourcePackages verbosity repoCtxt
273 let sourcePkgIndex
= packageIndex sourcePkgDb
277 (Map
.lookup name
(packagePreferences sourcePkgDb
))
279 -- Users may specify names of packages that are only installed, not
280 -- just available source packages, so we must resolve targets using
281 -- the combination of installed and source packages.
283 PackageIndex
.fromList
$
286 (InstalledPackageIndex
.allPackages installedPkgIndex
)
289 (PackageIndex
.allPackages sourcePkgIndex
)
301 either (dieWithException verbosity
) return $
307 updateFileSystemPackageDetails pkginfo
308 | pkgSpecifier
<- pkgSpecifiers
311 putStr $ unlines (map showPackageDetailedInfo pkgsinfo
)
314 :: (PackageName
-> VersionRange
)
315 -> InstalledPackageIndex
316 -> PackageIndex
.PackageIndex UnresolvedSourcePackage
317 -> PackageSpecifier UnresolvedSourcePackage
318 -> Either CabalInstallException PackageDisplayInfo
323 (NamedPackage name props
)
324 |
null (selectedInstalledPkgs
) && null (selectedSourcePkgs
) =
325 Left
$ GatherPkgInfo name
(simplifyVersionRange verConstraint
)
335 (pref
, installedPkgs
, sourcePkgs
) =
336 sourcePkgsInfo prefs name installedPkgIndex sourcePkgIndex
338 selectedInstalledPkgs
=
339 InstalledPackageIndex
.lookupDependency
344 PackageIndex
.lookupDependency
348 selectedSourcePkg
' = latestWithPref pref selectedSourcePkgs
350 -- display a specific package version if the user
351 -- supplied a non-trivial version constraint
352 showPkgVersion
= not (null verConstraints
)
353 verConstraint
= foldr intersectVersionRanges anyVersion verConstraints
354 verConstraints
= [vr | PackagePropertyVersion vr
<- props
]
359 (SpecificSourcePackage pkg
) =
368 name
= packageName pkg
369 selectedPkg
= Just pkg
370 (pref
, installedPkgs
, sourcePkgs
) =
371 sourcePkgsInfo prefs name installedPkgIndex sourcePkgIndex
374 :: (PackageName
-> VersionRange
)
376 -> InstalledPackageIndex
377 -> PackageIndex
.PackageIndex UnresolvedSourcePackage
378 -> (VersionRange
, [Installed
.InstalledPackageInfo
], [UnresolvedSourcePackage
])
379 sourcePkgsInfo prefs name installedPkgIndex sourcePkgIndex
=
380 (pref
, installedPkgs
, sourcePkgs
)
386 ( InstalledPackageIndex
.lookupPackageName
390 sourcePkgs
= PackageIndex
.lookupPackageName sourcePkgIndex name
392 -- | The info that we can display for each package. It is information per
393 -- package name and covers all installed and available versions.
394 data PackageDisplayInfo
= PackageDisplayInfo
395 { pkgName
:: PackageName
396 , selectedVersion
:: Maybe Version
397 , selectedSourcePkg
:: Maybe UnresolvedSourcePackage
398 , installedVersions
:: [Version
]
399 , sourceVersions
:: [Version
]
400 , preferredVersions
:: VersionRange
401 , homepage
:: ShortText
402 , bugReports
:: ShortText
403 , sourceRepo
:: String -- TODO
404 , synopsis
:: ShortText
405 , description
:: ShortText
406 , category
:: ShortText
407 , license
:: Either SPDX
.License License
408 , author
:: ShortText
409 , maintainer
:: ShortText
410 , dependencies
:: [ExtDependency
]
411 , flags
:: [PackageFlag
]
414 , executables
:: [UnqualComponentName
]
415 , modules
:: [ModuleName
]
416 , haddockHtml
:: FilePath
417 , haveTarball
:: Bool
420 -- | Covers source dependencies and installed dependencies in
423 = SourceDependency Dependency
424 | InstalledDependency UnitId
426 showPackageSummaryInfo
:: PackageDisplayInfo
-> String
427 showPackageSummaryInfo pkginfo
=
428 renderStyle
(style{lineLength
= 80, ribbonsPerLine
= 1}) $
430 <+> pretty
(pkgName pkginfo
)
434 [ maybeShowST
(synopsis pkginfo
) "Synopsis:" reflowParagraphs
435 , text
"Default available version:"
436 <+> case selectedSourcePkg pkginfo
of
437 Nothing
-> text
"[ Not available from any configured repository ]"
438 Just pkg
-> pretty
(packageVersion pkg
)
439 , text
"Installed versions:"
440 <+> case installedVersions pkginfo
of
442 | hasLib pkginfo
-> text
"[ Not installed ]"
443 |
otherwise -> text
"[ Unknown ]"
447 (preferredVersions pkginfo
)
449 , maybeShowST
(homepage pkginfo
) "Homepage:" text
450 , text
"License: " <+> either pretty pretty
(license pkginfo
)
456 | ShortText
.null l
= Disp
.empty
457 |
otherwise = text s
<+> f
(ShortText
.fromShortText l
)
459 showPackageDetailedInfo
:: PackageDisplayInfo
-> String
460 showPackageDetailedInfo pkginfo
=
461 renderStyle
(style{lineLength
= 80, ribbonsPerLine
= 1}) $
463 <+> pretty
(pkgName pkginfo
)
464 <<>> maybe Disp
.empty (\v -> char
'-' Disp
.<> pretty v
) (selectedVersion pkginfo
)
465 <+> text
(replicate (16 - length (prettyShow
(pkgName pkginfo
))) ' ')
470 [ entryST
"Synopsis" synopsis hideIfNull reflowParagraphs
474 (altText
null "[ Not available from server ]")
475 (dispTopVersions
9 (preferredVersions pkginfo
))
482 then "[ Not installed ]"
486 (dispTopVersions
4 (preferredVersions pkginfo
))
487 , entryST
"Homepage" homepage orNotSpecified text
488 , entryST
"Bug reports" bugReports orNotSpecified text
489 , entryST
"Description" description hideIfNull reflowParagraphs
490 , entryST
"Category" category hideIfNull text
491 , entry
"License" license alwaysShow
(either pretty pretty
)
492 , entryST
"Author" author hideIfNull reflowLines
493 , entryST
"Maintainer" maintainer hideIfNull reflowLines
494 , entry
"Source repo" sourceRepo orNotSpecified text
495 , entry
"Executables" executables hideIfNull
(commaSep pretty
)
496 , entry
"Flags" flags hideIfNull
(commaSep dispFlag
)
497 , entry
"Dependencies" dependencies hideIfNull
(commaSep dispExtDep
)
498 , entry
"Documentation" haddockHtml showIfInstalled text
499 , entry
"Cached" haveTarball alwaysShow dispYesNo
500 , if not (hasLib pkginfo
)
502 else text
"Modules:" $+$ nest
4 (vcat
(map pretty
. sort . modules
$ pkginfo
))
507 entry fname field cond format
= case cond
(field pkginfo
) of
508 Nothing
-> label
<+> format
(field pkginfo
)
509 Just Nothing
-> mempty
510 Just
(Just other
) -> label
<+> text other
512 label
= text fname Disp
.<> char
':' Disp
.<> padding
513 padding
= text
(replicate (13 - length fname
) ' ')
515 entryST fname field
= entry fname
(ShortText
.fromShortText
. field
)
519 replace msg
= Just
(Just msg
)
521 alwaysShow
= const normal
522 hideIfNull v
= if null v
then hide
else normal
524 |
not isInstalled
= hide
525 |
null v
= replace
"[ Not installed ]"
527 altText nul msg v
= if nul v
then replace msg
else normal
528 orNotSpecified
= altText
null "[ Not specified ]"
530 commaSep f
= Disp
.fsep
. Disp
.punctuate
(Disp
.char
',') . map f
531 dispFlag
= text
. unFlagName
. flagName
532 dispYesNo
True = text
"Yes"
533 dispYesNo
False = text
"No"
535 dispExtDep
(SourceDependency dep
) = pretty dep
536 dispExtDep
(InstalledDependency dep
) = pretty dep
538 isInstalled
= not (null (installedVersions pkginfo
))
539 hasExes
= length (executables pkginfo
) >= 2
540 -- TODO: exclude non-buildable exes
542 | hasLib pkginfo
&& hasExes
= text
"programs and library"
543 | hasLib pkginfo
&& hasExe pkginfo
= text
"program and library"
544 | hasLib pkginfo
= text
"library"
545 | hasExes
= text
"programs"
546 | hasExe pkginfo
= text
"program"
549 reflowParagraphs
:: String -> Doc
552 . intersperse (text
"") -- re-insert blank lines
553 . map (fsep
. map text
. concatMap words) -- reflow paragraphs
555 . L
.groupBy (\x y
-> "" `
notElem`
[x
, y
]) -- break on blank lines
558 reflowLines
:: String -> Doc
559 reflowLines
= vcat
. map text
. lines
561 -- | We get the 'PackageDisplayInfo' by combining the info for the installed
562 -- and available versions of a package.
564 -- * We're building info about a various versions of a single named package so
565 -- the input package info records are all supposed to refer to the same
569 -> [Installed
.InstalledPackageInfo
]
570 -> [UnresolvedSourcePackage
]
571 -> Maybe UnresolvedSourcePackage
573 -> PackageDisplayInfo
574 mergePackageInfo versionPref installedPkgs sourcePkgs selectedPkg showVer
=
575 assert
(length installedPkgs
+ length sourcePkgs
> 0) $
585 then fmap packageVersion selectedPkg
587 , selectedSourcePkg
= sourceSelected
588 , installedVersions
= map packageVersion installedPkgs
589 , sourceVersions
= map packageVersion sourcePkgs
590 , preferredVersions
= versionPref
615 , bugReports
= maybe mempty Source
.bugReports source
620 ( uncons Nothing Source
.repoLocation
621 . sortBy (comparing Source
.repoKind
)
625 , -- TODO: installed package info is missing synopsis
626 synopsis
= maybe mempty Source
.synopsis source
631 Installed
.description
639 , flags
= maybe [] Source
.genPackageFlags sourceGeneric
642 ||
maybe False (isJust . Source
.condLibrary
) sourceGeneric
643 , hasExe
= maybe False (not . null . Source
.condExecutables
) sourceGeneric
644 , executables
= map fst (maybe [] Source
.condExecutables sourceGeneric
)
647 (map Installed
.exposedName
. Installed
.exposedModules
)
649 -- NB: only for the PUBLIC library
650 (concatMap getListOfExposedModules
. maybeToList . Source
.library
)
654 ( map (SourceDependency
. simplifyDependency
)
655 . Source
.allBuildDepends
658 (map InstalledDependency
. Installed
.depends
)
663 . fmap (listToMaybe . Installed
.haddockHTMLs
)
665 , haveTarball
= False
668 combine f x g y
= fromJust (fmap f x `mplus`
fmap g y
)
669 installed
:: Maybe Installed
.InstalledPackageInfo
670 installed
= latestWithPref versionPref installedPkgs
672 getListOfExposedModules lib
=
673 Source
.exposedModules lib
675 Source
.moduleReexportName
676 (Source
.reexportedModules lib
)
679 |
isJust selectedPkg
= selectedPkg
680 |
otherwise = latestWithPref versionPref sourcePkgs
681 sourceGeneric
= fmap srcpkgDescription sourceSelected
682 source
= fmap flattenPackageDescription sourceGeneric
684 uncons
:: b
-> (a
-> b
) -> [a
] -> b
686 uncons _ f
(x
: _
) = f x
688 -- | Not all the info is pure. We have to check if the docs really are
689 -- installed, because the registered package info lies. Similarly we have to
690 -- check if the tarball has indeed been fetched.
691 updateFileSystemPackageDetails
:: PackageDisplayInfo
-> IO PackageDisplayInfo
692 updateFileSystemPackageDetails pkginfo
= do
696 (isFetched
. srcpkgSource
)
697 (selectedSourcePkg pkginfo
)
698 docsExist
<- doesDirectoryExist (haddockHtml pkginfo
)
701 { haveTarball
= fetched
702 , haddockHtml
= if docsExist
then haddockHtml pkginfo
else ""
705 latestWithPref
:: Package pkg
=> VersionRange
-> [pkg
] -> Maybe pkg
706 latestWithPref _
[] = Nothing
707 latestWithPref pref pkgs
= Just
(maximumBy (comparing prefThenVersion
) pkgs
)
709 prefThenVersion pkg
=
710 let ver
= packageVersion pkg
711 in (withinRange ver pref
, ver
)
713 -- | Rearrange installed and source packages into groups referring to the
714 -- same package by name. In the result pairs, the lists are guaranteed to not
717 :: [Installed
.InstalledPackageInfo
]
718 -> [UnresolvedSourcePackage
]
720 , [Installed
.InstalledPackageInfo
]
721 , [UnresolvedSourcePackage
]
724 mergePackages installedPkgs sourcePkgs
=
727 (\i a
-> fst i `
compare`
fst a
)
728 (groupOn packageName installedPkgs
)
729 (groupOn packageName sourcePkgs
)
731 collect
(OnlyInLeft
(name
, is
)) = (name
, is
, [])
732 collect
(InBoth
(_
, is
) (name
, as)) = (name
, is
, as)
733 collect
(OnlyInRight
(name
, as)) = (name
, [], as)
735 groupOn
:: Ord key
=> (a
-> key
) -> [a
] -> [(key
, [a
])]
737 map (\xs
-> (key
(head xs
), toList xs
))
738 . groupBy (equating key
)
739 . sortBy (comparing key
)
741 dispTopVersions
:: Int -> VersionRange
-> [Version
] -> Doc
742 dispTopVersions n pref vs
=
744 . Disp
.punctuate
(Disp
.char
',')
745 . map (\ver
-> if ispref ver
then pretty ver
else parens
(pretty ver
))
748 . interestingVersions ispref
753 ispref ver
= withinRange ver pref
754 extra
= length vs
- n
756 | extra
<= 0 = Disp
.empty
760 <+> Disp
.int
(length vs
- n
)
762 then Disp
.text
"other"
763 else Disp
.text
"others"
765 -- | Reorder a bunch of versions to put the most interesting / significant
766 -- versions first. A preferred version range is taken into account.
768 -- This may be used in a user interface to select a small number of versions
769 -- to present to the user, e.g.
771 -- > let selectVersions = sort . take 5 . interestingVersions pref
772 interestingVersions
:: (Version
-> Bool) -> [Version
] -> [Version
]
773 interestingVersions pref
=
774 map (mkVersion
. fst)
779 . reorderTree
(\(Node
(v
, _
) _
) -> pref
(mkVersion v
))
782 . map (or0
. versionNumbers
)
785 or0
(x
: xs
) = x
:| xs
787 swizzleTree
= unfoldTree
(spine
[])
789 spine ts
' (Node x
[]) = (x
, ts
')
790 spine ts
' (Node x
(t
: ts
)) = spine
(Node x ts
: ts
') t
792 reorderTree _
(Node x
[]) = Node x
[]
793 reorderTree p
(Node x ts
) = Node x
(ts
' ++ ts
'')
795 (ts
', ts
'') = partition p
(map (reorderTree p
) ts
)
797 reverseTree
(Node x cs
) = Node x
(reverse (map reverseTree cs
))
799 mkTree
:: forall a
. Eq a
=> [NonEmpty a
] -> Tree
([a
], Bool)
800 mkTree xs
= unfoldTree step
(False, [], xs
)
802 step
:: (Bool, [a
], [NonEmpty a
]) -> (([a
], Bool), [(Bool, [a
], [NonEmpty a
])])
803 step
(node
, ns
, vs
) =
805 , [ (any null vs
', n
: ns
, mapMaybe nonEmpty
(toList vs
'))
806 |
(n
, vs
') <- groups vs
810 groups
:: [NonEmpty a
] -> [(a
, NonEmpty
[a
])]
812 map (\g
-> (head (head g
), fmap tail g
))
813 . groupBy (equating
head)