yesod/yesod/Devel.hs
2011-09-10 01:04:58 +02:00

194 lines
5.6 KiB
Haskell
Executable File

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Devel
( devel
) where
import qualified Distribution.Simple.Utils as D
import qualified Distribution.Verbosity as D
import qualified Distribution.Package as D
import qualified Distribution.PackageDescription.Parse as D
import qualified Distribution.PackageDescription as D
import Control.Concurrent (forkIO, threadDelay)
import qualified Control.Exception as Ex
import Control.Monad (forever)
import qualified Data.List as L
import qualified Data.Map as Map
import Data.Maybe (listToMaybe)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import System.Directory (createDirectoryIfMissing, removeFile,
getDirectoryContents)
import System.Exit (exitFailure, exitSuccess)
import System.Posix.Types (EpochTime)
import System.PosixCompat.Files (modificationTime, getFileStatus)
import System.Process (runCommand, terminateProcess,
waitForProcess, rawSystem)
import Text.Shakespeare.Text (st)
import Build (recompDeps, getDeps,findHaskellFiles)
lockFile :: FilePath
lockFile = "dist/devel-terminate"
writeLock :: IO ()
writeLock = do
createDirectoryIfMissing True "dist"
writeFile lockFile ""
removeLock :: IO ()
removeLock = try_ (removeFile lockFile)
devel :: Bool -> IO ()
devel isDevel = do
writeLock
putStrLn "Yesod devel server. Press ENTER to quit"
_ <- forkIO $ do
cabal <- D.findPackageDesc "."
gpd <- D.readPackageDescription D.normal cabal
let pid = (D.package . D.packageDescription) gpd
checkCabalFile gpd
_ <- if isDevel
then rawSystem "cabal-dev" ["configure", "--cabal-install-arg=-fdevel"]
else rawSystem "cabal" ["configure", "-fdevel"]
T.writeFile "dist/devel.hs" (develFile pid)
mainLoop isDevel
_ <- getLine
writeLock
exitSuccess
mainLoop :: Bool -> IO ()
mainLoop isDevel = forever $ do
putStrLn "Rebuilding application..."
recompDeps
list <- getFileList
_ <- if isDevel
then rawSystem "cabal-dev" ["build"]
else rawSystem "cabal" ["build"]
removeLock
putStrLn "Starting development server..."
pkg <- pkgConfigs isDevel
ph <- runCommand $ concat ["runghc ", pkg, " dist/devel.hs"]
watchTid <- forkIO . try_ $ do
watchForChanges list
putStrLn "Stopping development server..."
writeLock
threadDelay 1000000
putStrLn "Terminating development server..."
terminateProcess ph
ec <- waitForProcess ph
putStrLn $ "Exit code: " ++ show ec
Ex.throwTo watchTid (userError "process finished")
watchForChanges list
try_ :: forall a. IO a -> IO ()
try_ x = (Ex.try x :: IO (Either Ex.SomeException a)) >> return ()
pkgConfigs :: Bool -> IO String
pkgConfigs isDev
| isDev = do
devContents <- getDirectoryContents "cabal-dev"
let confs = filter isConfig devContents
return . unwords $ inplacePkg :
map ("-package-confcabal-dev/"++) confs
| otherwise = return inplacePkg
where
inplacePkg = "-package-confdist/package.conf.inplace"
isConfig dir = "packages-" `L.isPrefixOf` dir &&
".conf" `L.isSuffixOf` dir
type FileList = Map.Map FilePath EpochTime
getFileList :: IO FileList
getFileList = do
files <- findHaskellFiles "."
deps <- getDeps
let files' = files ++ map fst (Map.toList deps)
fmap Map.fromList $ flip mapM files' $ \f -> do
fs <- getFileStatus f
return (f, modificationTime fs)
watchForChanges :: FileList -> IO ()
watchForChanges list = do
newList <- getFileList
if list /= newList
then return ()
else threadDelay 1000000 >> watchForChanges list
showPkgName :: D.PackageId -> String
showPkgName = (\(D.PackageName n) -> n) . D.pkgName
develFile :: D.PackageId -> T.Text
develFile pid = [st|
{-# LANGUAGE PackageImports #-}
import "#{showPkgName pid}" Application (withDevelAppPort)
import Data.Dynamic (fromDynamic)
import Network.Wai.Handler.Warp (run)
import Data.Maybe (fromJust)
import Control.Concurrent (forkIO)
import System.Directory (doesFileExist, removeFile)
import System.Exit (exitSuccess)
import Control.Concurrent (threadDelay)
main :: IO ()
main = do
putStrLn "Starting devel application"
wdap <- (return . fromJust . fromDynamic) withDevelAppPort
forkIO . wdap $ \(port, app) -> run port app
loop
loop :: IO ()
loop = do
threadDelay 100000
e <- doesFileExist "dist/devel-terminate"
if e then terminateDevel else loop
terminateDevel :: IO ()
terminateDevel = exitSuccess
|]
checkCabalFile :: D.GenericPackageDescription -> IO ()
checkCabalFile gpd = case D.condLibrary gpd of
Nothing -> do
putStrLn "Error: incorrect cabal file, no library"
exitFailure
Just ct ->
case lookupDevelLib ct of
Nothing -> do
putStrLn "Error: no library configuration for -fdevel"
exitFailure
Just dLib ->
case (D.hsSourceDirs . D.libBuildInfo) dLib of
[] -> return ()
["."] -> return ()
_ ->
putStrLn $ "WARNING: yesod devel may not work correctly with " ++
"custom hs-source-dirs"
lookupDevelLib :: D.CondTree D.ConfVar c a -> Maybe a
lookupDevelLib ct = listToMaybe . map (\(_,x,_) -> D.condTreeData x) .
filter isDevelLib . D.condTreeComponents $ ct
where
isDevelLib ((D.Var (D.Flag (D.FlagName "devel"))), _, _) = True
isDevelLib _ = False