Compile date and time using cmake

2019-01-25 05:33发布

问题:

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)?

回答1:

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")


回答2:

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.



回答3:

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)



回答4:

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"));


标签: c++ c cmake