Search code examples
haskellghccabalhackage

How to programmatically retrieve GHC package information?


More specifically, given an arbritary package name I need to retrieve the same library-dirs field that can be obtained with the ghc-pkg describe command from inside a running Haskell program.


Solution

  • Here's what I could come up with by peeking into the ghc-pkg source code.

    The getPkgInfos function returns the package definitions for all installed packages (hopefully including user-installed packages). With this in your hands, you can retrieve the library directories and other package information. See the documentation for details.

    The GHC_PKGCONF variable needs to point to the global package config file for systems where it isn't located at the usual place. ghc-pkg solves this problem by receiving a command line flag via a wrapper script in Ubuntu, for instance.

    import qualified Config
    import qualified System.Info
    import Data.List
    import Distribution.InstalledPackageInfo
    import GHC.Paths
    import System.Directory
    import System.Environment
    import System.FilePath
    import System.IO.Error
    
    getPkgInfos :: IO [InstalledPackageInfo]
    getPkgInfos = do
        global_conf <-
            catch (getEnv "GHC_PKGCONF")
                  (\err ->  if isDoesNotExistError err
                                then do let dir = takeDirectory $ takeDirectory ghc_pkg
                                            path1 = dir </> "package.conf"
                                            path2 = dir </> ".." </> ".." </> ".."
                                                        </> "inplace-datadir"
                                                        </> "package.conf"
                                        exists1 <- doesFileExist path1
                                        exists2 <- doesFileExist path2
                                        if exists1 then return path1
                                           else if exists2 then return path2
                                           else ioError $ userError "Can't find package.conf"
                                else ioError err)
    
        let global_conf_dir = global_conf ++ ".d"
        global_conf_dir_exists <- doesDirectoryExist global_conf_dir
        global_confs <-
            if global_conf_dir_exists
                then do files <- getDirectoryContents global_conf_dir
                        return  [ global_conf_dir ++ '/' : file
                                | file <- files
                                , isSuffixOf ".conf" file]
                else return []
    
        user_conf <-
            try (getAppUserDataDirectory "ghc") >>= either
                (\_ -> return [])
                (\appdir -> do
                    let subdir = currentArch ++ '-':currentOS ++ '-':ghcVersion
                        user_conf = appdir </> subdir </> "package.conf"
                    user_exists <- doesFileExist user_conf
                    return (if user_exists then [user_conf] else []))
    
        let pkg_dbs = user_conf ++ global_confs ++ [global_conf]
        return.concat =<< mapM ((>>= return.read).readFile) pkg_dbs
    
    currentArch = System.Info.arch
    currentOS = System.Info.os
    ghcVersion = Config.cProjectVersion
    

    I wrote this code myself, but it was largely inspired by ghc-pkg (with some pieces copied verbatim). The original code was licensed under a BSD-style license, I think this can be distributed under the cc-wiki license all Stackoverflow content is under, but I'm not really sure. Anyway, as anything else, I did some initial testing and it seems to work, but use it at your own risk.