C ++ proje organizasyonu (gtest, cmake ve doxygen ile)


123

Genel olarak programlamada yeniyim, bu yüzden C ++ 'da basit bir vektör sınıfı oluşturarak başlamaya karar verdim. Bununla birlikte, iş akışımı daha sonra değiştirmeye çalışmak yerine, en baştan iyi alışkanlıklar edinmek isterim.

Şu anda sadece iki dosyam var vector3.hppve vector3.cpp. Her şeye daha aşina hale geldikçe, bu proje yavaş yavaş büyümeye başlayacak (onu genel bir doğrusal cebir kitaplığı haline getirecek), bu yüzden hayatı daha sonra kolaylaştırmak için "standart" bir proje düzeni benimsemek istiyorum. Bu yüzden etrafa baktıktan sonra hpp ve cpp dosyalarını organize etmenin iki yolunu buldum, ilki:

project
└── src
    ├── vector3.hpp
    └── vector3.cpp

ve ikinci varlık:

project
├── inc
   └── project
       └── vector3.hpp
└── src
    └── vector3.cpp

Hangisini önerirsiniz ve neden?

İkinci olarak, kullanımı oldukça kolay göründüğü için kodumu birim testi için Google C ++ Test Çerçevesini kullanmak istiyorum. Bunu kodumla birlikte, örneğin bir inc/gtestveya contrib/gtestklasörde paketlemenizi önerir misiniz ? Paketlenmişse, fuse_gtest_files.pysayıyı veya dosyaları azaltmak için komut dosyasını kullanmanızı veya olduğu gibi bırakmanızı önerir misiniz ? Paketlenmemişse, bu bağımlılık nasıl ele alınır?

Yazma testleri söz konusu olduğunda, bunlar genel olarak nasıl düzenlenir? Her sınıf için bir cpp dosyasına sahip olmayı düşünüyordum ( test_vector3.cppörneğin), ancak hepsi birlikte kolayca çalıştırılabilmeleri için hepsi bir ikili dosyada derlendi mi?

Gtest kütüphanesi genellikle cmake ve make kullanılarak inşa edildiğinden, projemin de böyle inşa edilmesinin mantıklı olacağını düşünüyordum. Aşağıdaki proje düzenini kullanmaya karar verirsem:

├── CMakeLists.txt
├── contrib
   └── gtest
       ├── gtest-all.cc
       └── gtest.h
├── docs
   └── Doxyfile
├── inc
   └── project
       └── vector3.cpp
├── src
   └── vector3.cpp
└── test
    └── test_vector3.cpp

CMakeLists.txtYalnızca kitaplığı veya kitaplığı ve testleri inşa edebilmesi için nasıl görünmesi gerekir? Ayrıca bir buildve bir bindizini olan pek çok proje gördüm . Yapı, yapı dizininde mi oluyor ve ardından ikili dosyalar bin dizinine mi taşınıyor? Testler ve kütüphane için ikili dosyalar aynı yerde mi yaşar? Veya aşağıdaki gibi yapılandırmak daha mantıklı olur mu:

test
├── bin
├── build
└── src
    └── test_vector3.cpp

Kodumu belgelemek için doxygen kullanmak istiyorum. Bunun otomatik olarak cmake ve make ile çalışmasını sağlamak mümkün mü?

Pek çok soru için özür dilerim, ancak C ++ ile ilgili bu tür soruları tatmin edici bir şekilde yanıtlayan bir kitap bulamadım.


6
Güzel soru, ancak Stack Overflow'un Soru-Cevap formatı için uygun olduğunu düşünmüyorum . Yine de bir cevapla çok ilgileniyorum. +1 & fav
Luchian Grigore

1
Bunlar devasa birçok soru. Birkaç küçük soruya bölmek ve birbirine bağlantılar yerleştirmek daha iyi olabilir mi? Her neyse, son bölümü yanıtlamak için: CMake ile src dizininizin içinde ve dışında oluşturmayı seçebilirsiniz (dışarıda tavsiye ederim). Ve evet doxygen'i CMake ile otomatik olarak kullanabilirsiniz.
mistapink

Yanıtlar:


84

C ++ derleme sistemleri biraz siyah bir sanattır ve proje ne kadar eski olursa o kadar tuhaf şeyler bulabilirsin, bu yüzden çok fazla sorunun gelmesi şaşırtıcı değildir. Soruları tek tek gözden geçirmeye çalışacağım ve C ++ kitaplıkları oluşturmayla ilgili bazı genel şeylerden bahsedeceğim.

Dizinlerdeki üstbilgileri ve cpp dosyalarını ayırma. Bu, yalnızca gerçek bir uygulamanın aksine kitaplık olarak kullanılması gereken bir bileşen oluşturuyorsanız gereklidir. Başlıklarınız, kullanıcıların sunduğunuz ürün veya hizmetlerle etkileşim kurması için temel oluşturur ve yüklenmesi gerekir. Bu, bir alt dizinde olmaları gerektiği anlamına gelir (hiç kimse üst düzeyde çok sayıda başlık istemez /usr/include/) ve başlıklarınızın kendilerini böyle bir kuruluma dahil edebilmesi gerekir.

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

işe yarıyor, çünkü içerme yolları işliyor ve yükleme hedefleri için kolay genelleme kullanabilirsiniz.

Paketleme bağımlılıkları: Bence bu, büyük ölçüde derleme sisteminin bağımlılıkları bulma ve yapılandırma becerisine ve kodunuzun tek bir sürüme ne kadar bağımlı olduğuna bağlıdır. Ayrıca, kullanıcılarınızın ne kadar yetenekli olduğuna ve platformlarına bağımlılığın ne kadar kolay kurulduğuna da bağlıdır. CMake find_package, Google Test için bir komut dosyasıyla birlikte gelir . Bu, işleri çok daha kolaylaştırır. Sadece gerektiğinde bohçalama yapardım ve aksi halde bundan kaçınırdım.

Nasıl oluşturulur: Kaynak içi derlemelerden kaçının. CMake, kaynak derlemelerini kolaylaştırır ve hayatı çok daha kolaylaştırır.

Sisteminiz için testler çalıştırmak için CTest'i de kullanmak isteyeceğinizi düşünüyorum (ayrıca GTest için yerleşik destek ile birlikte gelir). Dizin düzeni ve test organizasyonu için önemli bir karar şu olacaktır: Sonunda alt projeler mi olacak? Öyleyse, CMakeLists'i kurarken biraz daha çalışmaya ihtiyacınız var ve alt projelerinizi, her biri kendi includeve srcdosyalarına sahip alt dizinlere bölmelisiniz . Belki kendi doxygen çalıştırmaları ve çıktıları bile (birden fazla doxygen projesini birleştirmek mümkündür, ancak kolay veya hoş değil).

Bunun gibi bir şeyle sonuçlanacaksın:

└── 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

nerede

  • (1) bağımlılıkları, platform özelliklerini ve çıktı yollarını yapılandırır
  • (2) oluşturacağınız kütüphaneyi yapılandırır
  • (3) test yürütülebilir dosyalarını ve test senaryolarını yapılandırır

Alt bileşenleriniz varsa, başka bir hiyerarşi eklemenizi ve her bir alt proje için yukarıdaki ağacı kullanmanızı öneririm. Sonra işler karmaşıklaşır, çünkü alt bileşenlerin bağımlılıklarını arayıp aramayacağına veya bunu en üst düzeyde yapıp yapmayacağınıza karar vermeniz gerekir. Buna vaka bazında karar verilmelidir.

Doxygen: Doxygen'in yapılandırma dansından geçmeyi başardıktan sonra, add_custom_commandbir doc hedefi eklemek için CMake kullanmak önemsizdir .

Projelerim böyle bitiyor ve çok benzer bazı projeler gördüm, ama elbette bu her şeyin çaresi değil.

Eklenti Bir noktada config.hpp , bir sürüm tanımını ve belki bazı sürüm kontrol tanımlayıcısını (bir Git hash veya SVN revizyon numarası) içeren bir dosya oluşturmak isteyeceksiniz . CMake, bu bilgileri bulmayı otomatikleştirmek ve dosya oluşturmak için modüller içerir. configure_fileBir şablon dosyasındaki değişkenleri, .make dosyasında tanımlanan değişkenlerle değiştirmek için CMake'leri kullanabilirsiniz CMakeLists.txt.

Kitaplıklar oluşturuyorsanız, derleyiciler arasındaki farkı doğru bir şekilde elde etmek için bir dışa aktarım tanımlamasına da ihtiyacınız olacaktır, örneğin __declspecMSVC ve visibilityGCC / clang üzerindeki öznitelikler.


2
Güzel yanıt, ancak başlık dosyalarınızı neden ek bir proje adlı alt dizine koymanız gerektiği konusunda hala açık değilim: "/prj/include/prj/foo.hpp", bana gereksiz görünüyor. Neden sadece "/prj/include/foo.hpp" değil? Kurulum sırasında kurulum dizinlerini yeniden düzenleme fırsatına sahip olacağınızı varsayıyorum, böylece yüklediğinizde <INSTALL_DIR> /include/prj/foo.hpp alacaksınız, yoksa CMake altında bu zor mu?
William Payne

6
@William Aslında CPack ile yapmak zor. Ayrıca, içerdiği kaynak dosyalarınız nasıl görünür? Bunlar kurulu bir sürümde sadece "header.hpp" ise "/ usr / include / prj /", "/ usr / include" yerine "include" yolunda olmalıdır.
pmr

37

Başlangıç ​​olarak, göz ardı edemeyeceğiniz bazı geleneksel isimler vardır, bunlar Unix dosya sistemindeki uzun geleneğe dayanmaktadır. Bunlar:

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

En azından en üst düzeyde bu temel düzene bağlı kalmak muhtemelen iyi bir fikirdir.

Başlık dosyalarını ve kaynak dosyalarını (cpp) bölme hakkında, her iki şema da oldukça yaygındır. Ancak, onları bir arada tutmayı tercih ediyorum, günlük işlerde dosyaların bir arada olması daha pratik. Ayrıca, kodun tamamı tek bir üst düzey klasörün, yani klasörün altında olduğunda trunk/src/, diğer tüm klasörlerin (bin, lib, include, doc ve belki bazı test klasörlerinin) en üst düzeyde olduğunu ve buna ek olarak kaynak dışı derlemenin "derleme" dizini, oluşturma sürecinde oluşturulan dosyalardan başka bir şey içermeyen tüm klasörlerdir. Ve bu nedenle, yalnızca src klasörünün yedeklenmesi veya daha da iyisi bir sürüm kontrol sistemi / sunucusu (Git veya SVN gibi) altında tutulması gerekir.

Ve başlık dosyalarınızı hedef sisteme yüklemek söz konusu olduğunda (sonunda kitaplığınızı dağıtmak istiyorsanız), CMake'in dosyaları yüklemek için bir komutu vardır (örtük olarak bir "yükleme" hedefi oluşturur, "make install" yapmak için) tüm başlıkları /usr/include/dizine yerleştirmek için kullanabilirsiniz . Bu amaçla sadece aşağıdaki cmake makrosunu kullanıyorum:

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

Nerede SRCROOTBen src klasörüne belirlemesinin bir cmake değişkendir ve INCLUDEROOTben başlıklarına gitmeye gerek her yere yapılandırmanız cmake değişkendir. Tabii ki, bunu yapmanın birçok yolu var ve eminim ki benim yöntemim en iyisi değildir. Mesele şu ki, başlıkları ve kaynakları ayırmak için bir neden yok çünkü hedef sisteme yalnızca başlıkların yüklenmesi gerekiyor, çünkü özellikle CMake (veya CPack) ile başlıkları seçip yapılandırmak çok kolay. ayrı bir dizinde bulundurmak zorunda kalmadan kurulabilir. Ve çoğu kütüphanede gördüğüm şey bu.

Alıntı: İkinci olarak, kullanımı oldukça kolay göründüğü için kodumu birim test etmek için Google C ++ Test Çerçevesini kullanmak istiyorum. Bunu kodumla, örneğin bir "inc / gtest" veya "katkıda / gtest" klasöründe paketlemenizi önerir misiniz? Paketlenmişse, sayıyı veya dosyaları azaltmak için fuse_gtest_files.py komut dosyasını kullanmanızı veya olduğu gibi bırakmanızı önerir misiniz? Paketlenmemişse, bu bağımlılık nasıl ele alınır?

Bağımlılıkları kitaplığınızla birleştirmeyin. Bu genellikle oldukça korkunç bir fikir ve bunu yapan bir kütüphane oluşturmaya çalışırken takılıp kaldığımda her zaman bundan nefret ediyorum. Bu sizin son çare olmalı ve tuzaklara karşı dikkatli olun. Çoğu zaman, insanlar ya korkunç bir geliştirme ortamını (örneğin, Windows) hedefledikleri için ya da söz konusu kitaplığın yalnızca eski (kullanımdan kaldırılmış) bir sürümünü (bağımlılık) destekledikleri için bağımlılıkları kitaplıklarıyla bir araya toplarlar. Temel tuzak, paketlenmiş bağımlılığınızın aynı kitaplığın / uygulamanın halihazırda yüklenmiş sürümleriyle çakışabilir (örneğin, gtest'i paketlediniz, ancak kitaplığınızı oluşturmaya çalışan kişi zaten daha yeni (veya daha eski) bir gtest sürümüne sahipse, o zaman ikisi çatışabilir ve o kişiye çok kötü bir baş ağrısı verebilir). Yani, dediğim gibi, riski size ait olacak şekilde yapın. ve sadece son çare olarak söyleyebilirim. İnsanlardan kitaplığınızı derlemeden önce birkaç bağımlılık kurmalarını istemek, paketlenmiş bağımlılıklarınız ve mevcut kurulumlarınız arasındaki çatışmaları çözmeye çalışmaktan çok daha az kötüdür.

Alıntı: Yazma testleri söz konusu olduğunda, bunlar genel olarak nasıl düzenlenir? Her sınıf için (örneğin test_vector3.cpp) bir cpp dosyasına sahip olmayı düşünüyordum, ancak hepsi birlikte kolayca çalıştırılabilmeleri için tek bir ikili dosyada derlendi mi?

Sınıf başına bir cpp dosyası (veya küçük birleşik sınıflar ve işlevler grubu) bence daha normal ve pratiktir. Ancak, kesinlikle, hepsini tek bir ikili dosyada derlemeyin, böylece "hepsi birlikte çalıştırılabilir". Bu gerçekten kötü bir fikir. Genel olarak, kodlama söz konusu olduğunda, makul olduğu kadar işleri bölmek istersiniz. Birim testleri söz konusu olduğunda, tek bir ikilinin tüm testleri çalıştırmasını istemezsiniz, çünkü bu, kitaplığınızdaki herhangi bir şeyde yapacağınız herhangi bir küçük değişikliğin muhtemelen o birim test programının neredeyse tamamen yeniden derlenmesine neden olacağı anlamına gelir. ve bu, yeniden derlemeyi bekleyen dakikalar / saatler. Basit bir şemaya bağlı kalın: 1 birim = 1 birim test programı. Sonra,

Alıntı: gtest kütüphanesi genellikle cmake ve make kullanılarak inşa edildiğinden, projemin de böyle inşa edilmesinin mantıklı olacağını düşünüyordum. Aşağıdaki proje düzenini kullanmaya karar verirsem:

Bu düzeni önermeyi tercih ederim:

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

Burada dikkat edilmesi gereken birkaç nokta. İlk olarak, src dizininizin alt dizinleri, içerme dizininizin alt dizinlerini yansıtmalıdır; bu, yalnızca şeyleri sezgisel tutmak içindir (ayrıca, alt dizin yapınızı makul derecede düz (sığ) tutmaya çalışın, çünkü klasörlerin derinlemesine yuvalanması genellikle her şeyden çok güçlüktür). İkincisi, "include" dizini sadece bir kurulum dizinidir, içeriği src dizininden seçilen başlıklardır.

Üçüncüsü, CMake sisteminin en üst düzeyde bir CMakeLists.txt dosyası olarak değil, kaynak alt dizinler üzerinden dağıtılması amaçlanmıştır. Bu, her şeyi yerel tutar ve bu iyidir (her şeyi bağımsız parçalara bölme ruhuyla). Yeni bir kaynak, yeni bir başlık veya yeni bir test programı eklerseniz, ihtiyacınız olan tek şey söz konusu alt dizindeki küçük ve basit bir CMakeLists.txt dosyasını başka hiçbir şeyi etkilemeden düzenlemektir. Bu aynı zamanda dizinleri kolaylıkla yeniden yapılandırmanıza izin verir (CMakeLists yereldir ve taşınan alt dizinlerde yer alır). En üst düzey CMakeListleri, hedef dizinleri, özel komutları (veya makroları) ayarlama ve sistemde yüklü paketleri bulma gibi üst düzey yapılandırmaların çoğunu içermelidir. Alt düzey CMakeListleri yalnızca basit başlıkların, kaynakların ve kaynakların listelerini içermelidir.

Alıntı: Yalnızca kitaplığı veya kitaplığı ve testleri oluşturabilmesi için CMakeLists.txt'nin nasıl görünmesi gerekir?

Temel yanıt, CMake'in belirli hedefleri "tümü" nden ("make" yazdığınızda oluşturulan şey) özel olarak hariç tutmanıza izin vermesidir ve ayrıca belirli hedef grupları da oluşturabilirsiniz. Burada bir CMake eğitimi yapamam, ancak kendi başınıza öğrenmek oldukça basittir. Bununla birlikte, bu özel durumda, elbette, önerilen çözüm, birim olarak işaretlenmiş bir dizi hedefi (programı) kaydetmek için CMakeLists dosyalarında kullanabileceğiniz ek bir komut seti olan CTest'i kullanmaktır. testleri. Dolayısıyla, CMake tüm testleri özel bir yapı kategorisine koyacak ve tam olarak istediğiniz şey bu, yani problem çözüldü.

Alıntı: Ayrıca, bir bin dizini oluşturma reklamı olan birkaç proje gördüm. Yapı, yapı dizininde mi oluyor ve ardından ikili dosyalar bin dizinine mi taşınıyor? Testler ve kütüphane için ikili dosyalar aynı yerde mi yaşar? Veya aşağıdaki gibi yapılandırmak daha mantıklı olur mu:

Kaynağın dışında bir derleme dizinine sahip olmak ("kaynak dışı" derleme) gerçekten yapılacak tek mantıklı şeydir, bu günlerde fiili standarttır. Dolayısıyla, kesinlikle, CMake insanlarının önerdiği ve şimdiye kadar tanıştığım her programcının yaptığı gibi, kaynak dizinin dışında ayrı bir "yapı" dizinine sahip olun. Bin dizinine gelince, bu bir kongre ve bu yazının başında söylediğim gibi, ona bağlı kalmak muhtemelen iyi bir fikirdir.

Alıntı: Kodumu belgelemek için doxygen kullanmak istiyorum. Bunun otomatik olarak cmake ve make ile çalışmasını sağlamak mümkün mü?

Evet. Mümkün olandan daha fazlası, harika. Ne kadar süslü olmak istediğinize bağlı olarak, birkaç olasılık vardır. CMake, find_package(Doxygen)bazı dosyalarda Doxygen çalıştıracak hedefleri kaydetmenize olanak tanıyan Doxygen (yani ) için bir modüle sahiptir . Doxyfile'daki sürüm numarasını güncellemek veya kaynak dosyalar için otomatik olarak bir tarih / yazar damgası girmek gibi daha süslü şeyler yapmak istiyorsanız, hepsi biraz CMake kung-fu ile mümkündür. Genel olarak, bunu yapmak, bulunacak ve CMake'nin ayrıştırma komutları ile değiştirilecek tokenlere sahip bir Doxyfile kaynağını (örneğin, yukarıdaki klasör düzenine koyduğum "Doxyfile.in") tutmanızı gerektirecektir. En üst düzey CMakeLists dosyamda , cmake-doxygen ile birkaç süslü şey yapan böyle bir CMake kung-fu parçası bulacaksınız.


Yani main.cppgitmeli trunk/binmi?
Ugnius Malūkas

17

Projeyi yapılandırma

Genel olarak aşağıdakileri tercih ederim:

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

Bu, kitaplığınız için çok açık bir şekilde tanımlanmış bir API dosyalarına sahip olduğunuz anlamına gelir ve yapı, kitaplığınızın istemcilerinin

#include "project/vector3.hpp"

daha az açıktan ziyade

#include "vector3.hpp"


/ Src ağacının yapısının / include ağacının yapısıyla eşleşmesini seviyorum, ama bu gerçekten kişisel bir tercih. Bununla birlikte, projeniz / include / project içindeki alt dizinleri içerecek şekilde genişlerse, genellikle / src ağacının içindekilerle eşleştirmeye yardımcı olur.

Testler için, onları test ettikleri dosyalara "yakın" tutmayı tercih ederim ve eğer / src içinde alt dizinlerle karşılaşırsanız, belirli bir dosyanın test kodunu bulmak isteyen diğerlerinin izlemesi oldukça kolay bir paradigma.


Test yapmak

İkinci olarak, kullanımı oldukça kolay göründüğü için kodumu birim testi için Google C ++ Test Çerçevesini kullanmak istiyorum.

Gtest'in kullanımı gerçekten basittir ve yetenekleri açısından oldukça kapsamlıdır. Yeteneklerini genişletmek için gmock ile birlikte çok kolay bir şekilde kullanılabilir , ancak gmock ile kendi deneyimlerim daha az elverişli olmuştur. Bunun kendi eksikliklerime bağlı olabileceğini kabul etmeye oldukça hazırım, ancak gmock testlerinin oluşturulması daha zor ve çok daha kırılgan / bakımı zor olma eğilimindedir. Gmock tabuttaki büyük bir çivi, akıllı işaretçilerle gerçekten iyi oynamamasıdır.

Bu, çok büyük bir soruya verilen çok önemsiz ve öznel bir cevaptır (muhtemelen gerçekten SO'ya ait değildir)

Bunu kodumla, örneğin bir "inc / gtest" veya "katkıda / gtest" klasöründe paketlemenizi önerir misiniz? Paketlenmişse, sayıyı veya dosyaları azaltmak için fuse_gtest_files.py komut dosyasını kullanmanızı veya olduğu gibi bırakmanızı önerir misiniz? Paketlenmemişse, bu bağımlılık nasıl ele alınır?

CMake'nin ExternalProject_Addmodülünü kullanmayı tercih ederim . Bu, gtest kaynak kodunu deponuzda tutmanıza veya herhangi bir yere kurmanıza gerek kalmaz. Yapı ağacınıza otomatik olarak indirilir ve oluşturulur.

Buradaki ayrıntılarla ilgili cevabımı görün .

Yazma testleri söz konusu olduğunda, bunlar genel olarak nasıl düzenlenir? Her sınıf için (örneğin test_vector3.cpp) bir cpp dosyasına sahip olmayı düşünüyordum, ancak hepsi birlikte kolayca çalıştırılabilmeleri için tek bir ikili dosyada derlendi mi?

İyi plan.


bina

CMake hayranıyım, ancak testle ilgili sorularınızda olduğu gibi, SO muhtemelen böylesine öznel bir konu hakkında fikir almak için en iyi yer değildir.

CMakeLists.txt dosyasının sadece kitaplığı veya kitaplığı ve testleri oluşturabilmesi için nasıl görünmesi gerekir?

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

Kitaplık bir hedef "Proje Kitaplığı" olarak ve test paketi bir hedef "Proje Testi" olarak görünecektir. Kitaplığı test exe'nin bir bağımlılığı olarak belirterek, test exe'nin oluşturulması, kütüphanenin tarihi geçmişse otomatik olarak yeniden oluşturulmasına neden olur.

Ayrıca, bir bin dizini oluşturma reklamı olan birkaç proje gördüm. Yapı, yapı dizininde mi oluyor ve ardından ikili dosyalar bin dizinine mi taşınıyor? Testler ve kütüphane için ikili dosyalar aynı yerde mi yaşar?

CMake "kaynak dışı" derlemeleri önerir, yani proje dışında kendi derleme dizininizi yaratırsınız ve CMake'i oradan çalıştırırsınız. Bu, kaynak ağacınızın derleme dosyalarıyla "kirlenmesini" önler ve bir vcs kullanıyorsanız oldukça arzu edilir bir durumdur.

Sen edebilirsiniz inşa kez ikili taşınmış veya farklı bir dizine kopyalanır belirtmek ya da başka bir dizinde varsayılan olarak oluşturulur, ama hiç gerek genelde yoktur. CMake, istenirse projenizi kurmanız için kapsamlı yollar sağlar veya diğer CMake projelerinin projenizin ilgili dosyalarını "bulmasını" kolaylaştırır.

CMake'nin gtest testlerini bulma ve yürütme konusundaki kendi desteğiyle ilgili olarak , gtest'i projenizin bir parçası olarak oluşturursanız, bu büyük ölçüde uygunsuz olacaktır. FindGtestModül gerçekten GTEST ayrı dışında projenin inşa edilmiştir durumunda kullanılmak üzere tasarlanmıştır.

CMake kendi test çerçevesini (CTest) sağlar ve ideal olarak, her gtest vakası bir CTest vakası olarak eklenir.

Bununla birlikte, gtest durumlarının tek tek ctest durumları olarak kolayca eklenmesine izin vermek için GTEST_ADD_TESTSsağlanan makro FindGtest, gtest'in TESTve dışındaki makrolar için çalışmaması bakımından eksiktir TEST_F. Value- veya Tip-parameterised testler kullanarak TEST_P, TYPED_TEST_Pvb hiç işlenmez.

Sorunun bildiğim kadar kolay bir çözümü yok. Gtest vakalarının listesini almanın en sağlam yolu, test exe'yi bayrakla çalıştırmaktır --gtest_list_tests. Ancak, bu yalnızca exe oluşturulduktan sonra yapılabilir, bu nedenle CMake bunu kullanamaz. Bu da size iki seçenek bırakıyor; CMake, testlerin adlarını çıkarmak için C ++ kodunu ayrıştırmaya çalışmalıdır (tüm gtest makrolarını, yorumlanmış testleri, devre dışı bırakılmış testleri hesaba katmak istiyorsanız aşırı derecede önemsiz değildir) veya test durumları el ile eklenir. CMakeLists.txt dosyası.

Kodumu belgelemek için doxygen kullanmak istiyorum. Bunun otomatik olarak cmake ve make ile çalışmasını sağlamak mümkün mü?

Evet - bu konuda tecrübem olmasa da. CMake FindDoxygenbu amaç için sağlar .


6

Diğer (mükemmel) cevaplara ek olarak, nispeten büyük ölçekli projeler için kullandığım bir yapıyı anlatacağım .
Diğer cevaplarda söylenenleri tekrar edeceğim için Doxygen ile ilgili alt soruyu ele almayacağım.


gerekçe

Modülerlik ve sürdürülebilirlik için, proje bir dizi küçük birim olarak düzenlenmiştir. Netlik sağlamak için, X = A, B, C, ... ile UnitX adını verelim (ancak herhangi bir genel isme sahip olabilirler). Rehber yapısı daha sonra bu seçimi yansıtacak şekilde düzenlenir ve gerekirse birimleri gruplama olanağı sağlanır.

Çözüm

Temel dizin düzeni aşağıdaki gibidir (birimlerin içeriği daha sonra ayrıntılı olarak açıklanmıştır):

project
├── CMakeLists.txt
├── UnitA
├── UnitB
├── GroupA
   └── CMakeLists.txt
   └── GroupB
       └── CMakeLists.txt
       └── UnitC
       └── UnitD
   └── UnitE

project/CMakeLists.txt aşağıdakileri içerebilir:

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)

ve project/GroupA/CMakeLists.txt:

add_subdirectory(GroupB)
add_subdirectory(UnitE)

ve project/GroupB/CMakeLists.txt:

add_subdirectory(UnitC)
add_subdirectory(UnitD)

Şimdi farklı birimlerin yapısına (örnek olarak UnitD alalım)

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]

Farklı bileşenlere:

  • Kaynak ( .cpp) ve başlıkların ( .h) aynı klasörde olmasını seviyorum . Bu, yinelenen bir dizin hiyerarşisini önler ve bakımı kolaylaştırır. Kurulum için, sadece başlık dosyalarını filtrelemek (özellikle CMake ile) sorun değildir.
  • Dizinin rolü UnitDdaha sonra ile dosyaların dahil edilmesine izin vermektir #include <UnitD/ClassA.h>. Ayrıca, bu birimi kurarken, dizin yapısını olduğu gibi kopyalayabilirsiniz. Kaynak dosyalarınızı alt dizinler halinde de düzenleyebileceğinizi unutmayın.
  • READMEÜnitenin neyle ilgili olduğunu özetlemek ve onunla ilgili yararlı bilgileri belirtmek için bir dosya seviyorum .
  • CMakeLists.txt basitçe şunları içerebilir:

    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
                          )

    Burada, gerekli değildir notu o istediğimiz için dizinleri içerdiğini CMake anlatmak UnitAve UnitCbu birimler yapılandırırken bu zaten belirtilmiş olduğu gibi,. Ayrıca, bağımlılığı otomatik olarak PUBLICiçermelerine bağlı olan tüm hedeflere söyleyecektir , ancak bu durumda gerekli olmayacaktır ( ).UnitDUnitAUnitCPRIVATE

  • test/CMakeLists.txt (GTest'i bunun için kullanmak istiyorsanız aşağıya bakın):

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

GoogleTest'i kullanma

Google Test için en kolayı, kaynağının kaynak dizininizde bir yerde bulunmasıdır, ancak onu gerçekten oraya kendiniz eklemeniz gerekmez. Bu projeyi otomatik olarak indirmek için kullanıyorum ve birkaç test hedefimiz olmasına rağmen kullanımını yalnızca bir kez indirildiğinden emin olmak için bir işlevle sarmalıyorum.

Bu CMake işlevi aşağıdaki gibidir:

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

ve sonra, test hedeflerimden birinde kullanmak istediğimde, aşağıdaki satırları ekleyeceğim CMakeLists.txt(bu yukarıdaki örnek içindir test/CMakeLists.txt):

import_gtest()
target_link_libraries(UnitDTests gtest_main gmock_main)

Orada Gtest ve cmake ile yaptığınız güzel "hack"! Kullanışlı! :)
Tanasis
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.