Pemformatan kode sumber ClangFormat di Linux: masalah dan solusi



Setuju, itu bagus dan berguna ketika kode sumber dalam proyek terlihat indah dan konsisten. Ini memfasilitasi pemahaman dan dukungannya. Kami menunjukkan dan memberi tahu Anda bagaimana menerapkan pemformatan kode sumber menggunakan clang-format , git dan sh .

Memformat masalah dan cara mengatasinya


Di sebagian besar proyek, ada aturan tertentu untuk mendesain kode. Bagaimana cara memastikan bahwa semua peserta mengeksekusi mereka? Program khusus datang ke penyelamatan - dentang format, astyle, tidak dapat dipercaya, - tetapi mereka memiliki kelemahan mereka.

Masalah utama dengan pemformat adalah mereka mengubah seluruh file, bukan hanya mengubah baris. Kami akan memberi tahu Anda bagaimana kami menangani hal ini, menggunakan ClangFormat sebagai bagian dari salah satu proyek untuk mengembangkan firmware untuk elektronik, di mana C ++ adalah bahasa utama. Beberapa orang bekerja dalam tim, jadi penting bagi kami untuk memberikan gaya kode yang seragam. Solusi kami tidak hanya cocok untuk programmer C ++, tetapi juga bagi mereka yang menulis kode dalam C, Objective-C, JavaScript, Java, Protobuf.

Untuk memformat, kami menggunakan clang-format-diff-6.0 . Pada awalnya, mereka memulai tim

git diff -U0 --tidak ada warna | dentang-format-diff-6.0 -i -p1 , tetapi ada masalah dengan itu:


  1. Program hanya menentukan jenis file berdasarkan ekstensi. Misalnya, file dengan ekstensi ts, yang kami miliki dalam format xml, dianggap sebagai JavaScript dan macet saat memformat. Kemudian, untuk beberapa alasan, dia mencoba memperbaiki pro-file proyek Qt, mungkin seperti Protobuf.
  2. Program harus dimulai secara manual sebelum menambahkan file ke indeks git. Mudah untuk melupakannya.

Solusi


Hasilnya adalah skrip-sh berikut, dijalankan sebagai pre-commit - hook for 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 

Apa yang dilakukan skrip:
GIT_DIFF = "git diff -U0 --no-color" - perubahan dalam kode yang akan diinput ke clang-format-diff-6.0.

  • -U0 : biasanya git diff menampilkan apa yang disebut "konteks": beberapa baris kode yang tidak berubah di sekitar yang telah diubah. Tapi dentang-format-diff-6.0 format mereka juga! Oleh karena itu, konteks dalam hal ini tidak diperlukan.

CLANG_FORMAT = "clang-format-diff-6.0 -p1 -v -sort-include -style = Chromium -iregex '. * \. (Cxx | cpp | hpp | h) $'" - perintah untuk memformat diff yang diterima melalui standar masukan.

  • clang-format-diff-6.0 - skrip dari paket clang-format-6.0 . Ada versi lain, tetapi semua tes hanya untuk yang satu ini.
  • -p1 diambil dari contoh dalam dokumentasi , menyediakan kompatibilitas dengan git diff output.
  • -style = Chromium - preset gaya format kode siap pakai. Nilai lain yang mungkin: LLVM, Google, Mozilla, WebKit .
  • -sort-include - opsi untuk mengurutkan menurut abjad # termasuk arahan (opsional).
  • -iregex '. * \. (cxx | cpp | hpp | h) $' adalah ekspresi reguler yang memfilter nama file dengan ekstensi. Hanya ekstensi yang perlu diformat yang tercantum di sini. Ini akan menyelamatkan program dari gangguan yang tidak terduga dan jatuh. Kemungkinan besar daftar ini perlu ditambah dalam proyek-proyek baru. Selain C ++, Anda dapat memformat C / Objective-C / JavaScript / Java / Protobuf . Meskipun kami tidak menguji jenis file ini.

GIT_APPLY = "git apply -v -p0 -" - terapkan tambalan yang dikeluarkan oleh perintah sebelumnya ke kode.

  • -p0 : secara default, git apply melompati komponen pertama dalam path file, ini tidak kompatibel dengan format yang menghasilkan clang-format-diff-6.0 . Lompatan ini dinonaktifkan di sini.

FORMATTER_DIFF = $ (eval $ {GIT_DIFF} --staged | eval $ {CLANG_FORMAT}) - perubahan formatter untuk indeks.

echo "$ {FORMATTER_DIFF}" | eval $ {GIT_APPLY} --cached memformat kode sumber dalam indeks (setelah git add ). Sayangnya, tidak ada kait yang berfungsi sebelum menambahkan file ke indeks. Oleh karena itu, pemformatan dibagi menjadi dua bagian: memformat apa yang ada dalam indeks dan secara terpisah apa yang tidak ditambahkan ke indeks.

eval $ {GIT_DIFF} | eval $ {CLANG_FORMAT} | eval $ {GIT_APPLY} - pemformatan kode tidak ada dalam indeks (dimulai hanya ketika sesuatu telah diformat dalam indeks). Format secara umum semua perubahan saat ini dalam proyek (di bawah kontrol versi), dan bukan hanya dari langkah sebelumnya. Sekilas, ini adalah keputusan yang kontroversial. Tapi ternyata nyaman, karena cepat atau lambat perubahan lain perlu diformat juga. Anda dapat mengganti "| eval $ {GIT_APPLY}" dengan opsi -i , yang akan memaksa $ {CLANG_FORMAT} untuk mengubah file itu sendiri.

Demonstrasi kerja


  1. Instal dentang-format-6.0
  2. cd / tmp && mkdir temp_project && cd temp_project
  3. git init
  4. Tambahkan kontrol versi dan komit setiap file C ++ dengan nama salah.cpp . Lebih disukai> 50 baris kode yang tidak diformat.
  5. Buat skrip .git / hooks / pre-commit seperti ditunjukkan di atas.
  6. Tetapkan hak untuk menjalankan skrip (untuk git): chmod + x .git / hooks / pre-commit .
  7. Jalankan skrip .git / hooks / pre-commit secara manual, harus dijalankan dengan pesan "Tidak ada yang diformat" , tanpa kesalahan juru bahasa.
  8. Buat file.cpp dengan isi int main () {for (int i = 0; i <100; ++ i) {std :: cout << "Huruf pertama" << std :: endl; std :: cout << "Kasus kedua" << std :: endl; std :: cout << "Kasus ketiga" << std :: endl; }} dengan satu baris atau dengan format buruk lainnya. Pada umpan garis akhir!
  9. git add file.cpp && git commit -m "file.cpp" harus berupa pesan dari skrip seperti "Patch file.cpp diterapkan tanpa kesalahan . "
  10. git log -p -1 harus menunjukkan penambahan file yang diformat.
  11. Jika file.cpp masuk ke dalam komit benar-benar diformat, maka Anda dapat menguji format hanya di diff. Ubah pasangan baris salah.cpp sehingga formatter meresponsnya . Misalnya, tambahkan indentasi yang tidak memadai dalam kode Anda bersama dengan perubahan lainnya. git commit -a -m "Format only diff" harus mengisi perubahan yang diformat, tetapi tidak mempengaruhi bagian lain dari file.

Kekurangan dan masalah


git diff --staged (yang ada di sini $ {GIT_DIFF} --staged ) hanya membedakan file yang ditambahkan ke indeks. Dan dentang-format-diff-6.0 mengakses versi lengkap file di luarnya. Oleh karena itu, jika Anda mengubah file, membuat git add , dan kemudian mengubah file yang sama, maka clang-format-diff-6.0 akan menghasilkan tambalan untuk memformat kode (dalam indeks) berdasarkan pada file yang berbeda. Jadi, lebih baik tidak mengedit file setelah git add dan sebelum melakukan.

Berikut ini contoh kesalahan seperti itu:

  1. Tambahkan std :: endl tambahan ke file.cpp , "Kasus kedua" . (std :: cout << "Kasus kedua" << std :: endl << std :: endl;) dan beberapa tab lekukan ekstra sebelum baris.
  2. git tambahkan file.cpp
  3. Kosongkan baris (dalam file yang sama) dengan "Huruf pertama" sehingga hanya jeda baris tetap di tempatnya (!).
  4. git commit -m "Formatter error on commit" .

Skrip harus melaporkan "kesalahan: saat mencari:" , mis. git berlaku tidak menemukan konteks tambalan yang dikeluarkan oleh clang-format-diff-6.0 . Jika Anda tidak mengerti apa masalahnya di sini, cukup jangan ubah file setelah git menambahkannya dan sebelum git melakukan . Jika Anda perlu mengubah, Anda dapat melakukan (tanpa menekan) dan kemudian git komit - ubah dengan perubahan baru.

Keterbatasan paling serius adalah kebutuhan untuk memiliki satu baris di akhir setiap file. Ini adalah fitur git lama, sehingga sebagian besar editor kode mendukung penyisipan otomatis terjemahan semacam itu di akhir file. Tanpa ini, skrip akan macet ketika melakukan file baru, tetapi tidak akan membahayakan.


Sangat jarang, clang-format-diff-6.0 memformat kode secara tidak tepat. Dalam hal ini, Anda dapat menambahkan beberapa elemen yang tidak berguna ke kode, seperti titik koma. Atau, kelilingi kode bermasalah dengan komentar, / * clang-format off * / dan / * clang-format on * / .


Juga dentang-format-diff-6.0 dapat menghasilkan tambalan yang tidak memadai. Ini berakhir dengan git berlaku tidak menerimanya, dan kode dari bagian komit tetap tidak diformat. Alasannya adalah di dalam dentang-format-diff . Tidak ada waktu untuk memahami semua kesalahan program. Dalam hal ini, Anda dapat melihat patch pemformatan menggunakan perintah git diff -U0 --no-color HEAD ^ | dentang-format-diff-6.0 -p1 -v -sort-include -style = Chromium -iregex '. * \. (cxx | cpp | hpp | h) $' . Solusi termudah adalah menambahkan opsi -i ke perintah sebelumnya. Dalam hal ini, utilitas tidak akan mengeluarkan tambalan, tetapi akan memformat kode. Jika ini tidak membantu, Anda dapat mencoba memformat untuk file individual sepenuhnya dentang-format-6.0 -i -sort-include -style = Chromium file.cpp . Berikutnya adalah git add file.cpp dan git commit --amend .

Ada asumsi bahwa semakin dekat konfigurasi .clang-format Anda dengan salah satu preset, semakin sedikit kesalahan yang akan Anda lihat. (Ini diganti dengan opsi -style = Chromium ).


Debugging


Jika Anda ingin melihat perubahan apa yang akan dilakukan skrip pada suntingan Anda saat ini (tidak ada dalam indeks), gunakan git diff -U0 --no-color | dentang-format-diff-6.0 -p1 -v -sort-include -style = Chromium -iregex '. * \. (cxx | cpp | hpp | h) $' Anda juga dapat memeriksa bagaimana skrip akan bekerja pada komit terbaru, misalnya , pada tigapuluh: git filter-branch -f --tree-filter "$ {PWD} /. git / hooks / pre-commit" --prune-empty HEAD ~ 30..HEAD . Perintah ini seharusnya telah memformat komit sebelumnya, tetapi sebenarnya hanya id mereka yang berubah. Karena itu, ada baiknya melakukan percobaan seperti itu dalam salinan proyek yang terpisah! Setelah dia menjadi tidak dapat digunakan.

Kesimpulan


Secara subyektif, keputusan seperti itu jauh lebih baik daripada merugikan. Tetapi Anda perlu menguji perilaku clang-format-diff dari berbagai versi pada kode proyek Anda, dengan konfigurasi untuk gaya kode Anda.

Sayangnya, kami tidak melakukan git-hook yang sama untuk Windows. Sarankan di komentar bagaimana melakukannya di sana. Dan jika Anda membutuhkan artikel untuk memulai cepat dengan format dentang , kami sarankan Anda melihat deskripsi Dentang Kebijakan .

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


All Articles