Подтвердить что ты не робот

Как запустить команду во время компиляции в 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.

4b9b3361

Ответ 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