Rake::Builder

Posted by Joe Yates Sat, 28 Aug 2010 23:42:00 GMT

I've put together a rake-based system for building C and C++ projects.

I've called it Rake::Builder, it's available on GitHub here.

autoconf

Build systems are complicated by external dependencies: finding third-party headers and libraries. Often there is a specific configuration for each operating system and distribution.

The standard system is GNU's autoconf system, but it is a nightmare. At the most simple level, you distribute source code with a Makefile.
The problem is that Makefiles are just pure dependency managers - if they don't find what they need, they just fail.
The solution to that problem is the use of configure. Configure runs a series of compatibility tests, sniffs the host system and spits out a Makefile.
So, with configure, the user then does the following:

$ ./configure
$ make
$ sudo make install

But, configure itself is a long file, and it's is a program: about 6000 lines of shell code.
A few lines:

ac_ext=cpp
ac_cpp='$CXXCPP $CPPFLAGS'
ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
The solution to having to maintain configure? Generate it!
With autotools, there is a seemingly eternal regress of configuration files and scripts generated by other scripts.
This was my project's root directory listing before I gave up on autoconf:
AUTHORS
COPYING
ChangeLog
EXAMPLE
INSTALL
Makefile
Makefile.am
Makefile.in
NEWS
README
aclocal.m4
autom4te.cache
config.h
config.h.in
config.log
config.status
configure
configure.ac
depcomp
include
install-sh
missing
objects
src
stamp-h1
test
Fifteen of those files are involved in the mechanics of autoconf.

Autoconf doesn't really address the issue of building on various systems and adapting semi-automatically. You still have to add all the settings, but you have to learn where in a myriad of files these settings are to be placed.

Rake

Of the autoconf alternatives, rake seems to offer the cleanest solution. Rake itself was apparently created as a build system for C, but out of the box it's too generic: it's simply a dependency handling system.

There are already a number of specializations of rake. In Rails projects, and Gem building, Rakefiles handle maintenance, testing, documentation building and packaging.
This is the Gem building part of Rake::Builder's own Rakefile:

spec = Gem::Specification.new do |s|
  s.name             = 'rake-builder'
  s.summary          = 'Rake for C/C++ Projects'
  s.description      = 'Provides Rake:Builder, a specific rake TaskLib for building C, C++, Objective-C and Objective-C++ projects'
  s.version          = Rake::Builder::VERSION::STRING

  s.homepage         = 'http://github.com/joeyates/rake-builder'
  s.author           = 'My Name'
  s.email            = 'my.email@xmail.com'

  s.files            = ADMIN_FILES +
                       SOURCE_FILES +
                       EXAMPLE_SOURCE_FILES +
                       EXAMPLE_EXTRA_FILES
  s.require_paths    = [ 'lib' ]
  s.add_dependency( 'rake', '>= 0.8.7' )

  s.has_rdoc         = true
  s.rdoc_options     += RDOC_OPTS
  s.extra_rdoc_files = RDOC_FILES

  s.test_files       = SPEC_FILES
end

Rake::Builder is my attempt to specialise rake to the task of building C and C++ projects.
I've got to version 0.0.11 and it now builds the following types of projects:

  • C++,
  • QT C++,
  • C
  • Objective-C

This is an example with two build targets: a static library and a test executable, with the test project depending on the library:

require 'rake/builder'

Rake::Builder.new do |builder|
  builder.target               = 'libactive_record_sqlite.a'
  builder.source_search_paths  = [ 'src' ]
  builder.header_search_paths  = [ 'include/**/*.h' ]
  builder.objects_path         = 'objects'
  builder.include_paths        = [ 'include' ]
  builder.library_dependencies = [ 'sqlite3' ]
end

Rake::Builder.new do |builder|
  builder.task_namespace       = :test
  builder.target               = 'active_record_test'
  builder.source_search_paths  = [ 'test' ]
  builder.header_search_paths  = [ 'test' ]
  builder.objects_path         = 'test/lib_objects'
  builder.include_paths        = [ 'include', 'test' ]
  builder.library_dependencies = [ 'sqlite3', 'gtest', 'gtest_main', 'active_record_sqlite' ]
  builder.library_paths        = [ 'objects' ]
  builder.target_prerequisites = [ :'rake:build' ]
  builder.default_task         = [ :run ]
end

I'm about to start using Rake::Builder on a sizable C++ project using QT, so I'll have a chance to see if it handles real world stuff without turning into autoconf.

googletest Hello World

Posted by Joe Yates Wed, 26 May 2010 13:17:00 GMT

This is a quick run down of how to get started with using googletest on Ubuntu.

Preparation

Assuming you have a working GCC build environment, all you have to do is install the googletest packages:

  $ sudo apt-get install libgtest0 libgtest-dev

Makefile

The only think of note about the Makefile is that it includes 'libgtest_main' - which implements main() and calls RUN_ALL_TESTS()

NAME = hello-world

LIBS = -lgtest_main

debug: all
run-debug:
	./${NAME}

all: $(NAME).o
	c++ -lstdc++ $(LIBS) -o $(NAME) $(NAME).o

compile: $(NAME).o

clean:
	find . -name '*.o' -exec rm -f {} ';'
	find . -name $(NAME) -exec rm -f {} ';'

$(NAME).o: $(NAME).c++
	gcc -c -I. -o $(NAME).o $(NAME).c++

.c++.o:
	gcc -c -I. -o $@ $<

Source

I've put everything into a single source file 'hello-world.c++' to keep things minimal:

/////////////////////////////
// In the header file

#include <sstream>
using namespace std;

class Salutation
{
public:
  static string greet(const string& name);
};

///////////////////////////////////////
// In the class implementation file

string Salutation::greet(const string& name) {
  ostringstream s;
  s << "Hello " << name << "!";
  return s.str();
}

///////////////////////////////////////////
// In the test file
#include <gtest/gtest.h>

TEST(SalutationTest, Static) {
  EXPECT_EQ(string("Hello World!"), Salutation::greet("World"));
}

Compilation

Just run:

  $ make

Output

This test produces the following:

$ ./hello-world
Running main() from gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from SalutationTest
[ RUN      ] SalutationTest.Static
[       OK ] SalutationTest.Static
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran.
[  PASSED  ] 1 test.

Conclusion

It couldn't really be much simpler!

Object Representation in C++

Posted by Joe Yates Wed, 12 May 2010 13:42:00 GMT

In this post I investigate the way that C++ objects' data and functions are laid out in memory by the two best known compilers: GCC and Microsoft's Visual C++.

A Rails Programmer's First Django Application 1

Posted by Joe Yates Sun, 04 Apr 2010 15:35:00 GMT

I wrote my first mini Django application - a local history timeline - to get an idea of the differences between Django and Rails.

Editing Javascript in Emacs

Posted by Joe Yates Fri, 02 Apr 2010 10:06:00 GMT

Steve Yegge of Google has open-sourced his Javascript mode for Emacs. The main features are: true program parsing, code highlighting and error highlighting.

A Sunrise and Sunset Time Calculator

Posted by Joe Yates Tue, 09 Mar 2010 09:14:00 GMT

I've created a new Ruby library that calculates sunrise and sunset times.

i18n Internationalization without Rails

Posted by Joe Yates Thu, 04 Mar 2010 23:30:00 GMT

I was unable to find examples of using the Ruby internationalization gem (i18n) outside of Rails apps. In this post I give a minimal "Ciao Mondo!" example.

Handling Multiple Versions of Ruby

Posted by Joe Yates Mon, 22 Feb 2010 23:04:00 GMT

Use Debian Alternatives to switch between Ruby versions (1.8, 1.9 and JRuby).

JRuby on Rails

Posted by Joe Yates Mon, 22 Feb 2010 12:06:00 GMT

How to set up JRuby and adapt a Ruby on Rails application to run on top of it.

Trying out Clojure

Posted by Joe Yates Tue, 09 Feb 2010 13:58:00 GMT

Clojure is a very promising new language that runs on the JVM. It handles concurrency in effective end innovative ways. I've written a mini-guide on how to get started.