Самый простой, но полный пример cmake

Каким-то образом я полностью смущен тем, как работает 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

после некоторого исследования у меня теперь есть собственная версия самого простого, но полного примера cmake. Вот он, и он пытается охватить большинство основ, включая ресурсы и упаковку.

одна вещь, которую она выполняет нестандартно, - это обработка ресурсов. По умолчанию cmake хочет поместить их в/usr/share/,/usr/local/share/и что-то подобное в окнах. Я хотел иметь простой 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 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)

Ответ 2

Самый простой, но полный пример можно найти в учебнике 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 "@[email protected]"

Затем в 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";