gdo 0.1.0 → 0.1.2
raw patch · 15 files changed
+177/−81 lines, 15 filesdep ~base
Dependency ranges changed: base
Files
- DepInfo.hs +17/−0
- Lib.hs +74/−26
- Main.hs +8/−16
- README.org +27/−1
- all.do +1/−1
- default.nix.do +3/−0
- examples/defaults/default.o.do +16/−0
- examples/test.c +7/−0
- examples/test.do +8/−0
- gdo.cabal +7/−5
- gdo.do +1/−1
- shell.nix.do +8/−0
- tests/defaults/default.o.do +0/−16
- tests/test.c +0/−7
- tests/test.do +0/−8
+ DepInfo.hs view
@@ -0,0 +1,17 @@+module DepInfo+ ( DepInfo(..),+ depInfoUpToDate+ )+ where++data DepInfo = DepInfo { depInfoFilePath :: FilePath+ , depInfoSavedMD5 :: Maybe String+ , depInfoCurrentMD5 :: Maybe String+ }+ deriving Show++-- | Returns true if the dependency has not changed since the last+-- recording.+depInfoUpToDate :: DepInfo -> Bool+depInfoUpToDate di =+ depInfoSavedMD5 di == depInfoCurrentMD5 di
Lib.hs view
@@ -4,13 +4,15 @@ -- * directory names , getMetaDir , getMissingDepsFile- , metaIgnoreFiles , getDefaultDirs -- * Actions , executeRecipe , getRecipeFile , upToDate , recordDependency+ , recordMissingDependency+ , purgeRecordedDependencies+ , getHashesAndDepsForTarget ) where @@ -21,21 +23,29 @@ import qualified Data.ByteString as BS import qualified Data.Map as Map (toList,fromList,insert) import Data.Maybe (listToMaybe)-import System.Directory (getCurrentDirectory, makeAbsolute, doesFileExist, renameFile, removeFile, createDirectoryIfMissing, getDirectoryContents)+import System.Directory (getCurrentDirectory, makeAbsolute, doesFileExist, renameFile, removeFile, createDirectoryIfMissing, getDirectoryContents, removeDirectoryRecursive, withCurrentDirectory) import System.Environment (getEnvironment,lookupEnv) import System.Exit (ExitCode(ExitSuccess)) import System.FilePath ((</>), takeBaseName, dropFileName, replaceBaseName, addExtension,splitSearchPath)-import System.IO (hPutStrLn,stderr,hPutStr,withFile,IOMode(ReadMode,WriteMode),hFileSize)+import System.IO (hPutStrLn,stderr,hPutStr,withFile,IOMode(ReadMode,WriteMode,AppendMode),hFileSize) import System.IO.Error (isDoesNotExistError)-import qualified System.Process as Proc+import qualified System.Process as Proc (env, waitForProcess, createProcess, proc) +import DepInfo++-- | Print a string to standard error output with a newline character+-- in the end. putStrLnErr :: (MonadIO m) => String -> m () putStrLnErr msg = liftIO $ hPutStrLn stderr msg +-- | Print a string to standard error output. putStrErr :: (MonadIO m) => String -> m () putStrErr = liftIO . hPutStr stderr +-- | Get the path for the directory where the targets meta information+-- is stored. This will only work, if you are in the same directory+-- as the target. getMetaDir :: (MonadIO m) => String -- ^ target name -> m FilePath@@ -60,21 +70,23 @@ -> FilePath -- ^ recipe file -> IO ExitCode executeRecipe target recipe = do- createFile tempFileName+ currentDir <- getCurrentDirectory+ let tempFilePath = currentDir </> tempFileName+ createFile tempFilePath finally (do process <- modEnv $ Proc.proc recipe- [targetDir,takeBaseName target, tempFileName]+ [targetDir,takeBaseName target, tempFilePath] (_,_,_,ph) <- liftIO $ Proc.createProcess process code <- liftIO $ Proc.waitForProcess ph if code /= ExitSuccess then putStrLnErr $ "*** redo failed for target "++show target- else do filesize <- getFileSize tempFileName- when (filesize /= 0) $ renameFile tempFileName target+ else do filesize <- getFileSize tempFilePath+ when (filesize /= 0) $ renameFile tempFilePath target return code )- (do exists <- doesFileExist tempFileName- when exists $ removeFile tempFileName+ (do exists <- doesFileExist tempFilePath+ when exists $ removeFile tempFilePath ) where modEnv process = do@@ -113,6 +125,11 @@ getDefaultDirs = maybe [] splitSearchPath <$> liftIO (lookupEnv "GDO_DEFAULTS") +hashFile :: (MonadIO m) => FilePath -> m (Digest MD5)+hashFile target = do+ bs <- liftIO $ BS.readFile target+ return $ hash bs+ upToDate :: (MonadIO m) => [FilePath] -> FilePath@@ -120,18 +137,23 @@ upToDate mDefaultsDir buildTarget = do metaDepDir <- getMetaDir buildTarget liftIO $- not . Prelude.null <$> getRecipeFile mDefaultsDir buildTarget >>= \canBuild ->- if canBuild- then do- r <- tryJust (guard . isDoesNotExistError) $ do- md5s <- Prelude.filter (not . (`elem` metaIgnoreFiles)) <$>- getDirectoryContents metaDepDir- depsNotChanged <- and <$> mapM upToDate' md5s- noNewFiles <- not <$> newFilesCreated- return $ depsNotChanged && noNewFiles- return $ either (const False) id r- else return True+ maybe+ -- When no recipe exists, we assume the target is up to date.+ (return True)+ ( const $ do+ r <- tryJust+ (guard . isDoesNotExistError)+ ( do md5s <- Prelude.filter (not . (`elem` metaIgnoreFiles)) <$>+ getDirectoryContents metaDepDir+ depsNotChanged <- and <$> mapM upToDate' md5s+ noNewFiles <- not <$> newFilesCreated+ return $ depsNotChanged && noNewFiles+ )+ return $ fromRight (const False) r+ ) =<< getRecipeFile mDefaultsDir buildTarget where+ fromRight :: (a -> b) -> Either a b -> b+ fromRight f = either f id upToDate' :: String -> IO Bool upToDate' oldHash = do metaDepDir <- getMetaDir buildTarget@@ -148,7 +170,8 @@ newFilesCreated = do missingDepsFile <- getMissingDepsFile buildTarget r <- tryJust (guard . isDoesNotExistError) $- Prelude.filter (\e -> (e `notElem` metaIgnoreFiles) || Prelude.null e) .+ Prelude.filter (\e -> (e `notElem` metaIgnoreFiles) ||+ Prelude.null e) . lines <$> readFile missingDepsFile case r of@@ -156,6 +179,22 @@ Right watchlist -> or <$> mapM doesFileExist watchlist +getHashesAndDepsForTarget :: FilePath -> IO [DepInfo]+getHashesAndDepsForTarget target = do+ metaDir <- getMetaDir target+ (filenames, oldMd5s) <- withCurrentDirectory metaDir $ do+ hashes <- filter (not . (`elem` metaIgnoreFiles)) <$>+ getDirectoryContents metaDir+ (,) <$> mapM readFile hashes <*> pure hashes+ md5s <- mapM hashFromPath filenames+ return (zipWith3 DepInfo+ filenames+ (map Just oldMd5s)+ (map (Just . show) md5s))+ where+ hashFromPath :: FilePath -> IO (Digest MD5)+ hashFromPath f = hash <$> BS.readFile f+ recordDependency :: FilePath -- ^ target name -> FilePath -- ^ dependency name -> IO ()@@ -167,7 +206,16 @@ liftIO $ withFile md5file WriteMode (`hPutStr` dep) -hashFile :: (MonadIO m) => FilePath -> m (Digest MD5)-hashFile target = do- bs <- liftIO $ BS.readFile target- return $ hash bs+recordMissingDependency :: FilePath -> FilePath -> IO ()+recordMissingDependency target dep = do+ metaDepsDir <- getMetaDir target+ missingDepsFile <- getMissingDepsFile target+ liftIO $ createDirectoryIfMissing True metaDepsDir+ liftIO $ withFile+ missingDepsFile+ AppendMode+ (`hPutStrLn` dep)++purgeRecordedDependencies :: FilePath -> IO ()+purgeRecordedDependencies target =+ removeDirectoryRecursive =<< getMetaDir target
Main.hs view
@@ -10,7 +10,6 @@ import System.Environment import System.Exit import System.FilePath-import System.IO import System.IO.Error import Lib@@ -20,16 +19,17 @@ clOption :: [OptDescr Flag] clOption = [ Option "" ["if"] (ReqArg read "MODE")- "Redo mode, valid modes are: IfChanged, IfCreated"+ "Redo mode, valid modes are: Changed, Created" , Option "h" ["help"] (NoArg Help) "Print usage info" ] variableInfo :: String variableInfo =- unlines- [ "Environment variables:"- , "\tGDO_DEFAULTS : Path to directory containing default do files"+ unlines $+ "Environment variables:" :+ map ('\t':)+ [ "GDO_DEFAULTS : List of paths to directories containing default do files" ] usageHeader :: String@@ -92,7 +92,7 @@ isUpToDate <- upToDate mDefaultsDir target unless (isUpToDate && targetDoesExist) $ do void . liftIO . tryJust (guard . isDoesNotExistError) $- deleteDepDir+ purgeRecordedDependencies target maybe (do targetExists <- liftIO $ doesFileExist target unless targetExists $ do (liftIO . putStrLnErr)@@ -104,12 +104,10 @@ redoIfChanged mDefaultsDir target recipe buildResult <- liftIO $ executeRecipe target recipe when (buildResult /= ExitSuccess)- (do liftIO deleteDepDir+ (do liftIO $ purgeRecordedDependencies target throwE buildResult) liftIO $ putStrLnErr $ "redone "++show target) =<< getRecipeFile mDefaultsDir target- where- deleteDepDir = removeDirectoryRecursive =<< getMetaDir target redoIfCreated :: (MonadIO m) => [FilePath] -- ^ directory path of defaults@@ -120,13 +118,7 @@ depExists <- liftIO $ doesFileExist dep if depExists then redoIfChanged mDefaultsDir buildTarget dep- else do metadir <- getMetaDir buildTarget- missingDepsFile <- getMissingDepsFile buildTarget- liftIO $ createDirectoryIfMissing True metadir- liftIO $ withFile- missingDepsFile- AppendMode- (`hPutStrLn` dep)+ else liftIO $ recordMissingDependency buildTarget dep redoIfChanged :: MonadIO m => [FilePath] -- ^ directory of defaults
README.org view
@@ -101,11 +101,37 @@ default rule based on the targets file extension. This only works for files which have an extension. Default do files are only searched in the same directory as the target by default. You can- extend the search path via the environment variable *GDO_DEFAULTS*.+ extend the search path via the environment variable *GDO\_DEFAULTS*. When specified this variable must be a list of paths seperated by colons. This path is searched for default do files after no default do files was found in the same directory as the target is in.++** EXAMPLE+ The source code you received with this product should contain a set+ of examples for using *gdo*. These examples can be found in the+ *examples* directory.++ This small example builds the "hello world" program in C. There+ are 3 files in this directory: The first one is the source for the+ program itself, *test.c*, the other two files are a do script for+ the final executable *test.do* and a default do script for building+ object files from C sources, *defaults/default.o.do*. To build the+ example you have to first point to the defaults directory, so that+ *gdo* can find the default do file. We assume that you are already+ in the examples file of the gdo source code. Then issue *gdo* to+ build the target *test*.+ #+begin_src sh+ export GDO_DEFAULTS=$PWD/defaults+ gdo test+ # output:+ # redo "test"+ # redo "test.o"+ # redone "test.o"+ # redone "test"+ #+end_src+ Now the targets should be build. You should take a look at the do+ files to see some examples of what *gdo* is capable of. * COPYRIGHT Copyright (C) 2016 by Sebastian Jordan. This file is part of *gdo*.
all.do view
@@ -1,3 +1,3 @@ #!/usr/bin/env sh -gdo --if Changed gdo tests/test default.nix README man/gdo.1+gdo --if Changed gdo default.nix README man/gdo.1
default.nix.do view
@@ -2,6 +2,9 @@ # generate a nix expression for the project +# This line is here because default.nix.do is also the default do file+# for all .nix files in the current directory.+gdo --if Created $2.do gdo --if Changed gdo.cabal cabal2nix . > $3
+ examples/defaults/default.o.do view
@@ -0,0 +1,16 @@+#!/usr/bin/env sh++set -e++CC=cc+GDO_ENVIRONMENT=$1/environment++gdo --if Changed $2.c $(cc -MM $2.c | tr -d '\n\\' | cut -d':' -f2)+gdo --if Created $2.o.do $GDO_ENVIRONMENT++# We want to check for custom environment variables like CC+if [ -e $GDO_ENVIRONMENT ]; then+ source $GDO_ENVIRONMENT+fi++${CC} -c $2.c -o $3 > /dev/null
+ examples/test.c view
@@ -0,0 +1,7 @@+#include "stdio.h"++int main(){+ printf("Hello World!");+ return 0;+}+
+ examples/test.do view
@@ -0,0 +1,8 @@+#!/bin/sh++set -e++gdo --if Changed test.o test.do+gdo --if Created stdio.h++gcc test.o -o $3
gdo.cabal view
@@ -2,7 +2,7 @@ -- see http://haskell.org/cabal/users-guide/ name: gdo-version: 0.1.0+version: 0.1.2 synopsis: recursive atomic build system description: *gdo* is a build system similar to **GNU Make**. It builds files from@@ -22,12 +22,13 @@ , all.do , clean.do , default.nix.do+ , shell.nix.do , gdo.cabal , gdo.do , man/gdo.1.do- , tests/test.c- , tests/test.do- , tests/defaults/default.o.do+ , examples/test.c+ , examples/test.do+ , examples/defaults/default.o.do cabal-version: >=1.10 @@ -38,7 +39,8 @@ executable gdo main-is: Main.hs other-modules: Lib- build-depends: base >=4.8 && <4.9,+ , DepInfo+ build-depends: base >=4.8 && <4.10, process >= 1.1, filepath >= 1.0, directory >= 1.2,
gdo.do view
@@ -4,7 +4,7 @@ HC="ghc -v0" -gdo --if Changed Main.hs Lib.hs+gdo --if Changed Main.hs Lib.hs DepInfo.hs gdo --if Created environment if [ -e environment ]; then
+ shell.nix.do view
@@ -0,0 +1,8 @@+#!/bin/sh++# generate a nix expression for the project++gdo --if Changed gdo.cabal++cabal2nix . --shell > $3+
− tests/defaults/default.o.do
@@ -1,16 +0,0 @@-#!/usr/bin/env sh--set -e--CC=cc-GDO_ENVIRONMENT=$1/environment--gdo --if Changed $2.c $(cc -MM $2.c | tr -d '\n\\' | cut -d':' -f2)-gdo --if Created $2.o.do $GDO_ENVIRONMENT--# We want to check for custom environment variables like CC-if [ -e $GDO_ENVIRONMENT ]; then- source $GDO_ENVIRONMENT-fi--${CC} -c $2.c -o $3 > /dev/null
− tests/test.c
@@ -1,7 +0,0 @@-#include "stdio.h"--int main(){- printf("Hello World!");- return 0;-}-
− tests/test.do
@@ -1,8 +0,0 @@-#!/bin/sh--set -e--gdo --if Changed test.o test.do-gdo --if Created stdio.h--gcc test.o -o $3