223 lines
6.7 KiB
Haskell
223 lines
6.7 KiB
Haskell
{-# LANGUAGE CPP, BangPatterns, DoAndIfThenElse, TupleSections #-}
|
|
|
|
module Language.Haskell.GhcMod.Find
|
|
#ifndef SPEC
|
|
( Symbol
|
|
, SymbolDb
|
|
, loadSymbolDb
|
|
, lookupSymbol
|
|
, dumpSymbol
|
|
, findSymbol
|
|
, lookupSym
|
|
, isOutdated
|
|
-- * Load 'SymbolDb' asynchronously
|
|
, AsyncSymbolDb
|
|
, newAsyncSymbolDb
|
|
, getAsyncSymbolDb
|
|
)
|
|
#endif
|
|
where
|
|
|
|
import Control.Applicative
|
|
import Control.Monad
|
|
import Control.Exception
|
|
import Control.Concurrent
|
|
import Data.List
|
|
import Data.Binary
|
|
import Data.IORef
|
|
import qualified Data.ByteString as BS
|
|
import qualified Data.ByteString.Lazy as LBS
|
|
import qualified GHC as G
|
|
import FastString
|
|
import Module
|
|
import OccName
|
|
import HscTypes
|
|
import Language.Haskell.GhcMod.Convert
|
|
import Language.Haskell.GhcMod.Gap
|
|
import Language.Haskell.GhcMod.Monad
|
|
import Language.Haskell.GhcMod.Output
|
|
import Language.Haskell.GhcMod.PathsAndFiles
|
|
import Language.Haskell.GhcMod.Types
|
|
import Language.Haskell.GhcMod.Utils
|
|
import Language.Haskell.GhcMod.World
|
|
import Language.Haskell.GhcMod.Target
|
|
import Language.Haskell.GhcMod.LightGhc
|
|
|
|
import Exception
|
|
|
|
import Control.Monad.Trans.Control
|
|
import Data.Function
|
|
import System.Directory
|
|
import System.Directory.ModTime
|
|
import System.FilePath ((</>))
|
|
import System.IO
|
|
import System.IO.Unsafe
|
|
import Prelude
|
|
|
|
import Data.Map (Map)
|
|
import qualified Data.Map as M
|
|
import Data.Set (Set)
|
|
import qualified Data.Set as S
|
|
|
|
----------------------------------------------------------------
|
|
|
|
-- | Type of function and operation names.
|
|
type Symbol = BS.ByteString
|
|
type ModuleNameBS = BS.ByteString
|
|
|
|
-- | Database from 'Symbol' to \['ModuleString'\].
|
|
data SymbolDb = SymbolDb
|
|
{ table :: Map Symbol [ModuleNameBS]
|
|
, symbolDbCachePath :: FilePath
|
|
}
|
|
|
|
isOutdated :: IOish m => SymbolDb -> GhcModT m Bool
|
|
isOutdated db =
|
|
(liftIO . isOlderThan (symbolDbCachePath db)) =<< timedPackageCaches
|
|
|
|
----------------------------------------------------------------
|
|
|
|
-- | Looking up 'SymbolDb' with 'Symbol' to \['ModuleString'\]
|
|
-- which will be concatenated. 'loadSymbolDb' is called internally.
|
|
findSymbol :: IOish m => String -> GhcModT m String
|
|
findSymbol sym = do
|
|
tmpdir <- cradleTempDir <$> cradle
|
|
loadSymbolDb tmpdir >>= lookupSymbol sym
|
|
|
|
-- | Looking up 'SymbolDb' with 'Symbol' to \['ModuleString'\]
|
|
-- which will be concatenated.
|
|
lookupSymbol :: IOish m => String -> SymbolDb -> GhcModT m String
|
|
lookupSymbol sym db = convert' $ lookupSym (fastStringToByteString $ mkFastString sym) db
|
|
|
|
lookupSym :: Symbol -> SymbolDb -> [ModuleString]
|
|
lookupSym sym db = map (ModuleString . unpackFS . mkFastStringByteString') $ M.findWithDefault [] sym $ table db
|
|
|
|
---------------------------------------------------------------
|
|
|
|
-- | Loading a file and creates 'SymbolDb'.
|
|
loadSymbolDb :: IOish m => FilePath -> GhcModT m SymbolDb
|
|
loadSymbolDb dir = do
|
|
ghcMod <- liftIO ghcModExecutable
|
|
readProc <- gmReadProcess
|
|
file <- liftIO $ chop <$> readProc ghcMod ["dumpsym", dir] ""
|
|
!db <- M.fromList . decode <$> liftIO (LBS.readFile file)
|
|
return $ SymbolDb
|
|
{ table = db
|
|
, symbolDbCachePath = file
|
|
}
|
|
where
|
|
chop :: String -> String
|
|
chop "" = ""
|
|
chop xs = init xs
|
|
|
|
----------------------------------------------------------------
|
|
-- used 'ghc-mod dumpsym'
|
|
|
|
-- | Dumping a set of ('Symbol',\['ModuleString'\]) to a file
|
|
-- if the file does not exist or is invalid.
|
|
-- The file name is printed.
|
|
|
|
dumpSymbol :: IOish m => FilePath -> GhcModT m String
|
|
dumpSymbol dir = do
|
|
create <- (liftIO . isOlderThan cache) =<< timedPackageCaches
|
|
pkgOpts <- packageGhcOptions
|
|
when create $ liftIO $ do
|
|
withLightHscEnv pkgOpts $ \env -> do
|
|
writeSymbolCache cache =<< getGlobalSymbolTable env
|
|
|
|
return $ unlines [cache]
|
|
where
|
|
cache = dir </> symbolCacheFile
|
|
|
|
writeSymbolCache :: FilePath
|
|
-> Map Symbol (Set ModuleNameBS)
|
|
-> IO ()
|
|
writeSymbolCache cache sm =
|
|
void . withFile cache WriteMode $ \hdl ->
|
|
LBS.hPutStr hdl (encode sm)
|
|
|
|
-- | Check whether given file is older than any file from the given set.
|
|
-- Returns True if given file does not exist.
|
|
isOlderThan :: FilePath -> [TimedFile] -> IO Bool
|
|
isOlderThan cache files = do
|
|
exist <- doesFileExist cache
|
|
if not exist
|
|
then return True
|
|
else do
|
|
tCache <- getModTime cache
|
|
return $ any (tCache <=) $ map tfTime files -- including equal just in case
|
|
|
|
-- | Browsing all functions in all system modules.
|
|
getGlobalSymbolTable :: HscEnv -> IO (Map Symbol (Set ModuleNameBS))
|
|
getGlobalSymbolTable hsc_env =
|
|
foldM (extend hsc_env) M.empty $ listVisibleModules $ hsc_dflags hsc_env
|
|
|
|
extend :: HscEnv
|
|
-> Map Symbol (Set ModuleNameBS)
|
|
-> Module
|
|
-> IO (Map Symbol (Set ModuleNameBS))
|
|
extend hsc_env mm mdl = do
|
|
eps <- readIORef $ hsc_EPS hsc_env
|
|
modinfo <- unsafeInterleaveIO $ runLightGhc hsc_env $ do
|
|
G.getModuleInfo mdl <* liftIO (writeIORef (hsc_EPS hsc_env) eps)
|
|
|
|
return $ M.unionWith S.union mm $ extractBindings modinfo mdl
|
|
|
|
extractBindings :: Maybe G.ModuleInfo
|
|
-> G.Module
|
|
-> Map Symbol (Set ModuleNameBS)
|
|
extractBindings Nothing _ = M.empty
|
|
extractBindings (Just inf) mdl = M.fromList $ do
|
|
name <- G.modInfoExports inf
|
|
let sym = fastStringToByteString $ occNameFS $ G.getOccName name
|
|
mdls = S.singleton $ fastStringToByteString $ moduleNameFS $ moduleName mdl
|
|
return (sym, mdls)
|
|
|
|
mkFastStringByteString' :: BS.ByteString -> FastString
|
|
#if !MIN_VERSION_ghc(7,8,0)
|
|
fastStringToByteString :: FastString -> BS.ByteString
|
|
fastStringToByteString = BS.pack . bytesFS
|
|
|
|
mkFastStringByteString' = mkFastStringByteList . BS.unpack
|
|
#elif __GLASGOW_HASKELL__ == 708
|
|
mkFastStringByteString' = unsafePerformIO . mkFastStringByteString
|
|
#else
|
|
mkFastStringByteString' = mkFastStringByteString
|
|
#endif
|
|
|
|
----------------------------------------------------------------
|
|
|
|
data AsyncSymbolDb = AsyncSymbolDb FilePath (MVar (Either SomeException SymbolDb))
|
|
|
|
asyncLoadSymbolDb :: IOish m
|
|
=> FilePath
|
|
-> MVar (Either SomeException SymbolDb)
|
|
-> GhcModT m ()
|
|
asyncLoadSymbolDb tmpdir mv = void $
|
|
liftBaseWith $ \run -> forkIO $ void $ run $ do
|
|
edb <- gtry $ loadSymbolDb tmpdir
|
|
liftIO $ putMVar mv edb
|
|
|
|
newAsyncSymbolDb :: IOish m => FilePath -> GhcModT m AsyncSymbolDb
|
|
newAsyncSymbolDb tmpdir = do
|
|
mv <- liftIO newEmptyMVar
|
|
asyncLoadSymbolDb tmpdir mv
|
|
return $ AsyncSymbolDb tmpdir mv
|
|
|
|
getAsyncSymbolDb :: forall m. IOish m => AsyncSymbolDb -> GhcModT m SymbolDb
|
|
getAsyncSymbolDb (AsyncSymbolDb tmpdir mv) = do
|
|
db <- liftIO $ handleEx <$> takeMVar mv
|
|
outdated <- isOutdated db
|
|
if outdated
|
|
then do
|
|
asyncLoadSymbolDb tmpdir mv
|
|
liftIO $ handleEx <$> readMVar mv
|
|
else do
|
|
liftIO $ putMVar mv $ Right db
|
|
return db
|
|
where
|
|
handleEx edb =
|
|
case edb of
|
|
Left ex -> throw ex
|
|
Right db -> db
|