Safe Haskell | None |
---|
This module is used for defining Shake build systems. As a simple example of a Shake build system,
let us build the file result.tar
from the files listed by result.txt
:
import Development.Shake import Development.Shake.FilePath main =shake
shakeOptions
$ dowant
["result.tar"] "*.tar"*>
\out -> do contents <-readFileLines
$replaceExtension
out "txt"need
contentssystem'
"tar" $ ["-cf",out] ++ contents
We start by importing the modules defining both Shake and routines for manipulating FilePath
values.
We define main
to call shake
with the default shakeOptions
. As the second argument to
shake
, we provide a set of rules. There are two common forms of rules, want
to specify target files,
and *>
to define a rule which builds a FilePattern
. We use want
to require that after the build
completes the file result.tar
should be ready.
The *.tar
rule describes how to build files with the extension .tar
, including result.tar
.
We readFileLines
on result.txt
, after changing the .tar
extension to .txt
. We read each line
into the variable contents
-- being a list of the files that should go into result.tar
. Next, we
depend (need
) all the files in contents
. If any of these files change, the rule will be repeated.
Finally we call the tar
program. If either result.txt
changes, or any of the files listed by result.txt
change, then result.tar
will be rebuilt.
When writing a Shake build system, start by defining what you want
, then write rules
with *>
to produce the results. Before calling system'
you should ensure that any files the command
requires are demanded with calls to need
. We offer the following advice to Shake users:
- If
ghc --make
orcabal
is capable of building your project, use that instead. Custom build systems are necessary for many complex projects, but many projects are not complex. - The CmdArgs package (http://hackage.haskell.org/package/cmdargs/) is well suited to providing
command line parsing for build systems, often using flags to set fields in
shakeOptions
. - Put all result files in a distinguished directory, for example
_make
. You can implement aclean
command by removing that directory, usingremoveDirectoryRecursive
. - To obtain parallel builds set
shakeThreads
to a number greater than 1. You may also need to compile with-threaded
. - Often the
want
commands will be determined by command line arguments, to mirror the behaviour ofmake
targets. For a default set ofwant
commands that you later override,withoutActions
can be useful. - Lots of compilers produce
.o
files. To avoid overlapping rules, use.c.o
for C compilers,.hs.o
for Haskell compilers etc. - Do not be afraid to mix Shake rules, system commands and other Haskell libraries -- use each for what it does best.
- The more accurate the dependencies are, the better. Use additional rules like
doesFileExist
andgetDirectoryFiles
to track information other than just the contents of files. For information in the environment that you suspect will change regularly (perhapsghc
version number), either write the information to a file withalwaysRerun
andwriteFileChanged
, or useaddOracle
.
The theory behind Shake is described in an ICFP 2012 paper, Shake Before Building -- Replacing Make with Haskell http://community.haskell.org/~ndm/downloads/paper-shake_before_building-10_sep_2012.pdf. The associated talk forms a short overview of Shake http://www.youtube.com/watch?v=xYCPpXVlqFM.
Acknowledgements: Thanks to Austin Seipp for properly integrating the profiling code.
- shake :: ShakeOptions -> Rules () -> IO ()
- shakeOptions :: ShakeOptions
- type ShakeValue a = (Show a, Typeable a, Eq a, Hashable a, Binary a, NFData a)
- class (ShakeValue key, ShakeValue value) => Rule key value | key -> value where
- storedValue :: key -> IO (Maybe value)
- data Rules a
- defaultRule :: Rule key value => (key -> Maybe (Action value)) -> Rules ()
- rule :: Rule key value => (key -> Maybe (Action value)) -> Rules ()
- action :: Action a -> Rules ()
- withoutActions :: Rules () -> Rules ()
- data Action a
- apply :: Rule key value => [key] -> Action [value]
- apply1 :: Rule key value => key -> Action value
- traced :: String -> IO a -> Action a
- liftIO :: MonadIO m => forall a. IO a -> m a
- data ShakeOptions = ShakeOptions {
- shakeFiles :: FilePath
- shakeThreads :: Int
- shakeVersion :: Int
- shakeVerbosity :: Verbosity
- shakeStaunch :: Bool
- shakeReport :: Maybe FilePath
- shakeLint :: Bool
- shakeDeterministic :: Bool
- shakeFlush :: Maybe Double
- shakeAssume :: Maybe Assume
- shakeAbbreviations :: [(String, String)]
- shakeProgress :: IO Progress -> IO ()
- data Assume
- data Progress = Progress {
- isRunning :: !Bool
- countSkipped :: !Int
- countBuilt :: !Int
- countUnknown :: !Int
- countTodo :: !Int
- timeSkipped :: !Double
- timeBuilt :: !Double
- timeUnknown :: !Double
- timeTodo :: !(Double, Int)
- progressSimple :: IO Progress -> IO ()
- progressDisplay :: Double -> (String -> IO ()) -> IO Progress -> IO ()
- progressTitlebar :: String -> IO ()
- data Verbosity
- = Silent
- | Quiet
- | Normal
- | Loud
- | Diagnostic
- getVerbosity :: Action Verbosity
- putLoud :: String -> Action ()
- putNormal :: String -> Action ()
- putQuiet :: String -> Action ()
- quietly :: Action a -> Action a
- system' :: FilePath -> [String] -> Action ()
- systemCwd :: FilePath -> FilePath -> [String] -> Action ()
- systemOutput :: FilePath -> [String] -> Action (String, String)
- copyFile' :: FilePath -> FilePath -> Action ()
- readFile' :: FilePath -> Action String
- readFileLines :: FilePath -> Action [String]
- writeFile' :: FilePath -> String -> Action ()
- writeFileLines :: FilePath -> [String] -> Action ()
- writeFileChanged :: FilePath -> String -> Action ()
- need :: [FilePath] -> Action ()
- want :: [FilePath] -> Rules ()
- (*>) :: FilePattern -> (FilePath -> Action ()) -> Rules ()
- (**>) :: [FilePattern] -> (FilePath -> Action ()) -> Rules ()
- (?>) :: (FilePath -> Bool) -> (FilePath -> Action ()) -> Rules ()
- (?>>) :: (FilePath -> Maybe [FilePath]) -> ([FilePath] -> Action ()) -> Rules ()
- (*>>) :: [FilePattern] -> ([FilePath] -> Action ()) -> Rules ()
- type FilePattern = String
- (?==) :: FilePattern -> FilePath -> Bool
- doesFileExist :: FilePath -> Action Bool
- doesDirectoryExist :: FilePath -> Action Bool
- getDirectoryContents :: FilePath -> Action [FilePath]
- getDirectoryFiles :: FilePath -> [FilePattern] -> Action [FilePath]
- getDirectoryDirs :: FilePath -> Action [FilePath]
- addOracle :: (ShakeValue q, ShakeValue a) => (q -> Action a) -> Rules (q -> Action a)
- askOracle :: (ShakeValue q, ShakeValue a) => q -> Action a
- askOracleWith :: (ShakeValue q, ShakeValue a) => q -> a -> Action a
- alwaysRerun :: Action ()
- data Resource
- newResource :: String -> Int -> IO Resource
- withResource :: Resource -> Int -> Action a -> Action a
Core
shake :: ShakeOptions -> Rules () -> IO ()Source
Main entry point for running Shake build systems. For an example see the top of the module Development.Shake.
Use ShakeOptions
to specify how the system runs, and Rules
to specify what to build.
shakeOptions :: ShakeOptionsSource
The default set of ShakeOptions
.
type ShakeValue a = (Show a, Typeable a, Eq a, Hashable a, Binary a, NFData a)Source
Define an alias for the six type classes required for things involved in Shake Rule
s.
This alias is only available in GHC 7.4 and above, and requires the ConstraintKinds
extension.
To define your own values meeting the necessary constraints it is convenient to use the extensions
GeneralizedNewtypeDeriving
and DeriveDataTypeable
to write:
newtype MyType = MyType (String, Bool) deriving (Show,Typeable,Eq,Hashable,Binary,NFData)
class (ShakeValue key, ShakeValue value) => Rule key value | key -> value whereSource
Define a pair of types that can be used by Shake rules. To import all the type classes required see Development.Shake.Classes.
storedValue :: key -> IO (Maybe value)Source
Retrieve the value
associated with a key
, if available.
As an example for filenames/timestamps, if the file exists you should return Just
the timestamp, but otherwise return Nothing
. For rules whose values are not
stored externally, storedValue
should return Nothing
.
Rule GetDirectoryQ GetDirectoryA | |
Rule DoesDirectoryExistQ DoesDirectoryExistA | |
Rule DoesFileExistQ DoesFileExistA | |
Rule FileQ FileA | |
Rule AlwaysRerunQ AlwaysRerunA | |
Rule FilesQ FilesA | |
(ShakeValue (OracleQ q), ShakeValue (OracleA a), ShakeValue q, ShakeValue a) => Rule (OracleQ q) (OracleA a) |
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) the Monad
instance and do
notation.
defaultRule :: Rule key value => (key -> Maybe (Action value)) -> Rules ()Source
Like rule
, but lower priority, if no rule
exists then defaultRule
is checked.
All default rules must be disjoint.
rule :: Rule key value => (key -> Maybe (Action value)) -> Rules ()Source
Add a rule to build a key, returning an appropriate Action
. All rules must be disjoint.
To define lower priority rules use defaultRule
.
action :: Action a -> Rules ()Source
Run an action, usually used for specifying top-level requirements.
withoutActions :: Rules () -> Rules ()Source
Remove all actions specified in a set of rules, usually used for implementing command line specification of what to build.
apply :: Rule key value => [key] -> Action [value]Source
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
or defaultRule
.
traced :: String -> IO a -> Action aSource
Write an action to the trace list, along with the start/end time of running the IO action.
The system'
command automatically calls traced
. The trace list is used for profile reports
(see shakeReport
).
Configuration
data ShakeOptions Source
Options to control the execution of Shake, usually specified by overriding fields in
shakeOptions
:
shakeOptions
{shakeThreads
=4,shakeReport
=Just "report.html"}
The Data
instance for this type reports the shakeProgress
field as having the abstract type ShakeProgress
,
because Data
cannot be defined for functions.
ShakeOptions | |
|
The current assumptions made by the build system, used by shakeAssume
. These options
allow the end user to specify that any rules run are either to be treated as clean, or as
dirty, regardless of what the build system thinks.
These assumptions only operate on files reached by the current action
commands. Any
other files in the database are left unchanged.
AssumeDirty | Assume that all rules reached are dirty and require rebuilding, equivalent to |
AssumeClean | This assumption is unsafe, and may lead to incorrect build results.
Assume that all rules reached are clean and do not require rebuilding, provided the rule
has a |
Progress reporting
Information about the current state of the build, obtained by passing a callback function
to shakeProgress
. Typically a program will use progressDisplay
to poll this value and produce
status messages, which is implemented using this data type.
Progress | |
|
progressSimple :: IO Progress -> IO ()Source
A simple method for displaying progress messages, suitable for using as
shakeProgress
. This function writes the current progress to
the titlebar every five seconds. The function is defined as:
progressSimple =progressDisplay
5progressTitlebar
progressDisplay :: Double -> (String -> IO ()) -> IO Progress -> IO ()Source
Given a sampling interval (in seconds) and a way to display the status message,
produce a function suitable for using as shakeProgress
.
This function polls the progress information every n seconds, produces a status
message and displays it using the display function.
Typical status messages will take the form of 1:25m (15%)
, indicating that the build
is predicted to complete in 1min 25sec, and 15% of the necessary build time has elapsed.
This function uses past observations to predict future behaviour, and as such, is only
guessing. The time is likely to go up as well as down, and will be less accurate from a
clean build (as the system has fewer past observations).
The current implementation is to predict the time remaining (based on timeTodo
) and the
work already done (timeBuilt
). The percentage is then calculated as remaining / (done + remaining)
,
while time left is calculated by scaling remaining
by the observed work rate in this build,
namely done / time_elapsed
.
progressTitlebar :: String -> IO ()Source
Set the title of the current console window to the given text. On Windows
this function uses the SetConsoleTitle
API, elsewhere it uses an xterm
escape sequence. This function may not work for all terminals.
Verbosity
The verbosity data type, used by shakeVerbosity
.
Silent | Don't print any messages. |
Quiet | Only print essential messages (typically errors). |
Normal | Print normal messages (typically errors and warnings). |
Loud | Print lots of messages (typically errors, warnings and status updates). |
Diagnostic | Print messages for virtually everything (for debugging a build system). |
getVerbosity :: Action VerbositySource
Get the current verbosity level, as set by shakeVerbosity
. If you
want to output information to the console, you are recommended to use
putLoud
/ putNormal
/ putQuiet
, which ensures multiple messages are
not interleaved.
putLoud :: String -> Action ()Source
Write a message to the output when the verbosity (shakeVerbosity
) is appropriate.
The output will not be interleaved with any other Shake messages
(other than those generated by system commands).
putNormal :: String -> Action ()Source
Write a message to the output when the verbosity (shakeVerbosity
) is appropriate.
The output will not be interleaved with any other Shake messages
(other than those generated by system commands).
putQuiet :: String -> Action ()Source
Write a message to the output when the verbosity (shakeVerbosity
) is appropriate.
The output will not be interleaved with any other Shake messages
(other than those generated by system commands).
Utility functions
systemOutput :: FilePath -> [String] -> Action (String, String)Source
Execute a system command, returning (stdout,stderr)
.
This function will raise an error if the exit code is non-zero.
Before running systemOutput
make sure you need
any required files.
copyFile' :: FilePath -> FilePath -> Action ()Source
copyFile old new
copies the existing file from old
to new
. The old
file is has need
called on it
before copying the file.
readFileLines :: FilePath -> Action [String]Source
A version of readFile'
which also splits the result into lines.
writeFileLines :: FilePath -> [String] -> Action ()Source
A version of writeFile'
which writes out a list of lines.
writeFileChanged :: FilePath -> String -> Action ()Source
Write a file, but only if the contents would change.
File rules
need :: [FilePath] -> Action ()Source
Require that the following files are built before continuing. Particularly
necessary when calling system'
. As an example:
"*.rot13"*>
\out -> do let src =dropExtension
outneed
[src]system'
["rot13",src,"-o",out]
want :: [FilePath] -> Rules ()Source
Require that the following are built by the rules, used to specify the target.
main =shake
shakeOptions
$ dowant
["Main.exe"] ...
This program will build Main.exe
, given sufficient rules.
(*>) :: FilePattern -> (FilePath -> Action ()) -> Rules ()Source
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
outneed
[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.
I.e., the file foo.cpp
produces object file foo.cpp.o
.
Note that matching is case-sensitive, even on Windows.
(**>) :: [FilePattern] -> (FilePath -> Action ()) -> Rules ()Source
Define a set of patterns, and if any of them match, run the associated rule. See *>
.
(?>) :: (FilePath -> Bool) -> (FilePath -> Action ()) -> Rules ()Source
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 outwriteFile'
. map toUpper =<<readFile'
src
(?>>) :: (FilePath -> Maybe [FilePath]) -> ([FilePath] -> Action ()) -> Rules ()Source
Define a rule for building multiple files at the same time, a more powerful
and more dangerous version of *>>
.
Given an application test ?>> ...
, test
should return Just
if the rule applies, and should
return the list of files that will be produced. This list must include the file passed as an argument and should
obey the invariant:
test x == Just ys ==> x `elem` ys && all ((== Just ys) . test) ys
As an example of a function satisfying the invariaint:
test x |takeExtension
x `elem` [".hi",".o"] = Just [dropExtension
x<.>
"hi",dropExtension
x<.>
"o"] test _ = Nothing
Regardless of whether Foo.hi
or Foo.o
is passed, the function always returns [Foo.hi, Foo.o]
.
(*>>) :: [FilePattern] -> ([FilePath] -> Action ()) -> Rules ()Source
Define a rule for building multiple files at the same time.
As an example, a single invokation of GHC produces both .hi
and .o
files:
["*.o","*.hi"]*>>
\[o,hi] -> do let hs =replaceExtension
o "hs"need
... -- all files the .hs importsystem'
"ghc" ["-c",hs]
However, in practice, it's usually easier to define rules with *>
and make the .hi
depend
on the .o
. When defining rules that build multiple files, all the FilePattern
values must
have the same sequence of //
and *
wildcards in the same order.
type FilePattern = StringSource
A type synonym for file patterns, containing //
and *
. For the syntax
and semantics of FilePattern
see ?==
.
(?==) :: FilePattern -> FilePath -> BoolSource
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 components.
Some examples that match:
"*.c"?==
"foo/bar/baz.c" "*.c"?==
"baz.c" "*.c"?==
"baz.c" "test.c"?==
"test.c"
Examples that don't match:
"*.c"?==
"foo/bar.c" "*/*.c"?==
"foo/bar/baz.c"
An example that only matches on Windows:
"foo/bar" ?==
"foo\\bar"
Directory rules
getDirectoryContents :: FilePath -> Action [FilePath]Source
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.
getDirectoryFiles :: FilePath -> [FilePattern] -> Action [FilePath]Source
Get the files in a directory that match any of a set of patterns.
For the interpretation of the pattern see ?==
.
getDirectoryDirs :: FilePath -> Action [FilePath]Source
Get the directories contained by a directory, does not include .
or ..
.
Additional rules
addOracle :: (ShakeValue q, ShakeValue a) => (q -> Action a) -> Rules (q -> Action a)Source
Add extra information which your build should depend on. For example:
newtype GhcVersion = GhcVersion () deriving (Show,Typeable,Eq,Hashable,Binary,NFData)
addOracle
$ \(GhcVersion _) -> return "7.2.1"
If a rule depends on the GHC version, it can use
, and
if the GHC version changes, the rule will rebuild. We use a askOracle
(GhcVersion ())newtype
around ()
to
allow the use of GeneralizedNewtypeDeriving
. It is common for the value returned
by askOracle
to be ignored, in which case askOracleWith
may help avoid ambiguous type
messages -- although a wrapper function with an explicit type is encouraged.
The result of addOracle
is simply askOracle
restricted to the specific type of the added oracle.
To import all the type classes required see Development.Shake.Classes.
We require that each call to addOracle
uses a different type of question
from any
other calls in a given set of Rule
s, otherwise a runtime error will be raised.
Actions passed to addOracle
will be run in every build they are required,
but if their value does not change they will not invalidate any rules depending on them.
To get a similar behaviour using files, see alwaysRerun
.
As an example, consider tracking package versions installed with GHC:
newtype GhcPkgList = GhcPkgList () deriving (Show,Typeable,Eq,Hashable,Binary,NFData) newtype GhcPkgVersion = GhcPkgVersion String deriving (Show,Typeable,Eq,Hashable,Binary,NFData) do getPkgList <-addOracle
$ \GhcPkgList{} -> do (out,_) <-systemOutput
"ghc-pkg" ["list","--simple-output"] return [(reverse b, reverse a) | x <- words out, let (a,_:b) = break (== '-') $ reverse x] -- getPkgVersion <-addOracle
$ \(GhcPkgVersion pkg) -> do pkgs <- getPkgList return $ lookup pkg pkgs
Using these definitions, any rule depending on the version of shake
should call getPkgVersion "shake"
to rebuild when shake
is upgraded.
askOracle :: (ShakeValue q, ShakeValue a) => q -> Action aSource
askOracleWith :: (ShakeValue q, ShakeValue a) => q -> a -> Action aSource
Get information previously added with addOracle
. The second argument is unused, but can
be useful to avoid ambiguous type error messages.
alwaysRerun :: Action ()Source
Always rerun the associated action. Useful for defining rules that query the environment. For example:
"ghcVersion.txt"*>
\out -> doalwaysRerun
(stdout,_) <-systemOutput
"ghc" ["--version"]writeFileChanged
out stdout
Finite resources
The type representing a finite resource, which multiple build actions should respect.
Created with newResource
in the IO
monad before calling shake
,
and used with withResource
in the Action
monad
when defining rules.
As an example, only one set of calls to the Excel API can occur at one time, therefore Excel is a finite resource of quantity 1. You can write:
do excel <-newResource
"Excel" 1shake
shakeOptions
{shakeThreads
=2} $ dowant
["a.xls","b.xls"] "*.xls"*>
\out ->withResource
excel 1 $system'
"excel" [out,...]
Now the two calls to excel
will not happen in parallel. Using Resource
is better than MVar
as it will not block any other threads from executing. Be careful that the
actions run within withResource
do not themselves require further quantities of this resource, or
you may get a "thread blocked indefinitely in an MVar operation" exception. Typically only
system commands (such as system'
) will be run inside withResource
,
not commands such as need
.
As another example, calls to compilers are usually CPU bound but calls to linkers are usually disk bound. Running 8 linkers will often cause an 8 CPU system to grid to a halt. We can limit ourselves to 4 linkers with:
do disk <-newResource
"Disk" 4shake
shakeOptions
{shakeThreads
=8} $ dowant
[show i<.>
"exe" | i <- [1..100]] "*.exe"*>
\out ->withResource
disk 1 $system'
"ld" ["-o",out,...] "*.o"*>
\out ->system'
"cl" ["-o",out,...]