I want to use cmake to set VERSION to a release version in case of release builds and to the compile time otherwise.
When using make for development builds, obtaining the compile time was easy via
-DVERSION=`date +%Y-%m-%d_%H:%M`
which could be used straight forward by c/c++ source code. Unfortunately, I haven't found out how the same can be achieved when using cmake.
string(TIMESTAMP VERSION "%Y-%m-%d %H:%M")
add_definitions(-DVERSION="${VERSION}")
sets VERSION to the time cmake was executed. How can I set VERSION to the compile time when using cmake (to avoid having to fiddle with __DATE__
and __TIME__
in the absence of a RELEASE flag)?
For relatively recent versions of CMake (>=2.8.11):
string(TIMESTAMP {output variable} [{format string}] [UTC])
(see http://www.cmake.org/cmake/help/v3.0/command/string.html). For example:
string(TIMESTAMP TODAY "%Y%m%d")
My cross-platform solution on the first run of CMake creates a file timestamp.cmake
in the binary directory and defines a target timestamp
which runs the generated file. The file timestamp.cmake
forms an ISO 8601 time stamp string using the STRING
CMake command and writes it to a file timestamp.h
with a #define _TIMEZ_
preprocessor directive prepended (defines with one leading underscore are okay; defines with two leading underscores should not be user-defined).
Include the following in your main CMake file.
# build time in UTC ISO 8601
FILE (WRITE ${CMAKE_BINARY_DIR}/timestamp.cmake "STRING(TIMESTAMP TIMEZ UTC)\n")
FILE (APPEND ${CMAKE_BINARY_DIR}/timestamp.cmake "FILE(WRITE timestamp.h \"#ifndef TIMESTAMP_H\\n\")\n")
FILE (APPEND ${CMAKE_BINARY_DIR}/timestamp.cmake "FILE(APPEND timestamp.h \"#define TIMESTAMP_H\\n\\n\")\n")
FILE (APPEND ${CMAKE_BINARY_DIR}/timestamp.cmake "FILE(APPEND timestamp.h \"#define _TIMEZ_ \\\"\${TIMEZ}\\\"\\n\\n\")\n")
FILE (APPEND ${CMAKE_BINARY_DIR}/timestamp.cmake "FILE(APPEND timestamp.h \"#endif // TIMESTAMP_H\\n\")\n")
ADD_CUSTOM_TARGET (
timestamp
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/timestamp.cmake
ADD_DEPENDENCIES ${CMAKE_BINARY_DIR}/timestamp.cmake)
Then use the ADD_DEPENDENCIES
CMake command to make your main target (probably the main executable file) dependent on the timestamp
target. It is always considered out-of-date by CMake, so it is being refreshed every time the main target rebuilds, refreshing the build time, as requested.
ADD_DEPENDENCIES (${CMAKE_BINARY_DIR}/${BINARY_NAME} timestamp)
You can specify multiple additional dependencies separated by a white space with this command, if you need to.
Then you can just #include "timestamp.h"
(assuming that the CMake binary dir is in the include path, which usually is. If not, that's simple: INCLUDE_DIRECTORIES (${CMAKE_BINARY_DIR})
), and use _TIMEZ_
whenever you want to have the build time stamp in ISO 8601 format (or, in fact, whatever you like: you can specify it yourself, see CMake documentation for STRING
command usage).
This could've been made simpler by directly (by hand) creating the file timestamp.cmake
and adding it to your code repository, but I've considered it as not being clean enough. It is a general drawback of CMake you cannot access the time stamp string forming procedure (the one used in the STRING
CMake command) at the stage where CMake's backend, whatever it is (for example, GNU make) runs so one has to use a separate CMake file and call it at that stage. This could've been done much much simpler and cleaner if you could call the CMake time stamp string forming procedure in the "CMake command mode" (cmake -E
type of invocation), for example, like this: cmake -E date [format] [UTC]
, but alas. I've filed a ticket in the CMake's Mantis bug tracker.
You can help that to happen by supporting my feature request posting some comments showing how much you need this on it.
Maybe you could use the compiler macros __DATE__
__TIME__
inside your code instead getting it from cmake. Worth mentioning that you will need to do clean/make to update these values (since GCC embeds it, if the object is already compiled it wont compile again, so no date/time change)
I end up with the following solution:
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
add_custom_target(
"linktimestamp"
ALL
COMMAND date +'%Y-%m-%d %H:%M:%S' > "linktimestamp.txt"
COMMAND objcopy --input binary --output elf64-x86-64 --binary-architecture i386:x86-64 --rename-section .data=.rodata,CONTENTS,ALLOC,LOAD,READONLY,DATA "linktimestamp.txt" "linktimestamp.o"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
COMMENT "link timestamp: ${LINK_TIMESTAMP}"
)
else()
add_custom_target(
"linktimestamp"
ALL
COMMAND date +'%Y-%m-%d %H:%M:%S' > "linktimestamp.txt"
COMMAND objcopy --input binary --output elf32-i386 --binary-architecture i386 --rename-section .data=.rodata,CONTENTS,ALLOC,LOAD,READONLY,DATA "linktimestamp.txt" "linktimestamp.o"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
COMMENT "link timestamp: ${LINK_TIMESTAMP}"
)
endif()
#add_dependencies(${PROJECT_NAME} "linktimestamp")
target_link_libraries(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/linktimestamp.o")
Binary target ${PROJECT_NAME}
linked every time with updated section.
Qt code to get this timestamp:
extern char _binary_linktimestamp_txt_start[];
//extern char _binary_linktimestamp_txt_end[];
extern char _binary_linktimestamp_txt_size[];
const auto text = QByteArray::fromRawData(_binary_linktimestamp_txt_start, reinterpret_cast< std::intptr_t >(_binary_linktimestamp_txt_size));
qDebug() << QDateTime::fromString(QString::fromUtf8(text), "yyyy-MM-dd HH:mm:ss\n"));