Contoh CMake paling sederhana namun lengkap

117

Entah bagaimana saya benar-benar bingung dengan cara kerja CMake. Setiap kali saya berpikir bahwa saya semakin dekat untuk memahami bagaimana CMake dimaksudkan untuk ditulis, itu menghilang dalam contoh berikutnya yang saya baca. Yang ingin saya ketahui adalah, bagaimana saya harus menyusun proyek saya, sehingga CMake saya memerlukan paling sedikit pemeliharaan di masa mendatang. Misalnya, saya tidak ingin memperbarui CMakeList.txt saya ketika saya menambahkan folder baru di pohon src saya, yang berfungsi persis seperti semua folder src lainnya.

Ini adalah bagaimana saya membayangkan struktur proyek saya, tapi tolong ini hanya sebuah contoh. Jika cara yang disarankan berbeda, tolong beri tahu saya, dan beri tahu saya cara melakukannya.

myProject
    src/
        module1/
            module1.h
            module1.cpp
        module2/
            [...]
        main.cpp
    test/
        test1.cpp
    resources/
        file.png
    bin
        [execute cmake ..]

Ngomong-ngomong, penting bahwa program saya tahu di mana sumber daya itu berada. Saya ingin mengetahui cara yang direkomendasikan untuk mengelola sumber daya. Saya tidak ingin mengakses sumber daya saya dengan "../resources/file.png"

Arne
sumber
1
For example I don't want to update my CMakeList.txt when I am adding a new folder in my src treedapatkah Anda memberikan contoh IDE yang mengumpulkan sumber secara otomatis?
7
no ide biasanya tidak mengumpulkan sumber secara otomatis, karena tidak perlu. Ketika saya menambahkan file atau folder baru, saya melakukannya di dalam ide, dan proyek diperbarui. Sistem build di sisi lain tidak memperhatikan ketika saya mengubah beberapa file, jadi ini adalah perilaku yang diinginkan yang mengumpulkan semua file sumber secara otomatis
Arne
4
Jika saya melihat tautan itu, saya mendapat kesan bahwa CMake gagal pada tugas terpenting yang ingin diselesaikannya: Membuat sistem pembangunan lintas platform menjadi mudah.
Arne

Jawaban:

94

setelah beberapa penelitian saya sekarang memiliki versi saya sendiri dari contoh cmake yang paling sederhana tapi lengkap. Ini dia, dan mencoba untuk mencakup sebagian besar hal mendasar, termasuk sumber daya dan pengemasan.

satu hal yang tidak standar adalah penanganan sumber daya. Secara default, cmake ingin meletakkannya di / usr / share /, / usr / local / share / dan sesuatu yang setara di windows. Saya ingin memiliki zip / tar.gz sederhana yang dapat Anda ekstrak di mana saja dan dijalankan. Oleh karena itu sumber daya dimuat relatif terhadap yang dapat dieksekusi.

aturan dasar untuk memahami perintah cmake adalah sintaks berikut: <function-name>(<arg1> [<arg2> ...])tanpa koma atau semicolor. Setiap argumen adalah string. foobar(3.0)dan foobar("3.0")sama. Anda dapat mengatur daftar / variabel dengan set(args arg1 arg2). Dengan kumpulan variabel ini foobar(${args}) dan foobar(arg1 arg2)secara efektif sama. Variabel yang tidak ada setara dengan daftar kosong. Sebuah daftar secara internal hanyalah sebuah string dengan titik koma untuk memisahkan elemennya. Oleh karena itu daftar dengan hanya satu elemen adalah menurut definisi elemen itu, tidak ada tinju yang terjadi. Variabel bersifat global. Fungsi builtin menawarkan beberapa bentuk argumen bernama oleh fakta bahwa mereka mengharapkan beberapa id seperti PUBLICatauDESTINATIONdalam daftar argumen mereka, untuk mengelompokkan argumen. Tapi itu bukan fitur bahasa, id tersebut juga hanya string, dan diurai oleh implementasi fungsi.

Anda dapat mengkloning semuanya dari github

cmake_minimum_required(VERSION 3.0)
project(example_project)

###############################################################################
## file globbing ##############################################################
###############################################################################

# these instructions search the directory tree when cmake is
# invoked and put all files that match the pattern in the variables 
# `sources` and `data`
file(GLOB_RECURSE sources      src/main/*.cpp src/main/*.h)
file(GLOB_RECURSE sources_test src/test/*.cpp)
file(GLOB_RECURSE data resources/*)
# you can use set(sources src/main.cpp) etc if you don't want to
# use globing to find files automatically

###############################################################################
## target definitions #########################################################
###############################################################################

# add the data to the target, so it becomes visible in some IDE
add_executable(example ${sources} ${data})

# just for example add some compiler flags
target_compile_options(example PUBLIC -std=c++1y -Wall -Wfloat-conversion)

# this lets me include files relative to the root src dir with a <> pair
target_include_directories(example PUBLIC src/main)

# this copies all resource files in the build directory
# we need this, because we want to work with paths relative to the executable
file(COPY ${data} DESTINATION resources)

###############################################################################
## dependencies ###############################################################
###############################################################################

# this defines the variables Boost_LIBRARIES that contain all library names
# that we need to link to
find_package(Boost 1.36.0 COMPONENTS filesystem system REQUIRED)

target_link_libraries(example PUBLIC
  ${Boost_LIBRARIES}
  # here you can add any library dependencies
)

###############################################################################
## testing ####################################################################
###############################################################################

# this is for our testing framework
# we don't add REQUIRED because it's just for testing
find_package(GTest)

if(GTEST_FOUND)
  add_executable(unit_tests ${sources_test} ${sources})

  # we add this define to prevent collision with the main
  # this might be better solved by not adding the source with the main to the
  # testing target
  target_compile_definitions(unit_tests PUBLIC UNIT_TESTS)

  # this allows us to use our executable as a link library
  # therefore we can inherit all compiler options and library dependencies
  set_target_properties(example PROPERTIES ENABLE_EXPORTS on)

  target_link_libraries(unit_tests PUBLIC
    ${GTEST_BOTH_LIBRARIES}
    example
  )

  target_include_directories(unit_tests PUBLIC
    ${GTEST_INCLUDE_DIRS} # doesn't do anything on Linux
  )
endif()

###############################################################################
## packaging ##################################################################
###############################################################################

# all install commands get the same destination. this allows us to use paths
# relative to the executable.
install(TARGETS example DESTINATION example_destination)
# this is basically a repeat of the file copy instruction that copies the
# resources in the build directory, but here we tell cmake that we want it
# in the package
install(DIRECTORY resources DESTINATION example_destination)

# now comes everything we need, to create a package
# there are a lot more variables you can set, and some
# you need to set for some package types, but we want to
# be minimal here
set(CPACK_PACKAGE_NAME "MyExample")
set(CPACK_PACKAGE_VERSION "1.0.0")

# we don't want to split our program up into several things
set(CPACK_MONOLITHIC_INSTALL 1)

# This must be last
include(CPack)
Arne
sumber
8
@SteveLorimer Saya hanya tidak setuju, bahwa file globbing adalah gaya yang buruk, saya pikir menyalin secara manual pohon file ke dalam CMakeLists.txt adalah gaya yang buruk karena itu berlebihan. Tetapi saya tahu bahwa orang-orang tidak setuju dengan topik ini, oleh karena itu saya meninggalkan komentar di kode, di mana Anda dapat mengganti globbing dengan daftar yang berisi semua file sumber secara eksplisit. Cari set(sources src/main.cpp).
Arne
3
@SteveLorimer ya sering kali saya harus memanggil cmake lagi. Setiap kali saya menambahkan sesuatu di pohon direktori, saya perlu mengaktifkan kembali cmake secara manual, sehingga get globbing dievaluasi ulang. Jika Anda meletakkan file di CMakeLists.txt, maka make (atau ninja) normal akan memicu pencabutan kembali cmake, jadi Anda tidak bisa melupakannya. Ini juga sedikit lebih ramah tim, karena dengan demikian anggota tim juga tidak boleh lupa untuk mengeksekusi cmake. Tapi menurut saya makefile tidak perlu disentuh, hanya karena seseorang menambahkan file. Tuliskan sekali, dan tidak ada yang perlu memikirkannya lagi.
Arne
3
@SteveLorimer Saya juga tidak setuju dengan pola untuk meletakkan satu CMakeLists.txt di setiap direktori proyek, itu hanya menyebarkan konfigurasi proyek di mana-mana, saya pikir satu file untuk melakukan semuanya harus cukup, jika tidak Anda kehilangan gambaran umum, tentang apa sebenarnya dilakukan dalam proses pembuatan. Itu tidak berarti tidak boleh ada subdirektori dengan CMakeLists.txt-nya sendiri, menurut saya itu hanya pengecualian.
Arne
2
Dengan asumsi "VCS" adalah singkatan dari "sistem kontrol versi" , maka itu tidak relevan. Masalahnya bukan, artefak itu tidak akan ditambahkan ke kontrol sumber. Masalahnya adalah, CMake akan gagal mengevaluasi kembali file sumber yang ditambahkan. Ini tidak akan menghasilkan kembali file input sistem build. Sistem build akan dengan senang hati tetap menggunakan file masukan yang sudah ketinggalan zaman, baik yang menyebabkan kesalahan (jika Anda beruntung), atau tidak diketahui, jika Anda tidak beruntung. GLOBbing menghasilkan celah dalam rantai kalkulasi ketergantungan. Ini adalah masalah yang signifikan, dan sebuah komentar tidak mengakui hal ini dengan semestinya.
IInspectable
2
CMake dan VCS beroperasi dalam isolasi lengkap. VCS tidak mengetahui CMake dan CMake tidak mengetahui adanya VCS. Tidak ada hubungan di antara mereka. Kecuali jika Anda menyarankan bahwa pengembang harus mengambil langkah manual, mengambil informasi dari VCS, dan berdasarkan beberapa pembersihan heuristik dan menjalankan ulang CMake. Jelas itu tidak berskala, dan rentan terhadap kekeliruan yang khas manusia. Tidak, maaf, sejauh ini Anda belum membuat poin yang meyakinkan untuk file GLOB.
IInspectable
39

Contoh paling dasar tetapi lengkap dapat ditemukan di tutorial CMake :

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)

Untuk contoh proyek Anda, Anda mungkin memiliki:

cmake_minimum_required (VERSION 2.6)
project (MyProject)
add_executable(myexec src/module1/module1.cpp src/module2/module2.cpp src/main.cpp)
add_executable(mytest test1.cpp)

Untuk pertanyaan tambahan Anda, satu cara untuk melanjutkan adalah lagi di tutorial: buat file header yang dapat dikonfigurasi yang Anda sertakan dalam kode Anda. Untuk ini, buat file configuration.h.indengan konten berikut:

#define RESOURCES_PATH "@RESOURCES_PATH@"

Kemudian di CMakeLists.txttambahkan:

set(RESOURCES_PATH "${PROJECT_SOURCE_DIR}/resources/"
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/configuration.h.in"
  "${PROJECT_BINARY_DIR}/configuration.h"
)

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")

Terakhir, jika Anda membutuhkan jalur dalam kode Anda, Anda dapat melakukan:

#include "configuration.h"

...

string resourcePath = string(RESOURCE_PATH) + "file.png";
sgvd
sumber
terima kasih banyak, terutama untuk RESOURCE_PATH, entah kenapa saya tidak mengerti bahwa configure_file adalah yang saya cari. Tetapi Anda menambahkan semua file dari proyek secara manual, apakah ada cara yang lebih baik untuk mendefinisikan pola di mana semua file ditambahkan dari pohon src?
Arne
Lihat jawaban Dieter, tetapi juga komentar saya tentang mengapa Anda tidak boleh menggunakannya. Jika Anda benar-benar ingin mengotomatiskannya, pendekatan yang lebih baik mungkin adalah menulis skrip yang dapat Anda jalankan untuk membuat ulang daftar file sumber (atau gunakan IDE sadar cmake yang melakukan ini untuk Anda; Saya tidak terbiasa dengan apa pun).
sgvd
3
@sgvd string resourcePath = string(RESOURCE_PATH) + "file.png"IMHO itu ide yang buruk untuk hardcode path absolut ke direktori sumber. Bagaimana jika Anda perlu menginstal proyek Anda?
2
Saya tahu mengumpulkan sumber secara otomatis kedengarannya bagus, tetapi itu dapat menyebabkan segala macam komplikasi. Lihat pertanyaan ini dari beberapa waktu yang lalu untuk diskusi singkat: stackoverflow.com/q/10914607/1401351 .
Peter
2
Anda mendapatkan kesalahan yang persis sama jika Anda tidak menjalankan cmake; menambahkan file dengan tangan membutuhkan satu detik satu kali, menjalankan cmake di setiap kompilasi membutuhkan satu detik setiap waktu; Anda benar-benar merusak fitur cmake; seseorang yang bekerja pada proyek yang sama dan menarik perubahan Anda akan melakukan: menjalankan make -> mendapatkan referensi yang tidak ditentukan -> semoga ingat untuk menjalankan ulang cmake, atau file bug dengan Anda -> menjalankan cmake -> menjalankan make dengan sukses, sedangkan jika Anda menambahkan file dengan tangan dia melakukan: berlari membuat sukses -> menghabiskan waktu bersama keluarga. Jumlahkan itu, jangan malas, dan biarkan diri Anda dan orang lain sakit kepala di masa depan.
sgvd
2

Di sini saya menulis contoh file CMakeLists.txt yang paling sederhana namun lengkap.

Kode sumber

  1. Tutorial dari hello world hingga lintas platform Android / iOS / Web / Desktop.
  2. Setiap platform saya merilis aplikasi sampel.
  3. Struct file 08-cross_platform diverifikasi oleh pekerjaan saya
  4. Ini mungkin tidak sempurna tapi berguna & praktik terbaik untuk tim saya sendiri

Setelah itu, saya menawarkan dokumen untuk detailnya.

Jika Anda memiliki pertanyaan, Anda dapat menghubungi saya dan saya ingin menjelaskannya.

MinamiTouma
sumber