Formateo del código fuente de ClangFormat en Linux: problemas y soluciones



De acuerdo, es agradable y útil cuando el código fuente del proyecto se ve hermoso y consistente. Esto facilita su comprensión y apoyo. Le mostramos y le decimos cómo implementar el formateo del código fuente usando el formato clang , git y sh .

Problemas de formato y cómo resolverlos


En la mayoría de los proyectos, hay ciertas reglas para el diseño de código. ¿Cómo asegurarse de que todos los participantes los ejecuten? Los programas especiales vienen al rescate: formato clang, astyle, incrustable , pero tienen sus inconvenientes.

El principal problema con los formateadores es que cambian archivos completos, no solo líneas cambiadas. Le diremos cómo lidiamos con esto, usando ClangFormat como parte de uno de los proyectos para desarrollar firmware para electrónica, donde C ++ era el lenguaje principal. Varias personas trabajaron en el equipo, por lo que era importante para nosotros proporcionar un estilo de código uniforme. Nuestra solución puede ser adecuada no solo para programadores de C ++, sino también para aquellos que escriben código en C, Objective-C, JavaScript, Java, Protobuf.

Para formatear, usamos clang-format-diff-6.0 . Al principio, comenzaron el equipo.

git diff -U0 --no-color | clang-format-diff-6.0 -i -p1 , pero hubo problemas con él:


  1. El programa determinó los tipos de archivos solo por extensión. Por ejemplo, los archivos con la extensión ts, que teníamos en formato xml, se percibieron como JavaScript y se bloquearon al formatear. Luego, por alguna razón, trató de arreglar los pro-archivos de proyectos Qt, probablemente como Protobuf.
  2. El programa tuvo que iniciarse manualmente antes de agregar archivos al índice git. Fue fácil olvidarlo.

Solución


El resultado es la siguiente secuencia de comandos sh, ejecutada como un compromiso previo : enlace para 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 

Lo que hace el guión:
GIT_DIFF = "git diff -U0 --no-color" - cambios en el código que se ingresará a clang-format-diff-6.0.

  • -U0 : por lo general, git diff muestra el llamado "contexto": algunas líneas de código sin cambios alrededor de las que se han cambiado. ¡Pero clang-format-diff-6.0 también los formatea! Por lo tanto, el contexto en este caso no es necesario.

CLANG_FORMAT = "clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex '. * \. (Cxx | cpp | hpp | h) $'" - un comando para formatear el diff recibido a través del estándar entrada.

  • clang-format-diff-6.0 : un script del paquete clang-format-6.0 . Hay otras versiones, pero todas las pruebas fueron solo en esta.
  • -p1 tomado de ejemplos en la documentación , proporciona compatibilidad con la salida de git diff .
  • -style = Chromium - preestablecido estilo de formato de código listo. Otros valores posibles: LLVM, Google, Mozilla, WebKit .
  • -sort-includes : opción para ordenar alfabéticamente las directivas #include (opcional).
  • -iregex '. * \. (cxx | cpp | hpp | h) $' es una expresión regular que filtra los nombres de archivo por extensión. Aquí solo se enumeran las extensiones que deben formatearse. Esto salvará al programa de fallas inesperadas y caídas. Lo más probable es que la lista deba complementarse en nuevos proyectos. Además de C ++, puede formatear C / Objective-C / JavaScript / Java / Protobuf . Aunque no probamos este tipo de archivos.

GIT_APPLY = "git apply -v -p0 -" - aplica el parche emitido por el comando anterior al código.

  • -p0 : de forma predeterminada, git apply omite el primer componente en la ruta del archivo, esto no es compatible con el formato que produce clang-format-diff-6.0 . Este salto está deshabilitado aquí.

FORMATTER_DIFF = $ (eval $ {GIT_DIFF} --staged | eval $ {CLANG_FORMAT}) - cambios de formateador para el índice.

echo "$ {FORMATTER_DIFF}" | eval $ {GIT_APPLY}: en caché formatea el código fuente en el índice (después de git add ). Desafortunadamente, no hay un gancho que funcione antes de agregar archivos al índice. Por lo tanto, el formato se divide en dos partes: formatear lo que está en el índice y separadamente lo que no se agrega al índice.

eval $ {GIT_DIFF} | eval $ {CLANG_FORMAT} | eval $ {GIT_APPLY} : el formato del código no está en el índice (comienza solo cuando algo se ha formateado en el índice). Formatea en general todos los cambios actuales en el proyecto (bajo control de versiones), y no solo del paso anterior. Esta es una decisión controvertida, a primera vista. Pero resultó ser conveniente, porque tarde o temprano, otros cambios también deben formatearse. Puede reemplazar "| eval $ {GIT_APPLY}" con la opción -i , lo que obligará a $ {CLANG_FORMAT} a cambiar los archivos ellos mismos.

Demostración de trabajo


  1. Instalar clang-format-6.0
  2. cd / tmp && mkdir temp_project && cd temp_project
  3. git init
  4. Agregue control de versiones y confirme cualquier archivo C ++ con el nombre wrong.cpp . Preferiblemente> 50 líneas de código sin formato.
  5. Cree el script .git / hooks / pre-commit que se muestra arriba.
  6. Asigne el derecho de ejecutar el script (para git): chmod + x .git / hooks / pre-commit .
  7. Ejecute el script .git / hooks / pre-commit manualmente, debe ejecutarse con el mensaje "No se debe formatear nada" , sin errores de intérprete.
  8. Cree file.cpp con los contenidos int main () {for (int i = 0; i <100; ++ i) {std :: cout << "Primer caso" << std :: endl; std :: cout << "Segundo caso" << std :: endl; std :: cout << "Tercer caso" << std :: endl; }} con una línea o con otro formato incorrecto. Al final - ¡avance de línea!
  9. git add file.cpp && git commit -m "file.cpp" deben ser mensajes de un script como "Patch file.cpp aplicado sin errores" .
  10. git log -p -1 debería mostrar la adición de un archivo formateado.
  11. Si file.cpp entró en el commit realmente formateado, entonces puede probar el formato solo en diff. Cambie el par de líneas incorrecto.cpp para que el formateador responda a ellas. Por ejemplo, agregue sangría inadecuada en su código junto con otros cambios. git commit -a -m "Format only diff" debe completar los cambios formateados, pero no afectar otras partes del archivo.

Desventajas y problemas


git diff --staged (que está aquí $ {GIT_DIFF} --staged ) solo difiere los archivos que se agregaron al índice. Y clang-format-diff-6.0 accede a las versiones completas de archivos fuera de él. Por lo tanto, si cambia un archivo, haga que git add y luego cambie el mismo archivo, entonces clang-format-diff-6.0 generará un parche para formatear el código (en el índice) basado en un archivo diferente. Por lo tanto, es mejor no editar el archivo después de git add y antes de confirmar.

Aquí hay un ejemplo de tal error:

  1. Agregue std :: endl adicional a file.cpp , "Segundo caso" . (std :: cout << "Segundo caso" << std :: endl << std :: endl;) y algunas pestañas de sangría adicional antes de la línea.
  2. git add file.cpp
  3. Borre la línea (en el mismo archivo) con "Primer caso" para que solo el salto de línea permanezca en su lugar (!).
  4. git commit -m "Error de formateador al confirmar" .

El script debe informar "error: al buscar:" , es decir git apply no encontró el contexto del parche emitido por clang-format-diff-6.0 . Si no comprende cuál es el problema aquí, simplemente no cambie los archivos después de git agréguelos y antes de git commit . Si necesita cambiar, puede comprometerse (sin push) y luego git commit - enmendar con nuevos cambios.

La limitación más grave es la necesidad de tener un salto de línea al final de cada archivo. Esta es una característica antigua de git, por lo que la mayoría de los editores de código admiten la inserción automática de dicha traducción al final del archivo. Sin esto, el script se bloqueará al confirmar un nuevo archivo, pero no hará ningún daño.


Muy raramente, clang-format-diff-6.0 formatea el código de manera inapropiada. En este caso, puede agregar algunos elementos inútiles al código, como un punto y coma. O bien, rodee el código problemático con comentarios, / * clang-format off * / y / * clang-format on * / .


También clang-format-diff-6.0 puede producir un parche inadecuado. Esto termina con git apply sin aceptarlo, y el código de la parte de confirmación permanece sin formatear. La razón está dentro de clang-format-diff . No hay tiempo para entender todos los errores del programa. En este caso, puede ver el parche de formato con el comando git diff -U0 --no-color HEAD ^ | clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex '. * \. (cxx | cpp | hpp | h) $' . La solución más fácil es agregar la opción -i al comando anterior. En este caso, la utilidad no emitirá un parche, pero formateará el código. Si esto no ayuda, puede intentar formatear completamente archivos individuales clang-format-6.0 -i -sort-includes -style = Chromium file.cpp . El siguiente es git add file.cpp y git commit --amend .

Se supone que cuanto más cerca esté su configuración de formato .clang de uno de los ajustes preestablecidos, menos errores verá. (Aquí se reemplaza por la opción -style = Chromium ).


Depuración


Si desea ver qué cambios hará el script en sus ediciones actuales (no en el índice), use git diff -U0 --no-color | clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex '. * \. (cxx | cpp | hpp | h) $' También puede comprobar cómo funcionará el script en las últimas confirmaciones, por ejemplo , a los treinta: git filter-branch -f --tree-filter "$ {PWD} /. git / hooks / pre-commit" --prune-empty HEAD ~ 30..HEAD . Este comando debería haber formateado las confirmaciones anteriores, pero de hecho solo cambia su id. ¡Por lo tanto, vale la pena realizar tales experimentos en una copia separada del proyecto! Después de que ella se vuelve inutilizable.

Conclusión


Subjetivamente, tal decisión es mucho más buena que perjudicial. Pero debe probar el comportamiento de clang-format-diff de diferentes versiones en el código de su proyecto, con una configuración para su estilo de código.

Desafortunadamente, no hicimos el mismo git-hook para Windows. Sugiere en los comentarios cómo hacerlo allí. Y si necesita un artículo para comenzar rápidamente con el formato clang , le recomendamos que consulte la descripción de ClangFormat .

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


All Articles