Use cmake for cross-platform software development
Original article: cross-platform software development using cmake http://www.linuxjournal.com/article/6700
Translated by Andrej cedilnik
Build your project in each system without having to worry about the specific methods for creating executable files and dynamic libraries.
When you observe a lot of projects, you will find one thing: the description of the build process is always stored in a group of files. These files may be simple shell scripts, makefiles, jam files, and complex script-based projects such as Autoconf and automake.
Recently, a new player cmake has joined the software build game. Cmake uses native build tools, such as make or even Microsoft's Visual Studio, instead of a build program. Supports multiple platforms, in-source and out-source build, cross-database dependency detection, parallel build, configurable header files. This greatly reduces the complexity of the cross-platform software development and maintenance process.
Build System
Looking at most of the software development projects, you will undoubtedly face a common problem. You have a lot of source files. Some files depend on others. You want to generate the final binary file. Sometimes you want to do more complex things, but in most cases.
You have a small project that you want to build on your Linux. You sat down and quickly wrote the following makefile:
main.o: main.c main.h
cc -c main.c
MyProgram: main.o
cc -o MyProgram main.o -lm -lz
After the file is written, all you need to do is enter the make command and the project is built. If any file is changed, all necessary files will be rebuilt. Good. Now you can congratulate yourself and have a drink.
Unless your boss says, "we just got a new XYZ-type computer and you need to build this software on it ." Therefore, copy the file, enter the make command, and get the following error message:
cc: Command not found
You know that the XYZ computer has a compiler called CC-XYZ, So modify the makefile and try again. However, zlib is not found in the system. So you can remove the-LZ parameter and directly package it into the zlib code.
As you can see, when using makefile, make becomes invalid as long as the file is moved to a new platform that uses different compiler names or parameters.
Let's look at a more detailed example of this problem. Let's take a look at our favorite compressed library zlib. Zlib is a simple library consisting of 17 C source files and 11 header files. Compiling zlib is simple. All you need to do is compile each c file and link them together. You can write a makefile to solve the problem, but on each platform, you must modify the makefile to make it available.
Tools such as Autoconf and automake can solve this problem well on UNIX and UNIX platforms. But they are usually too complicated. This makes things worse. In most projects, developers eventually need to write shell scripts in the input file of Autoconf. The results quickly become dependent on developers' assumptions. Because the Autoconf result depends on shell, these configuration files are invalid on platforms without the Bourne shell or similar standard/bin/sh. Autoconf and automake also depend on the tools installed on several systems.
Cmake is a solution to these problems. Compared with other similar tools, cmake makes fewer assumptions on the underlying system. Cmake uses the Standard C ++ implementation, so it can run on most modern operating systems. It does not use any other tool except the system's local build tool.
Installing cmake
On Some platforms, such as Debian GNU/Linux, cmake is a standard package. For most other platforms, including UNIX, Mac OS X, and microsof windows, the cmake binary package can be downloaded directly from the cmake web site. You can run cmake -- help to check whether cmake is installed. This command displays the cmake version and usage information.
Simple cmake
Now that cmake is installed, we can use it in our project. We must first prepare the cmake input file cmakelists.txt. For example, the following is a simple cmakelists.txt:
PROJECT(MyProject C)
ADD_LIBRARY(MyLibrary STATIC libSource.c)
ADD_EXECUTABLE(MyProgram main.c)
TARGET_LINK_LIBRARIES(MyProgram MyLibrary z m)
Using cmake to build a project is extremely simple. In the directory containing cmakelists.txt, enter the following two commands. The path is the source code path:
cmake path
make
Cmake reads the cmakelists.txt file from the source directory and generates appropriate makefiles for the system in the current directory. Cmake maintains the list of dependent header files, so dependency detection is ensured. If you want to add more source files, simply add them to the list. After makefiles are generated, you do not need to execute cmake because the dependency check on cmakelists.txt has been added to the generated makefils. If you want to ensure that the dependency is re-generated, You can execute make depend.
Cmake commands
Cmake is essentially a simple interpreter. The cmake input file has an extremely simple but powerful syntax. It consists of commands, original flow control constructs, macros, and variables. All commands have the same syntax:
COMMAND_NAME(ARGUMENT1 ARGUMENT2 ...)
For example, the add_library command specifies that a library should be created. The first parameter is the database name, the second optional parameter is used to specify whether the database is static or dynamic, and other parameters are the list of source files. Do you want dynamic libraries? Simply replace static with shared. For a list of all commands, see the cmake documentation.
There are also some stream control structures, such as if and foreach. If expressions cannot contain other commands. You can use not, And, or. The following is an example of an if statement:
IF(UNIX)
IF(APPLE)
SET(GUI "Cocoa")
ELSE(APPLE)
SET(GUI "X11")
ENDIF(APPLE)
ELSE(UNIX)
IF(WIN32)
SET(GUI "Win32")
ELSE(WIN32)
SET(GUI "Unknown")
ENDIF(WIN32)
ENDIF(UNIX)
MESSAGE("GUI system is ${GUI}")
This example shows the use of IF Statements and variables.
The parameter of the foreach command. The first is the variable that saves the iteration content, followed by the list of iterations. For example, if a column of executable files needs to be created and each executable file is created from a source file with the same name, you can use foreach as follows:
SET(SOURCES source1 source2 source3)
FOREACH(source ${SOURCES})
ADD_EXECUTABLE(${source} ${source}.c)
ENDFOREACH(source)
A macro can be defined using macro construction. When we need to create some executable files frequently and link some libraries. The macros below can be simpler in our life. In this example, create_executable is the macro name and other parameters. In a macro, all parameters are considered as variables. If a macro is created, it can be used as a common command. The definition and usage of create_executable are as follows:
MACRO(CREATE_EXECUTABLE NAME
SOURCES LIBRARIES)
ADD_EXECUTABLE(${NAME} ${SOURCES})
TARGET_LINK_LIBRARIES(${NAME}
${LIBRARIES})
ENDMACRO(CREATE_EXECUTABLE)
ADD_LIBRARY(MyLibrary libSource.c)
CREATE_EXECUTABLE(MyProgram main.c MyLibrary)
Macros are not equivalent to functions or processes in general programming languages. Macros cannot be called recursively.
Conditional compilation
An important feature of a good build program is that it can open or close a part of the build. The build program should also be able to locate and set the location of the system resources required by your project. All these functions are implemented using Conditional compilation in cmake. Let me demonstrate an example. Let's assume that your project has two modes: regular mode and debugging mode. Add some debugging code to the common code in debug mode. Therefore, your code is filled with such code snippets:
#ifdef DEBUG
fprintf(stderr,
"The value of i is: %dn", i);
#endif /* DEBUG */
To tell cmake to add a-ddebug command to the compilation command, you can use the set_source_files_properties command on the property compile_flags. However, you may not want to change the cmakelists.txt file every time to switch between debug mode and normal mode. The option command can be used to create a Boolean variable that can be set before the build project. The previous example can be improved:
OPTION(MYPROJECT_DEBUG
"Build the project using debugging code"
ON)
IF(MYPROJECT_DEBUG)
SET_SOURCE_FILE_PROPERTIES(
libSource.c main.c
COMPILE_FLAGS -DDEBUG)
ENDIF(MYPROJECT_DEBUG)
Now, you ask, "How do I set this variable ?" Cmake comes with three GUI tools. In Unix systems, there is a terminal GUI tool called ccmake, Which is text-based and can be used on a remote terminal connection. Cmake also provides GUI tools for Microsoft Windows and Mac OS X.
When you are using makefiles generated by cmake, if you have already executed cmake, you only need to enter the command make edit_cache. This command runs a suitable GUI tool. Among all gui tools, you have some options for setting variables. As you can see, cmake has several default options, such as executable_output_path and library_output_path (the path to which executable files are output in combination with library files). In our previous example, myproject_debug is also available. After changing some variable values, you can press the configuration button or the C key (in ccmake ).
In the GUI tool, you will set several different types of entries. Myproject_debug is Boolean, and another common variable type is path. Suppose our program depends on the position of pyhon. h file. In the cmakelists.txt file, add the following command to try to find a file:
FIND_PATH(PYTHON_INCLUDE_PATH Python.h
/usr/include
/usr/local/include)
It is a waste to specify all locations repeatedly in each separate project. You can include other cmakes files called modules. Cmake comes with some useful modules, from searching for different software packages to doing some practical things or defining some macro modules. For the list of all modules, see the cmake module sub-directory. For example, a module called findpythonlibs. cmake can be used in most systems to find Python library files and header file paths. However, if cmake cannot find what you want, you can always specify it in the GUI tool. You can also access the cmake variable through the command line. The following command sets myproject_debug to off:
cmake -DMYPROJECT_DEBUG:BOOL=OFF
What about subdirectories? Subdirectory?
As a software developer, you may need to organize the source code into sub-directories. Different subdirectories can represent different libraries, executable files, tests, and documents. Now we can enable or disable sub-directories so that part of the build project skips another part. Use the subdirs command to tell cmake to process a sub-directory. This command causes cmake to go down to the specified subdirectory to find the cmakelists.txt file. Using this command can make our project more organized. We move all the library files to the library subdirectory. The top-level cmakelists.txt now looks like this:
PROJECT(MyProject C)
SUBDIRS(SomeLibrary)
INCLUDE_DIRECTORIES(SomeLibrary)
ADD_EXECUTABLE(MyProgram main.c)
TARGET_LINK_LIBRARIES(MyProgram MyLibrary)
The include_directories Command tells the compiler where to find the header file used by main. C. Therefore, even if your project has 500 sub-directories, you can put all your source files into sub-directories without dependency issues. Cmake has helped you.
Return to zlib
Now, we need a "cmake" zlib. Start with a simple cmakelists.txt file:
PROJECT(ZLIB)
# source files for zlib
SET(ZLIB_SRCS
adler32.c gzio.c
inftrees.c uncompr.c
compress.c infblock.c
infutil.c zutil.c
crc32.c infcodes.c
deflate.c inffast.c
inflate.c trees.c
)
ADD_LIBRARY(zlib ${ZLIB_SRCS})
ADD_EXECUTABLE(example example.c)
TARGET_LINK_LIBRARIES(example zlib)
Now you can build it. However, some things should be remembered. First, zlib needs unistd. H files on some platforms. Therefore, we add the following test code:
INCLUDE (
${CMAKE_ROOT}/Modules/CheckIncludeFile.cmake)
CHECK_INCLUDE_FILE(
"unistd.h" HAVE_UNISTD_H)
IF(HAVE_UNISTD_H)
ADD_DEFINITION(-DHAVE_UNISTD_H)
ENDIF(HAVE_UNISTD_H)
In Windows, we must also do something extra for the shared library. Zlib must be compiled with the-dzlib_dll parameter to make the exported macro correct. Therefore, we add the following options:
OPTION(ZLIB_BUILD_SHARED
"Build ZLIB shared" ON)
IF(WIN32)
IF(ZLIB_BUILD_SHARED)
SET(ZLIB_DLL 1)
ENDIF(ZLIB_BUILD_SHARED)
ENDIF(WIN32)
IF(ZLIB_DLL)
ADD_DEFINITION(-DZLIB_DLL)
ENDIF(ZLIB_DLL)
Although this can also work, there is a better way. We can configure a header file to replace zlib_dll and have_unistd_h. We need to prepare an input file marked with cmake. The following is an example of zlibconfig. H, A zlib file:
#ifndef _zlibConfig_h
#define _zlibConfig_h
#cmakedefine ZLIB_DLL
#cmakedefine HAVE_UNISTD_H
#endif
Here, # cmakedefine VaR is replaced with # define var or # UNDEF var, depending on whether VaR is defined in cmake. Run the following cmake command to create the file zlibconfig. h.
CONFIGURE_FILE(
${ZLIB_SOURCE_DIR}/zlibDllConfig.h.in
${ZLIB_BINARY_DIR}/zlibDllConfig.h)
To infinity and further
Through this article, you can start to use cmake in your daily work. If your code is enough to be transplanted, you can now connect to your friend's AIX system to build your project. Cmake files are easier to understand than makefiles, so your friends can check your omissions.
However, this example involves a lot of work that cmake can do. In version 1.6, you can build platform-independent try_run and try_compile to conveniently test the system capabilities. Cmake native supports C and C ++, but limited support for building java files. Through some efforts, you can build anything from Python and Emacs scripts to latex documents. Using cmake as the test framework driver, you can implement independent regression testing on the platform. If you want to go further, you can use cmake's c API to write a plug-in for cmake to add your own commands.
Cmake is being actively used in several projects such as VTK and Itk. its benefits are enormous in traditional software development, however they become even more apparent, when portability is necessary. by using cmake for software development, your code will be significantly more "open", because it will build on a variety of platforms.
Note:
Cmake has been developed to version 2.6. This article was originally written in version 2003, which was only 1.6 at that time. Now we have made great improvements. Kde4 uses cmake for the entire project file.