Introduction
From the previous article, I believe everyone has understood what CMake
is, but it’s not something you can master just by looking at it; you need to practice in order to learn it. If you have already practiced, you will realize how difficult it is to use the content from the last article in practice, for example:
-
Generating a lot of junk files, this is my first experience….
-
Need to manually specify the files to compile
-
Cannot specify the compiler, compilation options, and many other things….
-
If there are subdirectories, it cannot be fully applied….
-
And some other issues….
In summary, it is just not applicable in real projects. So why am I writing this? Because it is part of my learning process; you can’t just swallow a big piece without chewing, can you? In the upcoming series of articles, I will make CMake
more practical, more automated, and easier to build…
External Build
The first issue is that after running cmake .
, a lot of junk files are generated. So we can compile in a build directory, placing the generated junk files in that directory, which can be cleared directly when not needed.
Some may wonder, can it not clean the compiled junk files directly like make clean
or make distclean
? I also Googled this when I was learning, but unfortunately, no. The reason I let it generate junk files in the build directory is that they are not really junk files; they are just some intermediate files that record certain things, which I do not need, so I consider them junk… that’s all.
The official explanation for this is:
CMakeLists.txt can execute scripts and generate some temporary files through scripts, but it cannot track which temporary files are generated, hence it cannot provide a reliable clean solution.
So what to do? It’s simple. From CMake
‘s syntax, we know that it specifies the PATH
when building, which is the path to the top-level CMakeLists.txt
entry.
cmake PATH
It can obviously be a relative path rather than an absolute path, since dot ‘.’
indicates the current path, and dot dot ‘..’
indicates the parent path. So we can create a build directory and run in the build directory:
cmake ..
This is known as an external build in CMake
, and CMake
strongly recommends external builds!
I also wrote a simple build script, which mainly does two things:
-
Create a build directory (it will not recreate if it exists)
-
Enter the build directory
-
Then perform the external build with cmake
-
Generate the Makefile and run the make command to compile
#!/bin/bash
mkdir -p build
cd build
cmake ..
make
So you can directly run this script during compilation, and all generated content will be in the build directory.
-
This is the original directory
.
├── build.sh
├── CMakeLists.txt
└── main.c
0 directories, 3 files
After compiling, many files are generated in the build directory, including Makefile, section2 (executable)
, etc.
CMakeCache.txt CMakeFiles cmake_install.cmake Makefile section2
One more thing, if you want to see that the junk files generated by cmake are more than your source code, you will fall in love with this external build operation~
Automatic Source File Search
I have to say, cmake is a very good automation build tool. Since it is automation, many things are automatic, such as searching for source files. CMake provides commands for searching source files:
aux_source_directory(<dir> <variable>)
The main purpose is: to find all source files in a certain path. Note that it finds all source files. When there are many source files in your directory, it will actively search. Of course, it only looks for source files, such as *.c, *.cpp, *.cc
, anything that is a source file is fine, but it will not record files like txt, *.h
.
-
dir
: the specified directory (can be an absolute or relative path) -
variable
: the variable in which to store the output result list.
This command is very convenient. I can see what source files are in a certain directory, and they will all be recorded in the <variable>
variable, which can then be used directly in CMake
. Of course, there are other commands to find source files later; you can’t eat a big piece at once, so let’s understand this first and learn gradually~
Variables
In CMake
, variables are quite common. Let me explain the basic syntax briefly:
The commonly used function to define a variable is:
set(VARIABLE_NAME VARIABLE)
To unset a variable:
unset(VARIABLE_NAME)
-
VARIABLE_NAME
: variable name -
VARIABLE
: variable value
The value of a variable is always of type string
, and variable names are case-sensitive. It’s generally better to name variables normally, avoiding too many special characters; just numbers, letters, underscores "_"
, and hyphens "-"
are sufficient. The variable’s scope can be either global or local, similar to C and Java, so I won’t elaborate further. In my examples, global variables are all uppercase, and local variables are all lowercase
, which makes them easier to distinguish.
The format for variable reference is ${variable_name}
. The variable reference is replaced by the variable’s value or an empty string if the variable is not set. Variable references can be nested, such as ${outer_${inner_variable}veriable}
; environment variable references use the format $ENV{VARIABLE}
and act as normal variable references within the same context.
Print Log
During the CMake build, if you are unsure about the contents of certain variables, you can print them out in the terminal, similar to the printf function in our code, just to print a string….
message([<mode>] "message to display" ...)
Firstly, <mode>
specifies the type of message:
-
(none)
= important message; -
STATUS
= non-important message; -
WARNING
= CMake warning, will continue executing; -
AUTHOR_WARNING
= CMake warning (dev), will continue executing; -
SEND_ERROR
= CMake error, continues executing but skips the generation steps; -
FATAL_ERROR
= CMake error, terminates all processing;
Normally, I output some status information—STATUS
, printing a variable, printing the execution order of the code, etc….
Then there are some string messages, where you can directly reference the variable since it is a string….
section3
Here is an example code: the current directory contains two c files, main.c
and power.c
, which simply calculate x raised to the power of y, purely a demo. I didn’t bother writing it myself; it’s not that the code is profound, but I found it online, from @潘伟洲’s cmake test code: https://github.com/wzpan/cmake-demo.
-
main.c
#include <stdio.h>
#include <stdlib.h>
#include "power.h"
int main(int argc, char *argv[])
{
if (argc < 3){
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
double result = power(base, exponent);
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
-
power.c
double power(double base, int exponent)
{
int result = base;
int i;
if (exponent == 0) {
return 1;
}
for(i = 1; i < exponent; ++i){
result = result * base;
}
return result;
}
Then here is the CMakeLists.txt
file:
# Minimum required version of CMake
cmake_minimum_required (VERSION 2.8)
# Project information
project (section3)
# Find all source files in the current directory
# and save the names to the DIR_SRCS variable
aux_source_directory(. DIR_SRCS)
# Specify the target to generate
add_executable(section3 ${DIR_SRCS})
Compared to the previous article, my CMakeLists.txt
file only added the aux_source_directory
command to automatically scan the source files in the current directory and save them into the DIR_SRCS
variable, that’s all!!
Then, in the add_executable
command, I replaced the specified source files source1 source2 ... sourceN
with ${DIR_SRCS}
. Of course, you can also print out what ${DIR_SRCS}
variable holds; if everything goes well, it should hold ./main.c; ./power.c
, indicating the two files main.c
and power.c
in the current directory, with a semicolon representing a list. I will explain how to extract the contents of the list later….
message(STATUS "${DIR_SRCS}")
Then compile the code using the external build method:
jie@pc:~/github/cmake/section3$ ./build.sh
-- The C compiler identification is GNU 7.4.0
-- The CXX compiler identification is GNU 7.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/jie/github/cmake/section3/build
Scanning dependencies of target section3
[ 33%] Building C object CMakeFiles/section3.dir/main.c.o
[ 66%] Building C object CMakeFiles/section3.dir/power.c.o
[100%] Linking C executable section3
[100%] Built target section3
It is clear that the executable file has been generated correctly~
Code Download
https://github.com/jiejieTop/cmake
To be continued….