(Video) Grundsätzliche Funktionsweise von CMake

Durch Zufall bin ich auf ein deutsches Video gestoßen, dass erklärt, wie die Funktionsweise von CMake unter Linux ist (CMake -> Make -> kompiliertes Projekt).

Ein weiterer Punkt, warum es sich lohnt CMake zu verwenden und der im Video aber nicht ganz dargestellt wird ist der, dass man aus einer CMakeLists.txt nicht nur ein Makefile generieren kann, sondern z.B. auch eine Solutionsdatei für Visual Studio und andere Buildsysteme.

../../src/pdf/SkDeflate.cpp(16,10): fatal error: ‘zlib.h’ file not found

Beim Bauen der Bibliothek skia auf Windows bekam ich kürzlich diese Fehlermeldung:

../../src/pdf/SkDeflate.cpp(16,10): fatal error: 'zlib.h' file not found

Wie sich herausstellte, musste ich beim Bauen noch einige Include-Ordner zu den extra_cflags hinzufügen, damit der Bau von skia erfolgreich durchlaufen konnte (hier fett dargestellt):

bin\gn gen out/Static --args="is_official_build=true clang_win=\"C:\Program Files\LLVM\" extra_cflags=[ \"-IC:\pfad\zu\skia\third_party\externals\zlib\" ]"

CMake-Beispiel mit harfbuzz

In diesem Post möchte ich ein kleines Beispiel zeigen, wie man ein C++-Projekt mit harfbuzz kompiliert. Das alles zeige ich in einem CentOS docker Container. Aber das sollte eigentlich keinen Unterschied machen, d.h. das gezeigte Beispiel sollte auch auf einem normalen Linuxsystem funktionieren.

Für dieses Beispiel habe ich ein einfaches “Hello World”-Beispiel genommen, dass man üblicherweise in jedem C++-Anfängerbuch findet. Diese Datei habe ich main.cpp benannt:

#include <iostream>

int main(int argc, char **argv) {
        std::cout << "Hello World!" << std::endl;
        return 0;
}

Obwohl es hier keinen Code gibt der auf die Bibliothek harfbuzz verweist, kann man diesen Code problemlos gegen diese Bibliothek kompilieren / linken. Ich gehe davon aus, dass normalerweise auch der entsprechende Code in den Quelltextdateien zu finden ist, aber für diesen Post ist das nicht vonnöten. Jetzt muss ich nur noch die CMakeLists.txt entsprechend anpassen:

cmake_minimum_required(VERSION 3.0)
# Projektname setzen
project(harfbuzz_ex1)

# Alle Quelltextdateien in eine Variable packen
set(SOURCES main.cpp)

# Eine ausführbare Datei soll generiert werden ...
add_executable(${PROJECT_NAME} ${SOURCES})

# ... mit harfbuzz als abhängige Bibliothek
target_link_libraries(${PROJECT_NAME} harfbuzz)

Ist die CMakeLists.txt fertig, kann man mit dem Kompilieren / Linken anfangen:

cmake -G "Unix Makefiles" .
make

Beim ersten Versuch das Projekt zu übersetzen, bekam ich diese Fehlermeldung:

sh-4.2# make
[ 50%] Linking CXX executable harfbuzz_ex1
/usr/bin/ld: cannot find -lharfbuzz
collect2: error: ld returned 1 exit status
make[2]: *** [harfbuzz_ex1] Error 1
make[1]: *** [CMakeFiles/harfbuzz_ex1.dir/all] Error 2
make: *** [all] Error 2

Ich hatte die Bibliothek harfbuzz noch gar nicht auf meinem System installiert. Das musste ich natürlich schnell nachholen:

yum install -y harfbuzz-devel

Danach reichte es aus nochmal make aufrufen und schon konnte ich mein Projekt problemlos bauen und ausführen:

sh-4.2# make
[ 50%] Linking CXX executable harfbuzz_ex1
[100%] Built target harfbuzz_ex1
sh-4.2# ./harfbuzz_ex1
Hello World!

NoClassDefFoundError: com/sun/mail/util/MailLogger

Wenn man die Fehlermeldung bekommt

Exception in thread "main" java.lang.NoClassDefFoundError: com/sun/mail/util/MailLogger
        at javax.mail.Session.initLogger(Session.java:283)
        at javax.mail.Session.(Session.java:268)
        at javax.mail.Session.getDefaultInstance(Session.java:378)
        at javax.mail.Session.getDefaultInstance(Session.java:418)
        at eu.masterdevops.sendmail.App.main(App.java:25)
Caused by: java.lang.ClassNotFoundException: com.sun.mail.util.MailLogger
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        … 5 more

dann fehlt vermutlich eine Abhängigkeit in der pom.xml:

<dependency>
  <groupId>com.sun.mail</groupId>
  <artifactId>javax.mail</artifactId>
  <version>1.6.2</version>
</dependency>

Cannot find default setter in class org.apache.maven.archiver.ManifestConfiguration

Beim Bauen eines meiner Java-Projekte bekam ich nach dem Anpassen der pom.xml diese Fehlermeldung

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-assembly-plugin:2.2-beta-5:single
(default-cli) on project <projektname>: Unable to parse configuration of mojo
org.apache.maven.plugins:maven-assembly-plugin:2.2-beta-5:single for parameter manifest:
Cannot find default setter in class org.apache.maven.archiver.ManifestConfiguration -> [Help 1]

Wie es sich herausgestellt hat, hatte ich eine falsche Einstellung in die pom.xml geschrieben (Fehler hier fett abgebildet):

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-assembly-plugin</artifactId>
  <configuration>
    <archive>      <manifest>com.project.projectname.App</manifest>
    </archive>
    <descriptorRefs>
      <descriptorRef>jar-with-dependencies</descriptorRef>
    </descriptorRefs>
  </configuration>
</plugin>

Zwischen <manifest> und </manifest> hätte was anderes rein müssen als die Main-Class:

<archive>
   <manifest>
     <mainClass>com.project.projectname.App </mainClass>
   </manifest>
 </archive>

Nach diese Änderung ist die Fehlermeldung verschwunden.

OpenSSL 1.1: undefined symbol: UINT32_it

Wenn man unter Linux mit dem GNU Compiler eine Bibliothek beim Kompilieren benötigt, kann es doch manchmal wichtig sein, in welcher Reihenfolge man die abhängigen Bibliotheken angibt. Das habe ich letztens wieder gemerkt als mein Programm eine Bibliothek laden wollte, die wiederum die Bibliothek OpenSSL als Abhängigkeit hatte.

Beim Laden der Bibliothek bekam ich diese Fehlermeldung:

undefined symbol: UINT32_it

Nach einer kurzen Suche hatte ich schon eine Antwort. Beim Bauen meiner Bibliothek hatte ich die OpenSSL Bibliotheken in dieser Reihenfolge als abhängige Bibliotheken angegeben:

g++ ... -lcrypto -lssl ...

Nachdem ich die Reihenfolge dieser beiden Bibliotheken beim Bau meiner Bibliothek in

g++ ... -lssl -lcrypto ...

geändert hatte, kam die Fehlermeldung nicht mehr wieder.

Eine statische Bibliothek unter Linux mit CMake erstellen

In diesem Post möchte ich ein kleines Beispiel zeigen, wie man eine statische C++ Bibliothek unter Linux mit Hilfe von CMake baut. Dazu habe ich eine sehr einfache C++-Datei namens funcs.cpp erstellt. Die in der Datei enthaltene Funktion add soll zu Beispielzwecken dann in der statischen Bibliothek enthalten sein.

int add(int a, int b)
{
  return (a+b);
}

Die schon erwähnte Funktion add soll zu Beispielzwecken einfach zwei ganze Zahlen addieren. Um die Datei nun in eine statische Bibliothek zu packen, wird eine weitere Datei namens CMakeLists.txt benötigt, die in etwa so aussieht:

cmake_minimum_required(VERSION 3.6)

# Projektname definieren
project(static_test)

# Liste aller Quelldateien in einer Variablen speichern.
# Aus diesen Dateien entsteht dann die statische Bibliothek
set(ST_SOURCES funcs.cpp)

# CMake sagen, dass eine statische Bibliothek erstellt werden soll.
add_library(static_test STATIC ${ST_SOURCES})

Mit dem Kommando project definiert man den Projektnamen. Mit dem Kommando set wird eine neue Variable namens ST_SOURCES erstellt und gleich definiert. Dabei soll ST_SOURCES aus einer Liste aller Quelltextdateien bestehen, die für die statische Bibliothek nötig sind. Da wir in diesem Beispiel nur eine Datei haben, enthält die Liste ST_SOURCES nur einen Eintrag namens funcs.cpp. Man kann hier aber auch mehrere Dateinamen oder -pfade hintereinander setzen, getrennt durch ein Leerzeichen. Der Variablenname ist frei wählbar, muss aber gültig sein.

Das Kommando add_library sagt CMake nun, dass wir eine Bibliothek bauen wollen. Erst das STATIC in dem Kommando macht CMake klar, dass es sich um eine statische Bibliothek handeln muss. Will man jedoch eine dynamische Bibliothek, ersetzt man das STATIC einfach durch SHARED.

Beide Dateien befinden sich im selben Ordner.

Wenn man nun die CMakeLists.txt erstellt hat, muss man anschließend in die Kommandozeile diese zwei Befehle eingeben:

cmake -G "Unix Makefiles" .
make
Die statische Bibliothek wird gebaut.

Der erste Befehl (cmake …) erstellt nun das entsprechende Makefile. Ist das erstellt, kann man make starten und die statische Bibliothek wird erstellt.

Ist alles erfolgreich verlaufen, müsste man nun im Ordner eine Datei namens libstatic_test.a finden.

Der Bau der statischen Bibliothek war erfolgreich.

‘build.plugins.plugin.version’ for … is missing.

Kürzlich habe ich versucht, ein Projekt mit Maven zu bauen und bekam während des Bauens diese Warnung:

[WARNING]
[WARNING] Some problems were encountered while building the effective model for ::jar:
[WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-jar-plugin is missing. @ line 17, column 15
[WARNING]
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING]
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING]

Wie es sich herausgestellt hat, hat bei einem verwendeten Maven-Plugin die Versionsangabe gefehlt. In der verwendeten pom.xml wurde das Maven-Plugin maven-jar-plugin verwendet, aber die Version hat gefehlt:

<plugin>
  <artifactId>maven-jar-plugin</artifactId>
  <configuration>
    <archive>
      <manifest>
        <mainClass>com.somepackage.SomeMainClass</mainClass>
      </manifest>
    </archive>
  </configuration>
</plugin>

Die Version für das Plugin konnte ich recht leicht herausfinden und anschließend die pom.xml anpassen:

 <plugin>
  <artifactId>maven-jar-plugin</artifactId>
  <version>3.1.2</version>
  <configuration>
    <archive>
      <manifest>
        <mainClass>com.somepackage.SomeMainClass</mainClass>
      </manifest>
    </archive>
  </configuration>
</plugin> 

Danach ist die Warnung verschwunden.

Dependency Graph Viewer Plugin einrichten

Wenn es mal unübersichtlich mit den Jobs in Jenkins wird, kann man mit dem Dependency Graph Viewer Plugin ein wenig Licht in die Sache bringen. Vor allem dann, wenn es darum geht zu sehen, welcher Job welchen anderen Job startet. Wenn z.B. der Job Bauen den Job Unittests aufruft, lässt sich das dem Dependency Graph Viewer Plugin gut visualisieren.

Ein Beispielgraph produziert durch den Dependency Graph Viewer Plugin

Zuerst muss man natürlich das Plugin in Jenkins installieren. Ich gehe jetzt davon aus, dass klar ist wie man in Jenkins ein Plugin installiert.

Als ich nach der Installation versucht hatte, einen Graph zu erstellen, bekam ich stattdessen in der Browser-Konsole eine Fehlermeldung dieser Art. Es gab wohl auch andere, die davon betroffen waren.

TypeError: div.firstChild is null hudson-behavior.js:862:21
    onSuccess http://192.168.0.26:8080/static/b568631b/scripts/hudson-behavior.js:862
    respondToReadyState http://192.168.0.26:8080/static/b568631b/scripts/prototype.js:1657
    onStateChange http://192.168.0.26:8080/static/b568631b/scripts/prototype.js:1600
    bind http://192.168.0.26:8080/static/b568631b/scripts/prototype.js:414

Die Lösung für das Problem besteht aus zwei Teilen:

1. Sicherstellen, dass graphviz aus dem Betriebssystem installiert ist. Mein Jenkins lief auf einem CentOS-System, da reichte es aus graphviz mit dem Befehl

sudo yum install graphviz

zu installieren. Wichtig ist hierbei, dass man nach der Installation das Programm dot finden kann:

sh-4.2# which dot
/usr/bin/dot

2. In den Systemeinstellungen muss der Pfad zu dem Programm dot gesetzt sein.

Hier kann es aber auch sein, dass man auch keinen Pfad setzen muss. Im Zweifel einfach mal testen. Wichtig ist jedoch, dass der Pfad zum dot Programm vollständig ist und hier nicht nur der Pfad gesetzt wird, in dem sich das Programm dot befindet.

Weitere mögliche Quellen:

ERROR: [WS-CLEANUP] Cannot delete workspace: Remote call on … failed

Vor kurzem hatte ich einen Job in Jenkins, der die Einstellung hatte den Arbeitsbereich auf einem Jenkins-Slave zu löschen bevor der Job fortführen sollte:

Auf einmal bekam ich jedoch regelmäßig Fehlermeldungen dieser Art:

[WS-CLEANUP] Deleting project workspace…
[WS-CLEANUP] Deferred wipeout is used…
ERROR: [WS-CLEANUP] Cannot delete workspace: Remote call on … failed
ERROR: Cannot delete workspace: Remote call on … failed

Die Lösung in diesem Fall war die: Ich musste den Jenkins-Slave erst einmal richtig trennen

um ihn anschließend wieder zu verbinden.

Im Grunde habe ich den Slave-Agenten neu gestartet. Seitdem trat der Fehler nicht mehr auf.