Merge remote-tracking branch 'upstream/no-set'

Conflicts:
	Language/Haskell/GhcMod/Find.hs
This commit is contained in:
Daniel Gröber 2014-10-03 21:21:26 +02:00
commit ab7059d5e4
12 changed files with 441 additions and 238 deletions

View File

@ -46,6 +46,7 @@ module Language.Haskell.GhcMod (
, dumpSymbol , dumpSymbol
-- * SymbolDb -- * SymbolDb
, loadSymbolDb , loadSymbolDb
, isOutdated
) where ) where
import Language.Haskell.GhcMod.Boot import Language.Haskell.GhcMod.Boot

View File

@ -1,4 +1,4 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE RecordWildCards, CPP #-}
-- | This module facilitates extracting information from Cabal's on-disk -- | This module facilitates extracting information from Cabal's on-disk
-- 'LocalBuildInfo' (@dist/setup-config@). -- 'LocalBuildInfo' (@dist/setup-config@).
@ -6,13 +6,17 @@ module Language.Haskell.GhcMod.CabalConfig (
CabalConfig CabalConfig
, cabalConfigDependencies , cabalConfigDependencies
, cabalConfigFlags , cabalConfigFlags
, setupConfigFile
, World
, getCurrentWorld
, isWorldChanged
) where ) where
import Language.Haskell.GhcMod.Error import Language.Haskell.GhcMod.Error
import Language.Haskell.GhcMod.GhcPkg import Language.Haskell.GhcMod.GhcPkg
import Language.Haskell.GhcMod.Utils
import Language.Haskell.GhcMod.Read import Language.Haskell.GhcMod.Read
import Language.Haskell.GhcMod.Types import Language.Haskell.GhcMod.Types
import Language.Haskell.GhcMod.Utils
import qualified Language.Haskell.GhcMod.Cabal16 as C16 import qualified Language.Haskell.GhcMod.Cabal16 as C16
import qualified Language.Haskell.GhcMod.Cabal18 as C18 import qualified Language.Haskell.GhcMod.Cabal18 as C18
@ -23,7 +27,7 @@ import qualified Language.Haskell.GhcMod.Cabal21 as C21
#endif #endif
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
import Control.Monad (mplus) import Control.Monad (unless, void, mplus)
#if MIN_VERSION_mtl(2,2,1) #if MIN_VERSION_mtl(2,2,1)
import Control.Monad.Except () import Control.Monad.Except ()
#else #else
@ -39,9 +43,17 @@ import Distribution.PackageDescription (FlagAssignment)
import Distribution.Simple.BuildPaths (defaultDistPref) import Distribution.Simple.BuildPaths (defaultDistPref)
import Distribution.Simple.Configure (localBuildInfoFile) import Distribution.Simple.Configure (localBuildInfoFile)
import Distribution.Simple.LocalBuildInfo (ComponentName) import Distribution.Simple.LocalBuildInfo (ComponentName)
import Data.Traversable (traverse)
import MonadUtils (liftIO) import MonadUtils (liftIO)
import System.Directory (doesFileExist, getModificationTime)
import System.FilePath ((</>)) import System.FilePath ((</>))
#if __GLASGOW_HASKELL__ <= 704
import System.Time (ClockTime)
#else
import Data.Time (UTCTime)
#endif
---------------------------------------------------------------- ----------------------------------------------------------------
-- | 'Show'ed cabal 'LocalBuildInfo' string -- | 'Show'ed cabal 'LocalBuildInfo' string
@ -53,20 +65,26 @@ type CabalConfig = String
getConfig :: (IOish m, MonadError GhcModError m) getConfig :: (IOish m, MonadError GhcModError m)
=> Cradle => Cradle
-> m CabalConfig -> m CabalConfig
getConfig cradle = liftIO (readFile path) `tryFix` \_ -> getConfig cradle = do
world <- liftIO $ getCurrentWorld cradle
let valid = isSetupConfigValid world
unless valid configure
liftIO (readFile file) `tryFix` \_ ->
configure `modifyError'` GMECabalConfigure configure `modifyError'` GMECabalConfigure
where where
file = setupConfigFile cradle
prjDir = cradleRootDir cradle prjDir = cradleRootDir cradle
path = prjDir </> configPath
configure :: (IOish m, MonadError GhcModError m) => m () configure :: (IOish m, MonadError GhcModError m) => m ()
configure = configure = withDirectory_ prjDir $ void $ readProcess' "cabal" ["configure"]
withDirectory_ prjDir $ readProcess' "cabal" ["configure"] >> return ()
setupConfigFile :: Cradle -> FilePath
setupConfigFile crdl = cradleRootDir crdl </> setupConfigPath
-- | Path to 'LocalBuildInfo' file, usually @dist/setup-config@ -- | Path to 'LocalBuildInfo' file, usually @dist/setup-config@
configPath :: FilePath setupConfigPath :: FilePath
configPath = localBuildInfoFile defaultDistPref setupConfigPath = localBuildInfoFile defaultDistPref
-- | Get list of 'Package's needed by all components of the current package -- | Get list of 'Package's needed by all components of the current package
cabalConfigDependencies :: (IOish m, MonadError GhcModError m) cabalConfigDependencies :: (IOish m, MonadError GhcModError m)
@ -175,3 +193,57 @@ extractField config field =
case extractParens <$> find (field `isPrefixOf`) (tails config) of case extractParens <$> find (field `isPrefixOf`) (tails config) of
Just f -> Right f Just f -> Right f
Nothing -> Left $ "extractField: failed extracting "++field++" from input, input contained `"++field++"'? " ++ show (field `isInfixOf` config) Nothing -> Left $ "extractField: failed extracting "++field++" from input, input contained `"++field++"'? " ++ show (field `isInfixOf` config)
----------------------------------------------------------------
#if __GLASGOW_HASKELL__ <= 704
type ModTime = ClockTime
#else
type ModTime = UTCTime
#endif
data World = World {
worldCabalFile :: Maybe FilePath
, worldCabalFileModificationTime :: Maybe ModTime
, worldPackageCache :: FilePath
, worldPackageCacheModificationTime :: ModTime
, worldSetupConfig :: FilePath
, worldSetupConfigModificationTime :: Maybe ModTime
} deriving (Show, Eq)
getCurrentWorld :: Cradle -> IO World
getCurrentWorld crdl = do
cachePath <- getPackageCachePath crdl
let mCabalFile = cradleCabalFile crdl
pkgCache = cachePath </> packageCache
setupFile = setupConfigFile crdl
mCabalFileMTime <- getModificationTime `traverse` mCabalFile
pkgCacheMTime <- getModificationTime pkgCache
exist <- doesFileExist setupFile
mSeetupMTime <- if exist then
Just <$> getModificationTime setupFile
else
return Nothing
return $ World {
worldCabalFile = mCabalFile
, worldCabalFileModificationTime = mCabalFileMTime
, worldPackageCache = pkgCache
, worldPackageCacheModificationTime = pkgCacheMTime
, worldSetupConfig = setupFile
, worldSetupConfigModificationTime = mSeetupMTime
}
isWorldChanged :: World -> Cradle -> IO Bool
isWorldChanged world crdl = do
world' <- getCurrentWorld crdl
return (world /= world')
isSetupConfigValid :: World -> Bool
isSetupConfigValid World{ worldSetupConfigModificationTime = Nothing, ..} = False
isSetupConfigValid World{ worldSetupConfigModificationTime = Just mt, ..} =
cond1 && cond2
where
cond1 = case worldCabalFileModificationTime of
Nothing -> True
Just mtime -> mtime <= mt
cond2 = worldPackageCacheModificationTime <= mt

View File

@ -8,9 +8,8 @@ module Language.Haskell.GhcMod.Check (
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
import Language.Haskell.GhcMod.DynFlags import Language.Haskell.GhcMod.DynFlags
import qualified Language.Haskell.GhcMod.Gap as Gap import qualified Language.Haskell.GhcMod.Gap as Gap
import qualified GHC as G
import Language.Haskell.GhcMod.Logger import Language.Haskell.GhcMod.Logger
import Language.Haskell.GhcMod.Monad (IOish, GhcModT, overrideGhcUserOptions) import Language.Haskell.GhcMod.Monad (IOish, GhcModT)
import Language.Haskell.GhcMod.Target (setTargetFiles) import Language.Haskell.GhcMod.Target (setTargetFiles)
---------------------------------------------------------------- ----------------------------------------------------------------
@ -30,10 +29,15 @@ checkSyntax files = either id id <$> check files
check :: IOish m check :: IOish m
=> [FilePath] -- ^ The target files. => [FilePath] -- ^ The target files.
-> GhcModT m (Either String String) -> GhcModT m (Either String String)
{-
check fileNames = overrideGhcUserOptions $ \ghcOpts -> do check fileNames = overrideGhcUserOptions $ \ghcOpts -> do
withLogger (setAllWarningFlags . setNoMaxRelevantBindings . Gap.setWarnTypedHoles . Gap.setDeferTypeErrors) $ do withLogger (setAllWarningFlags . setNoMaxRelevantBindings . Gap.setWarnTypedHoles . Gap.setDeferTypeErrors) $ do
_ <- G.setSessionDynFlags =<< addCmdOpts ghcOpts =<< G.getSessionDynFlags _ <- G.setSessionDynFlags =<< addCmdOpts ghcOpts =<< G.getSessionDynFlags
setTargetFiles fileNames setTargetFiles fileNames
-}
check fileNames =
withLogger (setAllWarningFlags . setNoMaxRelevantBindings) $
setTargetFiles fileNames
---------------------------------------------------------------- ----------------------------------------------------------------

View File

@ -1,4 +1,4 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP, BangPatterns #-}
module Language.Haskell.GhcMod.Find module Language.Haskell.GhcMod.Find
#ifndef SPEC #ifndef SPEC
@ -10,28 +10,26 @@ module Language.Haskell.GhcMod.Find
, dumpSymbol , dumpSymbol
, findSymbol , findSymbol
, lookupSym , lookupSym
, isOutdated
) )
#endif #endif
where where
import Config (cProjectVersion,cTargetPlatformString)
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
import Control.Monad (when, void) import Control.Monad (when, void)
import Control.Monad.Error.Class import Control.Monad.Error.Class
import Data.Function (on) import Data.Function (on)
import Data.List (groupBy, sort) import Data.List (groupBy, sort)
import Data.List.Split (splitOn)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import DynFlags (DynFlags(..), systemPackageConfig)
import Exception (handleIO)
import qualified GHC as G import qualified GHC as G
import Language.Haskell.GhcMod.Convert import Language.Haskell.GhcMod.Convert
import Language.Haskell.GhcMod.GhcPkg
import Language.Haskell.GhcMod.Monad import Language.Haskell.GhcMod.Monad
import Language.Haskell.GhcMod.Utils
import Language.Haskell.GhcMod.Types import Language.Haskell.GhcMod.Types
import Language.Haskell.GhcMod.Utils
import Name (getOccString) import Name (getOccString)
import System.Directory (doesDirectoryExist, getAppUserDataDirectory, doesFileExist, getModificationTime) import System.Directory (doesFileExist, getModificationTime)
import System.FilePath ((</>)) import System.FilePath ((</>), takeDirectory)
import System.IO import System.IO
#ifndef MIN_VERSION_containers #ifndef MIN_VERSION_containers
@ -51,8 +49,14 @@ import qualified Data.Map as M
-- | Type of function and operation names. -- | Type of function and operation names.
type Symbol = String type Symbol = String
-- | Database from 'Symbol' to \['ModuleString'\]. -- | Database from 'Symbol' to \['ModuleString'\].
newtype SymbolDb = SymbolDb (Map Symbol [ModuleString]) data SymbolDb = SymbolDb {
deriving (Show) table :: Map Symbol [ModuleString]
, packageCachePath :: FilePath
, symbolDbCachePath :: FilePath
} deriving (Show)
isOutdated :: SymbolDb -> IO Bool
isOutdated db = symbolDbCachePath db `isOlderThan` packageCachePath db
---------------------------------------------------------------- ----------------------------------------------------------------
@ -65,12 +69,6 @@ symbolCacheVersion = 0
symbolCache :: String symbolCache :: String
symbolCache = "ghc-mod-"++ show symbolCacheVersion ++".cache" symbolCache = "ghc-mod-"++ show symbolCacheVersion ++".cache"
packageCache :: String
packageCache = "package.cache"
packageConfDir :: String
packageConfDir = "package.conf.d"
---------------------------------------------------------------- ----------------------------------------------------------------
-- | Looking up 'SymbolDb' with 'Symbol' to \['ModuleString'\] -- | Looking up 'SymbolDb' with 'Symbol' to \['ModuleString'\]
@ -84,19 +82,21 @@ lookupSymbol :: IOish m => Symbol -> SymbolDb -> GhcModT m String
lookupSymbol sym db = convert' $ lookupSym sym db lookupSymbol sym db = convert' $ lookupSym sym db
lookupSym :: Symbol -> SymbolDb -> [ModuleString] lookupSym :: Symbol -> SymbolDb -> [ModuleString]
lookupSym sym (SymbolDb db) = fromMaybe [] $ M.lookup sym db lookupSym sym db = fromMaybe [] $ M.lookup sym $ table db
--------------------------------------------------------------- ---------------------------------------------------------------
-- | Loading a file and creates 'SymbolDb'. -- | Loading a file and creates 'SymbolDb'.
loadSymbolDb :: (IOish m, MonadError GhcModError m) => m SymbolDb loadSymbolDb :: (IOish m, MonadError GhcModError m) => m SymbolDb
loadSymbolDb = SymbolDb <$> readSymbolDb loadSymbolDb = do
readSymbolDb :: (IOish m, MonadError GhcModError m) => m (Map Symbol [ModuleString])
readSymbolDb = do
ghcMod <- liftIO ghcModExecutable ghcMod <- liftIO ghcModExecutable
file <- chop <$> readProcess' ghcMod ["dumpsym"] file <- chop <$> readProcess' ghcMod ["dumpsym"]
M.fromAscList . map conv . lines <$> liftIO (readFile file) !db <- M.fromAscList . map conv . lines <$> liftIO (readFile file)
return $ SymbolDb {
table = db
, packageCachePath = takeDirectory file </> packageCache
, symbolDbCachePath = file
}
where where
conv :: String -> (Symbol,[ModuleString]) conv :: String -> (Symbol,[ModuleString])
conv = read conv = read
@ -106,24 +106,18 @@ readSymbolDb = do
---------------------------------------------------------------- ----------------------------------------------------------------
-- used 'ghc-mod dumpsym' -- used 'ghc-mod dumpsym'
getSymbolCachePath :: IOish m => GhcModT m FilePath
getSymbolCachePath = do
u:_ <- filter (/= GlobalDb) . cradlePkgDbStack <$> cradle
Just db <- (liftIO . flip resolvePackageDb u) =<< G.getSessionDynFlags
return db
`catchError` const (fail "Couldn't find non-global package database for symbol cache")
-- | Dumping a set of ('Symbol',\['ModuleString'\]) to a file -- | Dumping a set of ('Symbol',\['ModuleString'\]) to a file
-- if the file does not exist or is invalid. -- if the file does not exist or is invalid.
-- The file name is printed. -- The file name is printed.
dumpSymbol :: IOish m => GhcModT m String dumpSymbol :: IOish m => GhcModT m String
dumpSymbol = do dumpSymbol = do
dir <- getSymbolCachePath crdl <- cradle
dir <- liftIO $ getPackageCachePath crdl
let cache = dir </> symbolCache let cache = dir </> symbolCache
pkgdb = dir </> packageCache pkgdb = dir </> packageCache
create <- liftIO $ cache `isNewerThan` pkgdb create <- liftIO $ cache `isOlderThan` pkgdb
when create $ (liftIO . writeSymbolCache cache) =<< getSymbolTable when create $ (liftIO . writeSymbolCache cache) =<< getSymbolTable
return $ unlines [cache] return $ unlines [cache]
@ -134,15 +128,15 @@ writeSymbolCache cache sm =
void . withFile cache WriteMode $ \hdl -> void . withFile cache WriteMode $ \hdl ->
mapM (hPrint hdl) sm mapM (hPrint hdl) sm
isNewerThan :: FilePath -> FilePath -> IO Bool isOlderThan :: FilePath -> FilePath -> IO Bool
isNewerThan ref file = do isOlderThan cache file = do
exist <- doesFileExist ref exist <- doesFileExist cache
if not exist then if not exist then
return True return True
else do else do
tRef <- getModificationTime ref tCache <- getModificationTime cache
tFile <- getModificationTime file tFile <- getModificationTime file
return $ tRef <= tFile -- including equal just in case return $ tCache <= tFile -- including equal just in case
-- | Browsing all functions in all system/user modules. -- | Browsing all functions in all system/user modules.
getSymbolTable :: IOish m => GhcModT m [(Symbol,[ModuleString])] getSymbolTable :: IOish m => GhcModT m [(Symbol,[ModuleString])]
@ -171,16 +165,3 @@ collectModules :: [(Symbol,ModuleString)]
collectModules = map tieup . groupBy ((==) `on` fst) . sort collectModules = map tieup . groupBy ((==) `on` fst) . sort
where where
tieup x = (head (map fst x), map snd x) 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

View File

@ -8,9 +8,12 @@ module Language.Haskell.GhcMod.GhcPkg (
, fromInstalledPackageId' , fromInstalledPackageId'
, getSandboxDb , getSandboxDb
, getPackageDbStack , getPackageDbStack
, getPackageCachePath
, packageCache
, packageConfDir
) where ) where
import Config (cProjectVersionInt) import Config (cProjectVersion, cTargetPlatformString, cProjectVersionInt)
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
import Control.Exception (SomeException(..)) import Control.Exception (SomeException(..))
import qualified Control.Exception as E import qualified Control.Exception as E
@ -18,8 +21,10 @@ import Data.Char (isSpace)
import Data.List (isPrefixOf, intercalate) import Data.List (isPrefixOf, intercalate)
import Data.List.Split (splitOn) import Data.List.Split (splitOn)
import Distribution.Package (InstalledPackageId(..)) import Distribution.Package (InstalledPackageId(..))
import Exception (handleIO)
import Language.Haskell.GhcMod.Types import Language.Haskell.GhcMod.Types
import Language.Haskell.GhcMod.Utils import Language.Haskell.GhcMod.Utils
import System.Directory (doesDirectoryExist, getAppUserDataDirectory)
import System.FilePath ((</>)) import System.FilePath ((</>))
ghcVersion :: Int ghcVersion :: Int
@ -46,6 +51,8 @@ getSandboxDbDir sconf = do
parse = head . filter (key `isPrefixOf`) . lines parse = head . filter (key `isPrefixOf`) . lines
extractValue = dropWhileEnd isSpace . dropWhile isSpace . drop keyLen extractValue = dropWhileEnd isSpace . dropWhile isSpace . drop keyLen
----------------------------------------------------------------
getPackageDbStack :: FilePath -- ^ Project Directory (where the getPackageDbStack :: FilePath -- ^ Project Directory (where the
-- cabal.sandbox.config file would be if it -- cabal.sandbox.config file would be if it
-- exists) -- exists)
@ -54,6 +61,8 @@ getPackageDbStack cdir =
(getSandboxDb cdir >>= \db -> return [GlobalDb, PackageDb db]) (getSandboxDb cdir >>= \db -> return [GlobalDb, PackageDb db])
`E.catch` \(_ :: SomeException) -> return [GlobalDb, UserDb] `E.catch` \(_ :: SomeException) -> return [GlobalDb, UserDb]
----------------------------------------------------------------
fromInstalledPackageId' :: InstalledPackageId -> Maybe Package fromInstalledPackageId' :: InstalledPackageId -> Maybe Package
fromInstalledPackageId' pid = let fromInstalledPackageId' pid = let
InstalledPackageId pkg = pid InstalledPackageId pkg = pid
@ -68,6 +77,8 @@ fromInstalledPackageId pid =
Nothing -> error $ Nothing -> error $
"fromInstalledPackageId: `"++show pid++"' is not a valid package-id" "fromInstalledPackageId: `"++show pid++"' is not a valid package-id"
----------------------------------------------------------------
-- | Get options needed to add a list of package dbs to ghc-pkg's db stack -- | Get options needed to add a list of package dbs to ghc-pkg's db stack
ghcPkgDbStackOpts :: [GhcPkgDb] -- ^ Package db stack ghcPkgDbStackOpts :: [GhcPkgDb] -- ^ Package db stack
-> [String] -> [String]
@ -78,6 +89,8 @@ ghcDbStackOpts :: [GhcPkgDb] -- ^ Package db stack
-> [String] -> [String]
ghcDbStackOpts dbs = ghcDbOpt `concatMap` dbs ghcDbStackOpts dbs = ghcDbOpt `concatMap` dbs
----------------------------------------------------------------
ghcPkgDbOpt :: GhcPkgDb -> [String] ghcPkgDbOpt :: GhcPkgDb -> [String]
ghcPkgDbOpt GlobalDb = ["--global"] ghcPkgDbOpt GlobalDb = ["--global"]
ghcPkgDbOpt UserDb = ["--user"] ghcPkgDbOpt UserDb = ["--user"]
@ -95,3 +108,31 @@ ghcDbOpt UserDb
ghcDbOpt (PackageDb pkgDb) ghcDbOpt (PackageDb pkgDb)
| ghcVersion < 706 = ["-no-user-package-conf", "-package-conf", pkgDb] | ghcVersion < 706 = ["-no-user-package-conf", "-package-conf", pkgDb]
| otherwise = ["-no-user-package-db", "-package-db", pkgDb] | otherwise = ["-no-user-package-db", "-package-db", pkgDb]
----------------------------------------------------------------
packageCache :: String
packageCache = "package.cache"
packageConfDir :: String
packageConfDir = "package.conf.d"
-- fixme: error handling
getPackageCachePath :: Cradle -> IO FilePath
getPackageCachePath crdl = do
let u:_ = filter (/= GlobalDb) $ cradlePkgDbStack crdl
Just db <- resolvePath u
return db
--- Copied from ghc module `Packages' unfortunately it's not exported :/
resolvePath :: GhcPkgDb -> IO (Maybe FilePath)
resolvePath (PackageDb name) = return $ Just name
resolvePath 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
resolvePath _ = error "GlobalDb cannot be used in resolvePath"

View File

@ -49,11 +49,16 @@ module Language.Haskell.GhcMod.Internal (
, (||>) , (||>)
, goNext , goNext
, runAnyOne , runAnyOne
-- * World
, World
, getCurrentWorld
, isWorldChanged
) where ) where
import GHC.Paths (libdir) import GHC.Paths (libdir)
import Language.Haskell.GhcMod.CabalApi import Language.Haskell.GhcMod.CabalApi
import Language.Haskell.GhcMod.CabalConfig
import Language.Haskell.GhcMod.DynFlags import Language.Haskell.GhcMod.DynFlags
import Language.Haskell.GhcMod.Error import Language.Haskell.GhcMod.Error
import Language.Haskell.GhcMod.GHCChoice import Language.Haskell.GhcMod.GHCChoice

View File

@ -200,11 +200,11 @@ initializeFlagsWithCradle opt c
| cabal = withCabal | cabal = withCabal
| otherwise = withSandbox | otherwise = withSandbox
where where
mCradleFile = cradleCabalFile c mCabalFile = cradleCabalFile c
cabal = isJust mCradleFile cabal = isJust mCabalFile
ghcopts = ghcUserOptions opt ghcopts = ghcUserOptions opt
withCabal = do withCabal = do
pkgDesc <- parseCabalFile c $ fromJust mCradleFile pkgDesc <- parseCabalFile c $ fromJust mCabalFile
compOpts <- getCompilerOptions ghcopts c pkgDesc compOpts <- getCompilerOptions ghcopts c pkgDesc
initSession CabalPkg opt compOpts initSession CabalPkg opt compOpts
withSandbox = initSession SingleFile opt compOpts withSandbox = initSession SingleFile opt compOpts

View File

@ -1,7 +1,7 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
module Language.Haskell.GhcMod.Utils where module Language.Haskell.GhcMod.Utils where
import Control.Applicative ((<$>))
import Language.Haskell.GhcMod.Error import Language.Haskell.GhcMod.Error
import MonadUtils (MonadIO, liftIO) import MonadUtils (MonadIO, liftIO)
import System.Directory (getCurrentDirectory, setCurrentDirectory) import System.Directory (getCurrentDirectory, setCurrentDirectory)
@ -9,6 +9,7 @@ import System.Exit (ExitCode(..))
import System.Process (readProcessWithExitCode) import System.Process (readProcessWithExitCode)
#ifndef SPEC #ifndef SPEC
import System.Environment import System.Environment
import System.FilePath ((</>), takeDirectory)
#endif #endif
-- dropWhileEnd is not provided prior to base 4.5.0.0. -- dropWhileEnd is not provided prior to base 4.5.0.0.
@ -51,15 +52,18 @@ withDirectory_ dir action =
-- this is a guess but >=7.6 uses 'getExecutablePath'. -- this is a guess but >=7.6 uses 'getExecutablePath'.
ghcModExecutable :: IO FilePath ghcModExecutable :: IO FilePath
#ifndef SPEC #ifndef SPEC
ghcModExecutable = getExecutable' ghcModExecutable = do
dir <- getExecutablePath'
return $ dir </> "ghc-mod"
#else
ghcModExecutable = do _ <- getExecutablePath' -- get rid of unused warning when
-- compiling spec
return "dist/build/ghc-mod/ghc-mod"
#endif
where where
getExecutable' :: IO FilePath getExecutablePath' :: IO FilePath
# if __GLASGOW_HASKELL__ >= 706 # if __GLASGOW_HASKELL__ >= 706
getExecutable' = getExecutablePath getExecutablePath' = takeDirectory <$> getExecutablePath
# else # else
getExecutable' = getProgName getExecutablePath' = return ""
# endif
#else
ghcModExecutable = return "dist/build/ghc-mod/ghc-mod"
# endif # endif

View File

@ -86,8 +86,8 @@ Library
Language.Haskell.GhcMod.Lang Language.Haskell.GhcMod.Lang
Language.Haskell.GhcMod.Lint Language.Haskell.GhcMod.Lint
Language.Haskell.GhcMod.Logger Language.Haskell.GhcMod.Logger
Language.Haskell.GhcMod.Monad
Language.Haskell.GhcMod.Modules Language.Haskell.GhcMod.Modules
Language.Haskell.GhcMod.Monad
Language.Haskell.GhcMod.PkgDoc Language.Haskell.GhcMod.PkgDoc
Language.Haskell.GhcMod.Read Language.Haskell.GhcMod.Read
Language.Haskell.GhcMod.SrcUtils Language.Haskell.GhcMod.SrcUtils
@ -148,8 +148,11 @@ Executable ghc-modi
Default-Language: Haskell2010 Default-Language: Haskell2010
Main-Is: GHCModi.hs Main-Is: GHCModi.hs
Other-Modules: Paths_ghc_mod Other-Modules: Paths_ghc_mod
Misc
Utils Utils
GHC-Options: -Wall -threaded GHC-Options: -Wall -threaded
if os(windows)
Cpp-Options: -DWINDOWS
Default-Extensions: ConstraintKinds, FlexibleContexts Default-Extensions: ConstraintKinds, FlexibleContexts
HS-Source-Dirs: src HS-Source-Dirs: src
Build-Depends: base >= 4.0 && < 5 Build-Depends: base >= 4.0 && < 5
@ -157,7 +160,10 @@ Executable ghc-modi
, containers , containers
, directory , directory
, filepath , filepath
, old-time
, process
, split , split
, time
, ghc , ghc
, ghc-mod , ghc-mod

View File

@ -20,36 +20,27 @@ module Main where
import Config (cProjectVersion) import Config (cProjectVersion)
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
import Control.Concurrent.Async (Async, async, wait) import Control.Exception (SomeException(..))
import Control.Exception (SomeException(..), Exception)
import qualified Control.Exception as E import qualified Control.Exception as E
import Control.Monad (when) import Control.Monad (when)
import CoreMonad (liftIO) import CoreMonad (liftIO)
import Data.List (find, intercalate) import Data.List (intercalate)
import Data.List.Split (splitOn) import Data.List.Split (splitOn)
import Data.Maybe (fromMaybe)
import Data.Set (Set)
import qualified Data.Set as S
import Data.Typeable (Typeable)
import Data.Version (showVersion) import Data.Version (showVersion)
import qualified GHC as G
import Language.Haskell.GhcMod import Language.Haskell.GhcMod
import Language.Haskell.GhcMod.Internal import Language.Haskell.GhcMod.Internal
import Paths_ghc_mod import Paths_ghc_mod
import System.Console.GetOpt import System.Console.GetOpt
import System.Directory (setCurrentDirectory) import System.Directory (setCurrentDirectory)
import System.Environment (getArgs) import System.Environment (getArgs)
import System.IO (hFlush,stdout)
import System.Exit (ExitCode, exitFailure) import System.Exit (ExitCode, exitFailure)
import System.IO (hFlush,stdout)
import Misc
import Utils import Utils
---------------------------------------------------------------- ----------------------------------------------------------------
type Logger = IO String
----------------------------------------------------------------
progVersion :: String progVersion :: String
progVersion = "ghc-modi version " ++ showVersion version ++ " compiled by GHC " ++ cProjectVersion ++ "\n" progVersion = "ghc-modi version " ++ showVersion version ++ " compiled by GHC " ++ cProjectVersion ++ "\n"
@ -79,13 +70,6 @@ parseArgs spec argv
---------------------------------------------------------------- ----------------------------------------------------------------
data GHCModiError = CmdArg [String]
deriving (Show, Typeable)
instance Exception GHCModiError
----------------------------------------------------------------
-- Running two GHC monad threads disables the handling of -- Running two GHC monad threads disables the handling of
-- C-c since installSignalHandlers is called twice, sigh. -- C-c since installSignalHandlers is called twice, sigh.
@ -96,14 +80,21 @@ main = E.handle cmdHandler $
cmdHandler (CmdArg _) = putStr $ usageInfo usage argspec cmdHandler (CmdArg _) = putStr $ usageInfo usage argspec
go (_,"help":_) = putStr $ usageInfo usage argspec go (_,"help":_) = putStr $ usageInfo usage argspec
go (_,"version":_) = putStr progVersion go (_,"version":_) = putStr progVersion
go (opt,_) = flip E.catches handlers $ do go (opt,_) = emptyNewUnGetLine >>= run opt
run :: Options -> UnGetLine -> IO ()
run opt ref = flip E.catches handlers $ do
cradle0 <- findCradle cradle0 <- findCradle
let rootdir = cradleRootDir cradle0 let rootdir = cradleRootDir cradle0
-- c = cradle0 { cradleCurrentDir = rootdir } TODO: ????? -- c = cradle0 { cradleCurrentDir = rootdir } TODO: ?????
setCurrentDirectory rootdir setCurrentDirectory rootdir
symDb <- async $ runGhcModT opt loadSymbolDb prepareAutogen cradle0
(res, _) <- runGhcModT opt $ loop S.empty symDb -- Asynchronous db loading starts here.
symdbreq <- newSymDbReq opt
(res, _) <- runGhcModT opt $ do
crdl <- cradle
world <- liftIO $ getCurrentWorld crdl
loop symdbreq ref world
case res of case res of
Right () -> return () Right () -> return ()
Left (GMECabalConfigure msg) -> do Left (GMECabalConfigure msg) -> do
@ -114,6 +105,7 @@ main = E.handle cmdHandler $
-- this is just in case. -- this is just in case.
-- If an error is caught here, it is a bug of GhcMod library. -- If an error is caught here, it is a bug of GhcMod library.
handlers = [ E.Handler (\(_ :: ExitCode) -> return ()) handlers = [ E.Handler (\(_ :: ExitCode) -> return ())
, E.Handler (\(_ :: Restart) -> run opt ref)
, E.Handler (\(SomeException e) -> bug $ show e) ] , E.Handler (\(SomeException e) -> bug $ show e) ]
bug :: String -> IO () bug :: String -> IO ()
@ -132,91 +124,63 @@ replace needle replacement = intercalate replacement . splitOn needle
---------------------------------------------------------------- ----------------------------------------------------------------
loop :: IOish m => Set FilePath -> SymDbReq -> GhcModT m () loop :: IOish m => SymDbReq -> UnGetLine -> World -> GhcModT m ()
loop set symDbReq = do loop symdbreq ref world = do
cmdArg <- liftIO getLine -- blocking
cmdArg <- liftIO $ getCommand ref
-- after blocking, we need to see if the world has changed.
crdl <- cradle
changed <- liftIO $ isWorldChanged world crdl
when changed $ do
liftIO $ ungetCommand ref cmdArg
E.throw Restart
cradle >>= liftIO . prepareAutogen
let (cmd,arg') = break (== ' ') cmdArg let (cmd,arg') = break (== ' ') cmdArg
arg = dropWhile (== ' ') arg' arg = dropWhile (== ' ') arg'
(ret,ok,set') <- case cmd of (ret,ok) <- case cmd of
"check" -> checkStx set arg "check" -> checkStx arg
"find" -> findSym set arg symDbReq "find" -> findSym arg symdbreq
"lint" -> lintStx set arg "lint" -> lintStx arg
"info" -> showInfo set arg "info" -> showInfo arg
"type" -> showType set arg "type" -> showType arg
"split" -> doSplit set arg "split" -> doSplit arg
"sig" -> doSig set arg "sig" -> doSig arg
"refine" -> doRefine set arg "refine" -> doRefine arg
"auto" -> doAuto set arg "auto" -> doAuto arg
"boot" -> bootIt set "boot" -> bootIt
"browse" -> browseIt set arg "browse" -> browseIt arg
"quit" -> return ("quit", False, set) "quit" -> return ("quit", False)
"" -> return ("quit", False, set) "" -> return ("quit", False)
_ -> return ([], True, set) _ -> return ([], True)
if ok then do if ok then do
liftIO $ putStr ret liftIO $ putStr ret
liftIO $ putStrLn "OK" liftIO $ putStrLn "OK"
else do else do
liftIO $ putStrLn $ notGood ret liftIO $ putStrLn $ notGood ret
liftIO $ hFlush stdout liftIO $ hFlush stdout
when ok $ loop set' symDbReq when ok $ loop symdbreq ref world
---------------------------------------------------------------- ----------------------------------------------------------------
checkStx :: IOish m checkStx :: IOish m => FilePath -> GhcModT m (String, Bool)
=> Set FilePath checkStx file = do
-> FilePath eret <- check [file]
-> GhcModT m (String, Bool, Set FilePath)
checkStx set file = do
set' <- newFileSet set file
let files = S.toList set'
eret <- check files
case eret of case eret of
Right ret -> return (ret, True, set') Right ret -> return (ret, True)
Left ret -> return (ret, True, set) -- fxime: set Left ret -> return (ret, True)
newFileSet :: IOish m => Set FilePath -> FilePath -> GhcModT m (Set FilePath)
newFileSet set file = do
let set1
| S.member file set = set
| otherwise = S.insert file set
mx <- isSameMainFile file <$> getModSummaryForMain
return $ case mx of
Nothing -> set1
Just mainfile -> S.delete mainfile set1
getModSummaryForMain :: IOish m => GhcModT m (Maybe G.ModSummary)
getModSummaryForMain = find isMain <$> G.getModuleGraph
where
isMain m = G.moduleNameString (G.moduleName (G.ms_mod m)) == "Main"
isSameMainFile :: FilePath -> (Maybe G.ModSummary) -> Maybe FilePath
isSameMainFile _ Nothing = Nothing
isSameMainFile file (Just x)
| mainfile == file = Nothing
| otherwise = Just mainfile
where
mmainfile = G.ml_hs_file (G.ms_location x)
-- G.ms_hspp_file x is a temporary file with CPP.
-- this is a just fake.
mainfile = fromMaybe (G.ms_hspp_file x) mmainfile
---------------------------------------------------------------- ----------------------------------------------------------------
type SymDbReq = Async (Either GhcModError SymbolDb, GhcModLog) findSym :: IOish m => Symbol -> SymDbReq -> GhcModT m (String, Bool)
findSym sym symdbreq = do
findSym :: IOish m => Set FilePath -> String -> SymDbReq db <- getDb symdbreq >>= checkDb symdbreq
-> GhcModT m (String, Bool, Set FilePath)
findSym set sym dbReq = do
db <- hoistGhcModT =<< liftIO (wait dbReq)
ret <- lookupSymbol sym db ret <- lookupSymbol sym db
return (ret, True, set) return (ret, True)
lintStx :: IOish m => Set FilePath lintStx :: IOish m => FilePath -> GhcModT m (String, Bool)
-> FilePath lintStx optFile = do
-> GhcModT m (String, Bool, Set FilePath)
lintStx set optFile = do
ret <- withOptions changeOpt $ lint file ret <- withOptions changeOpt $ lint file
return (ret, True, set) return (ret, True)
where where
(opts,file) = parseLintOptions optFile (opts,file) = parseLintOptions optFile
hopts = if opts == "" then [] else read opts hopts = if opts == "" then [] else read opts
@ -239,85 +203,56 @@ parseLintOptions optFile = case brk (== ']') (dropWhile (/= '[') optFile) of
---------------------------------------------------------------- ----------------------------------------------------------------
showInfo :: IOish m showInfo :: IOish m => FilePath -> GhcModT m (String, Bool)
=> Set FilePath showInfo fileArg = do
-> FilePath
-> GhcModT m (String, Bool, Set FilePath)
showInfo set fileArg = do
let [file, expr] = splitN 2 fileArg let [file, expr] = splitN 2 fileArg
set' <- newFileSet set file
ret <- info file expr ret <- info file expr
return (ret, True, set') return (ret, True)
showType :: IOish m showType :: IOish m => FilePath -> GhcModT m (String, Bool)
=> Set FilePath showType fileArg = do
-> FilePath
-> GhcModT m (String, Bool, Set FilePath)
showType set fileArg = do
let [file, line, column] = splitN 3 fileArg let [file, line, column] = splitN 3 fileArg
set' <- newFileSet set file
ret <- types file (read line) (read column) ret <- types file (read line) (read column)
return (ret, True, set') return (ret, True)
doSplit :: IOish m doSplit :: IOish m => FilePath -> GhcModT m (String, Bool)
=> Set FilePath doSplit fileArg = do
-> FilePath
-> GhcModT m (String, Bool, Set FilePath)
doSplit set fileArg = do
let [file, line, column] = splitN 3 fileArg let [file, line, column] = splitN 3 fileArg
set' <- newFileSet set file
ret <- splits file (read line) (read column) ret <- splits file (read line) (read column)
return (ret, True, set') return (ret, True)
doSig :: IOish m doSig :: IOish m => FilePath -> GhcModT m (String, Bool)
=> Set FilePath doSig fileArg = do
-> FilePath
-> GhcModT m (String, Bool, Set FilePath)
doSig set fileArg = do
let [file, line, column] = splitN 3 fileArg let [file, line, column] = splitN 3 fileArg
set' <- newFileSet set file
ret <- sig file (read line) (read column) ret <- sig file (read line) (read column)
return (ret, True, set') return (ret, True)
doRefine :: IOish m doRefine :: IOish m => FilePath -> GhcModT m (String, Bool)
=> Set FilePath doRefine fileArg = do
-> FilePath
-> GhcModT m (String, Bool, Set FilePath)
doRefine set fileArg = do
let [file, line, column, expr] = splitN 4 fileArg let [file, line, column, expr] = splitN 4 fileArg
set' <- newFileSet set file
ret <- refine file (read line) (read column) expr ret <- refine file (read line) (read column) expr
return (ret, True, set') return (ret, True)
doAuto :: IOish m doAuto :: IOish m => FilePath -> GhcModT m (String, Bool)
=> Set FilePath doAuto fileArg = do
-> FilePath
-> GhcModT m (String, Bool, Set FilePath)
doAuto set fileArg = do
let [file, line, column] = splitN 3 fileArg let [file, line, column] = splitN 3 fileArg
set' <- newFileSet set file
ret <- auto file (read line) (read column) ret <- auto file (read line) (read column)
return (ret, True, set') return (ret, True)
---------------------------------------------------------------- ----------------------------------------------------------------
bootIt :: IOish m bootIt :: IOish m => GhcModT m (String, Bool)
=> Set FilePath bootIt = do
-> GhcModT m (String, Bool, Set FilePath)
bootIt set = do
ret <- boot ret <- boot
return (ret, True, set) return (ret, True)
browseIt :: IOish m browseIt :: IOish m => ModuleString -> GhcModT m (String, Bool)
=> Set FilePath browseIt mdl = do
-> ModuleString
-> GhcModT m (String, Bool, Set FilePath)
browseIt set mdl = do
let (det,rest') = break (== ' ') mdl let (det,rest') = break (== ' ') mdl
rest = dropWhile (== ' ') rest' rest = dropWhile (== ' ') rest'
ret <- if det == "-d" ret <- if det == "-d"
then withOptions setDetailed (browse rest) then withOptions setDetailed (browse rest)
else browse mdl else browse mdl
return (ret, True, set) return (ret, True)
where where
setDetailed opt = opt { detailed = True } setDetailed opt = opt { detailed = True }

154
src/Misc.hs Normal file
View File

@ -0,0 +1,154 @@
{-# LANGUAGE DeriveDataTypeable, CPP #-}
module Misc (
GHCModiError(..)
, Restart(..)
, UnGetLine
, emptyNewUnGetLine
, ungetCommand
, getCommand
, SymDbReq
, newSymDbReq
, getDb
, checkDb
, prepareAutogen
) where
import Control.Applicative ((<$>))
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (Async, async, wait)
import Control.Exception (Exception)
import Control.Monad (unless, when)
import CoreMonad (liftIO)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.List (isPrefixOf)
import Data.Maybe (isJust)
import Data.Typeable (Typeable)
import System.Directory (doesDirectoryExist, getDirectoryContents)
import System.IO (openBinaryFile, IOMode(..))
import System.Process
import Language.Haskell.GhcMod
import Language.Haskell.GhcMod.Internal
----------------------------------------------------------------
data GHCModiError = CmdArg [String] deriving (Show, Typeable)
instance Exception GHCModiError
----------------------------------------------------------------
data Restart = Restart deriving (Show, Typeable)
instance Exception Restart
----------------------------------------------------------------
newtype UnGetLine = UnGetLine (IORef (Maybe String))
emptyNewUnGetLine :: IO UnGetLine
emptyNewUnGetLine = UnGetLine <$> newIORef Nothing
ungetCommand :: UnGetLine -> String -> IO ()
ungetCommand (UnGetLine ref) cmd = writeIORef ref (Just cmd)
getCommand :: UnGetLine -> IO String
getCommand (UnGetLine ref) = do
mcmd <- readIORef ref
case mcmd of
Nothing -> getLine
Just cmd -> do
writeIORef ref Nothing
return cmd
----------------------------------------------------------------
type SymDbReqAction = (Either GhcModError SymbolDb, GhcModLog)
data SymDbReq = SymDbReq (IORef (Async SymDbReqAction)) (IO SymDbReqAction)
newSymDbReq :: Options -> IO SymDbReq
newSymDbReq opt = do
let act = runGhcModT opt loadSymbolDb
req <- async act
ref <- newIORef req
return $ SymDbReq ref act
getDb :: IOish m => SymDbReq -> GhcModT m SymbolDb
getDb (SymDbReq ref _) = do
req <- liftIO $ readIORef ref
-- 'wait' really waits for the asynchronous action at the fist time.
-- Then it reads a cached value from the second time.
hoistGhcModT =<< liftIO (wait req)
checkDb :: IOish m => SymDbReq -> SymbolDb -> GhcModT m SymbolDb
checkDb (SymDbReq ref act) db = do
outdated <- liftIO $ isOutdated db
if outdated then do
-- async and wait here is unnecessary because this is essentially
-- synchronous. But Async can be used a cache.
req <- liftIO $ async act
liftIO $ writeIORef ref req
hoistGhcModT =<< liftIO (wait req)
else
return db
----------------------------------------------------------------
build :: IO ProcessHandle
build = do
#ifdef WINDOWS
nul <- openBinaryFile "NUL" AppendMode
#else
nul <- openBinaryFile "/dev/null" AppendMode
#endif
(_, _, _, hdl) <- createProcess $ pro nul
return hdl
where
pro nul = CreateProcess {
cmdspec = RawCommand "cabal" ["build"]
, cwd = Nothing
, env = Nothing
, std_in = Inherit
, std_out = UseHandle nul
, std_err = UseHandle nul
, close_fds = False
#if __GLASGOW_HASKELL__ >= 702
, create_group = True
#endif
#if __GLASGOW_HASKELL__ >= 707
, delegate_ctlc = False
#endif
}
autogen :: String
autogen = "dist/build/autogen"
isAutogenPrepared :: IO Bool
isAutogenPrepared = do
exist <- doesDirectoryExist autogen
if exist then do
files <- filter ("." `isPrefixOf`) <$> getDirectoryContents autogen
if length files >= 2 then
return True
else
return False
else
return False
watch :: Int -> ProcessHandle -> IO ()
watch 0 _ = return ()
watch n hdl = do
prepared <- isAutogenPrepared
if prepared then
interruptProcessGroupOf hdl
else do
threadDelay 100000
watch (n - 1) hdl
prepareAutogen :: Cradle -> IO ()
prepareAutogen crdl = when (isJust $ cradleCabalFile crdl) $ do
prepared <- isAutogenPrepared
unless prepared $ do
hdl <- build
watch 30 hdl