2014-07-17 08:16:44 +00:00
|
|
|
{-# LANGUAGE CPP #-}
|
2014-04-24 08:02:50 +00:00
|
|
|
|
2014-07-17 14:15:02 +00:00
|
|
|
module Language.Haskell.GhcMod.Find
|
|
|
|
#ifndef SPEC
|
|
|
|
(
|
2014-07-16 09:14:12 +00:00
|
|
|
Symbol
|
|
|
|
, SymbolDb
|
2014-07-17 05:04:28 +00:00
|
|
|
, loadSymbolDb
|
2014-07-16 09:14:12 +00:00
|
|
|
, lookupSymbol
|
|
|
|
, dumpSymbol
|
|
|
|
, findSymbol
|
2014-07-17 14:15:02 +00:00
|
|
|
)
|
|
|
|
#endif
|
|
|
|
where
|
2014-04-24 08:02:50 +00:00
|
|
|
|
2014-07-16 09:14:12 +00:00
|
|
|
import Config (cProjectVersion,cTargetPlatformString)
|
|
|
|
import Control.Applicative ((<$>))
|
2014-07-17 03:37:38 +00:00
|
|
|
import Control.Exception (handle, SomeException(..))
|
2014-07-16 09:14:12 +00:00
|
|
|
import Control.Monad (when, void)
|
|
|
|
import CoreMonad (liftIO)
|
2014-04-24 08:02:50 +00:00
|
|
|
import Data.Function (on)
|
|
|
|
import Data.List (groupBy, sort)
|
2014-07-16 09:14:12 +00:00
|
|
|
import Data.List.Split (splitOn)
|
2014-04-24 08:02:50 +00:00
|
|
|
import Data.Maybe (fromMaybe)
|
2014-07-16 09:14:12 +00:00
|
|
|
import DynFlags (DynFlags(..), systemPackageConfig)
|
2014-07-17 05:40:35 +00:00
|
|
|
import Exception (ghandle, handleIO)
|
2014-04-24 08:02:50 +00:00
|
|
|
import qualified GHC as G
|
2014-05-11 22:40:00 +00:00
|
|
|
import Language.Haskell.GhcMod.Convert
|
2014-07-16 09:14:12 +00:00
|
|
|
import Language.Haskell.GhcMod.Monad
|
2014-04-24 12:08:45 +00:00
|
|
|
import Language.Haskell.GhcMod.Types
|
2014-07-16 09:14:12 +00:00
|
|
|
import Name (getOccString)
|
|
|
|
import System.Directory (doesDirectoryExist, getAppUserDataDirectory, doesFileExist, getModificationTime)
|
|
|
|
import System.FilePath ((</>))
|
|
|
|
import System.IO
|
|
|
|
import System.Process (readProcess)
|
2014-04-24 08:02:50 +00:00
|
|
|
|
|
|
|
#ifndef MIN_VERSION_containers
|
|
|
|
#define MIN_VERSION_containers(x,y,z) 1
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if MIN_VERSION_containers(0,5,0)
|
|
|
|
import Data.Map.Strict (Map)
|
|
|
|
import qualified Data.Map.Strict as M
|
|
|
|
#else
|
|
|
|
import Data.Map (Map)
|
|
|
|
import qualified Data.Map as M
|
|
|
|
#endif
|
|
|
|
|
2014-07-16 09:14:12 +00:00
|
|
|
----------------------------------------------------------------
|
|
|
|
|
2014-07-17 05:04:28 +00:00
|
|
|
-- | Type of function and operation names.
|
2014-04-24 08:02:50 +00:00
|
|
|
type Symbol = String
|
2014-07-17 05:04:28 +00:00
|
|
|
-- | Database from 'Symbol' to \['ModuleString'\].
|
2014-07-17 13:54:59 +00:00
|
|
|
newtype SymbolDb = SymbolDb (Map Symbol [ModuleString])
|
2014-07-16 09:14:12 +00:00
|
|
|
|
|
|
|
----------------------------------------------------------------
|
|
|
|
|
|
|
|
symbolCache :: String
|
|
|
|
symbolCache = "ghc-mod.cache"
|
|
|
|
|
|
|
|
packageCache :: String
|
|
|
|
packageCache = "package.cache"
|
|
|
|
|
|
|
|
packageConfDir :: String
|
|
|
|
packageConfDir = "package.conf.d"
|
|
|
|
|
|
|
|
----------------------------------------------------------------
|
2014-04-24 08:02:50 +00:00
|
|
|
|
2014-04-30 01:51:34 +00:00
|
|
|
-- | Finding modules to which the symbol belong.
|
2014-07-12 09:16:16 +00:00
|
|
|
findSymbol :: IOish m => Symbol -> GhcModT m String
|
2014-07-17 05:04:28 +00:00
|
|
|
findSymbol sym = convert' =<< lookupSymbol' sym <$> liftIO loadSymbolDb
|
2014-04-24 12:08:45 +00:00
|
|
|
|
2014-07-16 09:14:12 +00:00
|
|
|
lookupSymbol' :: Symbol -> SymbolDb -> [ModuleString]
|
|
|
|
lookupSymbol' sym (SymbolDb db) = fromMaybe [] (M.lookup sym db)
|
|
|
|
|
2014-07-17 05:04:28 +00:00
|
|
|
-- | Looking up 'SymbolDb' with 'Symbol' to \['ModuleString'\]
|
|
|
|
-- which will be concatenated.
|
2014-07-16 09:14:12 +00:00
|
|
|
lookupSymbol :: Options -> Symbol -> SymbolDb -> String
|
|
|
|
lookupSymbol opt sym db = convert opt $ lookupSymbol' sym db
|
|
|
|
|
|
|
|
---------------------------------------------------------------
|
|
|
|
|
2014-07-17 05:04:28 +00:00
|
|
|
-- | Loading a file and creates 'SymbolDb'.
|
|
|
|
loadSymbolDb :: IO SymbolDb
|
|
|
|
loadSymbolDb = SymbolDb <$> readSymbolDb
|
2014-07-16 09:14:12 +00:00
|
|
|
|
2014-07-17 13:54:59 +00:00
|
|
|
readSymbolDb :: IO (Map Symbol [ModuleString])
|
2014-07-17 05:04:28 +00:00
|
|
|
readSymbolDb = handle (\(SomeException _) -> return M.empty) $ do
|
2014-07-16 09:14:12 +00:00
|
|
|
file <- chop <$> readProcess "ghc-mod" ["dumpsym"] []
|
2014-07-17 03:37:38 +00:00
|
|
|
M.fromAscList . map conv . lines <$> readFile file
|
2014-04-24 08:02:50 +00:00
|
|
|
where
|
2014-07-16 09:14:12 +00:00
|
|
|
conv :: String -> (Symbol,[ModuleString])
|
|
|
|
conv = read
|
|
|
|
chop "" = ""
|
|
|
|
chop xs = init xs
|
|
|
|
|
|
|
|
----------------------------------------------------------------
|
|
|
|
-- used 'ghc-mod dumpsym'
|
|
|
|
|
|
|
|
getPath :: IOish m => GhcModT m (Maybe String)
|
|
|
|
getPath = do
|
|
|
|
df <- G.getSessionDynFlags
|
|
|
|
stack <- cradlePkgDbStack . gmCradle <$> ask
|
|
|
|
case filter (GlobalDb /=) stack of
|
|
|
|
[] -> return Nothing
|
|
|
|
u:_ -> liftIO $ resolvePackageDb df u
|
|
|
|
|
2014-07-17 05:04:28 +00:00
|
|
|
-- | Dumping a set of ('Symbol',\['ModuleString'\]) to a file
|
|
|
|
-- if the file does not exist or is invalid.
|
|
|
|
-- The file name is printed.
|
2014-07-16 09:14:12 +00:00
|
|
|
dumpSymbol :: IOish m => GhcModT m String
|
|
|
|
dumpSymbol = do
|
|
|
|
mdir <- getPath
|
|
|
|
ret <- case mdir of
|
|
|
|
Nothing -> return ""
|
|
|
|
Just dir -> do
|
|
|
|
let cache = dir </> symbolCache
|
|
|
|
pkgdb = dir </> packageCache
|
2014-07-17 05:40:35 +00:00
|
|
|
ghandle (\(SomeException _) -> return "") $ do
|
2014-07-16 09:14:12 +00:00
|
|
|
create <- liftIO $ needToCreate cache pkgdb
|
|
|
|
when create $ do
|
|
|
|
sm <- getSymbol
|
|
|
|
void . liftIO $ withFile cache WriteMode $ \hdl ->
|
2014-07-17 08:16:44 +00:00
|
|
|
mapM (hPrint hdl) sm
|
2014-07-16 09:14:12 +00:00
|
|
|
return cache
|
|
|
|
return $ ret ++ "\n"
|
2014-04-24 08:02:50 +00:00
|
|
|
|
2014-07-16 09:14:12 +00:00
|
|
|
needToCreate :: FilePath -> FilePath -> IO Bool
|
|
|
|
needToCreate file1 file2 = do
|
|
|
|
exist <- doesFileExist file1
|
|
|
|
if not exist then
|
|
|
|
return True
|
|
|
|
else do
|
|
|
|
m1 <- getModificationTime file1
|
|
|
|
m2 <- getModificationTime file2
|
|
|
|
return $ m1 <= m2 -- including equal just in case
|
2014-05-14 16:54:56 +00:00
|
|
|
|
2014-07-16 09:14:12 +00:00
|
|
|
-- | Browsing all functions in all system/user modules.
|
|
|
|
getSymbol :: IOish m => GhcModT m [(Symbol,[ModuleString])]
|
|
|
|
getSymbol = do
|
|
|
|
ms <- G.packageDbModules True
|
|
|
|
let ns = map (G.moduleNameString . G.moduleName) ms
|
|
|
|
is <- mapM G.getModuleInfo ms
|
|
|
|
let symbols = concatMap toNameModule (zip is ns)
|
|
|
|
return $ uniquefy symbols
|
|
|
|
|
|
|
|
toNameModule :: (Maybe G.ModuleInfo,ModuleString) -> [(Symbol,ModuleString)]
|
|
|
|
toNameModule (Nothing,_) = []
|
|
|
|
toNameModule (Just inf,mdlname) = map (\name -> (getOccString name, mdlname)) names
|
|
|
|
where
|
|
|
|
names = G.modInfoExports inf
|
|
|
|
|
|
|
|
uniquefy :: [(Symbol,ModuleString)] -> [(Symbol,[ModuleString])]
|
|
|
|
uniquefy = map tieup . groupBy ((==) `on` fst) . sort
|
|
|
|
where
|
|
|
|
tieup x = (head (map fst x), map snd x)
|
|
|
|
|
|
|
|
--- Copied from ghc module `Packages' unfortunately it's not exported :/
|
|
|
|
resolvePackageDb :: DynFlags -> GhcPkgDb -> IO (Maybe FilePath)
|
|
|
|
resolvePackageDb df GlobalDb = return $ Just (systemPackageConfig df)
|
|
|
|
resolvePackageDb _ (PackageDb name) = return $ Just name
|
|
|
|
resolvePackageDb _ UserDb = handleIO (\_ -> return Nothing) $ do
|
|
|
|
appdir <- getAppUserDataDirectory "ghc"
|
|
|
|
let dir = appdir </> (target_arch ++ '-':target_os ++ '-':cProjectVersion)
|
|
|
|
pkgconf = dir </> packageConfDir
|
|
|
|
exist <- doesDirectoryExist pkgconf
|
|
|
|
return $ if exist then Just pkgconf else Nothing
|
|
|
|
where
|
|
|
|
[target_arch,_,target_os] = splitOn "-" cTargetPlatformString
|