Most simple but complete CMake example

前端 未结 3 1894
你的背包
你的背包 2021-01-29 17:01

Somehow I am totally confused by how CMake works. Every time I think that I am getting closer to understand how CMake is meant to be written, it vanishes in the next example I r

相关标签:
3条回答
  • 2021-01-29 17:51

    After some research I have now my own version of the most simple but complete CMake example. Here it is, and it tries to cover most of the basics, including resources and packaging.

    One thing it does non-standard is resource handling. By default CMake wants to put them in /usr/share/, /usr/local/share/ and something equivalent on windows. I wanted to have a simple zip/tar.gz that you can extract anywhere and run. Therefore resources are loaded relative to the executable.

    The basic rule to understand CMake commands is the following syntax: <function-name>(<arg1> [<arg2> ...]) without comma or semicolon. Each argument is a string. foobar(3.0) and foobar("3.0") is the same. You can set lists/variables with set(args arg1 arg2). With this variable set foobar(${args}) and foobar(arg1 arg2) are effectively the same. A non existent variable is equivalent to an empty list. A list is internally just a string with semicolons to separate the elements. Therefore a list with just one element is by definition just that element, no boxing takes place. Variables are global. Builtin functions offer some form of named arguments by the fact that they expect some ids like PUBLIC or DESTINATION in their argument list, to group the arguments. But that's not a language feature, those ids are also just strings, and parsed by the function implementation.

    You can clone everything from 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)
    
    0 讨论(0)
  • 2021-01-29 17:53

    The most basic but complete example can be found in the CMake tutorial :

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

    For your project example you may have:

    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)
    

    For your additional question, one way to go is again in the tutorial: create a configurable header file that you include in your code. For this, make a file configuration.h.in with the following contents:

    #define RESOURCES_PATH "@RESOURCES_PATH@"
    

    Then in your CMakeLists.txt add:

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

    Finally, where you need the path in your code, you can do:

    #include "configuration.h"
    
    ...
    
    string resourcePath = string(RESOURCE_PATH) + "file.png";
    
    0 讨论(0)
  • 2021-01-29 17:59

    Here I write a most simple but complete CMakeLists.txt files sample.

    Source Code

    1. Tutorials from hello world to cross platform Android/iOS/Web/Desktop.
    2. Each platform I released a sample application.
    3. The 08-cross_platform file struct is verified by my work
    4. It may be not perfect but useful & best practice for a team on my own

    After that, I offered document for the details.

    If you have any questions, you can contact me and I'd like to explain it.

    0 讨论(0)
提交回复
热议问题