Найпростіший, але повний приклад CMake


117

Якось мене зовсім збентежує те, як працює CMake. Кожен раз, коли я думаю, що я все ближче розумію, як мається на увазі писати CMake, це зникає в наступному прикладі, який я читаю. Все, що я хочу знати, - це як я структурувати свій проект, щоб мій CMake вимагав найменшого обслуговування в майбутньому. Наприклад, я не хочу оновлювати свій CMakeList.txt, коли я додаю нову папку в своє дерево src, яка працює точно так само, як і всі інші папки src.

Так я уявляю структуру свого проекту, але будь ласка, це лише приклад. Якщо рекомендований спосіб відрізняється, скажіть, будь ласка, і розкажіть, як це зробити.

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

До речі, важливо, щоб моя програма знала, де знаходяться ресурси. Я хотів би знати рекомендований спосіб управління ресурсами. Я не хочу отримувати доступ до своїх ресурсів за допомогою "../resources/file.png"


1
For example I don't want to update my CMakeList.txt when I am adding a new folder in my src treeчи можете ви навести приклад IDE, який автоматично збирає джерела?

7
жодна ідея зазвичай не збирає джерела автоматично, тому що вони цього не потребують. Коли я додаю новий файл або папку, я це роблю в межах ide, і проект оновлюється. Система збирання з іншого боку не помічає, коли я змінюю деякі файли, тому бажана поведінка, щоб вона збирала всі вихідні файли автоматично
Arne

4
Якщо я бачу це посилання, у мене складається враження, що CMake не вдався до найважливішої задачі, яку він хотів вирішити: Полегшення системи побудови крос-платформ.
Арне

Відповіді:


94

після деяких досліджень у мене зараз є своя версія самого простого, але повного прикладу cmake. Ось він і намагається охопити більшість основ, включаючи ресурси та упаковку.

одне, що робить нестандартно - це обробка ресурсів. За замовчуванням cmake хоче поставити їх у / usr / share /, / usr / local / share / і щось еквівалентне у Windows. Я хотів мати простий zip / tar.gz, який можна витягти де завгодно і запустити. Тому ресурси завантажуються відносно виконуваного файлу.

основне правило для розуміння команд cmake - це наступний синтаксис: <function-name>(<arg1> [<arg2> ...])без коми або крапки. Кожен аргумент - це рядок. foobar(3.0)і foobar("3.0")те саме. ви можете встановити списки / змінні за допомогою set(args arg1 arg2). З цією змінною безліч foobar(${args}) і foobar(arg1 arg2)фактично однакові. Неіснуюча змінна еквівалентна порожньому списку. Список - це внутрішньо лише рядок з крапками з комою для розділення елементів. Тому список із лише одним елементом є за визначенням саме цим елементом, бокс не відбувається. Змінні - глобальні. Вбудовані функції пропонують певну форму названих аргументів тим, що вони очікують деяких ідентифікаторів, як PUBLICабоDESTINATIONу списку аргументів, щоб згрупувати аргументи. Але це не мовна особливість, ці ідентифікатори - це також лише рядки та проаналізовані реалізацією функції.

ви можете клонувати все з 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)

8
@SteveLorimer Я просто не погоджуюся, що глобулювання файлів - це поганий стиль, я думаю, що вручну копіювати дерево файлів у CMakeLists.txt це поганий стиль, оскільки це зайве. Але я знаю, що люди не згодні з цією темою, тому я залишив коментар у коді, де ви можете замінити глобулінг списком, який містить явні всі вихідні файли. Шукати set(sources src/main.cpp).
Арн

3
@SteveLorimer так часто мені доводилося знову запускати cmake. Кожного разу, коли я додаю щось у дерево каталогів, мені потрібно відновлювати cmake вручну, щоб переоцінити отримання глобуса. Якщо ви помістите файли в CMakeLists.txt, то звичайний make (або ніндзя) призведе до повторного виклику cmake, тому ви не можете його забути. Це також трохи дружніше, адже тоді члени команди також не можуть забути виконати cmake. Але я думаю, що makefile не потрібно чіпати, лише тому, що хтось додав файл. Напишіть це один раз, і нікому більше не потрібно буде про це думати.
Арн

3
@SteveLorimer Я також не згоден з шаблоном розміщення одного CMakeLists.txt у кожному каталозі проектів, він просто розкидає конфігурацію проекту скрізь, я думаю, одного файлу для цього все повинно вистачити, інакше ви втратите огляд того, що насправді робиться в процесі збирання. Це не означає, що не може бути підкаталогів з власними CMakeLists.txt, я просто думаю, що це має бути винятком.
Арн

2
Якщо припустити, що "VCS" - це "система контролю версій" , це не має значення. Справа не в тому, що артефакти не будуть додані до контролю джерел. Проблема полягає в тому, що CMake не зможе переоцінити додані вихідні файли. Він не буде знову генерувати файли введення системи. Система збирання буде щасливо дотримуватися застарілих вхідних файлів, що призводить до помилок (якщо вам пощастило), або залишається непоміченим, якщо у вас не вистачає удачі. GLOBbing створює розрив у ланцюзі обчислення залежності. Це є суттєвою проблемою, і коментар неналежним чином визнати це.
Неочікувана

2
CMake та VCS працюють у повній ізоляції. VCS не знає про CMake, а CMake не знає про будь-який VCS. Між ними немає зв’язку. Якщо ви не запропонуєте, що розробники повинні вживати вручну кроки, виймаючи інформацію з VCS та спираючись на деякі евристичні чисті та повторні CMake. Це, очевидно, не має масштабів і є чутливим до помилок, властивих людині. Ні, вибачте, ви поки що не зробили переконливого моменту для файлів GLOBbing.
Неочікувана

39

Найбільш основний, але повний приклад можна знайти в навчальному посібнику CMake :

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

Для прикладу проекту ви можете мати:

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)

Для вашого додаткового запитання в підручнику знову знайдеться один із способів: створіть файл заголовка, який можна настроювати, який ви включите у свій код. Для цього зробіть файл configuration.h.inіз таким вмістом:

#define RESOURCES_PATH "@RESOURCES_PATH@"

Потім у вашому CMakeLists.txtдодаванні:

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}")

Нарешті, там, де вам потрібен шлях у вашому коді, ви можете:

#include "configuration.h"

...

string resourcePath = string(RESOURCE_PATH) + "file.png";

велике дякую, особливо за RESOURCE_PATH, я якось не зрозумів, що configure_file - це те, що я шукав. Але ви додали всі файли з проекту вручну, чи є кращий спосіб просто визначити шаблон, в якому всі файли додаються з дерева src?
Арне

Дивіться відповідь Дітера, а також мої коментарі, чому ви не повинні його використовувати. Якщо ви дійсно хочете автоматизувати це, кращим підходом може бути написання сценарію, який ви можете запустити для відновлення списку вихідних файлів (або використовувати IDE, знаючи IDE, який робить це для вас; я не знайомий).
sgvd

3
@sgvd string resourcePath = string(RESOURCE_PATH) + "file.png"IMHO - це погана ідея жорсткого коду абсолютного шляху до вихідного каталогу. Що робити, якщо вам потрібно встановити свій проект?

2
Я знаю, що автоматично збирати джерела звучить приємно, але це може призвести до різного роду ускладнень. Дивіться це питання час від часу для короткого обговорення: stackoverflow.com/q/10914607/1401351 .
Петро

2
Ви отримуєте точно таку ж помилку, якщо не запустите cmake; додавання файлів вручну займає одну секунду один раз, запуск cmake в кожній компіляції займає одну секунду кожен раз; ви фактично порушуєте функцію cmake; хтось, хто працює над тим самим проектом і втягує ваші зміни, зробив би: запускає make -> отримує невизначені посилання -> сподіваємось, не забудьте повторно запустити cmake, або помилки файлів з вами -> run cmake -> запускає зробити успішно, тоді як якщо ви додасте файл вручну він робить: успішно працює -> проводить час з сім'єю. Підсумуйте це, не лінуйтеся, і шкодуйте себе та інших в майбутньому.
sgvd

2

Тут я пишу найпростіший, але повний зразок файлів CMakeLists.txt.

Вихідний код

  1. Підручники з привітного світу для переходу на платформу Android / iOS / Web / Desktop.
  2. На кожній платформі я випустив зразок програми.
  3. Структура файлів 08-cross_platform перевірена моєю роботою
  4. Це може бути не ідеальною, але корисною та найкращою практикою для команди самостійно

Після цього я запропонував документ для деталей.

Якщо у вас є якісь питання, ви можете зв’язатися зі мною, і я хотів би пояснити це.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.