[X] Boost.Build v2
-
[quote="GPC"]
Artchi schrieb:
Ich hab n OpenBook rumliegen, welches die autotools vorzüglich erklärt, halte nen Artikel drüber für überflüssig.
Dann verlink es bitte irgendwo in euren Artikeln, damit es auch andere finden können, die sich zu build-tools informieren wollen.
-
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 Links1 Einleitung
Eine typische Situation als C++ Programmierer: ich finde im Web eine interessante Bibliothek, sie kann mir weiter helfen, mich unterstützen, mein aktuelles Projekt könnte damit in der Entwicklung um einiges schneller voran schreiten. Ich lade mir diese also herrunter, entpacke sie und lese erstmal die Dokumentation der Bibliothek. Wie baue ich sie für meine Platform? Wie baue ich sie für meinen Compiler? Wie baue ich Debug-Versionen? Welche Include-Pfade muß ich in meiner IDE setzen? Welche Lib-Dateien muß ich verlinken? Wenn ich diese Infos in der Dokumentation finde, haben die Bibliotheks-Entwickler gute Arbeit geleistet. Noch besser ist es, wenn die Entwickler ein Build-Script oder Projektdatei bereit stellen, 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 renomierten Open Source Bibliotheken. Ja, einige sind sogar auf bestimmte Compiler beschränkt, was die Dokumentation betrifft. Ob es idiologische 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++ Libraries gedacht. Denn Boost ist eine Sammlung von Bibliotheken für verschiedene Platformen (Windows, Linux, BSD, Solaris u.a.) und wiederrum verschiedene Compiler (MS VC, GCC, Intel Compiler u.a.). Jede Bibliothek muß 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 soviele 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 herunter laden, 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 Platformen. Ohne das der Anwender für jede Compiler- und Platform-Kombination in eine Dokumentation schauen muß. 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 erleichern.
3 Architektur
Zur Zeit (März 2006) wird Boost.Build Version 2 (kurz BBv2) entwickelt und diese Version werde ich hier behandeln. Das hat den Grund, das 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 wiederrum ist aus dem Build Tool jam von der Firma Perforce entstanden. Für uns ist nur wichtig zu wissen, das bjam und Boost.Build zusammen gehören. Wir selbst werden aber nur mit BBv2 direkt in Kontakt kommen, da bjam zur Laufzeit auf BBv2 zugreift.
Folgende Compiler werden zur Zeit unterstützt:
- GNU gcc (various versions), on Linux, NT, Cygwin and Darwin.
- Microsoft VC++ compiler (various version), on NT and Cygwin.
- Borland's compiler, on NT and Cygwin.
- Metrowerks Code Warrior, on NT.
- Intel C++ Compiler, on Linux and NT.
- Sun's C++ Compiler.Da bjam in C implementiert wird und auch als Sourcecode vorhanden ist, können Sie es auch für exotische Platformen kompilieren und anpassen.
4 Grundlagen
4.1 Installation
Das Package boost-build herunter laden:
http://www.kharchi.de/files/cpp/boost-build.zip
Entpacken, nach boost-build/jam_src wechseln und das bjam bauen lassen in dem 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 Platform wird man das bjam-Binary in einem eigenst neu erstellten Unterverzeichnis finden. Um bjam einfach nutzen zu können, gibt es zwei Möglichkeiten:
1. Das bjam-Binary sollte man dort hin kopieren, wo es ohne Pfadangabe gefunden wird.
2. Oder man fügt das bjam-Verzeichnis der PATH-Variable hinzu. Unter Windows wie folgt:set PATH=C:\boost-build\jam_src\bin.ntx86;%PATH%
BBv2 muß auch gefunden werden, es gibt zwei Möglichkeiten:
1. Sie richten eine Umgebungsvariable BOOST_BUILD_PATH welches auf boost-build zeigt ein. Unter Windows wie folgt:set BOOST_BUILD_PATH=C:\boost-build\
2. Sie legen in jedes Ihrer Projekte eine Datei namens boost-build.jam an, in der drin steht, wo sich das Verzeichnis boost-build befindet. Ein Beispiel dieser Datei findet man in 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 (Kommpilieren und Linken) lassen.
Im Gegensatz zu make u.a. Build Tools muß 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 soviele Compiler und Platformen wie möglich zu unterstützen. Da wäre es selbstverständlich kontraproduktiv, wenn wir z.B. spezielle GCC-Parameter benutzen.
Am besten wir schreiben ein "Hello World!" Projekt. Wir brauchen eine hello.cpp Datei, die kompiliert werden soll:
#include <iostream> int main() { std::cout << "Hello bjam!" << std::endl; }
Ein dazugehöriges BBv2 Projekt-Script würde wie folgt aussehen:
exe hello : hello.cpp ;
Dieses Script speichern Sie unter dem Dateinamen Jamroot in ihr Quellverzeichnis. Die Verzeichnisstruktur kann dabei so aussehen:
top/ | +- Jamroot +- hello.cpp
bjam schaut in die Jamroot Datei rein und weiß damit, das eine Executable (exe) mit dem Namen hello erstellt werden soll. Als Sourcedateien muß 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:
exe hello : hello.cpp hello2.cpp hello3.cpp ;
Auch für den Anwender des Scripts ist alles transparent. Der Anwender muß BBv2 lädiglich über ein sogenanntes Property (Eigenschaft) mitteilen, welchen Compiler er verwendet. Haben Sie einen MS VisualC++ Compiler, starten Sie den Buildprozess wie folgt:
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:
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:
bjam
Der Buildprozess gibt bei erfolgreichem Durchlauf folgende Meldungen aus (beim MSVC):
...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-Version erstellt. Sie können auch explizit eine release-Version erstellen:
bjam variant=release
Aber auch beide Versionen sind in einem Vorgang möglich:
bjam variant=debug variant=release
Es ist also sehr einfach und intuitiv zu handhaben.
Übrigens, bjam rufen wir bisher nach folgendem Muster auf:
bjam [i]property-key=property-value[/i]
Doch bjam kann auch einfach nur mit Property Values arbeiten, hier ein paar Beispiele:
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 Properties kann bjam auch mit Optionen aufgerufen werden, die zur Folge haben, das diese vor dem Build-Prozess etwas auswirken oder einfach nur Informationen anzeigen.
Die Optionen haben folgendes Muster:
bjam [i]--option[/i]
Die wichtigsten Optionen sollten Sie kennen:
--version zeigt die bjam und BBv2 Version an
--clean löscht alle autom. erzeugten Dateien vom Typ .o, .obj, .lib, .exe usw.
--help zeigt weitere Optionen anManchmal ist es ratsam vor einem Build nochmal 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. Schliesslich wollen Sie ja die Auswirkungen sehen? Die Clean-Option lässt sich selbstverständlich auch mit Properties kombinieren. Hier ein paar Beispiele:
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 realisierbar. Natürlich hat auch BBv2 hier eine Lösung im Angebot. Erstellen wir uns also eine Mini-Bibliothek in einem neuen Quellverzeichnis:
// myLib.hpp int foo();
// myLib.cpp #include "myLib.hpp" #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 \1:
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:
...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:
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:
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.
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:
bjam hello
Wollen Sie hello als release bauen, ist wie immer eine Kombination von Properties möglich:
bjam hello release
Mit dem Wissen über Targets können wir nun zu den Projekten übergehen.
7 Komplexe Projekte
Bisher hatten wir ein Verzeichnis in dem entweder ein Projekt oder mehrere Projekte waren. Das ist leider sehr unübersichtlich und 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 ein eigenes Target identifizierbar und doch kann alles mit einem Aufruf gebaut werden. Deshalb hier ein Beispiel, wie die Verzeichnisstruktur aussehen kann:
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) autom. 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 unserem gesamten Projekt ist unsere Anwendung "hello" das Kernprojekt. Wenn wir also bjam aufrufen, soll am Ende unser hello.exe zusammen gebaut werden. Dafür schreiben Sie in Jamroot:
build-project hello ;
Durch einen bjam Aufruf wird hello gebaut. Sollte aber hello.cpp einen Funktionsaufruf foo() aus myLib haben, wie hier:
#include "myLib.hpp" int main() { foo(); }
stehen wir vor einem Compile- und Linker-Problem.
Wir wollen also das hello eine Abhängigkeit zu myLib hat. Wie definieren wir aber eine Abhängigkeit? In Ihrer top/hello/Jamfile schreiben Sie folgendes:
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 drin stehen, deshalb muß das gewünschte genannt werden. In unserem Fall ist das Dependency Target "myLib".
Wenn Sie jetzt bjam im Jamroot Verzeichnis ausführen, werden Sie fesstellen, das nicht nur hello gebaut wird sondern auch abhängige 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:
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:
exe hello : hello.cpp /my-lib-id/lib1//myLib ;
/my-lib-id/lib1 ist die ID des Projektes, nach den doppelten Schrägstrichen wie gewohnt das gewünschte Target. In Zukunft muß nur noch Jamroot angepasst werden, sollte sich an der Verzeichnisstruktur was ändern.
8 Schlusswort
Mit diesem bisherigen Wissen, können Sie schon mal Ihre eigenen Projekte sehr einfach und komfortabel builden. Meiner Meinung nach ist BBv2 eines der einfachsten und doch leistungsfähigsten Buildtools. Natürlich konnte dieser Artikel nur die Grundlagen vermitteln. BBv2 hat 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.
Ich will jedoch Stichwortartig ein paar Dinge nennen, die Aufgaben neben dem reinen Build sind und trotzdem von BBv2 unterstützt werden:
- BBv2 unterstützt direkt Unit-Tests
- BBv2 kann Dokumentation mittels Boost.Book oder Doxygen aus Ihren Sourcen erzeugen
- Sie können BBv2 um eigene Funktionalitäten erweitern
- aus einem CVS Repository den aktuellen Entwicklungsstand auschecken und diesen für den Build benutzen
- und einige andere Dinge mehrBBv2 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 C++ Libraries Homepage
-
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 Links1 Einleitung
Eine typische Situation als C++-Programmierer: ich finde im Web eine interessante Bibliothek, sie kann mir weiter helfen, 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-Versionen? 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 Bibliotheks-Entwickler 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++-Libraries gedacht. Denn Boost ist eine Sammlung von Bibliotheken für verschiedene Plattformen (Windows, Linux, BSD, Solaris u.a.) und wiederum verschiedene Compiler (MS VC, GCC, Intel Compiler u.a.). Jede 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 herunter laden, 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
Zur Zeit (März 2006) wird Boost.Build Version 2 (kurz BBv2) entwickelt und diese Version werde ich hier behandeln. Das hat den Grund, das 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 zusammen gehören. Wir selbst werden aber nur mit BBv2 direkt in Kontakt kommen, da bjam zur Laufzeit auf BBv2 zugreift.
Folgende Compiler werden zur Zeit unterstützt:
- GNU gcc (various versions), on Linux, NT, Cygwin and Darwin.
- Microsoft VC++ compiler (various version), on NT and Cygwin.
- Borland's compiler, on NT and Cygwin.
- Metrowerks Code Warrior, on NT.
- Intel C++ Compiler, on Linux and NT.
- Sun's C++ Compiler.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 Package boost-build herunter laden:
http://www.kharchi.de/files/cpp/boost-build.zip
Entpacken, nach boost-build/jam_src wechseln und das bjam bauen lassen in dem 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 dort hin kopieren, wo es ohne Pfadangabe gefunden wird.
2. Oder man fügt das bjam-Verzeichnis der PATH-Variable hinzu. Unter Windows wie folgt:set PATH=C:\boost-build\jam_src\bin.ntx86;%PATH%
BBv2 muss auch gefunden werden, es gibt zwei Möglichkeiten:
1. Sie richten eine Umgebungsvariable BOOST_BUILD_PATH welches auf boost-build zeigt ein. Unter Windows wie folgt:set BOOST_BUILD_PATH=C:\boost-build\
2. Sie legen in jedes Ihrer Projekte eine Datei namens boost-build.jam an, in der drin steht, 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 kontraproduktiv, 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:
#include <iostream> int main() { std::cout << "Hello bjam!" << std::endl; }
Ein dazugehöriges BBv2 Projekt-Script würde wie folgt aussehen:
exe hello : hello.cpp ;
Dieses Script speichern Sie unter dem Dateinamen Jamroot in ihr Quellverzeichnis. Die Verzeichnisstruktur kann dabei so aussehen:
top/ | +- Jamroot +- hello.cpp
bjam schaut in die Jamroot Datei rein und weiß damit, das 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:
exe hello : hello.cpp hello2.cpp hello3.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:
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:
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:
bjam
Der Buildprozess gibt bei erfolgreichem Durchlauf folgende Meldungen aus (beim MSVC):
...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-Version erstellt. Sie können auch explizit eine release-Version erstellen:
bjam variant=release
Aber auch beide Versionen sind in einem Vorgang möglich:
bjam variant=debug variant=release
Es ist also sehr einfach und intuitiv zu handhaben.
Übrigens, bjam rufen wir bisher nach folgendem Muster auf:
bjam [i]property-key=property-value[/i]
Doch bjam kann auch einfach nur mit Property-Values arbeiten, hier ein paar Beispiele:
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 Properties 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:
bjam [i]--option[/i]
Die wichtigsten Optionen sollten Sie kennen:
--version zeigt die bjam und BBv2 Version an
--clean löscht alle autom. erzeugten Dateien vom Typ .o, .obj, .lib, .exe usw.
--help zeigt weitere Optionen anManchmal 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 Properties kombinieren. Hier ein paar Beispiele:
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 realisierbar. Natürlich hat auch BBv2 hier eine Lösung im Angebot. Erstellen wir uns also eine Mini-Bibliothek in einem neuen Quellverzeichnis:
// myLib.hpp int foo();
// myLib.cpp #include "myLib.hpp" #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-\1:
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:
...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:
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:
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.
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:
bjam hello
Wollen Sie hello als release bauen, ist wie immer eine Kombination von Properties möglich:
bjam hello release
Mit dem Wissen über Targets können wir nun zu den Projekten übergehen.
7 Komplexe Projekte
Bisher hatten wir ein Verzeichnis in dem entweder ein Projekt oder mehrere Projekte waren. Das ist leider sehr unübersichtlich und 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 ein eigenes Target identifizierbar und doch kann alles mit einem Aufruf gebaut werden. Deshalb hier ein Beispiel, wie die Verzeichnisstruktur aussehen kann:
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) autom. 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 unserem gesamten Projekt ist unsere Anwendung "hello" das Kernprojekt. Wenn wir also bjam aufrufen, soll am Ende unser hello.exe zusammen gebaut werden. Dafür schreiben Sie in Jamroot:
build-project hello ;
Durch einen bjam Aufruf wird hello gebaut. Sollte aber hello.cpp einen Funktionsaufruf foo() aus myLib haben, wie hier:
#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:
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 drin stehen, deshalb muss das gewünschte genannt werden. In unserem Fall ist das Dependency Target "myLib".
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:
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:
exe hello : hello.cpp /my-lib-id/lib1//myLib ;
/my-lib-id/lib1 ist die ID des Projektes, nach den doppelten Schrägstrichen 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 builden. Meiner Meinung nach ist BBv2 eines der einfachsten und doch leistungsfähigsten Buildtools. Natürlich konnte dieser Artikel nur die Grundlagen vermitteln. BBv2 hat 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.
Ich will jedoch stichwortartig ein paar Dinge nennen, die Aufgaben neben dem reinen Build sind und trotzdem von BBv2 unterstützt werden:
- BBv2 unterstützt direkt Unit-Tests
- BBv2 kann Dokumentation mittels Boost.Book oder Doxygen aus Ihren Sourcen erzeugen
- Sie können BBv2 um eigene Funktionalitäten erweitern
- aus einem CVS Repository den aktuellen Entwicklungsstand auschecken und diesen für den Build benutzen
- und einige andere Dinge mehr
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 C++ Libraries Homepage
-
@estartu:
mir ist gerade etwas aufgefallen:
im Anleitungs-Thread im Layout-Abschnitt hat sich ein kleiner Fehler eingeschlichen.
Die "Unterüberschrift" im Beispiel ist kursiv und unterstrichen dargestellt (so war es iirc gedacht), unten drunter stehen aber bei "So gehts:" [b]- statt [u]-Tags.
Deswegen hat es Artchi wahrscheinlich auch falsch gemacht...
-
Vielen Dank für das Korrekturlesen, predator!
-
-predator- schrieb:
@estartu:
mir ist gerade etwas aufgefallen:
im Anleitungs-Thread im Layout-Abschnitt hat sich ein kleiner Fehler eingeschlichen.
Die "Unterüberschrift" im Beispiel ist kursiv und unterstrichen dargestellt (so war es iirc gedacht), unten drunter stehen aber bei "So gehts:" [b]- statt [u]-Tags.
Deswegen hat es Artchi wahrscheinlich auch falsch gemacht...Ups, vielen Dank.
Jetzt stimmt es.
-
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 Links1 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-Versionen? 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 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:
- GNU gcc (various versions), on Linux, NT, Cygwin and Darwin.
- Microsoft VC++ compiler (various version), on NT and Cygwin.
- Borland's compiler, on NT and Cygwin.
- Metrowerks Code Warrior, on NT.
- Intel C++ Compiler, on Linux and NT.
- Sun's C++ Compiler.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 Package boost-build herunterladen:
http://www.kharchi.de/files/cpp/boost-build.zip
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:set PATH=C:\boost-build\jam_src\bin.ntx86;%PATH%
BBv2 muss auch gefunden werden; dazu es gibt zwei Möglichkeiten:
1. Sie richten eine Umgebungsvariable BOOST_BUILD_PATH ein, welche auf boost-build zeigt. Unter Windows wie folgt:set BOOST_BUILD_PATH=C:\boost-build\
2. 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:
#include <iostream> int main() { std::cout << "Hello bjam!" << std::endl; }
Ein dazugehöriges BBv2 Projekt-Script würde wie folgt aussehen:
exe hello : hello.cpp ;
Dieses Script speichern Sie unter dem Dateinamen Jamroot in Ihr Quellverzeichnis. Die Verzeichnisstruktur kann dabei so aussehen:
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:
exe hello : hello.cpp hello2.cpp hello3.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:
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:
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:
bjam
Der Buildprozess gibt bei erfolgreichem Durchlauf folgende Meldungen aus (beim MSVC):
...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-Version erstellt. Sie können auch explizit eine Release-Version erstellen:
bjam variant=release
Aber auch beide Versionen sind in einem Vorgang möglich:
bjam variant=debug variant=release
Es ist also sehr einfach und intuitiv zu handhaben.
Übrigens, bjam rufen wir bisher nach folgendem Muster auf:
bjam [i]property-key=property-value[/i]
Doch bjam kann auch einfach nur mit Property-Values arbeiten, hier ein paar Beispiele:
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:
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 anManchmal 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:
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:
// myLib.hpp int foo();
// myLib.cpp #include "myLib.hpp" #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-\1:
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:
...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:
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:
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.
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:
bjam hello
Wollen Sie hello als release bauen, ist wie immer eine Kombination von Propertys möglich:
bjam hello release
Mit dem Wissen über Targets können wir nun zu den Projekten übergehen.
7 Komplexe Projekte
Bisher hatten wir ein Verzeichnis, in dem entweder ein Projekt oder mehrere Projekte waren. Das ist leider sehr unübersichtlich und 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 ein eigenes Target identifizierbar, und doch kann alles mit einem Aufruf gebaut werden. Deshalb hier ein Beispiel, wie die Verzeichnisstruktur aussehen kann:
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 unserem gesamten Projekt 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:
build-project hello ;
Durch einen bjam Aufruf wird hello gebaut. Sollte aber hello.cpp einen Funktionsaufruf foo() aus myLib haben, wie hier:
#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:
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".
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:
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:
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 builden. 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.
Ich will jedoch stichwortartig ein paar Dinge nennen, die Aufgaben neben dem reinen Build sind und trotzdem von BBv2 unterstützt werden:
- BBv2 unterstützt direkt Unit-Tests
- BBv2 kann Dokumentation mittels Boost.Book oder Doxygen aus Ihren Sourcen erzeugen
- Sie können BBv2 um eigene Funktionalitäten erweitern
- aus einem CVS-Repository den aktuellen Entwicklungsstand auschecken und diesen für den Build benutzen
- und einige andere Dinge mehr
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 C++ Libraries Homepage
-
da gabs noch ein paar fehler wie z. b. ein kleingeschriebenes "Sie" usw.
nichts gravierendes mehr.Mr. B
-
Vielen Dank für die Korrektur, Mr. B!
Dann kann der Artikel ja am Wochenende auf den nächsten Status gesetzt werden.
-
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 Links1 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:
- Comeau C/C++.
- Borland's Compiler, auf NT und Cygwin.
- Digital Mars C++.
- GNU gcc (diverse Versionen), auf Linux, NT, Cygwin und MacOS X.
- GNU Fortran.
- HP C++ Version 7.1 für Tru64 UNIX.
- HP Fortran.
- IBM VisualAge C++.
- Intel C++ Compiler, auf Linux und NT.
- Metrowerks Code Warrior, auf NT.
- Microsoft VC++ Compiler (diverse Versionen), auf NT und Cygwin.
- QCC Compiler für QNX (ist in Arbeit).
- Sun C++ Compiler.
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.bz2Entpacken, 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: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: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:
#include <iostream> int main() { std::cout << "Hello bjam!" << std::endl; }
Ein dazugehöriges BBv2 Projekt-Script würde wie folgt aussehen:
exe hello : hello.cpp ;
Dieses Script speichern Sie unter dem Dateinamen Jamroot in Ihr Quellverzeichnis. Die Verzeichnisstruktur kann dabei so aussehen:
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:
exe hello : hello.cpp hello2.cpp hello3.cpp ;
Wollen Sie alle Dateien eines Verzeichnisses angeben, ist dies mit dem Stern-Wildcard möglich:
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:
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:
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:
bjam
Der Buildprozess gibt bei erfolgreichem Durchlauf folgende Meldungen aus (beim MSVC):
...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:
bjam variant=release
Aber auch beide Varianten sind in einem Vorgang möglich:
bjam variant=debug variant=release
Es ist also sehr einfach und intuitiv zu handhaben.
Übrigens, bjam rufen wir bisher nach folgendem Muster auf:
bjam [i]property-key=property-value[/i]
Doch bjam kann auch einfach nur mit Property-Values arbeiten, hier ein paar Beispiele:
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:
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 anManchmal 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:
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:
// myLib.hpp void foo();
// 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-\1:
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:
...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:
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:
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.
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:
bjam hello
Wollen Sie hello als release bauen, ist wie immer eine Kombination von Propertys möglich:
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:
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:
build-project hello ;
Durch einen bjam Aufruf wird hello gebaut. Sollte aber hello.cpp einen Funktionsaufruf foo() aus myLib haben, wie hier:
#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:
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:
[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:
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:
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 unterstützt Unit-Tests
- BBv2 kann Dokumentation mittels Boost.Book oder Doxygen aus Ihren Sourcen erzeugen
- Sie können BBv2 um eigene Funktionalitäten mittels bjam-Scripts erweitern
- aus einem CVS-Repository den aktuellen Entwicklungsstand auschecken und diesen für den Build benutzen
- und einige andere Dinge mehr
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