Как запустить команду во время компиляции в Makefile, сгенерированном CMake? - PullRequest
33 голосов
/ 17 сентября 2009

Я хотел бы передать некоторые параметры компилятору. Опция должна быть рассчитана во время компиляции - каждый раз, когда вызывается make, а не когда cmake, поэтому команда execute_process не обрезает его. (так?)

Например, передача даты компилятору g ++ следующим образом:

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

Но с DATETIME, рассчитанным во время компиляции.

Есть идеи, как это сделать в CMake?

Редактирование щедрости:

Будет принято наименее хакерское решение.

Обратите внимание, что я хочу иметь возможность выполнять произвольную команду во время компиляции, а не только 'date'.

Редактировать 2:

Он должен работать на Linux, Windows (VS), Mingw, Cygwin и OS X. Вы не можете использовать Ruby, Perl или Python, поскольку они нестандартны в Windows. Вы можете предположить BOOST, но я думаю, что это бесполезно.

Цель состоит в том, чтобы заставить cmake сгенерировать Makefile (в случае Linux), который при выполнении make выполнит эту работу.

Создание собственного * .h файла в порядке, но его должен инициировать Makefile (или эквивалентный в других ОС) make. Создание * .h не должно (и не должно) использовать cmake.

Ответы [ 4 ]

45 голосов
/ 24 сентября 2009

Вы опускаете некоторую информацию, например, какие платформы вам нужны чтобы запустить это, и если есть какие-либо дополнительные инструменты, которые вы можете использовать. Если Вы можете использовать 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 для деталей), поэтому моя первоначальная идея не работает (создание custom.bat в шаге PRE_BUILD, а затем сделать "вызвать custom.bat", чтобы получить набор переменных, который позже может быть упоминается в командной строке компилятора). Если есть эквивалент обратные пометки в пакетных файлах, которые решат проблему.

Надеюсь, это даст некоторые идеи и отправные точки.

(Теперь к неизбежному встречному вопросу: что вы действительно пытаешься сделать?)

РЕДАКТИРОВАТЬ: Я не уверен, почему вы не хотите, чтобы CMake использовался для генерации заголовочного файла. Использование $ {CMAKE_COMMAND} расширится до CMake, используемого для генерации Makefiles / .vcproj-файлов, и, поскольку 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)

Отрегулируйте кавычки по мере необходимости.

4 голосов
/ 10 октября 2012

Решение выше (использование отдельного файла сценария 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}\"")

Первая строка запускает команду оболочки 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;

Кажется, это хорошо работает на разных платформах и сводит к минимуму количество кода для конкретной платформы.

2 голосов
/ 29 сентября 2009

Я бы использовал следующий подход:

  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)

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

В результате получается заголовок «generate.h», который создается заново при каждой сборке. Если вам не нужен DATETIME, этот пример может быть существенно упрощен, так как в CMake отсутствует эта функция, и для имитации функциональности необходимо создать программу.

Однако я бы подумал более двух раз, прежде чем делать это. Помните, что заголовочный файл будет обновляться каждый раз при запуске make, делая вашу цель недействительной в все раз. У вас не будет никогда двоичного файла, который считается современным.

0 голосов
/ 17 сентября 2009

Это работает?

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