{ |one, step, back| } http://www.onestepback.org/index.cgi Jim Weirich's Blog en-us { |one, step, back| } http://onestepback.org http://onestepback.org/images/jwface.gif Rake Tutorial -- Another C Example http://www.onestepback.org/index.cgi/Tech/Rake/Tutorial/RakeTutorialAnotherCExample.red <blockquote> <p><em>Mark Probert on the Ruby-Talk mailing lists asks: &#8220;I am not sure how to create a set of Rake rules to do the following. Can anyone prove assistance?&#8221;</em></p> </blockquote> <p>I had planned for the next Rake tutorial to go into using prepackaged task libraries, but Mark&#8217;s question highlights an interesting problem (and resolution) with rules. I posted an answer to Mark on the list, but why waste a perfectly good explaination when I can recycle it here.</p> <h2>The Problem</h2> <p>Mark has two separate source directories (he calls them <code>src_a</code> and <code>src_b</code>, but I suspect they are more creatively named in real life. Both directories contain <code>.c</code> files. However, the kicker is that all the object files are to be placed in a single directory (named <code>obj</code>) no matter which source directory contained the original C code.</p> <p>The rule we introduced in the last tutorial isn&#8217;t powerful enough to move the <code>.o</code> file into the <code>obj</code> directory. We need to tweek it just a bit.</p> <h2>The <code>.o</code> File Rule</h2> <p>Here is the rule in question. </p> <pre><code>rule '.o' =&gt; '.c' do |t| sh "cc -c -o #{t.name} #{t.source}" end</code></pre> <p>To recap, the rule specifies how to create a <code>.o</code> file from a similarly named <code>.c</code> file. But as noted above, the <code>.c</code> are in a different location. We can fix this by giving the rule a general purpose function that transforms the object file name into the correct source file name.</p> <h2>Finding the Source File.</h2> <p>But how do we find the source file? Assuming we have a constant named <code>SRC</code> that contains a list of all our source files, this simple find command will do the trick (assume <code>objfile</code> is the name of the object file):</p> <pre><code>SRC.find { |s| File.basename(s, '.c') == File.basename(objfile, '.o') }</code></pre> <p>Wrapping this in a method (named <code>find_source</code>) gives us a nice way to find the source file.</p> <h2>Tweeking the Rule</h2> <p>We can now write the rule like this&#8230;</p> <pre><code>rule '.o' =&gt; lambda { |objfile| find_source(objfile) } do |t| sh "cc -c -o #{t.name} #{t.source}" end</code></pre> <h2>The Whole Rakefile</h2> <p>Just a couple notes about the Rakefile</p> <ol> <li>Note that we invoke the <span class="caps">OBJDIR</span> task directly in the rule. Because it is a rule, there is no opportunity to list <span class="caps">OBJDIR</span> as an explicit dependency. By invoking it directly inside the rule, we will build that directory if it is needed (but only if it is needed).</li> <li>If searching the <span class="caps">SRC</span> list has performance problems (because <span class="caps">SRC</span> is very long), then an alternative is to create a mapping of object names to source names at the top of the file. Then finding the source name is a simple hash lookup.</li> </ol> <h3>Rakefile</h3> <pre> require 'rake/clean' PROG = "foo" LIBNAME = PROG LIBFILE = "lib#{LIBNAME}.a" SRC = FileList['**/*.c'] OBJDIR = 'obj' OBJ = SRC.collect { |fn| File.join(OBJDIR, File.basename(fn).ext('o')) } CLEAN.include(OBJ, OBJDIR, LIBFILE) CLOBBER.include(PROG) task :default =&gt; [:build, :run] task :build =&gt; [PROG] task :run =&gt; [PROG] do sh "./#{PROG}" end file PROG =&gt; [LIBFILE] do sh "cc -o #{PROG} -L . -l#{LIBNAME}" end file LIBFILE =&gt; OBJ do sh "ar cr #{LIBFILE} #{OBJ}" sh "ranlib #{LIBFILE}" end directory OBJDIR rule '.o' =&gt; lambda{ |objfile| find_source(objfile) } do |t| Task[OBJDIR].invoke sh "cc -c -o #{t.name} #{t.source}" end def find_source(objfile) base = File.basename(objfile, '.o') SRC.find { |s| File.basename(s, '.c') == base } end </pre> <h2>Alternatives</h2> <p>On possible alternative is to replace the rule with a loop that explicitly creates tasks to compile each .c file. It might look something like this:</p> <pre><code>SRC.each do |srcfile| objfile = File.join(OBJDIR, File.basename(srcfile).ext('o')) file objfile =&gt; [srcfile, OBJDIR] do sh "cc -c -o #{objfile} #{srcfile}" end end</code></pre> <p>What I like about this solution is the ability to put the <span class="caps">OBJDIR</span> dependency directly in the task definition.</p> Rake Tutorial -- Handling Common Actions http://www.onestepback.org/index.cgi/Tech/Rake/Tutorial/RakeTutorialRules.red <blockquote> <p><em>Rake is a tool for controlling builds. In this part of the Rake tutorial, we see how to organize the Rake actions to apply to many similar tasks. </em></p> </blockquote> <p>In the RakeTutorialIntroduction, we talked about the basics of specifying dependencies and associating actions to build the files. We ended up with a nice Rakefile that built our simple C program, but with some duplication in the build rules.</p> <h2>But First, Some Extra Rake Targets</h2> <p>But before we get into all that, lets add some convience targets to our Rakefile. First of all, it would be nice to have a default target that is invoked when we don&#8217;t give any explicit task names to rake. The default target looks like this:</p> <pre> task :default =&gt; ["hello"] </pre> <p>Until now, the only kind of task we have seen in Rake are <em>file</em> tasks. File tasks are knowledgable about time stamps on files. A file task will not execute its action unless the file it represents doesn&#8217;t exist, or is older than any of its prerequisites.</p> <p>A non-file task (or just plain &#8220;task&#8221;) does not represent the creation of a file. Since there is no timestamp for comparison, non-file tasks <em>always</em> execute their actions (if they have any). Since the <em>default</em> task does not represent a file named &#8220;default&#8221;, we use a regular non-file task for this purpose. Non-file tasks just use the <code>task</code> keyword (instead of the <code>file</code> keyword).</p> <p>Here are a couple of other really useful tasks that I almost always include in a Rakefile.</p> <strong><code>clean</code>:</strong> <blockquote> <p>Remove temporary files created during the build process.</p> </blockquote> <strong><code>clobber</code>:</strong> <blockquote> <p>Remove <strong>all</strong> files generated during the build process.</p> </blockquote> <p><code>clean</code> tidies up the directories and removes any files that generated as part of the build process, but are not the final goal of the build process. For example, the <code>.o</code> files used to link up the final executable hello program would fall in this category. After the executable program is built, the <code>.o</code> files are no longer needed and will be removed by saying &#8220;<tt>rake clean</tt>&#8221;.</p> <p><code>clobber</code> is like <code>clean</code>, but even more aggressive. &#8220;<tt>rake clobber</tt>&#8221; will remove all files that are not part of the original package. It should return a project to the &#8220;just checked out of <span class="caps">CVS</span>&#8221; state. So it removes the final executable program as well as the files removed by <code>clean</code>.</p> <p>In fact, these tasks are so common, Rake comes with a predefined library that implements <code>clean</code> and <code>clobber</code>.</p> <p>But every project is different, how do we specify <em>which</em> files are to be cleaned and clobbered on a per project basis?</p> <p>The answer is File lists.</p> <h2>File Lists to the Rescue</h2> <p>A file list is simply a list of file names. Since a lot of what Rake does involves files and lists of those files, a file list has some special features to make manipulating file names rather easy.</p> <p>Suppose you want a list of all the C files in your project. You could add this to your rake file:</p> <pre> SRC = FileList['*.c'] </pre> <p>This will collect all the files ending in &#8221;.c&#8221; in the top level directory of your project. File lists understand glob patterns (i.e. things like "*.c") and will find all the matching files.</p> <p>By the way, no matter where you invoke it, <code>rake</code> always executes in the directory where the Rakefile is found. This keeps your path names consistent without depending on the current directory the user interactive shell.</p> <p>The <code>clean</code> and <code>clobber</code> tasks use file lists to manage the files to remove. So if we want to clean up all the <code>.o</code> files in a project we could try &#8230;</p> <pre> CLEAN = FileList['*.o'] </pre> <p>(<code>CLEAN</code> is the file list associated with the <code>clean</code> task. I&#8217;ll let you guess the name of the file list associated with <code>clobber</code>).</p> <h2>The Rakefile So Far &#8230;</h2> <p>With the addtion of a few extra tasks, our Rakefile now looks like this. Notice the <tt>require &#8216;rake/clean&#8217;</tt> line used to enable the <code>clean</code> and <code>clobber</code> tasks.</p> <pre> require 'rake/clean' CLEAN.include('*.o') CLOBBER.include('hello') task :default =&gt; ["hello"] file 'main.o' =&gt; ["main.c", "greet.h"] do sh "cc -c -o main.o main.c" end file 'greet.o' =&gt; ['greet.c'] do sh "cc -c -o greet.o greet.c" end file "hello" =&gt; ["main.o", "greet.o"] do sh "cc -o hello main.o greet.o" end </pre> <p>Ok, now its time to address the redundant compile commands.</p> <h2>Dynamically Building Tasks</h2> <p>The command to compile the <tt>main.c</tt> and <tt>greet.c</tt> files is identical, except for the name of the files involved. The simpliest and most direct way to address the problem is to define the compile task in a loop. Perhaps something like this &#8230;</p> <pre> SRC = FileList['*.c'] SRC.each do |fn| obj = fn.sub(/\.[^.]*$/, '.o') file obj do sh "cc -c -o #{obj} #{fn}" end end </pre> <p>Just a couple things to note about the above code.</p> <ul> <li>The dependencies are not specified. This is a common where we specify the dependents at one place and the actions in another. Rake is smart enough to combine the dependencies with the actions.</li> </ul> <ul> <li>Although the task was named after the <code>.o</code> (which is, after all, what we want to generate), the file list is defined in terms of the <code>.c</code> files. Why?</li> </ul> <p style="padding-left:1em;"> The simple reason is that file lists search for file names that exist in the file system. We have no guarantee that the <code>.o</code> files even exist at this point (indeed, the will not after invoking the <code>clean</code> task). The <code>.c</code> are source and will always be there.</p> <h2>Rake Can Automatically Generate Tasks</h2> <p>Defining tasks in a loop is pretty cool, but is really not needed in a number of simple cases. Rake can automatically generate file based tasks according to some simple pattern matching rules.</p> <p>For example, we can capture the above logic in a single rule &#8230; no need to find all the source files and iterate through them.</p> <pre> rule '.o' =&gt; '.c' do |t| sh "cc -c -o #{t.name} #{t.source}" end </pre> <p>The above rule says that if you want to generate a file ending in <code>.o</code>, then you if you have a file with the same base name, but ending in <code>.c</code>, then you can generate the <code>.o</code> from the <code>.c</code>.</p> <p><tt>t.name</tt> is the name of the task, and in file based tasks will be the name of the file we are trying to generate. <tt>t.source</tt> is the name of the source file, i.e. the one that matches the second have of the rule pattern. <tt>t.source</tt> is only valid in the body of a rule.</p> <p>Rules are actually much more flexible than you are led to believe here. But that&#8217;s an advanced topic that we will save for another day.</p> <h2>Final Rakefile</h2> <p>Here is our final resule. Notice how we use the <code>SRC</code> and <code>OBJ</code> file lists to manage our lists of scource files and object files.</p> <pre> require 'rake/clean' CLEAN.include('*.o') CLOBBER.include('hello') task :default =&gt; ["hello"] SRC = FileList['*.c'] OBJ = SRC.ext('o') rule '.o' =&gt; '.c' do |t| sh "cc -c -o #{t.name} #{t.source}" end file "hello" =&gt; OBJ do sh "cc -o hello #{OBJ}" end # File dependencies go here ... file 'main.o' =&gt; ['main.c', 'greet.h'] file 'greet.o' =&gt; ['greet.c'] </pre> <h2>Up Next</h2> <p>In our next tutorial, we will look at using Rake to handle some tasks other than compiling C code.</p> Rake Tutorial -- Getting Started http://www.onestepback.org/index.cgi/Tech/Rake/Tutorial/RakeTutorialIntroduction.rdoc <dl> <dt> </dt><dd><b>Received via EMail:</b> </dd> <dt> </dt><dd><em>I have just started using the excellent Rake tool (thanks, Jim!) and I am at a bit of a loss on how to proceed. I am attempting to create unit test for some C++ code I am creating, [&#8230;]</em> </dd> </dl> Several people recently have made similar comments, they really like <tt>rake</tt>, but have had trouble getting started. Although the Rake documentation is fairly complete, it really does assume you are familiar with other build tools such as <tt>ant</tt> and <tt>make</tt>. It is not really material for the newbie. <p> To adderess this lack, I&#8217;m going to post several Rake tutorial articles that will take you through some of the basics. Eventually, I&#8217;ll organize the articles into a document somewhere. </p> <p> Here&#8217;s the first one! </p> <h2>The Problem</h2> <p> We will start with a very simple build problem, the type of problem that make (and now rake) were desiged to deal with. </p> <p> Suppose I am a C programmer and I have a simple C program consisting of the following files. </p> <p> <b>main.c</b> </p> <pre> #include &quot;greet.h&quot; int main() { greet (&quot;World&quot;); return 0; } </pre> <p> <b>greet.h</b> </p> <pre> extern void greet(const char * who); </pre> <p> <b>greet.c</b> </p> <pre> #include &lt;stdio.h&gt; void greet (const char * who) { printf (&quot;Hello, %s\n&quot;, who); } </pre> <p> (Yes, it really is the old standard &quot;Hello, World&quot; program. I did say we were starting with the basics!) </p> <p> To compile and run this collection of files, a simple shell script like the following is adequate. </p> <p> <b>build.sh</b> </p> <pre> cc -c -o main.o main.c cc -c -o greet.o greet.c cc -o hello main.o greet.o </pre> <dl> <dt> </dt><dd><em>For those not familiar with compiling C code, the <tt>cc</tt> command is the C compiler. It generates an output file (specified by the <tt>-o</tt> flag) from the source files listed on the command line. </em> </dd> </dl> <p> Running it gives us the following results &#8230; </p> <pre> $ build.sh $ ./hello Hello, World </pre> <h2>Building C Programs</h2> <p> Compiling C programs is really a two step process. First you compile all the source code file into object files. Then you take all the object files and link them together to make the executable. </p> <p> The following figure illustrates the progression from source files to object files to executable program. </p> <p> <img src="http://onestepback.org/images/raketut_files.png"> </p> <p> Our program is so small that there is little benefit in doing more than the three line build script above. However, as projects grow, there are more and more source files and object files to manage. Recompiling everything for a simple one line change in a single source file gets old quickly. It is much more efficient to just recompile the few files that change and then relink. </p> <p> But how do we know what to recompile? Keeping track of that would be quite error prone if we tried to do that by hand. Here is where Rake become useful. </p> <h2>File Dependencies</h2> <p> First, lets take a look at when files need to be recompiled. Consider the <tt>main.o</tt>. Obviously if the <tt>main.c</tt> file changes, then we need to rebuild <tt>main.o</tt>. But are the other files that can trigger a recompile of <tt>main.o</tt>? </p> <p> Actually, yes. Looking at the source of <tt>main.c</tt>, we see that it includes the header file <tt>greet.h</tt>. That means any changes in <tt>greet.h</tt> could possibly effect the <tt>main.o</tt> file as well. </p> <p> We say that <tt>main.o</tt> has a dependency on the files <tt>main.c</tt> and <tt>greet.h</tt>. We can capture this dependency in Rake with the following line: </p> <pre> file &quot;main.o&quot; =&gt; [&quot;main.c&quot;, &quot;greet.h&quot;] </pre> <p> The rake dependency declaration is just regular Ruby code. We take advantage of the fact that we can construct hash arguments on the fly, and that Ruby doesn&#8217;t require parenthesis around the method arguement to create a file task declaration that reads very naturally to the humans reading the rake file. But its still just Ruby code. </p> <p> Likewise, we can declare the dependencies for creating the &quot;greet.o&quot; file as well. </p> <pre> file &quot;greet.o&quot; =&gt; [&quot;greet.c&quot;] </pre> <p> <tt>greet.c</tt> does include <tt>stdio.h</tt>, but since that is a system header file and not subject to change (often), we can leave it out of the dependency list. </p> <p> Finally we can declare the dependencies for the executable program <tt>hello</tt>. It just depends on the two object files. </p> <pre> file &quot;hello&quot; =&gt; [&quot;main.o&quot;, &quot;greet.o&quot;] </pre> <p> Notice that we only have to declare the direct dependencies of <tt>hello</tt>. Yes, <tt>hello</tt> depends on <tt>main.o</tt> which in turn depends on <tt>main.c</tt>. But the <tt>.c</tt> files are not directly used in building <tt>hello</tt>, so they can safely be omitted from the list. </p> <h2>Building the Files</h2> <p> We have carefully specified how the files are related. Now we need to say what Rake would have to do to build the files when needed. </p> <p> This part is pretty simple. The three line build script that we started with contains all the commands needed to build the program. We just need to put those actions with the right set of dependencies. Use a Ruby <b>do</b> / <b>end</b> block to capture actions &#8230; </p> <p> The result looks like this: </p> <p> <b>Rakefile</b> </p> <pre> file 'main.o' =&gt; [&quot;main.c&quot;, &quot;greet.h&quot;] do sh &quot;cc -c -o main.o main.c&quot; end file 'greet.o' =&gt; ['greet.c'] do sh &quot;cc -c -o greet.o greet.c&quot; end file &quot;hello&quot; =&gt; [&quot;main.o&quot;, &quot;greet.o&quot;] do sh &quot;cc -o hello main.o greet.o&quot; end </pre> <h2>Trying it out</h2> <p> So, let&#8217;s see if it works! </p> <pre> $ rake hello (in /home/jim/pgm/rake/intro) cc -c -o main.o main.c cc -c -o greet.o greet.c cc -o hello main.o greet.o </pre> <p> The command line <tt>rake hello</tt> instructs rake to look through its list of tasks and find one called &quot;hello&quot;. It then checks hello&#8217;s dependencies and builds them if required. Finally, when everything is ready it builds <tt>hello</tt> by executing the C compiler command. </p> <p> Rake dutifully reports what it is doing as it goes along. We can see that each compiler invocation is done in the correct order, building the main program at the end. So, does the program work? Let&#8217;s find out. </p> <pre> $ ./hello Hello, World </pre> <p> Success! </p> <p> But what happens when we change a file. Lets change the greet function in <tt>greet.c</tt> to print &quot;Hi&quot; instead of hello. </p> <pre> $ xemacs greet.c $ rake hello (in /home/jim/pgm/rake/intro) cc -c -o greet.o greet.c cc -o hello main.o greet.o $ $ ./hello Hi, World </pre> <p> Notice that it recompiles greet.c making a new greet.o. And then it needs to relink hello with the new greet.o. Then it is done. There is no need to recompile main.c since it never changed. </p> <p> What do you think will happend if we run Rake again? </p> <pre> $ rake hello (in /home/jim/pgm/rake/intro) $ </pre> <p> That&#8217;s right &#8230; nothing. Everything is up to date with its dependencies, so there is no work for Rake to do. </p> <p> Ok, sure. Rake is a bit of overkill for only two source files and a header. But imagine a large project with hundreds of files and dependencies. All of a sudden, a tool like Rake becomes very attractive. </p> <h2>Summary</h2> <p> What have we learned? Building a Rakefile involves identifying dependencies and the actions required to create the target files. Then declaring the dependencies and actions are as simple as writing them down in standard Ruby code. Rake then handles the details of building </p> <h2>What&#8217;s Up Next</h2> <p> We notice that even our small example has a bit of duplication in it. We have specify how to compile both C file separately, even though the only difference is the files that are used. The next installment will look at fixing that problem as well as introduce non-file based tasks, rules and file lists. </p> <p> Until then &#8230; Code Red, Code Ruby. </p>