CMake Learning Summary (Part One)
CMake Learning Summary (Part Two)
About the Concepts of Dynamic Libraries and Static Libraries:
Creating and Using Static Link Libraries in C
Hello everyone, in the previous CMake article, we also left a question to implement at the end of the article, which is to put the source files in the src directory and the header files in the include directory. This is more in line with what others and myself will do in the future to configure the project (as soon as you see these two directories, you can understand what they mean, clear and straightforward). At the same time, the ELF files generated in the Linux environment will be placed in the bin directory. However, a few days after the article was published, some netizens raised some new requirements:

(If netizens have any actual needs, feel free to contact me. As long as it is within my capability, I can write an article to share with everyone.) Those who are familiar with me know that I am also a novice and will start sharing from very basic things. Although they are mostly theoretical, they are all accumulations over time (sometimes, in actual project development, you don’t learn a lot; more often, you rely on the accumulation of basic knowledge and then extend it. Overall, it is indeed worthwhile to tinker regularly!); at the same time, if there are any practical needs, we will gradually delve deeper, taking it step by step, and solid knowledge cannot be obtained with any falsehood or carelessness. Now, let’s get into the main topic:
1. Using src, include, and bin Directories (More Standardized):
1. First, start by creating these three directory structures and placing the corresponding files into them:
root@txp-virtual-machine:/home/txp/testmy# mkdir bin build src include
root@txp-virtual-machine:/home/txp/testmy# ls
bin build include src
Place the files into the include directory (the contents of test1.h and test2.h continue from the previous article, so I won’t reinvent the wheel here):
root@txp-virtual-machine:/home/txp/testmy/include# ls
test1.h test2.h
Place the files into the src directory (the contents of test1.c and test2.c continue from the previous article, so I won’t reinvent the wheel here):
root@txp-virtual-machine:/home/txp/testmy/src# ls
main.c test1.c test2.c
Finally, we need to create a CMakeLists.txt in both the testmy directory and the src directory:
/* CMakeLists.txt content in the testmy directory: */
cmake_minimum_required(VERSION 2.8)
project(main)
add_subdirectory(src)
/* CMakeLists.txt content in the src directory: */
aux_source_directory(. SRC_LIST)
include_directories(../include)
add_executable(main ${SRC_LIST})
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
Explanation of the unfamiliar statements in the first CMakeLists.txt:
-
add_subdirectory(src) means adding a subdirectory that stores source files to the current project and can specify where the intermediate binary and target binaries are stored (the term subdirectory means a subdirectory, so it means: here it specifies that the src directory stores the source files, and when executing cmake, it will enter the src directory to find the CMakeLists.txt in the src directory, so a CMakeLists.txt is also established in the src directory). The official usage is like this (but I won’t delve into it for now):
add_subdirectory
----------------
Add a subdirectory to the build.
::
add_subdirectory(source_dir [binary_dir]
[EXCLUDE_FROM_ALL])
Add a subdirectory to the build. The source_dir specifies the
directory in which the source CMakeLists.txt and code files are
located. If it is a relative path it will be evaluated with respect
to the current directory (the typical usage), but it may also be an
absolute path. The binary_dir specifies the directory in which to
place the output files. If it is a relative path it will be evaluated
with respect to the current output directory, but it may also be an
absolute path. If binary_dir is not specified, the value of
source_dir, before expanding any relative path, will be used (the
typical usage). The CMakeLists.txt file in the specified source
directory will be processed immediately by CMake before processing in
the current input file continues beyond this command.
If the EXCLUDE_FROM_ALL argument is provided then targets in the
subdirectory will not be included in the ALL target of the parent
directory by default, and will be excluded from IDE project files.
Users must explicitly build targets in the subdirectory. This is
meant for use when the subdirectory contains a separate part of the
project that is useful but not necessary, such as a set of examples.
Typically the subdirectory should contain its own project() command
invocation so that a full build system will be generated in the
subdirectory (such as a VS IDE solution file). Note that inter-target
dependencies supercede this exclusion. If a target built by the
parent project depends on a target in the subdirectory, the dependee
target will be included in the parent project build system to satisfy
the dependency.
Analysis of the second CMakeLists.txt content:
-
aux_source_directory (. SRC_LIST): places the source files in the current directory: main.c test1.c test2.c into the variable SRC_LIST.
-
include_directories (../include): includes the header files from the include directory.
-
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin): here EXECUTABLE_OUT_PATH and PROJECT_SOURCE_DIR are predefined variables in CMake, and their functions are as follows:
-
EXECUTABLE_OUTPUT_PATH: the storage location of the target binary executable file
-
PROJECT_SOURCE_DIR: the root directory of the project
So the final generated ELF file (which is our final executable file) will be placed in the bin directory, and then some configuration intermediate files will be generated in the build directory.
The specific process steps I wrote out:
root@txp-virtual-machine:/home/txp/testmy# vim CMakeLists.txt
root@txp-virtual-machine:/home/txp/testmy# cd src
root@txp-virtual-machine:/home/txp/testmy/src# ls
main.c test1.c test2.c
root@txp-virtual-machine:/home/txp/testmy/src# vim CMakeLists.txt
Finally, the structure looks like this:
root@txp-virtual-machine:/home/txp/testmy# tree
.
├── bin
├── build
├── CMakeLists.txt
├── include
│ ├── test1.h
│ └── test2.h
└── src
├── CMakeLists.txt
├── main.c
├── test1.c
└── test2.c
2. Compile and Run:
root@txp-virtual-machine:/home/txp/testmy# cd build
root@txp-virtual-machine:/home/txp/testmy/build# ls
root@txp-virtual-machine:/home/txp/testmy/build# cmake ..
-- The C compiler identification is GNU 4.8.4
-- The CXX compiler identification is GNU 4.8.4
-- 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
-- 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
-- Configuring done
-- Generating done
-- Build files have been written to: /home/txp/testmy/build
root@txp-virtual-machine:/home/txp/testmy/build# make
Scanning dependencies of target main
[ 33%] Building C object src/CMakeFiles/main.dir/test2.c.o
[ 66%] Building C object src/CMakeFiles/main.dir/test1.c.o
[100%] Building C object src/CMakeFiles/main.dir/main.c.o
Linking C executable ../../bin/main
[100%] Built target main
root@txp-virtual-machine:/home/txp/testmy/build# cd ../bin
root@txp-virtual-machine:/home/txp/testmy/bin# ls
main
Note that here we switch to the build directory to execute cmake. Why do we do this? We can see that when executing cmake .. and make commands in the build directory, the generated configuration files will be in this directory, not in others, making it look much cleaner:
root@txp-virtual-machine:/home/txp/testmy/build# ls
CMakeCache.txt CMakeFiles cmake_install.cmake Makefile src
Now the entire structure looks like this:
root@txp-virtual-machine:/home/txp/testmy# tree
.
├── bin
│ └── main
├── build
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ │ ├── 2.8.12.2
│ │ │ ├── CMakeCCompiler.cmake
│ │ │ ├── CMakeCXXCompiler.cmake
│ │ │ ├── CMakeDetermineCompilerABI_C.bin
│ │ │ ├── CMakeDetermineCompilerABI_CXX.bin
│ │ │ ├── CMakeSystem.cmake
│ │ │ ├── CompilerIdC
│ │ │ │ ├── a.out
│ │ │ │ └── CMakeCCompilerId.c
│ │ │ └── CompilerIdCXX
│ │ │ ├── a.out
│ │ │ └── CMakeCXXCompilerId.cpp
│ │ ├── cmake.check_cache
│ │ ├── CMakeDirectoryInformation.cmake
│ │ ├── CMakeOutput.log
│ │ ├── CMakeTmp
│ │ ├── Makefile2
│ │ ├── Makefile.cmake
│ │ ├── progress.marks
│ │ └── TargetDirectories.txt
│ ├── cmake_install.cmake
│ ├── Makefile
│ └── src
│ ├── CMakeFiles
│ │ ├── CMakeDirectoryInformation.cmake
│ │ ├── main.dir
│ │ │ ├── build.make
│ │ │ ├── C.includecache
│ │ │ ├── cmake_clean.cmake
│ │ │ ├── DependInfo.cmake
│ │ │ ├── depend.internal
│ │ │ ├── depend.make
│ │ │ ├── flags.make
│ │ │ ├── link.txt
│ │ │ ├── main.c.o
│ │ │ ├── progress.make
│ │ │ ├── test1.c.o
│ │ │ └── test2.c.o
│ │ └── progress.marks
│ ├── cmake_install.cmake
│ └── Makefile
├── CMakeLists.txt
├── include
│ ├── test1.h
│ └── test2.h
└── src
├── CMakeLists.txt
├── main.c
├── test1.c
└── test2.c
Check the final execution result:
root@txp-virtual-machine:/home/txp/testmy/bin# ./main
i like the cmake
a=8
TXP Embedded
2. Learning Dynamic Libraries and Static Libraries:
This topic goes back to the requirement raised by the netizen at the beginning; however, we will start learning from the simple and gradually delve deeper. Sometimes we only need to compile dynamic libraries and static libraries and then call them from other programs. How can we achieve this in CMake? The specifics are as follows:
Note that the source files used here (test1.c test2.c test1.h test2.h main.c) are all the same for testing purposes.
1. For clarity, I created a new directory project to demonstrate:
root@txp-virtual-machine:/home/txp# mkdir testcmake
root@txp-virtual-machine:/home/txp/testcmake# mkdir build lib lib_test
root@txp-virtual-machine:/home/txp/testcmake# ls
build lib lib_test
Then, place our source files test1.c and test1.h in the lib_test directory, and also create a CMakeLists.txt in the lib_test directory; create a CMakeLists.txt in the testcmake directory as well:
root@txp-virtual-machine:/home/txp/testcmake/lib_test# ls
CMakeLists.txt test1.c test1.h
/* CMakeLists.txt content in the lib_test directory: */
aux_source_directory(. SRC_LIST)
add_library(test1_shared SHARED ${SRC_LIST})
add_library(test1_static STATIC ${SRC_LIST})
set_target_properties(test1_shared PROPERTIES OUTPUT_NAME "test1")
set_target_properties(test1_static PROPERTIES OUTPUT_NAME "test1")
set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
Explanation:
-
add_library: generates a dynamic or static library (the first parameter specifies the name of the library; the second parameter determines whether it is dynamic or static; if omitted, it defaults to static; the third parameter specifies the source files to generate the library).
-
set_target_properties: sets the output name and other functions, such as setting the version number of the library, etc.
-
LIBRARY_OUTPUT_PATH: the default output path for the library files, set to the lib directory under the project directory.
The CMakeLists.txt content in the testcmake directory:
cmake_minimum_required(VERSION 2.8)
project(main)
add_subdirectory(lib_test)
Finally, the structure looks like this:
root@txp-virtual-machine:/home/txp/testcmake# ls
build CMakeLists.txt lib lib_test
root@txp-virtual-machine:/home/txp/testcmake# tree
.
├── build
├── CMakeLists.txt
├── lib
└── lib_test
├── CMakeLists.txt
├── test1.c
└── test1.h
3 directories, 4 files
2. Compilation Results:
root@txp-virtual-machine:/home/txp/testcmake/build# cmake ..
-- The C compiler identification is GNU 4.8.4
-- The CXX compiler identification is GNU 4.8.4
-- 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
-- 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
-- Configuring done
-- Generating done
-- Build files have been written to: /home/txp/testcmake/build
root@txp-virtual-machine:/home/txp/testcmake/build# make
Scanning dependencies of target test1_shared
[ 50%] Building C object lib_test/CMakeFiles/test1_shared.dir/test1.c.o
Linking C shared library ../../lib/libtest1.so
[ 50%] Built target test1_shared
Scanning dependencies of target test1_static
[100%] Building C object lib_test/CMakeFiles/test1_static.dir/test1.c.o
Linking C static library ../../lib/libtest1.a
[100%] Built target test1_static
root@txp-virtual-machine:/home/txp/testcmake/build# cd ../lib
root@txp-virtual-machine:/home/txp/testcmake/lib# ls
libtest1.a libtest1.so
Note that the compilation rules have been discussed above, so I won’t repeat them here.
From the lib directory, we can see that both static and dynamic libraries have been generated: libtest1.a and libtest1.so
3. Linking Libraries:
Now we will use the libraries we just generated. Clear the configuration files in the build directory, and also create bin and src directories in the testcmake directory (similar to what was discussed above):
root@txp-virtual-machine:/home/txp/testcmake# mkdir src bin
root@txp-virtual-machine:/home/txp/testcmake# ls
bin build CMakeLists.txt lib lib_test src
root@txp-virtual-machine:/home/txp/testcmake# cd src
root@txp-virtual-machine:/home/txp/testcmake/src# vim main.c
And modify the CMakeLists.txt content in the testcmake directory to:
cmake_minimum_required(VERSION 2.8)
project(main)
add_subdirectory(lib_test)
add_subdirectory(src)
A CMakeLists.txt should also be created in the src directory:
aux_source_directory(. SRC_LIST)
include_directories(../lib_test)
link_directories(${PROJECT_SOURCE_DIR}/lib)
add_executable(main ${SRC_LIST})
target_link_libraries(main test1)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
Explanation:
–link_directories: adds a non-standard shared library search path.
–target_link_libraries: links the target file with the library file.
4. Compile:
root@txp-virtual-machine:/home/txp/testcmake/build# cmake ..
-- The C compiler identification is GNU 4.8.4
-- The CXX compiler identification is GNU 4.8.4
-- 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
-- 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
-- Configuring done
-- Generating done
-- Build files have been written to: /home/txp/testcmake/build
root@txp-virtual-machine:/home/txp/testcmake/build# make
Scanning dependencies of target test1_shared
[ 33%] Building C object lib_test/CMakeFiles/test1_shared.dir/test1.c.o
Linking C shared library ../../lib/libtest1.so
[ 33%] Built target test1_shared
Scanning dependencies of target test1_static
[ 66%] Building C object lib_test/CMakeFiles/test1_static.dir/test1.c.o
Linking C static library ../../lib/libtest1.a
[ 66%] Built target test1_static
Scanning dependencies of target main
[100%] Building C object src/CMakeFiles/main.dir/main.c.o
Linking C executable ../../bin/main
[100%] Built target main
root@txp-virtual-machine:/home/txp/testcmake/build# cd ../bin
root@txp-virtual-machine:/home/txp/testcmake/bin# ls
main
From the above, we can see that the execution was successful. Also note:
In the lib directory, we have both dynamic and static libraries, while in the CMakeLists.txt in the src directory, the target_link_libraries(main test1) by default uses the dynamic library. If there is only a static library in the lib directory, this syntax will link to the static library. You can also directly specify whether to use the dynamic or static library, written as: target_link_libraries(main libtest1.so) or target_link_libraries(main libtest1.a)
At the same time, we can check which library files the ELF file is using:
root@txp-virtual-machine:/home/txp/testcmake/bin# readelf -d ./main
Dynamic section at offset 0xe08 contains 26 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libtest1.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000f (RPATH) Library rpath: [/home/txp/testcmake/lib]
0x000000000000000c (INIT) 0x4006a0
0x000000000000000d (FINI) 0x400894
0x0000000000000019 (INIT_ARRAY) 0x600df0
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x600df8
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x400298
0x0000000000000005 (STRTAB) 0x4004d8
0x0000000000000006 (SYMTAB) 0x4002f8
0x000000000000000a (STRSZ) 260 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x601000
0x0000000000000002 (PLTRELSZ) 96 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x400640
0x0000000000000007 (RELA) 0x400628
0x0000000000000008 (RELASZ) 24 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffffe (VERNEED) 0x400608
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x4005dc
0x0000000000000000 (NULL) 0x0
3. Summary:
Here is the implementation that another netizen proposed. Now that we know how to use dynamic and static libraries, it will be easier; due to space constraints, this test will be left for the next CMake article to experiment to see if it can be realized (I haven’t done the experiment yet, but once I do, I will share it. This article took a long time to share, and everyone can digest it for now); for more exciting content, see you in the next article.
Everyone can follow the public account: TXP Embedded. Or add my personal WeChat, and I will pull you into the WeChat group; at the same time, I established a QQ group during lunch yesterday, mainly for everyone to download some e-books and project learning:


