11{-# LANGUAGE DeriveDataTypeable #-}
22{-# LANGUAGE DeriveGeneric #-}
3+ {-# LANGUAGE TupleSections #-}
4+ {-# LANGUAGE TypeApplications #-}
35-----------------------------------------------------------------------------
46-- |
57-- Module : Distribution.Solver.Types.PkgConfigDb
@@ -21,14 +23,15 @@ module Distribution.Solver.Types.PkgConfigDb
2123 ) where
2224
2325import Distribution.Solver.Compat.Prelude
24- import Prelude ()
26+ import Prelude (read )
2527
26- import Control.Exception (handle )
27- import Control.Monad (mapM )
28+ import Control.Exception (handle , handleJust )
2829import qualified Data.Map as M
29- import System.FilePath (splitSearchPath )
30+ import System.FilePath (splitSearchPath , takeBaseName , (</>) )
3031
3132import Distribution.Compat.Environment (lookupEnv )
33+ import Distribution.Compat.Directory
34+ import Distribution.Compat.Time (getModTime , ModTime )
3235import Distribution.Package (PkgconfigName , mkPkgconfigName )
3336import Distribution.Parsec
3437import Distribution.Simple.Program
@@ -38,6 +41,7 @@ import Distribution.Simple.Utils (info)
3841import Distribution.Types.PkgconfigVersion
3942import Distribution.Types.PkgconfigVersionRange
4043import Distribution.Verbosity (Verbosity )
44+ import System.IO.Error (isDoesNotExistError )
4145
4246-- | The list of packages installed in the system visible to
4347-- @pkg-config@. This is an opaque datatype, to be constructed with
@@ -63,19 +67,34 @@ readPkgConfigDb verbosity progdb = handle ioErrorHandler $ do
6367 case mpkgConfig of
6468 Nothing -> noPkgConfig " Cannot find pkg-config program"
6569 Just (pkgConfig, _) -> do
66- pkgList <- lines <$> getProgramOutput verbosity pkgConfig [" --list-all" ]
67- -- The output of @pkg-config --list-all@ also includes a description
68- -- for each package, which we do not need.
69- let pkgNames = map (takeWhile (not . isSpace)) pkgList
70- (pkgVersions, _errs, exitCode) <-
71- getProgramInvocationOutputAndErrors verbosity
72- (programInvocation pkgConfig (" --modversion" : pkgNames))
73- case exitCode of
74- ExitSuccess -> (return . pkgConfigDbFromList . zip pkgNames) (lines pkgVersions)
75- -- if there's a single broken pc file the above fails, so we fall back into calling it individually
76- _ -> do
77- info verbosity (" call to pkg-config --modversion on all packages failed. Falling back to querying pkg-config individually on each package" )
78- pkgConfigDbFromList . catMaybes <$> mapM (getIndividualVersion pkgConfig) pkgNames
70+ -- TODO use a more sensible data structure
71+ -- should I just add a `ModTime` field to `PkgConfigDb` and serialise that directly?
72+ -- TODO don't hardcode path (how to get? does this need to be its own preference?)
73+ let cacheFile = " /home/gthomas/.local/state/cabal/pkg-config"
74+ writeCache = writeFile cacheFile . show @ [(String , ModTime , String )]
75+ readCache = handleJust (guard . isDoesNotExistError) (\ () -> pure [] ) $ read @ [(String , ModTime , String )] <$> readFile cacheFile
76+ -- TODO more logging
77+ pcPaths <- splitOn " :" . dropWhileEnd isSpace
78+ -- TODO verbose logs imply we already call this elsewhere
79+ <$> getProgramOutput verbosity pkgConfig [" --variable" , " pc_path" , " pkg-config" ]
80+ -- ["/usr/lib/pkgconfig", "/usr/share/pkgconfig"]
81+ ts <- traverse (\ (d, f) -> (takeBaseName f,) <$> getModTime (d </> f))
82+ -- TODO why are there directories here (`personality`)?
83+ -- note that if we remove them, we get exactly the same list here that we'd get from `pkg-config --list-package-names`
84+ -- should we sanity-check that, or is it part of the `pkg-config` spec?
85+ -- =<< filterM (fmap not . doesFileExist)
86+ =<< foldMap (\ p -> map (p,) <$> listDirectory p) pcPaths
87+ cache <- M. fromList . map (\ (p, t, v) -> (p, (t, v))) <$> readCache
88+ r <- fmap catMaybes $ for ts $ \ (p, t) -> do
89+ case M. lookup p cache of
90+ Just (storedTime, storedVersion) | t == storedTime -> pure $ Just (p, t, storedVersion)
91+ -- TODO the `Nothing` case is (on my machine) always for packages with missing dependencies
92+ -- since this call will always fail for them, for these never get cached, and thus get checked again on every iteration
93+ -- but maybe that's fine? since we'd hope there aren't many of them
94+ -- TODO inline `getIndividualVersion` and add better error reporting?
95+ _ -> fmap ((p, t,) . snd ) <$> getIndividualVersion pkgConfig p
96+ writeCache r
97+ pure $ pkgConfigDbFromList $ map (\ (p, _t, v) -> (p, v)) r
7998 where
8099 -- For when pkg-config invocation fails (possibly because of a
81100 -- too long command line).
@@ -85,6 +104,18 @@ readPkgConfigDb verbosity progdb = handle ioErrorHandler $ do
85104 ++ extra)
86105 return NoPkgConfigDb
87106
107+ -- TODO vendored from `extra` - what should I do?
108+ splitOn :: (Eq a ) => [a ] -> [a ] -> [[a ]]
109+ splitOn [] _ = error " splitOn, needle may not be empty"
110+ splitOn _ [] = [[] ]
111+ splitOn needle haystack = a : if null b then [] else splitOn needle $ drop (length needle) b
112+ where
113+ (a, b) = breakOn needle haystack
114+ breakOn :: Eq a => [a ] -> [a ] -> ([a ], [a ])
115+ breakOn needle haystack | needle `isPrefixOf` haystack = ([] , haystack)
116+ breakOn _ [] = ([] , [] )
117+ breakOn needle (x: xs) = first (x: ) $ breakOn needle xs
118+
88119 ioErrorHandler :: IOException -> IO PkgConfigDb
89120 ioErrorHandler e = noPkgConfig (show e)
90121
0 commit comments