194 lines
5.6 KiB
Haskell
Executable File
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
|
|
|
|
|