CMake – Spielereien mit Kompilierflags

Die Funktion set_target_properties ist dazu da, einige Eigenschaften für ein entsprechendes CMake-Target zu definieren. Möchte man beispielsweise, dass der GNU Compiler alle Warnungen ausgibt, kann man das über den CMake-Befehl set_target_properties machen. Hier eine sehr einfache CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
project(warnings123)
 
set(SOURCES main.cpp)
 
add_executable(${PROJECT_NAME} ${SOURCES})
# GNU Compiler soll alle Warnungen ausgeben,
# und zwar für alle Dateien für das Target PROJECT_NAME
set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-Wall")

Die main.cpp für die obige CMakeLists.txt sieht folgendermaßen aus:

#include <iostream>
 
int main(int argc, char **argv)
{
        int c = 10;
        std::cout << "Hallo, Welt!" << std::endl;
        return 0;
}

Führt man nun CMake und make aus, taucht folgende (bewusst eingebaute) Warnung aus:

/home/cmake/warnings123/main.cpp: In function ‘int main(int, char**)’:
/home/cmake/warnings123/main.cpp:5:6: warning: unused variable ‘c’ [-Wunused-variable]
  int c = 10;
      ^

Hat man aber mehrere Dateien im Projekt und man möchte für einen oder mehrere spezielle Dateien die Warnungen ausblenden, nimmt man hierfür die Hilfe der Funktion set_source_files_properties. Auch hier wieder eine Beipsiel-CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
project(warnings123)
 
set(SOURCES main.cpp func.cpp)
 
# Die Datei func.cpp hat auch eingebaute
# Warnungen, die jedoch für dieses Beispiel ausgeschaltet werden sollen
set_source_files_properties(func.cpp PROPERTIES COMPILE_FLAGS "-w")
 
add_executable(${PROJECT_NAME} ${SOURCES})
set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-Wall")

CMake – Makefiles debuggen

Ich wollte eben ein bisschen was mit CMake rumtesten. Eigentlich wollte ich nur für den Kompiliervorgang die Warnstufe höher stellen, damit der GNU Compiler beim kompilieren auf die Nase fällt, wenn etwas nicht ganz nach C++ ISO Norm geschrieben worden ist. Ganz konkret ging es um folgende CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
project(warnings123)
 
set(SOURCES main.cpp)
 
add_executable(${PROJECT_NAME} ${SOURCES})
target_compile_definitions(${PROJECT_NAME} PRIVATE "-Wall")

CMake ist problemlos durchgelaufen, aber beim Kompilieren gab es einen Fehler:

Scanning dependencies of target warnings123
[ 50%] Building CXX object CMakeFiles/warnings123.dir/main.cpp.o
<command-line>:0:1: error: macro names must be identifiers
make[2]: *** [CMakeFiles/warnings123.dir/main.cpp.o] Error 1
make[1]: *** [CMakeFiles/warnings123.dir/all] Error 2
make: *** [all] Error 2

Auf den ersten Blick sieht die Meldung doch etwas komisch aus. Nach etwas googeln, hatte jemand anderes ein ähnliches Problem. Doch damit konnte ich erstmal nichts anfangen. D.h. ich musste die ausgabefreudigkeit(?) – gemeint ist die verbosity – von dem Makefile erhöhen. Dies habe ich über ein CMake-Kommando gesetzt:

# ...
project(warnings123)
 
set(CMAKE_VERBOSE_MAKEFILE ON)   # <-- die Zeile wurde hinzugefügt
set(SOURCES main.cpp)
 
# ...

Diesesmal war das Problem recht leicht zu finden:

...
[ 50%] Building CXX object CMakeFiles/warnings123.dir/main.cpp.o
/usr/bin/c++   -D-Wall   -o CMakeFiles/warnings123.dir/main.cpp.o -c /root/cmake/warnings/main.cpp
<command-line>:0:1: error: macro names must be identifiers
...

-D-Wall macht einfach keinen Sinn 😀 😉 Also nochmal die Beschreibung der Funktion target_compile_definitions genauer lesen… Diese Funktion setzt Präprozessordefinitionen. Und eigentlich wollte ich nur die Warnstufe höher stellen. Die Funktion, die ich in diesem Fall hätte verwenden sollen, war add_compile_options. Danach war alles in Ordnung. Hier also die funktionierende CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
project(warnings123)
 
add_compile_options("-Wall")
set(SOURCES main.cpp)
 
add_executable(${PROJECT_NAME} ${SOURCES})

CMake – link_libraries: globale Bibliotheksabhängigkeit hinzufügen

Man kann in CMake für jedes Target die Abhängigkeiten angeben, die das jeweilige Target braucht (siehe target_link_libraries). Man kann aber auch für alle Targets, die man hat, eine oder mehrere Abhängigkeiten angeben (siehe link_libraries). D.h. falls man ein Projekt hat, bei dem jedes Target ein- und diesselbe Abhängigkeit hat, braucht man diese Abhängigkeit nicht für jedes Target speziell anzugeben, sondern es reicht einfach die Funktion link_libraries zu nutzen.

Ich habe hier schon ein kleines Beispiel erstellt. Ich verwende eine C-Datei: zpipe.c. Diese Datei wird in zwei Targets benutzt (siehe CMakeLists.txt): impl_link2 und zlib_test. Ich hoffe, die Kommentare in der CMakeLists.txt sind hilfreich:

cmake_minimum_required(VERSION 3.5)
# Projektname setzen
project(impl_link2)
 
# Wir benutzen in unseren Quelltexten
# Funktionen aus der Bibliothek ZLIB
# Daher auch die folgende Zeile
include(FindZLIB)
 
# Alle Targets verwenden ZLIB,
# d.h. wir können hier die Funktion link_libraries verwenden
# statt target_link_libraries
link_libraries(${ZLIB_LIBRARIES})
 
# Liste aller Quelltextdateien setzen
set(SOURCES zpipe.c)
 
# Target 1: impl_link2 = Projektname
add_executable(${PROJECT_NAME} ${SOURCES})
# Target 2: zlib_test
add_executable(zlib_test ${SOURCES})

CMake / CPack – Ein RPM Beispiel

Mit Hilfe von CMake kann man nicht nur Projekte auf verschiedenen System kompilieren und linken, sondern es lassen sich auch unterschiedliche Installationspakete erstellen. In diesem Post möchte ich ein kleines Beispiel geben, wie man unter einem RedHat-System ein RPM Paket erstellt. Dazu habe ich eine einfache C++-Datei erstellt mit einer einfachen Funktion:

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

Das ist die einzige Funktion, die unsere Bibliothek kennt. Nun schreiben wir die dazugehörige CMakeLists.txt (wobei ich die Erklärungen in der Datei beschrieben habe):

# Mindestversion von CMake, die verlangt wird
# um diese CMakeLists.txt auszuführen
cmake_minimum_required(VERSION 3.5)
 
# Name des Projekts, in diesem Fall mathing
project(mathing)
 
# Wir definieren die Version unseres Projekts
set(MATHING_MAJOR 1)
set(MATHING_MINOR 2)
set(MATHING_PATCH 3)
 
# Die Quelltextdateien, die zu dem Beispielprojekt gehören
set(SOURCES func.cpp)
 
# Hier wird festgelegt, dass aus den Quelldateien 
# eine Bibliothek entstehen soll. Da hier nicht angegeben
# worden ist, ob die Bibliothek statisch oder dynamisch ist
# wird die standardmäßig auf statisch gesetzt.
add_library(${PROJECT_NAME} ${SOURCES})
 
# Einige Eigenschaften der Bibliothek sollen noch gesetzt werden.
# Falls es eine dynamische Bibliothek werden soll, dann werden
# auf diese Weise noch symbolische Links automatisch generiert
set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${MATHING_MAJOR}.${MATHING_MINOR}.${MATHING_PATCH} SOVERSION ${MATHING_MAJOR})
 
# Was für ein Paket wollen wir haben? Ein RedHat Paket
# deshalb wird der CPack Generator auf RPM gesetzt
set(CPACK_GENERATOR "RPM")
 
# Name des Pakets, das am Schluss herauskommt
# In diesem Fall benennen wir es wie das Projekt
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
 
# Auch das Paket bekommt eine Version, in diesem
# Fall diesselbe, wie die Bibliothek. Bei größeren Projekte
# kann man aber auch eine andere Version für das 
# endgültige RPM Paket angeben
set(CPACK_PACKAGE_VERSION ${MATHING_MAJOR}.${MATHING_MINOR}.${MATHING_PATCH})
 
# Hier wird angegeben, welche Datei / bzw. CMake-Target wohin kopiert
# werden soll. Da es sich um eine Bibliothek handelt, wird die
# Bibliothek nach lib kopiert.
install(TARGETS ${PROJECT_NAME} DESTINATION lib)
 
# CPack zur CMakeLists.txt hinzufügen
# Diese Zeile muss am Schluss stehen.
include(CPack)

Nun kann man die kleine Bibliothek erstellen und in ein Paket packen:

cmake . && make && make package

Am Schluss purzelt ein ein Paket names mathing-1.2.3-Linux.rpm heraus. Dieses kann man mit yum install auf einem anderen RedHat System installieren. Die Liste der Dateien, die dann installiert werden, bekommt man übrigens über den folgenden Befehl heraus:

rpm -qlp mathing-1.2.3-Linux.rpm

Ich hoffe, das kleine Beispiel hat euch gefallen.

CMake – Shared objects und ihre Versionsnummern

In diesem Post möchte ich ein kleines Beispiel zeigen, wie man bei shared objects unter Linux die Versionsnummer hinzufügt. Das heißt, die Bibliothek, die herauskommt, soll nicht nur beispielsweise libbibliothek.so heißen, sondern es sollten zusätzlich noch zwei weitere symbolische Links generiert werden, mit der Versionsnummer daran, die dann auf die Bibliothek zeigen. Außerdem sollte die Bibliothek eine Versionsnummer haben. Will man dies manuell machen, kann das zu einigem Aufwand führen. Aber auch CMake hat eine solche Funktionalität 😉

Hier mal ein Beispiel (wenn man nach dem Erstellen der Bibliothek ls -l aufruft):

lrwxrwxrwx. 1 root root    17 Apr  5 16:25 libsoname_ex.so -> libsoname_ex.so.2
lrwxrwxrwx. 1 root root    21 Apr  5 16:25 libsoname_ex.so.2 -> libsoname_ex.so.2.5.8
-rwxr-xr-x. 1 root root  7861 Apr  5 16:25 libsoname_ex.so.2.5.8

Code der Bibliothek

Dazu habe ich wieder eine Beispiel-C++-Datei erstellt (func.cpp):

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

Wie man sieht, recht simpel.

CMakeLists.txt

Als nächstes die CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
project(soname_ex)

set(SOURCES func.cpp)

add_library(${PROJECT_NAME} SHARED ${SOURCES})
set_target_properties(${PROJECT_NAME} PROPERTIES VERSION 2.5.8 SOVERSION 2)

Die wichtigste Funktion steht am Ende der Datei: set_target_properties. Diese Funktion kann natürlich noch einiges mehr als nur die Versionsnummer setzen, aber in diesem Beispiel will ich mich nur auf das konzentrieren. Wenn die CMakeLists.txt fertig ist, kann man die Bibliothek kompilieren:

cmake . && make

CMake – Release vs. Debug

Mit CMake lassen sich sowohl Debug als auch Release Versionsn von Projekten erstellen. Dazu muss man den CMake-Kommandozeilenbefehl etwas abgeändert aufrufen. In diesem Beispielkommandos nehme ich an, dass man mit der Kommandozeile in den Ordner gewechselt ist, in dem sich die CMakeLists.txt befindet.

Debug

cmake -DCMAKE_BUILD_TYPE=Debug .

Release

cmake -DCMAKE_BUILD_TYPE=Release .

EASy68k – Der erste Versuch, eine Zahl in einen String umzuwandeln

Ich habe versucht, eine Zahl in einen String umzuwandeln. Im Grunde funktioniert der folgende Code auch, aber die Zahl wird in umgekehrter Reihenfolge in der Konsole geschrieben. Wenn man beispielsweise die Zahl 2335 ausgeben möchte, sieht man in der Konsole die Zahl 5332. Die Idee hinter diesem Programm ist folgende: Wenn man in EASy68K eine Zahl durch eine andere teilt, steht im Datenregister D0 sowohl das Ergebnis der Teilung als auch der Rest. Die niederwertigen vier Bytes enthalten das Ergebnis der Teilung, die höherwertigen vier Bytes enthalten den Rest der Teilung. Im ersten Durchgang teile ich also die Zahl, die ich ausgeben möchte durch 10. Auf diese Weise habe ich ein Ergebnis der Teilung (E1) und eine Ziffer zwischen 0 und 9 (das ist der Rest der Teilung, R1). R1 wird ausgegeben. Nun mache ich weiter, ich teile E1 durch 10, bekomme also E2 und R2. R2 wird ausgegeben, E2 wird anschließend wieder durch 10 geteilt. Dies wird solange durchgeführt, bis En 0 ist. Auf diese Weise wird die Zahl zwar ausgegeben, aber in umgekehrter Reihenfolge… noch 😉 😀

;-----------------------------------------------------------
; Title      :
; Written by :
; Date       :
; Description:
;-----------------------------------------------------------
    ORG    $1000
START:                  ; first instruction of program
 
; Put program code here
 
LOOP
 
    ; Kopiere Zahl nach D0
    MOVE.L      ZAHL,D0
 
    ; Kopiere 10 nach D1
    MOVE.L      #10,D1
 
    ; Teile die Zahl durch 10
    ; Ergebnis steht in D0
    DIVU        D1,D0
 
    ; Speicher Zahl zwischen
    MOVE.W      D0,D2
    MOVE.L      D2,ZAHL
 
    ; Hole den Rest der Division
    ; d.h. rechtshift um 16 Bits.
    LSR.L       #8,D0
    LSR.L       #8,D0
 
    ; Berechnen des ASCII-Wertes der Ziffer
    ; die nun ausgegeben werden soll
    ADD.L       #48,D0
 
    ; Das Zeichen ausgeben
    ; Der ASCII-Wert sollte in D1 stehen
    MOVE.L      D0,D1
    MOVE.B      #6,D0
    TRAP        #15
 
    ; Prüfe, ob die Zahl schon null ist
    ; Falls ja
    CMP.L       #0,ZAHL
    BEQ         DONE
    BRA         LOOP
 
DONE
 
    SIMHALT             ; halt simulator
 
; Put variables and constants here
ZAHL            DC.L        2335    ; Zahl, die in einen
                                    ; String umgewandelt werden
                                    ; soll
 
    END    START        ; last line of source

configure_file-Beispiel in CMake

In diesem Post möchte ich mal ein Beispiel der Funktion configure_file geben. Die Definition dieser Funktion lautet wie folgt

configure_file(<input> <output> [COPYONLY] [ESCAPE_QUOTES] [@ONLY] [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])

Die Datei kopiert eine Datei von <input> nach <output> und definiert Variablen und setzt ihre Werte. Mehr Details findet ihr hier.

Wir erstellen zuerst eine C++-Datei, das eine Version ausgeben soll und zwar die Version unseres Programms:

#include <iostream>
#include "version.h"
 
int main(int argc, char **argv)
{
        std::cout << "Version: ";
        std::cout << PROJ_MAJOR << ".";
        std::cout << PROJ_MINOR << ".";
        std::cout << PROJ_PATCH << std::endl;
 
        return 0;
}

Als nächstes schreiben wir eine version.h.in Datei:

#define PROJ_MAJOR @PROJ_MAJOR@
#define PROJ_MINOR @PROJ_MINOR@
#define PROJ_PATCH @PROJ_PATCH@

In dieser Datei sollen die Werte von PROJ_MAJOR, PROJ_MINOR und PROJ_PATCH gesetzt werden, wenn CMake gerade durchläuft. Auf diese Weise kann man dynamisch die Versionsnummer setzen (oder man kann dies über die Kommandozeile machen, so braucht man die CMakeLists.txt nicht anzufassen, siehe zweite CMakeLists.txt in dem Post). Hier also die erste CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
project(Config_File_Ex)

set(PROJ_MAJOR 1)
set(PROJ_MINOR 2)
set(PROJ_PATCH 3)

configure_file(version.h.in version.h)

set(SOURCES main.cpp)
set(HEADERS version.h)

add_executable(${PROJECT_NAME} ${SOURCES})

In diesem Beispiel werden die drei Variablen PROJ_MAJOR, PROJ_MINOR und PROJ_PATCH in der CMakeLists.txt definiert. Sollte einer der Variablen nicht definiert sein, dann steht später in der <output>-Datei nichts da, also ein leerer String quasi. Ihr könnt das ja austesten, indem ihr einer der drei Variablen mit einer Raute (#) auskommentiert.

Nun haben wir die drei Dateien main.cpp, version.h.in und CMakeLists.txt. Nun kann man den Build-Prozess starten:

cmake .
make
./Config_File_Ex

Ihr solltet dann eine Ausgabe wie diese hier bekommen:

Version: 1.2.3

Versionen als Parameter übergeben

Jetzt ändern wir mal die CMakeLists.txt, sodass die drei Variablen PROJ_MAJOR, PROJ_MINOR und PROJ_PATCH nicht mehr in der CMakeLists.txt stehen:

cmake_minimum_required(VERSION 3.5)
project(Config_File_Ex)

configure_file(version.h.in version.h)

set(SOURCES main.cpp)
set(HEADERS version.h)

add_executable(${PROJECT_NAME} ${SOURCES})

Nun kann man die Werte der Variablen auch über die Kommandozeile eingeben, und zwar mit folgendem Befehl:

cmake -DPROJ_MAJOR=2 -DPROJ_MINOR=5 -DPROJ_PATCH=8 .
make
./Config_File_Ex

Herauskommen müsste eine Ausgabe wie:

Version: 2.5.8

Couldn’t find a tree builder with the features you requeste d: html5lib.

Beim Versuch, auf einem Windows Rechner ein Python Skript von mir laufen zu lassen, kam folgende Meldung:

Traceback (most recent call last):
  File "csrf.py", line 485, in 
    soup = getSoup(resp)
  File "csrf.py", line 57, in getSoup
    soup = bs4.BeautifulSoup(response, "html5lib")
  File "C:\Python34\lib\site-packages\bs4\__init__.py", line 156, in __init__
    % ",".join(features))
bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: html5lib. Do you need to install a parser library?

Lösung

Die Lösung war:

pip install html5lib

EASy68K – Datei lesen ohne NULL

Mittlerweile habe ich herausgefunden, wie man eine Datei liest und den Inhalt in die Konsole ausgibt, wenn die Datei kein NULL-Zeichen am Ende der Datei enthält (was gerade bei String-Ausgaben wichtig ist). Hierbei lese ich Zeichen für Zeichen ein, gebe Zeichen für Zeichen aus und prüfe jedesmal, ob schon das Ende der Datei erreicht worden ist (siehe Programmcode). Das ist zwar eine etwas langsame Variante, aber funktionieren tut sie dennoch (und ich lerne momentan noch). Also hier der Programmcode:

;-----------------------------------------------------------
; Title      :
; Written by :
; Date       :
; Description:
;-----------------------------------------------------------
    ORG    $1000
START:                  ; first instruction of program
 
; Put program code here
        ; Datei 'test1.txt' lesen
        LEA         FILENAME,A1
        MOVE        #51,D0
        TRAP        #15
 
        ; Speichere File-ID
        ; Speichern der File-ID ist nötig,
        ; um das D1-Register wieder mit der File-ID
        ; zu füllen, da es sonst zu Fehlern kommt.
        ; Falls nämlich D1 einen anderen Wert hat als
        ; die File-ID, dann kann EASy68K 
        ; die Datei nicht lesen.
        MOVE.L      D1,FILE_ID
 
SCHLEIFE
        ; Setze den Lesecursor
        ; auf das nächste Zeichen, das
        ; gelesen werden soll.
        MOVE.L      #55,D0
        MOVE.L      FILE_POS,D2
        TRAP        #15
 
        ; Lese ein Zeichen
        LEA         BUFFER,A1
        MOVE.L      #1,D2
        MOVE.L      #53,D0
        TRAP        #15
 
        ; Prüfe, ob EOF erreicht worden ist
        CMP.L       #1,D0
        BEQ         ENDE
 
        ; Prüfe, ob ein Fehler aufgetreten ist
        CMP.L       #2,D0
        BEQ         FEHLER
 
        ; Gib das Zeichen aus, das gelesen worden ist
        ; in der Konsole aus
        MOVE.L      #0,D1
        MOVE.B      BUFFER,D1
        MOVE.L      #6,D0
        TRAP        #15
 
        ; Setze die File-ID wieder ein
        MOVE.L      FILE_ID,D1
 
        ; Erhöhe die Leseposition um eins
        ADD.L       #1,FILE_POS
 
        ; Springe zum Schleifen anfang
        BRA         SCHLEIFE
 
FEHLER
        ; Gib eine Fehlermeldung aus
        LEA         ERR_MESS,A1
        MOVE.L      #14,D0
        TRAP        #15
 
        ; Und springe zum Ende
        BRA         ENDE
 
ENDE
        ; Schliessen aller Dateien
        MOVE.L      #50,D0
        TRAP        #15
 
    SIMHALT             ; halt simulator
 
; Put variables and constants here
FILENAME        DC.B        'test2.txt',0
FILE_POS        DC.L        0
BUFFER          DC.B        0
ERR_MESS        DC.B        'Es ist ein Fehler aufgetreten',0
FILE_ID         DC.L        0
 
    END    START        ; last line of source