34

我想将一些选项传递给编译器。该选项必须在编译时计算 - 每次调用'make'时,而不是'cmake'时,所以execute_process命令不会削减它。(可以?)

例如,将日期传递给 g++ 编译器,如下所示:

g++ prog.cpp -o prog -DDATETIME="17:09:2009,14:25"

但是在编译时计算 DATETIME。

知道如何在 CMake 中做到这一点吗?

赏金编辑:

将接受一个最少黑客的解决方案。

请注意,我希望能够在编译时执行任意命令,而不仅仅是“日期”。

编辑2:

它必须在 Linux、Windows (VS)、Mingw、Cygwin 和 OS X 上工作。你不能假设 Ruby、Perl 或 Python,因为它们在 Windows 上是非标准的。你可以假设 BOOST,但我想这没有用。

目标是强制 cmake 生成 Makefile(在 Linux 的情况下),当 make 执行时,它将完成这项工作。

创建自定义 *.h 文件是可以的,但它必须由 make 由 Makefile(或其他操作系统上的等效文件)启动。*.h 的创建不必(也不应该)使用 cmake。

4

4 回答 4

46

您遗漏了一些信息,例如您需要在哪些平台上运行它以及是否可以使用任何其他工具。如果你可以使用 Ruby、Perl 或 Python,事情就会变得简单得多。我假设您想在 Unix 和 Windows pqlatform 上运行,并且没有可用的额外工具。

如果您希望命令的输出包含在预处理器符号中,最简单的方法是生成头文件,而不是摆弄命令行参数。请记住,CMake 有一个脚本模式 (-P),它只处理文件中的脚本命令,因此您可以执行以下操作:

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)

文件“custom.h”在编译时由命令“cmake -P custom.cmake”生成。custom.cmake 看起来像这样:

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

它执行命令(在本例中为“uname -a”,您将用您希望的任何命令替换它),并将输出放入变量 _output,然后将其写入 custom.h。请注意,这仅在命令输出单行时才有效。(如果您需要多行输出,则必须编写更复杂的 custom.cmake,具体取决于您希望多行数据进入程序的方式。)

主程序如下所示:

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

如果您真的想在编译时计算编译器选项,事情就会变得更加棘手。对于 Bourne-shell 生成器,您只需将命令插入反引号内。如果您在搞清楚引用时生气了,请将您的命令的所有逻辑移动到一个 shell 脚本中,这样您只需要放入mycommand.sh您的 add_definitions():

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

对于基于 Windows 批处理文件的生成器,事情要复杂得多,而且我没有一个好的解决方案。问题是这些PRE_BUILD命令没有作为与实际编译器调用相同的批处理文件的一部分执行(研究 BuildLog.htm 了解详细信息),所以我最初的想法没有奏效(PRE_BUILD一步生成 custom.bat 然后执行在其上“调用 custom.bat”以获取变量集,稍后可以在编译器命令行中引用该变量集)。如果批处理文件中有等效的反引号,那将解决问题。

希望这能提供一些想法和起点。

(现在到不可避免的反问:你真正 想做什么?)

编辑:我不确定你为什么不想让 CMake 用于生成头文件。使用 ${CMAKE_COMMAND} 将扩展到用于生成 Makefiles/.vcproj-files 的 CMake,并且由于 CMake 并不真正支持便携式 Makefiles/.vcproj-files,因此您需要在目标计算机上重新运行 CMake。

出于这个明确的原因,CMake 也有一堆实用程序命令(运行“cmake -E”以获得列表)。你可以例如做

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

将 file1.h 复制到 file2.h。

无论如何,如果您不想使用 CMake 生成头文件,您将需要调用单独的 .bat/.sh 脚本来生成头文件,或者使用 echo:

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

根据需要调整报价。

于 2009-09-23T21:47:02.147 回答
6

上面的解决方案(使用单独的 CMake 脚本文件来生成头文件)看起来非常灵活,但对于示例中所做的事情来说有点复杂。

另一种方法是在单个源文件和/或目标上设置 COMPILE_DEFINITIONS 属性,在这种情况下,将为源文件设置定义的预处理器变量,或者编译目标中的文件。

COMPILE_DEFINITIONS 属性的格式与 add_definitions 命令中使用的格式不同,其优点是您无需担心“-D”或“\D”语法,它们可以跨平台工作。

示例代码

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

第一行运行一个 shell 命令svnversion并将结果放入变量中SVN_REV。需要该string(STRIP ...)命令从输出中删除尾随换行符。

请注意,这是假设正在运行的命令是跨平台的。如果没有,您可能需要为不同平台提供替代方案。例如,我使用 Unixdate命令的 cygwin 实现,并且有:

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)

对于日期命令,其中win_date.bat是一个以所需格式输出日期的 bat 文件。

这两个预处理器变量在文件中不可用,./VersionInfo.cpp但未在任何其他文件中设置。然后你可以有

-- 版本信息.cpp --

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

这似乎可以跨平台很好地工作,并最大限度地减少特定于平台的代码量。

于 2012-10-10T18:00:12.100 回答
2

我会使用以下方法:

  1. 创建将当前日期打印到标准输出的可执行文件(CMake 缺少此功能)
  2. 添加一个始终被认为已过期的目标
  3. 让目标调用另一个 CMake 脚本
  4. 让调用的 CMake 脚本生成一个头文件

示例代码:

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

--- 生成.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)

--- 生成.h.in ---

#pragma once

#define DATETIMESTRING "@DATETIMESTRING@"

--- 日期时间.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;
}

--- 测试.cpp ---

#include "generated.h"

#include <iostream>

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

这会产生一个在每次构建时重新生成的头文件“generated.h”。如果您不需要 DATETIME,则此示例可以大大简化,因为 CMake 缺少此功能,并且必须构建一个程序来模拟该功能。

但是,在这样做之前,我会考虑两次以上。请记住,每次运行 make 时都会重新生成头文件,从而使您的目标始终无效。你永远不会有一个被认为是最新的二进制文件。

于 2009-09-29T15:09:10.543 回答
-1

这行得通吗?

d=`perl -e"print qq(Whatever calculated at runtime);"`; g++ prog.cpp -o prog -DDATETIME=$$d
于 2009-09-17T12:57:34.097 回答