mnerge new-devel (Greg made me do it)

This commit is contained in:
Luite Stegeman 2011-09-06 09:51:32 +02:00
commit 1f8d753300
5 changed files with 217 additions and 163 deletions

View File

@ -1,34 +1,34 @@
{-# LANGUAGE OverloadedStrings #-}
module Build
( touch
, getDeps
( getDeps
, touchDeps
, touch
, findHaskellFiles
) where
-- FIXME there's a bug when getFileStatus applies to a file temporary deleted (e.g., Vim saving a file)
-- FIXME there's a bug when getFileStatus applies to a file
-- temporary deleted (e.g., Vim saving a file)
import Control.Applicative ((<|>))
import Control.Exception (SomeException, try)
import Control.Monad (when, filterM, forM, forM_)
import System.Directory (getDirectoryContents, doesDirectoryExist, doesFileExist)
import Data.List (isSuffixOf)
import qualified Data.Attoparsec.Text.Lazy as A
import qualified Data.Text.Lazy.IO as TIO
import Control.Applicative ((<|>))
import Data.Char (isSpace)
import Data.Monoid (mappend)
import Data.Char (isSpace)
import Data.Monoid (mappend)
import Data.List (isSuffixOf)
import qualified Data.Map as Map
import qualified Data.Set as Set
import System.PosixCompat.Files (accessTime, modificationTime, getFileStatus, setFileTimes)
import qualified System.Posix.Types
import Control.Monad (filterM, forM)
import Control.Exception (SomeException, try)
import qualified Data.Text.Lazy.IO as TIO
import qualified System.Posix.Types
import System.Directory
import System.FilePath (replaceExtension, (</>))
import System.PosixCompat.Files (getFileStatus,
accessTime, modificationTime)
-- | Touch any files with altered dependencies but do not build
touch :: IO ()
touch = do
hss <- findHaskellFiles "."
deps' <- mapM determineHamletDeps hss
let deps = fixDeps $ zip hss deps'
touchDeps deps
touch = touchDeps =<< getDeps
type Deps = Map.Map FilePath (Set.Set FilePath)
@ -39,24 +39,37 @@ getDeps = do
return $ fixDeps $ zip hss deps'
touchDeps :: Deps -> IO ()
touchDeps =
mapM_ go . Map.toList
touchDeps deps = (mapM_ go . Map.toList) deps
where
go (x, ys) = do
(_, mod1) <- getFileStatus' x
flip mapM_ (Set.toList ys) $ \y -> do
(access, mod2) <- getFileStatus' y
if mod2 < mod1
then do
putStrLn $ "Touching " ++ y ++ " because of " ++ x
_ <- try' $ setFileTimes y access mod1
return ()
else return ()
go (x, ys) =
forM_ (Set.toList ys) $ \y -> do
n <- x `isNewerThan` (hiFile y)
when n $ do
putStrLn ("Forcing recompile for " ++ y ++ " because of " ++ x)
removeHi y
-- | remove the .hi files for a .hs file, thereby forcing a recompile
removeHi :: FilePath -> IO ()
removeHi hs = mapM_ removeFile' hiFiles
where
removeFile' file = try' (removeFile file) >> return ()
hiFiles = map (\e -> "dist/build" </> replaceExtension hs e)
["hi", "p_hi"]
hiFile :: FilePath -> FilePath
hiFile hs = "dist/build" </> replaceExtension hs "hi"
try' :: IO x -> IO (Either SomeException x)
try' = try
getFileStatus' :: FilePath -> IO (System.Posix.Types.EpochTime, System.Posix.Types.EpochTime)
isNewerThan :: FilePath -> FilePath -> IO Bool
isNewerThan f1 f2 = do
(_, mod1) <- getFileStatus' f1
(_, mod2) <- getFileStatus' f2
return (mod1 > mod2)
getFileStatus' :: FilePath ->
IO (System.Posix.Types.EpochTime, System.Posix.Types.EpochTime)
getFileStatus' fp = do
efs <- try' $ getFileStatus fp
case efs of
@ -76,9 +89,10 @@ findHaskellFiles path = do
fmap concat $ mapM go contents
where
go ('.':_) = return []
go ('d':"ist") = return []
go ('c':"abal-dev" = return []
go ('d':"ist") = return []
go x = do
let y = path ++ '/' : x
let y = path </> x
d <- doesDirectoryExist y
if d
then findHaskellFiles y

285
yesod/Devel.hs Normal file → Executable file
View File

@ -1,127 +1,119 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Devel
( devel
) where
-- import qualified Distribution.Simple.Build as B
-- import Distribution.Simple.Configure (configure)
import Distribution.Simple (defaultMainArgs)
-- import Distribution.Simple.Setup (defaultConfigFlags, configConfigurationsFlags, configUserInstall, Flag (..), defaultBuildFlags, defaultCopyFlags, defaultRegisterFlags)
import Distribution.Simple.Utils (defaultPackageDesc, defaultHookedPackageDesc)
-- import Distribution.Simple.Program (defaultProgramConfiguration)
import Distribution.Verbosity (normal)
import Distribution.PackageDescription.Parse (readPackageDescription, readHookedBuildInfo)
import Distribution.PackageDescription (emptyHookedBuildInfo)
-- import Distribution.Simple.LocalBuildInfo (localPkgDescr)
import Build (getDeps, touchDeps, findHaskellFiles)
-- import Network.Wai.Handler.Warp (run)
-- import Network.Wai.Middleware.Debug (debug)
-- import Distribution.Text (display)
-- import Distribution.Simple.Install (install)
-- import Distribution.Simple.Register (register)
import Control.Concurrent (forkIO, threadDelay, ThreadId, killThread)
import Control.Exception (try, SomeException)
import System.PosixCompat.Files (modificationTime, getFileStatus)
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 System.Posix.Types (EpochTime)
-- import Blaze.ByteString.Builder.Char.Utf8 (fromString)
-- import Network.Wai (Application, Response (ResponseBuilder), responseLBS)
-- import Network.HTTP.Types (status500)
import Control.Monad (when, forever)
import System.Process (runCommand, terminateProcess, waitForProcess)
import qualified Data.IORef as I
import qualified Data.ByteString.Lazy.Char8 as L
import System.Directory (doesFileExist, removeFile, getDirectoryContents)
-- import Distribution.Package (PackageName (..), pkgName)
import Data.Maybe (mapMaybe)
import Data.Maybe (listToMaybe)
import qualified Data.Text as T
import qualified Data.Text.IO as T
appMessage :: L.ByteString -> IO ()
appMessage _ = forever $ do
-- run 3000 . const . return $ responseLBS status500 [("Content-Type", "text/plain")] l
threadDelay 10000
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)
swapApp :: I.IORef ThreadId -> IO ThreadId -> IO ()
swapApp i f = do
I.readIORef i >>= killThread
f >>= I.writeIORef i
import Text.Shakespeare.Text (st)
devel :: ([String] -> IO ()) -- ^ cabal
-> IO ()
devel cabalCmd = do
e <- doesFileExist "dist/devel-flag"
when e $ removeFile "dist/devel-flag"
listenThread <- forkIO (appMessage "Initializing, please wait") >>= I.newIORef
import Build (touch, getDeps, findHaskellFiles)
cabal <- defaultPackageDesc normal
_ <- readPackageDescription normal cabal
lockFile :: FilePath
lockFile = "dist/devel-terminate"
mhpd <- defaultHookedPackageDesc
_ <- case mhpd of
Nothing -> return emptyHookedBuildInfo
Just fp -> readHookedBuildInfo normal fp
writeLock :: IO ()
writeLock = do
createDirectoryIfMissing True "dist"
writeFile lockFile ""
cabalCmd ["configure", "-fdevel"]
removeLock :: IO ()
removeLock = try_ (removeFile lockFile)
let myTry :: IO () -> IO ()
myTry f = try f >>= \x -> case x of
Left err -> swapApp listenThread $ forkIO $ appMessage $ L.pack $ show (err :: SomeException)
Right y -> return y
let getNewApp :: IO ()
getNewApp = myTry $ do
putStrLn "Rebuilding app"
swapApp listenThread $ forkIO $ appMessage "Rebuilding your app, please wait"
devel :: Bool -> IO ()
devel isDevel = do
writeLock
putStrLn "Yesod devel server. Pres ENTER to quit"
_ <- forkIO $ do
cabal <- D.findPackageDesc "."
gpd <- D.readPackageDescription D.normal cabal
let pid = (D.package . D.packageDescription) gpd
deps <- getDeps
touchDeps deps
checkCabalFile gpd
cabalCmd ["build"]
defaultMainArgs ["install"]
_ <- if isDevel
then rawSystem "cabal-dev" ["configure", "--cabal-install-arg=-fdevel"]
else rawSystem "cabal" ["configure", "-fdevel"]
pi' <- getPackageName
writeFile "dist/devel.hs" $ unlines
[ "{-# LANGUAGE PackageImports #-}"
, concat
[ "import \""
, pi'
, "\" 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 Control.Concurrent (threadDelay)"
, ""
, "main :: IO ()"
, "main = do"
, " putStrLn \"Starting app\""
, " wdap <- return $ fromJust $ fromDynamic withDevelAppPort"
, " forkIO $ wdap $ \\(port, app) -> run port app"
, " loop"
, ""
, "loop :: IO ()"
, "loop = do"
, " threadDelay 100000"
, " e <- doesFileExist \"dist/devel-flag\""
, " if e then removeFile \"dist/devel-flag\" else loop"
]
swapApp listenThread $ forkIO $ do
putStrLn "Calling runghc..."
ph <- runCommand "runghc dist/devel.hs"
let forceType :: Either SomeException () -> ()
forceType = const ()
fmap forceType $ try sleepForever
writeFile "dist/devel-flag" ""
putStrLn "Terminating external process"
terminateProcess ph
putStrLn "Process terminated"
ec <- waitForProcess ph
putStrLn $ "Exit code: " ++ show ec
T.writeFile "dist/devel.hs" (develFile pid)
loop Map.empty getNewApp
mainLoop isDevel
_ <- getLine
writeLock
exitSuccess
sleepForever :: IO ()
sleepForever = forever $ threadDelay 1000000
mainLoop :: Bool -> IO ()
mainLoop isDevel = forever $ do
putStrLn "Rebuilding application..."
touch
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
@ -134,25 +126,68 @@ getFileList = do
fs <- getFileStatus f
return (f, modificationTime fs)
loop :: FileList -> IO () -> IO ()
loop oldList getNewApp = do
watchForChanges :: FileList -> IO ()
watchForChanges list = do
newList <- getFileList
when (newList /= oldList) getNewApp
threadDelay 1000000
loop newList getNewApp
if list /= newList
then return ()
else threadDelay 1000000 >> watchForChanges list
{-
errApp :: String -> Application
errApp s _ = return $ ResponseBuilder status500 [("Content-Type", "text/plain")] $ fromString s
-}
showPkgName :: D.PackageId -> String
showPkgName = (\(D.PackageName n) -> n) . D.pkgName
getPackageName :: IO String
getPackageName = do
xs <- getDirectoryContents "."
case mapMaybe (toCabal . reverse) xs of
[x] -> return x
[] -> error "No cabal files found"
_ -> error "Too many cabal files found"
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
toCabal ('l':'a':'b':'a':'c':'.':x) = Just $ reverse x
toCabal _ = Nothing
isDevelLib ((D.Var (D.Flag (D.FlagName "devel"))), _, _) = True
isDevelLib _ = False

View File

@ -26,4 +26,7 @@ Start your project:
cd ~project~ && cabal install && yesod devel
or if you use cabal-dev:
cd ~project~ && cabal-dev install && yesod --dev devel

8
yesod/main.hs Normal file → Executable file
View File

@ -1,12 +1,11 @@
import Scaffolding.Scaffolder
import System.Environment (getArgs)
import System.Exit (exitWith)
import System.Process (rawSystem)
import Build (touch)
import Devel (devel)
import System.Process (rawSystem)
main :: IO ()
main = do
args' <- getArgs
@ -15,13 +14,12 @@ main = do
"--dev":rest -> (True, rest)
_ -> (False, args')
let cmd = if isDev then "cabal-dev" else "cabal"
let cabal rest = rawSystem cmd rest >> return ()
let build rest = rawSystem cmd $ "build":rest
case args of
["init"] -> scaffold
"build":rest -> touch >> build rest >>= exitWith
["touch"] -> touch
["devel"] -> devel cabal
["devel"] -> devel isDev
["version"] -> putStrLn "0.9"
"configure":rest -> rawSystem cmd ("configure":rest) >>= exitWith
_ -> do
@ -32,4 +30,6 @@ main = do
putStrLn " build Build project (performs TH dependency analysis)"
putStrLn " touch Touch any files with altered TH dependencies but do not build"
putStrLn " devel Run project with the devel server"
putStrLn " use --dev devel to build with cabal-dev"
putStrLn " version Print the version of Yesod"

View File

@ -87,6 +87,7 @@ executable yesod
build-depends: base >= 4 && < 4.3
build-depends: parsec >= 2.1 && < 4
, text >= 0.11 && < 0.12
, shakespeare-text >= 0.10 && < 0.11
, bytestring >= 0.9 && < 0.10
, time >= 1.1.4 && < 1.3
, template-haskell
@ -97,6 +98,7 @@ executable yesod
, attoparsec-text >= 0.8.5 && < 0.9
, http-types >= 0.6.1 && < 0.7
, blaze-builder >= 0.2 && < 0.4
, filepath >= 1.2 && < 1.3
, process
ghc-options: -Wall -threaded
main-is: main.hs