6

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.

5
  • 1
    You might consider going the other direction: put the definition in CMake, and have CMake generate the .h with the correct replacement. See this answer for details. Commented Apr 7, 2019 at 23:10
  • The problem is, the library is 3rd party, so I don't have any control where informations are placed. Commented Apr 7, 2019 at 23:13
  • Ah, makes sense! Commented Apr 8, 2019 at 0:04
  • So your software will work with every version of libfoo? Commented Apr 8, 2019 at 6:48
  • @Bernhard: Once I know the version, I can also warn about unsupported old versions or emit a deprecation warning for supported versions that will lose support in future releases of my software. Commented Apr 8, 2019 at 7:04

3 Answers 3

4

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}")
Sign up to request clarification or add additional context in comments.

Comments

4

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.

5 Comments

You can use CMakeConfigurableFile.in to generate the file from a CMake string. To work around the compile-time macro output limitations you might actually generate a full working program with std::cout << FOO_MAJOR_VERSION << std::endl; in main() , run the program and capture stdout. Benefit of your approach is that you don't need the link step (which costs additional time and might fail if foo.h contains more definitions for example).
@user1556435 your approach won't work if cross-compilation, but the original approach with try_compile() will.
@usr1234567 Is there a way to make try_compile not verbose?
@morfis: I don't think so. But you can ask a question on its own, then you can describe what you actually want. Maybe someone knows a trick.
I would suggest to add to try_compile argument CMAKE_FLAGS "-D CMAKE_COLOR_DIAGNOSTICS=OFF", as without it cmake seems to add -fcolor-diagnostics what makes warning: being surrounded by some color ASCII characters, and causing regex match to fail.
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.