Suppose I have two libraries ABC, XYZ and two variables ABC_FOUND XYZ_FOUND in CMakeLists.txt. In a config.h.in file, I have the following lines
#define __USE_ABC__ #ABC_FOUND#
#define __USE_XYZ__ #XYZ_FOUND#
If I do configure_file(config.h.in config.h), the output config.h file will be changed to
#define __USE_ABC__ ON
#define __USE_XYZ__ OFF
My question is how can I convert the ON/OFF options into 1/0, in other word, my desired output config.h should be
#define __USE_ABC__ 1
#define __USE_XYZ__ 0
Define ABC_FOUND to either 0 or 1 with
#cmakedefine01 #ABC_FOUND#
Use an intermediate variable in either environment to change it to USE_ABC.
Likely, you should use #cmakedefine instead of hard defines (see examples e.g. here)
But please remember that in the case of #cmakedefine you get a configuration header where "enabled features" (in terms of CMake if() command) are #define-d to a supplied value, while "disabled features" are #undef-ined. So you should test them not with #if <something> but rather with #ifdef <something>.
Related
Background
I'm running through the CMake tutorial and found the solution for TODO #10, i.e:
TutorialConfig.h.in
#define Tutorial_VERSION_MAJOR #Tutorial_VERSION_MAJOR#
#define Tutorial_VERSION_MINOR #Tutorial_VERSION_MINOR#
seems to conflict with the manual for the configure_file command
configure_file(
[NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |
FILE_PERMISSIONS ...]
[COPYONLY] [ESCAPE_QUOTES] [#ONLY]
[NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
Copies an file to an file and substitutes variable
values referenced as #VAR# or ${VAR} in the input file content. Each
variable reference will be replaced with the current value of the
variable, or the empty string if the variable is not defined.
Furthermore, input lines of the form
#cmakedefine VAR ...
will be replaced with either
#define VAR ...
or
/* #undef VAR */
depending on whether VAR is set in CMake to any value not considered a
false constant by the if() command. The "..." content on the line
after the variable name, if any, is processed as above.
in that the #cmakedefine preprocessor directive should be used in the input file an turned into a #define directive in the output file rather than what is done in the solution (using the #define directive in the input file). I tried using the #cmakedefine directive before looking at the solution and it resulted in an undefined _VERSION_MINOR i.e. here is the generated header file:
TutorialConfig.h
#define Tutorial_VERSION_MAJOR 1
/* #undef Tutorial_VERSION_MINOR */
whereas when I use the #define directive within the input file (TutorialConfig.h.in), both Tutorial_VERSION_MAJOR and Tutorial_VERSION_MINOR are defined in the generated header file. i.e.
TutorialConfig.h
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 0
So what is the difference here and why would one properly grab the defined Tutorial_VERSION_MINOR and another not? Is there specific documentation regarding the #cmakedefine and #cmakedefine01 directives other than what is shown in the configure_file documentation?
Based on a comment received highlighting the following text from the configure_file documentation:
"[...] depending on whether VAR is set in CMake to any value not considered a false constant by the if() command".
I'm wondering if the only difference between using the #cmakedefine and #define directives is just that the #cmakedefine one goes through an if() statement and thus any value of 0 would result in the output header file showing an undefined variable rather than a string of 0 as intended.
cmake version 3.22.1
Minimal Working Example
A full working minimal example below or alternatively can be found here:
tutorial.cxx
#include <cstdlib>
#include <iostream>
#include <string>
#include "TutorialConfig.h"
int main()
{
std::cout << "Version: " << Tutorial_VERSION_MAJOR << "." << Tutorial_VERSION_MINOR << std::endl;
return 0;
}
TutorialConfig.h.in
#define Tutorial_VERSION_MAJOR #Tutorial_VERSION_MAJOR#
#define Tutorial_VERSION_MINOR #Tutorial_VERSION_MINOR#
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(Tutorial
VERSION 1.0)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
configure_file(TutorialConfig.h.in
TutorialConfig.h
#ONLY)
add_executable(Tutorial
tutorial.cxx)
target_include_directories(Tutorial
PUBLIC
${PROJECT_BINARY_DIR})
Steps to reproduce this:
from the directory containing the above 3 files (e.g. Step1) make a build directory with mkdir build
cd build
cmake ..
make
./Tutorial and note it prints Version: 1.0 as expected
cd ..
change the #define directives in TutorialConfig.h.in to #cmakedefine, i.e.
TutorialConfig.h.in:
#cmakedefine Tutorial_VERSION_MAJOR #Tutorial_VERSION_MAJOR#
#cmakedefine Tutorial_VERSION_MINOR #Tutorial_VERSION_MINOR#
repeat steps 2-4 and note the compile error:
Step1/tutorial.cxx:8:64: error: ‘Tutorial_VERSION_MINOR’ was not declared in this scope; did
you mean ‘Tutorial_VERSION_MAJOR’?
8 | std::cout << "Version: " << Tutorial_VERSION_MAJOR << "." << Tutorial_VERSION_MINOR << std::endl;
| ^~~~~~~~~~~~~~~~~~~~~~
| Tutorial_VERSION_MAJOR
As Friedrich already mentioned in the comments, CMake is doing exactly what it's documented to do.
You wrote:
project(Tutorial
VERSION 1.0)
, so in the following
#cmakedefine Tutorial_VERSION_MINOR #Tutorial_VERSION_MINOR#
, for the first "Tutorial_VERSION_MINOR", CMake looks at what the value of that CMake variable is, and sees that it is a value considered false by the if() command. Yes- Tutorial_VERSION_MINOR is a CMake variable. See the <PROJECT_NAME>_VERSION_MINOR docs. The specific docs that say 0 is considered as a falsy value in the if() command docs is under the "basic expressions" section:
False if the constant is 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in the suffix -NOTFOUND. Named boolean constants are case-insensitive.
And as the documentation you linked states, #cmakedefine VAR ... is replaced with /* #undef VAR */ when "VAR is set in CMake to any value considered a false constant by the if() command."
seems to conflict with the manual for the configure_file command [...] in that the #cmakedefine preprocessor directive should be used in the input file an turned into a #define directive in the output file rather than what is done in the solution (using the #define directive in the input file)
uhh no. The tutorial just uses the approach that will actually work here. This kind of scenario where you want to create a macro definition with the same name as a CMake variable where the CMake variable might be a CMake-falsy value is the weakness scenario of #cmakedefine that one can avoid by instead using #define and configure_file's variable substitution facilities- namely #VAR# and ${VAR}.
There's nothing special about #define with respect to configure_file. It just treats it like any other text. #cmakedefine, #cmakedefine01 and variable substitution with #VAR# and ${VAR} are the only things configure_file does special things for.
What is the easiest way to get the value of a C/C++ macro into a CMake variable?
Given I check for a library libfoo with the header foo.h. I know foo.h contains the macro #define FOO_VERSION_MAJOR <version> where version is an integer or string value. To extract the major version of the found library, I want to use the value from this macro.
As a bonus, if the macro is not found, this could indicate a version older then a specific version introducing the version macro.
I'd go with file(READ ...) to read the header followed by string(REGEX ...) to extract desired define.
Example code:
file(READ "foo.h" header)
string(REGEX MATCH "#define FOO_MAJOR_VERSION [0-9]+" macrodef "${header}")
string(REGEX MATCH "[0-9]+" FooMajorVersion "${macrodef}")
With try_compile and the right pragma it is possible to output the value of a pre-processor macro during compile time. CMake can parse the output to get the desired value.
CMake snippet:
try_compile(result "${CMAKE_BINARY_DIR}"
SOURCES "${CMAKE_SOURCE_DIR}/foo-version.cpp"
OUTPUT_VARIABLE fooversion)
string(REGEX MATCH ": [0-9]+" fooversionshort "${fooversion}")
string(REGEX MATCH "[0-9]+" FooMajorVersion "${fooversionshort}")
foo-version.cpp:
#include "foo.h"
/* definition to expand macro then apply to pragma message */
#define VALUE_TO_STRING(x) #x
#define VALUE(x) VALUE_TO_STRING(x)
#pragma message(VALUE(FOO_MAJOR_VERSION))
int main()
{
return 0;
}
Good:
Actual value from the variable, which might be calculated.
Bad:
Output of macros is only support by some newer compilers.
Parsing of output might break for untested compilers, as the format changes from compiler version to compiler version.
Kind of complicated code, verbose code which is difficult to read.
The macro expansion can be extracted by using the C preprocessor.
I used this method to extract specific typedef's without needing to know the exact location of the define in the file hierarchy.
Let say that we have this macro defined somewhere in foo.h
#define FOO_VERSION_MAJOR 666
You need to create a helper file helper.c with this content
#include "foo.h"
int __READ_FOO_MAJOR__ = FOO_VERSION_MAJOR ;
Note that I used a specific pattern __READ_FOO_MAJOR__ that I will use later as the pattern for a grep command
And from CMakeLists.txt you have to call the C (C++, etc..) preprocessor and filter its output like this
cmake_minimum_required(VERSION 3.0)
execute_process(
COMMAND bash "-c" "${CMAKE_C_COMPILER} -E ${CMAKE_CURRENT_SOURCE_DIR}/helper.cpp | grep __READ_FOO_MAJOR__ | awk '{ print $4}'"
OUTPUT_VARIABLE FOO_VERSION_MAJOR )
message("From CMake: FOO_VERSION_MAJOR=${FOO_VERSION_MAJOR}")
Note that awk '{ print $4}' extract the 4th word on the selected line.
When running cmake we get this result
From CMake: FOO_VERSION_MAJOR=666
The short shel pipeline used is built with Unix system V base commands and should run everywhere.
I have the following template
config.c.in
#define QPID_DISPATCH_VERSION "${QPID_DISPATCH_VERSION}"
#define QPID_DISPATCH_LIB "$<TARGET_FILE_NAME:qpid-dispatch>"
#cmakedefine01 USE_MEMORY_POOL
And I wish to obtain the following, by expanding the variable, #cmakedefine, and generator expression.
config.c
#define QPID_DISPATCH_VERSION "1.6.0-SNAPSHOT"
#define QPID_DISPATCH_LIB "libqpid-dispatch.so"
#define USE_MEMORY_POOL 1
The problem is, there does not seem to be a CMake function which can expand all three.
What I can do is
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h.tmp)
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/config.h INPUT ${CMAKE_CURRENT_BINARY_DIR}/config.h.tmp)
Is there a way to accomplish this without creating a temporary file? Cleaning up the file is tricky, because file GENERATE does not run immediately.
Based on #Tsyvarev's comment, I now have
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" CONFIG_H_IN)
string(CONFIGURE "${CONFIG_H_IN}" CONFIG_H_TMP)
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/config.h" CONTENT "${CONFIG_H_TMP}")
I am looking through the source-code for ZeroMQ, which I want to build from source. Inside I found platform.hpp.in, which contains:
...
#cmakedefine ZMQ_HAVE_SO_PEERCRED
#cmakedefine ZMQ_HAVE_LOCAL_PEERCRED
#cmakedefine ZMQ_HAVE_SOCK_CLOEXEC
#cmakedefine ZMQ_HAVE_SO_KEEPALIVE
#cmakedefine ZMQ_HAVE_TCP_KEEPCNT
...
I assume these cmakedefine macros are used as templates to generate a header file, but how exactly do they work in CMake? How can I determine what are valid values? How are values set by the user when building the project?
It's part of a file that's processed by CMake's configure_file command. When configure_file is called for the file, #cmakedefine FOO is replaced by:
#define FOO - if the CMake variable FOO is set to ON or TRUE.
/* #undef FOO */ - otherwise.
And that is one way to pass values from CMake into C or C++ source code: The result of configure_file() is a C/C++ header file, which is included by the code you want to respect the CMake variable values.
I have tested a bit of assembler on Linux using the AT&T syntax. One thing that struck me was that the book I was reading was written from a 32-bit standpoint. Thus, all sizes would have to be changed to the correct 64-bit versions for me. Or I could (which I did) assemble the code using the --32 flag for as and the -melf_i386 flag for ld when linking. I have also adapted some of the code and to run on Windows under Cygwin.
But that got me thinking. Is there a way to do ifdef like checks in assembler to do one thing if I'm on Windows and another under Linux and also handle 32 vs 64 bit that way? For example to have a .globl _start under Linux and a .globl _main under Windows.
Or is this handled by checking before assembling and having different source files to assemble based on the result of the checks?
I.e. foo_linux.s and foo_windows.s
If so, how do you overcome that fact that you will not know which .s files you will use, and thus have to include, when you are creating your program?
For example, say that we have a socket_linux.s and a socket_windows.s. They both present an identical interface but do the OS specific work associated to sockets. But when I work with the sockets in my program I will not know if I need the Windows or Linux version included. So I would be kinda screwed :)
So how is this handled in Assembler? In C++ for example I could include my socket.h and socket.cpp and wrap all the Linux and Windows specific code in #ifdef statements.
If you use GCC to compile your files and name them .S (with uppercase S) or .sx, it will pass them through the preprocessor before invoking the assembler.
From the docs:
file.s
Assembler code.
file.S
file.sx
Assembler code which must be preprocessed.
You can add -v to the command line to see how the various sub-processes are invoked.
in MASM (.asm), you can use ifdef, ifndef and the likes, as:
ifdef X64
endif
When writing for different platforms you can define some macro for loading target specific files:
FILE target.h
#if defined(__arm__)
#define target "arm"
#elif defined(__x86_64__)
#if defined(_WIN64)
#define target "win64"
#else
#define target "linux64" // all non-Win share the same calling convention
#endif
#else
// 32bit defs
#endif
Then you can include target specific files with the macro, two string literals successively get one single literal:
#include "target.h"
#include "target_specific_code_" target ".h"
It includes one of these files:
target_specific_code_arm.h
target_specific_code_win64.h
target_specific_code_linux64.h
...
EDIT:
Like this, you can also define target specific assembler instructions for later use in inline assembly:
#ifdef ...
#define ASM_PP_LOAD_WORD "movi "
#else
#define ASM_PP_LOAD_WORD "mov "
#endif
or as macro
#ifdef ...
// when using intel assembler there is a different
// order of parameters
#define ASM_PP_LOAD_WORD(a, b) "movi " #b ", " #a
#else
#define ASM_PP_LOAD_WORD(a, b) "mov " #a ", " #b
#endif