├── .gitignore ├── .travis.yml ├── Main.hs ├── README.md ├── Util.hs ├── examples ├── basic-aql ├── basic-fabricate.py ├── basic-fbuild.py ├── basic-gup │ ├── all.gup │ └── output.gup ├── basic-make ├── basic-ninja.ninja ├── basic-scons ├── basic-shake.hs ├── basic-tup ├── digest-aql ├── digest-fbuild.py ├── digest-gup │ ├── all.gup │ ├── input-digest.gup │ └── output.gup ├── digest-scons ├── digest-shake.hs ├── include-aql ├── include-fabricate.py ├── include-fbuild.py ├── include-gup │ ├── all.gup │ └── main.o.gup ├── include-make ├── include-ninja.ninja ├── include-scons ├── include-shake.hs ├── include-tup ├── intermediate-fbuild.py ├── intermediate-make ├── intermediate-shake.hs ├── monad1-aql ├── monad1-fabricate.py ├── monad1-fbuild.py ├── monad1-gup │ ├── all.gup │ └── output.gup ├── monad1-make ├── monad1-ninja.ninja ├── monad1-scons ├── monad1-shake.hs ├── monad1-tup ├── monad2-aql ├── monad2-fabricate.py ├── monad2-fbuild.py ├── monad2-gup │ ├── all.gup │ ├── list.gup │ └── output.gup ├── monad2-make ├── monad2-ninja.ninja ├── monad2-scons ├── monad2-shake.hs ├── monad2-tup ├── monad3-aql ├── monad3-fabricate.py ├── monad3-fbuild.py ├── monad3-gup │ ├── all.gup │ ├── gen.gup │ ├── list.gup │ └── output.gup ├── monad3-make ├── monad3-scons ├── monad3-shake.hs ├── multiple-aql ├── multiple-fabricate.py ├── multiple-fbuild.py ├── multiple-gup │ ├── all.gup │ ├── output1.gup │ ├── output2.gup │ ├── source.gup │ ├── source1.gup │ └── source2.gup ├── multiple-ninja.ninja ├── multiple-scons ├── multiple-shake.hs ├── multiple-tup ├── nofileout-aql ├── nofileout-fbuild.py ├── nofileout-gup │ ├── all.gup │ └── input-digest.gup ├── nofileout-tup ├── noleftover-shake.hs.broken ├── noleftover-tup ├── parallel-aql ├── parallel-fbuild.py ├── parallel-gup │ ├── all.gup │ ├── output1.gup │ └── output2.gup ├── parallel-make ├── parallel-ninja.ninja ├── parallel-scons ├── parallel-shake.hs ├── parallel-tup ├── pool-ninja.ninja ├── pool-shake.hs ├── secondary-make ├── secondary-shake.hs ├── spaces-aql ├── spaces-fabricate.py ├── spaces-fbuild.py ├── spaces-gup │ ├── all.gup │ └── output file.gup ├── spaces-make ├── spaces-ninja.ninja ├── spaces-scons ├── spaces-shake.hs ├── spaces-tup.broken ├── spaces-tuplua.lua ├── system1-aql ├── system1-fbuild.py ├── system1-gup │ ├── all.gup │ ├── output.gup │ └── source.gup ├── system1-scons ├── system1-shake.hs ├── system2-aql ├── system2-gup │ ├── all.gup │ ├── data.gup │ └── output.gup ├── system2-scons ├── system2-shake.hs ├── system2-tup ├── unchanged-aql ├── unchanged-fabricate.py ├── unchanged-fbuild.py ├── unchanged-gup │ ├── all.gup │ ├── output.gup │ └── source.gup ├── unchanged-ninja.ninja ├── unchanged-scons ├── unchanged-shake.hs ├── unchanged-tup ├── wildcard-aql ├── wildcard-fabricate.py ├── wildcard-fbuild.py ├── wildcard-gup │ ├── Gupfile │ └── copier ├── wildcard-make ├── wildcard-scons ├── wildcard-shake.hs └── wildcard-tup ├── travis.sh └── util ├── basic-run ├── digest-run ├── include-1.h ├── include-2.h ├── include-main.c ├── intermediate-run ├── monad2-run ├── monad3-gen ├── monad3-run ├── multiple-gen ├── multiple-run ├── nofileout-run ├── noleftover-run ├── parallel-run ├── pool-run ├── secondary-run ├── system1-gen ├── system1-run ├── system2-run ├── unchanged-gen └── unchanged-run /.gitignore: -------------------------------------------------------------------------------- 1 | temp 2 | .log 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: haskell 2 | install: echo No cabal install necessary 3 | script: sh travis.sh 4 | -------------------------------------------------------------------------------- /Main.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | 3 | -- | A test script to check those build systems claiming to implement a test 4 | -- do in fact do so. 5 | module Main(main) where 6 | 7 | import Util 8 | 9 | 10 | main :: IO () 11 | main = do 12 | test "basic" basic 13 | test "parallel" parallel 14 | test "include" include 15 | test "wildcard" wildcard 16 | test "spaces" spaces 17 | test "monad1" monad1 18 | test "monad2" monad2 19 | test "monad3" monad3 20 | test "unchanged" unchanged 21 | test "multiple" multiple 22 | test "system1" system1 23 | test "system2" system2 24 | test "pool" pool 25 | test "digest" digest 26 | test "nofileout" nofileout 27 | test "noleftover" noleftover 28 | test "secondary" secondary 29 | test "intermediate" intermediate 30 | 31 | 32 | basic :: ([Opt] -> IO ()) -> IO () 33 | basic run = do 34 | writeFile "input" "xyz" 35 | run [Contents "output" "xyz"] 36 | run [NoChange] 37 | writeFile "input" "abc" 38 | run [Contents "output" "abc"] 39 | run [NoChange] 40 | 41 | 42 | parallel :: ([Opt] -> IO ()) -> IO () 43 | parallel run = do 44 | writeFile "input1" "xyz" 45 | writeFile "input2" "abc" 46 | run [Parallel 2, Log "start start end end"] 47 | run [NoChange] 48 | 49 | 50 | include :: ([Opt] -> IO ()) -> IO () 51 | include run = do 52 | run [Change "main.o"] 53 | run [NoChange] 54 | appendFile "include-2.h" "\n/* comment */" 55 | run [Change "main.o"] 56 | run [NoChange] 57 | 58 | 59 | wildcard :: ([Opt] -> IO ()) -> IO () 60 | wildcard run = do 61 | x <- randomRIO (1::Int,1000000) 62 | let name = "name" ++ show x 63 | writeFile (name ++ ".in") "abc" 64 | run [Target $ name ++ ".out", Contents (name ++ ".out") "abc"] 65 | run [Target $ name ++ ".out", NoChange] 66 | writeFile (name ++ ".in") "xyz" 67 | run [Target $ name ++ ".out", Contents (name ++ ".out") "xyz"] 68 | run [Target $ name ++ ".out", NoChange] 69 | 70 | 71 | spaces :: ([Opt] -> IO ()) -> IO () 72 | spaces run = do 73 | writeFile "input file" "abc" 74 | run [Target "output file", Contents "output file" "abc"] 75 | run [Target "output file", NoChange] 76 | writeFile "input file" "xyz" 77 | run [Target "output file", Contents "output file" "xyz"] 78 | run [Target "output file", NoChange] 79 | 80 | 81 | monad1 :: ([Opt] -> IO ()) -> IO () 82 | monad1 run = do 83 | writeBinary "list" "input1\ninput2\n" 84 | writeFile "input1" "test" 85 | writeFile "input2" "again" 86 | run [Target "output", Contents "output" "testagain"] 87 | run [Target "output", NoChange] 88 | writeFile "input1" "more" 89 | run [Target "output", Contents "output" "moreagain"] 90 | run [Target "output", NoChange] 91 | writeBinary "list" "input1\n" 92 | run [Target "output", Contents "output" "more"] 93 | run [Target "output", NoChange] 94 | writeFile "input2" "x" 95 | run [Target "output", NoChange] 96 | 97 | 98 | monad2 :: ([Opt] -> IO ()) -> IO () 99 | monad2 run = do 100 | writeBinary "source" "output1\noutput2\n" 101 | writeFile "input1" "test" 102 | writeFile "input2" "again" 103 | run [Target "output", Contents "output" "testagain", Log "run"] 104 | run [Target "output", NoChange, Log "run"] 105 | writeFile "input1" "more" 106 | run [Target "output", Contents "output" "moreagain"] 107 | run [Target "output", NoChange] 108 | writeBinary "source" "output1\n" 109 | run [Target "output", Contents "output" "more", Log "run run"] 110 | run [Target "output", NoChange] 111 | writeFile "input2" "x" 112 | run [Target "output", NoChange, Log "run run"] 113 | 114 | 115 | monad3 :: ([Opt] -> IO ()) -> IO () 116 | monad3 run = do 117 | writeBinary "source" "output1\noutput2\n" 118 | writeFile "input1" "test" 119 | writeFile "input2" "again" 120 | run [Target "output", Contents "output" "testagain", Log "run"] 121 | run [Target "output", NoChange, Log "run", Missing "gen"] 122 | writeBinary "source" "gen\noutput2\n" 123 | run [Target "output", Contents "output" "Generated\nagain"] 124 | run [Target "output", NoChange] 125 | 126 | 127 | unchanged :: ([Opt] -> IO ()) -> IO () 128 | unchanged run = do 129 | writeFile "input" "foo is in here" 130 | run [Target "output", Contents "source" "foo is out here", Contents "output" "foo xs out here", Log "run"] 131 | run [Target "output", NoChange] 132 | writeFile "input" "bar is in here" 133 | run [Target "output", Contents "source" "bar is out here", Contents "output" "bar xs out here", Log "run run"] 134 | run [Target "output", NoChange] 135 | writeFile "input" "bar is out here" 136 | run [Target "output", Contents "source" "bar is out here", Contents "output" "bar xs out here", Log "run run"] 137 | run [Target "output", NoChange] 138 | 139 | 140 | multiple :: ([Opt] -> IO ()) -> IO () 141 | multiple run = do 142 | writeFile "input" "abbc" 143 | run [Target "output1", Target "output2", Contents "output1" "AbbC", Contents "output2" "aBBC", Log "run run"] 144 | run [Target "output1", Target "output2", NoChange] 145 | writeFile "input" "aBBc" 146 | run [Target "output1", Target "output2", Contents "output1" "ABBC", Contents "output2" "aBBC", Log "run run run"] 147 | run [Target "output1", NoChange] 148 | writeFile "input" "ab" 149 | run [Target "output1", Contents "output1" "Ab", Contents "output2" "aBBC"] 150 | run [Target "output2", Contents "output1" "Ab", Contents "output2" "aB"] 151 | run [Target "output1", Target "output2", NoChange] 152 | 153 | 154 | system1 :: ([Opt] -> IO ()) -> IO () 155 | system1 run = do 156 | writeFile "system1-data" "foo" 157 | writeFile "source" "none" 158 | run [Target "output", Contents "output" "foo", Log "gen run"] 159 | run [Target "output", Contents "output" "foo", Log "gen run gen"] 160 | writeFile "system1-data" "bar" 161 | run [Target "output", Contents "output" "bar", Log "gen run gen gen run"] 162 | 163 | 164 | system2 :: ([Opt] -> IO ()) -> IO () 165 | system2 run = do 166 | let varName = "SYSTEM2_DATA" 167 | run [Contents "output" "", Log "run"] 168 | run [NoChange] 169 | run [Contents "output" "foo", Log "run run", Env varName "foo"] 170 | run [NoChange, Env varName "foo"] 171 | run [Contents "output" "bar", Log "run run run", Env varName "bar"] 172 | run [NoChange, Env varName "bar"] 173 | run [Contents "output" "", Log "run run run run"] 174 | run [NoChange] 175 | 176 | 177 | pool :: ([Opt] -> IO ()) -> IO () 178 | pool run = do 179 | writeFile "input1" "xyz" 180 | writeFile "input2" "abc" 181 | writeFile "input3" "def" 182 | run [Parallel 8, Log "start start end start end end"] 183 | run [NoChange] 184 | 185 | 186 | digest :: ([Opt] -> IO ()) -> IO () 187 | digest run = do 188 | writeFile "input" "xyz" 189 | run [Contents "output" "xyz"] 190 | run [NoChange] 191 | writeFile "input" "abc" 192 | run [Contents "output" "abc"] 193 | run [NoChange] 194 | writeFile "input" "abc" 195 | run [NoChange] 196 | 197 | 198 | nofileout :: ([Opt] -> IO ()) -> IO () 199 | nofileout run = do 200 | writeFile "input" "xyz" 201 | run [Log "xyz"] 202 | run [NoChange] 203 | writeFile "input" "abc" 204 | run [Log "xyzabc"] 205 | run [NoChange] 206 | 207 | 208 | noleftover :: ([Opt] -> IO ()) -> IO () 209 | noleftover run = do 210 | writeFile "foo.in" "foo" 211 | writeFile "bar.in" "bar" 212 | run [Contents "foo.out" "foo", Contents "bar.out" "bar"] 213 | run [NoChange] 214 | removeFile "bar.in" 215 | writeFile "baz.in" "baz" 216 | run [Contents "foo.out" "foo", Contents "baz.out" "baz", Missing "bar.out"] 217 | run [NoChange] 218 | 219 | 220 | secondary :: ([Opt] -> IO ()) -> IO () 221 | secondary run = do 222 | writeFile "input" "xyz" 223 | run [Contents "output" "xyz * *", Contents "secondary" "xyz *", Log "run run"] 224 | run [NoChange] 225 | removeFile "secondary" 226 | run [Contents "output" "xyz * *", Missing "secondary", Log "run run"] 227 | run [NoChange] 228 | writeFile "input" "abc" 229 | run [Contents "output" "abc * *", Contents "secondary" "abc *", Log "run run run run"] 230 | run [NoChange] 231 | 232 | 233 | intermediate :: ([Opt] -> IO ()) -> IO () 234 | intermediate run = do 235 | writeFile "input" "xyz" 236 | run [Contents "output" "xyz * *", Missing "intermediate", Log "run run"] 237 | run [NoChange] 238 | writeFile "input" "abc" 239 | run [Contents "output" "abc * *", Missing "intermediate", Log "run run run run"] 240 | run [NoChange] 241 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build System Shootout [![Build Status](https://travis-ci.org/ndmitchell/build-shootout.png)](https://travis-ci.org/ndmitchell/build-shootout) 2 | 3 | This project attempts to clarify the relative power of various build systems. Compared to the [Computer Language Shootout](http://benchmarksgame.alioth.debian.org/), this Shootout attempts to answer whether a build system is capable of expressing a particular dependency structure, but does not measure performance. The following build systems have at least one entry: 4 | 5 | * [Make](http://www.gnu.org/software/make/) (GNU version), cross-platform. 6 | * [Ninja](http://martine.github.io/ninja/), cross-platform. 7 | * [Shake](https://github.com/ndmitchell/shake#readme), cross-platform. 8 | * [tup](http://gittup.org/tup/), cross-platform, requiring FUSE on Linux. Does not work with [Travis](https://travis-ci.org/) and cannot be compiled on Windows. 9 | * [fabricate](https://code.google.com/p/fabricate/), works on Linux, some Windows support on some machines, requires at least admin configuration on Vista and above. Works partially with [Travis](https://travis-ci.org/). 10 | * [SCons](http://www.scons.org/), cross-platform. 11 | * [Aqualid](https://github.com/aqualid/), cross-platform. 12 | * [Fbuild](https://github.com/felix-lang/fbuild), cross-platform. 13 | * [Gup](https://github.com/timbertson/gup), cross-platform. 14 | 15 | All build scripts are in the [examples directory](https://github.com/ndmitchell/build-shootout/tree/master/examples), as testname-buildsystem. You can run all the examples with `runhaskell Main` (after installing the [Haskell Platform](http://www.haskell.org/platform/), and any build systems you want to run). Use the argument `make` to only run Make examples, or `basic` to only run the basic test. 16 | 17 | Below are a list of tests, a description of the test, and how each build system fares on it. The tests have pseudo-code for the equivalent untracked straight-line shell script. 18 | 19 | To pass a test the build system must: 20 | 21 | * Follow the specification, including the test case. _Warning:_ this project is young, the specifications and tests are still evolving slightly. 22 | * Must not rebuild things in subsequent runs, all files must end up clean. 23 | * Must not require explicit assume dirty/assume clean flags to be specified. 24 | * Must not explicitly check for the existence of a file (you can always write a build system in a line of shell for any of these problems, the idea is to use the build system). 25 | * All build-shootout shell scripts must be treated as black boxes. any file listed before `--` is treated as an input, any file after is an output. Programs like `gcc`, `cat`, `xargs` and `cp` are the standard Posix utilities. 26 | 27 | Performance is deliberately not measured as all actions are specified via shell scripts to make the results as similar as possible - even if some of the build systems would not normally use that approach. 28 | 29 | #### Contributions 30 | 31 | I welcome contributions, including: 32 | 33 | * Examples in different build systems 34 | * New implementations for existing build systems 35 | * New test cases (provided they show something interesting) 36 | * Corrections of my egregious errors 37 | 38 | ## Test cases 39 | 40 | ### basic: Basic dependency 41 | 42 | Given an input file, create an output file which is a copy of the input file. If the input file changes, or the output file is not present, the output file should be created. 43 | 44 | basic-run input -- output 45 | 46 | * **fabricate: success** 47 | * **Make: success** 48 | * **Ninja: success** 49 | * **SCons: success** 50 | * **Aqualid: success** 51 | * **Fbuild: success** 52 | * **Shake: success** 53 | * **tup: success** 54 | * **Gup: success** 55 | 56 | ### parallel: Parallelism 57 | 58 | Given two targets, build them in parallel. 59 | 60 | parallel-run input1 -- output1; parallel-run input2 -- output2 61 | 62 | * fabricate: unimplemented, not tried 63 | * **Make: success** 64 | * **Ninja: success** 65 | * **SCons: success** 66 | * **Aqualid: success** 67 | * **Fbuild: success** 68 | * **Shake: success** 69 | * **tup: success** 70 | * **Gup: success** 71 | 72 | ### include: C #include files 73 | 74 | Given a C file, compile it, automatically figuring out any transitively included dependencies. If any of those dependencies change in future, it should rebuild. 75 | 76 | gcc main.o -c include-main.c -o main.o 77 | 78 | * **fabricate: success**, but fails on Travis 79 | * **Make: success** 80 | * **Ninja: success** 81 | * **SCons: success** 82 | * **Aqualid: success** 83 | * **Fbuild: success** 84 | * **Shake: success** 85 | * **tup: success** 86 | * **Gup: success** 87 | 88 | ### wildcard: Build a file specified by an extension wildcard 89 | 90 | Given a command line argument of `123.in`, copy `123.in` to `123.out`. Should work for any file with a `.in` suffix. 91 | 92 | cp $1 $1.out 93 | 94 | * **fabricate: success** 95 | * **Make: success** 96 | * Ninja: failure, requires all rules to be listed in full 97 | * **SCons: success** 98 | * **Aqualid: success** 99 | * **Fbuild: success** 100 | * **Shake: success** 101 | * **tup: success** 102 | * **Gup: success** 103 | 104 | ### spaces: Build a file containing spaces 105 | 106 | Work with files including spaces. 107 | 108 | cp "input file" "output file" 109 | 110 | * fabricate: partial, generally works but requires custom code to get command line support for space-including targets 111 | * **Make: success** 112 | * **Ninja: success** 113 | * **SCons: success** 114 | * **Aqualid: success** 115 | * **Fbuild: success** 116 | * **Shake: success** 117 | * **tup: success**, seems to require Lua 118 | * **Gup: success** 119 | 120 | ### monad1: Monadic patterns 121 | 122 | The monad series of tests are designed to probe the difference between applicative build systems and monadic ones, also showing which features allow applicative build systems to "fake" some monadic actions. The first requires depending on a list of files itself stored in a file. 123 | 124 | cat list | xargs cat > output 125 | 126 | * **fabricate: success**, but fails on Travis 127 | * **Make: success** 128 | * **Ninja: success** 129 | * **SCons: success** 130 | * **Aqualid: success** 131 | * **Fbuild: success** 132 | * **Shake: success** 133 | * **tup: success** 134 | * **Gup: success** 135 | 136 | ### monad2: More monadic patterns 137 | 138 | The second test is like the first, but the `list` file itself is generated. 139 | 140 | monad2-run source -- list 141 | cat list | xargs cat > output 142 | 143 | * **fabricate: success**, but fails on Travis 144 | * **Make: success** 145 | * **Ninja: success** 146 | * **SCons: success** 147 | * **Aqualid: success** 148 | * **Fbuild: success** 149 | * **Shake: success** 150 | * **tup: success** 151 | * **Gup: success** 152 | 153 | 154 | ### monad3: More monadic patterns 155 | 156 | The third test requires generating `list`, then generating the files `list` refers to. 157 | 158 | monad3-run source -- list 159 | monad3-gen -- gen # only if gen is in list 160 | cat list | xargs cat > output 161 | 162 | * **fabricate: success**, but fails on Travis 163 | * **Make: success, requires automatic restarting** 164 | * Ninja: unsure, no one has been able to implement it yet 165 | * **SCons: success** 166 | * **Aqualid: success** 167 | * **Fbuild: success** 168 | * **Shake: success** 169 | * tup: unsure, no one has been able to implement it yet 170 | * **Gup: success** 171 | 172 | 173 | ### unchanged: Handle files which do not change 174 | 175 | In some cases `input` will change, but `source` will not change in response. It is important that in these cases `output` is not regenerated. 176 | 177 | unchanged-gen input -- source 178 | unchanged-run source -- output 179 | 180 | * **fabricate: success**, but fails on Travis 181 | * Make: failure, does not seem to work 182 | * **Ninja: success**, requires `restat` to be added 183 | * **SCons: success** 184 | * **Aqualid: success** 185 | * **Fbuild: success** 186 | * **Shake: success** 187 | * **tup: success**, requires `^o^` to be added 188 | * **Gup: success**, requires explicitly calling `gup --contents` 189 | 190 | 191 | ### multiple: Rules with multiple outputs 192 | 193 | In some cases one output will change, but not the other. 194 | 195 | multiple-gen input -- source1 source2 196 | multiple-run source1 -- output1 197 | multiple-run source2 -- output2 198 | 199 | I believe this test can be written on top of `unchanged`, by encoding the dependencies appropriately. 200 | 201 | * **fabricate: success**, but fails on Travis 202 | * Make: failure, does not seem to work 203 | * **Ninja: success**, requires `restat` to be added 204 | * **SCons: success** 205 | * **Aqualid: success** 206 | * **Fbuild: success** 207 | * **Shake: success** 208 | * **tup: success**, requires `^o^` to be added 209 | * **Gup: success**, though hackish. 210 | 211 | 212 | ### system1: Dependency on system information 213 | 214 | Introduce a dependency on a piece of system information that must be recomputed every run. In this scenario `system1-gen` might be equivalent to `gcc --version` and `system1-run` might be `gcc -c`. You must always test the `gcc` version, but only want to rebuild if it changes. 215 | 216 | system1-gen -- source # must always be run 217 | system1-run source -- output # must not run if source does not change 218 | 219 | I believe that given a small amount of shell scripting glue (to run `system1-gen`) this test can be written on top of `unchanged`. 220 | 221 | * fabricate: unsure 222 | * Make: unsure 223 | * Ninja: unsure 224 | * **SCons: success** 225 | * **Aqualid: success** 226 | * **Fbuild: success** 227 | * **Shake: success** 228 | * tup: unsure 229 | * **Gup: success** 230 | 231 | 232 | ### system2: Dependency on system environment variable 233 | 234 | Rerun if and only if `output` does not exist or system environment variable 235 | `SYSTEM2_DATA` was changed. 236 | 237 | system2-run -- output 238 | 239 | * fabricate: unsure 240 | * Make: unsure 241 | * Ninja: unsure 242 | * SCons: unsure 243 | * **Aqualid: success** 244 | * **Shake: success** 245 | * Fbuild: failure 246 | * **tup: success** 247 | * **Gup: success** 248 | 249 | 250 | ### pool: Limit the parallelism in a specific stage 251 | 252 | Run with a parallelism of 8, but limit a specific stage to no more than 2 concurrent runs. 253 | 254 | pool-run input1 -- output1 255 | pool-run input2 -- output2 256 | pool-run input3 -- output3 257 | 258 | * fabricate: unsure 259 | * Make: failure, doesn't seem to work 260 | * **Ninja: success** 261 | * SCons: failure, doesn't support pools 262 | * Aqualid: failure, doesn't support pools 263 | * Fbuild: failure, doesn't support pools 264 | * **Shake: success** 265 | * tup: unsure, nothing I can see 266 | * Gup: failure, doesn't support pools 267 | 268 | 269 | ### digest: Don't rebuild when a file is modified to the same value 270 | 271 | The `input` file will be changed, but sometimes to the same value. 272 | 273 | digest-run input -- output 274 | 275 | * fabricate: unsure 276 | * Make: failure, doesn't support digests 277 | * Ninja: failure, doesn't support digests 278 | * **SCons: success** 279 | * **Aqualid: success** 280 | * **Fbuild: success** 281 | * **Shake: success**, requires setting `Digest` change mode. 282 | * tup: unsure 283 | * **Gup: success**, requires explicitly calling `gup --contents` 284 | 285 | 286 | ### nofileout: Don't produce an output file 287 | 288 | Rerun if and only if `input` file was changed. 289 | 290 | nofileout-run input -- 291 | 292 | * fabricate: unsure 293 | * Make: unsure 294 | * Ninja: unsure 295 | * SCons: unsure 296 | * **Aqualid: success** 297 | * **Fbuild: success** 298 | * Shake: failure, doesn't support rules that are only run if the dependencies change but don't produce an output file 299 | * **tup: success** 300 | * **Gup: success**, though a stamp file still needs to be created. 301 | 302 | 303 | ### noleftover: Remove files left over from a previous build 304 | 305 | # initialize 306 | echo "foo" > foo.in 307 | echo "bar" > bar.in 308 | # build 309 | noleftover-run foo.in -- foo.out 310 | noleftover-run bar.in -- bar.out 311 | # modify 312 | rm bar.in 313 | echo "baz" > baz.in 314 | # rebuld 315 | rm bar.out 316 | noleftover-run baz.in -- baz.out 317 | 318 | * fabricate: unsure 319 | * Make: unsure 320 | * Ninja: unsure 321 | * SCons: unsure 322 | * Aqualid: failure 323 | * Fbuild: failure 324 | * Shake: failure, doesn't seem to be supported 325 | * **tup: success** 326 | * Gup: failure 327 | 328 | 329 | ### secondary: Secondary target 330 | 331 | Building an `output` file from an `input` file requires an auxiliary result `secondary` file. 332 | Removing `secondary` file doesn't cause rebuilding `output` file as long as `input` file wasn't changed. 333 | Changing `input` file causes a rebuild of both files, `secondary` and `output`. 334 | 335 | Within the scope of this test `change` means modification of both, contents and timestamp. 336 | 337 | * fabricate: unsure 338 | * **Make: success** 339 | * Ninja: unsure 340 | * SCons: unsure 341 | * Aqualid: failure 342 | * Fbuild: failure 343 | * **Shake: success** 344 | * tup: unsure 345 | * Gup: failure 346 | 347 | 348 | ### intermediate: Intermediate target 349 | 350 | Building an `output` file from an `input` file requires an auxiliary result `intermediate` file which is automatically removed at the end. 351 | An `intermediate` file isn't created in subsequent builds as long as `input` file wasn't changed. 352 | Changing `input` file causes building `intermediate` file, rebuilding `output` file, and removing `intermediate` file, eventually. 353 | 354 | Within the scope of this test `change` means modification of both, contents and timestamp. 355 | 356 | * fabricate: unsure 357 | * **Make: success** 358 | * Ninja: unsure 359 | * SCons: unsure 360 | * Aqualid: failure 361 | * **Fbuild: success** 362 | * **Shake: success** 363 | * tup: unsure 364 | * Gup: failure 365 | 366 | 367 | ## Build System Power 368 | 369 | The intention of this project is to figure out what dependency features each build system offers, what power they give, and which features can be expressed in terms of others. This section is speculative and evolving. 370 | 371 | ### Pre dependencies (applicative) [all but Fabricate] 372 | 373 | A pre dependency is one where you can introduce a dependency at the start, for example Make's `output: input`. Each output is allowed to express multiple dependencies, but they are all evaluated in isolation from each other. 374 | 375 | ### Post dependencies [Ninja, Fbuild, Shake, tup, Gup] 376 | 377 | A post dependency is one where you introduce a dependency at the end, for example Ninja's `depfile`. These dependencies do not alter this build run, but will add dependencies for the next run. 378 | 379 | ### Mid dependencies (monadic) [Shake, Fbuild, Gup, subsumes pre and post dependencies] 380 | 381 | A monadic dependency lets you introduce a new dependency while running an action after consulting previous dependencies, for example Shake's `need`. 382 | 383 | ### Auto post dependencies [tup, subsumes post dependencies] 384 | 385 | An auto post dependency is one computed from what you actually used, rather than explicitly stated dependencies. 386 | 387 | ### Auto cached commands [fabricate, Fbuild] 388 | 389 | A cached command is where the inputs/outputs for a command are tracked, and the command is treated as a pure function and skipped if its inputs didn't change. This feature is more useful in build systems that go forward (from inputs to outputs) rather than the standard build systems that go from outputs to inputs. 390 | 391 | ### Regenerate [Make] 392 | 393 | Make lets you regenerate the Makefile and then continue again. How that works is anyones guess. 394 | -------------------------------------------------------------------------------- /Util.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ScopedTypeVariables #-} 2 | {-# OPTIONS_GHC -fwarn-unused-binds -fwarn-unused-imports #-} 3 | 4 | -- | A test script to check those build systems claiming to implement a test 5 | -- do in fact do so. 6 | module Util( 7 | test, Opt(..), 8 | randomRIO, 9 | writeBinary, removeFile 10 | ) where 11 | 12 | import Control.Concurrent 13 | import Control.Exception as E 14 | import Control.Monad 15 | import Data.Char 16 | import Data.List 17 | import System.Directory 18 | import System.Environment 19 | import System.Exit 20 | import System.FilePath 21 | import System.IO 22 | import System.Info 23 | import System.Process 24 | import System.Random 25 | import System.Posix.Files 26 | 27 | 28 | copyDir :: FilePath -> FilePath -> IO () 29 | copyDir src dst = do 30 | contents <- getDirectoryContents src 31 | let files = [ file | file <- contents 32 | , file /= "." 33 | , file /= ".." ] 34 | createDirectory dst 35 | forM_ files $ copy src dst 36 | 37 | 38 | copy :: FilePath -> FilePath -> FilePath -> IO () 39 | copy src dst file = do 40 | directoryExists <- doesDirectoryExist $ src file 41 | if directoryExists 42 | then do 43 | copyDir (src file) (dst file) 44 | else 45 | copyFile (src file) (dst file) 46 | 47 | 48 | data Opt 49 | = NoChange 50 | | Contents FilePath String 51 | | Missing FilePath 52 | | Change FilePath 53 | | Parallel Int 54 | | Target FilePath 55 | | Log String 56 | | Env String String 57 | deriving Show 58 | 59 | 60 | data Tool = Tup | TupLua | Ninja | Shake | Make | Fabricate | SCons | Aql | Fbuild | Gup 61 | deriving (Show,Eq,Enum,Bounded) 62 | 63 | 64 | writeBinary :: FilePath -> String -> IO () 65 | writeBinary file out = withBinaryFile file WriteMode $ \h -> hPutStr h out 66 | 67 | filterArgs :: IO ([String], String -> Bool, Tool -> Bool) 68 | filterArgs = do 69 | xs <- getArgs 70 | (args, xs) <- return $ partition ("-" `isPrefixOf`) xs 71 | let ts = [lcase $ show t| t :: Tool <- [minBound..maxBound]] 72 | let (tool,norm) = partition (`elem` ts) $ map lcase xs 73 | return (map (dropWhile (== '-')) args 74 | ,(\x -> null norm || lcase x `elem` norm) 75 | ,(\x -> null tool || lcase (show x) `elem` tool)) 76 | 77 | 78 | lcase :: String -> String 79 | lcase = map toLower 80 | 81 | 82 | test :: String -> (([Opt] -> IO ()) -> IO ()) -> IO () 83 | test name f = do 84 | (options, filtName, filtTool) <- filterArgs 85 | hSetBuffering stdout NoBuffering 86 | ts <- findTools name 87 | forM_ ts $ \t -> when (filtName name && filtTool t) $ do 88 | putStr $ "## " ++ name ++ " " ++ lcase (show t) ++ " ... " 89 | killDir "temp" 90 | createDir "temp" 91 | forM_ ["examples","util"] $ \dir -> do 92 | files <- getDirectoryContents dir 93 | forM_ files $ \file -> 94 | when ((lcase name ++ "-") `isPrefixOf` file) $ 95 | copy dir "temp" file 96 | writeFile ".log" "" 97 | withCurrentDirectory "temp" $ 98 | (if "continue" `elem` options then continue else id) $ 99 | f $ run name t 100 | killDir "temp" -- deliberately don't clean up on failure 101 | removeFile ".log" 102 | putStrLn $ "Success" 103 | 104 | 105 | continue :: IO () -> IO () 106 | continue act = E.catch act $ \(e :: SomeException) -> print e >> putStrLn "" 107 | 108 | 109 | 110 | killDir :: FilePath -> IO () 111 | killDir x = retryIO (fmap not $ doesDirectoryExist x) $ removeDirectoryRecursive x 112 | 113 | createDir :: FilePath -> IO () 114 | createDir x = retryIO (doesDirectoryExist x) $ createDirectoryIfMissing True x 115 | 116 | retryIO :: IO Bool -> IO () -> IO () 117 | retryIO test act = f 20 118 | where 119 | pause = threadDelay 100000 120 | f 0 = act >> pause 121 | f i = do b <- test 122 | unless b $ do 123 | E.catch act $ \(_ :: SomeException) -> return () 124 | pause 125 | f (i-1) 126 | 127 | 128 | findTools :: String -> IO [Tool] 129 | findTools name = do 130 | xs <- getDirectoryContents "examples" 131 | let ts = [v | x <- xs, takeExtension x /= ".broken", Just v <- [stripPrefix (name ++ "-") $ dropExtensions x]] 132 | return [x | x <- [minBound..maxBound], map toLower (show x) `elem` ts] 133 | 134 | 135 | run :: String -> Tool -> [Opt] -> IO () 136 | run name tool opts = do 137 | verbose <- fmap ("--verbose" `elem`) getArgs 138 | when verbose $ putStrLn $ "\n" ++ name ++ " " ++ show tool ++ " " ++ show opts 139 | xs <- mapM (opt tool) opts 140 | threadDelay 1000000 141 | let p = last $ 1 : [i | Parallel i <- opts] 142 | let target = unwords ["\"" ++ x ++ "\"" | Target x <- opts] 143 | let system_ cmd = do 144 | env <- getEnvironment 145 | (_,_,_,p) <- createProcess (shell cmd){env = Just $ env ++ [(a,b) | Env a b <- opts]} 146 | r <- waitForProcess p 147 | when (r /= ExitSuccess) $ error $ "System command failed: " ++ cmd 148 | case tool of 149 | Shake -> system_ $ "runhaskell -Werror -fwarn-unused-binds -fwarn-unused-imports " ++ name ++ "-shake.hs --quiet -j" ++ show p ++ " " ++ target 150 | Make -> system_ $ "make --file=" ++ name ++ "-make --quiet -j" ++ show p ++ " " ++ target 151 | Aql -> system_ $ "aql -f " ++ name ++ "-aql -s -j" ++ show p ++ " " ++ target 152 | SCons -> system_ $ "scons -f " ++ name ++ "-scons -s -j" ++ show p ++ " " ++ target 153 | Ninja -> system_ $ "ninja -f " ++ name ++ "-ninja.ninja -j" ++ show p ++ " " ++ target ++ " > " ++ devNull 154 | Tup -> do 155 | b <- doesDirectoryExist ".tup" 156 | unless b $ system_ $ "tup init > " ++ devNull 157 | writeFile ".tup/options" "[updater]\nwarnings = 0" 158 | copyFile (name ++ "-tup") "Tupfile" 159 | system_ $ "tup -j" ++ show p ++ " " ++ target ++ " > " ++ devNull 160 | removeFile "Tupfile" 161 | TupLua -> do 162 | b <- doesDirectoryExist ".tup" 163 | unless b $ system_ $ "tup init > " ++ devNull 164 | writeFile ".tup/options" "[updater]\nwarnings = 0" 165 | copyFile (name ++ "-tuplua.lua") "Tupfile.lua" 166 | system_ $ "tup -j" ++ show p ++ " " ++ target ++ " > " ++ devNull 167 | removeFile "Tupfile.lua" 168 | Fabricate -> do 169 | system_ $ "python " ++ name ++ "-fabricate.py" ++ (if verbose then "" else " --quiet") ++ " -j" ++ show p ++ " " ++ target 170 | when verbose $ putStrLn =<< readFile ".deps" 171 | Fbuild -> do 172 | copyFile (name ++ "-fbuild.py") "fbuildroot.py" 173 | system_ $ "fbuild -j" ++ show p ++ " " ++ target ++ " > " ++ devNull 174 | removeFile "fbuildroot.py" 175 | Gup -> do 176 | createSymbolicLink (name ++ "-gup") "gup" 177 | system_ $ "gup -u -j" ++ show p ++ " " ++ target ++ " &> " ++ devNull 178 | removeFile "gup" 179 | sequence_ xs 180 | 181 | windows :: Bool 182 | windows = os == "mingw32" 183 | 184 | devNull :: String 185 | devNull = if windows then "nul" else "/dev/null" 186 | 187 | 188 | opt :: Tool -> Opt -> IO (IO ()) 189 | opt _ (Missing file) = return $ do 190 | b <- doesFileExist file 191 | when b $ error $ "Fail should be missing: " ++ file 192 | opt _ (Contents file x) = return $ do 193 | src <- readFile file 194 | when (src /= x) $ error $ 195 | "File is wrong: " ++ file ++ "\n" ++ 196 | "Expected: " ++ show x ++ "\n" ++ 197 | "Got : " ++ show src 198 | opt _ (Log x) = return $ do 199 | src <- readFile "../.log" 200 | when (lines src /= words x) $ error $ 201 | "File is wrong: ../.log\n" ++ 202 | "Expected: " ++ show x ++ "\n" ++ 203 | "Got : " ++ show (unwords $ words src) 204 | opt _ NoChange = do 205 | dir <- getDirectoryContents "." 206 | times <- mapM modTime dir 207 | return $ do 208 | dir2 <- getDirectoryContents "." 209 | same "List of files changed" dir dir2 210 | times2 <- mapM modTime dir2 211 | same "File was modified" (zip dir times) (zip dir2 times2) 212 | opt _ (Change file) = do 213 | a <- modTime file 214 | return $ do 215 | b <- modTime file 216 | when (a == b) $ error "File did not change" 217 | opt _ _ = return $ return () 218 | 219 | 220 | same :: (Show a, Eq a) => String -> [a] -> [a] -> IO () 221 | same msg a b | null add && null del = return () 222 | | otherwise = error $ msg ++ "\nOld values: " ++ show del ++ "\nNew values: " ++ show add 223 | where 224 | add = b \\ a 225 | del = a \\ b 226 | 227 | 228 | modTime :: FilePath -> IO (Maybe String) 229 | modTime file | "." `isPrefixOf` file = return Nothing 230 | | otherwise = do 231 | b <- doesFileExist file 232 | if b then fmap (Just . show) $ getModificationTime file else return Nothing 233 | -------------------------------------------------------------------------------- /examples/basic-aql: -------------------------------------------------------------------------------- 1 | 2 | tools.Command( 'sh', './basic-run', File('./input'), '--', target = './output', cwd = '.' ) 3 | -------------------------------------------------------------------------------- /examples/basic-fabricate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fabricate import * 3 | 4 | def build(): 5 | run('sh','basic-run','input','--','output') 6 | 7 | main() 8 | -------------------------------------------------------------------------------- /examples/basic-fbuild.py: -------------------------------------------------------------------------------- 1 | import fbuild.db 2 | 3 | @fbuild.db.caches 4 | def run(ctx, src: fbuild.db.SRC, dst: fbuild.db.DST): 5 | ctx.execute(['sh', 'basic-run', src, '--', dst], 'basic-run') 6 | 7 | def build(ctx): 8 | run(ctx, 'input', 'output') 9 | -------------------------------------------------------------------------------- /examples/basic-gup/all.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | gup -u output 3 | -------------------------------------------------------------------------------- /examples/basic-gup/output.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u input 4 | sh basic-run input -- "$1" 5 | -------------------------------------------------------------------------------- /examples/basic-make: -------------------------------------------------------------------------------- 1 | 2 | output: input 3 | sh basic-run input -- output 4 | -------------------------------------------------------------------------------- /examples/basic-ninja.ninja: -------------------------------------------------------------------------------- 1 | 2 | rule run 3 | command = sh basic-run $in -- $out 4 | 5 | build output: run input 6 | -------------------------------------------------------------------------------- /examples/basic-scons: -------------------------------------------------------------------------------- 1 | import os 2 | env = Environment(ENV = os.environ) 3 | env.AppendENVPath('PATH','.') 4 | env.Command('output','input','sh basic-run $SOURCE -- $TARGET') 5 | -------------------------------------------------------------------------------- /examples/basic-shake.hs: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | 3 | main = shakeArgs shakeOptions $ do 4 | want ["output"] 5 | "output" %> \_ -> do 6 | need ["input"] 7 | cmd "sh basic-run input -- output" 8 | -------------------------------------------------------------------------------- /examples/basic-tup: -------------------------------------------------------------------------------- 1 | 2 | : input |> sh basic-run input -- output |> output 3 | -------------------------------------------------------------------------------- /examples/digest-aql: -------------------------------------------------------------------------------- 1 | 2 | out = tools.Command( 'sh', './digest-run', File('input'), '--', target = './output', cwd = '.' ) 3 | -------------------------------------------------------------------------------- /examples/digest-fbuild.py: -------------------------------------------------------------------------------- 1 | import fbuild.db 2 | 3 | @fbuild.db.caches 4 | def run(ctx, src: fbuild.db.SRC, dst: fbuild.db.DST): 5 | ctx.execute(['sh', 'digest-run', src, '--', dst], 'digest-run') 6 | 7 | def build(ctx): 8 | run(ctx, 'input', 'output') 9 | -------------------------------------------------------------------------------- /examples/digest-gup/all.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u output 4 | -------------------------------------------------------------------------------- /examples/digest-gup/input-digest.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup --always 4 | gup --contents input 5 | -------------------------------------------------------------------------------- /examples/digest-gup/output.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u input-digest 4 | sh digest-run input -- output 5 | -------------------------------------------------------------------------------- /examples/digest-scons: -------------------------------------------------------------------------------- 1 | import os 2 | env = Environment(ENV = os.environ) 3 | env.AppendENVPath('PATH','.') 4 | env.Command('output','input','sh digest-run $SOURCE -- $TARGET') 5 | -------------------------------------------------------------------------------- /examples/digest-shake.hs: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | 3 | main = shakeArgs shakeOptions{shakeChange=ChangeDigest} $ do 4 | want ["output"] 5 | "output" %> \_ -> do 6 | need ["input"] 7 | cmd "sh digest-run input -- output" 8 | -------------------------------------------------------------------------------- /examples/include-aql: -------------------------------------------------------------------------------- 1 | tools.c.Compile( 'include-main.c', target = './main' ) 2 | -------------------------------------------------------------------------------- /examples/include-fabricate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fabricate import * 3 | 4 | def build(): 5 | run('gcc','-c','include-main.c','-o','main.o') 6 | 7 | main() 8 | -------------------------------------------------------------------------------- /examples/include-fbuild.py: -------------------------------------------------------------------------------- 1 | from fbuild.builders.c import guess_static 2 | import fbuild.db, os 3 | 4 | @fbuild.db.caches 5 | def copy(ctx, src: fbuild.db.SRC, dst: fbuild.db.DST): 6 | src.copy(dst) 7 | 8 | def build(ctx): 9 | c = guess_static(ctx) 10 | c.compile('include-main.c', 'main.o') 11 | # Fbuild puts object files in ctx.buildroot, 12 | # which is good...except that Main.hs wants them in the current directory. 13 | # This symlinks it as a workaround 14 | try: os.symlink(ctx.buildroot / 'main.o', 'main.o') 15 | except: pass 16 | -------------------------------------------------------------------------------- /examples/include-gup/all.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | gup -u main.o -------------------------------------------------------------------------------- /examples/include-gup/main.o.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u include-main.c 4 | gcc -MM -MF main.d include-main.c 5 | read _ DEPS < main.d 6 | gup -u $DEPS 7 | gcc -c include-main.c -o "$1" 8 | -------------------------------------------------------------------------------- /examples/include-make: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.o: include-main.c 4 | gcc -MMD -MT main.o -MF main.d -c include-main.c -o main.o 5 | 6 | -include main.d 7 | -------------------------------------------------------------------------------- /examples/include-ninja.ninja: -------------------------------------------------------------------------------- 1 | 2 | rule gcc 3 | deps = gcc 4 | depfile = $out.d 5 | command = gcc -MMD -MT $out -MF $out.d -c $in -o $out 6 | 7 | build main.o: gcc include-main.c 8 | -------------------------------------------------------------------------------- /examples/include-scons: -------------------------------------------------------------------------------- 1 | Object('main.o','include-main.c') 2 | -------------------------------------------------------------------------------- /examples/include-shake.hs: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | import Development.Shake.Util 3 | 4 | main = shakeArgs shakeOptions $ do 5 | want ["main.o"] 6 | "main.o" %> \out -> do 7 | () <- cmd "gcc -MMD -MT main.o -MF main.d -c include-main.c -o main.o" 8 | needMakefileDependencies "main.d" 9 | -------------------------------------------------------------------------------- /examples/include-tup: -------------------------------------------------------------------------------- 1 | 2 | : |> gcc -c include-main.c -o main.o |> main.o 3 | 4 | -------------------------------------------------------------------------------- /examples/intermediate-fbuild.py: -------------------------------------------------------------------------------- 1 | from fbuild.path import Path 2 | import fbuild.db 3 | 4 | @fbuild.db.caches 5 | def run(ctx, src: fbuild.db.SRC, dst: fbuild.db.DST): 6 | i = Path('intermediate') 7 | ctx.execute(['sh', 'intermediate-run', src, '--', i], 'intermediate-run') 8 | ctx.execute(['sh', 'intermediate-run', i, '--', dst], 'intermediate-run') 9 | i.remove() 10 | 11 | def build(ctx): 12 | run(ctx, 'input', 'output') 13 | -------------------------------------------------------------------------------- /examples/intermediate-make: -------------------------------------------------------------------------------- 1 | output: intermediate 2 | sh intermediate-run intermediate -- output 3 | 4 | intermediate: input 5 | sh intermediate-run input -- intermediate 6 | 7 | .INTERMEDIATE: intermediate 8 | -------------------------------------------------------------------------------- /examples/intermediate-shake.hs: -------------------------------------------------------------------------------- 1 | 2 | import Development.Shake 3 | 4 | main = shakeArgs shakeOptions $ do 5 | want ["output"] 6 | "output" %> \out -> do 7 | need ["input"] 8 | orderOnly ["intermediate"] 9 | cmd "sh intermediate-run intermediate -- output" 10 | "intermediate" %> \out -> do 11 | need ["input"] 12 | removeFilesAfter "." ["intermediate"] 13 | cmd "sh intermediate-run input -- intermediate" 14 | -------------------------------------------------------------------------------- /examples/monad1-aql: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | from aql import readTextFile 4 | 5 | def CatFiles( builder, node, target ): 6 | src_file = node.getSources()[0] 7 | 8 | cmd = "cat '{src_file}' | xargs cat > '{target}'".format( src_file = src_file, target = target ) 9 | 10 | builder.execCmd( cmd, cwd = '.') 11 | 12 | files = readTextFile( src_file ) 13 | files = [ file.strip() for file in files.splitlines() ] 14 | 15 | node.addTargets( target, implicit_deps = files ) 16 | 17 | content = tools.ExecuteMethod( File('list'), method = CatFiles, args = ('output',) ) 18 | Alias( 'output', content ) 19 | -------------------------------------------------------------------------------- /examples/monad1-fabricate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fabricate import * 3 | 4 | def output(): 5 | run('sh','-c','cat list | xargs cat > output') 6 | 7 | main() 8 | -------------------------------------------------------------------------------- /examples/monad1-fbuild.py: -------------------------------------------------------------------------------- 1 | from fbuild.target import register 2 | import fbuild.db 3 | 4 | @fbuild.db.caches 5 | def run(ctx, srcs: fbuild.db.SRCS, dst: fbuild.db.DST): 6 | ctx.execute('cat {} > {}'.format(' '.join(srcs[1:]), dst), 'cat', shell=True) 7 | 8 | @register() 9 | def output(ctx): 10 | with open('list') as f: 11 | lines = f.read().splitlines() 12 | run(ctx, ['list']+lines, 'output') 13 | -------------------------------------------------------------------------------- /examples/monad1-gup/all.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u output -------------------------------------------------------------------------------- /examples/monad1-gup/output.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u list 4 | cat list | xargs gup -u 5 | cat list | xargs cat > "$1" -------------------------------------------------------------------------------- /examples/monad1-make: -------------------------------------------------------------------------------- 1 | 2 | output : list $(shell cat list) 3 | cat $(filter-out $<,$^) > $@ 4 | -------------------------------------------------------------------------------- /examples/monad1-ninja.ninja: -------------------------------------------------------------------------------- 1 | 2 | 3 | rule run 4 | depfile = $out.d 5 | command = sh -c "(cat $in | xargs cat > $out) && (cat $in | xargs echo $out: > $depfile)" 6 | 7 | build output: run list 8 | -------------------------------------------------------------------------------- /examples/monad1-scons: -------------------------------------------------------------------------------- 1 | import os 2 | import SCons.Util 3 | 4 | def ConsFileDeps(env, target, source, *args, **kw): 5 | if not SCons.SCons.Util.is_List(target): 6 | target = [target] 7 | if not SCons.SCons.Util.is_List(source): 8 | source = [source] 9 | env.Command(target[0],source[0],'cat $SOURCE | xargs cat > $TARGET') 10 | with open(source[0],"r") as f: 11 | for l in f.readlines(): 12 | env.Depends(target[0], l.rstrip('\n')) 13 | 14 | env = Environment(ENV = os.environ) 15 | env.AddMethod(ConsFileDeps, "ConsFileDeps") 16 | env.ConsFileDeps('output','list') 17 | -------------------------------------------------------------------------------- /examples/monad1-shake.hs: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | 3 | main = shakeArgs shakeOptions $ do 4 | "output" %> \out -> do 5 | src <- readFileLines "list" 6 | need src 7 | cmd Shell "cat" src ">" [out] 8 | -------------------------------------------------------------------------------- /examples/monad1-tup: -------------------------------------------------------------------------------- 1 | : |> cat list | xargs cat > %o |> output 2 | 3 | -------------------------------------------------------------------------------- /examples/monad2-aql: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | from aql import readTextFile 4 | 5 | def CatFiles( builder, node, target ): 6 | src_file = node.getSources()[0] 7 | 8 | cmd = "cat '{src_file}' | xargs cat > '{target}'".format( src_file = src_file, target = target ) 9 | 10 | builder.execCmd( cmd, cwd = '.') 11 | 12 | files = readTextFile( src_file ) 13 | files = [ file.strip() for file in files.splitlines() ] 14 | 15 | node.addTargets( target, implicit_deps = files ) 16 | 17 | gen_list = tools.Command( 'sh','./monad2-run', File('source'), '--', target = './list', cwd = '.' ) 18 | 19 | content = tools.Method( gen_list, method = CatFiles, args = ('output',) ) 20 | Alias( 'output', content ) 21 | -------------------------------------------------------------------------------- /examples/monad2-fabricate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fabricate import * 3 | 4 | def list(): 5 | run('sh','monad2-run','source','--','list') 6 | 7 | def output(): 8 | list() 9 | run('sh','-c','cat list | xargs cat > output') 10 | 11 | main() 12 | -------------------------------------------------------------------------------- /examples/monad2-fbuild.py: -------------------------------------------------------------------------------- 1 | from fbuild.target import register 2 | import fbuild.db 3 | 4 | @fbuild.db.caches 5 | def make_list(ctx, src: fbuild.db.SRC, dst: fbuild.db.DST): 6 | ctx.execute(['sh', 'monad2-run', src, '--', dst], 'monad2-run') 7 | 8 | @fbuild.db.caches 9 | def run(ctx, srcs: fbuild.db.SRCS, dst: fbuild.db.DST): 10 | ctx.execute('cat {} > {}'.format(' '.join(srcs[1:]), dst), 'cat', shell=True) 11 | 12 | @register() 13 | def output(ctx): 14 | make_list(ctx, 'source', 'list') 15 | with open('list') as f: 16 | lines = f.read().splitlines() 17 | run(ctx, ['list']+lines, 'output') 18 | -------------------------------------------------------------------------------- /examples/monad2-gup/all.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u output -------------------------------------------------------------------------------- /examples/monad2-gup/list.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u monad2-run source 4 | sh monad2-run source -- "$1" -------------------------------------------------------------------------------- /examples/monad2-gup/output.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u list 4 | cat list | xargs gup -u 5 | cat list | xargs cat > "$1" -------------------------------------------------------------------------------- /examples/monad2-make: -------------------------------------------------------------------------------- 1 | 2 | output: list 3 | cat list | xargs cat > output 4 | cat list | xargs echo output: > output.d 5 | 6 | list: source 7 | sh monad2-run source -- list 8 | 9 | -include output.d 10 | -------------------------------------------------------------------------------- /examples/monad2-ninja.ninja: -------------------------------------------------------------------------------- 1 | 2 | 3 | rule run 4 | depfile = $out.d 5 | command = sh -c "(cat $in | xargs cat > $out) && (cat $in | xargs echo $out: > $depfile)" 6 | 7 | build output: run list 8 | 9 | 10 | rule run2 11 | command = sh monad2-run $in -- $out 12 | 13 | build list: run2 source 14 | -------------------------------------------------------------------------------- /examples/monad2-scons: -------------------------------------------------------------------------------- 1 | import os 2 | import SCons.Builder 3 | 4 | def scansrcs(target, source, env): 5 | try: 6 | f=open(str(source[0]), 'r') 7 | except: 8 | print "scansrcs: Can't open source list file '%s'" % str(source[0]) 9 | raise 10 | for line in f: 11 | src = line.strip() 12 | try: 13 | env['SCANSRCS_FUNC'](env, src, env['SCANSRCS_TARGET']) 14 | except: 15 | print "SCANSRCS func raised exception:" 16 | raise 17 | f.close() 18 | 19 | def add_target(env, source, tgt): 20 | env.Depends(tgt, source) 21 | 22 | env = Environment(ENV = os.environ) 23 | env.AppendENVPath('PATH','.') 24 | env.Append(BUILDERS = {'ScanSrcs' : SCons.Builder.Builder(action=scansrcs)}) 25 | list = env.Command('list','source','sh monad2-run $SOURCE -- $TARGET') 26 | output = env.Command('output',list,'cat $SOURCE | xargs cat > $TARGET') 27 | dummy = env.ScanSrcs('#dummy-target', list, SCANSRCS_FUNC=add_target, SCANSRCS_TARGET=output) 28 | env.Depends(output, dummy) 29 | -------------------------------------------------------------------------------- /examples/monad2-shake.hs: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | 3 | main = shakeArgs shakeOptions $ do 4 | "output" %> \out -> do 5 | src <- readFileLines "list" 6 | need src 7 | cmd Shell "cat" src ">" [out] 8 | 9 | "list" %> \out -> do 10 | need ["source"] 11 | cmd Shell "sh monad2-run source -- list" 12 | -------------------------------------------------------------------------------- /examples/monad2-tup: -------------------------------------------------------------------------------- 1 | : source |> sh monad2-run %f -- %o |> list 2 | : list |> cat %f | xargs cat > %o |> output 3 | 4 | -------------------------------------------------------------------------------- /examples/monad3-aql: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | from aql import readTextFile 4 | 5 | 6 | def GenFile( builder, node ): 7 | src_file = node.getSources()[0] 8 | 9 | files = readTextFile( src_file ) 10 | files = [ line.strip() for line in files.splitlines() ] 11 | 12 | for file in files: 13 | if os.path.basename(file) == 'gen': 14 | builder.execCmd( ['sh','./monad3-gen', '--', 'gen'], cwd = '.') 15 | break 16 | 17 | node.setNoTargets() 18 | 19 | def CatFiles( builder, node, target ): 20 | src_file = node.getSources()[0] 21 | 22 | cmd = "cat '{src_file}' | xargs cat > '{target}'".format( src_file = src_file, target = target ) 23 | 24 | builder.execCmd( cmd, cwd = '.') 25 | 26 | files = readTextFile( src_file ) 27 | files = [ file.strip() for file in files.splitlines() ] 28 | 29 | node.addTargets( target, implicit_deps = files ) 30 | 31 | 32 | gen_list = tools.Command( 'sh','./monad3-run', File('source'), '--', target = './list', cwd = '.' ) 33 | 34 | gen = tools.Method( gen_list, method = GenFile ) 35 | 36 | content = tools.Method( gen_list, method = CatFiles, args = ('output',) ) 37 | Depends( content, gen ) 38 | 39 | Alias( 'output', content ) 40 | -------------------------------------------------------------------------------- /examples/monad3-fabricate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fabricate import * 3 | 4 | def input1(): 5 | pass # source file 6 | 7 | def gen(): 8 | run('sh','monad3-gen','--','gen') 9 | 10 | def lst(): 11 | run('sh','monad3-run','source','--','list') 12 | 13 | def output(): 14 | lst() 15 | array = [] 16 | with open('list', 'r') as f: 17 | for line in f: 18 | line = line.replace('\n', '') 19 | array.append(line) 20 | if line == "gen": gen() 21 | run('sh','-c','cat ' + ' '.join(array) + ' > output') 22 | 23 | main() 24 | -------------------------------------------------------------------------------- /examples/monad3-fbuild.py: -------------------------------------------------------------------------------- 1 | from fbuild.target import register 2 | import fbuild.db 3 | 4 | @fbuild.db.caches 5 | def make_list(ctx, src: fbuild.db.SRC, dst: fbuild.db.DST): 6 | ctx.execute(['sh', 'monad3-run', src, '--', dst], 'monad3-run') 7 | 8 | @fbuild.db.caches 9 | def gen(ctx, dst: fbuild.db.DST): 10 | ctx.execute(['sh', 'monad3-gen', '--', dst], 'monad3-gen') 11 | 12 | @fbuild.db.caches 13 | def run(ctx, srcs: fbuild.db.SRCS, dst: fbuild.db.DST): 14 | ctx.execute('cat {} > {}'.format(' '.join(srcs[1:]), dst), 'cat', shell=True) 15 | 16 | genmap = {'gen': gen} 17 | 18 | @register() 19 | def output(ctx): 20 | make_list(ctx, 'source', 'list') 21 | with open('list') as f: 22 | lines = f.read().splitlines() 23 | for dep in lines: 24 | genmap.get(dep, lambda *_: None)(ctx, dep) 25 | run(ctx, ['list']+lines, 'output') 26 | -------------------------------------------------------------------------------- /examples/monad3-gup/all.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u output -------------------------------------------------------------------------------- /examples/monad3-gup/gen.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u monad3-gen 4 | sh monad3-gen -- "$1" -------------------------------------------------------------------------------- /examples/monad3-gup/list.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u monad3-run source 4 | sh monad3-run source -- "$1" 5 | -------------------------------------------------------------------------------- /examples/monad3-gup/output.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u list 4 | cat list | xargs gup -u 5 | cat list | xargs cat > "$1" -------------------------------------------------------------------------------- /examples/monad3-make: -------------------------------------------------------------------------------- 1 | output: list 2 | cat `cat list` > output 3 | 4 | list.d: list 5 | echo output: `cat list` > list.d 6 | 7 | list: source 8 | sh monad3-run source -- list 9 | 10 | gen: 11 | sh monad3-gen -- gen 12 | 13 | -include list.d 14 | -------------------------------------------------------------------------------- /examples/monad3-scons: -------------------------------------------------------------------------------- 1 | import os 2 | import SCons.Builder 3 | 4 | def scansrcs(target, source, env): 5 | try: 6 | f=open(str(source[0]), 'r') 7 | except: 8 | print "scansrcs: Can't open source list file '%s'" % str(source[0]) 9 | raise 10 | for line in f: 11 | src = line.strip() 12 | try: 13 | if src != "gen": 14 | env['SCANSRCS_FUNC'](env, src, env['SCANSRCS_TARGET']) 15 | else: 16 | env['GEN_FUNC'](env, source, env['SCANSRCS_TARGET']) 17 | except: 18 | raise 19 | f.close() 20 | 21 | def add_target(env, source, tgt): 22 | env.Depends(tgt, source) 23 | 24 | def run_gencmd(env, source, tgt): 25 | gen = env.Command('gen', source, 'sh monad3-gen -- $TARGET') 26 | env.Depends(tgt, gen) 27 | 28 | env = Environment(ENV = os.environ) 29 | env.AppendENVPath('PATH','.') 30 | env.Append(BUILDERS = {'ScanSrcs' : SCons.Builder.Builder(action=scansrcs)}) 31 | list = env.Command('list','source','sh monad3-run $SOURCE -- $TARGET') 32 | output = env.Command('output',list,'cat $SOURCE | xargs cat > $TARGET') 33 | dummy = env.ScanSrcs('#dummy-target', list, 34 | SCANSRCS_FUNC=add_target, SCANSRCS_TARGET=output, 35 | GEN_FUNC=run_gencmd) 36 | env.Depends(output, dummy) 37 | -------------------------------------------------------------------------------- /examples/monad3-shake.hs: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | 3 | main = shakeArgs shakeOptions $ do 4 | "output" %> \out -> do 5 | src <- readFileLines "list" 6 | need src 7 | cmd Shell "cat" src ">" [out] 8 | 9 | "list" %> \out -> do 10 | need ["source"] 11 | cmd Shell "sh monad3-run source -- list" 12 | 13 | "gen" %> \out -> do 14 | cmd Shell "sh monad3-gen -- gen" 15 | -------------------------------------------------------------------------------- /examples/multiple-aql: -------------------------------------------------------------------------------- 1 | 2 | 3 | sources = tools.Command( 'sh', './multiple-gen', File('input'), '--', target = ['./source1', './source2'], cwd = '.' ) 4 | 5 | out1 = tools.Command( 'sh', './multiple-run', sources[0], '--', target = './output1', cwd = '.' ) 6 | out2 = tools.Command( 'sh', './multiple-run', sources[1], '--', target = './output2', cwd = '.' ) 7 | 8 | Alias( 'output1', out1 ) 9 | Alias( 'output2', out2 ) 10 | -------------------------------------------------------------------------------- /examples/multiple-fabricate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fabricate import * 3 | 4 | def sources(): 5 | run('sh','multiple-gen','input','--','source1','source2') 6 | 7 | def output1(): 8 | sources() 9 | run('sh','multiple-run','source1','--','output1') 10 | 11 | def output2(): 12 | sources() 13 | run('sh','multiple-run','source2','--','output2') 14 | 15 | main() 16 | -------------------------------------------------------------------------------- /examples/multiple-fbuild.py: -------------------------------------------------------------------------------- 1 | from fbuild.target import register 2 | import fbuild.db 3 | 4 | @fbuild.db.caches 5 | def gen(ctx, src: fbuild.db.SRC, dsts: fbuild.db.DSTS): 6 | ctx.execute(['sh', 'multiple-gen', src, '--'] + dsts, 'multiple-gen') 7 | 8 | @fbuild.db.caches 9 | def run(ctx, src: fbuild.db.SRC, dst: fbuild.db.DST): 10 | ctx.execute(['sh', 'multiple-run', src, '--', dst], 'multiple-run') 11 | 12 | @register() 13 | def output1(ctx): 14 | gen(ctx, 'input', ['source1', 'source2']) 15 | run(ctx, 'source1', 'output1') 16 | 17 | @register() 18 | def output2(ctx): 19 | gen(ctx, 'input', ['source1', 'source2']) 20 | run(ctx, 'source2', 'output2') 21 | -------------------------------------------------------------------------------- /examples/multiple-gup/all.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u output1 output2 -------------------------------------------------------------------------------- /examples/multiple-gup/output1.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u source1 4 | sh multiple-run source1 -- "$1" 5 | -------------------------------------------------------------------------------- /examples/multiple-gup/output2.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u source2 4 | sh multiple-run source2 -- "$1" -------------------------------------------------------------------------------- /examples/multiple-gup/source.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u input 4 | sh multiple-gen input -- source1 source2 5 | touch "$1" 6 | -------------------------------------------------------------------------------- /examples/multiple-gup/source1.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u source 4 | touch "$2" 5 | gup --contents "$2" 6 | -------------------------------------------------------------------------------- /examples/multiple-gup/source2.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u source 4 | touch "$2" 5 | gup --contents "$2" 6 | -------------------------------------------------------------------------------- /examples/multiple-ninja.ninja: -------------------------------------------------------------------------------- 1 | 2 | rule run 3 | command = sh multiple-run $in -- $out 4 | 5 | build output1: run source1 6 | build output2: run source2 7 | 8 | rule gen 9 | command = sh multiple-gen $in -- $out 10 | restat = 1 11 | 12 | build source1 source2: gen input 13 | -------------------------------------------------------------------------------- /examples/multiple-scons: -------------------------------------------------------------------------------- 1 | import os 2 | env = Environment(ENV = os.environ) 3 | env.AppendENVPath('PATH','.') 4 | env.Command(['source1','source2'], 'input', 'sh multiple-gen $SOURCE -- $TARGETS') 5 | env.Command('output1', 'source1', 'sh multiple-run $SOURCE -- $TARGET') 6 | env.Command('output2', 'source2', 'sh multiple-run $SOURCE -- $TARGET') 7 | -------------------------------------------------------------------------------- /examples/multiple-shake.hs: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | 3 | main = shakeArgs shakeOptions $ do 4 | let run inp out = out %> \_ -> do 5 | need [inp] 6 | cmd "sh multiple-run" [inp] "--" [out] 7 | run "source1" "output1" 8 | run "source2" "output2" 9 | ["source1","source2"] |%> \out -> do 10 | need ["input"] 11 | cmd "sh multiple-gen input -- source1 source2" 12 | -------------------------------------------------------------------------------- /examples/multiple-tup: -------------------------------------------------------------------------------- 1 | : input |> ^o^ sh multiple-gen input -- %o |> source1 source2 2 | : source1 |> sh multiple-run source1 -- %o |> output1 3 | : source2 |> sh multiple-run source2 -- %o |> output2 4 | -------------------------------------------------------------------------------- /examples/nofileout-aql: -------------------------------------------------------------------------------- 1 | 2 | tools.Command( 'sh', './nofileout-run', File('input'), '--', cwd = '.' ) 3 | -------------------------------------------------------------------------------- /examples/nofileout-fbuild.py: -------------------------------------------------------------------------------- 1 | import fbuild.db 2 | 3 | @fbuild.db.caches 4 | def run(ctx, src: fbuild.db.SRC): 5 | ctx.execute(['sh', 'nofileout-run', src], 'nofileout-run') 6 | 7 | def build(ctx): 8 | run(ctx, 'input') 9 | -------------------------------------------------------------------------------- /examples/nofileout-gup/all.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u input-digest 4 | sh nofileout-run input -- 5 | touch "$1" 6 | -------------------------------------------------------------------------------- /examples/nofileout-gup/input-digest.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup --always 4 | gup --contents input 5 | -------------------------------------------------------------------------------- /examples/nofileout-tup: -------------------------------------------------------------------------------- 1 | : input |> sh nofileout-run %f |> 2 | -------------------------------------------------------------------------------- /examples/noleftover-shake.hs.broken: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | import Development.Shake.FilePath 3 | 4 | main = shakeArgs shakeOptions $ do 5 | 6 | action $ do 7 | is <- getDirectoryFiles "." ["*.in"] 8 | let os = [i -<.> "out" | i <- is] 9 | need os 10 | 11 | "*.out" *> \o -> do 12 | let i = o -<.> "in" 13 | cmd "sh noleftover-run" [i] "--" [o] 14 | -------------------------------------------------------------------------------- /examples/noleftover-tup: -------------------------------------------------------------------------------- 1 | : foreach *.in |> sh noleftover-run %f -- %o |> %B.out 2 | -------------------------------------------------------------------------------- /examples/parallel-aql: -------------------------------------------------------------------------------- 1 | 2 | tools.Command( 'sh', './parallel-run', File('./input1'), '--', target = './output1', cwd = '.' ) 3 | tools.Command( 'sh', './parallel-run', File('./input2'), '--', target = './output2', cwd = '.' ) 4 | -------------------------------------------------------------------------------- /examples/parallel-fbuild.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | import fbuild.db 3 | 4 | @fbuild.db.caches 5 | def run(ctx, src: fbuild.db.SRC) -> fbuild.db.DST: 6 | dst = src.replace('in', 'out') 7 | ctx.execute(['sh', 'parallel-run', src, '--', dst], 'parallel-run') 8 | return dst 9 | 10 | def build(ctx): 11 | ctx.scheduler.map(partial(run, ctx), ['input1', 'input2']) 12 | -------------------------------------------------------------------------------- /examples/parallel-gup/all.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u output1 output2 -------------------------------------------------------------------------------- /examples/parallel-gup/output1.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u input1 4 | sh parallel-run input1 -- "$1" 5 | -------------------------------------------------------------------------------- /examples/parallel-gup/output2.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u input2 4 | sh parallel-run input2 -- "$1" 5 | -------------------------------------------------------------------------------- /examples/parallel-make: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: all 3 | all: output1 output2 4 | 5 | output1: input1 6 | sh parallel-run input1 -- output1 7 | 8 | output2: input2 9 | sh parallel-run input2 -- output2 10 | -------------------------------------------------------------------------------- /examples/parallel-ninja.ninja: -------------------------------------------------------------------------------- 1 | 2 | rule run 3 | command = sh parallel-run $in -- $out 4 | 5 | build output1: run input1 6 | build output2: run input2 7 | -------------------------------------------------------------------------------- /examples/parallel-scons: -------------------------------------------------------------------------------- 1 | import os 2 | env = Environment(ENV = os.environ) 3 | env.AppendENVPath('PATH','.') 4 | env.Command('output1','input1','sh parallel-run $SOURCE -- $TARGET') 5 | env.Command('output2','input2','sh parallel-run $SOURCE -- $TARGET') 6 | -------------------------------------------------------------------------------- /examples/parallel-shake.hs: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | 3 | main = shakeArgs shakeOptions $ do 4 | want ["output1","output2"] 5 | let run inp out = out %> \_ -> do 6 | need [inp] 7 | cmd "sh parallel-run" [inp] "--" [out] 8 | run "input1" "output1" 9 | run "input2" "output2" 10 | -------------------------------------------------------------------------------- /examples/parallel-tup: -------------------------------------------------------------------------------- 1 | : input1 |> sh parallel-run %f -- %o |> output1 2 | : input2 |> sh parallel-run %f -- %o |> output2 3 | 4 | -------------------------------------------------------------------------------- /examples/pool-ninja.ninja: -------------------------------------------------------------------------------- 1 | 2 | pool limit 3 | depth = 2 4 | 5 | rule run 6 | command = sh pool-run $in -- $out 7 | pool = limit 8 | 9 | build output1: run input1 10 | build output2: run input2 11 | build output3: run input3 12 | -------------------------------------------------------------------------------- /examples/pool-shake.hs: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | 3 | main = shakeArgs shakeOptions $ do 4 | want ["output1","output2","output3"] 5 | pool <- newResource "pool" 2 6 | let run inp out = out %> \_ -> do 7 | need [inp] 8 | withResource pool 1 $ cmd "sh pool-run" [inp] "--" [out] 9 | run "input1" "output1" 10 | run "input2" "output2" 11 | run "input3" "output3" 12 | -------------------------------------------------------------------------------- /examples/secondary-make: -------------------------------------------------------------------------------- 1 | output: secondary 2 | sh secondary-run secondary -- output 3 | 4 | secondary: input 5 | sh secondary-run input -- secondary 6 | 7 | .SECONDARY: secondary 8 | -------------------------------------------------------------------------------- /examples/secondary-shake.hs: -------------------------------------------------------------------------------- 1 | 2 | import Development.Shake 3 | 4 | main = shakeArgs shakeOptions $ do 5 | want ["output"] 6 | "output" %> \out -> do 7 | need ["input"] 8 | orderOnly ["secondary"] 9 | cmd "sh secondary-run secondary -- output" 10 | "secondary" %> \out -> do 11 | need ["input"] 12 | cmd "sh secondary-run input -- secondary" 13 | -------------------------------------------------------------------------------- /examples/spaces-aql: -------------------------------------------------------------------------------- 1 | 2 | target = GetBuildTargets()[0] 3 | 4 | node = tools.Command( 'cp', File('./input file'), target = './' + target, cwd = '.' ) 5 | Alias( target, node ) 6 | -------------------------------------------------------------------------------- /examples/spaces-fabricate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fabricate import * 3 | import sys 4 | 5 | def output_file(): 6 | run('cp','input file','output file') 7 | 8 | 9 | # Hacky, perhaps a bug that can be fixed by fabricate? 10 | for i, x in enumerate(sys.argv): 11 | if x == 'output file': 12 | sys.argv[i] = 'output_file' 13 | 14 | main() 15 | -------------------------------------------------------------------------------- /examples/spaces-fbuild.py: -------------------------------------------------------------------------------- 1 | from fbuild.target import register 2 | import fbuild.db 3 | 4 | @fbuild.db.caches 5 | def run(ctx, src: fbuild.db.SRC, dst: fbuild.db.DST): 6 | ctx.execute(['cp', src, dst], 'cp') 7 | 8 | @register(name='output file') 9 | def build(ctx): 10 | run(ctx, 'input file', 'output file') 11 | -------------------------------------------------------------------------------- /examples/spaces-gup/all.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u 'output file' -------------------------------------------------------------------------------- /examples/spaces-gup/output file.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u 'input file' 4 | cp 'input file' "$1" -------------------------------------------------------------------------------- /examples/spaces-make: -------------------------------------------------------------------------------- 1 | 2 | output\ file: input\ file 3 | cp input\ file output\ file 4 | -------------------------------------------------------------------------------- /examples/spaces-ninja.ninja: -------------------------------------------------------------------------------- 1 | 2 | rule cp 3 | command = cp $in $out 4 | 5 | build output$ file: cp input$ file 6 | -------------------------------------------------------------------------------- /examples/spaces-scons: -------------------------------------------------------------------------------- 1 | import os 2 | env = Environment(ENV = os.environ) 3 | env.Command('output file','input file','cp $SOURCE $TARGET') 4 | -------------------------------------------------------------------------------- /examples/spaces-shake.hs: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | 3 | main = shakeArgs shakeOptions $ do 4 | "output file" %> \out -> do 5 | copyFile' "input file" out 6 | -------------------------------------------------------------------------------- /examples/spaces-tup.broken: -------------------------------------------------------------------------------- 1 | : |> cp input\ file output\ file |> output\ file 2 | 3 | -------------------------------------------------------------------------------- /examples/spaces-tuplua.lua: -------------------------------------------------------------------------------- 1 | tup.rule({"input file"}, "cp '%f' '%o'", {"output file"}) 2 | -------------------------------------------------------------------------------- /examples/system1-aql: -------------------------------------------------------------------------------- 1 | 2 | source = tools.Command( 'sh', './system1-gen', '--', target = './source', cwd = '.' ) 3 | AlwaysBuild( source ) 4 | 5 | out = tools.Command( 'sh', './system1-run', source, '--', target = './output', cwd = '.' ) 6 | Alias( 'output', out ) 7 | -------------------------------------------------------------------------------- /examples/system1-fbuild.py: -------------------------------------------------------------------------------- 1 | from fbuild.target import register 2 | import fbuild.db 3 | 4 | def gen(ctx, dst) -> fbuild.db.DST: 5 | ctx.execute(['sh', 'system1-gen', '--', dst], 'system1-gen') 6 | return dst 7 | 8 | @fbuild.db.caches 9 | def run(ctx, src: fbuild.db.SRC, dst: fbuild.db.DST): 10 | ctx.execute(['sh', 'system1-run', src, '--', dst], 'system1-run') 11 | 12 | @register() 13 | def output(ctx): 14 | run(ctx, gen(ctx, 'source'), 'output') 15 | -------------------------------------------------------------------------------- /examples/system1-gup/all.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | gup -u output 3 | -------------------------------------------------------------------------------- /examples/system1-gup/output.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | gup -u source 3 | sh system1-run source -- "$1" 4 | -------------------------------------------------------------------------------- /examples/system1-gup/source.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | gup --always 3 | sh system1-gen -- "$1" 4 | gup --contents "$1" 5 | -------------------------------------------------------------------------------- /examples/system1-scons: -------------------------------------------------------------------------------- 1 | import os 2 | env = Environment(ENV = os.environ) 3 | env.AppendENVPath('PATH', '.') 4 | s = env.Command('source', [], 'sh system1-gen -- $TARGET') 5 | env.Precious(s) 6 | env.AlwaysBuild(s) 7 | env.Command('output', s, 'sh system1-run $SOURCE -- $TARGET') 8 | -------------------------------------------------------------------------------- /examples/system1-shake.hs: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | 3 | main = shakeArgs shakeOptions $ do 4 | "output" %> \out -> do 5 | need ["source"] 6 | cmd "sh system1-run source -- " [out] 7 | "source" %> \out -> do 8 | alwaysRerun 9 | cmd "sh system1-gen -- " [out] 10 | -------------------------------------------------------------------------------- /examples/system2-aql: -------------------------------------------------------------------------------- 1 | 2 | out = tools.Command( 'sh', './system2-run', '--', target = './output', cwd = '.' ) 3 | 4 | data = Entity( options.env.get().get( 'SYSTEM2_DATA', '' ) ) 5 | 6 | Depends( out, data ) 7 | 8 | Alias( 'output', out ) 9 | -------------------------------------------------------------------------------- /examples/system2-gup/all.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | gup -u output 3 | -------------------------------------------------------------------------------- /examples/system2-gup/data.gup: -------------------------------------------------------------------------------- 1 | #!bash -e 2 | 3 | gup --always 4 | gup --contents <<<"$SYSTEM2_DATA" 5 | -------------------------------------------------------------------------------- /examples/system2-gup/output.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | gup -u data 3 | sh system2-run -- "$1" 4 | -------------------------------------------------------------------------------- /examples/system2-scons: -------------------------------------------------------------------------------- 1 | import os 2 | import SCons.Node.Python 3 | env = Environment(ENV = os.environ) 4 | env.AppendENVPath('PATH', '.') 5 | r = env.Command('output', [], 'sh system2-run -- $TARGET') 6 | n = SCons.Node.Python.Value(os.environ.get('SYSTEM2_DATA','')) 7 | env.Depends(r, n) 8 | -------------------------------------------------------------------------------- /examples/system2-shake.hs: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | 3 | main = shakeArgs shakeOptions $ do 4 | want ["output"] 5 | "output" %> \out -> do 6 | getEnv "SYSTEM2_DATA" -- just for the dependency 7 | cmd "sh system2-run" "--" [out] 8 | -------------------------------------------------------------------------------- /examples/system2-tup: -------------------------------------------------------------------------------- 1 | export SYSTEM2_DATA 2 | : |> sh system2-run -- %o |> output 3 | -------------------------------------------------------------------------------- /examples/unchanged-aql: -------------------------------------------------------------------------------- 1 | 2 | src = tools.Command( 'sh', './unchanged-gen', File('input'), '--', target = './source', cwd = '.' ) 3 | out = tools.Command( 'sh', './unchanged-run', src, '--', target = './output', cwd = '.' ) 4 | 5 | Alias( 'output', out ) 6 | -------------------------------------------------------------------------------- /examples/unchanged-fabricate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fabricate import * 3 | 4 | def source(): 5 | run('sh','unchanged-gen','input','--','source') 6 | 7 | def output(): 8 | source() 9 | run('sh','unchanged-run','source','--','output') 10 | 11 | main() 12 | -------------------------------------------------------------------------------- /examples/unchanged-fbuild.py: -------------------------------------------------------------------------------- 1 | from fbuild.target import register 2 | import fbuild.db 3 | 4 | @fbuild.db.caches 5 | def gen(ctx, src: fbuild.db.SRC, dst: fbuild.db.DST): 6 | ctx.execute(['sh', 'unchanged-gen', src, '--', dst], 'unchanged-gen') 7 | 8 | @fbuild.db.caches 9 | def run(ctx, src: fbuild.db.SRC, dst: fbuild.db.DST): 10 | ctx.execute(['sh', 'unchanged-run', src, '--', dst], 'unchanged-run') 11 | 12 | @register() 13 | def output(ctx): 14 | gen(ctx, 'input', 'source') 15 | run(ctx, 'source', 'output') 16 | -------------------------------------------------------------------------------- /examples/unchanged-gup/all.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u output 4 | -------------------------------------------------------------------------------- /examples/unchanged-gup/output.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u unchanged-run source 4 | sh unchanged-run source -- "$1" 5 | -------------------------------------------------------------------------------- /examples/unchanged-gup/source.gup: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | gup -u unchanged-gen input 4 | sh unchanged-gen input -- "$1" 5 | gup --contents "$1" -------------------------------------------------------------------------------- /examples/unchanged-ninja.ninja: -------------------------------------------------------------------------------- 1 | 2 | rule run 3 | command = sh unchanged-run $in -- $out 4 | 5 | build output: run source 6 | 7 | rule gen 8 | command = sh unchanged-gen $in -- $out 9 | restat = 1 10 | 11 | build source: gen input 12 | -------------------------------------------------------------------------------- /examples/unchanged-scons: -------------------------------------------------------------------------------- 1 | import os 2 | env = Environment(ENV = os.environ) 3 | env.AppendENVPath('PATH','.') 4 | env.Command('output', 'source', 'sh unchanged-run $SOURCE -- $TARGET') 5 | env.Command('source', 'input', 'sh unchanged-gen $SOURCE -- $TARGET') 6 | -------------------------------------------------------------------------------- /examples/unchanged-shake.hs: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | 3 | main = shakeArgs shakeOptions $ do 4 | "output" %> \out -> do 5 | need ["source"] 6 | cmd "sh unchanged-run source -- output" 7 | "source" %> \out -> do 8 | need ["input"] 9 | cmd "sh unchanged-gen input -- source" 10 | -------------------------------------------------------------------------------- /examples/unchanged-tup: -------------------------------------------------------------------------------- 1 | : |> ^o^ sh unchanged-gen input -- %o |> source 2 | : source |> sh unchanged-run source -- %o |> output 3 | 4 | -------------------------------------------------------------------------------- /examples/wildcard-aql: -------------------------------------------------------------------------------- 1 | 2 | import os.path 3 | 4 | target = GetBuildTargets()[0] 5 | source = os.path.splitext(target)[0] + '.in' 6 | 7 | out = tools.Command( 'cp', File(source), target = './' + target, cwd = '.' ) 8 | 9 | Alias( target, out ) 10 | 11 | -------------------------------------------------------------------------------- /examples/wildcard-fabricate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from fabricate import * 3 | import sys, os, glob 4 | 5 | def get_cp(inf, out): 6 | @staticmethod 7 | def _f(): run('cp', inf, out) 8 | return _f 9 | 10 | for f in glob.glob('*.in'): 11 | base = os.path.splitext(f)[0] 12 | globals()[base] = type(base, (object,), {'out': get_cp(f, base+'.out')}) 13 | 14 | main() 15 | -------------------------------------------------------------------------------- /examples/wildcard-fbuild.py: -------------------------------------------------------------------------------- 1 | from fbuild.target import register 2 | from fbuild.path import Path 3 | import fbuild.db 4 | 5 | @fbuild.db.caches 6 | def cp(ctx, src: fbuild.db.SRC, dst: fbuild.db.DST): 7 | ctx.execute(['cp', src, dst], 'cp') 8 | 9 | for path in Path.glob('*.in'): 10 | out = path.replaceext('.out') 11 | @register(name=str(out)) 12 | def func(ctx): 13 | cp(ctx, path, out) 14 | -------------------------------------------------------------------------------- /examples/wildcard-gup/Gupfile: -------------------------------------------------------------------------------- 1 | copier: 2 | *.out -------------------------------------------------------------------------------- /examples/wildcard-gup/copier: -------------------------------------------------------------------------------- 1 | #!bash -eu 2 | 3 | SRC=${2%.out}.in 4 | gup -u "$SRC" 5 | cp "$SRC" "$1" -------------------------------------------------------------------------------- /examples/wildcard-make: -------------------------------------------------------------------------------- 1 | 2 | %.out: %.in 3 | cp $< $@ 4 | -------------------------------------------------------------------------------- /examples/wildcard-scons: -------------------------------------------------------------------------------- 1 | import os 2 | import SCons.Builder 3 | 4 | cpbuild_ = SCons.Builder.Builder(action="cp $SOURCE $TARGET", 5 | single_src=True, src_suffix=".in", suffix=".out") 6 | 7 | env = Environment(ENV=os.environ, BUILDERS={'CpBuild' : cpbuild_}) 8 | env.CpBuild([os.path.splitext(t)[0] for t in BUILD_TARGETS]) 9 | 10 | -------------------------------------------------------------------------------- /examples/wildcard-shake.hs: -------------------------------------------------------------------------------- 1 | import Development.Shake 2 | import Development.Shake.FilePath 3 | 4 | main = shakeArgs shakeOptions $ do 5 | "*.out" %> \out -> do 6 | copyFile' (out -<.> "in") out 7 | -------------------------------------------------------------------------------- /examples/wildcard-tup: -------------------------------------------------------------------------------- 1 | : foreach *.in |> cp %g.in %g.out |> %g.out 2 | -------------------------------------------------------------------------------- /travis.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | sudo apt-get update 3 | 4 | echo Install Shake 5 | git clone https://github.com/ndmitchell/shake 6 | (cd shake && cabal install) 7 | 8 | echo Install Ninja 9 | git clone https://github.com/martine/ninja 10 | (cd ninja && ./bootstrap.py) 11 | export PATH=$PATH:`pwd`/ninja 12 | 13 | echo Install tup 14 | sudo apt-get install libfuse-dev 15 | modprobe fuse 16 | git clone https://github.com/gittup/tup 17 | (cd tup && ./bootstrap.sh) 18 | export PATH=$PATH:`pwd`/tup 19 | 20 | echo Install fabricate 21 | wget https://raw.githubusercontent.com/SimonAlfie/fabricate/master/fabricate.py 22 | export PYTHONPATH=$PWD:$PYTHONPATH 23 | 24 | echo Install Aqualid 25 | wget https://github.com/aqualid/aqualid/releases/download/v0.53-beta/Aqualid-0.53.tar.bz2 26 | tar -xjf Aqualid-0.53.tar.bz2 27 | export AQLPATH=$PWD/Aqualid 28 | export PATH=$PATH:$AQLPATH 29 | export PYTHONPATH=$PYTHONPATH:$AQLPATH 30 | (cd Aqualid-0.53 && python setup.py install --install-lib $AQLPATH --install-headers $AQLPATH --install-scripts $AQLPATH --install-data $AQLPATH) 31 | 32 | echo Install fbuild 33 | sudo apt-get install python3 python3-setuptools 34 | git clone https://github.com/felix-lang/fbuild.git 35 | (cd fbuild && sudo python3 setup.py install) 36 | 37 | echo Install gup 38 | # see http://installion.co.uk/ubuntu/vivid/universe/0/0install-core/install/index.html 39 | sudo cat /etc/apt/sources.list 40 | sudo apt-get install 0install-core --no-install-recommends 41 | 0install add gup http://gfxmonk.net/dist/0install/gup.xml 42 | 43 | export PATH=/home/travis/.ghc-multi/7.6.3/bin:$PATH 44 | runhaskell Main shake make ninja fabricate aql fbuild tup 45 | # gup 46 | # Temporarily disabled gup due to: https://travis-ci.org/ndmitchell/build-shootout/builds/586561022#L1172 47 | -------------------------------------------------------------------------------- /util/basic-run: -------------------------------------------------------------------------------- 1 | cp $1 $3 2 | -------------------------------------------------------------------------------- /util/digest-run: -------------------------------------------------------------------------------- 1 | cp $1 $3 2 | -------------------------------------------------------------------------------- /util/include-1.h: -------------------------------------------------------------------------------- 1 | 2 | #include "include-2.h" 3 | -------------------------------------------------------------------------------- /util/include-2.h: -------------------------------------------------------------------------------- 1 | 2 | /* Empty */ 3 | -------------------------------------------------------------------------------- /util/include-main.c: -------------------------------------------------------------------------------- 1 | 2 | #include "include-1.h" 3 | 4 | int main; 5 | -------------------------------------------------------------------------------- /util/intermediate-run: -------------------------------------------------------------------------------- 1 | echo run >> ../.log 2 | cp $1 $3 3 | echo -n " *" >> $3 4 | -------------------------------------------------------------------------------- /util/monad2-run: -------------------------------------------------------------------------------- 1 | echo run >> ../.log 2 | sed 's/out/in/g' $1 > $3 3 | -------------------------------------------------------------------------------- /util/monad3-gen: -------------------------------------------------------------------------------- 1 | echo Generated > $2 2 | -------------------------------------------------------------------------------- /util/monad3-run: -------------------------------------------------------------------------------- 1 | echo run >> ../.log 2 | sed 's/out/in/g' $1 > $3 3 | -------------------------------------------------------------------------------- /util/multiple-gen: -------------------------------------------------------------------------------- 1 | if [ -f $3 ]; then 2 | OLD=`cat $3` 3 | else 4 | OLD=_none_ 5 | fi 6 | NEW=`sed 's/a/A/g' $1` 7 | if [ "$OLD" != "$NEW" ] ; then 8 | sed 's/a/A/g' $1 > $3 9 | fi 10 | 11 | if [ -f $4 ]; then 12 | OLD=`cat $4` 13 | else 14 | OLD=_none_ 15 | fi 16 | NEW=`sed 's/b/B/g' $1` 17 | if [ "$OLD" != "$NEW" ] ; then 18 | sed 's/b/B/g' $1 > $4 19 | fi 20 | -------------------------------------------------------------------------------- /util/multiple-run: -------------------------------------------------------------------------------- 1 | echo run >> ../.log 2 | sed 's/c/C/g' $1 > $3 3 | -------------------------------------------------------------------------------- /util/nofileout-run: -------------------------------------------------------------------------------- 1 | cat $1 >> ../.log 2 | -------------------------------------------------------------------------------- /util/noleftover-run: -------------------------------------------------------------------------------- 1 | cp $1 $3 2 | -------------------------------------------------------------------------------- /util/parallel-run: -------------------------------------------------------------------------------- 1 | echo start >> ../.log 2 | sleep 1s 3 | cp $1 $3 4 | echo end >> ../.log 5 | -------------------------------------------------------------------------------- /util/pool-run: -------------------------------------------------------------------------------- 1 | echo start >> ../.log 2 | if [ "$3" = "output1" ] ; then sleep 1s ; fi 3 | if [ "$3" = "output2" ] ; then sleep 2s ; fi 4 | if [ "$3" = "output3" ] ; then sleep 3s ; fi 5 | cp $1 $3 6 | echo end >> ../.log 7 | -------------------------------------------------------------------------------- /util/secondary-run: -------------------------------------------------------------------------------- 1 | echo run >> ../.log 2 | cp $1 $3 3 | echo -n " *" >> $3 4 | -------------------------------------------------------------------------------- /util/system1-gen: -------------------------------------------------------------------------------- 1 | echo gen >> ../.log 2 | A=`cat $2` 3 | B=`cat system1-data` 4 | if [ "$A" != "$B" ] ; then 5 | cp system1-data $2 6 | fi 7 | -------------------------------------------------------------------------------- /util/system1-run: -------------------------------------------------------------------------------- 1 | echo run >> ../.log 2 | cp $1 $3 3 | -------------------------------------------------------------------------------- /util/system2-run: -------------------------------------------------------------------------------- 1 | echo run >> ../.log 2 | echo -n "$SYSTEM2_DATA" > $2 3 | -------------------------------------------------------------------------------- /util/unchanged-gen: -------------------------------------------------------------------------------- 1 | if [ -f $3 ]; then 2 | OLD=`cat $3` 3 | else 4 | OLD=_none_ 5 | fi 6 | NEW=`sed 's/in/out/g' $1` 7 | if [ "$OLD" != "$NEW" ] ; then 8 | sed 's/in/out/g' $1 > $3 9 | fi 10 | -------------------------------------------------------------------------------- /util/unchanged-run: -------------------------------------------------------------------------------- 1 | echo run >> ../.log 2 | sed 's/i/x/g' $1 > $3 3 | --------------------------------------------------------------------------------