How to run a command at compile time within Makefile generated by CMake?

折月煮酒 提交于 2019-11-28 04:08:15

You're leaving out some information, such as which platforms you need to run this on and if there are any additional tools you can use. If you can use Ruby, Perl, of Python, things become much simpler. I'll assume that you want to run on both Unix and Windows pqlatform and that there are no extra tools available.

If you want the output from the command in a preprocessor symbol, the easiest way is to generate a header file instead of fiddling around with command line parameters. Remember that CMake has a script-mode (-P) where it only processes script commands in the file, so you can do something like this:

CMakeLists.txt:

project(foo)  
cmake_minimum_required(VERSION 2.6)
add_executable(foo main.c custom.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})  
add_custom_command(OUTPUT custom.h 
    COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/custom.cmake)

The file "custom.h" is generated at compile time by the command "cmake -P custom.cmake". custom.cmake looks like this:

execute_process(COMMAND uname -a 
    OUTPUT_VARIABLE _output OUTPUT_STRIP_TRAILING_WHITESPACE)
file(WRITE custom.h "#define COMPILE_TIME_VALUE \"${_output}\"")

It executes the command (in this case "uname -a", you'll replace it with whatever command you wish), and puts the output in the variable _output, which it then writes to custom.h. Note that this will only work if the command outputs a single line. (If you need multiline output, you'll have to write a more complex custom.cmake, depending on how you want the multiline data into your program.)

The main program looks like this:

#include <stdio.h>
#include "custom.h"
int main()
{
  printf("COMPILE_TIME_VALUE: %s\n", COMPILE_TIME_VALUE);
  return 0;
}

If you actually want to to calculate compiler options at compile time, things become much trickier. For Bourne-shell generators you can just insert the command inside backticks. If you get mad while figuring out quoting, move all the logic of your command inside a shell-script so you only need to put mycommand.sh in your add_definitions():

if(UNIX)
  add_definitions(`${CMAKE_CURRENT_SOURCE_DIR}/custom-options.sh`)
endif()

For Windows batch file based generators things are much tricker, and I don't have a good solution. The problem is that the PRE_BUILD commands are not executed as part of the same batch file as the actual compiler invocation (study the BuildLog.htm for details), so my initial idea did not work (generating a custom.bat in a PRE_BUILD step and then do "call custom.bat" on it to get a variable set which can later be referenced in the compiler command line). If there is an equivalent of backticks in batch files, that would solve the problem.

Hope this gives some ideas and starting points.

(Now to the inevitable counter-question: what are you really trying to do?)

EDIT: I'm not sure why you don't want to let CMake be used to generate the header-file. Using ${CMAKE_COMMAND} will expand to the CMake used to generate the Makefiles/.vcproj-files, and since CMake doesn't really support portable Makefiles/.vcproj-files you will need to rerun CMake on the target machines.

CMake also has a bunch of utility commands (Run "cmake -E" for a list) for this explicit reason. You can for example do

add_custom_command(OUTPUT custom.h COMMAND ${CMAKE_COMMAND} -E copy file1.h file2.h)

to copy file1.h to file2.h.

Anyway, if you don't want to generate the header-files using CMake, you will either need to invoke separate .bat/.sh scripts to generate the header file, or do it using echo:

add_custom_command(OUTPUT custom.h COMMAND echo #define SOMETHING 1 > custom.h)

Adjust quoting as needed.

The solution above (using a separate CMake script file to generate a header file) seems very flexible but a bit complicated for what is being done in the example.

An alternative is to set a COMPILE_DEFINITIONS property on either an individual source file and or target, in which case the defined pre-processor variables will only be set for the source file or files in the target are compiled.

The COMPILE_DEFINITIONS properties have a different format from that used in the add_definitions command, and have the advantage that you don't need to worry about "-D" or "\D" syntax and they work cross-platform.

Example code

-- CMakeLists.txt --

execute_process(COMMAND svnversion
    WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
    OUTPUT_VARIABLE SVN_REV)
string(STRIP ${SVN_REV} SVN_REV)

execute_process(COMMAND date "+%Y-%m-%d-%H:%M"
    OUTPUT_VARIABLE BUILD_TIME)
string(STRIP ${BUILD_TIME} BUILD_TIME)

set_source_files_properties(./VersionInfo.cpp
    PROPERTIES COMPILE_DEFINITIONS SVN_REV=\"${SVN_REV}\";BUILD_TIME=\"${BUILD_TIME}\"")

The first line runs a shell command svnversion and puts the result in the variable SVN_REV. The string(STRIP ...) command is needed to remove trailing newline characters from the output.

Note this is assuming that the command being run is cross-platform. If not you may need to have alternatives for different platforms. For example I use the cygwin implementation of the Unix date command, and have:

if(WIN32)
 execute_process(COMMAND cmd /C win_date.bat
    OUTPUT_VARIABLE BUILD_TIME)
else(WIN32)
  execute_process(COMMAND date "+%Y-%m-%d-%H:%M"
    OUTPUT_VARIABLE BUILD_TIME)
endif(WIN32)
string(STRIP ${BUILD_TIME} BUILD_TIME)

for the date commands, where win_date.bat is a bat file that outputs the date in the desired format.

The two pre-processor variables are not available in the file ./VersionInfo.cpp but not set in any other files. You could then have

-- VersionInfo.cpp --

std::string version_build_time=BUILD_TIME;
std::string version_svn_rev=SVN_REV;

This seems to work nicely across platforms and minimizes the amount of platform-specific code.

I would use the following approach:

  1. Create an executable that prints the current date to stdout (CMake lacks this functionality)
  2. Add a target that is always considered out of date
  3. Let the target invoke another CMake script
  4. Let the invoked CMake script generate a header file

Example code for this:

--- CMakeLists.txt ---

PROJECT(Foo)
ADD_EXECUTABLE(RetreiveDateTime ${CMAKE_CURRENT_SOURCE_DIR}/datetime.cpp)
ADD_CUSTOM_TARGET(GenerateFooHeader
                  COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/Generate.cmake
                  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
                  DEPENDS RetreiveDateTime)
ADD_EXECUTABLE(Foo "test.cpp" "${CMAKE_CURRENT_BINARY_DIR}/generated.h")
ADD_DEPENDENCIES(Foo GenerateFooHeader)

--- Generate.cmake ---

EXECUTE_PROCESS(COMMAND ${CMAKE_BINARY_DIR}/RetreiveDateTime OUTPUT_VARIABLE DATETIMESTRING)
MESSAGE(STATUS "DATETIME=\"${DATETIMESTRING}\"")
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/generated.h.in ${CMAKE_CURRENT_BINARY_DIR}/generated.h @ONLY)

--- generate.h.in ---

#pragma once

#define DATETIMESTRING "@DATETIMESTRING@"

--- datetime.cpp ---

#include <iostream>
#include <ctime>
#include <cstring>

int main(int, char*[])
{
 time_t now;
 time(&now);
 tm * timeinfo = localtime(&now);

 char * asstring = asctime(timeinfo);
 asstring[strlen(asstring) - 1] = '\0'; // Remove trailing \n
 std::cout << asstring;
 return 0;
}

--- test.cpp ---

#include "generated.h"

#include <iostream>

int main(int, char*[])
{
 std::cout << DATETIMESTRING << std::endl;
 return 0;
}

This results in an header "generated.h" that is regenerated on every build. If you do not need DATETIME this example could be substantially simplified as CMake lacks this feature and a program must be built to simulate the functionality.

I would however think more than twice before doing this. Remember that the header-file will be regenerated every time make is run, making your target invalid at all times. You will never have a binary that is considered up-to-date.

Does this work?

d=`perl -e"print qq(Whatever calculated at runtime);"`; g++ prog.cpp -o prog -DDATETIME=$$d
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!