CMake und gcov

Was ist gcov?

gcov ist ein Tool, um u.a. die Code Coverage zu messen. Dieses Tool kann aber nur im Zusammenhang mit dem GNU Compiler genutzt werden. Um dieses Tool zu nutzen, muss man den Quelltext mit den Parametern -fprofile-arcs und -ftest-coverage kompilieren.

Jetzt wollte ich das ganze mal mit CMake kombinieren. Die Idee hierbei ist folgende: Angenommen, jemand schreibt eine Bibliothek und möchte den Quellcode testen. Um den Quellcode zu testen, schreib der- oder diejenige kleine Testprogramme, die einige dieser Funktionen aufrufen und überprüfen, ob die jeweils getestete Funktion den Anforderungen entspricht. In dem folgenden CMake und gcov-Beispiel werde ich das nicht so aufwendig machen, sondern ich erstelle eine einzige C++-Datei, diese wird zu einer ausführbaren Datei kompiliert, anschließend gestartet (wir simulieren hier also das Starten eines Testprogramms) und zum Schluss werden die erzeugten gcov-Dateien gesammelt.

Diese gcov-Dateien, von denen hier die Rede ist, haben die Endungen gcno und gcda. Hat man diese Dateien gesammelt, kann man sie mit dem gcov-Tool öffnen, also z.B. gcov main.cpp.gcno. gcov erzeugt anschließend Dateien mit der Endung gcov, die man z.B. mit einem Texteditor öffnen und analysieren kann. Da ich die Dateien mit der Endung gcov auf demselben System angeschaut habe, auf dem auch der Quellcode liegt, hatte ich keine Probleme. Problematisch wird es dann, wenn man gcov startet, der Quelltext aber nicht vorhanden ist oder verschoben wurde.

Im Anschluss findet ihr eine Beispiel-CMakeLists.txt-Datei, die genau die beschriebenen Schritte durchführt. Ich hoffe, die Kommentare sind ausreichend. Falls nicht, schreibt mir in die Kommentare

cmake_minimum_required(VERSION 3.5)
project(gcov_ex)

# Beispielcode mit Parametern kompilieren, sodass
# man das Tool gcov verwenden kann
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")

set(SOURCES main.cpp)

# Zielordner, in dem die gcov-Dateien gesammelt werden
set(TARGET_DIR ${CMAKE_CURRENT_SOURCE_DIR}/target)

add_executable(${PROJECT_NAME} ${SOURCES})

# Sicherstellen, dass der Zielordner auch wirklich existiert
add_custom_command(TARGET ${PROJECT_NAME}
        POST_BUILD
        COMMAND mkdir -p ${TARGET_DIR})

# Testprogramm ausführen
add_custom_command(TARGET ${PROJECT_NAME}
        POST_BUILD
        COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME})

# gcno Dateie in den Zielordner kopieren
add_custom_command(TARGET ${PROJECT_NAME}
        POST_BUILD
        COMMAND
                pushd ${CMAKE_CURRENT_SOURCE_DIR}/CMakeFiles/${PROJECT_NAME}.dir && find . -name '*.gcno' -exec bash -c 'cp --parents -v "{}" ${TARGET_DIR}' \\\; && popd)

# gcda Dateien in den Zielordner kopieren
add_custom_command(TARGET ${PROJECT_NAME}
        POST_BUILD
        COMMAND
                pushd ${CMAKE_CURRENT_SOURCE_DIR}/CMakeFiles/${PROJECT_NAME}.dir && find . -name '*.gcda' -exec bash -c 'cp --parents -v "{}" ${TARGET_DIR}' \\\; && popd)

[Ubuntu] getdeb Repository entfernen

Um das getdeb Repository aus Ubuntus apt zu entfernen, muss man folgende Kommandos eingeben (als root):

apt-get remove getdeb-repository
apt purge getdeb-repository
apt clean
apt-get update

Wenn man die Befehle ausgeführt hat, muss man eventuell noch im Ordner /etc/apt/sources.list.d/ nachschauen, ob es in einen der Dateien noch Hinweise auf das getdeb Repository gibt. Wenn ja, dann kann man die Zeilen auskommentieren, indem man eine Raute davor setzt, z.B.

# deb http://archive.getdeb.net/ubuntu kali-rolling-getdeb games

find, xargs und die Pipe

Neulich habe ich mich ein wenig mit den Linux-Kommandotools find und xargs beschäftigt und das in Kombination mit einer Pipe. Wenn ich in der Kommandozeile das Kommando eintippe

# find . -name '*'

kommt bei mir folgendes heraus:

.
./eins
./zwei
./drei

Das sind drei Dateien, die ich zu Testzwecken erstellt hatte. find gibt also jede gefundene Datei in einer neuen Zeile aus. Möchte man aber, dass alles in einer Zeile ausgegeben wird, muss man noch zusätzlich den Parameter print0 angeben:

$ find . -name '*' -print0
../eins./zwei./drei

print0 macht nichts anderes als anstelle einer neuen Zeile (\n) ein NUL-Zeichen zu setzen. Nun wollte ich das Ergebnis durch eine Pipe schicken und mit xargs die gefundenen Dateien weiterleiten. Doch da tauchte plötzlich eine Warnung auf:

$ find . -name '*' -print0 | xargs echo
xargs: WARNING: a NUL character occurred in the input.  It cannot be passed through in the argument list.  Did you mean to use the --null option?
.

Also den Befehl wieder etwas abändern:

$ find . -name '*' -print0 | xargs --null echo
. ./eins ./zwei ./drei

CMake und proxygen

Vor kurzem musste ich ein kleines Beispiel in CMake mit der Bibliothek proxygen machen. Dabei kam es mir ziemlich gelegen, dass das Projekt auch ein Dockerfile hatte 😀 . Hier möchte ich kurz beschreiben, was ich gemacht habe, vielleicht hilft es ja dem ein oder anderen.

Erstmal das Projekt klonen:

git clone https://github.com/facebook/proxygen.git

Nach dem klonen, ein Docker Image bauen:

cd proxygen
docker build -t proxy_ex .

Beim Bau von dem Docker Image kam jedoch irgendwann diese Fehlermeldung:

sudo: command not found

Die Lösung dieses Problems war die, dass man noch das Paket sudo im Dockerfile mit installiert, d.h. man muss das Dockerfile in etwa so abhändern:

...
    autoconf-archive \
    libevent-dev \
    libgoogle-glog-dev \
    wget \
    sudo

...

Jedenfall hat das bei mir geholfen. Nachdem ich also die Änderung im Dockerfile gemacht habe, habe ich das Docker Image nochmal neu gebaut und es danach gestartet:

docker run -it proxy_ex /bin/bash

In dem gestarteten Dockercontainter kopierte ich ein Beispiel aus dem proxygen-Projekt und nahm es als Grundlage für mein proxygen-CMake-Beispiel

cd && cp -r /home/proxygen/proxygen/httpserver/samples/echo# .

Anschließend konnte ich eine Beispiel-CMakeLists.txt erstellen:

cmake_minimum_required(VERSION 2.8)
project(proxy_gen)

# Dieses Projekt braucht c++11 Unterstützung
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(SOURCES EchoHandler.cpp EchoServer.cpp)

add_executable(${PROJECT_NAME} ${SOURCES})
target_link_libraries(${PROJECT_NAME} proxygenhttpserver folly glog gflags pthread)

[CMake] Was bedeuten die Wörter PRIVATE, PUBLIC und INTERFACE?

In CMake kommen manchmal die Schlüsselwörter PRIVATE, PUBLIC und INTERFACE vor. In diesem Post will ich erläutern, welche Funktion diese Schlüsselwörter haben und wofür man sie braucht.

Bevor wir uns jedoch die Schlüsselwörter genauer anschauen, will ich hier noch zwei Begriffe erklären: private und öffentliche Header. Ich weiß nicht, ob das, was ich hier jetzt beschreibe, auch die wirkliche Definition ist, aber ich werde diese Begriffe weiter unten benutzen, deswegen will ich diese hier nochmal klar stellen.
Wenn wir eine Bibliothek B haben und diese Bibliothek weitergeben, damit ein anderer Programmierer mit dieser Bibliothek arbeiten kann, geben wir ihm eine Reihe von Headern dazu, in der in aller Regel Funktionen und sonstige genutzte Variablen deklariert sind. Dieser Header, die weiter gegeben werden, bezeichne ich hier als öffentliche Header. Alle anderen Header, die bei der Programmierung der Bibliothek B benutzt werden, aber nicht weiter gegeben werden, bezeichne ich hier als private Header.

Da wir nun die Begrifflichkeiten geklärt haben, nun zum eigentlichen Thema: Angenommen, man schreibt eine (C++-)Bibliothek A und diese Bibliothek ist von einer weiteren Bibliothek B abhängig. Nun gibt es verschiedene Fälle:

  1. In einer cpp-Datei oder einer privaten Header-Datei der Bibliothek A wurde eine Header-Datei der Bibliothek B aufgenommen (gemeint sind Einträge der Form #include <B.h>). Aber es wurde in keiner öffentlichen Header-Datei der Bibliothek A ein Header der Bibliothek B aufgenommen. Dann ist dieser Fall eine PRIVATE-Abhängigkeit.
  2. Von einer PUBLIC-Abhängigkeit wird gesprochen, wenn eine Header-Datei der Bibliothek B in sowohl einer öffentlichen Header-Datei aufgenommen worden ist, wie auch einer cpp- bzw. einer privaten Header-Datei.
  3. Wenn eine Header-Datei der Bibliothek B nur in öffentlichen Headern der Bibliothek A zu finden ist, dann sprechen wir von einer INTERFACE-Abhängigkeit.

Bei einfachen Abhängigkeitsstrukturen mag diese Unterscheidung noch uninteressant sein, wenn es jedoch komplexer wird, ist es gut, die Unterscheidung zu kennen. Wenn eine Bibliothek C von Bibliothek A abhängig ist, wie werden in CMake die Target-Eigenschaften weiter gegeben? Gemeint sind diejenigen Target-Eigenschaften, die man z.B. über Funktionen wie target_include_directories definiert.

Wenn beispielsweise A und B eine PRIVATE-Abhängigkeit haben, und C und A eine (egal was für eine) Abhängigkeit haben, dann bekommt C von B nichts mit. D.h. wenn wir in CMake z.B. eine Zeile der Art

tartget_include_directories(targetA PRIVATE ${INCLUDE_DIR_LIB_B})

haben, dann würde die Bibliothek C den Include-Pfad für Bibliothek B nicht mitbekommen. In den anderen Fällen (zwischen A und B) bekommt C jedoch von B was mit.

Wer mehr erfahren möchte, kann unter diesen Links weiter schmökern:

CMake und die Bibliothek GLFW

In diesem Post möchte ich ein Beispiel zeigen, wie man die Bibliothek GLFW in CMake nutzt. Alles, was ich hier mache, mache ich auf einer Ubuntu Maschine. Es war nicht sehr einfach, da es mehrere Probleme gab, aber ich konnte dennoch mein kleines Beispiel zum Laufen bringen. Ich möchte hier Schritt für Schritt zeigen, wie ich zur Lösung gekommen bin.

Fangen wir an. Zuerst eine main.cpp, die unser Beispielprogramm sein soll:

#include <iostream>
#include <GL/glew.h>
#include <GL/glfw.h>
 
int main(int argc, char **argv)
{
        if ( !glfwInit() ) {
                std::cout << "glfw failed" << std::endl;
        } else {
                std::cout << "glfw success" << std::endl;
        }
 
        return 0;
}

Um das Beispiel zum Laufen zu bekommen, muss man noch entsprechende Bibliotheken auf dem System installiert haben:

sudo apt-get install libglfw-dev libglew-dev

Nachdem wir also auch die Abhängigkeiten haben, fangen wir mit der CMakeLists.txt an:

cmake_minimum_required(VERSION 3.5)
project(glfw_ex)

set(SOURCES main.cpp)

find_package(PkgConfig REQUIRED)
pkg_search_module(GLFW REQUIRED glfw)

include_directories(${GLFW_INCLUDE_DIRS})

add_executable(${PROJECT_NAME} ${SOURCES})
target_link_libraries(${PROJECT_NAME} ${GLFW_LIBRARIES})

Als ich CMake gestartet hatte (Kommandozeilenbefehl: cmake . && make ), tauchte folgender Fehler auf:

-- Found PkgConfig: /usr/bin/pkg-config (found version "0.29") 
-- Checking for one of the modules 'glfw'
CMake Error at /usr/local/share/cmake-3.5/Modules/FindPkgConfig.cmake:575 (message):
  None of the required 'glfw' found
Call Stack (most recent call first):
  CMakeLists.txt:10 (pkg_search_module)

Da ein schnelles googlen keine Ergebnisse gebracht hat, musste ich wohl etwas tiefer graben. Zuerst wollte ich sichergehen, dass ich auch wirklich eine Bibliothek mit dem Namen glfw auf der Kiste habe. Der folgende Befehl listet alle Dateien auf einem Ubuntu-System auf, die ein Paket installiert hat:

dpkg-query -L libglfw-dev

Und tatsächlich fand ich Einträge wie

...
/usr/lib/i386-linux-gnu/libglfw.a
...
/usr/lib/i386-linux-gnu/libglfw.so

D.h. die Bibliotheken ware da, aber in CMake kam trotzdem eine Fehlermeldung.

pkg_search_module und pkg-config

Also musste ich weitersuchen. Ich habe mir die Frage gestellt: Was macht eigentlich die CMake Funktion pkg_search_module? Die Funktion basiert auf dem Linux-Tool pkg-config, mit der man Informationen über Software abfragen kann. Bei Bibliotheken kann man das Tool nutzen, um die Compiler- und Linker-Parameter für die entsprechende Bibliothek zu extrahieren. Wenn man z.B. meine obige C++-Datei nimmt, und diese auf der Kommandozeile kompilieren möchte (also ganz ohne CMake), kann mann das über den Befehl

g++ -o glfw_ex1 -lglfw main.cpp

oder man nutzt die Ausgabe von pkg-config

g++ -o glfw_ex2 $(pkg-config --libs libglfw) main.cpp
# Ausgabe von 'pkg-config --libs libglfw' ist
# -lglfw -pthread

Aber das sollte jetzt nur ein kleiner Exkurs sein.

Durch eine schnelle Internetrecherche kam außerdem heraus, dass das Tool Dateien vom Typ *.pc nutzt. Bei der Auflistung der installierten Dateien fand ich auch folgenden Eintrag:

/usr/lib/i386-linux-gnu/pkgconfig/libglfw.pc

Als ich die Zeile gesehen habe, habe ich mir gedacht, dass ich die CMakeLists.txt wohl etwas abändern muss (ich zeige hier nur die veränderte Zeile):

pkg_search_module(GLFW REQUIRED libglfw)

Doch auch als ich diese Zeile geändert hatte, kam eine Fehlermeldung, was seltsam war, da das Kommando

pkg-config --libs libglfw

keine Fehlermeldung erzeugte, sondern Linkeroptionen ausgab. Die geänderte Zeile habe ich aber dann so gelassen. Nach längerem Suchen in dem CMake-Modul FindPkgConfig.cmake habe ich herausgefunden, dass CMake intern das Tool pkg-config folgendermaßen aufruft

pkg-config --exists --print-errors --short-errors libglfw

Also habe ich diesen Befehl auch in der Kommandozeile getestet. Tatsächlich wurde mir auch hier ein Fehler angezeigt:

Package 'xrandr' required by 'libglfw', not found

Nachdem ich aufgrund der Fehlermeldung das Paket libxrandr-dev installiert hatte, kam keine Fehlermeldung mehr beim Aufruf vom CMake-internen pkg-config-Befehl. Also habe ich nochmal CMake gestartet und danach lief CMake ohne Probleme durch 🙂

Von CPack, RPM und ausführbaren Dateien

Letztens hatte ich ein komisches Problem. Ich habe mit Hilfe von CMake bzw. CPack ein RPM Paket erstellt. In der CMakeLists.txt hatte ich eine Zeile, die ungefähr so ging:

install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/usr/bin/executable DESTINATION bin)

D.h. ich hatte schon eine ausführbare Datei und beim installieren des RPM Pakets auf dem Zielsystem, sollte auch diese ausführbare Datei installiert werden. Wurde sie auch. Aber sobald ich in die Kommandozeile executable (also der Name der ausführbaren Datei) eingegeben habe, kam die Meldung: Permission denied. Nachdem ich eine Weile geforscht habe, kam heraus: Ich hatte keine Rechte, um das Programm auszuführen. Das lag wohl an meiner CMakeLists.txt. Diese musste ich nämlich etwas abändern, und zwar in

install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/usr/bin/executable DESTINATION bin)

Ich hätte die Dokumentation zum install Befehl genauer lesen müssen 🙂 🙂 🙂

Ein CHECK_CXX_SOURCE_RUNS Beispiel

In diesem Post möchte ich ein Beispiel zur Funktion CHECK_CXX_SOURCE_RUNS zeigen. Diese Funktion kompiliert einen Code und lässt diesen laufen. Das kompilieren und laufen passiert während des Prozesses, in dem CMake die Build- / Projektdateien generiert.

Das kann hilfreich sein, um z.B. bei OpenSource Projekten zu testen, ob bestimmte Dinge überhaupt kompilierbar sind oder nicht. Ich habe hier mal ein kleines Beispiel erstellt, um zu zeigen, wie die Funktion genutzt wird. Um die Funktion überhaupt nutzen zu können, muss man jedoch die Zeile „include(CheckCXXSourceRuns)“ relativ weit oben einfügen.

Hier also die CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
project(runs_ex)

include(CheckCXXSourceRuns)

set(SOURCES main.cpp)

CHECK_CXX_SOURCE_RUNS("
#include 

int main(int argc, char **argv)
{
        std::cout << \"Hallo :)\" << std::endl;
        return 0;
}" FUNC_CPP_RESULT)

message("-- FUNC_CPP_RESULT: ${FUNC_CPP_RESULT}")

add_executable(${PROJECT_NAME} ${SOURCES})

Wenn ich danach den Befehl

cmake .

aufrufe, dann bekomme ich folgende Ausgabe:

-- The C compiler identification is GNU 5.3.1
-- The CXX compiler identification is GNU 5.3.1
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Performing Test FUNC_CPP_RESULT
-- Performing Test FUNC_CPP_RESULT - Success
-- FUNC_CPP_RESULT: 1
-- Configuring done
-- Generating done
-- Build files have been written to: /home/kristian/Documents/cmake/check-cxx-source-runs

CMake und freetype unter Linux

Wenn man CMake mit freetype unter Linux nutzt, dann ist manches nicht immer ganz so offensichtlich, wie ich es mir wünschen würde. Aber erst einmal von Anfang an. Angenommen, man hat ein Projekt, bei dem man eine freetype Version verwendet, die vom System bereit gestellt wird, d.h. die man aus einem Linux Repository installiert hat (also: yum, apt-get, zypper etc). Dann müsste die entsprechende CMakeLists.txt in etwa so aussehen (minimales Beispiel hier):

cmake_minimum_required(VERSION 3.5)
project(freetype_ex)

find_package(Freetype)

set(SOURCES main.cpp)

include_directories(${FREETYPE_INCLUDE_DIRS})

add_executable(${PROJECT_NAME} ${SOURCES})
target_link_libraries(${PROJECT_NAME} ${FREETYPE_LIBRARIES})

Ich habe dieses Beispiel gerade nicht getestet, aber soweit ich weiß müsste das so weit laufen. Wenn man aber freetype nicht über ein Linux Repo installiert hat, sondern es selbst kompiliert hat und in einen Nicht-Standard-Ordner installiert hat, dann gibt es drei Möglichkeiten, freetype in CMake bekannt zu machen.

Erste Möglichkeit

Die erste Variante ist die, in der ich die Variablen FREETYPE_INCLUDE_DIRS und FREETYPE_LIBRARIES selbst setze. Hier wieder ein minimales Beispiel:

cmake_minimum_required(VERSION 3.5)
project(freetype_ex)

set(FREETYPE_DIR "/home/kristian/Documents/freetype")
set(FREETYPE_INCLUDE_DIRS ${FREETYPE_DIR}/include)
set(FREETYPE_LIBRARIES ${FREETYPE_DIR}/lib/libfreetype.so)

set(SOURCES main.cpp)

include_directories(${FREETYPE_INCLUDE_DIRS})

add_executable(${PROJECT_NAME} ${SOURCES})
target_link_libraries(${PROJECT_NAME} ${FREETYPE_LIBRARIES})

Zweite Möglichkeit

Die zweite Variante wäre, freetype über den Befehl find_package finden zu lassen. Hierzu muss man aber vorher noch die Variable CMAKE_PREFIX_PATH um einen weiteren Pfad erweitern:

cmake_minimum_required(VERSION 3.5)
project(freetype_ex)

set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} /home/kristian/Documents/freetype)
find_package(Freetype)

set(SOURCES main.cpp)

include_directories(${FREETYPE_INCLUDE_DIRS})

add_executable(${PROJECT_NAME} ${SOURCES})
target_link_libraries(${PROJECT_NAME} ${FREETYPE_LIBRARIES})

Dritte Möglichkeit

Eine dritte Möglichkeit besteht, indem man man der Environment-Variablen FREETYPE_DIR den Pfad setzt. Also, statt

set(FREETYPE_DIR "/home/kristianDocuments/freetype")

zu schreiben, schreibt man

set(ENV{FREETYPE_DIR} "/home/kristian/Documents/freetype")

Die CMakeLists.txt im ganzen:

cmake_minimum_required(VERSION 3.5)
project(freetype_ex)

set(ENV{FREETYPE_DIR} "/home/kristian/Documents/freetype")

find_package(Freetype)

set(SOURCES main.cpp)

include_directories(${FREETYPE_INCLUDE_DIRS})

add_executable(${PROJECT_NAME} ${SOURCES})
target_link_libraries(${PROJECT_NAME} ${FREETYPE_LIBRARIES})

Diese drei Varianten habe ich gerade getestet und sie scheinen zu funktionieren 🙂