Saya baru mengenal pemrograman secara umum jadi saya memutuskan bahwa saya akan mulai dengan membuat kelas vektor sederhana di C ++. Namun saya ingin membiasakan diri dengan kebiasaan baik sejak awal daripada mencoba mengubah alur kerja saya nanti.
Saat ini saya hanya memiliki dua file vector3.hpp
dan vector3.cpp
. Proyek ini perlahan-lahan akan mulai tumbuh (membuatnya lebih seperti perpustakaan aljabar linier umum) karena saya menjadi lebih akrab dengan semuanya, jadi saya ingin mengadopsi tata letak proyek "standar" untuk membuat hidup lebih mudah di kemudian hari. Jadi setelah melihat-lihat saya telah menemukan dua cara untuk mengatur file hpp dan cpp, yang pertama adalah:
project
└── src
├── vector3.hpp
└── vector3.cpp
dan makhluk kedua:
project
├── inc
│ └── project
│ └── vector3.hpp
└── src
└── vector3.cpp
Mana yang akan Anda rekomendasikan dan mengapa?
Kedua, saya ingin menggunakan Kerangka Pengujian Google C ++ untuk pengujian unit kode saya karena tampaknya cukup mudah digunakan. Apakah Anda menyarankan untuk menggabungkannya dengan kode saya, misalnya dalam folder inc/gtest
atau contrib/gtest
? Jika dibundel, apakah Anda menyarankan untuk menggunakanfuse_gtest_files.py
skrip untuk mengurangi jumlah atau file, atau membiarkannya apa adanya? Jika tidak digabungkan, bagaimana ketergantungan ini ditangani?
Dalam hal tes menulis, bagaimana biasanya tes ini diatur? Saya berpikir untuk memiliki satu file cpp untuk setiap kelas ( test_vector3.cpp
misalnya) tetapi semua dikompilasi ke dalam satu biner sehingga semuanya dapat dijalankan bersama dengan mudah?
Karena perpustakaan paling gt umumnya dibangun menggunakan cmake dan make, saya berpikir masuk akal jika proyek saya juga dibangun seperti ini? Jika saya memutuskan untuk menggunakan tata letak proyek berikut:
├── CMakeLists.txt
├── contrib
│ └── gtest
│ ├── gtest-all.cc
│ └── gtest.h
├── docs
│ └── Doxyfile
├── inc
│ └── project
│ └── vector3.cpp
├── src
│ └── vector3.cpp
└── test
└── test_vector3.cpp
Bagaimana CMakeLists.txt
harus terlihat sehingga bisa membangun hanya perpustakaan atau perpustakaan dan tes? Juga saya telah melihat beberapa proyek yang memiliki build
dan bin
direktori. Apakah build terjadi di direktori build lalu binari dipindahkan ke direktori bin? Apakah binari untuk pengujian dan pustaka berada di tempat yang sama? Atau akan lebih masuk akal untuk menyusunnya sebagai berikut:
test
├── bin
├── build
└── src
└── test_vector3.cpp
Saya juga ingin menggunakan doxygen untuk mendokumentasikan kode saya. Apakah mungkin untuk menjalankan ini secara otomatis dengan cmake and make?
Maaf untuk banyak pertanyaan, tapi saya belum menemukan buku tentang C ++ yang menjawab pertanyaan jenis ini dengan memuaskan.
sumber
Jawaban:
Sistem build C ++ adalah sedikit seni hitam dan semakin tua proyeknya semakin banyak hal aneh yang dapat Anda temukan sehingga tidak mengherankan jika banyak pertanyaan muncul. Saya akan mencoba menjawab pertanyaan satu per satu dan menyebutkan beberapa hal umum tentang membangun perpustakaan C ++.
Memisahkan file header dan cpp dalam direktori. Ini hanya penting jika Anda sedang membangun komponen yang seharusnya digunakan sebagai pustaka, bukan aplikasi sebenarnya. Header Anda adalah dasar bagi pengguna untuk berinteraksi dengan apa yang Anda tawarkan dan harus diinstal. Artinya,
/usr/include/
header harus berada dalam subdirektori (tidak ada yang menginginkan banyak header berakhir di level teratas ) dan header Anda harus dapat menyertakan dirinya sendiri dengan penyiapan seperti itu.berfungsi dengan baik, karena jalur penyertaan berhasil dan Anda dapat menggunakan globbing mudah untuk target pemasangan.
Membundel dependensi: Saya pikir ini sangat bergantung pada kemampuan sistem build untuk mencari dan mengonfigurasi dependensi dan seberapa bergantung kode Anda pada satu versi. Itu juga tergantung pada seberapa mampu pengguna Anda dan seberapa mudah ketergantungan untuk menginstal pada platform mereka. CMake dilengkapi dengan
find_package
skrip untuk Tes Google. Ini membuat segalanya lebih mudah. Saya akan pergi dengan membundel hanya jika perlu dan menghindarinya sebaliknya.Cara membangun: Hindari build dalam sumber. CMake menjadikan pembuatan sumber mudah dan membuat hidup jauh lebih mudah.
Saya kira Anda juga ingin menggunakan CTest untuk menjalankan tes untuk sistem Anda (itu juga dilengkapi dengan dukungan built-in untuk GTest). Keputusan penting untuk tata letak direktori dan organisasi pengujian adalah: Apakah Anda berakhir dengan subproyek? Jika demikian, Anda memerlukan lebih banyak pekerjaan saat menyiapkan CMakeLists dan harus membagi subproyek Anda menjadi subdirektori, masing-masing dengan miliknya
include
dansrc
file. Mungkin bahkan doxygen mereka sendiri berjalan dan keluar (menggabungkan beberapa proyek doxygen dimungkinkan, tetapi tidak mudah atau cantik).Anda akan mendapatkan hasil seperti ini:
dimana
Jika Anda memiliki sub-komponen, saya sarankan untuk menambahkan hierarki lain dan menggunakan pohon di atas untuk setiap sub-proyek. Kemudian segalanya menjadi rumit, karena Anda perlu memutuskan apakah sub-komponen mencari dan mengkonfigurasi dependensinya atau jika Anda melakukannya di tingkat atas. Ini harus diputuskan berdasarkan kasus per kasus.
Doxygen: Setelah Anda berhasil melalui tarian konfigurasi doxygen, sangat mudah menggunakan CMake
add_custom_command
untuk menambahkan target dokumen.Ini adalah bagaimana proyek saya berakhir dan saya telah melihat beberapa proyek yang sangat mirip, tetapi tentu saja ini tidak menyembuhkan semuanya.
Adendum Pada titik tertentu Anda akan ingin membuat
config.hpp
file yang berisi definisi versi dan mungkin definisi untuk beberapa pengenal kontrol versi (nomor Git hash atau revisi SVN). CMake memiliki modul untuk secara otomatis menemukan informasi itu dan untuk menghasilkan file. Anda dapat menggunakan CMakeconfigure_file
untuk mengganti variabel dalam file template dengan variabel yang ditentukan di dalamCMakeLists.txt
.Jika Anda membangun perpustakaan, Anda juga memerlukan export define untuk mendapatkan perbedaan antara compiler dengan benar, misalnya
__declspec
di MSVC danvisibility
atribut di GCC / clang.sumber
Sebagai permulaan, ada beberapa nama konvensional untuk direktori yang tidak dapat Anda abaikan, ini didasarkan pada tradisi lama dengan sistem file Unix. Ini adalah:
Mungkin ide yang baik untuk tetap menggunakan tata letak dasar ini, setidaknya di tingkat atas.
Tentang memisahkan file header dan file sumber (cpp), kedua skema cukup umum. Namun, saya cenderung lebih suka menyimpannya bersama, itu hanya lebih praktis pada tugas sehari-hari untuk memiliki file bersama. Selain itu, jika semua kode berada di bawah satu folder tingkat atas, yaitu
trunk/src/
folder, Anda dapat melihat bahwa semua folder lain (bin, lib, include, doc, dan mungkin beberapa folder uji) di tingkat atas, selain direktori "build" untuk build di luar sumber, adalah semua folder yang berisi tidak lebih dari file yang dihasilkan dalam proses build. Dan dengan demikian, hanya folder src yang perlu di-backup, atau lebih baik lagi, disimpan di bawah sistem / server kontrol versi (seperti Git atau SVN).Dan ketika datang untuk menginstal file header Anda pada sistem tujuan (jika Anda ingin mendistribusikan perpustakaan Anda), CMake memiliki perintah untuk menginstal file (secara implisit membuat target "install", untuk melakukan "make install") yang dapat Anda gunakan untuk meletakkan semua header ke dalam
/usr/include/
direktori. Saya hanya menggunakan makro cmake berikut untuk tujuan ini:Di mana
SRCROOT
variabel cmake yang saya setel ke folder src, danINCLUDEROOT
variabel cmake yang saya konfigurasikan ke mana pun ke header harus pergi. Tentu saja, ada banyak cara lain untuk melakukan ini, dan saya yakin cara saya bukanlah yang terbaik. Intinya, tidak ada alasan untuk membagi header dan sumber hanya karena hanya header yang perlu dipasang di sistem target, karena sangat mudah, terutama dengan CMake (atau CPack), untuk memilih dan mengkonfigurasi header untuk diinstal tanpa harus menyimpannya di direktori terpisah. Dan inilah yang saya lihat di sebagian besar perpustakaan.Jangan menggabungkan dependensi dengan perpustakaan Anda. Ini umumnya adalah ide yang sangat buruk, dan saya selalu membencinya ketika saya terjebak mencoba membangun perpustakaan yang melakukan itu. Ini harus menjadi pilihan terakhir Anda, dan waspadalah terhadap jebakan. Seringkali, orang menggabungkan dependensi dengan pustaka mereka baik karena mereka menargetkan lingkungan pengembangan yang buruk (misalnya, Windows), atau karena mereka hanya mendukung versi lama (tidak digunakan lagi) pustaka (ketergantungan) yang dimaksud. Masalah utamanya adalah dependensi yang dibundel Anda mungkin berbenturan dengan versi yang sudah terinstal dari pustaka / aplikasi yang sama (misalnya, Anda memaketkan gtest, tetapi orang yang mencoba membuat pustaka Anda sudah memiliki versi gtest yang lebih baru (atau lebih lama) yang sudah terpasang, lalu keduanya mungkin bentrok dan membuat orang itu sakit kepala parah). Jadi, seperti yang saya katakan, lakukan dengan risiko Anda sendiri, dan saya akan mengatakan hanya sebagai pilihan terakhir. Meminta orang-orang untuk menginstal beberapa dependensi sebelum dapat mengkompilasi perpustakaan Anda adalah kejahatan yang jauh lebih kecil daripada mencoba menyelesaikan bentrokan antara dependensi yang dibundel dan instalasi yang ada.
Satu file cpp per kelas (atau kelompok kecil kelas dan fungsi kohesif) lebih biasa dan praktis menurut saya. Namun, yang pasti, jangan mengkompilasi semuanya menjadi satu biner hanya agar "semuanya dapat dijalankan bersama". Itu ide yang sangat buruk. Secara umum, ketika datang ke pengkodean, Anda ingin membagi-bagi sebanyak mungkin hal yang masuk akal untuk dilakukan. Dalam kasus pengujian unit, Anda tidak ingin satu biner menjalankan semua pengujian, karena itu berarti bahwa sedikit perubahan apa pun yang Anda buat pada apa pun di perpustakaan Anda kemungkinan besar akan menyebabkan kompilasi ulang hampir total dari program pengujian unit tersebut. , dan itu hanya beberapa menit / jam yang hilang menunggu kompilasi ulang. Tetap berpegang pada skema sederhana: 1 unit = 1 program pengujian unit. Kemudian,
Saya lebih suka menyarankan tata letak ini:
Beberapa hal yang perlu diperhatikan di sini. Pertama, sub-direktori dari direktori src Anda harus mencerminkan sub-direktori dari direktori include Anda, ini hanya untuk menjaga agar semuanya tetap intuitif (juga, cobalah untuk menjaga struktur sub-direktori Anda tetap datar (dangkal), karena folder bersarang dalam sering kali lebih merepotkan dari apa pun). Kedua, direktori "include" hanyalah direktori instalasi, isinya hanyalah header apa pun yang diambil dari direktori src.
Ketiga, sistem CMake dimaksudkan untuk didistribusikan melalui sub-direktori sumber, bukan sebagai satu file CMakeLists.txt di tingkat atas. Ini membuat segala sesuatunya tetap lokal, dan itu bagus (dengan semangat memecah-belah menjadi beberapa bagian). Jika Anda menambahkan sumber baru, header baru, atau program pengujian baru, yang Anda perlukan hanyalah mengedit satu file CMakeLists.txt kecil dan sederhana di sub-direktori yang dimaksud, tanpa memengaruhi apa pun. Ini juga memungkinkan Anda untuk merestrukturisasi direktori dengan mudah (CMakeList bersifat lokal dan terdapat dalam sub-direktori yang sedang dipindahkan). CMakeList tingkat atas harus berisi sebagian besar konfigurasi tingkat atas, seperti menyiapkan direktori tujuan, perintah khusus (atau makro), dan menemukan paket yang diinstal pada sistem. CMakeList tingkat yang lebih rendah hanya berisi daftar header, sumber,
Jawaban dasarnya adalah bahwa CMake memungkinkan Anda mengecualikan target tertentu dari "semua" (yang dibuat saat Anda mengetik "make"), dan Anda juga dapat membuat kumpulan target tertentu. Saya tidak bisa melakukan tutorial CMake di sini, tetapi cukup mudah untuk mengetahuinya sendiri. Namun, dalam kasus khusus ini, solusi yang disarankan adalah, tentu saja, menggunakan CTest, yang hanya merupakan sekumpulan perintah tambahan yang dapat Anda gunakan dalam file CMakeLists untuk mendaftarkan sejumlah target (program) yang ditandai sebagai unit- tes. Jadi, CMake akan menempatkan semua pengujian dalam kategori build khusus, dan itulah yang Anda minta, jadi, masalah terpecahkan.
Memiliki direktori build di luar source (build "out-of-source") adalah satu-satunya hal yang waras untuk dilakukan, ini adalah standar de facto saat ini. Jadi, tentunya, miliki direktori "build" yang terpisah, di luar direktori source, seperti yang direkomendasikan oleh CMake, dan seperti yang dilakukan oleh setiap programmer yang pernah saya temui. Adapun direktori bin, yah, itu adalah konvensi, dan mungkin ide yang bagus untuk tetap menggunakannya, seperti yang saya katakan di awal posting ini.
Iya. Ini lebih dari mungkin, itu luar biasa. Tergantung pada seberapa mewah yang ingin Anda dapatkan, ada beberapa kemungkinan. CMake memiliki modul untuk Doxygen (yaitu,
find_package(Doxygen)
) yang memungkinkan Anda untuk mendaftarkan target yang akan menjalankan Doxygen pada beberapa file. Jika Anda ingin melakukan hal-hal yang lebih mewah, seperti memperbarui nomor versi di Doxyfile, atau secara otomatis memasukkan tanggal / stempel pengarang untuk file sumber dan sebagainya, itu semua mungkin dengan sedikit kung-fu CMake. Umumnya, melakukan ini akan melibatkan Anda menyimpan sumber Doxyfile (misalnya, "Doxyfile.in" yang saya letakkan di tata letak folder di atas) yang memiliki token untuk ditemukan dan diganti dengan perintah parsing CMake. Di file CMakeLists level atas saya , Anda akan menemukan salah satu bagian dari CMake kung-fu yang melakukan beberapa hal mewah dengan cmake-doxygen bersama-sama.sumber
main.cpp
harus pergi ketrunk/bin
?Penataan proyek
Saya biasanya menyukai yang berikut:
Ini berarti Anda memiliki sekumpulan file API yang sangat jelas untuk perpustakaan Anda, dan struktur tersebut berarti bahwa klien perpustakaan Anda akan melakukannya
bukan yang kurang eksplisit
Saya suka struktur pohon / src agar sesuai dengan pohon / include, tapi itu benar-benar preferensi pribadi. Namun, jika proyek Anda diperluas untuk memuat subdirektori di dalam / include / project, biasanya akan membantu mencocokkan subdirektori di dalam pohon / src.
Untuk pengujian, saya lebih suka membuatnya "dekat" dengan file yang mereka uji, dan jika Anda berakhir dengan subdirektori dalam / src, itu adalah paradigma yang cukup mudah untuk diikuti orang lain jika mereka ingin menemukan kode pengujian file tertentu.
Menguji
Gtest memang mudah digunakan dan cukup lengkap dalam hal kemampuannya. Ini dapat digunakan bersama gmock dengan sangat mudah untuk memperluas kemampuannya, tetapi pengalaman saya sendiri dengan gmock kurang menguntungkan. Saya cukup siap untuk menerima bahwa ini mungkin disebabkan oleh kekurangan saya sendiri, tetapi pengujian gmock cenderung lebih sulit dibuat, dan jauh lebih rapuh / sulit dipertahankan. Paku besar di peti mati gmock adalah bahwa itu benar-benar tidak cocok dengan petunjuk cerdas.
Ini adalah jawaban yang sangat sepele dan subjektif untuk pertanyaan besar (yang mungkin tidak benar-benar termasuk dalam SO)
Saya lebih suka menggunakan
ExternalProject_Add
modul CMake . Ini menghindari Anda harus menyimpan kode sumber paling gt di repositori Anda, atau menginstalnya di mana saja. Itu diunduh dan dibangun di pohon build Anda secara otomatis.Lihat jawaban saya berurusan dengan spesifik di sini .
Rencana yang bagus.
Bangunan
Saya penggemar CMake, tetapi seperti pertanyaan terkait pengujian Anda, SO mungkin bukan tempat terbaik untuk meminta pendapat tentang masalah subjektif semacam itu.
Pustaka akan muncul sebagai target "ProjectLibrary", dan rangkaian pengujian sebagai target "ProjectTest". Dengan menetapkan library sebagai dependensi exe pengujian, membuat exe pengujian secara otomatis akan membuat library tersebut dibangun kembali jika sudah usang.
CMake merekomendasikan build "out-of-source", yaitu Anda membuat direktori build sendiri di luar proyek dan menjalankan CMake dari sana. Ini untuk menghindari "mencemari" struktur kode sumber Anda dengan file build, dan sangat diinginkan jika Anda menggunakan vcs.
Anda dapat menentukan bahwa binari dipindahkan atau disalin ke direktori yang berbeda setelah dibuat, atau dibuat secara default di direktori lain, tetapi biasanya tidak diperlukan. CMake menyediakan cara yang komprehensif untuk menginstal proyek Anda jika diinginkan, atau memudahkan proyek CMake lain untuk "menemukan" file yang relevan dari proyek Anda.
Berkenaan dengan dukungan CMake sendiri untuk menemukan dan menjalankan pengujian gtest , ini sebagian besar tidak sesuai jika Anda membangun gtest sebagai bagian dari proyek Anda. The
FindGtest
Modul benar-benar dirancang untuk digunakan dalam kasus di mana gtest telah dibangun secara terpisah di luar proyek Anda.CMake menyediakan kerangka pengujiannya sendiri (CTest), dan idealnya, setiap kasus paling gt akan ditambahkan sebagai kasus CTest.
Namun,
GTEST_ADD_TESTS
makro yang disediakan olehFindGtest
untuk memungkinkan penambahan kasus gtest dengan mudah karena kasus ctest individual agak kurang karena tidak berfungsi untuk makro gtest selainTEST
danTEST_F
. Nilai- atau Type-parameterised tes menggunakanTEST_P
,TYPED_TEST_P
, dll tidak ditangani sama sekali.Masalahnya tidak memiliki solusi mudah yang saya tahu. Cara paling kuat untuk mendapatkan daftar kasus paling gt adalah dengan menjalankan exe pengujian dengan panji
--gtest_list_tests
. Namun, ini hanya dapat dilakukan setelah exe dibuat, jadi CMake tidak dapat menggunakan ini. Yang membuat Anda memiliki dua pilihan; CMake harus mencoba mengurai kode C ++ untuk menyimpulkan nama-nama pengujian (sangat tidak sepele jika Anda ingin mempertimbangkan semua makro paling gt, pengujian yang dikomentari, pengujian yang dinonaktifkan), atau kasus pengujian ditambahkan secara manual ke File CMakeLists.txt.Ya - meskipun saya tidak memiliki pengalaman di bidang ini. CMake menyediakan
FindDoxygen
untuk tujuan ini.sumber
Selain jawaban (luar biasa) lainnya, saya akan menjelaskan struktur yang telah saya gunakan untuk proyek berskala relatif besar .
Saya tidak akan membahas subpertanyaan tentang Doxygen, karena saya hanya akan mengulangi apa yang dikatakan di jawaban lain.
Alasan
Untuk modularitas dan pemeliharaan, proyek diatur sebagai sekumpulan unit kecil. Untuk kejelasan, beri nama UnitX, dengan X = A, B, C, ... (tetapi mereka dapat memiliki nama umum apa pun). Struktur direktori kemudian diatur untuk mencerminkan pilihan ini, dengan kemungkinan untuk mengelompokkan unit jika perlu.
Larutan
Tata letak direktori dasarnya adalah sebagai berikut (konten unit dirinci nanti):
project/CMakeLists.txt
bisa berisi yang berikut ini:dan
project/GroupA/CMakeLists.txt
:dan
project/GroupB/CMakeLists.txt
:Sekarang ke struktur unit yang berbeda (mari kita ambil, sebagai contoh, UnitD)
Untuk berbagai komponen:
.cpp
) dan headers (.h
) di folder yang sama. Ini menghindari hierarki direktori duplikat, membuat pemeliharaan lebih mudah. Untuk instalasi, tidak masalah (terutama dengan CMake) untuk hanya memfilter file header.UnitD
adalah nanti memungkinkan termasuk file dengan#include <UnitD/ClassA.h>
. Selain itu, saat menginstal unit ini, Anda cukup menyalin struktur direktori apa adanya. Perhatikan bahwa Anda juga dapat mengatur file sumber Anda di subdirektori.README
file untuk meringkas tentang apa unit itu dan menentukan informasi yang berguna tentangnya.CMakeLists.txt
hanya bisa berisi:lib/CMakeLists.txt
:Di sini, perhatikan bahwa tidak perlu memberi tahu CMake bahwa kita menginginkan direktori include untuk
UnitA
danUnitC
, karena ini sudah ditentukan saat mengkonfigurasi unit tersebut. Juga,PUBLIC
akan memberi tahu semua target yang bergantung padanyaUnitD
bahwa mereka harus secara otomatis menyertakanUnitA
ketergantungan, sementaraUnitC
tidak akan diperlukan then (PRIVATE
).test/CMakeLists.txt
(lihat lebih jauh di bawah jika Anda ingin menggunakan GTest untuk itu):Menggunakan GoogleTest
Untuk Google Test, yang paling mudah adalah jika sumbernya ada di suatu tempat di direktori sumber Anda, tetapi Anda tidak perlu benar-benar menambahkannya sendiri. Saya telah menggunakan proyek ini untuk mengunduhnya secara otomatis, dan saya membungkus penggunaannya dalam sebuah fungsi untuk memastikan bahwa itu diunduh hanya sekali, meskipun kami memiliki beberapa target pengujian.
Fungsi CMake ini adalah sebagai berikut:
dan kemudian, ketika saya ingin menggunakannya di dalam salah satu target pengujian saya, saya akan menambahkan baris berikut ke
CMakeLists.txt
(ini untuk contoh di atas,test/CMakeLists.txt
):sumber