build process - con - ¿Cómo ejecutar un comando en tiempo de compilación en Makefile generado por CMake?
gcc linux (4)
¿Esto funciona?
d=`perl -e"print qq(Whatever calculated at runtime);"`; g++ prog.cpp -o prog -DDATETIME=$$d
Me gustaría pasar algunas opciones a un compilador. La opción tendría que calcularse en tiempo de compilación, siempre que se invoque ''make'', no cuando ''cmake'', por lo que el comando execute_process no lo corta. (¿lo hace?)
Por ejemplo, pasar una fecha a un compilador de g ++ como ese:
g++ prog.cpp -o prog -DDATETIME="17:09:2009,14:25"
Pero con DATETIME calculado en tiempo de compilación.
¿Alguna idea de cómo hacerlo en CMake?
Bounty edit:
Se aceptará una solución menos hackish.
Tenga en cuenta que quiero poder ejecutar un comando arbitrario en tiempo de compilación, no solo ''fecha''.
Editar 2:
Tiene que funcionar en Linux, Windows (VS), Mingw, Cygwin y OS X. No se puede asumir que Ruby, Perl o Python no son estándar en Windows. Puedes asumir BOOST, pero supongo que no sirve de nada.
El objetivo es forzar a cmake a generar Makefile (en el caso de Linux) que cuando make se ejecuta, hará el trabajo.
Crear un archivo * .h personalizado está bien, pero debe ser iniciado por un Makefile (o equivalente en otro sistema operativo) por make. La creación de * .h no tiene que (y no debería tener que) usar cmake.
Está dejando de lado cierta información, como qué plataformas necesita para ejecutar esto y si hay herramientas adicionales que puede utilizar. Si puedes usar Ruby, Perl, de Python, las cosas se vuelven mucho más simples. Asumiré que desea ejecutar tanto en la plataforma de Unix como en Windows y que no hay herramientas adicionales disponibles.
Si desea la salida del comando en un símbolo de preprocesador, la forma más fácil es generar un archivo de encabezado en lugar de manipular los parámetros de línea de comando. Recuerde que CMake tiene un modo de secuencia de comandos (-P) donde solo procesa comandos de script en el archivo, por lo que puede hacer algo como esto:
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)
El archivo "custom.h" se genera en tiempo de compilación con el comando "cmake -P custom.cmake". custom.cmake se ve así:
execute_process(COMMAND uname -a
OUTPUT_VARIABLE _output OUTPUT_STRIP_TRAILING_WHITESPACE)
file(WRITE custom.h "#define COMPILE_TIME_VALUE /"${_output}/"")
Ejecuta el comando (en este caso "uname -a", lo reemplazará con el comando que desee) y coloca el resultado en la variable _output, que luego escribe en custom.h. Tenga en cuenta que esto solo funcionará si el comando genera una sola línea. (Si necesita una salida de línea múltiple, tendrá que escribir un custom.cmake más complejo, dependiendo de cómo quiera que los datos de la línea múltiple entren en su programa).
El programa principal se ve así:
#include <stdio.h>
#include "custom.h"
int main()
{
printf("COMPILE_TIME_VALUE: %s/n", COMPILE_TIME_VALUE);
return 0;
}
Si realmente desea calcular las opciones del compilador en el momento de la compilación, las cosas se vuelven mucho más complicadas. Para los generadores Bourne-shell puedes simplemente insertar el comando dentro de los backticks. Si se enoja mientras descifra las citas, mueva toda la lógica de su comando dentro de un script de shell, de modo que solo necesita poner mycommand.sh
en su add_definitions ():
if(UNIX)
add_definitions(`${CMAKE_CURRENT_SOURCE_DIR}/custom-options.sh`)
endif()
Para los generadores basados en archivos por lotes de Windows, las cosas son mucho más complicadas y no tengo una buena solución. El problema es que los comandos PRE_BUILD
no se ejecutan como parte del mismo archivo por lotes que la invocación del compilador real (estudie BuildLog.htm para obtener más detalles), por lo que mi idea inicial no funcionó (generando un custom.bat en un paso PRE_BUILD
y a continuación, haga "call custom.bat" en él para obtener un conjunto de variables que luego se puede referenciar en la línea de comandos del compilador). Si hay un equivalente de backticks en archivos por lotes, eso resolvería el problema.
Espero que esto dé algunas ideas y puntos de partida.
(Ahora a la inevitable contra pregunta: ¿qué estás realmente tratando de hacer?)
EDITAR: No estoy seguro de por qué no quieres que CMake se utilice para generar el archivo de encabezado. El uso de $ {CMAKE_COMMAND} se ampliará al CMake utilizado para generar los archivos Makefiles / .vcproj, y dado que CMake realmente no admite archivos Makefiles / .vcproj portátiles, deberá volver a ejecutar CMake en los equipos de destino.
CMake también tiene una serie de comandos de utilidad (Ejecute "cmake -E" para una lista) por este motivo explícito. Puedes, por ejemplo, hacer
add_custom_command(OUTPUT custom.h COMMAND ${CMAKE_COMMAND} -E copy file1.h file2.h)
para copiar archivo1.h a archivo2.h
De todos modos, si no quieres generar los archivos de cabecera usando CMake, necesitarás invocar scripts .bat / .sh separados para generar el archivo de cabecera, o hacerlo usando echo:
add_custom_command(OUTPUT custom.h COMMAND echo #define SOMETHING 1 > custom.h)
Ajuste las cotizaciones según sea necesario.
La solución anterior (usando un archivo de script CMake separado para generar un archivo de encabezado) parece muy flexible pero un poco complicada para lo que se está haciendo en el ejemplo.
Una alternativa es establecer una propiedad COMPILE_DEFINITIONS en un archivo de origen individual o en un destino, en cuyo caso las variables de preprocesador definidas solo se establecerán para el archivo de origen o se compilarán los archivos en el destino.
Las propiedades COMPILE_DEFINITIONS tienen un formato diferente del utilizado en el comando add_definitions, y tienen la ventaja de que no necesita preocuparse por la sintaxis "-D" o "/ D" y funcionan en plataformas cruzadas.
Código de ejemplo
- 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}/"")
La primera línea ejecuta un comando de shell svnversion
y coloca el resultado en la variable SVN_REV
. El comando de string(STRIP ...)
es necesario para eliminar los caracteres de nueva línea del final de la salida.
Tenga en cuenta que esto supone que el comando que se ejecuta es multiplataforma. Si no, es posible que deba tener alternativas para diferentes plataformas. Por ejemplo, uso la implementación cygwin del comando de date
Unix y tengo:
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)
para los comandos de fecha, donde win_date.bat
es un archivo bat que win_date.bat
la fecha en el formato deseado.
Las dos variables del preprocesador no están disponibles en el archivo ./VersionInfo.cpp
pero no se configuran en ningún otro archivo. Entonces podrías tener
- VersionInfo.cpp -
std::string version_build_time=BUILD_TIME;
std::string version_svn_rev=SVN_REV;
Esto parece funcionar muy bien en todas las plataformas y minimiza la cantidad de código específico de la plataforma.
Utilizaría el siguiente enfoque:
- Cree un archivo ejecutable que imprima la fecha actual en stdout (CMake no tiene esta funcionalidad)
- Agregar un objetivo que siempre se considera desactualizado
- Deje que el destino invoque otro script CMake
- Permitir que el script CMake invocado genere un archivo de cabecera
Código de ejemplo para esto:
--- 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)
--- generar.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;
}
Esto da como resultado un encabezado "generated.h" que se regenera en cada compilación. Si no necesita DATETIME, este ejemplo podría simplificarse sustancialmente ya que CMake carece de esta característica y debe crearse un programa para simular la funcionalidad.
Sin embargo, lo pensaría más de dos veces antes de hacer esto. Recuerde que el archivo de encabezado se regenerará cada vez que se ejecute make, haciendo que su destino sea inválido en todo momento. Nunca tendrá un archivo binario que se considere actualizado.