web-dev-qa-db-fra.com

Comment exécuter une commande au moment de la compilation dans Makefile généré par CMake?

Je voudrais passer quelques options à un compilateur. L'option devrait être calculée au moment de la compilation - à chaque fois que 'make' est invoqué, pas quand 'cmake', donc la commande execute_process ne la coupe pas. (le fait-il?)

Par exemple, passer une date à un compilateur g ++ comme ça:

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

Mais avec DATETIME calculé au moment de la compilation.

Une idée de comment le faire dans CMake?

Modification de la prime:

Une solution moins hackeuse sera acceptée.

Veuillez noter que je veux pouvoir exécuter une commande arbitraire au moment de la compilation, pas seulement "date".

Éditer 2:

Il doit fonctionner sur Linux, Windows (VS), Mingw, Cygwin et OS X. Vous ne pouvez pas supposer Ruby, Perl ou Python car ils ne sont pas standard sur Windows. Vous pouvez supposer BOOST , mais je suppose que cela ne sert à rien.

Le but est de forcer cmake à générer un Makefile (dans le cas de Linux) qui, lorsque make sera exécuté, fera le travail.

La création d'un fichier * .h personnalisé est correcte, mais elle doit être initiée par un Makefile (ou équivalent sur un autre OS) par make. La création de * .h ne doit pas (et ne devrait pas avoir) utiliser cmake.

33
Łukasz Lew

Vous omettez certaines informations, telles que les plates-formes sur lesquelles vous devez exécuter cela et s'il existe des outils supplémentaires que vous pouvez utiliser. Si vous pouvez utiliser Ruby, Perl, de Python, les choses deviennent beaucoup plus simples. Je suppose que vous souhaitez exécuter à la fois Unix et Windows pqlatform et qu'il n'y a pas d'outils supplémentaires disponibles.

Si vous souhaitez que la sortie de la commande dans un symbole de préprocesseur, le moyen le plus simple consiste à générer un fichier d'en-tête au lieu de jouer avec les paramètres de ligne de commande. N'oubliez pas que CMake a un mode script (-P) où il ne traite que les commandes de script dans le fichier, vous pouvez donc faire quelque chose comme ceci:

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)

Le fichier "custom.h" est généré au moment de la compilation par la commande "cmake -P custom.cmake". custom.cmake ressemble à ceci:

execute_process(COMMAND uname -a 
    OUTPUT_VARIABLE _output OUTPUT_STRIP_TRAILING_WHITESPACE)
file(WRITE custom.h "#define COMPILE_TIME_VALUE \"${_output}\"")

Il exécute la commande (dans ce cas "uname -a", vous la remplacerez par la commande que vous souhaitez) et place la sortie dans la variable _output, qu'il écrit ensuite dans custom.h. Notez que cela ne fonctionnera que si la commande génère une seule ligne. (Si vous avez besoin d'une sortie multiligne, vous devrez écrire un fichier custom.cmake plus complexe, selon la façon dont vous voulez les données multilignes dans votre programme.)

Le programme principal ressemble à ceci:

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

Si vous voulez réellement calculer les options du compilateur au moment de la compilation, les choses deviennent beaucoup plus délicates. Pour les générateurs Bourne-Shell, vous pouvez simplement insérer la commande dans des backticks. Si vous vous fâchez en trouvant des guillemets, déplacez toute la logique de votre commande dans un script Shell afin que vous n'ayez qu'à mettre mycommand.sh Dans votre add_definitions ():

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

Pour les générateurs basés sur des fichiers batch Windows, les choses sont beaucoup plus délicates et je n'ai pas de bonne solution. Le problème est que les commandes PRE_BUILD Ne sont pas exécutées dans le même fichier batch que l'invocation du compilateur (étudiez le BuildLog.htm pour plus de détails), donc mon idée initiale n'a pas fonctionné (générer un custom.bat dans une étape PRE_BUILD puis faites "appeler custom.bat" dessus pour obtenir un ensemble de variables qui pourra être référencé plus tard dans la ligne de commande du compilateur). S'il y a un équivalent de backticks dans les fichiers batch, cela résoudrait le problème.

J'espère que cela donne quelques idées et points de départ.

(Passons maintenant à la contre-question inévitable: qu'est-ce que vous vraiment essayez de faire?)

EDIT: Je ne sais pas pourquoi vous ne voulez pas laisser CMake être utilisé pour générer le fichier d'en-tête. L'utilisation de $ {CMAKE_COMMAND} s'étendra au CMake utilisé pour générer les fichiers Makefiles/.vcproj, et puisque CMake ne prend pas vraiment en charge les fichiers Makefiles/.vcproj-portables, vous devrez réexécuter CMake sur les machines cibles.

CMake a également un tas de commandes utilitaires (Exécutez "cmake -E" pour une liste) pour cette raison explicite. Vous pouvez par exemple faire

add_custom_command(OUTPUT custom.h COMMAND ${CMAKE_COMMAND} -E copy file1.h file2.h)

pour copier file1.h vers file2.h.

Quoi qu'il en soit, si vous ne souhaitez pas générer les fichiers d'en-tête à l'aide de CMake, vous devrez soit invoquer des scripts .bat/.sh distincts pour générer le fichier d'en-tête, soit le faire en utilisant echo:

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

Ajustez les devis au besoin.

45
JesperE

La solution ci-dessus (en utilisant un fichier de script CMake séparé pour générer un fichier d'en-tête) semble très flexible mais un peu compliquée pour ce qui est fait dans l'exemple.

Une alternative consiste à définir une propriété COMPILE_DEFINITIONS sur un fichier source individuel ou sur une cible, auquel cas les variables de préprocesseur définies ne seront définies que pour le fichier source ou les fichiers de la cible sont compilés.

Les propriétés COMPILE_DEFINITIONS ont un format différent de celui utilisé dans la commande add_definitions, et présentent l'avantage que vous n'avez pas à vous soucier de la syntaxe "-D" ou "\ D" et qu'elles fonctionnent sur plusieurs plates-formes.

Exemple de code

- 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 première ligne exécute une commande Shell svnversion et place le résultat dans la variable SVN_REV. La commande string(STRIP ...) est nécessaire pour supprimer les caractères de fin de ligne de fin de la sortie.

Notez que cela suppose que la commande en cours d'exécution est multiplateforme. Sinon, vous devrez peut-être avoir des alternatives pour différentes plates-formes. Par exemple, j'utilise l'implémentation cygwin de la commande Unix date et j'ai:

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)

pour les commandes de date, où win_date.bat est un fichier de chauve-souris qui affiche la date au format souhaité.

Les deux variables du pré-processeur ne sont pas disponibles dans le fichier ./VersionInfo.cpp Mais ne sont définies dans aucun autre fichier. Vous pourriez alors avoir

- VersionInfo.cpp -

std::string version_build_time=BUILD_TIME;
std::string version_svn_rev=SVN_REV;

Cela semble bien fonctionner sur toutes les plates-formes et minimise la quantité de code spécifique à la plate-forme.

4
user1735629

J'utiliserais l'approche suivante:

  1. Créer un exécutable qui imprime la date actuelle sur stdout (CMake n'a pas cette fonctionnalité)
  2. Ajouter une cible toujours considérée comme obsolète
  3. Laissez la cible invoquer un autre script CMake
  4. Laissez le script CMake invoqué générer un fichier d'en-tête

Exemple de code pour cela:

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

Il en résulte un en-tête "généré.h" qui est régénéré sur chaque génération. Si vous n'avez pas besoin de DATETIME, cet exemple pourrait être considérablement simplifié car CMake n'a pas cette fonctionnalité et un programme doit être construit pour simuler la fonctionnalité.

Je penserais cependant plus de deux fois avant de faire ceci. N'oubliez pas que le fichier d'en-tête sera régénéré à chaque exécution de make, rendant votre cible invalide à tous fois. Vous aurez jamais un binaire considéré comme à jour.

2
larsmoa