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 <stdio.h>
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 |> gcc hello.c -o hello |> 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">|></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 <stdio.h>
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 |> gcc <span class="added">-Wall </span>hello.c -o hello |> 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 <stdio.h>
<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 |> gcc -Wall <span class="added">-c </span>hello.c -o hello<span class="added">.o</span> |> hello<span class="added">.o</span>
<span class="added">: square.c |> gcc -Wall -c square.c -o square.o |> 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 |> gcc -Wall -c hello.c -o hello.o |> hello.o
: square.c |> gcc -Wall -c square.c -o square.o |> square.o
<span class="added">: hello.o square.o |> gcc hello.o square.o -o hello |> 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> |> gcc -Wall -c <font style="background-color: #ffbbff">hello.c</font> -o <font style="background-color: #ffffbb">hello.o</font> |> <font style="background-color: #ffffbb">hello.o</font>
: square.c |> gcc -Wall -c square.c -o square.o |> square.o
: hello.o square.o |> gcc hello.o square.o -o hello |> 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 |> 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> |> hello.o
: square.c |> 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> |> square.o
: hello.o square.o |> 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> |> hello
</pre>
<p>You should now have:</p>
<span class="samefileheader">Tupfile</span>
<pre class="code">: hello.c |> gcc -Wall -c %f -o %o |> hello.o
: square.c |> gcc -Wall -c %f -o %o |> square.o
: hello.o square.o |> gcc %f -o %o |> 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 |> gcc -Wall -c %f -o %o |> <font style="background-color: #ffbbff">hello</font>.o
: <font style="background-color: #ffffbb">square</font>.c |> gcc -Wall -c %f -o %o |> <font style="background-color: #ffffbb">square</font>.o
: hello.o square.o |> gcc %f -o %o |> 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 |> gcc -Wall -c %f -o %o |> <span class="removed">hello.o</span><span class="added">%B.o</span>
: square.c |> gcc -Wall -c %f -o %o |> <span class="removed">square.o</span><span class="added">%B.o</span>
: hello.o square.o |> gcc %f -o %o |> 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">|> gcc -Wall -c %f -o %o |> %B.o</font>
: square.c <font style="background-color: #ffbbff">|> gcc -Wall -c %f -o %o |> %B.o</font>
: hello.o square.o |> gcc %f -o %o |> 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>|> gcc -Wall -c %f -o %o |> %B.o
<span class="removed">: square.c |> gcc -Wall -c %f -o %o |> %B.o</span>
: hello.o square.o |> gcc %f -o %o |> 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> |> gcc -Wall -c %f -o %o |> %B.o
: hello.o square.o |> gcc %f -o %o |> 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> |> gcc -Wall -c %f -o %o |> %B.o
: <span class="removed">hello.o square.o</span><span class="added">*.o</span> |> gcc %f -o %o |> 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 |> gcc -Wall -c %f -o %o |> %B.o
: *.o |> gcc %f -o %o |> 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 > ~/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>
|