Dieser und noch weitere Artikel wurde von Artchi erstellt.


Folgende Themen werden von diesem Artikel berührt:


Druckversion des Artikels


Boost.Build v2

In diesem Artikel geht es um das C++-Build-System von Boost

1 Einleitung
2 Ein guter Grund
3 Architektur
4 Grundlagen
4.1 Installation
4.2 Unser erstes Script
4.3 Die bjam-Optionen
5 Bibliotheken bauen
6 Targets
7 Komplexe Projekte
8 Schlusswort
A Links

1 Einleitung

Eine typische Situation als C++-Programmierer: Ich finde im Web eine interessante Bibliothek, sie kann mir weiterhelfen, mich unterstützen, mein aktuelles Projekt könnte damit in der Entwicklung um einiges schneller voranschreiten. Ich lade mir diese also herunter, entpacke sie und lese erstmal die Dokumentation der Bibliothek. Wie baue ich sie für meine Plattform? Wie baue ich sie für meinen Compiler? Wie baue ich Debug-Varianten? Welche Include-Pfade muss ich in meiner IDE setzen? Welche Lib-Dateien muss ich verlinken? Wenn ich diese Infos in der Dokumentation finde, haben die Bibliotheksentwickler gute Arbeit geleistet. Noch besser ist es, wenn die Entwickler ein Build-Script oder Projektdatei bereitstellen, das ich mit einer Kommandozeile oder einem Mausklick in meiner IDE ausführen kann. Nach ein paar Minuten kann ich die Bibliothek für mein Projekt nutzen.

Diese Szenarios sind leider meistens Utopie, selbst bei renommierten Open-Source-Bibliotheken. Ja, einige sind sogar auf bestimmte Compiler beschränkt, was die Dokumentation betrifft. Ob es ideologische Gründe sind? Oder fehlende Resourcen in Form von Manpower? Als interessierter Anwender eines nicht unterstützten Compilers ist man bei komplexen Bibliotheken jedenfalls verloren.


2 Ein guter Grund

Diese Tatsache sollte einem doch zu denken geben! Genau das hat sich wohl auch die Mannschaft hinter den Boost-C++-Librarys gedacht. Denn Boost ist eine Sammlung von Bibliotheken für verschiedene Plattformen (Windows, Linux, BSD, Solaris u.a.) und wiederum verschiedenen Compilern (MS VC, GCC, Intel Compiler u.a.). Jede Boost-Bibliothek muss die Anforderung erfüllen, auf mind. zwei verschiedenen Compilern zu arbeiten, um in Boost aufgenommen zu werden. Den Aufwand für Dokumentation und Scriptpflege für so viele Bibliotheken kann man sich vorstellen...

Hier greift das vom Boost-Team entwickelte Boost.Build ein. Jeder der schon mal Boost benutzt hat (was ich nur empfehlen kann) kennt den Build-Vorgang: Boost-Archiv herunterladen, entpacken, Kommandofenster öffnen, in das Boost-Verzeichnis wechseln, eine bestimmte, aber einfache Kommandozeile eingeben... fertig! Ja, und das funktioniert mit allen Boost-Bibliotheken, auf allen unterstützten Compilern, unter allen unterstützten Plattformen. Ohne dass der Anwender für jede Compiler- und Plattform-Kombination in eine Dokumentation schauen muss. Sollte das nicht mit allen C++-Bibliotheken und allen C++-Projekten der Fall sein?

Lange Rede, kurzer Sinn: Boost.Build ist ein Tool für C und C++, um endlich die Komplexität von Build-Vorgängen zu erleichtern.


3 Architektur

Zurzeit (März 2006) wird Boost.Build Version 2 (kurz BBv2) entwickelt, und diese Version werde ich hier behandeln. Das hat den Grund, dass es ggü. Version 1 signifikante Änderungen gibt, die auch den Anwender betreffen. So müssen Leser später nicht umlernen. Weiterhin befindet sich BBv2 in einem sehr weiten Stadium und das Release rückt immer näher.

BBv2 ist ein Frontend und baut auf das Backend bjam (Boost Jam) auf. bjam wiederum ist aus dem Build-Tool jam von der Firma Perforce entstanden. Für uns ist nur wichtig zu wissen, das bjam und Boost.Build zusammengehören. Wir selbst werden aber nur mit BBv2 direkt in Kontakt kommen, da bjam zur Laufzeit auf BBv2 zugreift.

Folgende Compiler werden zurzeit unterstützt:

Da bjam in C implementiert wird und auch als Sourcecode vorhanden ist, können Sie es auch für exotische Plattformen kompilieren und anpassen.


4 Grundlagen

4.1 Installation

Das Nightly build Package boost-build herunterladen:

http://boost.org/boost-build2/boost-build.zip
http://boost.org/boost-build2/boost-build.tar.bz2

Entpacken, nach boost-build/jam_src wechseln und das bjam bauen lassen, indem man build.bat bzw. build.sh aufruft. Windows-Benutzer müssen vorher ihre Umgebungsvariablen des Compilers setzen. Bei VC++ am besten über Start->Programme->MS VC++->VS Tools->VS Command Prompt.

Je nach Plattform wird man das bjam-Binary in einem eigens neu erstellten Unterverzeichnis finden. Um bjam einfach nutzen zu können, gibt es zwei Möglichkeiten:
1. Das bjam-Binary sollte man dorthin kopieren, wo es ohne Pfadangabe gefunden wird.
2. Oder man fügt das bjam-Verzeichnis der PATH-Variable hinzu. Unter Windows wie folgt:
Code:
set PATH=C:\boost-build\jam_src\bin.ntx86\;%PATH%

Auch BBv2 muss gefunden werden; dazu gibt es zwei Möglichkeiten:
1. Sie richten eine Umgebungsvariable BOOST_BUILD_PATH ein, welche auf boost-build zeigt. Unter Windows wie folgt:
Code:
set BOOST_BUILD_PATH=C:\boost-build\

2. Oder Sie legen in jedes Ihrer Projekte eine Datei namens boost-build.jam an, in der drinsteht, wo sich das Verzeichnis boost-build befindet. Ein Beispiel dieser Datei findet man im selbigen Verzeichnis.

Das war es auch schon mit der Installation. Damit bjam in Zukunft aber immer bauen kann, müssen immer die Umgebungsvariablen des Compilers bekannt sein. Das setzen der Pfade sollten Sie im eigenen Interesse in eine Batch-Datei schreiben.


4.2 Unser erstes Script

Wollen wir nun langsam zur Praxis schreiten. Wir wollen eine eigene Anwendung von BBv2 bauen (kompilieren und linken) lassen.

Im Gegensatz zu make u.a. Build-Tools muss man als Script-Schreiber die Kommandos von Compilern nicht kennen. Für uns ist die Benutzung völlig transparent und wir stellen uns ganz unwissend. Denn unser Ziel ist es ja, mit einer einzigen Script-Version so viele Compiler und Plattformen wie möglich zu unterstützen. Da wäre es selbstverständlich destruktiv, wenn wir z.B. spezielle GCC-Parameter benutzen.

Am besten schreiben wir ein "Hello World!"-Projekt. Wir brauchen eine hello.cpp-Datei, die kompiliert werden soll:
C++:
#include <iostream>
 
int main() {
  std::cout << "Hello bjam!" << std::endl;
}


Ein dazugehöriges BBv2 Projekt-Script würde wie folgt aussehen:
Code:
exe hello : hello.cpp ;

Dieses Script speichern Sie unter dem Dateinamen Jamroot in Ihr Quellverzeichnis. Die Verzeichnisstruktur kann dabei so aussehen:
Code:
top/
  |
  +- Jamroot
  +- hello.cpp

bjam schaut in die Jamroot-Datei rein und weiß damit, dass eine Executable (exe) mit dem Namen hello erstellt werden soll. Als Sourcedateien muss es dafür hello.cpp kompilieren. Wenn Sie mehrere Sourcedateien in einer Anwendung haben, was ja eher der Fall ist, können Sie natürlich mehrere Dateien angeben:
Code:
exe hello : hello.cpp hello2.cpp hello3.cpp ;

Wollen Sie alle Dateien eines Verzeichnisses angeben, ist dies mit dem Stern-Wildcard möglich:
Code:
exe hello : [ glob *.cpp ] ;

Auch für den Anwender des Scripts ist alles transparent. Der Anwender muss BBv2 lediglich über ein so genanntes Property (Eigenschaft) mitteilen, welchen Compiler er verwendet. Haben Sie einen MS VisualC++ Compiler, starten Sie den Buildprozess wie folgt:
Code:
bjam toolset=msvc

Dieses Kommando müssen Sie in dem Verzeichnis aufrufen, in dem sich das Jamroot befindet, also im Quellverzeichnis.

Sollten Sie einen GCC-Compiler nutzen, ist dieses Kommando nötig:
Code:
bjam toolset=gcc

Ist Ihnen die Kommandozeile zu lang oder unflexibel, können Sie Ihren gewünschten Compiler auch in der Konfigurationsdatei user-config.jam von BBv2 fest einstellen (was ich sehr empfehlen kann). Dann reicht nur noch der Aufruf von:
Code:
bjam

Der Buildprozess gibt bei erfolgreichem Durchlauf folgende Meldungen aus (beim MSVC):
Code:
1
2
3
4
5
6
7
8
...found 11 targets...
...updating 7 targets...
MkDir1 bin
MkDir1 bin\msvc
MkDir1 bin\msvc\debug
msvc.compile.c++ bin\msvc\debug\hello.obj
hello.cpp
msvc.link bin\msvc\debug\hello.exe

Sie sehen, die Anwendung wurde im Verzeichnis bin\msvc\debug erstellt. Sollten Sie z.B. zwei Compiler haben und für beide auch einen Build laufen lassen wollen, würde es keine Konflikte oder Überschreibungen geben. Ohne jegliche Property wurde auch "nur" eine debug-Variante erstellt. Sie können auch explizit eine Release-Variante erstellen:
Code:
bjam variant=release

Aber auch beide Varianten sind in einem Vorgang möglich:
Code:
bjam variant=debug variant=release

Es ist also sehr einfach und intuitiv zu handhaben.

Übrigens, bjam rufen wir bisher nach folgendem Muster auf:
Code:
bjam [i]property-key=property-value[/i]

Doch bjam kann auch einfach nur mit Property-Values arbeiten, hier ein paar Beispiele:
Code:
bjam release
bjam msvc release
bjam release debug

Ich werde im restlichen Artikel auf das komplette Muster verzichten. Die Property-Keys sind in der BBv2-Dokumentation zu finden, sollte man diese benötigen.


4.3 Die bjam Optionen

Neben den Propertys kann bjam auch mit Optionen aufgerufen werden, die zur Folge haben, dass diese vor dem Build-Prozess etwas auswirken oder einfach nur Informationen anzeigen.

Die Optionen haben folgendes Muster:
Code:
bjam [i]--option[/i]


Die wichtigsten Optionen sollten Sie kennen:
--version zeigt die bjam- und BBv2-Version an
--clean löscht alle automatisch erzeugten Dateien vom Typ .o, .obj, .lib, .exe usw.
--help zeigt weitere Optionen an

Manchmal ist es ratsam, vor einem Build noch mal eine Bereinigung zu starten. Bei größeren Projekten sollte man das natürlich aus Zeitgründen (bzgl. des kompletten Buildvorgangs) mit Bedacht wählen. Aber es ist manchmal leider nötig. Auch in diesem Tutorial, wo wir immer wieder die gleichen Scripts aufrufen, sind Bereinigungen empfehlenswert. Schließlich wollen Sie ja die Auswirkungen sehen! Die Clean-Option lässt sich selbstverständlich auch mit Propertys kombinieren. Hier ein paar Beispiele:
Code:
bjam --clean
bjam --clean release

Letztere Variante bereinigt explizit die Release-Ordner, da auch bei clean von Haus aus nur Debug beachtet wird.


5 Bibliotheken bauen

Neben den Anwendungsprojekten erstellen Sie sicherlich auch Bibliotheken, um Ihre Projekte modularer und somit flexibler zu halten. Mit dem bisherigen Wissen ist dies jedoch nicht zu realisieren. Natürlich hat auch BBv2 hier eine Lösung im Angebot. Erstellen wir uns also eine Minibibliothek in einem neuen Quellverzeichnis:
C++:
// myLib.hpp
void foo();

C++:
// myLib.cpp

#ifdef _WIN32

__declspec(dllexport)
#endif
void foo() {
};

Unsere Bibliothek hat somit eine Funktion foo(), die wir später auch von unserem HelloWorld aufrufen und verlinken lassen wollen. Aber zuerst unser Jamroot-Script:
Code:
lib myLib : myLib.cpp ;

Sie sehen es sicherlich selbst, es hat sich ggü. einer Anwendung nicht viel geändert. Anstatt EXE steht dort jedoch LIB für Library. Gebaut wird die Bibliothek wieder durch Aufruf von bjam in der Konsole, und wir sehen, wie unsere Bibliothek gebaut wird:
Code:
1
2
3
4
5
6
7
8
9
10
...found 11 targets...
...updating 8 targets...
MkDir1 bin
MkDir1 bin\msvc
MkDir1 bin\msvc\debug
msvc.compile.c++ bin\msvc\debug\lib1.obj
lib1.cpp
msvc.link.dll bin\msvc\debug\myLib.dll bin\msvc\debug\myLib.lib
   Bibliothek 'bin\msvc\debug\myLib.lib' und Objekt 'bin\msvc\debug\myLib.exp' wird erstellt
...updated 8 targets...

BBv2 nimmt einem hier sehr viel Arbeit ab und das Wissen über ein Compiler-System ist unnötig. Besonders Einsteigern ohne IDE kann hier der C++-Einstieg um einiges erleichtert werden.


6 Targets

Sie werden es gemerkt haben, wir haben für unsere Anwendung und Bibliothek unterschiedliche Quellen - im Build-Fachjargon Targets (Ziele) genannt. Das gute ist, wir haben alles getrennt und somit modular. Doch ist es lästig, immer zwei Scripts separat anzusteuern, denn oft wird an mehreren Fronten gleichzeitig entwickelt. BBv2 hat deshalb eine Projektverwaltung, die mit Targets arbeitet, die für komplexere Projekte unerlässlich sind. Ich will den Effekt der Targets erstmal ohne komplexes BBv2-Projekt zeigen, um den Einstieg zu vereinfachen.

Bisher haben wir zwei unterschiedliche Quellverzeichnisse, wo unser hello-Anwendungsprojekt und myLib-Bibliotheksprojekt liegt. hello und myLib sind dabei jeweils durch einen Target-Namen gekennzeichnet, im Jamroot fett dargestellt:
Code:
exe [b]hello[/b] : hello.cpp ;

Targets sind da, um gezielt angesteuert bzw. ausgewählt werden zu können. Deshalb können wir auch bedenkenlos das hier in einem Jamroot-Script machen:
Code:
exe hello : hello.cpp ;
lib myLib : myLib.cpp ;

Natürlich müssen Sie auch alle nötigen Sourcedateien in das Quellverzeichnis kopieren, damit diese gebaut werden können.
Code:
top/
 |
 +- Jamroot
 +- hello.cpp
 +- myLib.cpp
 +- myLib.hpp

Wenn Sie jetzt bjam in der Konsole aufrufen, werden implizit alle Targets gebaut. Die Reihenfolge ist dabei nicht festgelegt, das entscheidet bjam. Wollen Sie jedoch z.B. nur hello bauen lassen, ist dies möglich:
Code:
bjam hello

Wollen Sie hello als release bauen, ist wie immer eine Kombination von Propertys möglich:
Code:
bjam hello release

Mit dem Wissen über Targets können wir nun zu den Projekten übergehen.


7 Komplexe Projekte

Bisher hatten wir ein Verzeichnis pro Projekt, in dem ein oder mehrere Targets waren. Das ist leider sehr unübersichtlich oder auch unflexibel. Entweder müssen wir jedes Projekt separat behandeln, was in vielen Arbeitsschritten ausartet. Oder wir haben alle in einem Verzeichnis und Script, was unübersichtlich und überladen ist. Optimal ist es, wenn man eine Projektmappe hat. Jedes Projekt in einem eigenen Unterverzeichnis, durch eigene Targets identifizierbar, und doch kann alles mit einem Aufruf gebaut werden. Deshalb hier ein Beispiel, wie die Verzeichnisstruktur aussehen kann:
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
top/
 |
 +- Jamroot
 |
 +- hello/
 |  |
 |  +- Jamfile
 |  +- hello.cpp
 |
 +- libs/
    |
    +- lib1/
    .   |
    .   +- Jamfile
    .   +- myLib.cpp
    .   +- myLib.hpp

Ein Jamroot haben wir immer noch, da es die Wurzel für alle Projekte ist. Wir haben jedoch die Targets hello und myLib unterhalb von Jamroot in eigene Unterverzeichnisse (Unterprojekte) verlegt. In diesen ist kein Jamroot-Script, sondern ein Jamfile genanntes Script. Da Jamroot die Wurzel ist, erben alle Unterprojekte (also Jamfiles) automatisch die Anforderungen und Eigenschaften von Jamroot.

Soll Ihre Projektmappe Abhängigkeiten (dependencies) zu externen Bibliotheken haben, können Sie diese in Jamroot definieren. Automatisch haben hello und myLib auch diese Abhängigkeit. So erspart man sich viel Arbeit, diese in jedem Jamfile zu definieren. Hat z.B. nur myLib eine Abhängigkeit, kann man diese natürlich in dem einen speziellen Jamfile definieren. Alle anderen Projekte wären dann davon unbetroffen.

In unserer gesamten Projektmappe ist unsere Anwendung "hello" das Kernprojekt. Wenn wir also bjam aufrufen, soll am Ende unser hello.exe zusammengebaut werden. Dafür schreiben Sie in Jamroot:
Code:
build-project hello ;

Durch einen bjam Aufruf wird hello gebaut. Sollte aber hello.cpp einen Funktionsaufruf foo() aus myLib haben, wie hier:
C++:
#include "myLib.hpp"
 
int main()
{
    foo();
}

stehen wir vor einem Compile- und Linker-Problem.

Wir wollen also, dass hello eine Abhängigkeit zu myLib hat. Wie definieren wir aber eine Abhängigkeit? In Ihrer top/hello/Jamfile schreiben Sie Folgendes:
Code:
exe hello : hello.cpp ../libs/lib1//myLib ;

Das ../libs/lib1 ist eine ganz normale relative Pfadangabe, die auf das lib1-Projekt zeigt. Danach sind die doppelten Schrägstriche zu beachten! Diese leiten den Namen des Targets ein, der in dem Projekt definiert ist. Denn es könnten ja auch mehrere Targets drinstehen, deshalb muss das gewünschte genannt werden. In unserem Fall ist das Dependency-Target "myLib".

Eine Sache fehlt noch. Die Header-Datei unserer myLib wird so nicht ohne weiteres gefunden werden. Sie müssen dem Projekt eine Includes-Anforderung mitgeben:
Code:
[b]project : usage-requirements <include>. ;[/b]
lib myLib : myLib.cpp ;

Die Nutzungsanforderung für includes gibt an, wo die Header-Dateien zu diesem Projekt zu finden sind. Durch den Punkt somit im aktuellen Verzeichnis. Sie könnten auch die Header-Dateien in ein Unterverzeichnis ablegen und den relativen Pfad angeben. Die Projekt-Anforderungen sind übrigens für alle Targets in diesem Projekt gültig.

Wenn Sie jetzt bjam im Jamroot-Verzeichnis ausführen, werden Sie feststellen, dass nicht nur hello gebaut wird, sondern auch myLib.

Die Sache funktioniert, hat aber einen Nachteil: Wenn irgendwann lib1 in ein anderes Verzeichnis verlegt wird, müssen alle Jamfiles angepasst werden, die vom Target myLib abhängig sind. Hier kommt Jamroot ins Spiel, in dem wir Projekt-IDs definieren. Da alle Projekte bzw. Jamfiles die Eigenschaften von Jamroot erben, können diese die Projekt-IDs anstatt Pfadangaben benutzen. Als Beispiel schreiben wir in Jamroot:
Code:
build-project hello ;
[b]use-project /my-lib-id/lib1 : libs/lib1 ;[/b]

Hinter use-project steht die Projekt-ID und nach dem Doppelpunkt der Pfad, in dem sich das Projekt mit den Targets befindet. Nun können Sie in hellos Jamfile schreiben:
Code:
exe hello : hello.cpp /my-lib-id/lib1//myLib ;

/my-lib-id/lib1 ist die ID des Projektes, nach den doppelten Schrägstrichen folgt wie gewohnt das gewünschte Target. In Zukunft muss nur noch Jamroot angepasst werden, sollte sich an der Verzeichnisstruktur etwas ändern.

8 Schlusswort

Mit diesem bisherigen Wissen können Sie schon mal Ihre eigenen Projekte sehr einfach und komfortabel bauen. Meiner Meinung nach ist BBv2 eines der einfachsten und doch leistungsfähigsten Buildtools. Natürlich konnte dieser Artikel nur die Grundlagen vermitteln. BBv2 bietet noch viel mehr Möglichkeiten, z.B. externe Bibliotheken oder fertige Bibliotheken, ohne vorhandenen Sourcecode mit einzubeziehen. Auch können in den Jamfiles Ausnahmen für spezielle Compiliervarianten gebildet werden, z.B. können für Debug-Builds ganz andere Bibliotheken verlinkt werden als in einem Release-Build. Auch können Optimierungsoptionen für bestimmte Targets ein- und ausgeschaltet werden. Die Möglichkeiten sind schlicht in diesem Artikel nicht aufzählbar, ich will Sie auf die bereits sehr gute BBv2-Dokumentation hinweisen.

Noch ein paar Punkte, die Aufgaben neben dem reinen Build sind und trotzdem von BBv2 unterstützt werden:

BBv2 ist ein Build-System, das sich intuitiv anwenden lässt und sich hoffentlich in der C++-Community verbreiten wird. Viel Spaß mit BBv2.


A Links
Boost.Build v2 Homepage
Boost.Build v2 Wiki
Boost C++ Libraries Homepage

Sie können Kommentare zu diesem Artikel im Forum schreiben. (Eine Registrierung ist nicht notwendig.)

Logo-Design: MastaMind Webdesign