Как запустить команду во время компиляции в Makefile, сгенерированном CMake?
Я хотел бы передать некоторые параметры компилятору.
Опция должна быть рассчитана во время компиляции - каждый раз, когда вызывается "make", а не когда "cmake", поэтому команда execute_process не режет. (это?)
Например, передача даты компилятору g++ следующим образом:
g++ prog.cpp -o prog -DDATETIME="17:09:2009,14:25"
Но с DATETIME, вычисленным во время компиляции.
Любая идея, как это сделать в CMake?
Редактирование Bounty:
Будет принято наименее хакерское решение.
Обратите внимание, что я хочу иметь возможность вызывать произвольную команду во время компиляции, а не только "date".
Изменить 2:
Он должен работать на Linux, Windows (VS), Mingw, Cygwin и OS X.
Вы не можете предполагать Ruby, Perl или Python, поскольку они не являются стандартными для Windows.
Вы можете предположить BOOST, но я думаю, что это бесполезно.
Цель состоит в том, чтобы заставить cmake генерировать Makefile (в случае Linux), что при выполнении make выполнит задание.
Создание пользовательского *.h файла в порядке, но оно должно быть инициировано Makefile (или эквивалентом в другой ОС) make. Создание *.h не обязательно (и не должно) использовать cmake.
Ответы
Ответ 1
Вы оставляете информацию, например, какие платформы вам нужны
для запуска этого и если есть дополнительные инструменты, которые вы можете использовать. Если
вы можете использовать Ruby, Perl, Python, все становится намного проще. Больной
предположим, что вы хотите работать как на платформе Unix, так и на Windows pqlatform и
что нет доступных дополнительных инструментов.
Если вы хотите, чтобы вывод команды из символа препроцессора,
Самый простой способ - создать заголовочный файл вместо того, чтобы возиться вокруг
с параметрами командной строки. Помните, что CMake имеет script -mode
(-P), где он обрабатывает только команды script в файле, поэтому вы можете
сделайте что-нибудь вроде этого:
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 вы можете просто
вставьте команду в backticks. Если вы разозлитесь, когда выясните
цитируя, переместите всю логику вашей команды внутри оболочки - script, поэтому
вам нужно только поставить mycommand.sh
в ваш add_definitions():
if(UNIX)
add_definitions(`${CMAKE_CURRENT_SOURCE_DIR}/custom-options.sh`)
endif()
Для генераторов на основе пакетного файла Windows все намного сложнее, и я
не имеют хорошего решения. Проблема в том, что команды PRE_BUILD
не выполняются как часть того же командного файла, что и фактический компилятор
вызов (изучите BuildLog.htm для деталей), поэтому моя первоначальная идея
не работает (генерирует custom.bat в шаге PRE_BUILD
, а затем делает
"call custom.bat" на нем, чтобы получить набор переменных, который позже может быть
указанную в командной строке компилятора). Если есть эквивалент
backticks в пакетных файлах, что могло бы решить проблему.
Надеюсь, что это дает некоторые идеи и отправные точки.
(Теперь к неизбежному встречному вопросу: что вы действительно
пытаясь сделать?)
EDIT: Я не уверен, почему вы не хотите, чтобы CMake использовался для создания файла заголовка. Использование ${CMAKE_COMMAND} будет расширяться до CMake, используемого для генерации файлов Makefiles/.vcproj, и поскольку CMake действительно не поддерживает переносные файлы Makefile/.vcproj, вам необходимо перезапустить CMake на целевых компьютерах.
У CMake также есть куча служебных команд (запустите "cmake -E" для списка) по этой явной причине. Вы можете, например, сделать
add_custom_command(OUTPUT custom.h COMMAND ${CMAKE_COMMAND} -E copy file1.h file2.h)
чтобы скопировать файл1.h в файл2.h.
В любом случае, если вы не хотите генерировать заголовочные файлы с помощью CMake, вам нужно будет вызывать отдельные сценарии .bat/.sh для генерации файла заголовка или сделать это с помощью echo:
add_custom_command(OUTPUT custom.h COMMAND echo #define SOMETHING 1 > custom.h)
При необходимости отредактируйте цитаты.
Ответ 2
Решение выше (с использованием отдельного файла CMake script для создания файла заголовка) кажется очень гибким, но немного сложным для того, что делается в примере.
Альтернативой является установка свойства 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}\"")
Первая строка запускает команду оболочки svnversion
и помещает результат в переменную SVN_REV
. Команда string(STRIP ...)
необходима для удаления завершающих символов новой строки с выхода.
Обратите внимание, что предполагается, что выполняемая команда кросс-платформенная. Если нет, вам может потребоваться альтернатива для разных платформ. Например, я использую реализацию cygwin для команды Unix date
и имеет:
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;
Это, по-видимому, хорошо работает на разных платформах и сводит к минимуму количество кода, специфичного для платформы.
Ответ 3
Я бы использовал следующий подход:
- Создайте исполняемый файл, который печатает текущую дату в stdout (CMake не хватает этой функции)
- Добавить цель, которая всегда считается устаревшей.
- Пусть целевой вызов вызывает другой CMake script
- Пусть вызываемый CMake script сгенерирует заголовочный файл
Пример кода для этого:
--- 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 "@[email protected]"
--- 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;
}
Это приводит к заголовку "сгенерированный .h", который восстанавливается при каждой сборке. Если вам не нужен DATETIME, этот пример может быть существенно упрощен, так как у CMake не хватает этой функции, и программа должна быть построена для имитации функциональности.
Я бы подумал более чем дважды, прежде чем делать это. Помните, что заголовочный файл будет регенерироваться каждый раз, когда make выполняется, что делает вашу цель недействительной в любое время. У вас никогда не будет бинарного файла, который считается актуальным.
Ответ 4
Это работает?
d=`perl -e"print qq(Whatever calculated at runtime);"`; g++ prog.cpp -o prog -DDATETIME=$$d