Panduan Lengkap CMake. Bagian Dua: Membangun Sistem


Pendahuluan


Artikel ini membahas penggunaan sistem bangun CMake yang digunakan dalam sejumlah besar proyek C / C ++. Sangat disarankan agar Anda membaca bagian pertama manual ini untuk menghindari kesalahpahaman pada sintaksis bahasa CMake, yang secara eksplisit muncul di seluruh artikel.


Peluncuran CMake


Berikut ini adalah contoh penggunaan bahasa CMake yang harus Anda praktikkan. Eksperimen dengan kode sumber dengan memodifikasi perintah yang ada dan menambahkan yang baru. Untuk menjalankan contoh-contoh ini, instal CMake dari situs web resmi .


Prinsip kerja


Sistem build CMake adalah pembungkus di atas utilitas yang bergantung pada platform lainnya (misalnya, Ninja atau Make ). Jadi, dalam proses perakitan itu sendiri, tidak peduli seberapa paradoksal ini terdengar, itu tidak langsung berpartisipasi.


Sistem build CMake menerima file CMakeLists.txt dengan deskripsi aturan build dalam bahasa formal CMake, dan kemudian menghasilkan file build intermediate dan asli dalam direktori yang sama yang diterima pada platform Anda.


File-file yang dihasilkan akan berisi nama-nama spesifik dari utilitas sistem, direktori dan kompiler, sementara perintah CMake hanya menggunakan konsep abstrak dari kompiler dan tidak terikat pada alat yang bergantung pada platform yang sangat berbeda pada sistem operasi yang berbeda.


Memeriksa Versi CMake


Perintah cmake_minimum_required memeriksa versi CMake yang sedang berjalan: jika lebih kecil dari minimum yang ditentukan, maka CMake berakhir dengan kesalahan fatal. Contoh yang menunjukkan penggunaan khas dari perintah ini di awal file CMake:


 #     CMake: cmake_minimum_required(VERSION 3.0) 

Seperti disebutkan dalam komentar, perintah cmake_minimum_required menetapkan semua flag kompatibilitas (lihat cmake_policy ). Beberapa pengembang sengaja mengatur CMake versi rendah, dan kemudian menyesuaikan fungsionalitas secara manual. Ini memungkinkan Anda untuk secara bersamaan mendukung versi kuno CMake dan di beberapa tempat memanfaatkan fitur baru.


Desain proyek


Pada awal CMakeLists.txt harus menentukan karakteristik proyek dengan tim proyek untuk desain yang lebih baik dengan lingkungan terintegrasi dan alat pengembangan lainnya.


 #    "MyProject": project(MyProject VERSION 1.2.3.4 LANGUAGES C CXX) 

Perlu dicatat bahwa jika kata kunci LANGUAGES dihilangkan, maka bahasa defaultnya adalah C CXX . Anda juga dapat menonaktifkan indikasi bahasa apa pun dengan menulis kata kunci NONE sebagai daftar bahasa atau hanya meninggalkan daftar kosong.


Menjalankan file skrip


Perintah include menggantikan baris panggilannya dengan kode file yang ditentukan, bertindak serupa dengan perintah C / C ++ preprocessor include . Contoh ini menjalankan file skrip MyCMakeScript.cmake perintah yang dijelaskan:


 message("'TEST_VARIABLE' is equal to [${TEST_VARIABLE}]") #   `MyCMakeScript.cmake`  : include(MyCMakeScript.cmake) message("'TEST_VARIABLE' is equal to [${TEST_VARIABLE}]") 

Dalam contoh ini, pesan pertama akan memberi tahu bahwa variabel TEST_VARIABLE belum didefinisikan, namun, jika skrip MyCMakeScript.cmake variabel ini, pesan kedua akan menginformasikan tentang nilai baru dari variabel uji. Dengan demikian, file skrip yang dimasukkan oleh perintah include tidak membuat cakupannya sendiri, yang disebutkan dalam komentar di artikel sebelumnya .


Kompilasi file yang dapat dieksekusi


Perintah add_executable mengkompilasi file yang dapat dieksekusi dengan nama yang diberikan dari daftar sumber. Penting untuk dicatat bahwa nama file akhir tergantung pada platform target (misalnya, <ExecutableName>.exe atau hanya <ExecutableName> ). Contoh khas dari pemanggilan perintah ini:


 #    "MyExecutable"  #  "ObjectHandler.c", "TimeManager.c"  "MessageGenerator.c": add_executable(MyExecutable ObjectHandler.c TimeManager.c MessageGenerator.c) 

Kompilasi perpustakaan


Perintah add_library mengkompilasi perpustakaan dengan tampilan dan nama yang ditentukan dari sumber. Penting untuk dicatat bahwa nama pustaka akhir tergantung pada platform target (misalnya, lib<LibraryName>.a atau <LibraryName>.lib ). Contoh khas dari pemanggilan perintah ini:


 #    "MyLibrary"  #  "ObjectHandler.c", "TimeManager.c"  "MessageConsumer.c": add_library(MyLibrary STATIC ObjectHandler.c TimeManager.c MessageConsumer.c) 

  • Pustaka statis didefinisikan oleh kata kunci STATIC sebagai argumen kedua dan merupakan arsip file objek yang terkait dengan file yang dapat dieksekusi dan pustaka lainnya pada waktu kompilasi;
  • Pustaka dinamis ditentukan oleh kata kunci SHARED sebagai argumen kedua dan pustaka biner yang dimuat oleh sistem operasi selama eksekusi program;
  • Perpustakaan modular didefinisikan oleh kata kunci MODULE sebagai argumen kedua dan perpustakaan biner dimuat menggunakan teknik eksekusi oleh executable itu sendiri;
  • Pustaka objek didefinisikan oleh kata kunci OBJECT sebagai argumen kedua dan merupakan kumpulan file objek yang terkait dengan file yang dapat dieksekusi dan pustaka lainnya pada waktu kompilasi.

Menambahkan sumber ke sasaran


Ada beberapa kasus yang membutuhkan beberapa tambahan file sumber ke target. Untuk melakukan ini, perintah target_sources , yang dapat menambahkan sumber ke target berkali-kali.


Argumen pertama ke perintah target_sources adalah nama target yang sebelumnya ditentukan menggunakan perintah add_library atau add_executable , dan argumen berikutnya adalah daftar file sumber yang akan ditambahkan.


Panggilan berulang ke target_sources menambahkan file sumber ke target sesuai urutan pemanggilannya, sehingga dua blok kode terbawah secara fungsional setara:


 #    "MyExecutable"   # "ObjectPrinter.c"  "SystemEvaluator.c": add_executable(MyExecutable ObjectPrinter.c SystemEvaluator.c) #    "MyExecutable"  "MessageConsumer.c": target_sources(MyExecutable MessageConsumer.c) #    "MyExecutable"  "ResultHandler.c": target_sources(MyExecutable ResultHandler.c) 

 #    "MyExecutable"   # "ObjectPrinter.c", "SystemEvaluator.c", "MessageConsumer.c"  "ResultHandler.c": add_executable(MyExecutable ObjectPrinter.c SystemEvaluator.c MessageConsumer.c ResultHandler.c) 

File yang dihasilkan


Lokasi file output yang dihasilkan oleh perintah add_executable dan add_library hanya ditentukan pada tahap pembuatan, namun, aturan ini dapat diubah dengan beberapa variabel yang menentukan lokasi akhir file biner:



File yang dapat dieksekusi selalu dianggap sebagai tujuan eksekusi, perpustakaan statis dianggap sebagai tujuan arsip, dan perpustakaan modular dianggap sebagai tujuan perpustakaan. Untuk platform "non-DLL", perpustakaan dinamis dianggap sebagai target perpustakaan, dan untuk "platform DLL", tujuan eksekusi. Variabel semacam itu tidak disediakan untuk pustaka objek, karena pustaka semacam ini dihasilkan di dalam direktori CMakeFiles .


Penting untuk dicatat bahwa semua platform berbasis Windows, termasuk Cygwin, dianggap sebagai "platform DLL".


Tata letak perpustakaan


Perintah target_link_libraries perpustakaan atau dieksekusi dengan perpustakaan lain yang disediakan. Argumen pertama untuk perintah ini adalah nama target yang dihasilkan oleh perintah add_executable atau add_library , dan argumen selanjutnya adalah nama target pustaka atau jalur lengkap ke pustaka. Contoh:


 #    "MyExecutable"  #  "JsonParser", "SocketFactory"  "BrowserInvoker": target_link_libraries(MyExecutable JsonParser SocketFactory BrowserInvoker) 

Perlu dicatat bahwa pustaka modular tidak dapat ditautkan dengan file yang dapat dieksekusi atau pustaka lain, karena pustaka tersebut dimaksudkan hanya untuk memuat dengan teknik eksekusi.


Bekerja dengan sasaran


Seperti disebutkan dalam komentar, target dalam CMake juga tunduk pada manipulasi manual, namun sangat terbatas.


Dimungkinkan untuk mengontrol sifat-sifat target yang dirancang untuk mengatur proses perakitan proyek. Perintah get_target_property nilai properti target ke variabel yang disediakan. Contoh ini menampilkan nilai properti C_STANDARD dari target C_STANDARD di layar:


 #   "VALUE"   "C_STANDARD": get_target_property(VALUE MyTarget C_STANDARD) #      : message("'C_STANDARD' property is equal to [${VALUE}]") 

Perintah set_target_properties menetapkan properti target yang ditentukan ke nilai yang ditentukan. Perintah ini menerima daftar sasaran untuk nilai properti yang akan ditetapkan, dan kemudian kata kunci PROPERTIES , diikuti oleh daftar formulir < > < > :


 #   'C_STANDARD'  "11", #   'C_STANDARD_REQUIRED'  "ON": set_target_properties(MyTarget PROPERTIES C_STANDARD 11 C_STANDARD_REQUIRED ON) 

Contoh di atas mengatur properti target MyTarget yang mempengaruhi proses kompilasi, yaitu: ketika mengkompilasi target MyTarget CMake akan MyTarget kompiler untuk menggunakan standar C11. Semua penamaan properti target yang dikenal tercantum di halaman ini .


Dimungkinkan juga untuk memverifikasi target yang ditentukan sebelumnya menggunakan if(TARGET <TargetName>) konstruksi:


 #  "The target was defined!"   "MyTarget"  , #    "The target was not defined!": if(TARGET MyTarget) message("The target was defined!") else() message("The target was not defined!") endif() 

Menambahkan Subproyek


Perintah add_subdirectory meminta CMake untuk segera memproses file subproyek yang ditentukan. Contoh di bawah ini menunjukkan penerapan mekanisme yang dijelaskan:


 #   "subLibrary"    , #       "subLibrary/build": add_subdirectory(subLibrary subLibrary/build) 

Dalam contoh ini, argumen pertama ke perintah add_subdirectory adalah subproyek add_subdirectory , dan argumen kedua adalah opsional dan menginformasikan CMake tentang folder yang ditujukan untuk file yang dihasilkan dari subproyek yang disertakan (misalnya, CMakeCache.txt dan cmake_install.cmake ).


Perlu dicatat bahwa semua variabel dari lingkup induk diwarisi oleh direktori yang ditambahkan, dan semua variabel yang didefinisikan dan didefinisikan ulang dalam direktori ini hanya akan terlihat olehnya (jika kata kunci PARENT_SCOPE tidak ditentukan oleh argumen perintah yang set ). Fitur ini disebutkan dalam komentar di artikel sebelumnya .


Pencarian Paket


Perintah find_package menemukan dan memuat pengaturan proyek eksternal. Dalam kebanyakan kasus, ini digunakan untuk menghubungkan perpustakaan eksternal berikutnya seperti Boost dan GSL . Contoh ini memanggil perintah yang dijelaskan untuk mencari perpustakaan GSL dan kemudian menautkan:


 #     "GSL": find_package(GSL 2.5 REQUIRED) #      "GSL": target_link_libraries(MyExecutable GSL::gsl) #      "GSL": target_include_directories(MyExecutable ${GSL_INCLUDE_DIRS}) 

Dalam contoh di atas, perintah find_package menerima nama paket sebagai argumen pertama, dan kemudian versi yang diperlukan. Opsi REQUIRED membutuhkan pencetakan kesalahan fatal dan mengakhiri CMake jika paket yang diperlukan tidak ditemukan. Yang sebaliknya adalah opsi QUIET , membutuhkan CMake untuk melanjutkan pekerjaannya, bahkan jika paket itu tidak ditemukan.


Selanjutnya, MyExecutable ditautkan ke perpustakaan GSL dengan perintah target_link_libraries menggunakan variabel GSL::gsl , yang merangkum lokasi GSL yang sudah dikompilasi.


Pada akhirnya, perintah target_include_directories , memberi tahu kompiler tentang lokasi file header perpustakaan GSL. Harap perhatikan bahwa variabel GSL_INCLUDE_DIRS digunakan untuk GSL_INCLUDE_DIRS lokasi header yang saya jelaskan (ini adalah contoh pengaturan paket yang diimpor).


Anda mungkin ingin memeriksa hasil pencarian paket jika Anda menentukan opsi QUIET . Ini dapat dilakukan dengan memeriksa <PackageName>_FOUND , yang secara otomatis ditentukan setelah perintah find_package . Misalnya, jika Anda berhasil mengimpor pengaturan GSL ke proyek Anda, variabel GSL_FOUND akan menjadi benar.


Secara umum, perintah find_package memiliki dua rasa peluncuran: modular dan konfigurasi. Contoh di atas menerapkan bentuk modular. Ini berarti bahwa ketika perintah dipanggil, CMake mencari file skrip dari form Find<PackageName>.cmake dalam direktori CMAKE_MODULE_PATH , dan kemudian meluncurkannya dan mengimpor semua pengaturan yang diperlukan (dalam hal ini, CMake meluncurkan file FindGSL.cmake standar).


Cara memasukkan header


Anda dapat memberi tahu kompilator tentang lokasi header yang disertakan dengan menggunakan dua perintah: include_directories dan target_include_directories . Anda memutuskan mana yang akan digunakan, namun ada baiknya mempertimbangkan beberapa perbedaan di antara mereka (ide tersebut disarankan dalam komentar ).


Perintah include_directories memengaruhi cakupan direktori. Ini berarti bahwa semua direktori header yang ditentukan oleh perintah ini akan digunakan untuk semua tujuan CMakeLists.txt saat ini, serta untuk subproyek yang diproses (lihat add_subdirectory ).


Perintah target_include_directories memengaruhi target yang ditentukan oleh argumen pertama, dan tidak memengaruhi target lain. Contoh di bawah ini menunjukkan perbedaan antara dua perintah:


 add_executable(RequestGenerator RequestGenerator.c) add_executable(ResponseGenerator ResponseGenerator.c) #     "RequestGenerator": target_include_directories(RequestGenerator headers/specific) #    "RequestGenerator"  "ResponseGenerator": include_directories(headers) 

Dalam komentar disebutkan bahwa dalam proyek modern penggunaan perintah include_directories dan link_libraries tidak diinginkan. Alternatifnya adalah perintah target_include_directories dan target_link_libraries yang hanya bertindak pada tujuan tertentu, dan bukan pada seluruh cakupan saat ini.


Instalasi Proyek


Perintah install menghasilkan aturan instalasi untuk proyek Anda. Perintah ini mampu bekerja dengan sasaran, file, folder, dan lainnya. Pertama, pertimbangkan untuk menetapkan tujuan.


Untuk menetapkan tujuan, Anda harus meneruskan kata kunci TARGETS sebagai argumen pertama dari fungsi yang dijelaskan, diikuti oleh daftar tujuan yang akan ditetapkan, dan kemudian kata kunci DESTINATION dengan lokasi direktori di mana tujuan yang ditentukan akan ditetapkan. Contoh ini menunjukkan pengaturan sasaran yang khas:


 #   "TimePrinter"  "DataScanner"   "bin": install(TARGETS TimePrinter DataScanner DESTINATION bin) 

Proses untuk menggambarkan instalasi file serupa, kecuali bahwa TARGETS harus menentukan FILES daripada kata kunci TARGETS . Contoh yang menunjukkan pemasangan file:


 #   "DataCache.txt"  "MessageLog.txt"   "~/": install(FILES DataCache.txt MessageLog.txt DESTINATION ~/) 

Proses untuk menggambarkan pemasangan folder serupa, kecuali Anda harus menentukan DIRECTORY alih-alih kata kunci FILES . Penting untuk dicatat bahwa selama instalasi seluruh isi folder akan disalin, dan bukan hanya namanya. Contoh menginstal folder adalah sebagai berikut:


 #   "MessageCollection"  "CoreFiles"   "~/": install(DIRECTORY MessageCollection CoreFiles DESTINATION ~/) 

Setelah menyelesaikan pemrosesan CMake dari semua file Anda, Anda dapat menginstal semua objek yang dijelaskan dengan sudo checkinstall (jika CMake menghasilkan Makefile ), atau Anda dapat melakukan tindakan ini dengan lingkungan pengembangan terintegrasi yang mendukung CMake.


Contoh visual proyek


Panduan ini tidak akan lengkap tanpa menunjukkan contoh nyata menggunakan sistem bangun CMake. Pertimbangkan diagram proyek sederhana menggunakan CMake sebagai satu-satunya sistem build:


 + MyProject - CMakeLists.txt - Defines.h - StartProgram.c + core - CMakeLists.txt - Core.h - ProcessInvoker.c - SystemManager.c 

File assembly utama CMakeLists.txt menjelaskan kompilasi seluruh program: pertama, perintah add_executable untuk mengkompilasi file yang dapat dieksekusi, kemudian perintah add_subdirectory , yang merangsang pemrosesan subproyek, dan akhirnya, file yang dapat dieksekusi dihubungkan ke perpustakaan yang dikompilasi:


 #    CMake: cmake_minimum_required(VERSION 3.0) #   : project(MyProgram VERSION 1.0.0 LANGUAGES C) #      "MyProgram": add_executable(MyProgram StartProgram.c) #    "core/CMakeFiles.txt": add_subdirectory(core) #    "MyProgram"  #    "MyProgramCore": target_link_libraries(MyProgram MyProgramCore) #    "MyProgram"   "bin": install(TARGETS MyProgram DESTINATION bin) 

File core/CMakeLists.txt dipanggil oleh file assembly utama dan mengkompilasi pustaka statis MyProgramCore dimaksudkan untuk menghubungkan dengan file yang dapat dieksekusi:


 #    CMake: cmake_minimum_required(VERSION 3.0) #      "MyProgramCore": add_library(MyProgramCore STATIC ProcessInvoker.c SystemManager.c) 

Setelah serangkaian perintah cmake . && make && sudo checkinstall cmake . && make && sudo checkinstall sistem build CMake selesai dengan sukses. Perintah pertama mulai memproses file CMakeLists.txt di direktori root proyek, perintah kedua akhirnya mengkompilasi file biner yang diperlukan, dan perintah ketiga menginstal MyProgram dikompilasi yang MyProgram ke dalam sistem.


Kesimpulan


Sekarang Anda dapat menulis sendiri dan memahami file CMake orang lain, dan Anda dapat membaca secara detail tentang mekanisme lain di situs web resmi .


Artikel selanjutnya dalam panduan ini akan fokus pada pengujian dan pembuatan paket menggunakan CMake dan akan dirilis dalam seminggu. Sampai ketemu lagi!

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


All Articles