What is a build system
In software development, a build system is an automated tool used to generate targets from source code that users can use. Targets can include libraries, executables, or generated scripts, and so on.
Typically, each build system has a corresponding build file (also called a configuration file, project file, etc.) that guides the build system on how to compile, link, and produce executables. The build file usually describes the target to be generated, the source files needed to generate the target, the dependency libraries, and so on. The names and content format specifications of the build files used by different build systems generally vary.
Common build systems
GNU Make
: Build system for UniX-like operating systems. The name of the build file is usuallyMakefile
ormakefile
.NMake
: can be understood as Windows platformGNU Make
, it is MicrosoftVisual Studio
The build system used by earlier versions, such asVc + + 6.0
. The suffix of the build file is.mak
.MSBuild
:NMake
The replacement, at first, was with.net
Frame bound.Visual Studio
from2013
Version started using it as a build system.Visual Studio
The project build depends onMSBuild
, butMSBuild
Does not depend on the former, can run independently. The suffix of the build file is.vcproj
(inC++
Project as an example).Ninja
: a small build system focused on speed,Chrome
Team development.
CMake
CMake is an open source, cross-platform build system for managing software builds that are not dependent on a specific compiler and can support multiple levels of directories, applications, and libraries. While CMake also controls the build process with build files (the name is cMakelists.txt), it does not directly build and generate the target. Instead, it generates the build files required by other build systems, which then build and generate the final target. Support MSBuild, GNU Make, MINGW Make and many more build systems.
qmake
Qmake is one of the tools that comes with Qt that helps simplify the build process for cross-platform projects. Like CMake, Qmake does not directly build and generate targets, but relies on other build systems. It automatically generates makefiles, Visual Studio project files, and XCode project files. Since qmake can be used whether or not a project uses the Qt framework, it can be used in many software build processes.
The build files used by Qmake are. Pro project files, which can be written by developers or generated by Qmake itself. Qmake includes additional features to facilitate Qt development, such as automatically including moC and UIC compilation rules. It is worth mentioning that CMake also supports Qt development, but does not rely on Qmake.
Step By Step
In this introductory Tutorial, I mainly refer to the official Tutorial of CMake and some materials on the Internet, and make some changes and supplements to make it easier to understand.
1. Install and configure the development environment
Before we start, we can choose a favorite IDE(integrated development environment) as a C/C++ development tool. CMake supports Visual Studio, QtCreator, Eclipse, CLion and other ides. Of course, you can also use text editors like VSCode, Vim and some plug-ins as development tools. Due to my personal habits and preferences, and mainly under Linux system development, I finally chose JetBrains family of CLion as the development tool.
-
Install the build toolchain (CMake, compiler, debugger, build system) :
Sudo apt install CMake cmake-qt-gui. If the version in apt is too low, you can manually compile and install CMake and cmake-gui. For Windows, go to the official website to download the installation program.
Others: Use the built-in GNU suite (make, GCC, GDB) for Linux, and mingW-W64, MSVC, or WSL for Windows.
-
Download and install: CLion website, free trial 30 days.
-
First run: The login account is activated and authorized, and preferences are configured. Students can register for a JetBrains account using an EDU email address and then have free access to the ULTIMATE version of all the JetBrains family ides.
-
Interface Chinese (optional) : Square X JetBrains series software Chinese package. My English is not very good, so it is necessary to be Chinese.
-
Configure the Build toolchain (Set -> Build, Execute, Deploy -> Toolchain) : This step is to configure the path of the tools required for the build in CLion. CMake can use a version that comes bundled with CLion, or you can choose your own installed version.
-
Configure the CMake options (Set -> Build, Execute, Deploy -> CMake) : Set the build type (Debug/Release), the CMake build options parameters, the build directory, and so on. Keep the default Settings until you need to change the CMake build options.
Create a CLion C++ project
Open CLion and create a new C++ executable project. For the C++ standard version, I chose C++17. If you select the standard version and build target type when you create a new project, you can change them from the cmakelists.txt file.
3. Project structure and content analysis of cMakelists.txt
After creating a project, the initial structure looks like this:
-
CMakeLearnDemo: project source directory, the top-level directory containing project source files.
-
Main. CPP: The automatically generated source file for the main function.
-
Cmake-build-debug: The default build directory generated by CLion calling cmake. What is a build directory? A top-level directory that stores build system files (such as makefiles and other cmake-related configuration files) and build output files (intermediate files generated by compilation, executables, libraries). Because you don’t want to mess up the project structure by mixing build generated files with project source files, you usually create a separate build directory. Of course, you can use the project source directory directly as the build directory if you prefer. With CLion we don’t need to manually call CMake on the command line to generate the build directory and the build project. CLion automatically regenerates the build directory when the contents of cMakelists. TXT are changed, and the project can be built by clicking the Build button. But in the learning phase, it is important to understand the basic usage of CMake, and once you are familiar with it, you will be comfortable using an IDE. Let’s create a new myBuild directory in the project source directory and use it as the build directory when we manually invoke the CMake command, as shown in the figure below:
-
Cmakelists. TXT: cmake project configuration file, specifically the cMake configuration file in the project top-level directory, because a project can have multiple cMakelists. TXT files in multiple directories. This should be the core cmake configuration file, and it is around this file that most of the changes to the project’s build configuration are made. Let’s see what cMakelists. TXT is automatically generated for us by CLion:
cmake_minimum_required(VERSION 3.15) Copy the code
Set the minimum version requirement for cmake. If cMake runs a version below the minimum required version, it will stop processing the project and report an error.
project(CMakeLearnDemo) Copy the code
Set the name of the project and store its value in the cmake built-in variable PROJECT_NAME. When called from top-level cMakelists.txt, it is also stored in the built-in variable CMAKE_PROJECT_NAME.
set(CMAKE_CXX_STANDARD 17) Copy the code
Sets the version of the C++ standard. Currently, the supported version values are 98, 11, 14, 17 and 20.
add_executable(CMakeLearnDemo main.cpp) Copy the code
Add an executable file type build target to the project. CMakeLearnDemo is the filename followed by a list of the source files needed to generate the executable.
4. Configure, build, and run a basic project
First, let’s change the contents of main. CPP to the following code:
#include <cmath>
#include <iostream>
int main(int argc, char *argv[])
{
if(argc < 2)
{
std: :cerr << "Must have at least 2 command line arguments." << std: :endl;
return 1;
}
try
{
double inputValue = std::stof(argv[1]);
double outputValue = std: :sqrt(inputValue);
std: :cout << "the square root of " << inputValue
<< " is " << outputValue << std: :endl;
}
catch(const std::invalid_argument& e)
{
std: :cerr << e.what() << std: :endl;
return 1;
}
return 0;
}
Copy the code
The main function reads the value of a command line argument, calculates its arithmetic square root, and prints it, including some errors.
You clearly need a version of the C++ standard
The previously set CMAKE_CXX_STANDARD is only an optional attribute, and if the compiler does not support this version of the standard, it is still possible to degenerate to the previous version. If we want to explicitly indicate that we need a C++ standard, we can do so by:
set(CMAKE_CXX_STANDARD_REQUIRED True)
Copy the code
To implement. In this case, if the compiler does not support the standard, CMake will simply stop running with an error.
Generate the project build system
This step can be understood as a project configuration process, and no compilation takes place. Cmake generates build files for the build system in the build directory based on the project’s cMakelists. TXT file. It also contains a number of cmake-related configuration files, but we don’t need to care about the contents of these automatically generated files right now.
The commands that generate the project build system take three forms:
Use the current directory as the build directory and
cmake [<options>] <path-to-source>
# For example,.. Represents the parent directory of the current directory
cd mybuild
cmake ..
Copy the code
Use
cmake [<options>] <path-to-existing-build>
# for
cmake mybuild
Copy the code
Use
cmake [<options>] -S <path-to-source> -B <path-to-build>
For example,. Represents the current directory
cmake -S . -B mybuild
Copy the code
I prefer the third way because it’s more intuitive. Let’s generate the project build system ourselves:
Switch build system
For each build system, there is a Cmake generator that generates the native related build files for the build system, which can be specified by -g
when calling the cmake command-line tool. It can also be specified in cmakelists.txt by setting the value of the CMAKE_GENERATOR variable.
Cmake generators are platform-specific, so each generator can only be used on a particular platform. You can use the cmake –helo command line to see what generators are available on the current platform. My Linux Mint 1903 output looks like this:
Switching build types
Build types such as Debug, Release, RelWithDebInfo, MinSizeRel, etc., can be specified with CMAKE_BUILD_TYPE variables, such as:
set(CMAKE_BUILD_TYPE Release)
Copy the code
Build the project
Once the project build system is generated, you can then choose to build the project. We can build the project directly by calling the corresponding build system, such as GNU Make, or we can call cmake to have it automatically select the corresponding build system to build the project. As follows:
Or:
Run the executable
Now that the build is complete, let’s run the executable and see what happens:
5. Add a version number for the project
It is possible to define the version number directly in the source file, but it is more flexible to set it in cmakelists.txt. The configuration version number is an optional item in the project command. The syntax is as follows:
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
Copy the code
It is not difficult to see that there are at least 1 and at most 4 levels of version numbers. Take version 0.8.1.23 as an example. The values and meanings at different levels are as follows:
major
: Indicates the major version number. The value is0
. Can be achieved bycmake
Built-in variablesPROJECT_VERSION_MAJOR
or<PROJECT-NAME>_VERSION_MAJOR
To get its value.minor
: Indicates the version number. The value is8
. Can be achieved bycmake
Built-in variablesPROJECT_VERSION_MINOR
or<PROJECT-NAME>_VERSION_MAJOR
To get its value.patch
: Indicates the patch version. The value is1
. Can be achieved bycmake
Built-in variablesPROJECT_VERSION_PATCH
or<PROJECT-NAME>_VERSION_PATCH
To get its value.tweak
: Slightly changed version number. The value is23
. Can be achieved bycmake
Built-in variablesPROJECT_VERSION_TWEAK
or<PROJECT-NAME>_VERSION_TWEAK
To get its value.- Full version number
0.8.1.23
Can be achieved bycmake
Built-in variablesPROJECT_VERSION
or<PROJECT-NAME>_VERSION
To get its value.
Now, let’s create an autoGeneratedHeaders folder to hold the header files that are automatically generated or updated by cmake. Next, create a projectconfig.h.id file with the following contents:
#ifndef CMAKELEARNDEMO_PROJECTCONFIG_H
#define CMAKELEARNDEMO_PROJECTCONFIG_H
#define PROJECT_VERSION "@CPROJECT_VERSION@"
#define PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@
#endif //CMAKELEARNDEMO_PROJECTCONFIG_H
Copy the code
Next add or change the following to cmakelists.txt:
project(CMakeLearnDemo VERSION 1.0.0)
configure_file(
autoGeneratedHeaders/projectConfig.h.in
${PROJECT_SOURCE_DIR}/autoGeneratedHeaders/projectConfig.h
)
target_include_directories(CMakeLearnDemo PUBLIC
autoGeneratedHeaders
)
Copy the code
Configure_file (
As for substitution, in projectconfig.h.inn, the @variable_name @ syntax is a reference to the value of a cmake variable, which will be replaced by the value of the corresponding variable after configure_file. How many project levels to define is up to you, but I’ve only defined three project levels and a full version number.
target_include_directories(…) The command is to add the specified directory to the compiler search included file directory for the specified build target. You can use it directly in main.cpp by adding autoGeneratedHeaders to the include file directory
#include "projectConfig.h"
/ / or
#include <projectConfig.h>
Copy the code
without
#include "autoGeneratedHeaders/projectConfig.h"
/ / or
#include <autoGeneratedHeaders/projectConfig.h>
Copy the code
Next, let’s output the project version information in main. CPP, as follows:
#include "projectConfig.h"
int main(int argc, char *argv[])
{
std: :cout << "Project Version: " << PROJECT_VERSION << std: :endl;
std: :cout << "Project Version Major: " << PROJECT_VERSION_MAJOR << std: :endl;
std: :cout << "Project Version Minor: " << PROJECT_VERSION_MINOR << std: :endl;
std: :cout << "Project Version Patch: " << PROJECT_VERSION_PATCH << std: :endl; .Copy the code
Regenerate the build system and, unsurprisingly, add a new projectconfig.h file.
Build and run the executable to see the output:
6. Add the library
We used STD :: SQRT in main to compute the square root. Now, let’s implement our own square root function, build it into a static library, and finally use our own square root library function in main instead of the standard library.
Code implementation, library generation and use
Create a myMath folder to hold the.h,.cpp, and cmakelists.txt files for our own square root functions. The structure is as follows:
Mymath.h has the following contents:
#ifndef CMAKELEARNDEMO_MYMATH_H
#define CMAKELEARNDEMO_MYMATH_H
#include <stdexcept>
namespace mymath
{
// calculate the square root of number
double sqrt(double number) noexcept(false);
}
#endif // CMAKELEARNDEMO_MYMATH_H
Copy the code
Mymath.cpp contains the following:
#include "mymath.h"
namespace mymath
{
double sqrt(double number)
{
static constexpr double precision = 1e-6;
static constexpr auto abs= [] (double n) ->double { return n > 0? n:-n; };
if(number < 0)
{
throw std::invalid_argument("Cannot calculate the square root of a negative number!");
}
double guess = number;
while( abs(guess * guess - number) > precision)
{
guess = ( guess + number / guess ) / 2;
}
returnguess; }}Copy the code
Cmakelists.txt contains the following:
add_library(mymath STATIC mymath.cpp)
Copy the code
Add_library is similar to add_execuable, except that it adds a library target instead of an executable file target. Mymath is the library name, STATIC means to generate the STATIC library, and myMath.cpp is the source file needed to generate the library.
Next we need to add or change the following in cMakelists.txt in the project root directory:
add_subdirectory(mymath)
target_include_directories(CMakeLearnDemo PUBLIC
autoGeneratedHeaders
mymath
)
target_link_libraries(CMakeLearnDemo PUBLIC
mymath
)
Copy the code
The function of add_subdirectory is to add a subdirectory containing cMakelists.txt to the build, because the child cMakelists.txt will not participate in the build if it has not been added to the root cMakelists.txt.
Add myMath to target_include_directories because you need to include the myMath. h header file in main.cpp; The function of target_link_libraries is to link the dependency libraries to the specified target, because the MyMath library is required in main.cpp.
Now let’s replace STD :: SQRT with our own square root function in main. CPP. The changes are as follows:
- #include <cmath>
+ #include "mymath.h".int main(int argc ,char* argv[])
{...double outputValue = mymath::sqrt(inputValue); . }Copy the code
Regenerate the build system and run the build. The output should be the same as before; Open myBuild directory and you will find a new subdirectory called myMath, which contains the library file libmyMath.a.
Make the library optional
Now let’s make the MyMath library user optional, which, while not necessarily necessary for a tutorial, is quite common in a large project.
First let’s add or change the following in cmakelists.txt:
option(USE_MYMATH "Use CMakeLearnDemo provided math implementation" ON)
message("value of USE_MYMATH is : " ${USE_MYMATH})
configure_file(
autoGeneratedHeaders/projectConfig.h.in
${PROJECT_SOURCE_DIR}/autoGeneratedHeaders/projectConfig.h
)
if(USE_MYMATH)
add_subdirectory(mymath)
list(APPEND EXTRA_INCLUDES mymath)
list(APPEND EXTRA_LIBS mymath)
endif(a)add_executable(CMakeLearnDemo main.cpp)
target_include_directories(CMakeLearnDemo PUBLIC
autoGeneratedHeaders
${EXTRA_INCLUDES}
)
target_link_libraries(CMakeLearnDemo PUBLIC
${EXTRA_LIBS}
)
Copy the code
Explanation:
option(<variable> "<help_text>" [value])
Copy the code
Add an option for the user to select ON or OFF. The final value is stored in the
. ”
The message command outputs a message when the build system is generated, usually to see the value of a variable or something like that.
The if() and endif() conditional statements work just like they do in other high-level languages. You can read the cmake if syntax to see which variable values will be True if and which will be False.
The column phenotype variable in cmake refers to the variable used by; Delimited strings, such as “a; b; c; d; E “. The list() command performs a series of operations on the column phenotype variables, such as add, insert, delete, get length, and so on. In this tutorial, we use the variables EXTRA_INCLUDES and EXTRA_LIBS to store separate include and library paths for optional libraries selected by the user.
To create a column phenotype variable, use the set() command. Try adding the following lines to cMakelists.txt and observe the output:
set(testVariable "a" "b" "c")
message(${testVariable})
list(LENGTH testVariable testLength)
message(${testLength})
set(testVariable "a; b; c")
message(${testVariable})
list(LENGTH testVariable testLength)
message(${testLength})
set(testVariable "a b c")
message(${testVariable})
list(LENGTH testVariable testLength)
message(${testLength})
Copy the code
Next, let’s add the following line to projectconfig.h.id:
#cmakedefine USE_MYMATH
Copy the code
The configure_file command replaces this line with the value of the USE_MYMATH variable. If the value is True by the if command, it will be replaced with:
#define USE_MYMATH
Copy the code
Otherwise, it will be replaced with:
/* #undef USE_MYMATH */
Copy the code
This way, we can choose to use the standard library or our own library in our code by using conditional compilation to determine whether the USE_MYMATH macro is defined. In main. CPP we need to make the following changes:
#ifdef USE_MYMATH
#include "mymath.h"
#else
#include <cmath>
#endif.int main(int argc ,char* argv[])
{...#ifdef USE_MYMATH
double outputValue = mymath::sqrt(inputValue);
#else
double outputValue = std: :sqrt(inputValue);
#endif. }Copy the code
Add usage requirements for the library
Using requirements provides better control in CMake over the linking and inclusion directories of libraries or executables, as well as the passing of properties between build targets. The main commands that affect usage requirements are as follows:
target_compile_definitions
target_compile_options
target_include_directories
target_link_libraries
Now let’s refactor our code to add usage requirements using the modern CMake method. We first require that any build target linked to the MyMath library include the MyMath directory, which the MyMath library itself does not, of course, so this could be an INTERFACE usage requirement.
Interfaces are requirements that consumers (other build targets that use the library) need but producers (the library itself) do not. Now let’s add the following to myMath/cMakelists.txt:
target_include_directories(mymath
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
Copy the code
The value of the CMAKE_CURRENT_SOURCE_DIR variable is the full path to the source directory currently being processed by CMake, which in this case is the myMath directory. This way, all build targets linked to the MyMath library automatically include the myMath directory, and EXTRA_INCLUDES can now be safely removed:
if(USE_MYMATH)
add_subdirectory(mymath)
# Delete list(APPEND EXTRA_INCLUDES myMath)
list(APPEND EXTRA_LIBS mymath)
endif()
target_include_directories(CMakeLearnDemo PUBLIC
autoGeneratedHeaders
# remove ${EXTRA_INCLUDES}
)
Copy the code
Use cmake-GUI to configure the project and generate the build system
Our project now has a build option, and the GUI graphical interface makes the build option more straightforward for the user, so instead of using the command line cmake, we use cmake-GUI to configure the project and generate the build system.
To open cmake-gui, first select the project source directory and the build directory, as shown in the figure:
Unix Makefiles
After determining and waiting for the configuration to complete, a number of red items appear, representing options in the current project that can be configured by the user. Let’s check the USE_MYMATH option, as shown below:
Click Configure again until the red option is gone, and click Generate to Generate a user-configured build system for the project, as shown in the figure below:
After the build system is generated, observe how the projectconfig.h and main.cpp files change, and then proceed with the project build, using the same method as before, such as cmake –build mybuild.
If you don’t want to use the graphical tool, you can also pass the value of the variable directly to the cmake tool on the command line. For example:
cmake -B mybuild -S . -D USE_MYMATH:BOOL=ON
Copy the code
7. Install
The so-called installation can be simply understood as copying several files required by the software or program to a specified location. So what files do you need to install for our CMakeLearnDemo project? For MyMath, you need to install the library and header files; For the entire application, you need to install the executable and the projectconfig.h header.
After the installation rules are determined, we add the following at the end of myMath/cMakelists.txt:
install(TARGETS mymath DESTINATION lib)
install(FILES mymath.h DESTINATION include)
Copy the code
At the end of the root cmakelists.txt add:
install(TARGETS CMakeLearnDemo DESTINATION bin)
install(FILES autoGeneratedHeaders/projectConfig.h DESTINATION include)
Copy the code
The install command is used to generate installation rules for a project, indicating what needs to be installed. TARGETS are installation build TARGETS; FILES is the installation file. If the relative path is used, it is relative to the current source directory that cmake processes. DESTINATION
is the installation path, relative to the value of the CMAKE_INSTALL_PREFIX variable if a relative path is used.
Next, let’s open cmake-gui and configure it the same way as before, except this time we need to set the CMAKE_INSTALL_PREFIX variable, which is the installation prefix path of the project, as shown in the figure:
After configuring the project, generating the project build system, and building the project, you are ready to install with the following command:
cmake --install mybuild
Copy the code
When you are done, open the installation prefix path you set before, and you will already have the corresponding file, as shown in the figure below:
Test 8.
Now let’s test the CMakeLearnDemo application. At the end of the root cMakelists.txt, we can enable tests and then add some basic tests to verify that the application is working properly, as follows:
enable_testing(a)function(do_test target arg result)
add_test(NAME sqrt${arg} COMMAND ${target} ${arg})
set_tests_properties(sqrt${arg} PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endfunction()
do_test(CMakeLearnDemo 4 "2")
do_test(CMakeLearnDemo 2 "1.414")
do_test(CMakeLearnDemo 5 "2.236")
do_test(CMakeLearnDemo 123.456 "11.111")
do_test(CMakeLearnDemo -4 "NaN|NULL|Null|null|[Ee]rror|[Nn]ot [Ee]xist|[Nn]egative")
Copy the code
Explanation:
function(<name> [arg1 arg2 ...] ) do sth ...endfunction(a)Copy the code
As the name implies, to define a function, you must have a function name and optional arguments.
The add_test COMMAND adds a test, NAME < NAME > specifies the NAME of the test, and COMMAND < COMMAND > [arg…] Specifies the command line to be invoked at test time, and if
The set_tests_properties command sets properties for the specified test. The PASS_REGULAR_EXPRESSION property means that in order to pass the test, the output of the command must match this regular expression, such as “\d+(\.\d+)?” If the output is “result is 1.5”, the test passes. To learn more about what Properties Tests in Cmake have, read Properties on Tests.
After the project is built, use CD mybuild to jump to the build directory and type ctest -n to see the tests that will be run, but not actually run them, as shown below:
Next, run ctest -vv to run the test and output the detailed test information, as shown in the figure below:
9. Add a system self-check
Now, let’s add some code to the myMath :: SQRT function that depends on functionality that may not be supported on some target platforms, so we need to check. The code we want to add is to calculate the square root using the following mathematical formula, using the logarithm function and the exp exponential function. If the target platform does not support these two functions, we will use the previous method:
Now let’s add the following to myMath/cMakelists.txt:
include(CheckCXXSymbolExists)
check_cxx_symbol_exists(log "cmath" HAVE_LOG)
check_cxx_symbol_exists(exp "cmath" HAVE_EXP)
if(HAVE_LOG AND HAVE_EXP)
target_compile_definitions(mymath
PRIVATE "HAVE_LOG" "HAVE_EXP")
endif(a)Copy the code
Explanation:
There are many optional modules in cmake, and you can enable specific modules in a project by using the include command.
check_symbol_exists(<symbol> <files> <variable>)
Copy the code
The CheckCXXSymbolExists command checks whether the
symbol is available in the specified files C++ header file. The symbol can be defined as a macro, variable, or function name. Is unrecognizable; The result value of the check is placed in
.
The target_compile_definitions command is used to add a compilation definition to the target, which is the -d option in GCC:
g++ -D HAVE_LOG -D HAVE_EXP -o mymath.o -c mymath.cpp
Copy the code
This is equivalent to manually defining two macros in mymath.cpp:
#define HAVE_LOG
#define HAVE_EXP
Copy the code
To recap, the new addition checks to see if the log and exp functions are defined and available in the cMath header. If both are, add the HAVE_LOG and HAVE_EXP compiler definitions to myMath.
You can also use the configure_file() approach, but it’s more cumbersome.
Let’s change myMath :: SQRT to look like this:
#include "mymath.h"
# if defined(HAVE_LOG) && defined(HAVE_EXP)
#include <cmath>
#endif
namespace mymath
{
double sqrt(double number)
{
static constexpr double precision = 1e-6;
static constexpr auto abs= [] (double n) ->double { return n > 0? n:-n; };
if(number < 0)
{
throw std::invalid_argument("Cannot calculate the square root of a negative number!");
}
# if defined(HAVE_LOG) && defined(HAVE_EXP)
return std: :exp(0.5 * std: :log(number) );
#endif
double guess = number;
while( abs(guess * guess - number) > precision)
{
guess = ( guess + number / guess ) / 2;
}
returnguess; }}Copy the code
– reload cMakelists. TXT in CLion, not surprising that the 2 lines that were included in the conditional compilation should now be highlighted, meaning that the log and exp functions are available on the current platform; Run the rebuild and see if it works.
10. Add custom commands and build files
Suppose for the purposes of this tutorial, we decide to move away from the log and exp functions and instead want to generate a table of precomputed values that can be used in the myMath :: SQRT function. In this section, we will create the tables during the build process and then compile them into the MyMath library.
In myMath/cMakelists.txt and myMath /mysqrt.cpp, let’s remove all the additions from the previous section. Then, in myMath /mysqrt.cpp, let’s add a new makeSqrtTable source file to generate the precalculated values table header file, as follows:
#include <iostream>
#include <fstream>
#include <cmath>
int main(int argc, char* argv[])
{
// argv[1] : output header file path
// argv[2] (optional) : max value of precomputed square root
if(argc < 2)
{
std: :cerr << "Must have at least 2 command line arguments." << std: :endl;
return 1;
}
int maxPrecomputedSqrtValue = 100;
if(argc >= 3)
{
try
{
maxPrecomputedSqrtValue = std::stoi(argv[2]);
}
catch(const std::invalid_argument& e)
{
std: :cerr << e.what() << std: :endl;
return 1; }}std: :ofstream ofstrm(argv[1].std::ios_base::out | std::ios_base::trunc);
if(! ofstrm.is_open()) {std: :cerr << "Can not open " << argv[1] < <" to write!" << std: :endl;
return 1;
}
ofstrm << "namespace mymath\n{\n\tstatic constexpr int maxPrecomputedSqrtValue = " << maxPrecomputedSqrtValue << "; \n";
ofstrm << "\tstatic constexpr double sqrtTable[] =\n\t{\n";
for(int i = 0; ; i++)
{
double precomputedSqrtValue = std: :sqrt(i);
ofstrm << "\t\t" << precomputedSqrtValue;
if(i == maxPrecomputedSqrtValue)
{
ofstrm << "\n\t}; \n}";
break;
}
else
{
ofstrm << ",\n";
}
}
ofstrm.close();
return 0;
}
Copy the code
Mymath/cMakelists.txt:
add_executable(MakeSqrtTable makeSqrtTable.cpp)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/sqrtTable.h
COMMAND MakeSqrtTable ${CMAKE_CURRENT_SOURCE_DIR}/sqrtTable.h 1000
DEPENDS MakeSqrtTable
)
add_library(mymath STATIC mymath.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sqrtTable.h )
Copy the code
Explanation:
The first line goes without saying. The add_custom_command command that follows is used to add custom build rules to the build system generation in a variety of ways, but in this case it is used to define the commands used to generate the specified output file. OUTPUT output1 […] Specifies which output files are available; COMMAND commands [args …] In this case, we call MakeSqrtTable and pass two parameters, one is the output file path and the other is the maximum precomputed square root value. Since MakeSqrtTable is a build target, a target level dependency is automatically created. To ensure that the target is built before this command is invoked; DEPENDS [depend …] Declare the file on which the command is to be executed, and it will also create a target-level dependency when the dependency is a build target, and if the build target is an executable file or library, it will also create a file-level dependency to re-run the custom command when the build target is recompiled.
Since the OUTPUT file path is passed as an argument in COMMAND, OUTPUT output1 [… What does the option do? In fact, custom commands are invoked in the build target during the build process. Which build target? In the same cMakelists. TXT, specify any OUTPUT file declared in the OUTPUT option of add_custom_command as the build target of the source file, in this case the myMath library. If there is no build target that uses the OUTPUT file, then the custom command will not be called. Although the header file is included in CPP, it does not need to be displayed in the add build target command, but in order for the custom command to be invoked, it does. Also, do not list output files in more than one separate target that may be built in parallel, or the resulting output instances may conflict.
Finally, modify the myMath :: SQRT function to look like this:
#include "mymath.h"
#include "sqrtTable.h"
#include <iostream>
namespace mymath
{
double sqrt(double number)
{
static constexpr double precision = 1e-6;
static constexpr auto abs= [] (double n) ->double { return n > 0? n:-n; };
if(number < 0)
{
throw std::invalid_argument("Cannot calculate the square root of a negative number!");
}
int integerPart = static_cast<int>(number);
if(integerPart <= maxPrecomputedSqrtValue && abs(number - integerPart) <= precision)
{
std: :cout << "use precomputed square root : " << integerPart << std: :endl;
return sqrtTable[integerPart];
}
double guess = number;
while( abs(guess * guess - number) > precision)
{
guess = ( guess + number / guess ) / 2;
}
returnguess; }}Copy the code
Rebuild and run myMath :: SQRT to see if it uses a precomputed table. An example of the results is shown below:
11. Package projects
This is the final step of the tutorial. What we will do is to use the Cpack tool to package the project. There are two forms of package: source code package and binary installation package:
A source code package is a package of source code for a version of a piece of software so that, once distributed, users who download it can configure, build, and install it as they see fit. The software package can be tar.gz,. Zip, or.7z.
A binary installation package is when the author prebuilds a version of the software and packages the installation file (specified by the install() command) into a package for the user to install. The software package can be a simple tar.gz package, a. Shshell script, a. Deb debian installation package, or a Windows installation package.
Now let’s briefly describe the workflow of using CPACK packaging in a project:
-
For each installer or package format, CPACK has a specific back-end handler, called a “generator,” that generates the required installation package and invokes the specific package creation tool.
-
We can set the value of the associated cmake variable in cMakelists.txt to control the various properties of the generated package, known as “customization”. All software packages have common properties, such as CPACK_PACKAGE_NAME, CPACK_PACKAGE_VERSION, and so on. Of course, each software package has its own properties that can be set. After setting the properties, include the CPACK module:
include(CPack) Copy the code
-
During the process of generating the project build system, cmake generates two configuration files in the build directory based on the properties we set above: Cmake and CPackSourceConfig. Cmake, one for controlling binary installation package generation and one for controlling source code package generation.
-
After the project build system is generated, you can use the CPack command line tool to generate the source code package in the build directory. Once the project is built, the binary installation package can be generated. The default is to generate a binary installation package. If you want to generate a source code package, you need to specify, such as:
cpack --config CPackSourceConfig.cmake -G tar.gz Copy the code
There are many other options you can set for cpack, see Cpack Options for details.
This project is just a tutorial, but since you want to generate software packages for users to use, or add a software LICENSE description file is more formal ha, in the root of the project to create a new LICENSE file, the content is as follows:
Copyright (c) 2019: Siwei Zhu
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Copy the code
Setting the various properties of the package can take up a lot of lines, which can add a lot to the contents of the cMakelists.txt file, which would be easier for project configuration management if they were placed in a separate file. In fact, the cmake code can be placed in a file with the.cmake extension as well as in cMakelists.txt. The include() command can include either a cmake module or a.cmake file. It is similar to #include in c++, which includes the cmake code from other files. Each cmake module has a
.cmake file. So including modules is essentially the same as including.cmake files. Next let’s create a projectCpack. cmake file in the root of the project to configure the installation and packaging of the project, and then remove the lines of install(XXX) from the root cMakelists.txt file. And replace it with include(projectcpack.cmake). Finally, projectCpack.cmake looks like this:
# Install content
install(TARGETS CMakeLearnDemo DESTINATION bin)
install(FILES autoGeneratedHeaders/projectConfig.h DESTINATION include)
install(FILES LICENSE DESTINATION .)
Set the name of the package
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}")
Set the provider of the package
set(CPACK_PACKAGE_VENDOR "siwei Zhu")
Set the description of the package
set(CPACK_PACKAGE_DESCRIPTION "a simple cmake learn demo.")
Set the LICENSE
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
Set package version information
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
Set the name of the package file to be generated, excluding the extension
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_SYSTEM_NAME}")
Set the source code package to ignore files, similar to gitignore
set(CPACK_SOURCE_IGNORE_FILES "${PROJECT_BINARY_DIR}; /cmake-build-debug/; /.git/; .gitignore")
Set the list of source package generators
set(CPACK_SOURCE_GENERATOR "ZIP; TGZ")
Set the list of binary package generators
set(CPACK_GENERATOR "ZIP; TGZ")
Set the package installation prefix directory
set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/${PROJECT_NAME}")
if(UNIX AND CMAKE_SYSTEM_NAME MATCHES "Linux")
# Add generator for deb installation package
list(APPEND CPACK_GENERATOR "DEB")
# Package maintainer
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "siwei Zhu")
Devel refers to the development tool class software
set(CPACK_DEBIAN_PACKAGE_SECTION "devel")
# package depends on
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6")
elseif(WIN32 OR MINGW)
# Add Windows NSIS installation package generator
list(APPEND CPACK_GENERATOR "NSIS")
Set the package installation directory
set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}")
The default installation directory provided by the NSIS installer to the end user is located in this root directory.
${CPACK_NSIS_INSTALL_ROOT}/${CPACK_PACKAGE_INSTALL_DIRECTORY}
set(CPACK_NSIS_INSTALL_ROOT "C:\\Program Files\\")
Set contact information for questions and comments about the installation process
set(CPACK_NSIS_CONTACT "siwei Zhu")
# First ask to uninstall the previous version. If I set it to ON,
# Then setup will look for the previously installed version and, if found, ask the user if you want to uninstall it before proceeding with the installation.
set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON)
endif(a)include(CPack)
Copy the code
In the above code, we first set the file to install, several properties of the package, and finally the CPack module. Zip,.tar.gz, and.deb for Linux, and NSIS for Windows or MINGW.
Rebuild the project build system and build. When finished, go to the mybuild directory and run the following 2 commands:
cpack
cpack --config CPackSourceConfig.cmake
Copy the code
To generate source and binary packages (all package types specified by CPACK_GENERATOR or CPACK_SOURCE_GENERATOR variables if the -g option is not used).
Open the myBuild directory, and you should be able to generate several packages, as shown below:
We double-click the cMakelearndemo-1.0.0-source.tar. gz file and use the archive manager to view the directory structure of the Source code package as follows:
Let’s double-click on cMakelearndemo-1.0.0 -.deb to install our binary package, as shown in the figure below:
When the installation is complete, go to the /opt directory and you can see that the installation file is already there:
Open the terminal and type:
sudo dpkg -l | grep cmakelearndemo
Copy the code
The results are as follows:
You can see that the installation record of this package is available. Finally, type:
sudo apt remove cmakelearndemo
Copy the code
To uninstall the software package, as shown below:
CMake learning experience and resource sharing
Finally, here are some ways to learn CMake and share resources:
First of all, the tutorial above should have covered most of the common cmake commands and methods. The official cMake tutorial has a total of more than a dozen steps, but this article only includes the first seven. The reason is that the official cmake documentation is not well written, not to say that the content is not detailed, but mainly the lack of use cases. And some places are illogical, especially in this cmake tutorial, the first few steps look more smooth, at one go, the more you see the back of the more confusing, some places added a file, the result is not a word, the document content is not given, is really suffocating; Another reason is that some of the features of the latter steps are used less and are not suitable for the introduction tutorial. If I have time, I can use them to write a separate article.
For example, if I want to see which cmake variables can be used in project configuration, select cmake variables, as shown in the figure below:
To be more specific, if you want to search for details about the use and function of a particular command or variable, you can enter the search in the search box on the left, as shown below:
Finally, I stumbled upon the folk Chinese translation of the CMake Cookbook, in gitbook form, directly online, very convenient. Books on cmake are rare, let alone in Chinese, so cherish them.
Getting Started Project source code repository
Code – CMakeLearnDemo clouds