Organisasi proyek C ++ (dengan gtest, cmake, dan doxygen)

123

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.hppdan 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/gtestatau 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.cppmisalnya) 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.txtharus terlihat sehingga bisa membangun hanya perpustakaan atau perpustakaan dan tes? Juga saya telah melihat beberapa proyek yang memiliki builddan bindirektori. 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.

berkeliaran
sumber
6
Pertanyaan bagus, tapi menurut saya itu tidak cocok untuk format Q&A Stack Overflow . Saya sangat tertarik dengan jawaban. +1 & fav
Luchian Grigore
1
Ini adalah banyak pertanyaan besar. Mungkin lebih baik untuk membaginya menjadi beberapa pertanyaan kecil dan menempatkan tautan satu sama lain. Pokoknya untuk menjawab bagian terakhir: Dengan CMake Anda dapat memilih untuk membangun di dalam dan di luar direktori src Anda (saya sarankan di luar). Dan ya, Anda dapat menggunakan doxygen dengan CMake secara otomatis.
mistapink

Jawaban:

84

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.

└── prj
    ├── include
       └── prj
           ├── header2.h
           └── header.h
    └── src
        └── x.cpp

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_packageskrip 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 includedansrc 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:

└── prj
    ├── CMakeLists.txt <-- (1)
    ├── include
       └── prj
           ├── header2.hpp
           └── header.hpp
    ├── src
       ├── CMakeLists.txt <-- (2)
       └── x.cpp
    └── test
        ├── CMakeLists.txt <-- (3)
        ├── data
           └── testdata.yyy
        └── testcase.cpp

dimana

  • (1) mengonfigurasi dependensi, spesifikasi platform, dan jalur keluaran
  • (2) mengkonfigurasi perpustakaan yang akan Anda buat
  • (3) mengkonfigurasi test executable dan test-case

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_commanduntuk 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 CMake configure_fileuntuk mengganti variabel dalam file template dengan variabel yang ditentukan di dalam CMakeLists.txt.

Jika Anda membangun perpustakaan, Anda juga memerlukan export define untuk mendapatkan perbedaan antara compiler dengan benar, misalnya __declspecdi MSVC dan visibilityatribut di GCC / clang.

pmr
sumber
2
Jawaban yang bagus, tapi saya masih belum mengerti mengapa Anda perlu meletakkan file header Anda di subdirektori tambahan bernama proyek: "/prj/include/prj/foo.hpp", yang menurut saya berlebihan. Mengapa tidak hanya "/prj/include/foo.hpp"? Saya berasumsi bahwa Anda akan memiliki kesempatan untuk melakukan jig ulang direktori instalasi pada saat instalasi, jadi Anda mendapatkan <INSTALL_DIR> /include/prj/foo.hpp saat menginstal, atau apakah itu sulit di bawah CMake?
William Payne
6
@William Itu sebenarnya sulit dilakukan dengan CPack. Juga, bagaimana tampilan file sumber dalam Anda? Jika hanya berupa "header.hpp" pada versi yang diinstal "/ usr / include / prj /" harus ada di jalur include, bukan hanya "/ usr / include".
pmr
37

Sebagai permulaan, ada beberapa nama konvensional untuk direktori yang tidak dapat Anda abaikan, ini didasarkan pada tradisi lama dengan sistem file Unix. Ini adalah:

trunk
├── bin     : for all executables (applications)
├── lib     : for all other binaries (static and shared libraries (.so or .dll))
├── include : for all header files
├── src     : for source files
└── doc     : for documentation

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:

# custom macro to register some headers as target for installation:
#  setup_headers("/path/to/header/something.h" "/relative/install/path")
macro(setup_headers HEADER_FILES HEADER_PATH)
  foreach(CURRENT_HEADER_FILE ${HEADER_FILES})
    install(FILES "${SRCROOT}${CURRENT_HEADER_FILE}" DESTINATION "${INCLUDEROOT}${HEADER_PATH}")
  endforeach(CURRENT_HEADER_FILE)
endmacro(setup_headers)

Di mana SRCROOTvariabel cmake yang saya setel ke folder src, dan INCLUDEROOTvariabel 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.

Kutipan: 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 digabungkan, apakah Anda menyarankan untuk menggunakan skrip fuse_gtest_files.py untuk mengurangi jumlah atau file, atau membiarkannya apa adanya? Jika tidak digabungkan, bagaimana ketergantungan ini ditangani?

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.

Kutipan: Dalam hal tes menulis, bagaimana biasanya ini diatur? Saya berpikir untuk memiliki satu file cpp untuk setiap kelas (test_vector3.cpp misalnya) tetapi semua dikompilasi menjadi satu biner sehingga semuanya dapat dijalankan bersama dengan mudah?

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,

Kutipan: Karena pustaka paling hebat 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:

Saya lebih suka menyarankan tata letak ini:

trunk
├── bin
├── lib
   └── project
       └── libvector3.so
       └── libvector3.a        products of installation / building
├── docs
   └── Doxyfile
├── include
   └── project
       └── vector3.hpp
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

├── src
   └── CMakeLists.txt
   └── Doxyfile.in
   └── project                 part of version-control / source-distribution
       └── CMakeLists.txt
       └── vector3.hpp
       └── vector3.cpp
       └── test
           └── test_vector3.cpp
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

├── build
└── test                        working directories for building / testing
    └── test_vector3

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,

Kutipan: Bagaimana tampilan CMakeLists.txt sehingga dapat membuat hanya pustaka atau pustaka dan pengujian?

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.

Kutipan: Saya juga telah melihat beberapa proyek yang memiliki direktori iklan build bin. 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:

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.

Kutipan: Saya juga ingin menggunakan doxygen untuk mendokumentasikan kode saya. Apakah mungkin untuk menjalankan ini secara otomatis dengan cmake and make?

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.

Mikael Persson
sumber
Jadi main.cppharus pergi ke trunk/bin?
Ugnius Malūkas
17

Penataan proyek

Saya biasanya menyukai yang berikut:

├── CMakeLists.txt
|
├── docs/
│   └── Doxyfile
|
├── include/
│   └── project/
│       └── vector3.hpp
|
├── src/
    └── project/
        └── vector3.cpp
        └── test/
            └── test_vector3.cpp

Ini berarti Anda memiliki sekumpulan file API yang sangat jelas untuk perpustakaan Anda, dan struktur tersebut berarti bahwa klien perpustakaan Anda akan melakukannya

#include "project/vector3.hpp"

bukan yang kurang eksplisit

#include "vector3.hpp"


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

Kedua, saya ingin menggunakan Kerangka Pengujian Google C ++ untuk pengujian unit kode saya karena tampaknya cukup mudah digunakan.

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)

Apakah Anda menyarankan untuk menggabungkannya dengan kode saya, misalnya dalam folder "inc / gtest" atau "contrib / gtest"? Jika digabungkan, apakah Anda menyarankan menggunakan skrip fuse_gtest_files.py untuk mengurangi jumlah atau file, atau membiarkannya apa adanya? Jika tidak digabungkan, bagaimana ketergantungan ini ditangani?

Saya lebih suka menggunakan ExternalProject_Addmodul 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 .

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 menjadi satu biner sehingga semuanya dapat dijalankan bersama dengan mudah?

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.

Bagaimana tampilan CMakeLists.txt sehingga dapat membangun hanya perpustakaan atau perpustakaan dan pengujian?

add_library(ProjectLibrary <All library sources and headers>)
add_executable(ProjectTest <All test files>)
target_link_libraries(ProjectTest ProjectLibrary)

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.

Saya juga telah melihat beberapa proyek yang memiliki direktori iklan build bin. Apakah build terjadi di direktori build lalu binari dipindahkan ke direktori bin? Apakah binari untuk pengujian dan pustaka berada di tempat yang sama?

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 FindGtestModul 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_TESTSmakro yang disediakan oleh FindGtestuntuk memungkinkan penambahan kasus gtest dengan mudah karena kasus ctest individual agak kurang karena tidak berfungsi untuk makro gtest selain TESTdan TEST_F. Nilai- atau Type-parameterised tes menggunakan TEST_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.

Saya juga ingin menggunakan doxygen untuk mendokumentasikan kode saya. Apakah mungkin untuk menjalankan ini secara otomatis dengan cmake and make?

Ya - meskipun saya tidak memiliki pengalaman di bidang ini. CMake menyediakan FindDoxygenuntuk tujuan ini.

Fraser
sumber
6

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
├── UnitA
├── UnitB
├── GroupA
   └── CMakeLists.txt
   └── GroupB
       └── CMakeLists.txt
       └── UnitC
       └── UnitD
   └── UnitE

project/CMakeLists.txt bisa berisi yang berikut ini:

cmake_minimum_required(VERSION 3.0.2)
project(project)
enable_testing() # This will be necessary for testing (details below)

add_subdirectory(UnitA)
add_subdirectory(UnitB)
add_subdirectory(GroupA)

dan project/GroupA/CMakeLists.txt:

add_subdirectory(GroupB)
add_subdirectory(UnitE)

dan project/GroupB/CMakeLists.txt:

add_subdirectory(UnitC)
add_subdirectory(UnitD)

Sekarang ke struktur unit yang berbeda (mari kita ambil, sebagai contoh, UnitD)

project/GroupA/GroupB/UnitD
├── README.md
├── CMakeLists.txt
├── lib
   └── CMakeLists.txt
   └── UnitD
       └── ClassA.h
       └── ClassA.cpp
       └── ClassB.h
       └── ClassB.cpp
├── test
   └── CMakeLists.txt
   └── ClassATest.cpp
   └── ClassBTest.cpp
   └── [main.cpp]

Untuk berbagai komponen:

  • Saya suka memiliki source ( .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.
  • Peran direktori UnitDadalah 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.
  • Saya suka READMEfile untuk meringkas tentang apa unit itu dan menentukan informasi yang berguna tentangnya.
  • CMakeLists.txt hanya bisa berisi:

    add_subdirectory(lib)
    add_subdirectory(test)
  • lib/CMakeLists.txt:

    project(UnitD)
    
    set(headers
        UnitD/ClassA.h
        UnitD/ClassB.h
        )
    
    set(sources
        UnitD/ClassA.cpp
        UnitD/ClassB.cpp    
        )
    
    add_library(${TARGET_NAME} STATIC ${headers} ${sources})
    
    # INSTALL_INTERFACE: folder to which you will install a directory UnitD containing the headers
    target_include_directories(UnitD
                               PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
                               PUBLIC $<INSTALL_INTERFACE:include/SomeDir>
                               )
    
    target_link_libraries(UnitD
                          PUBLIC UnitA
                          PRIVATE UnitC
                          )

    Di sini, perhatikan bahwa tidak perlu memberi tahu CMake bahwa kita menginginkan direktori include untuk UnitAdan UnitC, karena ini sudah ditentukan saat mengkonfigurasi unit tersebut. Juga, PUBLICakan memberi tahu semua target yang bergantung padanya UnitDbahwa mereka harus secara otomatis menyertakan UnitAketergantungan, sementara UnitCtidak akan diperlukan then ( PRIVATE).

  • test/CMakeLists.txt (lihat lebih jauh di bawah jika Anda ingin menggunakan GTest untuk itu):

    project(UnitDTests)
    
    add_executable(UnitDTests
                   ClassATest.cpp
                   ClassBTest.cpp
                   [main.cpp]
                   )
    
    target_link_libraries(UnitDTests
                          PUBLIC UnitD
    )
    
    add_test(
            NAME UnitDTests
            COMMAND UnitDTests
    )

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:

function(import_gtest)
  include (DownloadProject)
  if (NOT TARGET gmock_main)
    include(DownloadProject)
    download_project(PROJ                googletest
                     GIT_REPOSITORY      https://github.com/google/googletest.git
                     GIT_TAG             release-1.8.0
                     UPDATE_DISCONNECTED 1
                     )
    set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Prevent GoogleTest from overriding our compiler/linker options when building with Visual Studio
    add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL)
  endif()
endfunction()

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):

import_gtest()
target_link_libraries(UnitDTests gtest_main gmock_main)
oLen
sumber
"Retas" bagus yang Anda lakukan di sana dengan Gtest dan cmake! Berguna! :)
Tanasis