Vyhledání a odstranění zbytečných #includů hlavičkových souborů v C++

Od Petr Zemek, 2014-12-15

O důležitosti snižování počtu #includovaných hlavičkových souborů v C a C++ jsem zde již psal. Důvodem je urychlení překladu, především pak toho inkrementálního během vývoje. V dnešním příspěvku se podíváme na nástroj include-what-you-use, který umí zbytečné #includy detekovat a případně i automaticky odstranit či nahradit za dopředné deklarace.

Popis nástroje

Nástroj include-what-you-use je určen pro vyhledání zbytečných #includů a jejich případné odstranění/nahrazení za dopředné deklarace. Je postaven nad Clangem, což je C/C++/Objective-{C,C++} front-end pro LLVM. Výhoda je, že zdrojáky skutečně parsuje, což má za následek přesnější výsledky, než kdyby se jednalo o řešení postavené na textovém zpracování zdrojáků (regulární výrazy apod.).

Podle oficiálních stránek by tento nástroj měl být použitelný i pro projekty v C. Já jsem jej však zkoušel pouze na projektu napsaném v C++. Ve zbytku příspěvku se budu tedy zabývat jen C++. Nástroj jsem dále zkoušel pouze na Linuxu. Jak je to s jeho podporou na Windows jsem nezjišťoval.

Instalace

Dle návodu je více možností instalace, závisejících na tom, zda již máte někde přeložené LLVM a Clang. Mně se osvědčil následující způsob, který zmínil jeden uživatel v komentářích. Pomocí něj proběhne stáhnutí a přeložení všeho potřebného:

svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm
cd llvm/tools
svn co http://llvm.org/svn/llvm-project/cfe/trunk clang
cd clang/tools
svn co http://include-what-you-use.googlecode.com/svn/trunk/ include-what-you-use
echo "add_subdirectory(include-what-you-use)" >> CMakeLists.txt
sed -i 's/^DIRS.*:=/DIRS := include-what-you-use/' Makefile
cd ../../..
mkdir build
cd build
cmake ..
make -j$PROC

Za $PROC jen doplníte počet procesorů/jader, která se pro překlad mají použít. Nyní už stačí jen počkat, než překlad doběhne (v závislosti na výkonnosti vašeho HW počítejte klidně i s hodinou). Po doběhnutí překladu byste v adresáři llvm/build/bin měli mít (mimo jiné) program include-what-you-use.

Spuštění

Opět existuje více způsobů, jak program spustit. Mně se osvědčil následující způsob, prezentovaný v jednom komentáři v návodu. Do zvoleného adresáře (např. $HOME/bin) vložíte spustitelný skript s následujícím obsahem:

#!/bin/bash
/where/your/compiler/is/bin/`basename $0` "$@" || exit $?
args="$*"
if [ "${args#* -c}" != "$args" ]; then
    out="${args#* -o}"
    out="${out%.o *}"
    if [ "x$out" != "x" ]; then
        /path/to/llvm/build/bin/include-what-you-use "$@" > $out.iwyu.txt 2>&1
    fi
fi
exit 0

Název skriptu by měl korespondovat k překladači, který používáte k překladu (g++, c++, clang++ apod.). Poté si v shellu změníte PATH tak, aby se místo překladače spustil onen skript:

export PATH="$HOME/bin:$PATH"

Co vytvořený skript dělá, tak je, že zavolá překladač s předanými parametry. Pokud překlad proběhl úspěšně, tak následně spustí onen program include-what-you-use s tím, že výsledky analýzy uloží do souboru se suffixem .iwyu.txt. Pokud se např. překládá modul x.cpp do x.o, tak výsledky pro tento soubor lze nalézt v x.iwyu.txt.

Nyní už stačí jen spustit překlad vašeho projektu dle použitého build systému (CMake, make atd.). Při překladu se budou překládané soubory průběžně analyzovat a výsledky ukládat.

Samozřejmě, tento skript a úpravu PATH použijte pouze pro získání výsledků. Po jejich získání si PATH vraťte zpět. Ona analýza je časově náročná a pravděpodobně nechcete, aby běžela i při běžných překladech.

Interpretace výsledků

Každý z vygenerovaných textových souborů má následující strukturu:

/path/to/file.h should add these lines:
[..]
 
/path/to/file.h should remove these lines:
[..]
 
The full include-list for /path/to/file.h:
[..]
---
 
/path/to/file.cpp should add these lines:
[..]
 
/path/to/file.cpp should remove these lines:
[..]
 
The full include-list for /path/to/file.cpp:
[..]
---

Místo [..] tam jsou uvedeny hlavičkové soubory a dopředné deklarace. Příklad:

#include <memory>                    // for shared_ptr
#include <utility>                   // for pair
namespace ns1 { namespace ns2 { class CustomClass; } }

kde CustomClass je třída ve jmenném prostoru ns1::ns2, u níž není potřeba #includovat hlavičkový soubor, ale stačí použít dopřednou deklaraci.

Poznámka: nástroj ignoruje hlavičkové soubory, ke kterým neexistuje implementační soubor (.cpp apod.).

Využití výsledků

Výsledky lze využít následujícími způsoby:

  • Manuální úpravy. Pokud se jedná o menší projekt, tak může stačit projít výsledky a soubory upravit manuálně.
  • Automatické úpravy pomocí přiloženého skriptu. Dle návodu můžete využít Python skript fix_includes.py pro automatickou opravu #includů a přidání dopředných deklarací. V případě potřeby si onen skript můžete upravit, aby vyhovoval vašim potřebám.

Jiné nástroje

Kromě popsaného nástroje samozřejmě existují i jiné, které můžete vyzkoušet. Zkuste mrknout na následující odkazy:

Obsah tohoto pole je soukromý a nebude veřejně zobrazen.

Filtrované HTML (využíváno)

  • Povolené HTML značky: <a href hreflang> <em> <strong> <cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <table>
  • Zvýraznění syntaxe kódu lze povolit přes následující značky: <code>, <blockcode>, <bash>, <c>, <cpp>, <haskell>, <html>, <java>, <javascript>, <latex>, <perl>, <php>, <python>, <ruby>, <rust>, <sql>, <text>, <vim>, <xml>, <yaml>.
  • Řádky a odstavce se zalomí automaticky.
  • Webové a e-mailové adresy jsou automaticky převedeny na odkazy.
CAPTCHA
3 + 0 =
Vyřešte tento jednoduchý matematický příklad a vložte výsledek. Např. pro 1+3 vložte 4.
Nějak se mi tady rozmohl spam, takže poprosím o ověření.