ClangFormat-Quellcode-Formatierung unter Linux: Probleme und Lösungen



Stimmen Sie zu, es ist schön und nützlich, wenn der Quellcode im Projekt schön und konsistent aussieht. Dies erleichtert sein Verständnis und seine Unterstützung. Wir zeigen und erklären Ihnen, wie Sie die Quellcode-Formatierung mit clang-format , git und sh implementieren.

Formatierungsprobleme und deren Lösung


In den meisten Projekten gibt es bestimmte Regeln für das Entwerfen des Codes. Wie kann sichergestellt werden, dass alle Teilnehmer sie ausführen? Spezielle Programme helfen - Clang-Format, Astyle, Uncrustify - aber sie haben ihre Nachteile.

Das Hauptproblem bei Formatierern besteht darin, dass sie ganze Dateien ändern, nicht nur geänderte Zeilen. Wir werden Ihnen erklären, wie wir damit umgegangen sind, indem wir ClangFormat als Teil eines der Projekte zur Entwicklung von Firmware für die Elektronik verwendet haben, bei der C ++ die Hauptsprache war. Im Team arbeiteten mehrere Personen, daher war es uns wichtig, einen einheitlichen Codestil bereitzustellen. Unsere Lösung eignet sich nicht nur für C ++ - Programmierer, sondern auch für diejenigen, die Code in C, Objective-C, JavaScript, Java und Protobuf schreiben.

Für die Formatierung haben wir clang-format-diff-6.0 verwendet . Zu Beginn haben sie das Team gegründet

git diff -U0 --no-color | clang-format-diff-6.0 -i -p1 , aber es gab Probleme damit:


  1. Das Programm bestimmte Dateitypen nur durch Erweiterung. Beispielsweise wurden Dateien mit der Erweiterung ts, die wir im XML-Format hatten, als JavaScript wahrgenommen und stürzten beim Formatieren ab. Dann versuchte sie aus irgendeinem Grund, die Pro-Dateien von Qt-Projekten zu reparieren, wahrscheinlich wie Protobuf.
  2. Das Programm musste manuell gestartet werden, bevor Dateien zum Git-Index hinzugefügt wurden. Es war leicht, es zu vergessen.

Lösung


Das Ergebnis war das folgende sh-Skript, das als Pre-Commit ausgeführt wurde - Hook für Git:

#!/bin/sh CLANG_FORMAT="clang-format-diff-6.0 -p1 -v -sort-includes -style=Chromium -iregex '.*\.(cxx|cpp|hpp|h)$' " GIT_DIFF="git diff -U0 --no-color " GIT_APPLY="git apply -v -p0 - " FORMATTER_DIFF=$(eval ${GIT_DIFF} --staged | eval ${CLANG_FORMAT}) echo "\n------Format code hook is called-------" if [ -z "${FORMATTER_DIFF}" ]; then echo "Nothing to be formatted" else echo "${FORMATTER_DIFF}" echo "${FORMATTER_DIFF}" | eval ${GIT_APPLY} --cached echo " ---Format of staged area completed. Begin format unstaged files---" eval ${GIT_DIFF} | eval ${CLANG_FORMAT} | eval ${GIT_APPLY} fi echo "------Format code hook is completed----\n" exit 0 

Was das Skript macht:
GIT_DIFF = "git diff -U0 --no-color" - Änderungen im Code, der in clang-format-diff-6.0 eingegeben wird .

  • -U0 : Normalerweise zeigt git diff den sogenannten "Kontext" an: einige unveränderte Codezeilen um die geänderten. Aber clang-format-diff-6.0 formatiert sie auch! Daher wird der Kontext in diesem Fall nicht benötigt.

CLANG_FORMAT = "clang-format-diff-6.0 -p1 -v -sort-enthält -style = Chromium -iregex '. * \. (Cxx | cpp | hpp | h) $'" - ein Befehl zum Formatieren von Diff, der über Standard empfangen wird Eingabe.

  • clang-format-diff-6.0 - ein Skript aus dem Paket clang-format-6.0 . Es gibt andere Versionen, aber alle Tests waren nur auf dieser.
  • -p1 aus Beispielen in der Dokumentation , bietet Kompatibilität mit der Git-Diff- Ausgabe.
  • -style = Chromium - Voreinstellung des vorgefertigten Codeformats. Andere mögliche Werte: LLVM, Google, Mozilla, WebKit .
  • -sort- include - Option zum alphabetischen Sortieren von # include- Direktiven (optional).
  • -iregex '. * \. (cxx | cpp | hpp | h) $' ist ein regulärer Ausdruck, der Dateinamen nach Erweiterung filtert. Hier werden nur die Erweiterungen aufgelistet, die formatiert werden müssen. Dies schützt das Programm vor Stürzen und unerwarteten Störungen. Höchstwahrscheinlich muss die Liste in neuen Projekten ergänzt werden. Zusätzlich zu C ++ können Sie C / Objective-C / JavaScript / Java / Protobuf formatieren . Obwohl wir diese Dateitypen nicht getestet haben.

GIT_APPLY = "git apply -v -p0 -" - wendet den vom vorherigen Befehl ausgegebenen Patch auf den Code an.

  • -p0 : Standardmäßig überspringt git apply die erste Komponente im Dateipfad. Dies ist nicht kompatibel mit dem Format, das clang-format-diff-6.0 erzeugt. Dieses Überspringen ist hier deaktiviert.

FORMATTER_DIFF = $ (eval $ {GIT_DIFF} --staged | eval $ {CLANG_FORMAT}) - Formatierungsänderungen für den Index.

echo "$ {FORMATTER_DIFF}" | eval $ {GIT_APPLY} --cached formatiert den Quellcode im Index (nach dem Hinzufügen von Git ). Leider gibt es keinen Hook, der funktioniert, bevor Dateien zum Index hinzugefügt werden. Daher ist die Formatierung in zwei Teile unterteilt: Formatieren der Inhalte im Index und separate Formatierungen der nicht zum Index hinzugefügten Elemente.

eval $ {GIT_DIFF} | eval $ {CLANG_FORMAT} | eval $ {GIT_APPLY} - Die Code-Formatierung befindet sich nicht im Index (sie beginnt erst, wenn etwas im Index formatiert wurde). Formatiert im Allgemeinen alle aktuellen Änderungen im Projekt (unter Versionskontrolle) und nicht nur aus dem vorherigen Schritt. Dies ist auf den ersten Blick eine kontroverse Entscheidung. Aber es stellte sich als bequem heraus, weil früher oder später müssen auch andere Änderungen formatiert werden. Sie können "| eval $ {GIT_APPLY}" durch die Option -i ersetzen, wodurch $ {CLANG_FORMAT} die Dateien selbst ändert.

Arbeitsdemonstration


  1. Installieren Sie clang-format-6.0
  2. cd / tmp && mkdir temp_project && cd temp_project
  3. git init
  4. Fügen Sie die Versionskontrolle hinzu und schreiben Sie eine C ++ - Datei unter dem Namen false.cpp fest . Vorzugsweise> 50 Zeilen unformatierten Codes.
  5. Erstellen Sie das oben gezeigte Skript .git / hooks / pre-commit .
  6. Weisen Sie das Recht zum Ausführen des Skripts (für git) zu: chmod + x .git / hooks / pre-commit .
  7. Führen Sie das Skript .git / hooks / pre-commit manuell aus. Es sollte mit der Meldung "Nichts zu formatieren" ohne Interpreterfehler ausgeführt werden.
  8. Erstellen Sie die Datei file.cpp mit dem Inhalt int main () {for (int i = 0; i <100; ++ i) {std :: cout << "Erster Fall" << std :: endl; std :: cout << "Zweiter Fall" << std :: endl; std :: cout << "Dritter Fall" << std :: endl; }} mit einer Zeile oder mit einer anderen schlechten Formatierung. Am Ende - Zeilenvorschub!
  9. git add file.cpp && git commit -m "file.cpp" sollte Nachrichten aus einem Skript wie "Patch file.cpp ohne Fehler angewendet" sein.
  10. git log -p -1 sollte das Hinzufügen einer formatierten Datei anzeigen.
  11. Wenn file.cpp wirklich formatiert in das Commit gelangt ist , können Sie die Formatierung nur in diff testen. Ändern Sie das Zeilenpaar false.cpp so, dass der Formatierer darauf reagiert . Fügen Sie Ihrem Code beispielsweise zusammen mit anderen Änderungen eine unzureichende Einrückung hinzu. git commit -a -m "Nur Format formatieren " sollte die formatierten Änderungen ausfüllen, aber keine Auswirkungen auf andere Teile der Datei haben.

Nachteile und Probleme


git diff --staged (hier $ {GIT_DIFF} --staged ) unterscheidet nur die Dateien, die dem Index hinzugefügt wurden. Und clang-format-diff-6.0 greift auf die Vollversionen von Dateien außerhalb zu. Wenn Sie also eine Datei ändern, git hinzufügen und dann dieselbe Datei ändern, generiert clang-format-diff-6.0 einen Patch, um den Code (im Index) basierend auf einer anderen Datei zu formatieren. Daher ist es besser, die Datei nach dem Hinzufügen von Git und vor dem Festschreiben nicht zu bearbeiten.

Hier ist ein Beispiel für einen solchen Fehler:

  1. Fügen Sie file.cpp , "Zweiter Fall", zusätzliches std :: endl hinzu . (std :: cout << "Zweiter Fall" << std :: endl << std :: endl;) und einige Tabulatoren mit zusätzlichen Einrückungen vor der Zeile.
  2. git add file.cpp
  3. Löschen Sie die Zeile (in derselben Datei) mit "Erster Fall", sodass nur der Zeilenumbruch an seiner Stelle (!) Bleibt.
  4. git commit -m "Formatierungsfehler beim Festschreiben" .

Das Skript sollte "Fehler: während der Suche:" melden, d. H. git apply hat den Kontext des von clang-format-diff-6.0 ausgegebenen Patches nicht gefunden. Wenn Sie nicht verstehen, wo das Problem liegt, ändern Sie die Dateien einfach nicht, nachdem git sie hinzugefügt und git festgeschrieben hat . Wenn Sie Änderungen vornehmen müssen, können Sie (ohne Push) festschreiben und dann git commit - mit neuen Änderungen ändern.

Die schwerwiegendste Einschränkung ist die Notwendigkeit eines Zeilenumbruchs am Ende jeder Datei. Dies ist eine alte Git-Funktion, daher unterstützen die meisten Code-Editoren das automatische Einfügen einer solchen Übersetzung am Ende der Datei. Ohne dies stürzt das Skript beim Festschreiben einer neuen Datei ab, kann jedoch keinen Schaden anrichten.


Sehr selten formatiert clang-format-diff-6.0 den Code unangemessen. In diesem Fall können Sie dem Code einige nutzlose Elemente hinzufügen, z. B. ein Semikolon. Oder umgeben Sie den problematischen Code mit Kommentaren, / * clang-format off * / und / * clang-format on * / .


Auch clang-format-diff-6.0 kann einen unzureichenden Patch erzeugen. Dies führt dazu, dass git apply es nicht akzeptiert und der Code des Commit-Teils unformatiert bleibt. Der Grund liegt in clang-format-diff . Es ist keine Zeit, alle Programmfehler zu verstehen. In diesem Fall können Sie den Formatierungs-Patch mit dem Befehl git diff -U0 --no-color HEAD ^ | anzeigen clang-format-diff-6.0 -p1 -v -sort-enthält -style = Chromium -iregex '. * \. (cxx | cpp | hpp | h) $' . Die einfachste Lösung besteht darin, die Option -i zum vorherigen Befehl hinzuzufügen. In diesem Fall gibt das Dienstprogramm keinen Patch aus, sondern formatiert den Code. Wenn dies nicht hilft, können Sie versuchen, einzelne Dateien vollständig zu formatieren. Clang-format-6.0 -i -sort-includes -style = Chromium file.cpp . Als nächstes folgt git add file.cpp und git commit --amend .

Es wird davon ausgegangen, dass je näher Ihre Konfiguration im .clang-Format an einer der Voreinstellungen liegt, desto weniger solche Fehler werden angezeigt . (Hier wird es durch die Option -style = Chromium ersetzt ).


Debuggen


Wenn Sie sehen möchten, welche Änderungen das Skript an Ihren aktuellen Änderungen vornimmt (nicht im Index), verwenden Sie git diff -U0 --no-color | clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex '. * \. (cxx | cpp | hpp | h) $' Sie können beispielsweise auch überprüfen, wie das Skript bei den neuesten Commits funktioniert , um dreißig: git filter-branch -f --tree-filter "$ {PWD} /. git / hooks / pre-commit" --prune-empty HEAD ~ 30..HEAD . Dieser Befehl sollte frühere Commits formatiert haben, aber tatsächlich ändert sich nur ihre ID. Daher lohnt es sich, solche Experimente in einer separaten Kopie des Projekts durchzuführen! Nachdem sie unbrauchbar wird.

Fazit


Subjektiv ist eine solche Entscheidung viel mehr gut als schaden. Sie müssen jedoch das Verhalten von Clang-Format-Diff verschiedener Versionen im Code Ihres Projekts mit einer Konfiguration für Ihren Codestil testen.

Leider haben wir nicht den gleichen Git-Hook für Windows gemacht. Schlagen Sie in den Kommentaren vor, wie es dort gemacht wird. Wenn Sie einen Artikel für einen schnellen Einstieg in das Clang-Format benötigen, empfehlen wir Ihnen, sich die ClangFormat-Beschreibung anzusehen .

Source: https://habr.com/ru/post/de433832/


All Articles