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 #-} {-# LANGUAGE OverloadedStrings #-}
module Build module Build
( touch ( getDeps
, getDeps
, touchDeps , touchDeps
, touch
, findHaskellFiles , findHaskellFiles
) where ) 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 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 Control.Applicative ((<|>))
import Control.Exception (SomeException, try)
import Control.Monad (when, filterM, forM, forM_)
import qualified Data.Attoparsec.Text.Lazy as A
import Data.Char (isSpace) import Data.Char (isSpace)
import Data.Monoid (mappend) import Data.Monoid (mappend)
import Data.List (isSuffixOf)
import qualified Data.Map as Map import qualified Data.Map as Map
import qualified Data.Set as Set import qualified Data.Set as Set
import System.PosixCompat.Files (accessTime, modificationTime, getFileStatus, setFileTimes) import qualified Data.Text.Lazy.IO as TIO
import qualified System.Posix.Types
import Control.Monad (filterM, forM) import qualified System.Posix.Types
import Control.Exception (SomeException, try) 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 :: IO ()
touch = do touch = touchDeps =<< getDeps
hss <- findHaskellFiles "."
deps' <- mapM determineHamletDeps hss
let deps = fixDeps $ zip hss deps'
touchDeps deps
type Deps = Map.Map FilePath (Set.Set FilePath) type Deps = Map.Map FilePath (Set.Set FilePath)
@ -39,24 +39,37 @@ getDeps = do
return $ fixDeps $ zip hss deps' return $ fixDeps $ zip hss deps'
touchDeps :: Deps -> IO () touchDeps :: Deps -> IO ()
touchDeps = touchDeps deps = (mapM_ go . Map.toList) deps
mapM_ go . Map.toList
where where
go (x, ys) = do go (x, ys) =
(_, mod1) <- getFileStatus' x forM_ (Set.toList ys) $ \y -> do
flip mapM_ (Set.toList ys) $ \y -> do n <- x `isNewerThan` (hiFile y)
(access, mod2) <- getFileStatus' y when n $ do
if mod2 < mod1 putStrLn ("Forcing recompile for " ++ y ++ " because of " ++ x)
then do removeHi y
putStrLn $ "Touching " ++ y ++ " because of " ++ x
_ <- try' $ setFileTimes y access mod1 -- | remove the .hi files for a .hs file, thereby forcing a recompile
return () removeHi :: FilePath -> IO ()
else return () 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' :: IO x -> IO (Either SomeException x)
try' = try 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 getFileStatus' fp = do
efs <- try' $ getFileStatus fp efs <- try' $ getFileStatus fp
case efs of case efs of
@ -76,9 +89,10 @@ findHaskellFiles path = do
fmap concat $ mapM go contents fmap concat $ mapM go contents
where where
go ('.':_) = return [] go ('.':_) = return []
go ('c':"abal-dev" = return []
go ('d':"ist") = return [] go ('d':"ist") = return []
go x = do go x = do
let y = path ++ '/' : x let y = path </> x
d <- doesDirectoryExist y d <- doesDirectoryExist y
if d if d
then findHaskellFiles y then findHaskellFiles y

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

View File

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

View File

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