Как запустить команду во время компиляции в Makefile, сгенерированном CMake?

StackOverflow https://stackoverflow.com/questions/1438535

Вопрос

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

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

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

Но с ДАТОЙ-ВРЕМЕНЕМ, вычисляемым во время компиляции.

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

Баунти править:

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

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

Правка 2:

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

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

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

Это было полезно?

Решение

Вы не указываете некоторую информацию, например, какие платформы вам нужны для запуска этого и есть ли какие-либо дополнительные инструменты, которые вы можете использовать.Если вы можете использовать Ruby, Perl или Python, все становится намного проще.Я буду предполагать, что вы хотите запускать pqlatform как в Unix, так и в Windows и что нет доступных дополнительных инструментов.

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

Основная программа выглядит следующим образом:

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

Если вы действительно хотите вычислять параметры компилятора во время компиляции, все становится намного сложнее.Для генераторов Bourne-shell вы можете просто вставить команду в backticks.Если вы разозлитесь, разбираясь с цитированием, переместите всю логику вашей команды внутрь shell-скрипта, так что вам нужно только поместить mycommand.sh в вашем add_definitions():

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

Для генераторов на основе пакетных файлов Windows все намного сложнее, и у меня нет хорошего решения.Проблема в том, что PRE_BUILD команды не выполняются как часть того же пакетного файла, что и фактический компилятор вызов (подробнее изучите BuildLog.htm), поэтому моя первоначальная идея не сработала (создание пользовательского файла.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)

чтобы скопировать файл1.h в файл2.h.

В любом случае, если вы не хотите генерировать заголовочные файлы с помощью CMake, вам нужно будет либо вызвать отдельные .bat/.sh скрипты для генерации заголовочного файла, либо сделать это с помощью echo:

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

Корректируйте цитирование по мере необходимости.

Другие советы

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

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

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

<Ол>
  • Создайте исполняемый файл, который печатает текущую дату в стандартный вывод (в CMake такой функции нет)
  • Добавьте цель, которая всегда считается устаревшей
  • Пусть цель вызовет другой сценарий CMake
  • Пусть вызываемый сценарий 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, делая вашу цель недействительной all раз. У вас никогда не будет двоичного файла, который считается современным.

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

    d=`perl -e"print qq(Whatever calculated at runtime);"`; g++ prog.cpp -o prog -DDATETIME=$d
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top