Самый простой, но полный пример 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
no ide обычно не собирают исходники автоматически, потому что в этом нет необходимости. Когда я добавляю новый файл или папку, я делаю это в ide, и проект обновляется. Система сборки на другой стороне не замечает, когда я меняю некоторые файлы, поэтому желательно, чтобы все исходные файлы собирались автоматически,
Арне

4
Если я вижу эту ссылку, у меня складывается впечатление, что CMake не справился с самой важной задачей, которую хотел решить: упростить кроссплатформенную систему сборки.
Arne

Ответы:


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 вручную, чтобы переоценить глобализацию get. Если вы поместите файлы в CMakeLists.txt, то обычный make (или ниндзя) вызовет повторный вызов cmake, поэтому вы не сможете его забыть. Это также немного удобнее для команды, потому что тогда члены команды также не могут забыть выполнить cmake. Но я думаю, что make-файл не нужно трогать только потому, что кто-то добавил файл. Напишите это один раз, и никто больше не должен думать об этом.
Arne

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

2
Если предположить, что «VCS» - это сокращение от «система контроля версий» , тогда это не имеет значения. Проблема не в том, что артефакты не будут добавлены в систему управления версиями. Проблема в том, что CMake не сможет повторно оценить добавленные исходные файлы. Он не будет повторно создавать входные файлы системы сборки. Система сборки с радостью придерживается устаревших входных файлов, что либо приведет к ошибкам (если вам повезет), либо останется незамеченным, если вам не повезет. GLOBbing приводит к разрыву в цепочке вычисления зависимостей. Это является серьезной проблемой, и комментарий не надлежащим образом признать это.
Inspectable 08

2
CMake и VCS работают полностью изолированно. VCS не знает о CMake, а CMake не знает о каких-либо VCS. Между ними нет связи. Если только вы не предложите разработчикам выполнять действия вручную, извлекая информацию из VCS и основываясь на некоторой эвристической очистке и повторном запуске CMake. Очевидно, это не масштабируется и подвержено ошибке, свойственной людям. Нет, извините, вы пока не сделали убедительного довода в пользу файлов GLOBbing.
Inspectable 08

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?
Arne

См. Ответ Дитера, а также мои комментарии о том, почему вы не должны его использовать. Если вы действительно хотите автоматизировать это, лучшим подходом может быть написать сценарий, который вы можете запустить для восстановления списка исходных файлов (или использовать интегрированную среду разработки с поддержкой cmake, которая сделает это за вас; я не знаком ни с одним).
sgvd

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

2
Я знаю, что автоматический сбор исходников звучит неплохо, но может привести к разного рода осложнениям. См. Этот вопрос недавно для краткого обсуждения: stackoverflow.com/q/10914607/1401351 .
Питер

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

2

Здесь я пишу самый простой, но полный пример файла CMakeLists.txt.

Исходный код

  1. Учебники от hello world до кросс-платформенных Android / iOS / Web / Desktop.
  2. Для каждой платформы я выпустил образец приложения.
  3. Структура файла 08-cross_platform проверена моей работой
  4. Это может быть не идеально, но полезно и лучше всего для одной команды

После этого я предложил документ для подробностей.

Если у вас есть вопросы, вы можете связаться со мной, и я хотел бы это объяснить.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.