shake
minimal build system that generates Ninja build files
git clone https://9o.is/git/shake.git
README.md
(6649B)
1 # Shake
2
3 Shake is a minimal build system that generates [Ninja](https://ninja-build.org) build files from declarative `Shakefile` definitions, which are just POSIX shell scripts. It provides a clean, composable way to describe recursive builds without the complexity of traditional build systems like Make.
4
5 ## Motivation
6
7 Shake, a portmanteau of shell and make, is a generalized set of posix shell functions I wrote to build my statically-linked linux distro from source, but it's flexible enough to be used in a wide-variety of tasks, like managing dot files or even server deployment scripts. Since it's powered by Ninja, you get the benefit of incremental, recursive builds. Why shell? Because it's a commonly used language for orchestrating tasks on unix systems. Still, the orchestration gets chaotic and become difficult to manage very quickly at scale. Incorporating a build backend with dependency management is a solution to that problem.
8
9 ## Requirements
10
11 - [Ninja](https://ninja-build.org)
12 - POSIX Shell
13
14 ## Installation
15
16 Run the installation script:
17
18 ```sh
19 ./install --prefix=/usr/local
20 ```
21
22 ### Installation Options
23
24 | Option | Description | Default |
25 |--------|-------------|---------|
26 | `--prefix=DIR` | Installation prefix | `/usr/local` |
27 | `--destdir=DIR` | Staging directory for packaging | (none) |
28 | `--shakelib=DIR` | Path to shake library | `/share/shake` |
29
30 ## Quick Start
31
32 1. **Create a Shakefile** in your project root:
33
34 ```sh
35 let CC ${CC:-cc}
36 let CFLAGS ${CFLAGS:--Wall -g}
37
38 rule cc '$CC $CFLAGS -c $in -o $out'
39 bind description 'CC $in'
40
41 rule link '$CC $in -o $out'
42 bind description 'LINK $out'
43
44 cc main.o: main.c
45 link hello: main.o
46
47 default hello
48 ```
49
50 Shake's goal is to remain minimal, yet provide helpful functions to get you productive
51 quickly. The syntax tries to stay close to Ninja's syntax, so it would be very helpful to
52 [understand and read how Ninja works](https://ninja-build.org/manual.html).
53
54 2. **Generate Ninja files**:
55
56 ```sh
57 shake
58 ```
59
60 3. **Build your project**:
61
62 ```sh
63 ninja
64 ```
65
66 That's it! On subsequent builds, Ninja will only rebuild what's changed. Running shake again is
67 only necessary if you add a new Shakefile. Modifications to existing Shakefiles are detected
68 and rebuilt by ninja.
69
70 ## Recursive Builds
71
72 Shake supports recursive builds - each subdirectory can have its own Shakefile:
73
74 ```sh
75 # Root Shakefile (extends the example above)
76 shake lib
77
78 let LIBDIR $dir/lib
79 let CFLAGS $CFLAGS -I$LIBDIR
80
81 cc main.o: main.c - $LIBDIR/lib.h
82 link hello: main.o $LIBDIR/lib.a
83 ```
84
85 ```sh
86 # lib/Shakefile
87 let AR ${AR:-ar}
88
89 rule ar '$AR rcs $out $in'
90 bind description 'AR $out'
91
92 cc lib.o: lib.c
93 ar lib.a: lib.o
94 ```
95
96 Notice that `lib/Shakefile` inherits variables and rules from its parent such as
97 CFLAGS and cc. In the root Shakefile, a `-` is used to indicate that the following
98 files are dependencies and not inputs. See Ninja's manual. The `-` is identical to
99 Ninja's `|`.
100
101 To run it:
102
103 ```sh
104 shake lib
105 ninja
106 ```
107
108 See `example/nested/` for a complete working example.
109
110 ## Usage
111
112 ```
113 shake [options] [directory]
114 -C dir change to directory before building
115 -l dir override shake lib path
116 -o dir set output directory
117 -h show help
118 ```
119
120 The optional `directory` argument specifies a subdirectory to generate:
121
122 ```sh
123 shake lib # Generate the lib/ subproject
124 shake . # Generate the current directory (no subproject)
125 ```
126
127 Shake automatically finds the project root by looking for a `.shake/` directory, so
128 you can run shake from any subdirectory.
129
130 ## Syntax Reference
131
132 ### Variables
133
134 Define variables with `let`:
135
136 ```sh
137 let CC ${CC:-cc}
138 let CFLAGS -Wall -g
139 ```
140
141 Variables can reference environment variables with `${VAR:-default}` syntax. The let
142 command essentially writes a variable to the ninja file, and defines a shell variable
143 so you can refer to it from any Shakefile.
144
145 ### Rules
146
147 Define build rules with `rule`:
148
149 ```sh
150 rule cc '$CC $CFLAGS -c $in -o $out'
151 bind description 'CC $in'
152 ```
153
154 The rule body specifies the command to run. Available variables:
155 - `$in` - input files
156 - `$out` - output files
157
158 Use `bind` to set rule attributes:
159 - `bind description` - human-readable description shown during build
160 - `bind depfile` - path to dependency file
161 - `bind generator` - mark as a generator rule
162 - `bind restat` - re-stat outputs after command runs
163
164 ### Build Rules
165
166 Declare what to build:
167
168 ```sh
169 cc main.o: main.c
170 cc hello.o: hello.c
171 link hello: main.o hello.o
172 ```
173
174 Multiple inputs and outputs are supported.
175
176 ### Phony Targets
177
178 Create phony targets for actions that aren't actual files:
179
180 ```sh
181 phony clean rm -f *.o hello
182 phony install $DESTDIR$PREFIX/bin/hello: hello
183 ```
184
185 ### Default Targets
186
187 Set which targets to build when none is specified:
188
189 ```sh
190 default hello
191 ```
192
193 ### Subdirectory Builds
194
195 Include subdirectory builds with `shake`:
196
197 ```sh
198 # In parent Shakefile
199 shake lib
200 shake bin
201 ```
202
203 Each subdirectory should have its own `Shakefile`. Shake generates Ninja files for each subproject and includes them.
204
205 ### Directory Variables
206
207 Two special variables are automatically available:
208 - `$dir` - the directory containing the current Shakefile
209 - `$outdir` - the output directory (can be overridden with `-o` or let command)
210
211 ### Import Files
212
213 Include shared definitions with `import`:
214
215 ```sh
216 import rules.sh
217 ```
218
219 ### Grouping build inputs and outputs
220
221 Build inputs and outputs are tracked by shake. You can group inputs or outputs
222 using an extended regular expression pattern to create a phony target.
223
224 For example, the following creates a phony headers target and groups all generated header files:
225
226 ```sh
227 groupout headers '.*\.h$'
228 ```
229
230 If you need to fetch source files and extract them into `src/` subdirectory before
231 compiling, the following groups all src files to depend on the phony fetch target:
232
233 ```sh
234 groupin fetch '\$dir/src/.*'
235 ```
236
237 ## Examples
238
239 See the `example/` directory for working examples:
240
241 ### Simple Example
242
243 A minimal single-directory C project:
244
245 ```sh
246 cd example/simple
247 shake
248 ninja
249 ```
250
251 Builds `hello` from `main.c` and `hello.c`.
252
253 ### Nested Example
254
255 A multi-directory project with recursive subbuilds:
256
257 ```sh
258 cd example/nested
259 shake
260 ninja
261 ```
262
263 Structure:
264 - `lib/` - builds a static library
265 - `bin/` - builds an executable linking against the library
266
267 ## How It Works
268
269 Shake reads your `Shakefile` and generates `local.ninja` (and `build.ninja` symlink). When you run `ninja`, it executes the actual builds.
270
271 Because Shake generates Ninja files, you get:
272 - Fast incremental builds (Ninja tracks dependencies)
273 - Parallel builds with `ninja -jN`
274 - Build visualization with `ninja -t deps` and `ninja -t targets`