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
Add the usage requirements for the library
Sample program address
Usage requirements provide better control over the linking and containing lines of a library or executable, as well as better control over the passing properties of CMake internal targets. The main commands for utilizing usage requirements are:
-
target_compile_definitions
Adds a compilation definition to the specified target.
-
target_compile_options
Adds compilation options to the specified target.
-
target_include_directories
Adds a include directory to the specified target.
-
target_link_libraries
Specifies the library or flag to use when linking to a given target or its dependencies.
There are three types of passed properties that control CMake internal targets:
-
PRIVATE
Attributes apply only to the target, not to targets that link to the target. Producers need it, consumers don’t.
-
PUBLIC
Attributes apply to both the target and the target of the linked target. Both producers and consumers.
-
INTERFACE
Attributes are not applied to the target, but to the target that links to the target. Producers don’t need it, consumers do.
Let’s refactor the code for the “Provide Options” project to use the modern CMake usage requirements approach. Let’s start by stating that anyone linking to MathFunctions needs to include the current source directory, and MathFunctions itself does not. So, INTERFACE is used here.
Add the following line to the MathFunctions/CMakeLists. TXT at the end of the:
# state that anybody linking to us needs to include the current source dir
# to find MathFunctions.h, while we don't.
# States that anyone linking to us needs to include the current source directory to find mathfunctions.h, which we don't.
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
Copy the code
Now that we’ve specified the requirements for the use of MathFunction, we can safely remove the use of the EXTRA_INCLUDES variable from the top-level cMakelists.txt:
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
endif()
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
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
The installation
Sample program address
The installation rules are simple: for MathFunctions, we install the libraries and headers, and for applications, we install the executables and configured headers.
Therefore, in the MathFunctions/CMakeLists. Added to the end of the TXT:
# install rules
# Installation Rules
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
Copy the code
And at the end of the top cMakelists.txt add:
# add the install targets
# Add installation rules
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include
)
Copy the code
That’s all you need for a local installation.
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
To install the executable, run the following command in the project root directory:
cmake --install cmake-build-debug
Copy the code
CMake uses the CMake –install installation file from 3.15. The CMake variable CMAKE_INSTALL_PREFIX is used to determine the installation root directory of the file. If you use cmake –install, you can specify a custom installation directory with the –prefix argument. For multiple configuration tools, use the –config parameter to specify the configuration.
Terminal output:
-- Install configuration: ""
-- Installing: /usr/local/lib/libMathFunctions.a
-- Installing: /usr/local/include/MathFunctions.h
-- Installing: /usr/local/bin/Tutorial
-- Installing: /usr/local/include/TutorialConfig.h
Copy the code
Run the following command from the project root directory:
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
In this Tutorial, we call the Tutorial file in /usr/local/bin instead of cmake-build-debug. We can check the Tutorial location by using the command:
where Tutorial
Copy the code
Terminal output:
/usr/local/bin/Tutorial
Copy the code
test
Sample program address
Next, test our application. At the end of the top-level CMakeLists file, we can enable testing and then add some basic tests to verify that the application is working properly.
# enable testing
# Enable tests
enable_testing(a)# does the application run
Test whether the application is running
add_test(NAME Runs COMMAND Tutorial 25)
# does the usage message work?
Does the test message work?
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
)
# define a function to simplify adding tests
# Define a function to simplify adding tests
function(do_test target arg result)
add_test(NAME Comp${arg} COMMAND ${target} ${arg})
set_tests_properties(Comp${arg}
PROPERTIES PASS_REGULAR_EXPRESSION ${result}
)
endfunction(do_test)
# do a bunch of result based tests
# Do a bunch of outcome-based tests
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is [-nan|nan|0]")
do_test(Tutorial 0.0001 "0.0001 is 0.01")
Copy the code
The first test simply verifies that the application is running without segment errors or other crashes, and returns a value of zero. This is the basic form of a CTest test.
The next test uses the PASS_REGULAR_EXPRESSION test property to verify that the output of the test contains certain strings. In this case, verify that a usage message was printed when an incorrect number of arguments were provided.
Finally, we have a function called do_test, which runs the application and verifies that the computed square root is correct for a given input. For each call to do_test, another test is added to the project based on the parameters passed, with a name, input, and expected result.
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
Test the application by running commands at the project root:
cd cmake-build-debug
ctest
Copy the code
Terminal output:
Test project /Users/taylor/Project/Taylor/C/Study/cmake-tutorial/cmake-test/cmake-build-debug
Start 1: Runs
1/9 Test #1: Runs ............................. Passed 0.00 SEC
Start 2: Usage
2/9 Test #2: Usage ............................ Passed 0.00 SEC
Start 3: Comp4
3/9 Test #3: Comp4 ............................ Passed 0.00 SEC
Start 4: Comp9
4/9 Test #4: Comp9 ............................ Passed 0.00 SEC
Start 5: Comp5
5/9 Test #5: Comp5 ............................ Passed 0.00 SEC
Start 6: Comp7
6/9 Test #6: Comp7 ............................ Passed 0.00 SEC
Start 7: Comp25
7/9 Test #7: Comp25 ........................... Passed 0.00 SEC
Start 8: Comp-25
8/9 Test #8: Comp-25 .......................... Passed 0.00 SECStart 9: comp.0.0001 9/9 Test# 9: Comp0.0001... Passed 0.00 SEC100% tests passed, 0 tests failed out of 9 Total Test time (real) = 0.03 SECCopy the code
System self-check
Sample program address
Let’s consider adding some code to our project that depends on features that the target platform may not have.
For this example, we will add some code, depending on whether the target platform has log and exp functions. Of course, almost every platform has these features, but for the purposes of this tutorial, let’s assume they are uncommon.
If the platform has log and exp, then we will use them to calculate the square root in the mysqrt function. We first test the availability of these features using the CheckSymbolExists. Cmake macro in the top-level CMakeList.
# does this system provide the log and exp functions?
Does the system provide log and exp functions?
include(CheckSymbolExists)
set(CMAKE_REQUIRED_LIBRARIES "m")
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
Copy the code
It’s important to complete the log and exp tests before the configure_file command in tutorialconfig. h, which uses the current Settings in CMake to immediately configure the file, So the check_symbol_EXISTS command should go before configure_file.
Now, add these definitions to tutorialconfig.h.id so that we can use them from mysqrt.cxx:
// does the platform provide exp and log functions? // Whether the platform is providedlogAnd the exp function, right?#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
Copy the code
MathFunctions update/CMakeLists. TXT file, so that mysqrt. CXX knows the location of the file:
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_BINARY_DIR}
)
Copy the code
Modify mysqrt.cxx to include cmath and tutorialconfig.h. Next, in the same file as the mysqrt function, we can provide an alternative implementation based on log and exp using the following code (if available on the system) (don’t forget #endif! Before returning the result). :
We will use the new definition in tutorialconfig.h.id, so be sure to set it up before configuring the file.
#if defined(HAVE_LOG) && defined(HAVE_EXP)
double result = exp(log(x) * 0.5);
std: :cout << "Computing sqrt of " << x << " to be " << result
<< " using log and exp" << std: :endl;
#else
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;
}
#endif
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.41421 using logAnd exp The square root of 2 is 1.41421Copy 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