Formatação do código-fonte ClangFormat no Linux: problemas e soluções



Concordo, é bom e útil quando o código-fonte do projeto é bonito e consistente. Isso facilita sua compreensão e apoio. Mostramos e mostramos como implementar a formatação do código fonte usando o formato clang , git e sh .

Problemas de formatação e como resolvê-los


A maioria dos projetos possui certas regras para o design do código. Como garantir que todos os participantes os executem? Programas especiais vêm em socorro - formato clang, estilo, falta de credibilidade - , mas eles têm suas desvantagens.

O principal problema dos formatadores é que eles alteram arquivos inteiros, não apenas as linhas alteradas. Contaremos como lidamos com isso, usando o ClangFormat como parte de um dos projetos para o desenvolvimento de firmware para eletrônicos, onde o C ++ era a linguagem principal. Várias pessoas trabalharam na equipe, por isso era importante fornecer um estilo de código uniforme. Nossa solução pode ser adequada não apenas para programadores em C ++, mas também para quem escreve código em C, Objective-C, JavaScript, Java, Protobuf.

Para formatação, usamos o clang-format-diff-6.0 . No começo, eles começaram a equipe

git diff -U0 - sem cor | clang-format-diff-6.0 -i -p1 , mas houve problemas com ele:


  1. O programa determinou os tipos de arquivo apenas por extensão. Por exemplo, arquivos com a extensão ts, que tínhamos no formato xml, percebidos como JavaScript e travavam ao formatar. Então, por algum motivo, ela tentou consertar os arquivos profissionais dos projetos Qt, provavelmente como o Protobuf.
  2. O programa teve que ser iniciado manualmente antes de adicionar arquivos ao índice git. Foi fácil esquecer isso.

Solução


O resultado foi o seguinte sh-script, executado como um pré-commit - hook 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 

O que o script faz:
GIT_DIFF = "git diff -U0 --no-color" - altera o código que será inserido no clang-format-diff-6.0.

  • -U0 : geralmente o git diff exibe o chamado "contexto": algumas linhas de código inalteradas em torno daquelas que foram alteradas. Mas o clang-format-diff-6.0 também os formata! Portanto, o contexto neste caso não é necessário.

CLANG_FORMAT = "clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex '. * \. (Cxx | cpp | hpp | h) $'" - um comando para formatar diff recebido via padrão entrada.

  • clang-format-diff-6.0 - um script do pacote clang-format-6.0 . Existem outras versões, mas todos os testes foram apenas nessa.
  • -p1, tirado de exemplos na documentação , fornece compatibilidade com a saída git diff .
  • -style = Chromium - predefinição de estilo de formato de código pronta. Outros valores possíveis: LLVM, Google, Mozilla, WebKit .
  • -sort-includes - opção para classificar alfabeticamente #include diretivas (opcional).
  • -iregex '. * \. (cxx | cpp | hpp | h) $' é uma expressão regular que filtra nomes de arquivos por extensão. Somente as extensões que precisam ser formatadas são listadas aqui. Isso salvará o programa de quedas e falhas inesperadas. Provavelmente, a lista precisará ser complementada em novos projetos. Além do C ++, você pode formatar C / Objective-C / JavaScript / Java / Protobuf . Embora não tenhamos testado esses tipos de arquivos.

GIT_APPLY = "git apply -v -p0 -" - aplica o patch emitido pelo comando anterior ao código.

  • -p0 : por padrão, o git apply ignora o primeiro componente no caminho do arquivo, isso não é compatível com o formato produzido pelo clang-format-diff-6.0 . Este salto está desativado aqui.

FORMATTER_DIFF = $ (eval $ {GIT_DIFF} - encenado | eval $ {CLANG_FORMAT}) - as alterações do formatador para o índice.

eco "$ {FORMATTER_DIFF}" | eval $ {GIT_APPLY} --cached formata o código fonte no índice (após o git add ). Infelizmente, não há gancho que funcione antes de adicionar arquivos ao índice. Portanto, a formatação é dividida em duas partes: formatar o que está no índice e separadamente o que não é adicionado ao índice.

avaliação $ {GIT_DIFF} | avaliação $ {CLANG_FORMAT} | eval $ {GIT_APPLY} - a formatação do código não está no índice (ela inicia apenas quando algo foi formatado no índice). Formata, em geral, todas as alterações atuais no projeto (sob controle de versão), e não apenas na etapa anterior. Esta é uma decisão controversa, à primeira vista. Mas acabou sendo conveniente, porque mais cedo ou mais tarde, outras alterações também precisam ser formatadas. Você pode substituir "| eval $ {GIT_APPLY}" pela opção -i , que forçará $ {CLANG_FORMAT} a alterar os arquivos.

Demonstração de trabalho


  1. Instale o clang-format-6.0
  2. cd / tmp && mkdir temp_project && cd temp_project
  3. git init
  4. Adicione controle de versão e confirme qualquer arquivo C ++ com o nome wrong.cpp . De preferência> 50 linhas de código não formatado.
  5. Crie o script .git / hooks / pre-commit mostrado acima.
  6. Atribua o direito de executar o script (para git): chmod + x .git / hooks / pre-commit .
  7. Execute o script .git / hooks / pre-commit manualmente, ele deve ser executado com a mensagem "Nada a ser formatado" , sem erros de interpretador.
  8. Crie file.cpp com o conteúdo int main () {for (int i = 0; i <100; ++ i) {std :: cout << "Primeiro caso" << std :: endl; std :: cout << "Segundo caso" << std :: endl; std :: cout << "Terceiro caso" << std :: endl; }} com uma linha ou com outra formatação incorreta. No final da linha!
  9. git add file.cpp && git commit -m "file.cpp" devem ser mensagens de um script como "Patch file.cpp aplicado sem erros" .
  10. O git log -p -1 deve mostrar a adição de um arquivo formatado.
  11. Se file.cpp entrou no commit realmente formatado, você pode testar a formatação apenas no diff. Altere o par de linhas wrong.cpp para que o formatador responda a elas. Por exemplo, adicione recuo inadequado no seu código junto com outras alterações. git commit -a -m "Somente formato" deve preencher as alterações formatadas, mas não afeta outras partes do arquivo.

Desvantagens e problemas


O git diff --staged (que está aqui $ {GIT_DIFF} --staged ) difere apenas os arquivos que foram adicionados ao índice. E o clang-format-diff-6.0 acessa as versões completas dos arquivos fora dele. Portanto, se você alterar um arquivo, faça o git add e, em seguida, altere o mesmo arquivo, o clang-format-diff-6.0 gerará um patch para formatar o código (no índice) com base em um arquivo diferente. Portanto, é melhor não editar o arquivo após o git add e antes de confirmar.

Aqui está um exemplo de tal erro:

  1. Adicione std :: endl extra a file.cpp , "Segundo caso" . (std :: cout << "Segundo caso" << std :: endl << std :: endl;) e algumas guias de recuo extra antes da linha.
  2. git add file.cpp
  3. Limpe a linha (no mesmo arquivo) com "Primeiro caso" para que apenas a quebra de linha permaneça em seu lugar (!).
  4. git commit -m "Erro do formatador ao confirmar" .

O script deve relatar "erro: ao pesquisar:" , ou seja, O git apply não encontrou o contexto do patch emitido pelo clang-format-diff-6.0 . Se você não entender qual é o problema, simplesmente não altere os arquivos após o git os adicionar e antes do git commit . Se você precisar alterar, poderá confirmar (sem push) e, em seguida, git commit --amend com novas alterações.

A limitação mais séria é a necessidade de interromper a linha no final de cada arquivo. Esse é um recurso antigo do git; portanto, a maioria dos editores de código suporta a inserção automática dessa tradução no final do arquivo. Sem isso, o script falhará ao confirmar um novo arquivo, mas não causará nenhum dano.


Muito raramente, o clang-format-diff-6.0 formata o código de maneira inadequada. Nesse caso, você pode adicionar alguns elementos inúteis ao código, como um ponto e vírgula. Ou, envolva o código problemático com comentários, / * clang-format desativado * / e / * clang-format ativado * / .


O clang-format-diff-6.0 também pode produzir um patch inadequado. Isso acaba com o git apply e não o aceita, e o código da parte de confirmação permanece não formatado. O motivo está dentro do clang-format-diff . Não há tempo para entender todos os erros do programa. Nesse caso, você pode observar o patch de formatação usando o comando git diff -U0 --no-color HEAD ^ | clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex '. * \. (cxx | cpp | hpp | h) $' . A solução mais fácil é adicionar a opção -i ao comando anterior. Nesse caso, o utilitário não emitirá um patch, mas formatará o código. Se isso não ajudar, tente a formatação de arquivos individuais, clang-format-6.0 -i -sort-includes -style = Chromium file.cpp . A seguir, git add file.cpp e git commit --amend .

Supõe-se que, quanto mais próxima sua configuração do formato .clang estiver de uma das predefinições, menos erros ocorrerão. (Aqui é substituído pela opção -style = Chromium ).


Depuração


Se você quiser ver quais alterações o script fará nas edições atuais (não no índice), use git diff -U0 --no-color | clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex '. * \. (cxx | cpp | hpp | h) $' Você também pode verificar como o script funcionará nas confirmações mais recentes, por exemplo , às trinta: git filter-branch -f --tree-filter "$ {PWD} /. git / hooks / pre-commit" --prune-empty HEAD ~ 30..HEAD . Este comando deve ter formatado as confirmações anteriores, mas na verdade apenas as alterações de identificação. Portanto, vale a pena realizar esses experimentos em uma cópia separada do projeto! Depois que ela se torna inutilizável.

Conclusão


Subjetivamente, essa decisão é muito mais boa do que prejudicial. Mas você precisa testar o comportamento do clang-format-diff de diferentes versões no código do seu projeto, com uma configuração para o seu estilo de código.

Infelizmente, não fizemos o mesmo git-hook para Windows. Sugira nos comentários como fazê-lo lá. E se você precisar de um artigo para iniciar rapidamente o formato clang , recomendamos que você analise a descrição ClangFormat .

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


All Articles