From: Jim Weirich Date: 2005-04-01T07:48:40+09:00 Subject: Re: rake help Mark Probert said: > 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, using the cxxtest tool. Hi Mark, Sorry it took me so long to respond to this. You probably have solved this already, but I'll post for the general education of all Rake Users everywhere (well, at least the ones on this list. > > cxxtest has its tests contained in a .h file. These are then parsed by > the tool to give your .cpp file. This is then complied and linked > with the object file. > > So, assuming I have my code in foo.cpp, my tests in foo_test.h, then the > sequence looks like: > > $ c++ -c -o foo.o foo.cpp > $ cxxtest.pl -o foo_test.cpp foo_test.h > $ c++ -o foo_test foo_test.cpp foo.o Ok, I'm going to work with this example rather than the Rakefile you posted below. There should be enough info to apply it to your rakefile. > So, there are two issues that I am having problems with. The first is > turning the .h into a .cpp. Not a problem ... You want to generate .cpp files from .h files with the same base name. The easiest way is to express this as an explicit file task: file "foo_test.cpp" => ["foo_test.h"] do sh %{cxxtest.pl -o foo_test.cpp foo_test.h} end The above says that file "foo_test.cpp" depends on a header file named "foo_test.h", and that to create the .cpp file, all you need to do is run the cxxtest.pl command with the given parameters. Using the same logic, we can come up with tasks for the other two files as well: file "foo.o" => ["foo.cpp"] do sh %{c++ -c -o foo.o foo.cpp} end file "foo_test" => ["foo_test.cpp", "foo.o"] do sh %{c++ -o foo_test foo_test.cpp foo.o} end Those three file tasks together in a rake file will build foo_test whenever you type "rake foo_test" at the command line. Supposedly you want to run the tests as well. Simply add task :unittest => ["foo_test"] do sh "foo_test" end And if you want that to be the default task, then add: task :default => [:unittest] Take all of the above together and you get a working Rakefile. Now lets fine tune it a bit. First of all, the Rakefile is overly restrictive. Suppose you added "bar_test.h" to the mix? You would have to add a task to build it and modify existing tasks to depend upon it. Fortunately, you are using Ruby, and you can create those tasks in a test... FileList['*_test.h'].each do |source| target = source.sub(/\.h$/, '.cpp') file target => [fn] do sh "cxxtest.pl -o #{target} #{source}" end end We are assuming that the test header files contain end in "_test.h". Modify according to your needs. If you have 20 test headers, the above code will create 20 explicit tasks for generating the test cpp files. This works great, however, there is another way ... rules. rule '.cpp' => '.h' do |t| sh "cxxtest.pl -o #{t.name} #{t.source}" end This rule says that whenever you need a .cpp file, and you have a .h file with the same name, here's how you would generate the .cpp file. It is similar to generating the tasks explicitly (as we did in the loop above), but instead of generating the tasks all at once it only does it on an as needed basis. In the same way, we can give a rule for compiling .cpp files into .o files. rule '.o' => '.cpp' do |t| sh "c++ -c -o #{t.name} #{t.source}" end I also like to create a bunch of file lists that describe the categories of files. Here's what might work for you ... CPP_SOURCE = FileList['*.cpp'].exclude('*_test.cpp') TEST_HEADERS = FileList['*_test.h'] Now I want to combine these lists into a single list of all the object files I will be dealing with ... OBJ = FileList[ CPP_SOURCE.sub(/\.cpp$/, '.o'), TEST_HEADERS.sub(/\.h$/, '.o'), ] That leaves our final compile rule to look like this ... file "foo_test" => OBJ do sh "c++ -o foo_test #{OBJ}" end I'll show the complete Rakefile at the end and include some simple clean/clobber rules. > The second is how to get the test to conditionally depend on foo.o. I > only want to create foo.o if it isn't there. [...] Not a problem. Rake will only regenerate a file in a file task when the file is out of date w.r.t. its dependencies. > Anyway, here is my rakefile, which isn't quite right. Giving: > > $ rake > c++ -o unittest unittest scanner.o > c++: unittest: No such file or directory > rake aborted! In your :unittest task, you are compiling and EXE, but give it t.name as the file to compile. Since the task name is :unittest, there is no file by that name. Here's my Rakefile ... ---------------------------------------------------- # -*- ruby -*- require 'rake/clean' CLEAN.include('*.o') CLOBBER.include('foo_test', 'foo_test.cpp') CPP_SOURCE = FileList['*.cpp'].exclude(/test.cpp$/) TEST_HEADERS = FileList['*_test.h'] OBJ = FileList[ CPP_SOURCE.sub(/\.cpp$/, '.o'), TEST_HEADERS.sub(/\.h$/, '.o'), ] task :default => :unittest task :unittest => "foo_test" do sh "foo_test" end file "foo_test" => OBJ do sh "c++ -o foo_test #{OBJ}" end rule '.o' => '.cpp' do |t| sh "c++ -c -o #{t.name} #{t.source}" end rule '.cpp' => '.h' do |t| sh "cxxtest.pl -o #{t.name} #{t.source}" end ---------------------------------------------------- -- -- Jim Weirich jim@weirichhouse.org http://onestepback.org ----------------------------------------------------------------- "Beware of bugs in the above code; I have only proved it correct, not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)