capri, a script language

Home | Documentation | Releases | Sources | Contact

How to write a simple build script for a C++ application

On this page you will learn how to write simple build script that builds a C++ application, and also only recompiles source files that have actually changed.

First, we want to specify that this script requires at least version 2.0 of the interpreter. The version statement makes the interpreter throw an error if its version is lower than the one specified:

version 2.0;

Now we want to import a few things. The compilation support script has functions that support buildscripts. The io library provides standard input/output.

import lang;
import io;
import compilation;

The project MyApplication is the context for our project. It is not necessary, but can be useful to encapsulate your build script in a project; like this you can build one project from within another one by using for example MyApplication.all.

project MyApplication {

We specify a few variables that we use in the build tasks. Here you can see the usage of the specialization statement; on Windows we want to create an "exe" file.

	sourceSuffixes = {".cpp", ".c"};
	headerSuffixes = {".hpp", ".h"};
	
	src = "src";
	obj = "obj";
	bin = "bin";
	
	CXX = "g++";
	CFLAGS = '-std=c++11 -I"{src}"';
	LD = "g++";
	LDFLAGS = '';
	
	artifact = "myprogram";
	on system windows: artifact = "{artifact}.exe";

	COMPILE_COMMAND	= '{CXX} {CFLAGS} -c "$file$" -o "{obj}/$objectName$.o"';
	LINK_COMMAND	= '{LD} -o {artifact} {obj}/*.o {LDFLAGS}';

The "all" task will call "compile" and "link" but itself do nothing:

	task all depends compile, link;

In the "compile" task, we first check if any of the headers has changed. If so, we want to perform a full rebuild. Then we use the Compilation.executeForChanged to compile each source file. Finally, we want to update the modify dates of all header files.

	task compile {
		def fullRebuild = Compilation.hasChanges(src, headerSuffixes);
		assert Compilation.executeForChanged(src, sourceSuffixes, COMPILE_COMMAND, fullRebuild);
		Compilation.rememberModifyDates(src, headerSuffixes);
	}

The "link" task simply links all objects to the binary artifact.

	task link {
		IO.println("linking {artifact}...");
		IO.println("\t{LINK_COMMAND}");
		return System.execute(LINK_COMMAND) == 0;
	}

Finally, we'll also want to have a "clean" task. It deletes the artifact, clears the modify date cache, and deletes all objects from the "obj" folder.

	task clean {
		IO.println("cleaning");
		File.deleteFile(artifact);
		File.clearModifyCache();
		File.cleanFolder(obj);
	}

}

Done! You can now run this script with capri all to compile your application.

Here is the full script.

version 2.0;
import lang;
import io;
import compilation;

/**
 *
 */
project MyApplication {

	sourceSuffixes = {".cpp", ".c"};
	headerSuffixes = {".hpp", ".h"};
	
	src = "src";
	obj = "obj";
	bin = "bin";
	
	CXX = "g++";
	CFLAGS = '-std=c++11 -I"{src}"';
	LD = "g++";
	LDFLAGS = '';
	
	artifact = "myprogram";
	on system windows: artifact = "{artifact}.exe";

	COMPILE_COMMAND = '{CXX} {CFLAGS} -c "$file$" -o "{obj}/$objectName$.o"';
	LINK_COMMAND = '{LD} -o {artifact} {obj}/*.o {LDFLAGS}';
	
	/**
	 *
	 */
	task compile {
		def fullRebuild = Compilation.hasChanges(src, headerSuffixes);
		assert Compilation.executeForChanged(src, sourceSuffixes, COMPILE_COMMAND, fullRebuild);
		Compilation.rememberModifyDates(src, headerSuffixes);
	}
		
	/**
	 *
	 */
	task link {
		IO.println("linking {artifact}...");
		IO.println("\t{LINK_COMMAND}");
		return System.execute(LINK_COMMAND) == 0;
	}
			
	/**
	 *
	 */
	task clean {
		IO.println("cleaning");
		File.deleteFile(artifact);
		File.clearModifyCache();
		File.cleanFolder(obj);
	}

}