One of our CMake projects includes a GENERATED
config file that needs to include the project version converted into hexadecimal form. With CMake 3.13+, easy-peasy:
math(EXPR PROJECT_VERSION_HEX
"(${PROJECT_VERSION_MAJOR} << 16) + \
(${PROJECT_VERSION_MINOR} << 8) + \
(${PROJECT_VERSION_PATCH})" OUTPUT_FORMAT HEXADECIMAL )
But we also need to support CMake versions all the way down to 3.2 (I knoooow), where that won’t work. For the longest time we were just hardcoding “0x0m0n0p
” in a variable that we tried (and failed) to remember to update whenever ${PROJECT_VERSION}
changed.
Since that was tedious even when we did remember (and we usually didn’t), I eventually hit on a workaround that I wanted to share in case it’s useful to others.
The trick is to leverage the CMake try_run()
functionality, which can be used to execute a C or C++ program during the build generation phase — much like execute_process()
, but for source code instead. (You could certainly also do this with a shell command and execute_process()
.)
I chose to write my hex_version.cpp
generically, in case it ever has other uses. So, the inputs are passed as COMPILE_DEFINITIONS
on the try_run()
command line, and it could theoretically be used multiple times with different arguments. Source:
#include <iostream>
#include <ios>
// The following values must be defined at compile time:
// VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH
#if !defined(VERSION_MAJOR) || !defined(VERSION_MINOR) || !defined(VERSION_PATCH)
#pragma error "Define version components on compiler command line!"
#endif
int main()
{
int hex_version = (VERSION_MAJOR << 16) +
(VERSION_MINOR << 8) +
(VERSION_PATCH);
std::cout << std::hex << "0x" << hex_version;
}
Then, when older CMake versions process CMakeLists.txt
, that’s called to retrieve the PROJECT_VERSION_HEX
value instead of using math()
:
# Compile and run a C++ program to generate the hex version
set(HEX_COMPILE_DEFINITIONS
-DVERSION_MAJOR=${PROJECT_VERSION_MAJOR}
-DVERSION_MINOR=${PROJECT_VERSION_MINOR}
-DVERSION_PATCH=${PROJECT_VERSION_PATCH}
)
try_run(HEX_VERSION_RUN HEX_VERSION_BUILD
${CMAKE_CURRENT_BINARY_DIR}/hex_version
${PROJECT_SOURCE_DIR}/src/hex_version.cpp
COMPILE_DEFINITIONS ${HEX_COMPILE_DEFINITIONS}
RUN_OUTPUT_VARIABLE HEX_VERSION_OUTPUT
)
if (NOT HEX_VERSION_BUILD)
message(ERROR "Failed to compile hex-version utility!")
elseif(HEX_VERSION_RUN STREQUAL FAILED_TO_RUN)
message(ERROR "Could not execute hex-version utility!")
else()
set(PROJECT_VERSION_HEX ${HEX_VERSION_OUTPUT})
endif()