Rake::Builder
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
So, with configure, the user then does the following:
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'
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 testFifteen 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
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!