Formatage du code source de ClangFormat sous Linux: problèmes et solutions



D'accord, c'est agréable et utile lorsque le code source du projet est beau et cohérent. Cela facilite sa compréhension et son soutien. Nous vous montrons et vous expliquons comment implémenter la mise en forme du code source à l'aide de clang-format , git et sh .

Formater les problèmes et comment les résoudre


Dans la plupart des projets, il existe certaines règles de conception du code. Comment s'assurer que tous les participants les exécutent? Des programmes spéciaux viennent à la rescousse - format clang, astyle, peu crédible - mais ils ont leurs inconvénients.

Le principal problème avec les formateurs est qu'ils changent des fichiers entiers, pas seulement des lignes modifiées. Nous allons vous expliquer comment nous avons géré cela, en utilisant ClangFormat dans le cadre de l'un des projets de développement de micrologiciels pour l'électronique, où C ++ était le langage principal. Plusieurs personnes travaillaient dans l'équipe, il était donc important pour nous de fournir un style de code uniforme. Notre solution peut convenir non seulement aux programmeurs C ++, mais aussi à ceux qui écrivent du code en C, Objective-C, JavaScript, Java, Protobuf.

Pour le formatage, nous avons utilisé clang-format-diff-6.0 . Au début, ils ont commencé l'équipe

git diff -U0 - pas de couleur | clang-format-diff-6.0 -i -p1 , mais il y a eu des problèmes:


  1. Le programme a déterminé les types de fichiers uniquement par extension. Par exemple, les fichiers avec l'extension ts, que nous avions au format xml, ont été perçus comme JavaScript et se sont écrasés lors du formatage. Puis, pour une raison quelconque, elle a essayé de corriger les fichiers pro des projets Qt, probablement comme Protobuf.
  2. Le programme devait être démarré manuellement avant d'ajouter des fichiers à l'index git. C'était facile de l'oublier.

Solution


Le résultat était le sh-script suivant, exécuté en tant que pré-commit - hook pour 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 

Ce que fait le script:
GIT_DIFF = "git diff -U0 --no-color" - changements dans le code qui sera entré dans clang-format-diff-6.0.

  • -U0 : généralement git diff affiche le soi-disant «contexte»: quelques lignes de code inchangées autour de celles qui ont été modifiées. Mais clang-format-diff-6.0 les formate aussi! Par conséquent, le contexte dans ce cas n'est pas nécessaire.

CLANG_FORMAT = "clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex '. * \. (Cxx | cpp | hpp | h) $'" - une commande de formatage de diff reçue via la norme entrée.

  • clang-format-diff-6.0 - un script du package clang-format-6.0 . Il existe d'autres versions, mais tous les tests étaient uniquement sur celle-ci.
  • -p1 extrait des exemples de la documentation , fournit la compatibilité avec la sortie git diff .
  • -style = Chrome - prédéfini de style de format de code prêt à l'emploi. Autres valeurs possibles: LLVM, Google, Mozilla, WebKit .
  • -sort-includes - option pour trier alphabétiquement les directives #include (facultatif).
  • -iregex '. * \. (cxx | cpp | hpp | h) $' est une expression régulière qui filtre les noms de fichiers par extension. Seules les extensions qui doivent être formatées sont répertoriées ici. Cela évitera au programme de tomber et de pépins inattendus. Il est fort probable que la liste devra être complétée dans de nouveaux projets. En plus de C ++, vous pouvez formater C / Objective-C / JavaScript / Java / Protobuf . Bien que nous n'ayons pas testé ces types de fichiers.

GIT_APPLY = "git apply -v -p0 -" - applique le correctif émis par la commande précédente au code.

  • -p0 : par défaut, git apply ignore le premier composant du chemin du fichier, ce n'est pas compatible avec le format produit par clang-format-diff-6.0 . Ce saut est désactivé ici.

FORMATTER_DIFF = $ (eval $ {GIT_DIFF} --staged | eval $ {CLANG_FORMAT}) - modifications du formateur pour l'index.

echo "$ {FORMATTER_DIFF}" | eval $ {GIT_APPLY} --cached formate le code source dans l'index (après git add ). Malheureusement, aucun hook ne fonctionne avant d'ajouter des fichiers à l'index. Par conséquent, la mise en forme est divisée en deux parties: la mise en forme de ce qui est dans l'index et séparément ce qui n'est pas ajouté à l'index.

eval $ {GIT_DIFF} | eval $ {CLANG_FORMAT} | eval $ {GIT_APPLY} - le formatage du code n'est pas dans l'index (il ne démarre que lorsque quelque chose a été formaté dans l'index). Formate en général toutes les modifications en cours dans le projet (sous contrôle de version), et pas seulement à partir de l'étape précédente. Il s'agit d'une décision controversée, à première vue. Mais cela s'est avéré pratique, car tôt ou tard, d'autres modifications doivent également être formatées. Vous pouvez remplacer "| eval $ {GIT_APPLY}" par l'option -i , ce qui forcera $ {CLANG_FORMAT} à modifier les fichiers eux-mêmes.

Démonstration de travail


  1. Installez clang-format-6.0
  2. cd / tmp && mkdir temp_project && cd temp_project
  3. git init
  4. Ajoutez le contrôle de version et validez n'importe quel fichier C ++ sous le nom false.cpp . De préférence> 50 lignes de code non formaté.
  5. Créez le script .git / hooks / pre-commit illustré ci-dessus.
  6. Attribuez le droit d'exécuter le script (pour git): chmod + x .git / hooks / pre-commit .
  7. Exécutez le script .git / hooks / pre-commit manuellement, il doit être exécuté avec le message "Rien à formater" , sans erreurs d'interpréteur.
  8. Créez file.cpp avec le contenu int main () {for (int i = 0; i <100; ++ i) {std :: cout << "First case" << std :: endl; std :: cout << "Deuxième cas" << std :: endl; std :: cout << "Troisième cas" << std :: endl; }} avec une ligne ou avec une autre mise en forme incorrecte. Au bout du fil!
  9. git add file.cpp && git commit -m "file.cpp" devrait être des messages d'un script comme "Patch file.cpp appliqué sans erreur" .
  10. git log -p -1 devrait montrer l'ajout d'un fichier formaté.
  11. Si file.cpp est entré dans le commit vraiment formaté, vous ne pouvez tester le formatage qu'en diff. Modifiez la paire de lignes incorrectes.cpp afin que le formateur y réponde . Par exemple, ajoutez une indentation inadéquate dans votre code avec d'autres modifications. git commit -a -m "Format only diff" devrait remplir les modifications formatées, mais n'affecte pas les autres parties du fichier.

Inconvénients et problèmes


git diff --staged (qui est ici $ {GIT_DIFF} --staged ) diffère uniquement les fichiers qui ont été ajoutés à l'index. Et clang-format-diff-6.0 accède aux versions complètes des fichiers en dehors de celui-ci. Par conséquent, si vous modifiez un fichier, faites git add , puis modifiez le même fichier, alors clang-format-diff-6.0 générera un correctif pour formater le code (dans l'index) en fonction d'un fichier différent. Ainsi, il est préférable de ne pas modifier le fichier après git add et avant de valider.

Voici un exemple d'une telle erreur:

  1. Ajoutez std :: endl supplémentaire à file.cpp , "Second case" . (std :: cout << "Second case" << std :: endl << std :: endl;) et quelques onglets d'indentation supplémentaire avant la ligne.
  2. git add file.cpp
  3. Effacez la ligne (dans le même fichier) avec "Premier cas" afin que seul le saut de ligne reste à sa place (!).
  4. git commit -m "Erreur de formatage lors de la validation" .

Le script doit signaler "erreur: lors de la recherche:" , c'est-à-dire git apply n'a pas trouvé le contexte du patch émis par clang-format-diff-6.0 . Si vous ne comprenez pas quel est le problème ici, ne modifiez pas les fichiers après les avoir ajoutés par git et avant de les valider . Si vous devez changer, vous pouvez valider (sans push) puis git commit --amend avec de nouvelles modifications.

La limitation la plus sérieuse est la nécessité d'avoir un saut de ligne à la fin de chaque fichier. Il s'agit d'une ancienne fonctionnalité git, donc la plupart des éditeurs de code prennent en charge l'insertion automatique d'une telle traduction à la fin du fichier. Sans cela, le script plantera lors de la validation d'un nouveau fichier, mais il ne fera aucun mal.


Très rarement, clang-format-diff-6.0 formate le code de manière inappropriée. Dans ce cas, vous pouvez ajouter des éléments inutiles au code, comme un point-virgule. Ou, entourez le code problématique de commentaires, / * format clang désactivé * / et / * format clang activé * / .


De plus, clang-format-diff-6.0 peut produire un correctif inadéquat. Cela finit par git apply ne pas l'accepter, et le code de la partie commit reste non formaté. La raison en est à l'intérieur de clang-format-diff . Il n'y a pas de temps pour comprendre toutes les erreurs de programme. Dans ce cas, vous pouvez consulter le correctif de formatage à l'aide de la commande git diff -U0 --no-color HEAD ^ | clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex '. * \. (cxx | cpp | hpp | h) $' . La solution la plus simple consiste à ajouter l'option -i à la commande précédente. Dans ce cas, l'utilitaire n'émettra pas de correctif, mais formatera le code. Si cela ne vous aide pas, vous pouvez essayer de formater des fichiers individuels entièrement clang-format-6.0 -i -sort-includes -style = Chromium file.cpp . Viennent ensuite git add file.cpp et git commit --amend .

Il est supposé que plus votre configuration au format .clang est proche de l'un des préréglages, moins vous verrez de telles erreurs. (Ici, il est remplacé par l'option -style = Chromium ).


Débogage


Si vous voulez voir quels changements le script apportera sur vos modifications actuelles (pas dans l'index), utilisez git diff -U0 --no-color | clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex '. * \. (cxx | cpp | hpp | h) $' Vous pouvez également vérifier le fonctionnement du script sur les dernières validations, par exemple , à trente ans: git filter-branch -f --tree-filter "$ {PWD} /. git / hooks / pre-commit" --prune-empty HEAD ~ 30..HEAD . Cette commande devrait avoir formaté les validations précédentes, mais en fait, seul leur identifiant change. Par conséquent, cela vaut la peine de mener de telles expériences dans une copie séparée du projet! Après, elle devient inutilisable.

Conclusion


Subjectivement, une telle décision est beaucoup plus bonne que nuisible. Mais vous devez tester le comportement de clang-format-diff de différentes versions sur le code de votre projet, avec une configuration pour votre style de code.

Malheureusement, nous n'avons pas fait le même git-hook pour Windows. Suggérez dans les commentaires comment y faire. Et si vous avez besoin d'un article pour un démarrage rapide avec le format clang , nous vous recommandons de consulter la description de ClangFormat .

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


All Articles