shake 0.1 → 0.1.1
raw patch · 10 files changed
+150/−27 lines, 10 files
Files
- Development/Shake.hs +18/−2
- Development/Shake/Core.hs +14/−10
- Development/Shake/Derived.hs +13/−4
- Development/Shake/Directory.hs +8/−0
- Development/Shake/File.hs +46/−1
- Development/Shake/FilePath.hs +20/−5
- Examples/Self/Main.hs +2/−2
- Examples/Tar/Main.hs +1/−1
- Examples/Tar/list.txt +3/−0
- shake.cabal +25/−2
Development/Shake.hs view
@@ -1,6 +1,22 @@ -- | Main module for defining Shake build systems. You may also want to include--- "Development.Shake.FilePath".+-- "Development.Shake.FilePath", for manipulating file paths. As a simple example,+-- let us build a @result.tar@ file from the contents of @result.txt@:+--+-- @+--import "Development.Shake"+--import "Development.Shake.FilePath"+--+--main = 'shake' 'shakeOptions' $ do+-- 'want' [\"result.tar\"]+-- \"*.tar\" *> \out -> do+-- contents <- 'readFileLines' $ replaceExtension out \"txt\"+-- 'need' contents+-- 'system'' \"tar\" $ [\"-cf\",out] ++ contents+-- @+--+-- For the background theory behind a previous version of Shake the online video:+-- <http://vimeo.com/15465133>. module Development.Shake( shake, -- * Core of Shake@@ -22,7 +38,7 @@ import qualified Development.Shake.File as X import qualified Development.Shake.Directory as X --- | Main entry point for running Shake build systems.+-- | Main entry point for running Shake build systems. For an example see "Development.Shake". shake :: ShakeOptions -> Rules () -> IO () shake opts r = do X.run opts $ do
Development/Shake/Core.hs view
@@ -30,15 +30,15 @@ --------------------------------------------------------------------- -- OPTIONS --- | Options to specify how to control 'shake'.+-- | Options to control 'shake'. data ShakeOptions = ShakeOptions- {shakeFiles :: FilePath -- ^ Where shall I store the database and journal files (defaults to @.@)- ,shakeParallel :: Int -- ^ What is the maximum number of rules I should run in parallel (defaults to @1@)- ,shakeVersion :: Int -- ^ What is the version of your build system, increment to force everyone to rebuild- ,shakeVerbosity :: Int -- ^ 1 = normal, 0 = quiet, 2 = loud+ {shakeFiles :: FilePath -- ^ Where shall I store the database and journal files (defaults to @.@).+ ,shakeParallel :: Int -- ^ What is the maximum number of rules I should run in parallel (defaults to @1@).+ ,shakeVersion :: Int -- ^ What is the version of your build system, increment to force a complete rebuild.+ ,shakeVerbosity :: Int -- ^ 1 = normal, 0 = quiet, 2 = loud. } --- | A default set of 'ShakeOptions'.+-- | The default set of 'ShakeOptions'. shakeOptions :: ShakeOptions shakeOptions = ShakeOptions "." 1 1 1 @@ -46,13 +46,16 @@ --------------------------------------------------------------------- -- RULES --- | Define a pair of types that can be used as a Shake rule.+-- | Define a pair of types that can be used by Shake rules. class ( Show key, Typeable key, Eq key, Hashable key, Binary key, Show value, Typeable value, Eq value, Hashable value, Binary value ) => Rule key value | key -> value where -- | Given that the database contains @key@/@value@, does that still match the on-disk contents?- -- Return 'True' if no work needs to be done.+ --+ -- As an example for filenames/timestamps, if the file exists and had the same timestamp, you+ -- would return 'True', but otherwise return 'False'. For rule values which are not also stored+ -- on disk, 'validStored' should always return 'True'. validStored :: key -> value -> IO Bool validStored _ _ = return True @@ -69,7 +72,7 @@ ruleStored _ k v = unsafePerformIO $ validStored k v -- safe because of the invariants on validStored --- | Define a set of rules. Rules can be created with calls to 'rule'/'action'. Rules are combined+-- | Define a set of rules. Rules can be created with calls to 'rule', 'defaultRule' or 'action'. Rules are combined -- with either the 'Monoid' instance, or more commonly using the 'Monad' instance and @do@ notation. data Rules a = Rules {value :: a -- not really used, other than for the Monad instance@@ -92,6 +95,7 @@ -- | Like 'rule', but lower priority, if no 'rule' exists then 'defaultRule' is checked.+-- All default rules must be disjoint. defaultRule :: Rule key value => (key -> Maybe (Action value)) -> Rules () defaultRule r = mempty{rules=[(0,ARule r)]} @@ -190,7 +194,7 @@ -- | Execute a rule, returning the associated values. If possible, the rules will be run in parallel.--- This function requires that appropriate rules have been added with 'rule'/'defaultRule'.+-- This function requires that appropriate rules have been added with 'rule' or 'defaultRule'. apply :: Rule key value => [key] -> Action [value] apply ks = Action $ do modify $ \s -> s{depends=map newKey ks:depends s}
Development/Shake/Derived.hs view
@@ -12,29 +12,38 @@ import Development.Shake.FilePath -system' :: [String] -> Action ()-system' (x:xs) = do- let cmd = unwords $ toNative x : xs+-- | Execute a system command. This function will raise an error if the exit code is non-zero.+-- Before running 'system'' make sure you 'need' any required files.+system' :: FilePath -> [String] -> Action ()+system' path args = do+ let path2 = toNative path+ let cmd = unwords $ path2 : args putLoud cmd- res <- liftIO $ system cmd+ res <- liftIO $ rawSystem path2 args when (res /= ExitSuccess) $ do k <- currentRule error $ "System command failed while building " ++ show k ++ ", " ++ cmd +-- | @copyFile old new@ copies the existing file from @old@ to @new@. The @old@ file is has 'need' called on it+-- before copying the file. copyFile' :: FilePath -> FilePath -> Action () copyFile' old new = need [old] >> liftIO (copyFile old new) +-- | Read a file, after calling 'need'. readFile' :: FilePath -> Action String readFile' x = need [x] >> liftIO (readFile x) +-- | Write a file, lifted to the 'Action' monad. writeFile' :: FilePath -> String -> Action () writeFile' name x = liftIO $ writeFile name x +-- | A version of 'readFile'' which also splits the result into lines. readFileLines :: FilePath -> Action [String] readFileLines = fmap lines . readFile' +-- | A version of 'writeFile'' which writes out a list of lines. writeFileLines :: FilePath -> [String] -> Action () writeFileLines name = writeFile' name . unlines
Development/Shake/Directory.hs view
@@ -70,15 +70,23 @@ liftIO $ getDir x +-- | Returns 'True' if the file exists. doesFileExist :: FilePath -> Action Bool doesFileExist = apply1 . Exist +-- | Get the contents of a directory. The result will be sorted, and will not contain+-- the files @.@ or @..@ (unlike the standard Haskell version). It is usually better to+-- call either 'getDirectoryFiles' or 'getDirectoryDirs'. The resulting paths will be relative+-- to the first argument. getDirectoryContents :: FilePath -> Action [FilePath] getDirectoryContents x = getDirAction $ GetDir x +-- | Get the files in a directory that match a particular pattern.+-- For the interpretation of the pattern see '?=='. getDirectoryFiles :: FilePath -> FilePattern -> Action [FilePath] getDirectoryFiles x f = getDirAction $ GetDirFiles x f +-- | Get the directories contained by a directory, does not include @.@ or @..@. getDirectoryDirs :: FilePath -> Action [FilePath] getDirectoryDirs x = getDirAction $ GetDirDirs x
Development/Shake/File.hs view
@@ -3,7 +3,7 @@ module Development.Shake.File( FilePattern, need, want, defaultRuleFile,- (?==), (?>), (**>), (*>)+ (?==), (*>), (**>), (?>) ) where import Control.Monad.IO.Class@@ -19,6 +19,8 @@ import System.FilePath(takeDirectory) +-- | A type synonym for file patterns, containing @\/\/@ and @*@. For the syntax+-- and semantics of 'FilePattern' see '?=='. type FilePattern = String newtype File = File FilePath@@ -50,13 +52,34 @@ return $ fromMaybe (error msg) res +-- | Require that the following files are built before continuing. Particularly+-- necessary when calling 'system''. As an example:+--+-- > "//*.rot13" *> \out -> do+-- > let src = dropExtension out+-- > need [src]+-- > system' ["rot13",src,"-o",out] need :: [FilePath] -> Action () need xs = (apply $ map File xs :: Action [FileTime]) >> return () +-- | Require that the following are built by the rules, used to specify the target.+--+-- > main = shake shakeOptions $ do+-- > want ["Main.exe"]+-- > ...+--+-- This program will build @Main.exe@, given sufficient rules. want :: [FilePath] -> Rules () want xs = action $ need xs +-- | Define a rule to build files. If the first argument returns 'True' for a given file,+-- the second argument will be used to build it. Usually '*>' is sufficient, but '?>' gives+-- additional power. For any file used by the build system, only one rule should return 'True'.+--+-- > (all isUpper . takeBaseName) *> \out -> do+-- > let src = replaceBaseName out $ map toLower $ takeBaseName out+-- > writeFile' . map toUpper =<< readFile' src (?>) :: (FilePath -> Bool) -> (FilePath -> Action ()) -> Rules () (?>) test act = rule $ \(File x) -> if not $ test x then Nothing else Just $ do@@ -67,13 +90,35 @@ return $ fromMaybe (error msg) res +-- | Define a set of patterns, and if any of them match, run the associate rule. See '*>'. (**>) :: [FilePattern] -> (FilePath -> Action ()) -> Rules () (**>) test act = (\x -> any (x ?==) test) ?> act +-- | Define a rule that matches a 'FilePattern'. No file required by the system must be+-- matched by more than one pattern. For the pattern rules, see '?=='.+--+-- > "*.asm.o" *> \out -> do+-- > let src = dropExtension out+-- > need [src]+-- > system' ["as",src,"-o",out]+--+-- To define a build system for multiple compiled languages, we recommend using @.asm.o@,+-- @.cpp.o@, @.hs.o@, to indicate which language produces an object file. (*>) :: FilePattern -> (FilePath -> Action ()) -> Rules () (*>) test act = (test ?==) ?> act +-- | Match a 'FilePattern' against a 'FilePath', There are only two special forms:+--+-- * @*@ matches an entire path component, excluding any separators.+--+-- * @\/\/@ matches an arbitrary number of path componenets.+--+-- Some examples that match:+--+-- > "//*.c" ?== "foo/bar/baz.c"+-- > "*.c" ?== "baz.c"+-- > "test.c" ?== "test.c" (?==) :: FilePattern -> FilePath -> Bool (?==) ('/':'/':x) y = any (x ?==) $ y : [i | '/':i <- tails y] (?==) ('*':x) y = any (x ?==) $ a ++ take 1 b
Development/Shake/FilePath.hs view
@@ -1,7 +1,12 @@ --- | Module for 'FilePath' operations, to be used in place of--- "System.FilePath". It uses @\/@ as the directory separator to ensure--- uniqueness, and squashes any @\/.\/@ components.+-- | A module for 'FilePath' operations, to be used instead of "System.FilePath"+-- when writing build systems. In build systems, when using the file name+-- as a key for indexing rules, it is important that two different strings do+-- not refer to the same on-disk file. We therefore follow the conventions:+--+-- * Always use @\/@ as the directory separator, even on Windows.+--+-- * When combining 'FilePath' values with '</>' we squash any @\/.\/@ components. module Development.Shake.FilePath( module System.FilePath.Posix, dropDirectory1, takeDirectory1, normalise,@@ -34,18 +39,28 @@ takeDirectory1 = takeWhile (not . isPathSeparator) +-- | Normalise a 'FilePath', translating any path separators to @\/@. normalise :: FilePath -> FilePath-normalise = map (\x -> if x == '\\' then '/' else x)+normalise = map (\x -> if isPathSeparator x then '/' else x) +-- | Convert to native path separators, namely @\\@ on Windows. toNative :: FilePath -> FilePath-toNative = map (\x -> if x == '/' then Native.pathSeparator else x)+toNative = map (\x -> if isPathSeparator x then Native.pathSeparator else x) +-- | Combine two file paths, an alias for 'combine'. (</>) :: FilePath -> FilePath -> FilePath (</>) = combine +-- | Combine two file paths. Any leading @.\/@ or @..\/@ components in the right file+-- are eliminated.+--+-- > combine "aaa/bbb" "ccc" == "aaa/bbb/ccc"+-- > combine "aaa/bbb" "./ccc" == "aaa/bbb/ccc"+-- > combine "aaa/bbb" "../ccc" == "aaa/ccc" combine :: FilePath -> FilePath -> FilePath combine x ('.':'.':'/':y) = combine (takeDirectory x) y+combine x ('.':'/':y) = combine x y combine x y = Posix.combine x y
Examples/Self/Main.hs view
@@ -23,7 +23,7 @@ src <- readFileLines $ replaceExtension res "deps" let os = map (out . moduleToFile "o") $ "Main":src need os- system' $ ["ghc","-o",res] ++ os ++ flags+ system' "ghc" $ ["-o",res] ++ os ++ flags out "/*.deps" *> \res -> do dep <- readFileLines $ replaceExtension res "dep"@@ -45,7 +45,7 @@ dep <- readFileLines $ replaceExtension res "dep" let hs = unout $ replaceExtension res "hs" need $ hs : map (out . moduleToFile "hi") dep- system' $ ["ghc","-c",hs,"-odir=output/self","-hidir=output/self","-i=output/self"] ++ flags+ system' "ghc" $ ["-c",hs,"-odir=output/self","-hidir=output/self","-i=output/self"] ++ flags hsImports :: String -> [String]
Examples/Tar/Main.hs view
@@ -10,4 +10,4 @@ "output/tar/result.tar" *> \out -> do contents <- readFileLines "Examples/Tar/list.txt" need contents- system' $ ["tar","-cf",out] ++ contents+ system' "tar" $ ["-cf",out] ++ contents
+ Examples/Tar/list.txt view
@@ -0,0 +1,3 @@+Examples/Tar/Main.hs+Main.hs+Examples/Tar/list.txt
shake.cabal view
@@ -1,7 +1,7 @@ cabal-version: >= 1.6 build-type: Simple name: shake-version: 0.1+version: 0.1.1 license: BSD3 license-file: LICENSE category: Development@@ -10,9 +10,32 @@ copyright: Neil Mitchell 2011 synopsis: Build system creator description:- Write build systems, mostly works but seriously early adopters only.+ Shake is a Haskell library for writing build systems - designed as a+ replacement for make. To use Shake the user writes a Haskell program+ that imports the Shake library, defines some build rules, and calls+ shake. Thanks to do notation and infix operators, a simple Shake program+ is not too dissimilar from a simple Makefile. However, as build systems+ get more complex, Shake is able to take advantage of the excellent+ abstraction facilities offered by Haskell and easily support much larger+ projects.+ .+ The Shake library provides all the standard features available in other+ build systems, including automatic parallelism and minimal rebuilds.+ Shake provides highly accurate dependency tracking, including seamless+ support for generated files, and dependencies on system information+ (i.e. compiler version). Shake will eventually be able to produce profile reports, indicating+ which files and take longest to build, and providing an analysis of the+ parallelism.+ .+ The theory behind an old version of Shake is described in a video at+ <http://vimeo.com/15465133>, and an example is given at the top of+ "Development.Shake". Some further examples are included in the Cabal tarball,+ under the @Examples@ directory. homepage: http://community.haskell.org/~ndm/shake/ stability: Beta+extra-source-files:+ Examples/Tar/list.txt+ source-repository head type: darcs