CMake builds static libraries and dynamic libraries and uses

1. Tasks

Use examples to explain the process of CMake building static libraries and dynamic libraries in an easy-to-understand manner.

Task:

  1. Build a static library and a dynamic library, provide the HelloFunc function for other program programming, and HelloFunc outputs the Hello World string to the terminal.
  2. Install header files and shared libraries.
  3. Write a program to use the built shared library.

2. Preparation

(1) Create a t3 directory to store the projects involved in this section.

mkdir t3

(2) Create a shared library.

cd t3
mkdir lib

(3) Create CMakeLists.txt under the t3 directory, the content is as follows:

PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)

(4) Create two source files hello.c and hello.h under the lib directory.

The content of hello.c is as follows:

#include "hello.h"
void hello_func(void) 
{
    
    
	printf("Hello World!\n");
	return;
}

The content of hello.h is as follows:

#ifndef HELLO_H_
#define HELLO_H_ (1)
#include <stdio.h>
void hello_func(void);
#endif

(5) Create CMakeLists.txt in the lib directory, the content is as follows:

SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

3. Compile the shared library

Use the out-of-source compilation method to create a build directory and execute in the build directory:

mkdir build
cd build
cmake ..
make

At this time, you can get a libhello.so in the lib directory, which is the shared library.

If you want to specify the location where libhello.so is generated, you can specify a compilation output location by modifying the ADD_SUBDIRECTORY(lib) directive in the main project file CMakeLists.txt or add SET(LIBRARY_OUTPUT_PATH <path>) to lib/CMakeLists.txt a new location.

Four, ADD_LIBRARY command

grammar:

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

You don’t need to write the whole libhello.so, just fill in hello, and the cmake system will automatically generate libhello.X

There are three types:

  1. SHARED, dynamic library.
  2. STATIC, static library.
  3. MODULE, valid on systems using dyld, if dyld is not supported, it will be treated as SHARED.

The EXCLUDE_FROM_ALL parameter means that this library will not be built by default unless there are other components that depend on it or build it manually.

5. Compile the static library

The name of the static library should be the same as that of the dynamic library, except that the suffix is ​​.a. Next, use this command to add the static library:

 ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})

Note that if the above content is added on the basis of compiling the dynamic library above, you will find that the static library is not built at all, and only one dynamic library is still generated. Because hello as a target cannot be renamed, so the static library building instruction is invalid. If you change the above hello to hello_static:

ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})

You can build a static library of libhello_static.a.

But this kind of result is not what we want. What we need is a static library and a dynamic library with the same name, because the target name is unique, so it must not be realized through the ADD_LIBRARY instruction. At this time we need to use another command SET_TARGET_PROPERTIES.

5.1, SET_TARGET_PROPERTIES command

SET_TARGET_PROPERTIES basic syntax:

SET_TARGET_PROPERTIES(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)

This command can be used to set the name of the output, and for dynamic libraries, it can also be used to specify the dynamic library version and API version.

In this case, all that needs to be done is to add another entry to lib/CMakeLists.txt:

SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello") 

In this way, we can get two libraries libhello.so and libhello.a at the same time.

The final content of lib/CMakeLists.txt:

SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")

5.2, GET_TARGET_PROPERTY command

The instruction corresponding to SET_TARGET_PROPERTY is GET_TARGET_PROPERTY. The basic syntax is:

GET_TARGET_PROPERTY(VAR target property)

The specific usage is as follows, add to lib/CMakeListst.txt:

GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
 
MESSAGE(STATUS "This is the hello_static OUTPUT_NAME:"${OUTPUT_VALUE})

Returns NOTFOUND if this property is not defined.

As a result of the build, it will be found that libhello.a has been built and is located in the build/lib directory, but libhello.so has disappeared. The reason for this problem is: when cmake builds a new target, it will try to clean up other libraries using this name, because when building libhello.a, it will clean up libhello.so.

To circumvent this problem, for example define the CLEAN_DIRECT_OUTPUT property again using SET_TARGET_PROPERTIES.

Add to lib/CMakeLists.txt:

SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

At this time, if we build again, we will find that libhello.so and libhello.a are both generated in the build/lib directory.

6. Dynamic library version number

According to the rules, the dynamic library should contain a version number. We can look at the dynamic library of the system. The general situation is:

libhello.so.1.0
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.0

In order to implement the dynamic library version number, the SET_TARGET_PROPERTIES directive needs to be used.

The specific usage method is as follows:

SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1) 

VERSION refers to the dynamic library version, SOVERSION refers to the API version.

Add the above instructions to lib/CMakeLists.txt, rebuild, and generate in the build/lib directory:

libhello.so.1.2 libhello.so.1->libhello.so.1.2 libhello.so ->libhello.so.1

7. Install shared libraries and header files

In the above example, libhello.a, libhello.so.x and hello.h need to be installed in the system directory, so that other people can develop and use it. In this example, we install the shared library of hello to < prefix >/lib directory, install hello.h to the < prefix >/include/hello directory.

Using the INSTALL command learned in the previous article, add the following command to lib/CMakeLists.txt:

INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)

Note that static libraries use the ARCHIVE keyword

Install with the following command:

cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
sudo make install

This will install the header files and shared libraries into the system directories /usr/lib and /usr/include/hello.

[ 50%] Built target hello
[100%] Built target hello_static
Install the project...
-- Install configuration: ""
-- Installing: /usr/lib/libhello.so.1.2
-- Installing: /usr/lib/libhello.so.1
-- Installing: /usr/lib/libhello.so
-- Installing: /usr/lib/libhello.a
-- Installing: /usr/include/hello/hello.h

If an error is reported:

CMake Error: cmake_symlink_library: System Error: Operation not supported
CMake Error: cmake_symlink_library: System Error: Operation not supported
make[2]: *** [lib/CMakeFiles/hello_dynamic.dir/build.make:85: lib/libhello.so.1.2] Error 1
make[2]: *** Deleting file 'lib/libhello.so.1.2'
make[1]: *** [CMakeFiles/Makefile2:130: lib/CMakeFiles/hello_dynamic.dir/all] Error 2
make: *** [Makefile:130: all] Error 2

It means that make may be performed in the Windows directory shared by hgfs. When generating so, it needs to be run in a pure linux environment. For example, copy t3 to ~/t3, and first clear the contents of the corresponding build directory, and then execute again:

cmake .. 
make

8. Use external shared libraries and header files

8.1. Preparations

(1) Create a t4 directory, all resources in this section will be stored in the t4 directory.

mkdir t4

(2) Create a src directory under the t4 directory.

cd t4
mkdir src

(3) Write the source file main.c in the src directory, the content is as follows:

#include "hello.h"
int main(void) 
{
    
    
	hello_func();
	return 0;
}

(4) Write the project main file CMakeLists.txt in the t4 directory.

PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)

(5) Write src/CMakeLists.txt in the t4/src directory.

ADD_EXECUTABLE(main main.c)

8.2. Introduce header file search path

After the preparation work is completed, the direct build fails because the header file "hello.h" cannot be found. The error output is:

cmake/t4/src/main.c:1:19: error: hello.h: 没有那个文件或目录

In order for our project to find the hello.h header file, a new instruction INCLUDE_DIRECTORIES needs to be introduced, and its complete syntax is:

INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)

This command can be used to add multiple specific header file search paths to the project. The paths are separated by spaces. If the paths contain spaces, they can be enclosed in double quotes. The default behavior is to append to the current header Behind the file search path, there are two ways to control how the search path is added:

CMAKE_INCLUDE_DIRECTORIES_BEFORE, by setting this cmake variable to on, the added header file search path can be placed in front of the existing path.
Through the AFTER or BEFORE parameters, you can also control whether to append or prepend.

Now we add a header file search path in src/CMakeLists.txt, the way is very simple, add:

INCLUDE_DIRECTORIES(/usr/include/hello)

Enter the build directory and build again. At this time, the error that hello.h cannot be found has disappeared, but a new error has appeared:

CMakeFiles/main.dir/main.o:在函数‘main’中:
main.c:(.text+0x5):对‘hello_func’未定义的引用
collect2: error: ld returned 1 exit status
src/CMakeFiles/main.dir/build.make:96: recipe for target 'src/main' failed
make[2]: *** [src/main] Error 1
CMakeFiles/Makefile2:97: recipe for target 'src/CMakeFiles/main.dir/all' failed
make[1]: *** [src/CMakeFiles/main.dir/all] Error 2
Makefile:90: recipe for target 'all' failed
make: *** [all] Error 2

Because we did not link to the shared library libhello.

8.3. Add shared library for target

The task that needs to be done now is to link the target file to libhello, here we need to introduce two new directives: LINK_DIRECTORIES and TARGET_LINK_LIBRARIES.

The full syntax for LINK_DIRECTORIES is:

LINK_DIRECTORIES(directory1 directory2 ...)

This command is very simple. It adds a non-standard shared library search path. For example, if there are both shared libraries and executable binaries in the project, you need to specify the path of these shared libraries when compiling. We don't use this command in this example.

The full syntax of TARGET_LINK_LIBRARIES is:

TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2 ...)

This instruction can be used to add a shared library that needs to be linked for the target. In this example, it is an executable file, but it can also be used to add a shared library link for a shared library written by yourself.

In order to solve the HelloFunc undefined error we encountered earlier, what needs to be done is to add the following instructions to src/CMakeLists.txt:

TARGET_LINK_LIBRARIES(main hello)

can also be written as:

TARGET_LINK_LIBRARIES(main libhello.so)

The hello here refers to the previously built shared library libhello.XX.

Enter the build directory to rebuild.

cmake ..
make

At this time, an executable program main connected to libhello is obtained, which is located in the build/src directory. The result of running main is the output:

$ ./main
Hello World!

Let's take a look at the main link:

$ ldd src/main 
        linux-vdso.so.1 (0x00007ffd531fd000)
        libhello.so.1 => /usr/lib/libhello.so.1 (0x00007ff212afe000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff21270d000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ff212d00000)

It can be clearly seen that main does link the shared library libhello, and the link is the dynamic library libhello.so.1.

9. Use external static libraries and header files

So how to link to the static library? The method is very simple:

Change the TARGET_LINK_LIBRRARIES directive to:

TARGET_LINK_LIBRARIES(main libhello.a)

After rebuilding, take a look at the main link:

$ ldd src/main 
        linux-vdso.so.1 (0x00007ffc7a161000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff1da80c000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ff1dabfd000)

It shows that main is indeed linked to the static library libhello.a.

10. Special environment variables

CMAKE_INCLUDE_PATH and CMAKE_LIBRARY_PATH. It is important to note that these two are environment variables and not cmake variables.

The method of use is to use export in bash or use the set command in csh to set or CMAKE_INCLUDE_PATH=/home/include cmake …etc.

These two variables are mainly used to solve the support of parameters such as --extra-include-dir in previous autotools projects. That is, if the header files are not stored in the regular path (/usr/include, /usr/local/include, etc.), these variables can be used to make up for it.

Take hello.h in this example as an example, it is stored in the /usr/include/hello directory, so it will definitely not be found if you search directly.

Earlier we directly used the absolute path INCLUDE_DIRECTORIES (/usr/include/hello) to tell the project the header file directory.

In order to make the program more intelligent, we can use CMAKE_INCLUDE_PATH to do it. The method of using bash is as follows:

export CMAKE_INCLUDE_PATH=/usr/include/hello
Then replace INCLUDE_DIRECTORIES(/usr/include/hello) in the header file with:

FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${
    
    myHeader})
ENDIF(myHeader)

FIND_PATH is used to search for file names in the specified path, for example:

FIND_PATH(myHeader NAMES hello.h PATHS /usr/include /usr/include/hello)

Here we do not specify the path, but cmake can still help us find the path where hello.h is stored, because we set the environment variable CMAKE_INCLUDE_PATH.

If FIND_PATH is not used, the setting of the CMAKE_INCLUDE_PATH variable has no effect, and it cannot be expected to directly add the parameter -I < CMAKE_INCLUDE_PATH > to the compiler command.

Using this example, CMAKE_LIBRARY_PATH can be used in FIND_LIBRARY.

Also, since these variables are used directly by the FIND_ directive, all cmake modules that use the FIND_ directive will benefit.

Summarize

  1. Build dynamic and static libraries with the ADD_LIBRARY directive.
  2. Build both dynamic and static libraries with the same name through SET_TARGET_PROPERTIES.
  3. Control the dynamic library version through SET_TARGET_PROPERTIES.
  4. Use the INSTALL command to install header files and dynamic and static libraries.
  5. Include non-standard header file search paths with the INCLUDE_DIRECTORIES directive.
  6. Add non-standard library file search paths with the LINK_DIRECTORIES directive.
  7. Add library links for libraries or executable binaries via TARGET_LINK_LIBRARIES.
  8. The way CMake links to static libraries.
  9. CMake still has many advanced topics that have not been discussed, such as compilation condition checking, compiler definition, platform judgment, how to use it with pkgconfig, and so on.
  10. The process of using cmake is actually the process of learning cmake language and writing cmake programs. Since it is "cmake language", it naturally involves variables, grammar, etc.

insert image description here

Guess you like

Origin blog.csdn.net/Long_xu/article/details/129139244