packages feed

hackage-revdeps 0.2 → 0.3

raw patch · 5 files changed

+74/−39 lines, 5 filesdep +zlibdep ~timePVP ok

version bump matches the API change (PVP)

Dependencies added: zlib

Dependency ranges changed: time

API changes (from Hackage documentation)

- Hackage.RevDeps: allLastVersions :: FilePath -> Maybe UTCTime -> IO (Map PackageName ByteString)
- Hackage.RevDeps: lastVersions :: Set ByteString -> FilePath -> Maybe UTCTime -> IO (Map PackageName ByteString)
+ Hackage.RevDeps: extractAllDependencies :: ByteString -> Map PackageName VersionRange
+ Hackage.RevDeps: lastVersionsOfPackages :: (PackageName -> Bool) -> FilePath -> Maybe UTCTime -> IO (Map PackageName ByteString)
+ Hackage.RevDeps: lastVersionsOfPackagesWithNeedles :: (PackageName -> Bool) -> Set ByteString -> FilePath -> Maybe UTCTime -> IO (Map PackageName ByteString)

Files

CHANGELOG.md view
@@ -1,3 +1,8 @@+## 0.3++* Another API overhaul.+* Enable seamless processing of `01-index.tar.gz` as well as `01-index.tar`.+ ## 0.2  * Overhaul the API.
app/History.hs view
@@ -59,7 +59,7 @@   cnfTransitive <-     switch $       long "transitive"-        <> help "Count transitive (both direct and indirect) dependencies"+        <> help "Count transitive (both direct and indirect) dependencies. This mode is imprecise when passing multiple package names at once."   pure Config {..}  main :: IO ()
app/Main.hs view
@@ -49,7 +49,7 @@   cnfTransitive <-     switch $       long "transitive"-        <> help "Count transitive (both direct and indirect) dependencies"+        <> help "Count transitive (both direct and indirect) dependencies. This mode is imprecise when passing multiple package names at once."   pure Config {..}  main :: IO ()
hackage-revdeps.cabal view
@@ -1,12 +1,12 @@ cabal-version:   2.2 name:            hackage-revdeps-version:         0.2+version:         0.3 license:         BSD-3-Clause license-file:    LICENSE maintainer:      andrew.lelechenko@gmail.com author:          Bodigrim tested-with:-    ghc ==9.12.2 ghc ==9.10.2 ghc ==9.8.4 ghc ==9.6.7 ghc ==9.4.8+    ghc == 9.14.1 ghc ==9.12.2 ghc ==9.10.3 ghc ==9.8.4 ghc ==9.6.7 ghc ==9.4.8  synopsis:        List Hackage reverse dependencies description:@@ -38,16 +38,17 @@         bytestring <0.13,         containers >=0.5.8 && <0.9,         filepath <1.6,-        tar <0.7,+        tar <0.8,         text >=2.0 && <2.2,         time <1.16,+        zlib <0.8,         Cabal-syntax >=3.8 && <3.17  executable hackage-revdeps     main-is:          Main.hs     hs-source-dirs:   app     default-language: GHC2021-    ghc-options:      -Wall -Wunused-packages+    ghc-options:      -Wall -Wunused-packages -rtsopts     build-depends:         base,         ansi-terminal >=0.11.3 && <1.2,@@ -64,7 +65,7 @@     main-is:          History.hs     hs-source-dirs:   app     default-language: GHC2021-    ghc-options:      -Wall -Wunused-packages+    ghc-options:      -Wall -Wunused-packages -rtsopts     build-depends:         base,         ansi-terminal >=0.11.3 && <1.2,
src/Hackage/RevDeps.hs view
@@ -1,14 +1,16 @@ -- | Functions to list Hackage reverse dependencies. module Hackage.RevDeps (-  lastVersions,-  allLastVersions,+  lastVersionsOfPackagesWithNeedles,+  lastVersionsOfPackages,   extractDependencies,+  extractAllDependencies,   getReverseDependencies,   getTransitiveReverseDependencies, ) where  import Codec.Archive.Tar qualified as Tar import Codec.Archive.Tar.Entry qualified as Tar+import Codec.Compression.GZip qualified as Gzip import Control.Exception (throwIO) import Data.ByteString (ByteString) import Data.ByteString.Char8 qualified as B@@ -38,7 +40,7 @@ import Distribution.Version (Version, intersectVersionRanges, mkVersion, simplifyVersionRange) import System.FilePath (isPathSeparator) --- | Scan Cabal index @01-index.tar@ and return Cabal files+-- | Scan Cabal index and return Cabal files -- of last versions (not necessarily latest releases or revisions), which -- contain one of the needles as an entire word (separated by spaces -- or punctuation).@@ -46,20 +48,25 @@ -- To avoid ambiguity: we first select the last versions, -- then filter them by needles. ----- @since 0.2-lastVersions-  :: Set ByteString+-- @since 0.3+lastVersionsOfPackagesWithNeedles+  :: (PackageName -> Bool)+  -- ^ Which packages are we looking for? Could be 'const' 'True'.+  -> Set ByteString   -- ^ Needles to search in Cabal files.   -> FilePath-  -- ^ Path to @01-index.tar@.-  -- One can use @Cabal.Config.cfgRepoIndex@ from @cabal-install-parsers@-  -- to obtain it.+  -- ^ Path to @01-index.tar@ or @01-index.tar.gz@.   -> Maybe UTCTime   -- ^ Timestamp of index state at which to stop scanning.   -> IO (Map PackageName ByteString)   -- ^ Map from packages with largest versions to their Cabal files.-lastVersions needles idx indexState =-  filterByNeedles needles <$> allLastVersions idx indexState+lastVersionsOfPackagesWithNeedles pkgPred needles idx indexState =+  -- There is a memory vs. speed trade off: instead of collecting all last+  -- versions into a massive map (~300 M) and then filtering them,+  -- we could have filtered them as we go. The downside would be+  -- filtering each and every Cabal file, even for old versions+  -- which are soon to be rewritten by newer releases.+  filterByNeedles needles <$> lastVersionsOfPackages pkgPred idx indexState  filterByNeedles   :: Set ByteString@@ -72,16 +79,21 @@ -- | Scan Cabal index @01-index.tar@ and return Cabal files -- of last versions (not necessarily latest releases or revisions). ----- @since 0.2-allLastVersions-  :: FilePath+-- @since 0.3+lastVersionsOfPackages+  :: (PackageName -> Bool)+  -- ^ Which packages are we looking for? Could be 'const' 'True'.+  -> FilePath+  -- ^ Path to @01-index.tar@ or @01-index.tar.gz@.   -> Maybe UTCTime+  -- ^ Timestamp of index state at which to stop scanning.   -> IO (Map PackageName ByteString)-allLastVersions idx indexState =+  -- ^ Map from packages with largest versions to their Cabal files.+lastVersionsOfPackages pkgPred idx indexState =   fmap (fmap snd) $     foldCabalFilesInIndex idx indexState mempty go   where-    go pkg ver cnt = M.alter (Just . f) pkg+    go pkg ver cnt = if pkgPred pkg then M.alter (Just . f) pkg else id       where         new = (ver, cnt)         f = maybe new (\old@(ver', _) -> if ver' <= ver then new else old)@@ -112,7 +124,10 @@   -> (PackageName -> Version -> ByteString -> a -> a)   -> IO a foldCabalFilesInIndex fp indexState ini action = do-  contents <- BL.readFile fp+  contents' <- BL.readFile fp+  -- 1F 8B is GZip magic header, see https://en.wikipedia.org/wiki/Gzip#File_structure+  let isGzip = BL.pack [0x1f, 0x8b] `BL.isPrefixOf` contents'+      contents = if isGzip then Gzip.decompress contents' else contents'   let entries' = Tar.read contents       entries = case indexState of         Nothing -> entries'@@ -129,7 +144,9 @@         Tar.NormalFile contents _ ->           if isCabalFile then action pkgName version bs acc else acc           where-            bs = BL.toStrict contents+            -- If we do not force 'bs' at this point, we'll end up+            -- retaining entire 'contents' in memory, which is ~1G.+            !bs = BL.toStrict contents             fpath = Tar.entryPath entry             isCabalFile = ".cabal" `isSuffixOf` fpath             (rawPkgName, fpath') = break isPathSeparator fpath@@ -157,7 +174,7 @@     Tar.Done     Tar.Fail --- | Scan Cabal file looking for package names,+-- | Scan Cabal file looking for package names matching needles, -- coalescing version bounds from all components and under all conditions. -- -- @since 0.1@@ -168,31 +185,40 @@   -- ^ Content of a Cabal file.   -> Map PackageName VersionRange   -- ^ Needles found in the Cabal file and their version bounds.-extractDependencies needles = relevantDeps needles . extractDeps+extractDependencies needles = relevantDeps (`S.member` needles) . extractDeps +-- | Scan Cabal file looking for all dependencies,+-- coalescing version bounds from all components and under all conditions.+--+-- @since 0.3+extractAllDependencies+  :: ByteString+  -- ^ Content of a Cabal file.+  -> Map PackageName VersionRange+  -- ^ All dependencies found in the Cabal file and their version bounds.+extractAllDependencies = relevantDeps (const True) . extractDeps+ extractDeps :: ByteString -> [Dependency] extractDeps cnt = case parseGenericPackageDescriptionMaybe cnt of   Nothing -> mempty   Just descr -> foldMap targetBuildDepends $ toListOf Lens.traverseBuildInfos descr -relevantDeps :: Set PackageName -> [Dependency] -> Map PackageName VersionRange-relevantDeps needles =+relevantDeps :: (PackageName -> Bool) -> [Dependency] -> Map PackageName VersionRange+relevantDeps predicate =   fmap simplifyVersionRange . M.fromListWith intersectVersionRanges . mapMaybe go   where     go (Dependency pkg ver _)-      | pkg `elem` needles = Just (pkg, ver)+      | predicate pkg = Just (pkg, ver)       | otherwise = Nothing --- | Combination of 'lastVersions' and 'extractDependencies'.+-- | Combination of 'lastVersionsOfPackagesWithNeedles' and 'extractDependencies'. -- -- @since 0.2 getReverseDependencies   :: Set PackageName   -- ^ Needles to search in Cabal files.   -> FilePath-  -- ^ Path to @01-index.tar@.-  -- One can use @Cabal.Config.cfgRepoIndex@ from @cabal-install-parsers@-  -- to obtain it.+  -- ^ Path to @01-index.tar@ or @01-index.tar.gz@.   -> Maybe UTCTime   -- ^ Timestamp of index state at which to stop scanning.   -> IO (Map PackageName (Map PackageName VersionRange))@@ -200,7 +226,12 @@   -- inner keys are needles,   -- inner values are accepted version ranges. getReverseDependencies args fp indexState = do-  releases <- lastVersions (S.map (B.pack . unPackageName) args) fp indexState+  releases <-+    lastVersionsOfPackagesWithNeedles+      (const True)+      (S.map (B.pack . unPackageName) args)+      fp+      indexState   pure $ extractDependencies' args releases  extractDependencies'@@ -222,15 +253,13 @@   :: Set PackageName   -- ^ Needles to search in Cabal files.   -> FilePath-  -- ^ Path to @01-index.tar@.-  -- One can use @Cabal.Config.cfgRepoIndex@ from @cabal-install-parsers@-  -- to obtain it.+  -- ^ Path to @01-index.tar@ or @01-index.tar.gz@.   -> Maybe UTCTime   -- ^ Timestamp of index state at which to stop scanning.   -> IO (Map PackageName (Map PackageName VersionRange))   -- ^ Reverse dependencies, including transitive ones. getTransitiveReverseDependencies args fp indexState = do-  releases <- allLastVersions fp indexState+  releases <- lastVersionsOfPackages (const True) fp indexState   go mempty releases args   where     go