Difference between revisions of "CMake:How To Write Platform Checks"

From KitwarePublic
Jump to navigationJump to search
(Separate definition list entries by blank lines)
(Replace content with link to new CMake community wiki)
 
Line 1: Line 1:
If you want to write software which compiles and runs on different operating systems, you have to take care for the special properties of the different platforms. On different operating systems there are subtle differences, e.g. on FreeBSD you should not use malloc.h, while it is perfectly ok to use it on Linux. These differences are typically handled by providing a header file which contains a bunch of define-statements according to the platform properties, usually named config.h:
{{CMake/Template/Moved}}


<pre>
This page has moved [https://gitlab.kitware.com/cmake/community/wikis/doc/tutorials/How-To-Write-Platform-Checks here].
#define HAVE_MALLOC_H 1
/* #undef HAVE_SYS_MNTTAB_H 1 */
/* #undef HAVE_SYS_MNTENT_H 1 */
#define HAVE_SYS_MOUNT_H 1
</pre>
 
This header file is then included in the source files and handled appropriately:
 
foo.c:
<pre>
#include "config.h"
 
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#else
#include <stdlib.h>
#endif
 
void do_something()
{
  void *buf=malloc(1024);
...
}
</pre>
 
The contents of config.h depend on the platform where the sources are compiled, so there needs to be a way to generate this header file before the actual compilation process starts. If you are using autotools-based software, you probably know the <tt>./configure</tt> step, which has to be executed before starting <tt>make</tt>. The <tt>./configure</tt> script does some system introspection and generates from the gathered information the <tt>config.h</tt> header file.
CMake is able to do the same, and I'll show you how to do it.
 
Additionally to the builtin commands, cmake offers more commands implemented by cmake script files, called modules. These files are located in the cmake module directory, on UNIX systems is this by default <tt>/usr/local/share/CMake/Modules </tt>.
 
To use commands from these modules, they have to be included in the CMakeLists.txt. CMake comes with several modules for checking the system, they all follow the same style, as an example here CHECK_INCLUDE_FILES:
 
<pre>
INCLUDE (CheckIncludeFiles)
# usage: CHECK_INCLUDE_FILES (<header> <RESULT_VARIABLE> )
 
CHECK_INCLUDE_FILES (malloc.h HAVE_MALLOC_H)
CHECK_INCLUDE_FILES ("sys/param.h;sys/mount.h" HAVE_SYS_MOUNT_H)
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
</pre>
 
The CMake module CheckIncludeFiles offers the command CHECK_INCLUDE_FILES(). The first argument to this command is the header you want to check for. The second argument is the variable which will contain the result. If the given header was found, it is set to 1, otherwise it is empty.
If another header is required to use the header you are looking for, you have to list the header files separated by semicolons, as you can see above. To see what CHECK_INCLUDE_FILES() exactly does, have a look at the implementation: <tt>/usr/local/share/CMake/Modules/CheckIncludeFiles.cmake</tt> .
There you'll see that it tries to compile a simple source file which includes the specified header files. The results of the tests are stored in the file CMakeCache.txt, so if you want to check later whether the test succeeded or not look in the CMakeCache.txt file :
<pre>
//Have include HAVE_MALLOC_H
HAVE_MALLOC_H:INTERNAL=1
</pre>
As long as the result is in the cache, the test won't be executed again. If you want it to be executed again, either delete the file CMakeCache.txt, then all tests will be executed again, or just remove the entries for the variables you want to have tested again. This can save some time. If the test failed, and you want to find out why, open <tt>CMakeFiles/CMakeError.log</tt> and search for the header name (or function, etc.) you were testing for. There you should see the code which failed to compile or link, the compile command and the error message.
Together with the implementation of the test in CheckIncludeFiles.cmake you should be able to figure out what went wrong.
 
Ok, now that we have tested whether e.g. malloc.h exists and have the result in the cmake variable HAVE_MALLOC_H, we still have to create a header <tt>config.h</tt>. To do this, we use the cmake command <tt>CONFIGURE_FILE()</tt>, as you have seen above. This copies a source file to a target file and edits it while doing so, see the man page for details.
So we write a source file named <tt>config.h.in</tt>, but you could give it any name you want (with autotools it's also usually named <tt>config.h.in</tt>):
<pre>
#cmakedefine HAVE_MALLOC_H 1
#cmakedefine HAVE_SYS_MOUNT_H
</pre>
 
Now when cmake runs, it will replace <tt>#cmakedefine</tt>. If HAVE_MALLOC_H and HAVE_SYS_MOUNT_H are true, it will produce a <tt>config.h</tt>:
<pre>
#define HAVE_MALLOC_H 1
#define HAVE_SYS_MOUNT_H
</pre>
If both are false, it will produce:
<pre>
/* #undef HAVE_MALLOC_H */
/* #undef HAVE_SYS_MOUNT_H */
</pre>
 
By including this header into your source files you can check these properties using <tt>#ifdef</tt>.
You can insert such checks in any of the CMakeLists.txt in your project, not only in the top-level CMakeLists.txt. If you have several ''configured'' headers, you shouldn't name them all <tt>config.h</tt>, this might lead to problems with the include path. Better give them names like <tt>config.h</tt>, <tt>config-foo.h</tt> for the configure header in the subdirectory <tt>foo/</tt> and <tt>config-bar.h</tt> in the <tt>bar/</tt> subdirectory, etc.
 
The other commands coming with cmake to do system checks follow this style, so we can handle them much shorter now.
 
----
 
;Module: INCLUDE (CheckIncludeFiles)
 
;Usage: CHECK_INCLUDE_FILES(headers variable)
 
;Example: CHECK_INCLUDE_FILES(strings.h HAVE_STRINGS_H)
 
As just discussed at length, this can be used to check for the existence of a header.
 
----
 
;Module: INCLUDE (CheckFunctionExists)
 
;Usage: CHECK_FUNCTION_EXISTS(function variable)
 
;Example: CHECK_FUNCTION_EXISTS(madvise HAVE_MADVISE)
 
Checks whether the given function exists. This is done by linking a small program, which may not result in undefined references.
 
----
 
;Module: INCLUDE (CheckSymbolExists)
 
;Usage: CHECK_SYMBOL_EXISTS(symbol headers variable)
 
;Example: CHECK_SYMBOL_EXISTS((LC_MESSAGES "locale.h" HAVE_LC_MESSAGES)
 
Checks whether the given symbol exists if the specified headers are included.  "Symbol" is defined as something that is either a preprocessor macro, or whose address can be taken.  Notably, this '''does not''' include values of a C enumeration.
 
----
 
;Module: INCLUDE (CheckLibraryExists)
 
;Usage: CHECK_LIBRARY_EXISTS(library function location variable)
 
;Example: CHECK_LIBRARY_EXISTS(volmgt volmgt_running "" HAVE_VOLMGT)
 
Checks whether the given library exists and contains the given function. This is done by linking a small program which uses the function and links to the library. In the location parameter an additional link directory (-Ldir) can be given if required.
 
----
 
;Module: INCLUDE (CheckTypeSize)
 
;Usage: SET(CMAKE_EXTRA_INCLUDE_FILES header)<br> CHECK_TYPE_SIZE(type variable)<br> SET(CMAKE_EXTRA_INCLUDE_FILES)
 
;Example: SET(CMAKE_EXTRA_INCLUDE_FILES sys/socket.h)<br> CHECK_TYPE_SIZE("struct ucred" STRUCT_UCRED)<br> SET(CMAKE_EXTRA_INCLUDE_FILES)
 
Checks whether the specified type exists and returns the size of the type. In the variable the size of the type will be returned, additionally a variable HAVE_STRUCT_UCRED will be set to true if the type exists. Please not that you have to set CMAKE_EXTRA_INCLUDE_FILES to the required headers for this type, and you should reset it after calling CHECK_TYPE_SIZE. If you are not really interested in the size of the type, but only whether it exists or not, you can also use STRUCT_UCRED directly, if the type doesn't exist, it will be empty and so also evaluate to FALSE (as will HAVE_STRUCT_UCRED).
 
----
 
;Module: INCLUDE (CheckPrototypeExists)
 
;Usage: CHECK_PROTOTYPE_EXISTS(function headers variable)
 
;Example: CHECK_PROTOTYPE_EXISTS(mkstemps "stdlib.h;unistd.h" HAVE_MKSTEMPS_PROTO)
 
Checks whether the ''headers'' provide the declaration for the given ''function'', i.e. it does not check whether using ''function'' will lead to undefined references.
 
----
 
;Module: INCLUDE (CheckCXXSourceCompiles)<br> INCLUDE (CheckCSourceCompiles)
 
;Usage: CHECK_CXX_SOURCE_COMPILES(source variable)<br> CHECK_C_SOURCE_COMPILES(source variable)
 
Checks whether the code given in ''source'' will compile and link. You can set CMAKE_REQUIRED_LIBRARIES, CMAKE_REQUIRED_FLAGS and CMAKE_REQUIRED_INCLUDES accordingly if additional libraries or compiler flags are required.
 
{{CMake/Template/Footer}}
 
[[Category:Tutorials]]

Latest revision as of 15:40, 30 April 2018


The CMake community Wiki has moved to the Kitware GitLab Instance.

This page has moved here.