Include What You Use

Das im Rahmen von clang entwickelte Tool include-what-you-use hilft dabei, die #include-Anweisungen in C und C++ Quellcode zu optimieren. Wir zeigen im Folgenden, wie man dieses Tool (unabhängig vom clang-Compiler) verwenden und in CMake-Projekte einbinden kann.

Generell sollte man #include-Anweisungen in C/C++ so verwenden, dass in einem File genau die Deklarationen zur Verfügung stehen, die der Compiler zu dessen Übersetzung tatsächlich benötigt. Dort, wo es möglich ist Symbole mit Hilfe von Forward-Deklarationen zur Verfügung zu stellen, sollte man dies bevorzugen. Unnötige #include-Anweisungen verschlechtern die Wartbarkeit des Codes und erhöhen die Zeit für den Build-Vorgang. Es ist in der Praxis aber oft gar nicht so einfach zu erkennen, welche #include-Anweisungen tatsächlich benötigt werden und welche nicht. An dieser Stelle hilft uns include-what-you-use (kurz IWYU).

Unser Beispiel wurde auf Linux entwickelt und getestet, lässt sich aber ohne große Mühe auch auf Windows oder OS-X übertragen. Die im Beispiel verwendeten Quellen stehen in unserem öffentlichen Git-Archiv für Sie bereit: https://github.com/maurer-treutner/iwyu_sample.

Installation

Die für Ubuntu 16.04 über apt derzeit (Anfang Juli 2017) verfügbare Version von IWYU ist ziemlich veraltet, deshalb installieren wir die aktuelle Version aus dem binären Paket. In der Shell führen wir dazu folgende Kommandos aus:

curl -O https://include-what-you-use.org/downloads/include-what-you-use-0.7-x86_64-linux-gnu-ubuntu-16.04.tar.gz
tar -xvzf include-what-you-use-0.7-x86_64-linux-gnu-ubuntu-16.04.tar.gz
sudo mv include-what-you-use/bin/* /usr/local/bin
sudo mv include-what-you-use/lib/* /usr/local/lib/
sudo mkdir -p /usr/local/share/clang/include-what-you-use/
sudo mv include-what-you-use/mappings /usr/local/share/clang/include-what-you-use/
sudo ln /usr/local/bin/include-what-you-use /usr/local/bin/iwyu

Jetzt können wir die Version von IWYU abfragen:

$ include-what-you-use --version
include-what-you-use 0.7 (git:136736f) based on clang version 3.9.0 (tags/RELEASE_390/final)

Ein einfacher Test

Zunächst wollen wir IWYU mit einer einfachen C++ Quelle testen, die wir als iwyu_test.cxx anlegen:

#include <cstdarg>
#include <iostream>
#include <vector>

int main(int argc,char *argv[])
{
	std::cout<<"Test iwyu"<<std::endl;
}

Offensichtlich werden die beiden includierten Dateien <cstdarg> und <vector> für die Übersetzung unseres Programms nicht benötigt. Wir wenden IWYU an, um zu überprüfen, ob das Tool das findet: 

$ iwyu iwyu_test.cxx

Dieses Kommando liefert uns folgende Ausgabe:


iwyu_test.cxx should add these lines:

iwyu_test.cxx should remove these lines:
- #include <cstdarg>  // lines 1-1
- #include <vector>  // lines 3-3

The full include-list for iwyu_test.cxx:
#include <iostream>  // for operator<<, basic_ostream, cout, endl, ostream
---

Die überflüssigen #include-Anweisungen werden also wie erwartet gefunden.

IWYU enthält das Python-Programm fix_includes.py, das die Ausgabe von IWYU auswertet und die #include-Anweisungen automatisch korrigiert. Mit der Option --dry_run kann man das Kommando dazu bringen, die Änderungen auszugeben, anstatt sie direkt auf unsere Quelle anzuwenden. Das Kommando

$ iwyu iwyu_test.cxx 2>&1 | fix_inlcudes.py --dry_run

erzeugt folgende Ausgabe (die Ausgabe von iwyu erfolgt auf standard error, deshalb müssen wir die Ausgabe des Kommandos mit  2>&1 auf standard output umleiten):

>>> Fixing #includes in 'iwyu_test.cxx'
@@ -1,6 +1,4 @@
-#include <cstdarg>
 #include <iostream>
-#include <vector>

 int main(int argc,char *argv[])
 {
IWYU edited 0 files on your behalf.

 

Wenn wir die Option --dry_run weglassen, werden die Änderungen von fix_includes.py direkt in unsere Datei iwyu_test.cxx geschrieben und wir erhalten:

#include <iostream>

int main(int argc,char *argv[])
{
	std::cout<<"Test iwyu"<<std::endl;
}

Einbinden in CMake

CMake bietet ab Version 3.3 das Property <LANG>_INCLUDE_WHAT_YOU_USE, mit dessen Hilfe der Aufruf von IWYU während des Make-Vorgangs automatisiert werden kann. Das Property erfordert einen Pfad auf das Programm include-what-you-use, den wir in unserer CMakeLists.txt mit Hilfe folgender Anweisungen ermitteln können:

# For iwyu support, we need the program inclued-what-you-use. 
find_program(iwyu_path NAMES include-what-you-use iwyu)
if(NOT iwyu_path)
  message(WARNING "include-what-you-use not found, no analysis of include files possible")
endif()

Um die Quellen eines C++ Targets mit IWYU überprüfen zu können, setzen wir für das Target unser Property CPP_INCLUDE_WHAT_YOU_USE:

# if iwyu has been found, we set the property to use it during our build
if(iwyu_path)
    set_property(TARGET ${APP_NAME} PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${iwyu_path})
endif()

Die mit CMake erzeugten Makefiles rufen include-what-you-use nun für alle Quellen auf, die zu unserem Target gehören – die entsprechenden Ausgaben erfolgen im Rahmen des Build. Wenn wir make auf die erzeugten Makefiles duchführen, erhalten wir in der Ausgabe die Informationen von IWYU, z.B.:

[ 50%] Building CXX object iwyu_sample/CMakeFiles/IVYUSample.dir/HelloHelper.cxx.o
cd /home/peter/github.maurer-treutner/iwyu_sample/build/debug/iwyu_sample && /usr/local/bin/cmake -E __run_iwyu --iwyu=/usr/local/bin/include-what-you-use -- /usr/bin/c++    -g   -std=gnu++14 -o CMakeFiles/IVYUSample.dir/HelloHelper.cxx.o -c /home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/HelloHelper.cxx
Warning: include-what-you-use reported diagnostics:

/home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/HelloHelper.h should add these lines:
class LangHelper;

/home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/HelloHelper.h should remove these lines:
- #include "LangHelper.h"  // lines 17-17

The full include-list for /home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/HelloHelper.h:
#include <memory>  // for shared_ptr
#include <string>  // for string
class LangHelper;
---

Unser Beispiel zeigt, dass IWYU auch analysiert, ob eine Forward-Deklaration anstelle einer #include-Anweisung möglich ist.

Verwendung mit einer Compilation Database

Neben der oben beschriebenen Methode, bei der IWYU direkt im Build-Vorgang aufgerufen wird, kann man auch eine Compilation Database verwenden. Mit Hilfe der Option CMAKE_EXPORT_COMPILE_COMMANDS kann man CMake (ab der Version 3.5) dazu bringen, eine Compilation Database anzulegen, die den Compiler-Aufruf für alle Übersetzungseinheiten enthält. Das Python-Programm iwyu_tool.py wertet diese Datei aus und ruft IWYU für alle darin enthaltenen Übersetzungseinheiten auf. Die Compilation Database wird beim Aufruf von CMake erzeugt, wenn CMAKE_EXPORT_COMPILE_COMMANDS gesetzt ist. Wir legen dazu im Rootverzeichnis unsers Projekts ein Verzeichnis build an, wechseln in dieses Verzeichnis und geben ein:

$ cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ../src

In unserem Verzeichnis build befindet sich nun die von CMake erzeugte Makefile-Struktur und die Compilation Database compile_command.json. Wir können jetzt iwyu_tool.py für unser Make-Verzeichnis aufrufen:

$ iwyu_tool.py -p .

Damit wird IWYU für alle Übersetzungseinheiten unseres Beispielprojekts aufgerufen und wir erhalten folgende Ausgabe:


/home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/IVYUSampleMain.cxx should add these lines:

/home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/IVYUSampleMain.cxx should remove these lines:
- #include <iostream>  // lines 10-10

The full include-list for /home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/IVYUSampleMain.cxx:
#include "HelloHelper.h"  // for HelloHelper
---

/home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/HelloHelper.h should add these lines:
class LangHelper;

/home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/HelloHelper.h should remove these lines:
- #include "LangHelper.h"  // lines 17-17

The full include-list for /home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/HelloHelper.h:
#include <memory>  // for shared_ptr
#include <string>  // for string
class LangHelper;
---

/home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/HelloHelper.cxx should add these lines:
#include "LangHelper.h"  // for LangHelper, LangHelper::::ge_ge

/home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/HelloHelper.cxx should remove these lines:

The full include-list for /home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/HelloHelper.cxx:
#include "HelloHelper.h"
#include <iostream>      // for basic_ostream, operator<<, cout, endl, ostream
#include "LangHelper.h"  // for LangHelper, LangHelper::::ge_ge
---

(/home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/LangHelper.h has correct #includes/fwd-decls)

(/home/peter/github.maurer-treutner/iwyu_sample/src/iwyu_sample/LangHelper.cxx has correct #includes/fwd-decls)

Natürlich können wir auch iwyu_tool.py nutzen, um die #include-Anweisungen mit Hilfe von fix_includes.py automatisch anzupassen. Wir geben dazu folgendes Kommando ein:

$ iwyu_tool.py -p . 2>&1 | fix_includes.py

Dadurch werden alle von IWYU vorgeschlagenen Änderungen in unseren Quelldateien durchgeführt.

Fazit

Bei IWYU handelt es sich um ein sehr nützliches Tool, das dazu beitragen kann die Qualität und Wartbarkeit von C/C++ Quellen nachhaltig zu verbessern. Durch die gute Einbindung in CMake ist es einfach, IWYU in CMake-basierten Projekten zu verwenden. Mit Hilfe einer Reihe von IWYU-Pragmas ist es möglich, das Tool mit zusätzlichen Informationen zu versorgen, die aus den Quellen nicht direkt hervorgehen und so unerwünschte Änderungen an den Quellen zu verhindern. Gerade für größere Projekte bei denen der Compiliervorgang relativ lang dauert, lohnt sich der Einsatz von IWYU.

Telefon: 07247-954550
info@maurer-treutner.de