module System.Plugins.Env (
env,
withModEnv,
withDepEnv,
withPkgEnvs,
withMerged,
modifyModEnv,
modifyDepEnv,
modifyPkgEnv,
modifyMerged,
addModule,
rmModule,
addModules,
isLoaded,
loaded,
addModuleDeps,
getModuleDeps,
rmModuleDeps,
isMerged,
lookupMerged,
addMerge,
addPkgConf,
union,
addStaticPkg,
isStaticPkg,
rmStaticPkg,
grabDefaultPkgConf,
readPackageConf,
lookupPkg
) where
#include "../../../config.h"
import System.Plugins.LoadTypes (Module)
import System.Plugins.Consts ( sysPkgSuffix )
import Control.Monad ( liftM )
import Data.IORef ( writeIORef, readIORef, newIORef, IORef() )
import Data.Maybe ( isJust, isNothing, fromMaybe )
import Data.List ( (\\), nub, )
import System.IO.Unsafe ( unsafePerformIO )
import System.Directory ( doesFileExist )
#if defined(CYGWIN) || defined(__MINGW32__)
import Prelude hiding ( catch, ioError )
import System.IO.Error ( catch, ioError, isDoesNotExistError )
#endif
import Control.Concurrent.MVar ( MVar(), newMVar, withMVar )
import GHC.Paths (libdir)
import DynFlags (
#if MIN_VERSION_ghc(7,8,0)
Way(WayDyn), dynamicGhc, ways,
#endif
defaultDynFlags, initDynFlags)
import SysTools (initSysTools)
import Distribution.Package hiding (
#if MIN_VERSION_ghc(7,6,0)
Module,
#endif
depends, packageName, PackageName(..)
#if MIN_VERSION_ghc(7,10,0)
, installedPackageId
#endif
)
import Distribution.Text
import Distribution.InstalledPackageInfo
import Distribution.Simple.Compiler
import Distribution.Simple.GHC
import Distribution.Simple.PackageIndex
import Distribution.Simple.Program
import Distribution.Verbosity
import qualified Data.Map as M
import qualified Data.Set as S
type FiniteMap k e = M.Map k e
emptyFM :: FiniteMap key elt
emptyFM = M.empty
addToFM :: (Ord key) => FiniteMap key elt -> key -> elt -> FiniteMap key elt
addToFM = \m k e -> M.insert k e m
addWithFM :: (Ord key)
=> (elt -> elt -> elt) -> FiniteMap key elt -> key -> elt -> FiniteMap key elt
addWithFM = \comb m k e -> M.insertWith comb k e m
delFromFM :: (Ord key) => FiniteMap key elt -> key -> FiniteMap key elt
delFromFM = flip M.delete
lookupFM :: (Ord key) => FiniteMap key elt -> key -> Maybe elt
lookupFM = flip M.lookup
type ModEnv = FiniteMap String (Module,Int)
type DepEnv = FiniteMap Module [Module]
type PkgEnv = FiniteMap PackageName PackageConfig
type StaticPkgEnv = S.Set PackageName
type MergeEnv = FiniteMap (FilePath,FilePath) FilePath
type PkgEnvs = [PkgEnv]
type Env = (MVar (),
IORef ModEnv,
IORef DepEnv,
IORef PkgEnvs,
IORef StaticPkgEnv,
IORef MergeEnv)
env = unsafePerformIO $ do
mvar <- newMVar ()
ref1 <- newIORef emptyFM
ref2 <- newIORef emptyFM
p <- grabDefaultPkgConf
ref3 <- newIORef p
ref4 <- newIORef (S.fromList ["base","Cabal","haskell-src", "containers",
"arrays", "directory", "random", "process",
"ghc", "ghc-prim"])
ref5 <- newIORef emptyFM
return (mvar, ref1, ref2, ref3, ref4, ref5)
withModEnv :: Env -> (ModEnv -> IO a) -> IO a
withDepEnv :: Env -> (DepEnv -> IO a) -> IO a
withPkgEnvs :: Env -> (PkgEnvs -> IO a) -> IO a
withStaticPkgEnv :: Env -> (StaticPkgEnv -> IO a) -> IO a
withMerged :: Env -> (MergeEnv -> IO a) -> IO a
withModEnv (mvar,ref,_,_,_,_) f = withMVar mvar (\_ -> readIORef ref >>= f)
withDepEnv (mvar,_,ref,_,_,_) f = withMVar mvar (\_ -> readIORef ref >>= f)
withPkgEnvs (mvar,_,_,ref,_,_) f = withMVar mvar (\_ -> readIORef ref >>= f)
withStaticPkgEnv (mvar,_,_,_,ref,_) f = withMVar mvar (\_ -> readIORef ref >>= f)
withMerged (mvar,_,_,_,_,ref) f = withMVar mvar (\_ -> readIORef ref >>= f)
modifyModEnv :: Env -> (ModEnv -> IO ModEnv) -> IO ()
modifyDepEnv :: Env -> (DepEnv -> IO DepEnv) -> IO ()
modifyPkgEnv :: Env -> (PkgEnvs -> IO PkgEnvs) -> IO ()
modifyStaticPkgEnv :: Env -> (StaticPkgEnv -> IO StaticPkgEnv) -> IO ()
modifyMerged :: Env -> (MergeEnv -> IO MergeEnv)-> IO ()
modifyModEnv (mvar,ref,_,_,_,_) f = lockAndWrite mvar ref f
modifyDepEnv (mvar,_,ref,_,_,_) f = lockAndWrite mvar ref f
modifyPkgEnv (mvar,_,_,ref,_,_) f = lockAndWrite mvar ref f
modifyStaticPkgEnv (mvar,_,_,_,ref,_) f = lockAndWrite mvar ref f
modifyMerged (mvar,_,_,_,_,ref) f = lockAndWrite mvar ref f
lockAndWrite mvar ref f = withMVar mvar (\_->readIORef ref>>=f>>=writeIORef ref)
addModule :: String -> Module -> IO ()
addModule s m = modifyModEnv env $ \fm -> let c = maybe 0 snd (lookupFM fm s)
in return $ addToFM fm s (m,c+1)
rmModule :: String -> IO Bool
rmModule s = do modifyModEnv env $ \fm -> let c = maybe 1 snd (lookupFM fm s)
fm' = delFromFM fm s
in if c1 <= 0
then return fm'
else return fm
withModEnv env $ \fm -> return (isNothing (lookupFM fm s))
addModules :: [(String,Module)] -> IO ()
addModules ns = mapM_ (uncurry addModule) ns
isLoaded :: String -> IO Bool
isLoaded s = withModEnv env $ \fm -> return $ isJust (lookupFM fm s)
loaded :: String -> IO Bool
loaded m = do t <- isLoaded m ; return (not t)
addModuleDeps :: Module -> [Module] -> IO ()
addModuleDeps m deps = modifyDepEnv env $ \fm -> return $ addToFM fm m deps
getModuleDeps :: Module -> IO [Module]
getModuleDeps m = withDepEnv env $ \fm -> return $ fromMaybe [] (lookupFM fm m)
rmModuleDeps :: Module -> IO ()
rmModuleDeps m = modifyDepEnv env $ \fm -> return $ delFromFM fm m
addPkgConf :: FilePath -> IO ()
addPkgConf f = do
ps <- readPackageConf f
modifyPkgEnv env $ \ls -> return $ union ls ps
union :: PkgEnvs -> [PackageConfig] -> PkgEnvs
union ls ps' =
let fm = emptyFM
in foldr addOnePkg fm ps' : ls
where
addOnePkg p fm' = addToPkgEnvs (addToPkgEnvs (addToPkgEnvs fm' (display $ sourcePackageId p) p) (display $ installedPackageId p) p)
(packageName p) p
addToPkgEnvs = addWithFM higherVersion
higherVersion pkgconf1 pkgconf2
| installedPackageId pkgconf1 >= installedPackageId pkgconf2 = pkgconf1
| otherwise = pkgconf2
grabDefaultPkgConf :: IO PkgEnvs
grabDefaultPkgConf = do
pc <- configureAllKnownPrograms silent defaultProgramConfiguration
#if MIN_VERSION_Cabal(1,24,0)
(compiler, _platform, _programConfiguration)
<- configure silent Nothing Nothing pc
pkgIndex <- getInstalledPackages silent compiler
[GlobalPackageDB, UserPackageDB] pc
#else
pkgIndex <- getInstalledPackages silent [GlobalPackageDB, UserPackageDB] pc
#endif
return $ [] `union` allPackages pkgIndex
readPackageConf :: FilePath -> IO [PackageConfig]
readPackageConf f = do
pc <- configureAllKnownPrograms silent defaultProgramConfiguration
#if MIN_VERSION_Cabal(1,24,0)
(compiler, _platform, _programConfiguration)
<- configure silent Nothing Nothing pc
pkgIndex <- getInstalledPackages silent compiler [GlobalPackageDB, UserPackageDB, SpecificPackageDB f] pc
#else
pkgIndex <- getInstalledPackages silent [GlobalPackageDB, UserPackageDB, SpecificPackageDB f] pc
#endif
return $ allPackages pkgIndex
addStaticPkg :: PackageName -> IO ()
addStaticPkg pkg = modifyStaticPkgEnv env $ \set -> return $ S.insert pkg set
isStaticPkg :: PackageName -> IO Bool
isStaticPkg pkg = withStaticPkgEnv env $ \set -> return $ S.member pkg set
rmStaticPkg :: String -> IO Bool
rmStaticPkg pkg = do
(willRemove, s) <- withStaticPkgEnv env $ \s -> return (S.member pkg s, s)
if not willRemove then return False
else do modifyStaticPkgEnv env $ \s' -> return $ S.delete pkg s'
return True
lookupPkg :: PackageName -> IO ([FilePath],[FilePath])
lookupPkg pn = go [] pn
where
go :: [PackageName] -> PackageName -> IO ([FilePath],[FilePath])
go seen p = do
(ps, (f, g)) <- lookupPkg' p
static <- if not (null f) && null g
then addStaticPkg p >> return True
else isStaticPkg p
(f', g') <- liftM unzip $ mapM (go (nub $ seen ++ ps)) (ps \\ seen)
return $ (nub $ (concat f') ++ f, if static then [] else nub $ (concat g') ++ g)
data LibrarySpec
= DLL String
| DLLPath FilePath
classifyLdInput :: FilePath -> IO (Maybe LibrarySpec)
classifyLdInput ('-':'l':lib) = return (Just (DLL lib))
classifyLdInput ('-':'L':path) = return (Just (DLLPath path))
classifyLdInput _ = return Nothing
#if defined(MACOSX)
mkSOName root = "lib" ++ root ++ ".dylib"
#elif defined(CYGWIN) || defined(__MINGW32__)
mkSOName root = root
#else
mkSOName root = "lib" ++ root ++ ".so"
#endif
#if defined(MACOSX)
mkDynPkgName root = mkSOName (root ++ "_dyn")
#else
mkDynPkgName root = mkSOName root
#endif
data HSLib = Static FilePath | Dynamic FilePath
lookupPkg' :: PackageName -> IO ([PackageName],([FilePath],[FilePath]))
lookupPkg' p = withPkgEnvs env $ \fms -> go fms p
where
go [] _ = return ([],([],[]))
go (fm:fms) q = case lookupFM fm q of
Nothing -> go fms q
Just pkg -> do
let hslibs = hsLibraries pkg
extras' = extraLibraries pkg
cbits = filter (\e -> reverse (take (length "_cbits") (reverse e)) == "_cbits") extras'
extras = filter (flip notElem cbits) extras'
ldopts = ldOptions pkg
deppkgs = packageDeps pkg
ldInput <- mapM classifyLdInput ldopts
let ldOptsLibs = [ path | Just (DLL path) <- ldInput ]
ldOptsPaths = [ path | Just (DLLPath path) <- ldInput ]
dlls = map mkSOName (extras ++ ldOptsLibs)
#if defined(CYGWIN) || defined(__MINGW32__)
libdirs = fix_topdir (libraryDirs pkg) ++ ldOptsPaths
#else
libdirs = libraryDirs pkg ++ ldOptsPaths
#endif
settings <- initSysTools (Just libdir)
dflags <- initDynFlags $ defaultDynFlags settings
libs <- mapM (findHSlib
#if MIN_VERSION_ghc(7,8,0)
(WayDyn `elem` ways dflags || dynamicGhc)
#else
False
#endif
libdirs)
(cbits ++ hslibs)
#if defined(CYGWIN) || defined(__MINGW32__)
windowsos <- catch (getEnv "OS")
(\e -> if isDoesNotExistError e then return "Windows_98" else ioError e)
windowsdir <-
if windowsos == "Windows_9X"
then return "C:/windows"
else return "C:/winnt"
sysroot <- catch (getEnv "SYSTEMROOT")
(\e -> if isDoesNotExistError e then return windowsdir else ioError e)
let syslibdir = sysroot ++ (if windowsos == "Windows_9X" then "/SYSTEM" else "/SYSTEM32")
libs' <- mapM (findDLL $ syslibdir : libdirs) dlls
#else
libs' <- mapM (findDLL libdirs) dlls
#endif
let slibs = [ lib | Right (Static lib) <- libs ]
dlibs = [ lib | Right (Dynamic lib) <- libs ]
return (deppkgs, (slibs,map (either id id) libs' ++ dlibs) )
#if defined(CYGWIN) || defined(__MINGW32__)
fix_topdir [] = []
fix_topdir (x:xs) = replace_topdir x : fix_topdir xs
replace_topdir [] = []
replace_topdir ('$':xs)
| take 6 xs == "topdir" = ghcLibraryPath ++ (drop 6 xs)
| otherwise = '$' : replace_topdir xs
replace_topdir (x:xs) = x : replace_topdir xs
#endif
findHSlib' :: [FilePath] -> String -> IO (Maybe FilePath)
findHSlib' [] _ = return Nothing
findHSlib' (dir:dirs) lib = do
let l = dir </> lib
b <- doesFileExist l
if b then return $ Just l
else findHSlib' dirs lib
findHSslib dirs lib = findHSlib' dirs $ "lib" ++ lib ++ sysPkgSuffix
findHSdlib dirs lib = findHSlib' dirs $ mkDynPkgName lib
findHSlib :: Bool -> [FilePath] -> String -> IO (Either String HSLib)
findHSlib dynonly dirs lib = do
dl <- findHSdlib dirs lib
let rdl = case dl of
Just file -> Right $ Dynamic file
Nothing -> Left lib
if dynonly then return rdl else do
rsl <- findHSslib dirs lib
return $ case rsl of
Just file -> Right $ Static file
Nothing -> rdl
findDLL :: [FilePath] -> String -> IO (Either String FilePath)
findDLL [] lib = return (Left lib)
findDLL (dir:dirs) lib = do
let l = dir </> lib
b <- doesFileExist l
if b then return $ Right l
else findDLL dirs lib
isMerged :: FilePath -> FilePath -> IO Bool
isMerged a b = withMerged env $ \fm -> return $ isJust (lookupFM fm (a,b))
lookupMerged :: FilePath -> FilePath -> IO (Maybe FilePath)
lookupMerged a b = withMerged env $ \fm -> return $ lookupFM fm (a,b)
addMerge :: FilePath -> FilePath -> FilePath -> IO ()
addMerge a b z = modifyMerged env $ \fm -> return $ addToFM fm (a,b) z
(</>) :: FilePath -> FilePath -> FilePath
[] </> b = b
a </> b = a ++ "/" ++ b
packageName :: PackageConfig -> PackageName
packageDeps :: PackageConfig -> [PackageName]
type PackageName = String
type PackageConfig = InstalledPackageInfo
packageName = display . pkgName . sourcePackageId
packageDeps = (map display) . depends