如何在编译时在 CMake 生成的 Makefile 中运行命令?

发布于 2024-08-05 21:20:17 字数 651 浏览 5 评论 0原文

我想将一些选项传递给编译器。 该选项必须在编译时计算 - 每次调用“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 文件(或其他操作系统上的等效文件)启动。 *.h 的创建不必(也不应该)使用 cmake。

I would like to pass some options to a compiler.
The option would have to be calculated at compile time - everytime when 'make' is invoked, not when 'cmake', so execute_process command does not cut it. (does it?)

For instance passing a date to a g++ compiler like that:

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

But with DATETIME calculated at compile time.

Any idea how to do it in CMake?

Bounty edit:

A least hackish solution will be accepted.

Please note that I want to be able to exectue an arbitrary command at compile time, not only 'date'.

Edit 2:

It have to work on Linux, Windows (VS), Mingw, Cygwin and OS X.
You can't assume Ruby, Perl or Python as they are non standard on Windows.
You can assume BOOST, but I guess it's no use.

The goal is to force cmake to generate Makefile (in case of Linux) that when make is executed, will do the job.

Creating custom *.h file is ok, but it has to be initiated by a Makefile (or equivalent on other OS) by make. The creation of *.h doesn't have to (and shouldn't have to) use cmake.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

执着的年纪 2024-08-12 21:20:17

您遗漏了一些信息,例如您需要哪些平台
运行它以及是否有任何其他工具可以使用。如果
你可以使用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 文件的 CMake,并且由于 CMake 并不真正支持可移植的 Makefiles/.vcproj 文件,因此您需要在目标计算机上重新运行 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)

根据需要调整引用来生成头文件。

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.

我ぃ本無心為│何有愛 2024-08-12 21:20:17

上面的解决方案(使用单独的 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 ...) 命令从输出中删除尾随换行符。

请注意,这是假设正在运行的命令是跨平台的。如果没有,您可能需要针对不同平台的替代方案。例如,我使用 Unix date 命令的 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 中不可用,但未在任何其他文件中设置。然后您可以拥有

-- VersionInfo.cpp --

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

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

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.

洋洋洒洒 2024-08-12 21:20:17

我将使用以下方法:

  1. 创建一个将当前日期打印到 stdout 的可执行文件(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)

---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;
}

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

然而,在这样做之前我会三思而后行。请记住,每次运行 make 时都会重新生成头文件,从而使您的目标在所有次都无效。您将永远拥有被认为是最新的二进制文件。

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.

把梦留给海 2024-08-12 21:20:17

这有效吗?

d=`perl -e"print qq(Whatever calculated at runtime);"`; g++ prog.cpp -o prog -DDATETIME=$d

Does this work?

d=`perl -e"print qq(Whatever calculated at runtime);"`; g++ prog.cpp -o prog -DDATETIME=$d
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文