module UHC.Light.Compiler.Base.PackageDatabase
( pkgDbLookup
, pkgWritePkgOptionAsCfg
, pkgDbFromDirs
, pkgDbSelectBySearchFilter
, PkgModulePartition
, pkgPartInclDirs
, pkgExposedPackages
, pkgDbFreeze
, fileLocSearch )
where
import UHC.Light.Compiler.Base.Common
import UHC.Light.Compiler.Opts
import UHC.Light.Compiler.Error
import qualified UHC.Light.Compiler.Config as Cfg
import UU.Parsing
import UHC.Util.ParseUtils
import UHC.Light.Compiler.Base.Parser2
import UHC.Util.ScanUtils
import UHC.Light.Compiler.Scanner.Common
import UHC.Light.Compiler.Base.HsName
import UHC.Light.Compiler.Base.ParseUtils
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.Version
import Data.List
import Data.Maybe
import Data.Char
import System.Environment
import System.Directory
import Control.Monad
import Control.Monad.State
import System.IO
import System.Exit
import System.Environment
import UHC.Util.FPath
import UHC.Util.Utils
import UHC.Util.Debug
pkgCfgFPath :: Bool -> FPath -> IO (Maybe (FPath, FilePath))
pkgCfgFPath createIfAbsent pkgfp = do
when createIfAbsent $ createDirectoryIfMissing True pkgdir
isDir <- doesDirectoryExist pkgdir
if isDir
then return $ Just (fpathSetDir pkgdir $ fpathFromStr Cfg.ehcPkgConfigfileName, pkgdir)
else return Nothing
where pkgdir = fpathToStr pkgfp
pkgMpUnion :: PackageMp -> PackageMp -> PackageMp
pkgMpUnion
= Map.unionWith (Map.unionWith cmb)
where cmb p1s p2s = head $ groupBy eqp $ sortBy cmpp $ p1s ++ p2s
eqp p1 p2 = pkginfoOrder p1 == pkginfoOrder p2
cmpp p1 p2 = pkginfoOrder p1 `compare` pkginfoOrder p2
pkgMpLookup :: PkgKey -> PackageMp -> Maybe PackageInfo
pkgMpLookup (k1,k2) m1
= fmap head $ mapLookup2 k1 k2 m1
pkgMpFindMin :: PackageMp -> PackageInfo
pkgMpFindMin m1
= head ps
where (_,m2) = Map.findMin m1
(_,ps) = Map.findMin m2
pkgMpDifference :: PackageMp -> PackageMp -> PackageMp
pkgMpDifference mp mpDiff
= foldr diff mp $ Map.toList mpDiff
where diff (k1,mp2Diff) mp
= case Map.lookup k1 mp of
Just mp2 | Map.null m -> Map.delete k1 mp
| otherwise -> Map.insert k1 m mp
where m = Map.difference mp2 mp2Diff
_ -> mp
pkgMpUnions :: [PackageMp] -> PackageMp
pkgMpUnions = foldr pkgMpUnion Map.empty
pkgMpSingletonL :: PkgKey -> [PackageInfo] -> PackageMp
pkgMpSingletonL (k1,k2) i = Map.singleton k1 (Map.singleton k2 i)
pkgMpSingleton :: PkgKey -> PackageInfo -> PackageMp
pkgMpSingleton k i = pkgMpSingletonL k [i]
mod2pkgMpUnion :: Module2PackageMp -> Module2PackageMp -> Module2PackageMp
mod2pkgMpUnion = Map.unionWith (++)
mod2pkgMpUnions :: [Module2PackageMp] -> Module2PackageMp
mod2pkgMpUnions = foldr mod2pkgMpUnion Map.empty
pkgDbLookup:: PkgKey -> PackageDatabase -> Maybe PackageInfo
pkgDbLookup key db
= pkgMpLookup key $ pkgDbPkgMp db
pkgMpSelectMpOnKey :: PkgKey -> PackageMp -> PackageMp
pkgMpSelectMpOnKey key@(pkgNm,mbVersion) mp
= case mbVersion of
Just pkgVersion -> case lkup of
Just m -> maybe emptyPackageMp (\i -> pkgMpSingletonL key i) $ Map.lookup mbVersion m
_ -> emptyPackageMp
_ -> maybe emptyPackageMp (Map.singleton pkgNm) lkup
where lkup = Map.lookup pkgNm mp
pkgDbSelectMpOnKey :: PkgKey -> PackageDatabase -> PackageMp
pkgDbSelectMpOnKey key@(pkgNm,mbVersion) db = pkgMpSelectMpOnKey key $ pkgDbPkgMp db
pkgWritePkgOptionAsCfg :: PkgOption -> FPath -> IO ()
pkgWritePkgOptionAsCfg pkgopt pkgfp
= do mbCfgFP@(~(Just (cfgFP, pkgdir))) <- pkgCfgFPath True pkgfp
if isJust mbCfgFP
then writeFile (fpathToStr cfgFP) $ unlines $ map (\(k,v) -> k ++ ": " ++ v)
[ ("exposed-modules", unwords $ pkgoptExposedModules pkgopt )
, ("build-depends" , unwords $ pkgoptBuildDepends pkgopt )
]
else return ()
pkgCfgParse :: String -> [PackageInfo -> PackageInfo]
pkgCfgParse s
= map (\(k,v) -> case map toLower k of
"exposed-modules" -> add (\ns i -> i {pkginfoExposedModules = Set.fromList ns}) parseModuleNames v
"build-depends" -> add (\ns i -> i {pkginfoBuildDepends = Set.fromList ns}) parsePkgKeys v
"exposed" -> add (\ex i -> i {pkginfoIsExposed = ex }) parseBool v
_ -> id
) $ Map.toList kvs
where add upd parse v = maybe id upd $ parse v
(kvs,_) = foldr p (Map.empty,"") $ lines s
p s (kvs,saccum)
= case elemIndex ':' s of
Just colpos -> (Map.insert k (appendaccum v) kvs, "")
where (k,_:v) = splitAt colpos s
_ -> (kvs,appendaccum s)
where appendaccum s | null saccum = s
| otherwise = s ++ " " ++ saccum
pModuleNames :: P [HsName]
pModuleNames = pList (tokMkQName <$> (pQConidTk <|> pConidTk))
parseModuleNames :: String -> Maybe [HsName]
parseModuleNames = parseString (hsScanOpts defaultEHCOpts) pModuleNames
pBool :: P Bool
pBool = True <$ pKeyTk "True" <|> False <$ pKeyTk "False"
parseBool :: String -> Maybe Bool
parseBool = parseString (defaultScanOpts {scoKeywordsTxt = Set.fromList ["True","False"]}) pBool
pkgMpFromDirFile :: EHCOpts -> PkgKey -> Int -> FPath -> IO PackageMp
pkgMpFromDirFile opts pkgkey order pkgfp
= do
mbCfgFP@(~(Just (cfgFP, pkgdir))) <- pkgCfgFPath False pkgfp
if isJust mbCfgFP
then do { let fpCfg = fpathToStr cfgFP
pkgInfo = PackageInfo (mkPkgFileLoc pkgkey pkgdir) order Set.empty Set.empty True
; cfgExists <- doesFileExist fpCfg
; pm <- if cfgExists
then do h <- openFile fpCfg ReadMode
cfg <- hGetContents h
let i = foldr ($) pkgInfo (pkgCfgParse cfg)
i `seq` hClose h
return $ pkgMpSingleton pkgkey i
else return $ pkgMpSingleton pkgkey pkgInfo
; return pm
}
else return Map.empty
pkgMpFromDir :: EHCOpts -> Int -> FilePath -> IO PackageMp
pkgMpFromDir opts order fp
= do { isDir <- doesDirectoryExist fp
; if isDir
then do { pkgWithVersions <- getDirectoryContents fp
; let pkgWithVersionsBases = catMaybes $
map (\f -> do { k <- parsePkgKey f
; return $ (k,fpathPrependDir fp $ fpathFromStr $
mkInternalPkgFileBase k (Cfg.installVariant opts)
(ehcOptTarget opts) (ehcOptTargetFlavor opts))
})
pkgWithVersions
; mps <- mapM (\ (k,f) -> pkgMpFromDirFile opts k order f) pkgWithVersionsBases
; let mpsu = pkgMpUnions mps
; return mpsu
}
else return Map.empty
}
pkgDbFromDirs :: EHCOpts -> [FilePath] -> IO PackageDatabase
pkgDbFromDirs opts fps
= do mps <- zipWithM (pkgMpFromDir opts) [1..] fps
let mpsu = pkgMpUnions mps
return (emptyPackageDatabase {pkgDbPkgMp = mpsu})
data SearchFilterState
= SearchFilterState
{ sfstMp :: PackageMp
, sfstErr :: [Err]
}
pkgDbSelectBySearchFilterM :: [PackageSearchFilter] -> PackageDatabase -> State SearchFilterState ()
pkgDbSelectBySearchFilterM searchFilters fullDb
= all searchFilters
where all searchFilters = forM_ searchFilters sel
sel PackageSearchFilter_HideAll = modify $ \st -> st {sfstMp = emptyPackageMp}
sel (PackageSearchFilter_HidePkg keys) = forM_ keys (one (\_ -> return ()) (flip pkgMpDifference) (\_ m -> m))
sel (PackageSearchFilter_ExposePkg keys) = forM_ keys (one onerec (\_ m -> m ) pkgMpUnion )
one onerec cmbPresent cmbAbsent k = do
let s = pkgDbSelectMpOnKey k fullDb
if Map.null s
then modify $ \st -> st {sfstErr = sfstErr st ++ [mkErr_NamesNotIntrod emptyRange "package" [mkHNm k]]}
else do
mp <- gets sfstMp
let inMp = pkgMpSelectMpOnKey k mp
if Map.null inMp
then do
modify $ \st -> st {sfstMp = cmbAbsent s mp}
onerec s
else
modify $ \st -> st {sfstMp = cmbPresent s mp}
onerec s = all [PackageSearchFilter_ExposePkg $ Set.toList $ pkginfoBuildDepends $ pkgMpFindMin s]
pkgDbSelectBySearchFilter :: [PackageSearchFilter] -> PackageDatabase -> (PackageDatabase, [Err])
pkgDbSelectBySearchFilter searchFilters fullDb
= (emptyPackageDatabase {pkgDbPkgMp = sfstMp st}, sfstErr st)
where st = execState (pkgDbSelectBySearchFilterM searchFilters fullDb) (SearchFilterState emptyPackageMp [])
type PkgModulePartition
= ( PkgKey
, String
, [HsName]
)
pkgPartInclDirs :: EHCOpts -> [PkgModulePartition] -> ([FilePath], Map.Map PkgKey (FilePath,[HsName]))
pkgPartInclDirs opts pkgKeyDirL
= ( [ mki kind dir | FileLoc kind dir <- ehcOptImportFileLocPath opts, not (null dir) ]
, Map.fromList $ catMaybes [ mkp p | p <- pkgKeyDirL ]
)
where mki (FileLocKind_Dir ) d = d
mki (FileLocKind_Pkg _ _) d = Cfg.mkPkgIncludeDir $ filePathMkPrefix d
mki FileLocKind_PkgDb d = Cfg.mkPkgIncludeDir $ filePathMkPrefix d
mkp (k,_,ms) = fmap (\d -> (k, (Cfg.mkPkgIncludeDir $ filePathMkPrefix $ filelocDir $ pkginfoLoc d, ms))) $ pkgDbLookup k $ ehcOptPkgDb opts
pkgExposedPackages :: PackageDatabase -> [PkgModulePartition]
pkgExposedPackages db
= [ ((k1,k2),"",[])
| (k1,mp1) <- Map.toList $ pkgDbPkgMp db
, (k2,is ) <- Map.toList mp1
, i <- is
, pkginfoIsExposed i
]
pkgDbFreeze :: PackageDatabase -> PackageDatabase
pkgDbFreeze db
= db {pkgDbMod2PkgMp = m2p}
where m2p
= mod2pkgMpUnions
[ mod2pkgMpUnions
[ Map.singleton m [(k1,k2)]
| m <- Set.toList $ pkginfoExposedModules i
]
| (k1,mp1) <- Map.toList $ pkgDbPkgMp db
, (k2,is) <- Map.toList mp1
, i <- is
]
pkgDbSearch :: PackageDatabase -> HsName -> Maybe (PkgKey,FilePath,[Err])
pkgDbSearch db modNm
= do pkgs <- mbPkgs
dirOf $ disambig pkgs
where mbPkgs = Map.lookup modNm $ pkgDbMod2PkgMp db
disambig pks
= case sortBy cmp pks of
[k] -> (k,[])
(k@(_,Nothing):_) -> (k,[])
ks@(k:_) -> (k,[rngLift emptyRange Err_AmbiguousNameRef "module" "package" modNm (map (mkHNm . strOf) ks)])
where cmp (_,Nothing) (_,Nothing) = EQ
cmp (_,Nothing) (_,_ ) = LT
cmp (_,k21 ) (_,k22 ) = compare k22 k21
pkgOf k = pkgMpLookup k $ pkgDbPkgMp db
strOf k = showPkgKey k ++ ": " ++ maybe "" (\i -> show (pkginfoLoc i) ++ ":" ++ show (pkginfoOrder i)) (pkgOf k)
dirOf (k,e) = fmap (\i -> (k,filelocDir $ pkginfoLoc i,e)) $ pkgOf k
fileLocSearch :: EHCOpts -> FileLoc -> HsName -> FPath -> [(FileLoc,FPath,[Err])]
fileLocSearch opts loc modNm fp
= case filelocKind loc of
FileLocKind_PkgDb
-> maybe [] srch $ pkgDbSearch (ehcOptPkgDb opts) modNm
where srch (k,d,e) = [ (mkPkgFileLoc k d',fp',e) | (d',fp') <- searchFPathFromLoc d fp ]
_ -> [ (loc,fp',[]) | (_,fp') <- searchFPathFromLoc (filelocDir loc) fp ]