module Stack.Ide (ide) where
import Control.Concurrent
import Control.Monad.Catch
import Control.Monad.IO.Class
import Control.Monad.Logger
import Control.Monad.Reader
import Data.Aeson
import qualified Data.ByteString as S
import qualified Data.ByteString.Char8 as S8
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as L
import Data.List
import qualified Data.Map.Strict as M
import Data.Maybe
import Data.Monoid
import qualified Data.Set as S
import Data.Text (Text)
import qualified Data.Text as T
import Path
import Path.IO
import Stack.Build.Source
import Stack.Constants
import Stack.Exec (defaultEnvSettings)
import Stack.Package
import Stack.Types
import System.Directory (doesFileExist)
import System.Exit
import System.IO
import qualified System.Process as P
import System.Process.Read
ide
:: (HasConfig r, HasBuildConfig r, HasEnvConfig r, MonadReader r m, MonadIO m, MonadThrow m, MonadLogger m, MonadCatch m)
=> [Text]
-> [String]
-> m ()
ide targets useropts = do
econfig <- asks getEnvConfig
bconfig <- asks getBuildConfig
pwd <- getWorkingDir
pkgs <-
liftM catMaybes $
forM (M.toList (bcPackages bconfig)) $
\(dir,validWanted) ->
do cabalfp <- getCabalFileName dir
name <- parsePackageNameFromFilePath cabalfp
let config =
PackageConfig
{ packageConfigEnableTests = True
, packageConfigEnableBenchmarks = True
, packageConfigFlags = localFlags mempty bconfig name
, packageConfigGhcVersion = envConfigGhcVersion econfig
, packageConfigPlatform = configPlatform
(getConfig bconfig)
}
pkg <- readPackage config cabalfp
if validWanted && wanted pwd cabalfp pkg
then do
pkgOpts <- getPackageOpts (packageOpts pkg) cabalfp
srcfiles <-
getPackageFiles (packageFiles pkg) Modules cabalfp
autogen <- liftM autogenDir (distDirFromDir dir)
paths_foo <-
liftM
(autogen </>)
(parseRelFile
("Paths_" ++
packageNameString name ++ ".hs"))
paths_foo_exists <- fileExists paths_foo
return
(Just
( packageName pkg
, pkgOpts
, mapMaybe
(stripDir pwd)
(S.toList srcfiles <>
if paths_foo_exists
then [paths_foo]
else [])))
else return Nothing
localdb <- packageDatabaseLocal
depsdb <- packageDatabaseDeps
let pkgopts = filter (not . badForGhci) (concat (map _2 pkgs))
srcfiles = concatMap (map toFilePath . _3) pkgs
pkgdbs =
["--package-db", toFilePath depsdb <> ":" <> toFilePath localdb]
$logInfo
("Configuring GHCi with the following packages: " <>
T.intercalate ", " (map packageNameText (map _1 pkgs)))
exec
"ide-backend-client"
("empty" :
"--local-work-dir" :
toFilePath pwd :
map ("--ghc-option=" ++) (pkgopts <> useropts) <> pkgdbs)
(encode (initialRequest srcfiles))
where
wanted pwd cabalfp pkg = isInWantedList || targetsEmptyAndInDir
where
isInWantedList = elem (packageNameText (packageName pkg)) targets
targetsEmptyAndInDir = null targets || isParentOf (parent cabalfp) pwd
badForGhci x =
isPrefixOf "-O" x || elem x (words "-debug -threaded -ticky")
_1 (x,_,_) = x
_2 (_,x,_) = x
_3 (_,_,x) = x
initialRequest :: [FilePath] -> Value
initialRequest srcfiles =
object
[ "request" .= "updateSession"
, "update" .=
map
(\fp ->
object
[ "update" .= "updateSourceFileFromFile"
, "filePath" .= fp])
srcfiles]
exec :: (HasConfig r, MonadReader r m, MonadIO m, MonadLogger m, MonadThrow m)
=> String -> [String] -> ByteString -> m b
exec cmd args input = do
config <- asks getConfig
menv <-
liftIO
(configEnvOverride
config
defaultEnvSettings
{ esIncludeGhcPackagePath = False
})
exists <- liftIO $ doesFileExist cmd
cmd' <-
if exists
then return cmd
else liftM toFilePath $
join $ System.Process.Read.findExecutable menv cmd
let cp =
(P.proc cmd' args)
{ P.env = envHelper menv
, P.delegate_ctlc = True
, P.std_in = P.CreatePipe
}
$logProcessRun cmd' args
(Just procin,Nothing,Nothing,ph) <- liftIO (P.createProcess cp)
liftIO
(do hSetBuffering stdin LineBuffering
hSetBuffering procin LineBuffering)
liftIO (do S8.hPutStrLn stdout (L.toStrict input)
S8.hPutStrLn procin (L.toStrict input))
_tid <-
liftIO
(forkIO
(forever
(do bytes <- S.getLine
S.hPutStr procin bytes)))
ec <- liftIO (P.waitForProcess ph)
liftIO (exitWith ec)