web-dev-qa-db-fra.com

Méthode moderne pour définir les drapeaux du compilateur dans le projet cmake multiplateforme

Je veux écrire un fichier cmake qui définit différentes options de compilation pour clang ++, g ++ et MSVC dans les versions de débogage et de publication. Ce que je fais actuellement ressemble à ceci:

if(MSVC)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++latest /W4")
    # Default debug flags are OK 
    set(CMAKE_CXX_FLAGS_RELEASE "{CMAKE_CXX_FLAGS_RELEASE} /O2")
else()
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1z -Wall -Wextra -Werror")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} some other flags")
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")

    if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
    else()
        # nothing special for gcc at the moment
    endif()
endif()

Mais j'ai quelques problèmes avec ceci:

  1. D'abord le trivial: n'y a-t-il vraiment aucune commande comme appen qui me permettrait de remplacer set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} Foo") par append(CMAKE_CXX_FLAGS "Foo")?
  2. J'ai lu plusieurs fois, que l'on ne devrait pas définir manuellement CMAKE_CXX_FLAGS Et des variables similaires en premier lieu, mais je ne sais pas quel autre mécanisme utiliser.
  3. Plus important encore: la façon dont je le fais ici, j'ai besoin d'un répertoire de construction distinct pour chaque compilateur et configuration Idéalement, je voudrais transformer cela en plusieurs cibles havin dans le même répertoire afin que je puisse par exemple appelez make foo_debug_clang.

Donc mes questions sont

  • a) Existe-t-il une meilleure façon d'écrire le script cmake qui résout mes "points douloureux"? solution aux points mentionnés ci-dessus?
  • b) Existe-t-il quelque chose comme une meilleure pratique moderne et acceptée sur la façon de mettre en place de tels projets?

La plupart des références que j'ai pu trouver sur Internet sont obsolètes ou ne montrent que des exemples triviaux. J'utilise actuellement cmake3.8, mais si cela fait une différence, je suis encore plus intéressé par la réponse pour les versions plus récentes.

28
MikeMB

Votre approche serait - comme l'a commenté @Tsyvarev - tout à fait correcte, tout comme vous avez demandé la "nouvelle" approche dans CMake, voici à quoi votre code se traduirait:

cmake_minimum_required(VERSION 3.8)

project(HelloWorld)

string(
    APPEND _opts
    "$<IF:$<CXX_COMPILER_ID:MSVC>,"
        "/W4;$<$<CONFIG:RELEASE>:/O2>,"
        "-Wall;-Wextra;-Werror;"
            "$<$<CONFIG:RELEASE>:-O3>"
            "$<$<CXX_COMPILER_ID:Clang>:-stdlib=libc++>"
    ">"
)

add_compile_options("${_opts}")

add_executable(HelloWorld "main.cpp")

target_compile_features(HelloWorld PUBLIC cxx_lambda_init_captures)

Vous prenez add_compile_options() et - comme @ Al.G. a commenté - "utilisez le sale expressions du générateur ".

Il existe certains inconvénients des expressions de générateur:

  1. L'expression très utile $<IF:...,...,...> N'est disponible que dans la version CMake> = 3.8
  2. Vous devez l'écrire sur une seule ligne. Pour l'éviter, j'ai utilisé le string(APPEND ...) , que vous pouvez également utiliser pour "optimiser" vos appels set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ....
  3. C'est difficile à lire et à comprendre. Par exemple. les points-virgules sont nécessaires pour en faire une liste d'options de compilation (sinon CMake le cite).

Il vaut donc mieux utiliser une approche plus lisible et rétrocompatible avec add_compile_options():

if(MSVC)
    add_compile_options("/W4" "$<$<CONFIG:RELEASE>:/O2>")
else()
    add_compile_options("-Wall" "-Wextra" "-Werror" "$<$<CONFIG:RELEASE>:-O3>")
    if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
        add_compile_options("-stdlib=libc++")
    else()
        # nothing special for gcc at the moment
    endif()
endif()

Et oui, vous ne spécifiez plus explicitement la norme C++, vous nommez juste le fonctionnalité C++ votre code/cible dépend des appels target_compile_features() .

Pour cet exemple, j'ai choisi cxx_lambda_init_captures Qui serait par exemple un ancien compilateur GCC donne l'erreur suivante (comme exemple ce qui se passe si un compilateur ne prend pas en charge cette fonctionnalité):

The compiler feature "cxx_lambda_init_captures" is not known to CXX compiler

"GNU"

version 4.8.4.

Et vous devez écrire un script wrapper pour construire plusieurs configurations avec un générateur de makefile "configuration unique" ou utiliser un IDE "multi configuration" comme Visual Studio.

Voici les références d'exemples:

J'ai donc testé ce qui suit avec le support Open Folder De Visual Studio 2017 CMake pour combiner dans cet exemple les cl , clang et mingw compilateurs:

Configurations

CMakeSettings.json

{
    // See https://go.Microsoft.com//fwlink//?linkid=834763 for more information about this file.
    "configurations": [
        {
            "name": "x86-Debug",
            "generator": "Visual Studio 15 2017",
            "configurationType": "Debug",
            "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
            "buildCommandArgs": "-m -v:minimal",
        },
        {
            "name": "x86-Release",
            "generator": "Visual Studio 15 2017",
            "configurationType": "Release",
            "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
            "buildCommandArgs": "-m -v:minimal",
        },
        {
            "name": "Clang-Debug",
            "generator": "Visual Studio 15 2017",
            "configurationType": "Debug",
            "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
            "cmakeCommandArgs": "-T\"LLVM-vs2014\"",
            "buildCommandArgs": "-m -v:minimal",
        },
        {
            "name": "Clang-Release",
            "generator": "Visual Studio 15 2017",
            "configurationType": "Release",
            "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
            "cmakeCommandArgs": "-T\"LLVM-vs2014\"",
            "buildCommandArgs": "-m -v:minimal",
        },
        {
            "name": "GNU-Debug",
            "generator": "MinGW Makefiles",
            "configurationType": "Debug",
            "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
            "variables": [
                {
                    "name": "CMAKE_MAKE_PROGRAM",
                    "value": "${projectDir}\\mingw32-make.cmd"
                }
            ]
        },
        {
            "name": "GNU-Release",
            "generator": "Unix Makefiles",
            "configurationType": "Release",
            "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
            "variables": [
                {
                    "name": "CMAKE_MAKE_PROGRAM",
                    "value": "${projectDir}\\mingw32-make.cmd"
                }
            ]
        }
    ]
}

mingw32-make.cmd

@echo off
mingw32-make.exe %~1 %~2 %~3 %~4

Donc, vous pouvez utiliser n'importe quel générateur CMake à partir de Visual Studio 2017, il y a des citations malsaines en cours (comme pour septembre 2017, peut-être corrigées plus tard) qui nécessitent cet intermédiaire mingw32-make.cmd (En supprimant les guillemets).

13
Florian

Abordant les deux premiers points, mais pas le troisième:

  1. J'ai lu plusieurs fois, que l'on ne devrait pas définir manuellement CMAKE_CXX_FLAGS et des variables similaires en premier lieu, mais je ne sais pas quel autre mécanisme utiliser.

La commande que vous souhaitez est set_property. CMake prend en charge un tas de propriétés - pas tout, mais beaucoup - d'une manière qui vous évite de faire un travail spécifique au compilateur. Par exemple:

set_property(TARGET foo PROPERTY CXX_STANDARD 17)

ce qui pour certains compilateurs entraînera --std=c++17 mais pour les précédents avec --std=c++1z (avant la finalisation de C++ 17). ou:

set_property(TARGET foo APPEND PROPERTY COMPILE_DEFINITIONS HELLO WORLD)

résultera en -DHELLO-DWORLD pour gcc, clang et MSVC, mais pour les compilateurs étranges, il peut utiliser d'autres commutateurs.

Y a-t-il vraiment aucune commande comme append qui me permettrait de remplacer set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} Foo") par append(CMAKE_CXX_FLAGS "Foo")?

set_property Peut être utilisé en mode set ou en mode ajout (voir les exemples ci-dessus).

Je ne peux pas dire si c'est préférable à add_compile_options Ou target_compile_features, Cependant.

3
einpoklum

Vous pouvez utiliser target_compile_options () pour "ajouter" les options de compilation.

1
user8537712

Une autre façon consiste à utiliser des fichiers .rsp.

set(rsp_file "${CMAKE_CURRENT_BINARY_DIR}/my.rsp")
configure_file(my.rsp.in ${rsp_file} @ONLY)
target_compile_options(mytarget PUBLIC "@${rsp_file}")

ce qui pourrait faciliter l'inclusion d'options multiples et ésotériques.

1
Richard Hodges