Audio and video development journey (65) - learning and practicing CMake with problems

Posted by nediaz on Sun, 06 Mar 2022 17:08:20 +0100

catalogue

  1. Create an executable program using CMake
  2. Create a dynamic / static library and use it as a library
  3. The third-party library is introduced in the form of source code and used in the form of multi-layer directory
  4. The third lib library is shared across platforms to change the hierarchical structure of the code
  5. Other small details (problems encountered in project practice)
  6. data
  7. harvest

CMake is a very basic and important link in our CPP development. Just like the ant and Gradle functions of Java, it is similar to building and compiling CPP code.
There are also a lot of learning materials about systematic CMake. I passed it cmake practice And [cmake examples]( https://github.com/ttroy50/cmake-examples )For learning and practice. It is suggested to read the contents of these materials first, systematically study and practice, practice and practice, and then read this article.
On the one hand, this article summarizes the harvest of learning and practice in the form of "problem solution". On the other hand, it also records the problems and pits encountered in using Cmake in recent work.

Let's start our CMake learning and practice journey.

1, Create an executable program using CMake

Source code file

#include <stdio.h>
int main()
{
    printf("Hello World from t1 main\n");
    return 0;
}

CMakeLists.txt file

//Each CMakeList has its own project, which defines the name of the final product compiled by CMake.
PROJECT (HELLO)

//SET can SET the value of the variable. Here, assign the source file name to the variable SRC_LIST, if there are multiple source files, separate them with spaces
SET(SRC_LIST helloworld.cpp)

//The following four statements Print out the values of some variables through MESSAGE.
//Where < ProjectName >_ SOURCE_ DIR,<projectname>_ BINARY_ Dir is an implicit variable, and ProjectName is the value defined in the first line above.
//PROJECT_SROURCE_DIR,PROJECT_BINARY_DIR is an explicit variable, which has the same effect as the above two.
//Since the Projectname can be changed through the first instruction, if you use implicit variables, you need to modify them accordingly, while explicit variables are not.
//PROJECT_SROURCE_DIR: the path where the source file (also known as CMAKE) is located
//PROJECT_BINARY_DIR: the path of the generated binary file product. Generally, the source file and product file are separated under the build directory. At this time, the corresponding path is the build folder path
MESSAGE(STATUS "This is BINARY DIR ${HELLO_BINARY_DIR}")
MESSAGE(STATUS "This is PROJECT BINARY DIR ${PROJECT_BINARY_DIR}")
MESSAGE(STATUS "This is SOURCE DIR ${HELLO_SOURCE_DIR}")
MESSAGE(STATUS "This is PROJECT SOURCE DIR ${PROJECT_SOURCE_DIR}")

//This instruction is used to generate an executable file. The first parameter is the name of the product producing the executable file, and the second parameter is the source file used to generate the executable file
ADD_EXECUTABLE(hello ${SRC_LIST}) # hello can be written as ${PROJECT_NAME}

Then execute MKDIR build & & CD build & & cmake&& make
You can see the output MESSAGE information and the generated executable file

 mkdir build && cd build && cmake .. && make
...
-- This is BINARY DIR /xxx/cmake/cmake practice/t1/build
-- This is PROJECT BINARY DIR /xxx/cmake/cmake practice/t1/build
-- This is SOURCE DIR /xxx/cmake/cmake practice/t1
-- This is PROJECT SOURCE DIR /xxx/cmake/cmake practice/t1

[100%] Linking CXX executable hello
[100%] Built target hello

/xxx/.../build % ls
CMakeCache.txt      Makefile        hello
CMakeFiles      cmake_install.cmake

 ./hello 
Hello World from t1 main

Section summary:

  1. Learn three instructions: PROJECT, MESSAGE and ADD_EXECUTABLE
  2. Understand five variables: PROJECT_SROURCE_DIR,PROJECT_BINARY_DIR,<projectname>_ SOURCE_ DIR,<projectname>_ BINARY_ DIR,PROJECT_NAME
  3. A simple executable program is built through Cmake

2, Create a dynamic / static library and use it as a library

Source file: HelloWorld cpp

#include <stdio.h>
void func()
{
    printf("Hello World\n");
}

CMakeList.txt

SET(LIB_HELLOWORLD_SRC helloworld.cpp)


//ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 .. sourceN)

//There are three types of SHARED|STATIC|MODULE
//SHARED: the dynamic library generates dylib under mac and so under linux
//STATIC: STATIC library
//MODULE: generally not used

//This instruction is used to generate binary library files. The first parameter is the name of the product; The second parameter is the type of Library: dynamic library, static library, etc; The third parameter is the source file used to generate the product
ADD_LIBRARY(hello SHARED ${LIB_HELLOWORLD_SRC})

//The above instruction generates a dynamic library through SHARED, while this instruction generates a STATIC library through STATIC
ADD_LIBRARY(hello_static STATIC ${LIB_HELLOWORLD_SRC})

//This instruction is used to set some attributes of the output target product, which requires four parameters
//The first parameter is the name of the target
//The second parameter is the fixed keyword PROPERTIES
//The third parameter is the attribute name, just like the key of Map
//The fourth parameter is the value of the property setting
// To make the names of static and dynamic libraries the same, use PROPERTIES OUTPUT_NAME "XXX"
SET_TARGET_PROPERTIES(hello_static PROPERTIES  OUTPUT_NAME "hello")

//In order to generate static and dynamic libraries at the same time, and the latter does not clear the former, properties clean is used_ DIRECT_ OUTPUT 1
SET_TARGET_PROPERTIES(hello PROPERTIES  CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES  CLEAN_DIRECT_OUTPUT 1)

Similarly, execute MKDIR build & & CD build & & cmake&& make
You can see the dynamic library libhello Dylib and static library libhello A generation

[ 25%] Building CXX object lib/CMakeFiles/hello_static.dir/helloworld.o
[ 50%] Linking CXX static library libhello.a
[ 50%] Built target hello_static
[ 75%] Building CXX object lib/CMakeFiles/hello.dir/helloworld.o
[100%] Linking CXX shared library libhello.dylib
[100%] Built target hello
yangbin@yangbindeMacBook-Pro build % ls lib/
CMakeFiles      cmake_install.cmake libhello.dylib
Makefile        libhello.a

Use the generated dynamic / static library to produce executable files
Source file: main cpp

#include "helloworld.h"
int main()
{
    func();
    return 0;
}

CMakeLists.txt

ADD_EXECUTABLE(main main.cpp)
MESSAGE(STATUS "PROJECT_BINARY_DIR"${PROJECT_BINARY_DIR})
MESSAGE(STATUS "PROJECT_SOURCE_DIR"${PROJECT_SOURCE_DIR})
#LINK_DIRECTORIES(lib) # If this line is written in ADD_EXECUTABLE error ld: warning: directory not found for option '-Llib'

#Using dynamic libraries
#ADD_LIBRARY(hello SHARED IMPORTED)
#SET_TARGET_PROPERTIES(hello PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/lib/libhello.dylib) # Use here/ lib/libhello.dylib doesn't work. When you prompt link, you can't find the corresponding library. Use ${PROJECT_SOURCE_DIR} absolute path to set it

#Using static libraries
ADD_LIBRARY(hello STATIC IMPORTED)
SET_TARGET_PROPERTIES(hello PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/lib/libhello.a) # Use here/ lib/libhello.a just can't. when you prompt link, you can't find the corresponding library. Use ${PROJECT_SOURCE_DIR} absolute path to set it
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/lib/include)


TARGET_LINK_LIBRARIES(main hello)

Section summary:

  1. Learn two instructions: ADD_LIBRARY,SET_TARGET_PROPERTIES
  2. Learn how to produce dynamic and static libraries
  3. Using dynamic / static libraries

3, The third-party library is introduced in the form of source code and used in the form of multi-layer directory

In the previous section, we learned how to use time through dynamic library or static library. Some scenarios need to be introduced in the form of source code rather than dynamic / static. At the same time, in order to ensure the mutual independence of codes, multi-layer directories are used instead of in the same directory. For example, in order to facilitate the scene of calling, modifying and debugging third-party libraries. In this section, we practice it.

├── CMakeLists.txt
├── lib
│ ├── CMakeLists.txt
│ ├── helloworld.cpp
│ └── include
│ └── helloworld.h
└── main.cpp

The code remains unchanged, and the modification point is CMakeList
Outer cmakelist txt

ADD_EXECUTABLE(main main.cpp)
//Here, a new instruction is used to add subfolders. CmakeList with subfolders is compiled into a library for external use
//ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

//This instruction is used to add a subdirectory for storing source files to the current project, and the location of intermediate binary and target binary can be specified through the second parameter. EXCLUDE_ FROM_ The all parameter means to exclude this directory from the compilation process, such as the test or sample directory in the project. It may be necessary to enter the corresponding directory for separate construction after the project construction is completed
ADD_SUBDIRECTORY(lib)


//The following two instructions are to add header files, but they are used in different ways and functions.

//The main differences are:
//1. include_ The directories will act on the whole project, target_include_directories will act on the project of target
//The target file (add. Table) or the command (add. Table) must already exist_ Library () and cannot be modified by IMPORTED
//3. The keywords INTERFACE, PUBLIC and PRIVATE specify the scope of the following parameters. The PRIVATE and PUBLIC entries populate the include of the targe target file_ The directories property.
//4 the public and INTERFACE items will fill in the INTERFACE of the target file_ INCLUDE_ The directories property. Subsequent parameters specify the include directory.

//target_include_directories(<target> [SYSTEM] [AFTER|BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1...])
TARGET_INCLUDE_DIRECTORIES(main PUBLIC lib/include)

//include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
#INCLUDE_DIRECTORIES(lib/include)

TARGET_LINK_LIBRARIES(main hello)

Cmakelist. Of inner subfolder txt

SET(LIB_HELLOWORLD_SRC helloworld.cpp)

ADD_LIBRARY(hello SHARED ${LIB_HELLOWORLD_SRC})
INCLUDE_DIRECTORIES(include)

Section summary:

  1. Learning and practicing three instructions: ADD_SUBDIRECTORY,INCLUDE_DIRECTORIES,TARGET_INCLUDE_DIRECTORIES
  2. It is used in the form of organization code in the structure of subfolders

4, The third lib library is shared across platforms to change the hierarchical structure of the code

If the code structure changes and lib is not placed in the subfolder of src, what's the difference
There are also many applications in this scenario. For example, lib is a general cross platform library, while src is android
Or some unique code of ios and other platforms. For the convenience of public use, lib will adopt this organizational form

├── lib
│ ├── CMakeLists.txt
│ ├── helloworld.cpp
│ └── include
│ └── helloworld.h
└── src
├── CMakeLists.txt
└── main.cpp

Camke .. The following error will be reported when.

CMake Error at CMakeLists.txt:3 (ADD_SUBDIRECTORY):
  ADD_SUBDIRECTORY not given a binary directory but the given source
  directory "/xxx/cmake practice/t6/lib"
  is not a subdirectory of
  "/xxx/cmake practice/t6/src".  When
  specifying an out-of-tree source a binary directory must be explicitly
  specified.

The reason is obvious in the structure of the file_ If the folder of subdirectory is not a sub file of target, the second parameter is required to indicate the path of the binary product generated by the sub target.

Modification point: CmakeList

ADD_EXECUTABLE(main main.cpp)

//If the outputs folder does not exist, it is created
file(MAKE_DIRECTORY output)

//Add the second parameter to indicate the storage location of the product compiling the sub target
ADD_SUBDIRECTORY(../lib output)
TARGET_INCLUDE_DIRECTORIES(main PUBLIC ../lib/include)
#INCLUDE_DIRECTORIES(../lib/include)

TARGET_LINK_LIBRARIES(main hello)

Section summary:

  1. Change the hierarchical structure of code and better cross screen platform support.

5, Other small details (problems encountered in project practice)

How to use CMake to call external tool Libraries
If the sub target passes set_ TARGET_ Output of properties_ The name attribute sets the name of the output library, as follows:

ADD_LIBRARY(hello_static STATIC ${LIB_HELLOWORLD_SRC})

SET_TARGET_PROPERTIES(hello_static PROPERTIES  OUTPUT_NAME "hello")

It is best to use add in the target_ The name of Library (i.e. hello_static), not output_ The name after name (i.e. Hello), otherwise it can't be found when link, and an error will be reported when compiling on some ides (such as Android studio or clion). In particular, Android studio doesn't directly prompt that the product of sub target can't be found, but the sub target and target target are compiled in parallel...

Why can't Message print out sometimes
This may also have something to do with platform compatibility. There is no problem on the computer or ios side, but it may not output normally on android. You need to set the WARNING level to be greater than or equal to. The corresponding log cannot be output on the STATUS level andorid

//The log cannot be output on android platform, but other platforms can
MESSAGE(STATUS "PROJECT_SOURCE_DIR"${PROJECT_SOURCE_DIR})

//andorid platform can also output
MESSAGE(WARNING "PROJECT_SOURCE_DIR"${PROJECT_SOURCE_DIR})

The code of this article has been uploaded to github-mediajourney

6, Information

[exercise item cmake examples Chinese]
Exercise items - cmake examples

Book practice.cmake pdf
Book CMake Cookbook (Chinese version)

7, Harvest

Through the study and practice of this article,

  1. Understand some common instructions of Cmake
  2. Engineering practice through Cmake (practice from multiple perspectives such as library, source code and cross screen organization form)
  3. Summarize and record the problems encountered in practice

Thank you for reading
Welcome to the official account of the "audio and video development tour" and learn and grow together.
Welcome to exchange

Topics: C++ Back-end