File: ex_a_first_tupfile.html

package info (click to toggle)
tup 0.8-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 21,076 kB
  • sloc: ansic: 256,651; sh: 19,101; perl: 184; python: 67; lisp: 63; makefile: 56
file content (342 lines) | stat: -rw-r--r-- 18,877 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
<p>In this example, we'll create a small C program using tup. The program itself will be completely useless, but hopefully you can use your imagination to see how you might use tup in a real project. Let's start with a typical "Hello, world!" in C. Since we're using tup, you'll want to run <span class="cmd">tup init</span> at the top of the project, which in this example will be the tup_test directory.</p>
<pre>
<span class="prompt">$</span> mkdir tup_test
<span class="prompt">$</span> cd tup_test
<span class="prompt">$</span> tup init
<span class="prompt">$</span> EDITOR hello.c
</pre>

<span class="fileheader">hello.c</span>
<pre class="code">#include &lt;stdio.h&gt;

int main(void)
{
	printf("Hello, world!\n");
	return 0;
}
</pre>

<p>Here's what we have so far:</p>
<pre>
<span class="prompt">$</span> ls -a
.  ..  .tup  hello.c
</pre>
<p>The <i>.tup</i> directory contains the dependency database, and some lock files. You shouldn't mess with files in this directory manually, unless you want to play around. If there was a warranty for tup, you would lose it by doing that.</p>

<p>If you were going to compile <i>hello.c</i> manually, you might run something like <span class="cmd">gcc hello.c -o hello</span>. Instead of doing that, however, we'll put that exact string in a Tupfile (along with some additional annotations).</p>

<span class="fileheader">Tupfile</span>
<pre class="code">: hello.c |&gt; gcc hello.c -o hello |&gt; hello
</pre>

<p>This line we typed is known as a ":-rule", since the line begins with a <span class="keyword">:</span>. You can see the gcc command in between the <span class="keyword">|&gt;</span> symbols. The file listed on the left side is the input, and the file listed on the right side is the output. Ignore the redundancy for now -- we'll fix that later.</p>

<p>Now that you have a Tupfile to tell tup what to do, you can run <span class="cmd">tup</span> to start the build process. If all goes well, you should see something like the following, only with more colors and cool progress bars:</p>
<pre>
<span class="prompt">$</span> tup
[ tup ] Scanning filesystem...0.007s
[ tup ] No tup.config changes.
[ tup ] Parsing Tupfiles...
[    1/1    ] .
[ tup ] No files to delete.
[ tup ] Executing Commands...
[    1/1    ] gcc hello.c -o hello
[ tup ] Updated.
<span class="prompt">$</span> ls
Tupfile  hello  hello.c
<span class="prompt">$</span> ./hello
Hello, world!
</pre>

<p>You can try to run <span class="cmd">tup</span> again to verify that the file isn't compiled unnecessarily (since it hasn't been changed). Then you can edit the file and see that it *is* rebuilt:</p>
<pre>
<span class="prompt">$</span> tup
[ tup ] Scanning filesystem...0.000s
[ tup ] No tup.config changes.
[ tup ] No Tupfiles to parse.
[ tup ] No files to delete.
[ tup ] No commands to execute.
[ tup ] Updated.
<span class="prompt">$</span> EDITOR hello.c
</pre>

<span class="modfileheader">hello.c</span>
<pre class="code">#include &lt;stdio.h&gt;

int main(void)
{
	printf(<span class="removed">"Hello, world!\n"</span><span class="added">"Hi, everybody!\n"</span>);
	return 0;
}
</pre>

<pre>
<span class="prompt">$</span> tup
[ tup ] Scanning filesystem...0.006s
[ tup ] No tup.config changes.
[ tup ] No Tupfiles to parse.
[ tup ] No files to delete.
[ tup ] Executing Commands...
[    1/1    ] gcc hello.c -o hello
[ tup ] Updated.
<span class="prompt">$</span> ./hello
Hi, everybody!
</pre>

<p>Finally, we will try to change the gcc command string itself. In this case, we won't change the <span class="filename">hello.c</span> file, only the <span class="filename">Tupfile</span>.</p>

<span class="modfileheader">Tupfile</span>
<pre class="code">: hello.c |&gt; gcc <span class="added">-Wall </span>hello.c -o hello |&gt; hello
</pre>

<pre>
<span class="prompt">$</span> tup
[ tup ] Scanning filesystem...0.007s
[ tup ] No tup.config changes.
[ tup ] Parsing Tupfiles...
[    1/1    ] .
[ tup ] No files to delete.
[ tup ] Executing Commands...
[    1/1    ] gcc -Wall hello.c -o hello
[ tup ] Updated.
</pre>

<p>Since the <span class="filename">Tupfile</span> changed, you can see that the top-level directory (represented by <span class="filename">.</span>) had to be re-parsed. Tup then saw that the command changed, so the new command was executed, even though <span class="filename">hello.c</span> was unchanged.</p>

<p>If you only ever want one C file, you can continue modifying this example as long as you like. Just edit the Tupfile when you want to change how the program is built, and edit the C file when you want to change the program itself.</p>

<p>Read on for other examples to see how to build more than one file, and make the Tupfiles less redundant and more manageable.</p>

<hr>
<h3>A Program Grows</h3>
<p>Continuing from the previous example, we will build on the Hello World program by adding a new C file. Like any useless program example, this file will provide a function to square a number.</p>

<span class="fileheader">square.c</span>
<pre class="code">#include "square.h"

int square(int x)
{
	return x * x;
}
</pre>

<span class="fileheader">square.h</span>
<pre class="code">int square(int x);
</pre>

<span class="modfileheader">hello.c</span>
<pre class="code">#include &lt;stdio.h&gt;
<span class="added">#include "square.h"</span>

int main(void)
{
	printf("Hi, everybody!\n");
	<span class="added">printf("Five squared is: %i\n", square(5));</span>
	return 0;
}
</pre>

<p>Now we can try to build the new program:</p>
<pre>
<span class="prompt">$</span> tup
[ tup ] Scanning filesystem...0.007s
[ tup ] No tup.config changes.
[ tup ] Parsing Tupfiles...
[    1/1    ] .
[ tup ] No files to delete.
[ tup ] Executing Commands...
[    1/1    ] gcc -Wall hello.c -o hello
/tmp/ccClookD.o: In function `main':
hello.c:(.text+0x1d): undefined reference to `square'
collect2: ld returned 1 exit status
 *** tup errors ***
 *** Command ID=6 failed with return value 1
</pre>

<p>Oops, that obviously didn't work, since we forgot to actually compile and link the new C file. Let's do that now.</p>
<p>Consider how you might build this program with the shell, keeping scalability in mind. As a first step, you would probably compile each C file individually with <span class="cmd">gcc -Wall -c hello.c -o hello.o</span> and <span class="cmd">gcc -Wall -c square.c -o square.o</span>. Similar to before, we will type these compilation commands exactly as is in the Tupfile, and add the input/output annotations. Note that we change the first :-rule to use -c and output to <span class="filename">hello.o</span> instead of <span class="filename">hello</span>. After you've made the changes, run <span class="cmd">tup</span>.</p>

<span class="modfileheader">Tupfile</span>
<pre class="code">: hello.c |&gt; gcc -Wall <span class="added">-c </span>hello.c -o hello<span class="added">.o</span> |&gt; hello<span class="added">.o</span>
<span class="added">: square.c |&gt; gcc -Wall -c square.c -o square.o |&gt; square.o</span>
</pre>

<pre>
<span class="prompt">$</span> tup
[ tup ] Scanning filesystem...0.007s
[ tup ] No tup.config changes.
[ tup ] Parsing Tupfiles...
[    1/1    ] .
[ tup ] Deleting files...
[    1/1    ] hello
[ tup ] Executing Commands...
[    1/2    ] gcc -Wall -c square.c -o square.o
[    2/2    ] gcc -Wall -c hello.c -o hello.o
[ tup ] Updated.
<span class="prompt">$</span> ls
Tupfile  hello.c  hello.o  square.c  square.h  square.o
<span class="prompt">$</span> ./hello
bash: ./hello: No such file or directory
</pre>

<p>Since we changed the first rule to compile <span class="filename">hello.o</span> instead of <span class="filename">hello</span>, the main executable is no longer generated. Tup sees this, and removes <span class="filename">hello</span> from the filesystem. In this way, tup always keeps your build as if you had just built everything from scratch. This is why you don't ever have to write or manually invoke a "clean" target, like you do in most other build systems.</p>

<p>To finish the Tupfile, let's go ahead and add the linker rule:</p>

<span class="modfileheader">Tupfile</span>
<pre class="code">: hello.c |&gt; gcc -Wall -c hello.c -o hello.o |&gt; hello.o
: square.c |&gt; gcc -Wall -c square.c -o square.o |&gt; square.o
<span class="added">: hello.o square.o |&gt; gcc hello.o square.o -o hello |&gt; hello</span>
</pre>

<pre>
<span class="prompt">$</span> tup
[ tup ] Scanning filesystem...0.006s
[ tup ] No tup.config changes.
[ tup ] Parsing Tupfiles...
[    1/1    ] .
[ tup ] No files to delete.
[ tup ] Executing Commands...
[    1/1    ] gcc hello.o square.o -o hello
[ tup ] Updated.
<span class="prompt">$</span> ./hello
Hi, everybody!
Five squared is: 25
</pre>

<p>One thing you may have noticed is that both C files include the <span class="filename">square.h</span> header, but we haven't specified it as an input to the command. You may be surprised to see that we can still change the header and cause both files to recompile to pick up the change:</p>

<span class="modfileheader">square.h</span>
<pre class="code">int square(int x);
<span class="added">#define SECRET 42</span>
</pre>

<pre>
<span class="prompt">$</span> tup
[ tup ] Scanning filesystem...0.007s
[ tup ] No tup.config changes.
[ tup ] No Tupfiles to parse.
[ tup ] No files to delete.
[ tup ] Executing Commands...
[    1/3    ] gcc -Wall -c square.c -o square.o
[    2/3    ] gcc -Wall -c hello.c -o hello.o
[    3/3    ] gcc hello.o square.o -o hello
[ tup ] Updated.
</pre>

<p>The trick is that tup instruments all commands that it executes in order to determine what files were actually read from (the inputs) and written to (the outputs). When the C preprocessor opens the header file, tup will notice that and automatically add the dependency. In fact, we don't have to specify the C input file either, but you can leave that in there for now since we'll use it in the next section.</p>

<hr>
<h3>A Simpler Tupfile</h3>
<p>Let's take a closer look at the Tupfile from before:</p>

<span class="samefileheader">Tupfile</span>
<pre class="code">: <font style="background-color: #ffbbff">hello.c</font> |&gt; gcc -Wall -c <font style="background-color: #ffbbff">hello.c</font> -o <font style="background-color: #ffffbb">hello.o</font> |&gt; <font style="background-color: #ffffbb">hello.o</font>
: square.c |&gt; gcc -Wall -c square.c -o square.o |&gt; square.o
: hello.o square.o |&gt; gcc hello.o square.o -o hello |&gt; hello
</pre>

<p>The first :-rule has been highlighted to show the redundant information. It's pretty annoying whenever you have to type something twice, so we'll get rid of the duplication. To do that, we'll make use of tup's %-flags. The <span class="keyword">%f</span> flag can be used to represent the inputs, while the <span class="keyword">%o</span> flag can be used to represent the outputs. Let's change each of the rules to use these new flags:</p>

<span class="modfileheader">Tupfile</span>
<pre class="code">: hello.c |&gt; gcc -Wall -c <span class="removed">hello.c</span><span class="added">%f</span> -o <span class="removed">hello.o</span><span class="added">%o</span> |&gt; hello.o
: square.c |&gt; gcc -Wall -c <span class="removed">square.c</span><span class="added">%f</span> -o <span class="removed">square.o</span><span class="added">%o</span> |&gt; square.o
: hello.o square.o |&gt; gcc <span class="removed">hello.o square.o</span><span class="added">%f</span> -o <span class="removed">hello</span><span class="added">%o</span> |&gt; hello
</pre>

<p>You should now have:</p>

<span class="samefileheader">Tupfile</span>
<pre class="code">: hello.c |&gt; gcc -Wall -c %f -o %o |&gt; hello.o
: square.c |&gt; gcc -Wall -c %f -o %o |&gt; square.o
: hello.o square.o |&gt; gcc %f -o %o |&gt; hello
</pre>

<p>An easy way to see that your Tupfile refactoring didn't cause any functional changes is to run <span class="cmd">tup refactor</span>. If the commands expand to the same string, you will only see the Tupfile being re-parsed, followed by "No files to delete" message:</p>

<pre>
<span class="prompt">$</span> tup refactor
[ tup ] Scanning filesystem...0.007s
[ tup ] No tup.config changes.
[ tup ] Parsing Tupfiles...
[    1/1    ] .
[ tup ] No files to delete.
</pre>

<h4>More!</h4>
<p>There is still some more redundancy we get get rid of:</p>
<span class="samefileheader">Tupfile</span>
<pre class="code">: <font style="background-color: #ffbbff">hello</font>.c |&gt; gcc -Wall -c %f -o %o |&gt; <font style="background-color: #ffbbff">hello</font>.o
: <font style="background-color: #ffffbb">square</font>.c |&gt; gcc -Wall -c %f -o %o |&gt; <font style="background-color: #ffffbb">square</font>.o
: hello.o square.o |&gt; gcc %f -o %o |&gt; hello
</pre>

<p>In this case we'll want to make the output file the same name as the input file, only with a different extension. We can use some %-flags in the output file list as well. For example, <span class="keyword">%B</span> (basename, no extension) will be replaced with the input filename, minus any directory and extension information. Let's use it in the output section now:</p>

<span class="modfileheader">Tupfile</span>
<pre class="code">: hello.c |&gt; gcc -Wall -c %f -o %o |&gt; <span class="removed">hello.o</span><span class="added">%B.o</span>
: square.c |&gt; gcc -Wall -c %f -o %o |&gt; <span class="removed">square.o</span><span class="added">%B.o</span>
: hello.o square.o |&gt; gcc %f -o %o |&gt; hello
</pre>

<p>If you run <span class="cmd">tup refactor</span> again you should not see any errors.</p>

<h4>More! More!</h4>
<p>Continuing our efforts to remove redundancy, let's take another look at the first two rules:</p>

<span class="samefileheader">Tupfile</span>
<pre class="code">: hello.c <font style="background-color: #ffbbff">|&gt; gcc -Wall -c %f -o %o |&gt; %B.o</font>
: square.c <font style="background-color: #ffbbff">|&gt; gcc -Wall -c %f -o %o |&gt; %B.o</font>
: hello.o square.o |&gt; gcc %f -o %o |&gt; hello
</pre>

<p>Here you can see we have two rules that differ only in their input filename - both the command string and output string are identical (even though they evaluate to different values). We can combine these rules into one using the <span class="keyword">foreach</span> keyword. The foreach keyword iterates over the input list in a loop, and creates a new rule for each one. Since we have two inputs, there are two new rules, which exactly correspond to the two separate rules we had written earlier.</p>

<span class="modfileheader">Tupfile</span>
<pre class="code">: <span class="added">foreach </span>hello.c <span class="added">square.c </span>|&gt; gcc -Wall -c %f -o %o |&gt; %B.o
<span class="removed">: square.c |&gt; gcc -Wall -c %f -o %o |&gt; %B.o</span>
: hello.o square.o |&gt; gcc %f -o %o |&gt; hello
</pre>

<p>Once again verify that no functional changes have been made by running <span class="cmd">tup refactor</span>.</p>

<h4>MMMMMMMMMore!</h4>
<p>By now you can see that there isn't a whole lot of redundancy left. One thing that may not be apparent is that the Tupfile is redundant with the directory itself!</p>

<span class="samefileheader">Tupfile</span>
<pre class="code">: foreach <font style="background-color: #ffbbff">hello.c square.c</font> |&gt; gcc -Wall -c %f -o %o |&gt; %B.o
: hello.o square.o |&gt; gcc %f -o %o |&gt; hello
</pre>
<pre>
<span class="prompt">$</span> ls *.c
<font style="background-color: #ffbbff">hello.c  square.c</font>
</pre>

<p>If you like explicitly listing the source files, you are certainly free to do so. However, you might like to just be able to create a new C file in the directory and have it automatically get compiled and linked in. To do so, just use the globbing feature:</p>

<span class="modfileheader">Tupfile</span>
<pre class="code">: foreach <span class="removed">hello.c square.c</span><span class="added">*.c</span> |&gt; gcc -Wall -c %f -o %o |&gt; %B.o
: <span class="removed">hello.o square.o</span><span class="added">*.o</span> |&gt; gcc %f -o %o |&gt; hello
</pre>

<p>One thing to note is that the file globbing does not take place on the filesystem, but rather the tup database itself. Consider if we had used this Tupfile before we ever created <span class="filename">hello.o</span> or <span class="filename">square.o</span> - the <span class="keyword">*.o</span> wouldn't have anything to match! However, when tup parses the <span class="keyword">foreach</span> rule, the new files are added to the tup database before they are created in the filesystem. This way the <span class="keyword">*.o</span> will match the files, and the proper linker rule will be generated.</p>

<h4>A Brief Look Inside Tup</h4>

<p>Let's take one last look at the final Tupfile for this example:</p>

<span class="samefileheader">Tupfile</span>
<pre class="code">: foreach *.c |&gt; gcc -Wall -c %f -o %o |&gt; %B.o
: *.o |&gt; gcc %f -o %o |&gt; hello
</pre>

<p>It doesn't look too much like a shell script anymore. Fret not, for the script is still in tup! However, it is not a true shell script, but rather a graph of commands connected by their inputs and outputs. If you have <a href="http://www.graphviz.org/">Graphviz</a> installed (which gives you the <span class="cmd">dot</span> program), you can generate a graph like so:</p>

<pre>
<span class="prompt">$</span> tup graph . | dot -Tpng &gt; ~/hello.png
</pre>

<p><div class="img"><div class="desc">Program Dependencies</div><img src="hello.png"></div></p>
<p>If you were to make a <a href="http://en.wikipedia.org/wiki/Topological_ordering">topological sort (Wikipedia)</a> of the graph, keeping only the rectangle shaped nodes, you would have your shell script again. When <span class="cmd">tup</span> gets to the final stage of its processing and actually goes to execute commands, it will start with the files you changed (like <span class="filename">hello.c</span>) and follow the arrows upward until there are no more arrows to follow. Try starting at each of the three base files and see which commands will be executed. It also works for any combination of inputs. In this way, you can think of the tup database as a collection of <span class="keyword">2^n</span> shell scripts, where <span class="keyword">n</span> is the number of input files (since each file can be either "modified" or "unmodified", you get an exponential number of possible fileset changes). Depending on which files you modified, tup will pick out the smallest shell script that updates everything quickly and correctly. Of course, the shell script is generated on the fly using the graph - storing an exponential number of shell scripts would be crazy.</p>

<p>This is merely scratching the surface of how tup works - there is much more than just picking out the correct rectangles! After all, those dots and lines have to get in the graph in the first place...</p>