Automatiser la compilation de libwebrtc avec CMake.

I. Introduction

Libwebrtc est la base de nombreux produits. C’est, bien sûr, la base de l’implémentation de l’API JavaScript du même nom dans Chrome, Firefox et Opera, mais c’est aussi la base de beaucoup de SDKs et autres produits sur mobiles. Conserver une version de libwebrtc à jour, et testée sur un code de base stable, est, comme chacun le sait, difficile si vous n’êtes pas un employé de chez Google. Ce post est le premier d’une série qui explique pas à pas comment mettre en place un système capable de compiler, tester et générer un installteur de libwebrtc automatiquement sur de nombreuses plateformes. Il fait suite à un précédent post qui expliquait comment fonctionne le système de compilation de Google (depot tools), et va plus se concentrer sur la partie automatisation grâce à CMake.

II. Il existe déjà (quelques) très bonnes ressources

Il n’existe pas de « meilleure solution » pour compiler des bibliothèques. Du moment qu’elles sont compilées, testées et intégrées automatiquement, le boulot est fait. Pour le mobile spécifiquement, les scripts développés par Pristine (voir ci-dessous) font un bon boulot. J’avoue avoir un avis biaisé vis-à-vis de CMake puisque c’est le moteur de production multi-plateformes avec lequel j’ai travaillé pendant la plus grande partie de la dernière décennie, mais il y a de très bonnes raisons qui expliquent pourquoi j’ai continué à utiliser CMake pendant si longtemps. Ainsi, ne pas avoir à gérer les différences de compilateurs est un vrai bonheur. De même, ne pas avoir à se préoccuper des différences de systèmes d’exploitation est tout autant une source de joie. Ajoutez à cela être capable d’ajouter un test à la volée avec une simple commande qui à son tour permettra l’envoi du résultat du test vers le tableau de bord, ce qui est une immense joie. Mais ce n’est pas tout, sachez que Valgrind est supporté par défaut, ce qui est ÉNORME. Enfin, même la génération d’un installeur est facilitée (et oui, tout ce que je viens de lister est multi-plateformes).

Avec cette série de posts, vous serez capable en quelques jours seulement d’implémenter un système très performant assurant à la fois la compilation, les tests et la production d’un installeur pour libwebrtc, alors que ça pourrait facilement vous prendre beaucoup plus de temps (d’après mon expérience).

Bien sûr, si vous disposez déjà d’une infrastructure de tests et d’une solution d’intégration continue, elles ne seront probablement pas très compatibles avec CMake. Mais il est souvent plus efficace de séparer la compilation de libwebrtc de la solution que vous construisez autour de libwebrtc, qu’il s’agisse d’un plugin WebRTC, d’un wrapper Apache Cordova (voir ci-dessous) ou d’un SDK mobile.

Pour approfondir:

III. Comment utiliser CMake au mieux

CMake est un gestionnaire de configuration multi-plateformes multi-compilateurs, parfois appelé système de méta-construction. Sa fonctionnalité principale est de trouver pour vous, sur l’ordinateur hôte, les bibliothèques et les programmes dont vous avez besoin pour votre compilation, et de préparer la génération les cibles (fichiers exécutables, bibliothèques, tests…), quel que soit votre système d’exploitation et votre compilateur.

Au strict minimum, vous pouvez utiliser CMake comme n’importe quel autre langage de script et essayer d’encapsuler le système de génération de libwebrtc dans des appels système. La commande « execute_process » sert à celà. Cette façon de procéder a le désavantage de tout faire au moment de la configuration, et ce de façon linéaire. À chaque fois que vous ferez appel à ce type de script, toutes les commandes seront relancées. Il est préférable de définir les cibles et les dépendances au moment de la configuration (c’est-à-dire la seule fois où vous lancerez directement cmake) et ensuite d’utiliser le compilateur natif pour s’occuper du reste du travail.

Un usage intégral de cmake consisterait à l’utiliser pour remplacer entièrement le système de génération de libwebrtc et redéfinir toutes les bibliothèques, les fichiers exécutables, les fichiers sources correspondants, les options de compilation, etc. Bien que cela soit faisable pour un seul fichier exécutable, ou une bibliothèque, réaliser cette opération pour tout libwebrtc voudrait dire devoir migrer tous les fichiers gyp[i] et s’assurer qu’on les garde synchrones à chaque mise à jour de libwebrtc. N’importe qui, saint d’esprit, qui s’est penché sur la complexité que représentent les 6000 lignes du fichier src/build/common.gypi, n’a aucune envie de s’attaquer à ce genre de tâche.

Dans ce post, nous allons utiliser l’approche: dite de « super build » qui consiste à laisser cmake piloter le système de compilation de Google, importer et lancer les tests, ajouter un support de couverture de code, générer les installeurs et ainsi de suite. Ainsi, nous utiliserons le système de compilation de Google tel quel pour les actions de base, et nous réserverons l’ussge de CMake pour les tâches où il est le meilleur.

IV. C’est parti !

1. Un nouveau module CMake pour détecter depot tools.

CMake dispose d’une collection de modules lui permettant de détecter et d’utiliser vos bibliothèques habituelles plus facilement. Ce mécanisme est multi-plateformes. La plupart de ces modules consistent en du code CMake placé dans un fichier appelé FindXXX.cmake. Vous pouvez appeler ce module en invoquant « find_package( XXX ) ». S’il s’agit d’un paquet obligatoire, vous pouvez demander à find_package de se terminer immédiatement en erreur en ajoutant l’argument REQUIRED, Par exemple, CMake est livré avec un FindGit.cmake pré-installé que nous pouvons utiliser dans notre code en écrivant simplement ceci:

  1. find_package(Git REQUIRED)

Actuellement, depot tools nest pas reconnu par CMake. Toutefois, il existe un mécanisme permettant aux projets CMake d’augmenter les capacités de CMake. Vous pouvez écrire vos propres modules FindXXXXX.cmake et permettre à CMake de les trouver en définissant dans votre code la variable CMAKE_MODULE_PATH . Faites bien attention d’étendre et non pas d’écraser cette variable, dans le cas où votre code serait appelé depuis un autre code CMake (style super génération). Dans notre cas, nous mettons les scripts dans un répertoire « CMake » juste sous le répertoire racine du projet CMake.

  1. set( CMAKE_MODULE_PATH
  2. ${CMAKE_MODULE_PATH} # pour intégration en super construction
  3. ${CMAKE_CURRENT_SOURCE_DIR}/Cmake
  4. )

Le code complet est disponible ici.

Il nous reste encore à écrire le code de FindDepotTools.cmake. CMake fournit toutes les primitives pour cela. Dans notre cas, il suffit seulement de trouver la commande gclient. Tout le reste est pris en charge par CMake pour nous, y compris les différences de système d’exploitation et de chemins.

  1. find_program(DEPOTTOOLS_GCLIENT_EXECUTABLE
  2. NAMES gclient gclient.bat   # indices à propos du nom de l’exécutable. gclient.bat est la version Windows
  3. )
  4. #  En-dessous de cette ligne on trouve le code standard de CMake pour que tous les paquets gèrent les options QUIET, REQUIRED…
  5. include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardARGS.cmake)
  6. find_package_handle_standard_args(DepotTools
  7. REQUIRED_VARS DEPOTTOOLS_GCLIENT_EXECUTABLE
  8. FAIL_MESSAGE »could not find the gclient executable »
  9. )

Maintenant, le cœur du script principal est relativement facile à écrire.

  1. project(webrtc)
  2. set(CMAKE_MODULE_PATH
  3. ${CMAKE_MODULE_PATH}  # pour une intégration dans une super génération.
  4. ${CMAKE_CURRENT_SOURCE_DIR}/Cmake
  5. )
  6. find_package(depottools REQUIRED)
  7. find_package(Git                REQUIRED)

Le code complet est disponible ici.

2. Implémenter « gclient config » comme une commande et une cible personnalisées de CMake.

Maintenant nous voulons lancer la ligne de commande « gclient config » depuis CMake. Cette ligne de commande créera un fichier .gclient qui ne devait pas déjà exister. Si le fichier existe, la commande ne devrait rien faire. De plus, je veux que le système de construction essaye à chaque fois qu’il est lancé. Je veux que ça fonctionne quel que soit le système d’exploitation.

Commençons par définir ce que la commande devrait être d’une manière multi-plateformes.

  1. set( gclient_config
  2. $(DEPOTTOOLS_GCLIENT_EXECUTABLE} config  # gclient a déjà été trouvé automatiquement
  3. –name src
  4. http://chromium.googlesource.com/external/webrtc.git
  5. )
  6. if(WIN32)
  7.     set(gclient_config cmd /c ${gclient_config})  # syntaxe pour le prompt de Windows
  8. endif()

Les premières contraintes surviennent avec le add_custom_command() de CMake. Il permet de créer une commande qui générera un fichier en sortie mais cette commande ne sera pas déclenchée si le fichier existe déjà.

  1. add_custom_command(
  2. OUTPUT ${CMAKE_SOURCE_DIR}/.gclient
  3. COMMAND ${glient_config}
  4. )

Toutefois, rien dans le script ne vous dit quand lancer cette commande personnalisée. C’est là que add_custom_target() entre en scène. Il crée une cible (éventuellement vide) pour le système de génération choisi sur laquelle les commandes pourront s’attacher. Cela permet aussi de définir les dépendances avec d’autres cibles pour définir l’odre de construction/exécution. Dans le cas de la configuration de gclient, il n’y a pas d’étape précédente donc pas de dépendance. En faisant dépendre la cible du fichier généré par la commande vous les liez automatiquement. Le paramètre ALL inclue toujours cette cible dans la génération.

  1. add_custom_target(
  2.      webrtc_configuration ALL
  3.      DEPENDS ${CMAKE_SOURCE_DIR}/.gclient
  4. )

3. Implémenter « gclient sync » et ajouter une dépendance.

Implémenter « gclient sync » est plus ou moins la même chose : définir la commande, ajouter une commande personnalisée, puis ajouter une cible personnalisée. Cependant, cette fois, nous voulons que cette cible s’exécute uniquement APRÈS « gclient config ». Nous devons donc ajouter une dépendance. Le code est auto-explicatif. Notez que nous forçons la commande sync à NE PAS lancer les hooks (option -n).

  1. set(gclient_sync ${DEPOTTOOLS_GCLIENT_EXECUTABLE} sync -n -D -r 88a4298)
  2. if(win32)
  3.    set(gclient_sync cmd /c ${gclient_sync})
  4. endif()
  5. add_custom_command(
  6.    OUTPUT ${CMAKE_SOURCE_DIR}/src/all.gyp
  7.    COMMAND ${gclient_sync}
  8. )
  9. add_custom_target(
  10.    webrtc_synchronization ALL
  11.    DEPENDS ${CMAKE_SOURCE_DIR}/src/all.gyp
  12. )
  13. add_dependencies( webrtc_synchronization webrtc_configuration )

4. Implémenter « gclient runhooks »

Gclient runhooks va produire des fichiers de compilation, donc nous devons être sûr que les paramètres les plus évidents sont définis au préalable. Ninja gère les modes Release et Debug au moment de la compilation, nous n’avons donc pas besoin de nous en soucier pour le moment. Le passage d’une architecture 32 bits à une architecture 64 bits (ou arm pour les mobiles) est géré par des variables d’environnement. CMake a une fonction proxy pour y accéder:

  1. if(DEFINED ENV{GYP_DEFINES})
  2.    message( WARNING « GYP_DEFINES is already set to ENV{GYP_DEFINES} »
  3. else()
  4. if(APPLE)
  5.     set(ENV{GYP_DEFINES} « target_arch=x64 »)
  6. else()
  7.    set(ENV{GYP_DEFINES} « target_arch=ia32 »)
  8. endif()
  9. endif()

Maintenant le reste du code est sensiblement le même qu’avant:

  1. set(gclient_runhooks ${DEPOTTOOLS_GCLIENT_EXECUTABLE}runhooks)
  2. if(WIN32)
  3.  set(gclient_runhooks cmd / c ${gclient_runhooks})
  4. endif()
  5. add_custom_command(
  6.  OUTPUT ${CMAKE_SOURCE_DIR}/src/out  # c’est arbitraire
  7.  COMMAND ${gclient_runhooks}
  8. )
  9. add_custom_target(
  10.   webrtc_runhooks ALL
  11.  DEPENDS ${CMAKE_SOURCE_DIR}/src/out
  12. )
  13. add_dependencies(webrtc_runhooks webrtc_synchronization)

L’intégralité du code pour sync et unhook se trouve ici.

5. Préparer la compilation avec la cible Ninja.

Comme mentionné précédemment, on doit traiter ici les modes Debug / Release. Nous suivons la syntaxe CMake qui, par chance, correspond à la syntaxe Ninja.

  1. if( NOT CMAKE_BUILD_TYPE)       # permet de le mettre dans un « superscript » et de le traiter ici
  2. set(CMAKE_BUILD_TYPE Debug)  # Debug par défaut
  3. endif()

Nous pouvons donc lancer l’habituel.

  1.  set(webrtc_build ninja -v -C ${CMAKE_SOURCE_DIR}/src/out/ ${CMAKE_BUILD_TYPE}
  2.  add_custom_command(
  3.     OUTPUT ${CMAKE_SOURCE_DIR}/src/out/${CMAKE_BUILD_TYPE}/ libwebrtc.a  # c’est arbitraire
  4.     COMMAND ${webrtc_build}
  5. )
  6. add_custom_target(
  7.     webrtc_build ALL
  8.     DEPENDS ${CMAKE_SOURCE_DIR}/src/out/${CMAKE_BUILD_TYPE} /libwebrtc.a
  9. )
  10. add_dependencies(webrtc_build webrtc_runhooks)

6. Enfin nous arrivons à la compilation !

Vous pouvez maintenant lancer cmake une seule fois et ensuite lancer la compilation avec votre compilateur d’origine. Ce code a été testé sur Mac (en utilisant « make ») et Windows (ouvrez webrtc.sln dans MS Visual Studio, et construisez TOUTES les cibles, ou appuyez sur F7).

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Time limit is exhausted. Please reload CAPTCHA.