I. Background introduction

From a practical point of view, I will sort out a basic CMake tutorial that can help you quickly eliminate illiteracy, which is also a learning summary of the project I am currently in charge of. Considering the practicality of the project, the following explanation may not be complete, but more in line with some features currently used in the project.

What is CMake?

You may have heard of many Make tools, such as GNU Make, QT Qmake, MS Nmake, BSD Make (pmake), Makepp, and so on. These Make tools follow different specifications and standards, and the Makefile formats they execute vary widely. This presents a serious problem: if software is to be cross-platform, it must be able to compile on different platforms. If you use the Make tool above, you have to write a Makefile for each standard, which can be a frustrating task.


CMake is a tool designed to address the above problems: It first allows developers to write a platform-independent cmakelist.txt file to customize the entire compilation process, and then further generates the required localized makefiles and engineering files based on the target user’s platform. Such as Unix Makefiles or Windows Visual Studio projects. To achieve “Write once, Run everywhere”. Obviously, CMake is a more advanced compilation and configuration tool than any of the above.

The core process of CMake:

Cmakelists. TXT file for compilation and configuration. In general, cmakelists. TXT needs to specify a target, whether executable or library, or a target. 2. Execute the cmake command in the file with cmakelists. TXT to generate the corresponding platform makefile; 3. Compile using the make command. This is a simple description, but compilation and configuration in a production environment is actually more complex and will need to be learned in the following examples.Copy the code

Commonly used concept

CMake builds depending on the specified Target, whether a library or executable file is generated, and all subsequent operations will use Target as the Target.

3. Simple CMake usage examples

Before we start looking at the examples, we can ponder the following questions:

  1. How do you turn a bunch of c++ code into an executable or a library?
  2. How do you modularize your project (code level only, not design)? How do the modules depend on each other?
  3. How can a project use (rely on) already compiled third-party libraries?
  4. How do I dynamically control some compilation options? (such as being able to access the version number in the code without having to change the code every time, being able to use different platform features to distinguish Android, iOS, MAC, Windows, etc.)

Above a few questions, may be compiler small white often ask a few questions. Next, we will write a simple example, following the example you will have a sense of the use of CMake, the above questions will be revealed step by step in the example:

Sample background

The foreign friend wants to buy apples, but he pays in US dollars. The total consumption price given to the foreign friend in the settlement should be in US dollars, so the vendor needs to rely on an exchange rate conversion tool to convert RMB into US dollars.

Ps: Ignore the code structure and design of the examples and just focus on the script.

Engineering structure

|---project
   |----------app_module    # This project is the main project of the APP, which has a vendor class and a main program entrance.
   |----------second_module # This project is a binary module that will be source-dependent on app_module, with a foreign class that represents foreign friends.
   |----------third_party   # This is a third-party library that the project relies on. The exchange rate conversion tool in the example will be provided by the bank and integrated as a third-party library, not source dependent.
Copy the code

Examples of

  1. Help the bank to generate a currency library, which we’ll call rate_util and send out to vendors to use.

    Declare the minimum version number of cmake requiredCmake_minimum_required (VERSION 3.11)# specify the generated target (a bank tool library that can be used to do currency conversion)
    add_library(rate_util SHARED rate_util.cpp)
    
    Library output directory
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/build/libs/${OS})
    Install the header file to the specified directory
    install(FILES "rate_util.h" DESTINATION "${PROJECT_SOURCE_DIR}/build/inc")
    
    If it is iOS, then framework is generated
    if("${OS}" STREQUAL "iOS")
        set_target_properties(rate_util PROPERTIES FRAMEWORK TRUE)
    endif()
    Copy the code

    Cmake requires Target, which is the tool library to be generated. Here we compile a dynamic library and use add_library to specify Target as a library

    SHARED

    Mark this as a dynamic library and specify the source file to compile. There is only one here: rate_util.cpp. If an executable is to be generated, it is add_executable, which will be seen later when the main program is generated.

    Now we have to specify where the library output goes, and here we use a variable that’s built into cmake, okay

    CMAKE_LIBRARY_OUTPUT_DIRECTORY

    Cmake has a number of other built-in variables, which are actually code above

    PROJECT_SOURCE_DIR

    It’s also a built-in variable, and we’ll talk about cmake variables later.

    Then we specify the location of the library’s header file, because the library will be used by others, so the corresponding header file must not be sent out. Here we use the install command, which is a cmake built-in command, just like add_library is a cmake built-in command. We’ll talk about the cmake command later.

    This is where the basic cmake configuration is actually done, and we added a little bit of a bonus, which is to set a property for this target to indicate that if you build for iOS, you want to build the Framework instead of dylib, a dynamic library. That brings us to the answer to the first question. (Sample download: bank.zip)

  2. Now that the third-party library of ** Bank is available, we need to build our APP project. We will follow the engineering structure mentioned above. 支那

    Cmake requires a cmakelists. TXT configuration file. It would be crazy if we wrote all the configuration, all the module configuration, into a cmakelists. TXT file as in the first step. Luckily, cmake has a super handy built-in command called add_subdirectory, which, as the name suggests, can be used to specify subdirectories, which of course means that there must also be a cmakelists.txt in the subdirectory, so that we can hierarchical write configurations. The configuration of each module is written in its own cmakelists. TXT. So this is what our engineering structure should look like

    |---project
       |----------CMakeLists.txt# project outermost entrance of cmakelists.txt
       |----------app_module    # This project is the main project of the APP, which has a vendor class and a main program entrance.
                  |---------CMakeLists.txt #app_module Cmakelists.txt for this submodule
       |----------second_module # This project is a binary module that will be source-dependent on app_module, with a foreign class that represents foreign friends.
                  |----------CMakeLists.txt #second_module Cmakelists.txt for this sub-module
       |----------third_party   # This is a third-party library that the project relies on. The exchange rate conversion tool in the example will be provided by the bank and integrated as a third-party library, not source dependent.
                  |----------CMakeLists.txt# Third party library general configuration cmakelists.txt
                  |----------bank
                             |---------CMakeLists.txt# bank this third party library of cmakelists.txt
    
    Copy the code

    In fact, this structure is the structure of our demo project. Next, we will analyze it one by one in cmakelists.txt. First look at project-> cmakelists. TXT, which is the configuration of the main entrance of the project:

    # CMake Minimum version number requirementCmake_minimum_required (VERSION 3.11)# Project information
    project(p)
    Add a bunch of child modules
    add_subdirectory(app_module ${PROJECT_SOURCE_DIR}/build-cache/app_module/)
    add_subdirectory(second_module ${PROJECT_SOURCE_DIR}/build-cache/second_module/)
    add_subdirectory(third_party ${PROJECT_SOURCE_DIR}/build-cache/third_party/)
    
    Copy the code

    App_module -> cmakelists. TXT -> cmakelists. TXT -> cmakelists. TXT -> cmakelists. TXT

  3. Cmake specifies the lowest version number of cmakeCmake_minimum_required (VERSION 3.11)Declare that the target to compile is an executable file with the target name main
    add_executable(main
            saler.cpp
            )
    
    # specify other targets that Main links to, which in effect are libraries to which these targets correspond
    target_link_libraries(main
            foreign
            rate_util
            )
    
    # Specify the include paths for this target, where the compiler will look for the header files
    target_include_directories(main
            PUBLIC
            ${PROJECT_SOURCE_DIR}/third_party
            )
    
    Set the output path of the executable file
    set(EXECUTABLE_OUTPUT_PATH   ${PROJECT_SOURCE_DIR}/build/bin/${OS})
    Copy the code

The final executable file add_executable was generated here and a target named main was defined. Since the main program of this app also relies on two libraries, foreign friends and bank exchange rate tool library, we need to specify the link library for it. Here we use cmake, another built-in command

target_link_libraries
target_include_directories
EXECUTABLE_OUTPUT_PATH,

  1. Declare the minimum cmake version numberCmake_minimum_required (VERSION 3.11)Set target (foreign) to a static library
    add_library(foreign
            STATIC
            ForeignFriend.cpp
            )
    PUBLIC indicates that the path will also be passed to libraries that depend on the target. In this project, app_module relies on foreign, so app_module can access the path as well
    # why does app_module file reference foreign library header file not add the previous path, directly use the file name
    target_include_directories(foreign
            PUBLIC
            ${CMAKE_CURRENT_LIST_DIR}
            )
    Copy the code

You can see that foreign is declared as another target, specifying that a static library be generated with the name foreign, and also specifying the path to include the header file. Since this is the source code dependency, the header file does not need to be distributed to the outside world, so there is no need to install. Finally, let’s take a look at how the third party library – bank exchange rate conversion tool library is used.

  1. Declare the minimum cmake version numberCmake_minimum_required (VERSION 3.11)# Add a imported library
    add_library(rate_util SHARED IMPORTED
            GLOBAL)
    Set the location of the library
    set(__lib_name lib_rateutil.so)
    if("${OS}" STREQUAL "android")
        set(__lib_name librate_util.so)
    elseif("${OS}" STREQUAL "iOS")
        set(__lib_name rate_util.framework)
    else(a)set(__lib_name librate_util.dylib)
    endif()
    Select the path of the target imported library
    set_target_properties(rate_util PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/third_party/bank/libs/${OS}/${__lib_name})
    Select target directories and select include_directories instead of target_include_directories
    include_directories(rate_util
            PUBLIC
            ${CMAKE_CURRENT_LIST_DIR}/inc)
    Copy the code

The third-party library is also declared as a separate target, but instead of specifying a source file for target (because there is no source file, this is an external compiled dynamic library that can be used directly), the target is declared as a separate target

IMPORTED
IMPORTED_LOCATION

3. How to generate libraries for different platforms

Toolchain is a configuration file that specifies the values of cmake built-in variables that should be set on the target platform, such as CMAKE_SYSTEM_NAME, CMAKE_SYSROOT, and so on. Let’s take a look at an example of toolchain for Android.

  1.  # import the NDK own toolchain documents include ($ENV {NDK_ROOT} / build/cmake/android. The toolchain. Cmake)

    Set some custom values for variables within toolchain

    Set (ANDROID_STL c++ +_static) # set (ANDROID_STL c++ +_static)

Toolchain on Android platforms can be created using the make-standalone toolchain.sh tool that comes with the NDK. If you are interested, Google toolchain on other platforms. Since my example project was written on a MAC, the MAC cmake does not configure toolchain separately, because cmake uses the platform’s environment variables by default. Of course, toolchain can only cover part of the answer to question 4 at the beginning. There are actually some dynamic controls that can be implemented with macros, which are not used in the sample project and won’t be covered here.

  1. ** 总结**

The examples above show some common features of CMake at the level of generating an executable/link library from a separate compilation, relying on a third-party import library, relying on a binary source library, and cross-compiling across platforms. This example should give you an idea of how to use CMake, and you’ll be able to explain common features in more detail when you get a chance.