CMake is a cross-platform, free, open source software tool for managing the software build process using a compiler independent approach. NDK development on Android Studio defaults to using CMake to manage C/C++ code, so it’s a good idea to have some knowledge of CMake before learning the NDK.

This article mainly to translate CMake official tutorial documentation, plus some of their own understanding, this tutorial covers the common use scenarios of CMake. Due to the limited ability, the translation part adopts machine turn + human proofreading, translation problems, I apologize for the place.

Development environment:

  • MacOS 10.14.6
  • CMake 3.15.1
  • CLion 2018.2.4

Infrastructure projects

Sample program address

The most basic project is an executable built from a single source code file.

The source code file provided with this example is tutorial. CXX, which can be used to calculate the square root of a number. The code is as follows:

// A simple program that computes the square root of a number
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <string>

int main(int argc, char *argv[]) {
    if (argc < 2) {
        std: :cout << "Usage: " << argv[0] < <" number" << std: :endl;
        return 1;
    }

    // convert input to double
    const double inputValue = atof(argv[1]);

    // calculate square root
    const double outputValue = sqrt(inputValue);
    std: :cout << "The square root of " << inputValue << " is " << outputValue
              << std: :endl;
    return 0;
}
Copy the code

For simple projects, just three lines of cMakelists.txt file, which will be the starting point for this tutorial. Create a cMakelists. TXT file in the project root directory with the following contents:

Set the minimum version of CMake required to run this configuration file
cmake_minimum_required(VERSION 3.15)

# set the project name
# Set the project name
project(Tutorial)

# add the executable
Add an executable file
add_executable(Tutorial tutorial.cxx)
Copy the code

Note that this example uses lowercase commands in the cMakelists.txt file. CMake supports uppercase, lowercase, and mixed-case commands.

The current project structure is as follows:

.├ ── cmakelists. TXT ├─ tutorialCopy the code

Run commands in the project root directory to generate the build intermediate file and the makefile:

cmake .
Copy the code

After the command is executed, a file is generated in the root directory of the project. The project structure is as follows:

.├ ── CMakeFiles ├── cmakelists.txt ├── Makefile ├── cmake_install.cmake ├─ tutorial.cxxCopy the code

In this way, the source files and generated files are mixed together, which is inconvenient to manage. It is recommended to use a special directory to manage these generated files. Run the build command at the root of the project and specify the build directory:

cmake -B cmake-build-debug
Copy the code

The project structure is as follows:

.├ ── cmakelists.txt │ ├── cmakecache.txt │ ├── CMakeFiles │ ├─ Makefile │ ├─ Cmake_install. Cmake └ ─ ─ tutorial. CXXCopy the code

Run commands in the project root directory to generate executable files:

cmake --build cmake-build-debug
Copy the code

After the command is executed, the executable file Tutorial is generated. The project structure is as follows:

.├ ── cmakelists.txt │ ├── cmakecache.txt │ ├── CMakeFiles │ ├─ Makefile+ │ ├ ─ ─ Tutorial│ ├ ─ cmake_installCopy the code

Run the generated executable in the project root directory with no arguments:

./cmake-build-debug/Tutorial
Copy the code

Terminal output:

Usage: ./cmake-build-debug/Tutorial number
Copy the code

Run the generated executable in the project root directory with parameters:

./cmake-build-debug/Tutorial 2
Copy the code

Terminal output:

The square root of 2 is 1.41421
Copy the code

Add the version number and configuration header file

Sample program address

The first feature we added was to provide version numbers for our executables and projects. While we can do this only in source code, using cMakelists.txt provides more flexibility.

First, modify the cMakelists.txt file to set the version number.

project(Tutorial VERSION 1.0)
Copy the code

Then, configure the header file to pass the version number to the source code:

# configure a header file to pass some of the CMake settings
# to the source code
# Configure headers to pass some CMake Settings to the source code
configure_file(TutorialConfig.h.in TutorialConfig.h)
Copy the code

Since the configured files will be written to the binary directory, we must add that directory to the list of paths to search for included files. Add the following line to the end of the cMakelists.txt file:

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
# Add the binary directory to the search path containing the files so that we find tutorialconfig.h
target_include_directories(Tutorial PUBLIC
        "${PROJECT_BINARY_DIR}"
        )
Copy the code

Using your favorite editor, create tutorialconfig.h.id in the source directory with the following:

// the configured options and settings forTutorial // Configuration options and Settings for the Tutorial#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
Copy the code

When CMake configures this header file, it generates a file called tutorialconfig. h in the binary directory and copies the contents of tutorialconfig.h.id into it. Just replace @tutorial_version_major @ and @tutorial_version_minor @ with 1 and 0 in the configuration in cmakelists.txt.

How do 1 and 0 relate to Tutorial_VERSION_MAJOR and Tutorial_VERSION_MINOR? After specifying the VERSION in project(), CMake stores the VERSION information in the following variables:

  • PROJECT_VERSION.<PROJECT-NAME>_VERSION
  • PROJECT_VERSION_MAJOR.<PROJECT-NAME>_VERSION_MAJOR
  • PROJECT_VERSION_MINOR.<PROJECT-NAME>_VERSION_MINOR
  • PROJECT_VERSION_PATCH.<PROJECT-NAME>_VERSION_PATCH
  • PROJECT_VERSION_TWEAK.<PROJECT-NAME>_VERSION_TWEAK.

MAJOR, MINOR, PATCH, and TWEAK respectively represent four bits of the version number, such as version 1.2.3.4, MAJOR=1, MINOR=2, PATCH=3, TWEAK=4. The version number does not have to be 4 bits. It can have only 1 bit, but a maximum of 4 bits.

Here the project-name value is Tutorial, so the version information can be read from Tutorial_VERSION_MAJOR and Tutorial_VERSION_MINOR.

When the project() command is called from the top-level cMakelists.txt, the version is also stored in the variable CMAKE_PROJECT_VERSION.

Next, modify tutorial.cxx to include the configuration header, tutorialconfig.h, and print the publication number, as shown below:

// A simple program that computes the square root of a number
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <string>

+ #include "TutorialConfig.h"

int main(int argc, char *argv[]) {
    if (argc < 2) {
+ // report version
+ std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
+ << Tutorial_VERSION_MINOR << std::endl;
        std::cout << "Usage: " << argv[0] << " number" << std::endl;
        return 1;
    }

    // convert input to double
    const double inputValue = atof(argv[1]);

    // calculate square root
    const double outputValue = sqrt(inputValue);
    std::cout << "The square root of " << inputValue << " is " << outputValue
              << std::endl;
    return 0;
}
Copy the code

Run commands in the project root directory to compile the project and generate executable files:

cmake -B cmake-build-debug
cmake --build cmake-build-debug
Copy the code

Run the generated executable in the project root directory with no arguments:

./cmake-build-debug/Tutorial
Copy the code

Terminal output:

/cmake-build-debug/Tutorial Version 1.0 Usage:./cmake-build-debug/Tutorial numberCopy the code

Specify C++ standards

Sample program address

The easiest way to enable support for a specific C ++ standard in CMake is to use the CMAKE_CXX_STANDARD variable. For this tutorial, set the variable CMAKE_CXX_STANDARD in the cmakelists.txt file to 11 and set CMAKE_CXX_STANDARD_REQUIRED to True:

# specify the C++ standard
# Specifies the C ++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
Copy the code

Next, we added some C ++ 11 functionality to our project by replacing ATof with STD :: stod in tutorial. CXX. Also, remove #include

.

// A simple program that computes the square root of a number
#include <cmath>
- #include <cstdlib>
#include <iostream>
#include <string>

#include "TutorialConfig.h"

int main(int argc, char *argv[]) {
    if (argc < 2) {
        // report version
        std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
                  << Tutorial_VERSION_MINOR << std::endl;
        std::cout << "Usage: " << argv[0] << " number" << std::endl;
        return 1;
    }

    // convert input to double
- const double inputValue = atof(argv[1]);
+ const double inputValue = std::stod(argv[1]);

    // calculate square root
    const double outputValue = sqrt(inputValue);
    std::cout << "The square root of " << inputValue << " is " << outputValue
              << std::endl;
    return 0;
}
Copy the code

Run commands in the project root directory to compile the project and generate executable files:

cmake -B cmake-build-debug
cmake --build cmake-build-debug
Copy the code

Run the generated executable in the project root directory:

./cmake-build-debug/Tutorial 2
Copy the code

Terminal output:

The square root of 2 is 1.41421
Copy the code

Add the library

Sample program address

Now, we will add a library to our project that calculates the square root of a number and can be used by the executable instead of using the standard square root function provided by the compiler. The library has two files:

  • MathFunctions.h

    double mysqrt(double x);
    Copy the code
  • mysqrt.cxx

    The source file has a function for mysqrt that provides functionality similar to the compiler’s SQRT function.

    #include <iostream>
    
    #include "MathFunctions.h"
    
    // a hack square root calculation using simple operations
    double mysqrt(double x) {
        if (x <= 0) {
            return 0;
        }
    
        double result = x;
    
        // do ten iterations
        for (int i = 0; i < 10; ++i) {
            if (result <= 0) {
                result = 0.1;
            }
            double delta = x - (result * result);
            result = result + 0.5 * delta / result;
            std: :cout << "Computing sqrt of " << x << " to be " << result << std: :endl;
        }
        return result;
    }
    Copy the code

Create a folder called MathFunctions in the project root directory, place the library under it, and create a cMakelists.txt file under it that reads as follows:

add_library(MathFunctions mysqrt.cxx)
Copy the code

To use the new library, we will add the add_subdirectory call to the top-level cMakelists.txt file to build the library. We added the new library to the executable and MathFunctions to the include directory so that the MQsqrt.h header file can be found. The last few lines of the top-level cMakelists.txt file should now look like this:

# add the MathFunctions library
Add MathFunctions library
add_subdirectory(MathFunctions)

# add the executable
Add an executable file
add_executable(Tutorial tutorial.cxx)

target_link_libraries(Tutorial PUBLIC MathFunctions)

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
# Add the binary directory to the search path containing the files so that we find tutorialconfig.h
target_include_directories(Tutorial PUBLIC
        "${PROJECT_BINARY_DIR}"
        "${PROJECT_SOURCE_DIR}/MathFunctions"
        )
Copy the code

Modify tutorial. CXX to use the imported library as follows:

// A simple program that computes the square root of a number
- #include <cmath>
#include <iostream>
#include <string>

#include "TutorialConfig.h"
+ #include "MathFunctions.h"

int main(int argc, char *argv[]) {
    if (argc < 2) {
        // report version
        std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
                  << Tutorial_VERSION_MINOR << std::endl;
        std::cout << "Usage: " << argv[0] << " number" << std::endl;
        return 1;
    }

    // convert input to double
    const double inputValue = std::stod(argv[1]);

    // calculate square root
- const double outputValue = sqrt(inputValue);
+ const double outputValue = mysqrt(inputValue);
    std::cout << "The square root of " << inputValue << " is " << outputValue
              << std::endl;
    return 0;
}
Copy the code

Run commands in the project root directory to compile the project and generate executable files:

cmake -B cmake-build-debug
cmake --build cmake-build-debug
Copy the code

Run the generated executable in the project root directory:

./cmake-build-debug/Tutorial 2
Copy the code

Terminal output:

Computing SQRT of 2 to be 1.5 Computing SQRT of 2 to be 1.41667 Computing SQRT of 2 to be 1.41422 Computing SQRT of 2 to be 1.41422 Be 1.41421 Computing SQRT of 2 to be 1.41421 Computing SQRT of 2 to be 1.41421 SQRT of 2 to be 1.41421 SQRT of 2 to be 1.41421 The square root of 2 is 1.41421Copy the code

Provide options

Sample program address

Now let’s make the MathFunctions library optional. While this is certainly not necessary for this tutorial, it is common for large projects. The first step is to add an option to the top-level cMakelists.txt file:

# should we use our own math functions
Should we use our own mathematical functions
option(USE_MYMATH "Use tutorial provided math implementation" ON)
Copy the code

This option will be displayed in the CMake GUI and CCmake, and the default value ON can be changed by the user. This setting is stored in the cache, so the user does not need to set this value every time he or she runs CMake on the build directory.

The next one is to make it conditional to create and link MathFunctions libraries. To do this, we change the end of the top-level cMakelists.txt file to look like this:

# add the MathFunctions library
Add MathFunctions library
if (USE_MYMATH)
    add_subdirectory(MathFunctions)
    list(APPEND EXTRA_LIBS MathFunctions)
    list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif(a)# add the executable
Add an executable file
add_executable(Tutorial tutorial.cxx)

target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
# Add the binary directory to the search path containing the files so that we find tutorialconfig.h
target_include_directories(Tutorial PUBLIC
        "${PROJECT_BINARY_DIR}"
        ${EXTRA_INCLUDES}
        )
Copy the code

Note that the variable EXTRA_LIBS is used here to collect all the optional libraries for later linking into the executable. The variable EXTRA_INCLUDES is similarly used for optional header files. This is a classic approach when dealing with many optional components, and we’ll cover the modern approach in the next step.

The corresponding changes to the source code are very simple. First, decide whether to include the MathFunctions header or

in tutori.cxx as needed:

// should we include the MathFunctions header?
#ifdef USE_MYMATH
#include "MathFunctions.h"
#else
#include <cmath>
#endif
Copy the code

Then, in the same file, use USE_MYMATH to determine which square root function to use:

#ifdef USE_MYMATH
  const double outputValue = mysqrt(inputValue);
#else
  const double outputValue = sqrt(inputValue);
#endif
Copy the code

Since the source code now requires USE_MYMATH, you can add it to tutorialconfig.h.id with the following line:

#cmakedefine USE_MYMATH
Copy the code

Download the corresponding version of cmake-gui from download according to your own platform. After installation, open the software, select the source code directory and the generated file, as shown in the figure below:

Click the “Generate” button in the lower left corner, and the software will pop up a popover to select the project generator. The default is fine here. Click the “Done” button, and cmake-GUI starts to compile the project and Generate the intermediate file, and you can see the options we provide for users in the software:

Cmake-build-debug/tutorialconfig. h

// the configured options and settings forTutorial // Configuration options and Settings for the Tutorial#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 0
#define USE_MYMATH
Copy the code

Run commands in the project root directory to generate executable files:

cmake --build cmake-build-debug
Copy the code

Run the generated executable in the project root directory:

./cmake-build-debug/Tutorial 2
Copy the code

Terminal output:

Computing SQRT of 2 to be 1.5 Computing SQRT of 2 to be 1.41667 Computing SQRT of 2 to be 1.41422 Computing SQRT of 2 to be 1.41422 Be 1.41421 Computing SQRT of 2 to be 1.41421 Computing SQRT of 2 to be 1.41421 SQRT of 2 to be 1.41421 SQRT of 2 to be 1.41421 The square root of 2 is 1.41421Copy the code

Uncheck USE_MYMATH in cmake-gui and click the Generate button to recompile the project. At this time, cmake-build-debug/ tutorialconfig. h will look like this:

// the configured options and settings forTutorial // Configuration options and Settings for the Tutorial#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 0/ *#undef USE_MYMATH */
Copy the code

Run commands in the project root directory to generate executable files:

cmake --build cmake-build-debug
Copy the code

Run the generated executable in the project root directory:

./cmake-build-debug/Tutorial 2
Copy the code

Terminal output:

The square root of 2 is 1.41421
Copy the code

CMake use tutorial series

  • How to use CMake
    • Infrastructure projects
    • Add the version number and configuration header file
    • Specify C++ standards
    • Add the library
    • Provide options
  • CMake Tutorial (2)
    • Add the usage requirements for the library
    • The installation
    • test
    • System self-check
  • CMake Tutorial (3)
    • Specify compilation definitions
    • Add custom commands and generated files
    • Generate setup
    • Add support for dashboards
  • CMake Tutorial (4)
    • Mixed static and shared
    • Adding a generator expression
    • Adding export Configuration