Monter une Usine De Développement iPhone

le 08/02/2010 par Vincent Daubry
Tags: Software Engineering

Octo a récemment participé à la réalisation de l’application iNomineo pour iPhone (cf  OCTO Technology accompagne Generali sur l’iPhone ) :

"logo"

Pourquoi avons nous mis en place une usine de développement (UDD) suite à ce projet ? A première vue, cela soulève plusieurs questions :

  • La mise en place d'une UDD relève d’une problématique d'industrialisation : comment rendre plus productif un process que l'on maîtrise. Alors qu’un projet iPhone évoque plutôt l’innovation : un langage peu connu, de nouveaux outils, une nouvelle plateforme.

  • Une UDD a pour vocation de simplifier le travail d'intégration entre les différents développeurs : plus ceux-ci sont nombreux, plus l'UDD se révèle payante. Or nous n'étions que 3 développeurs sur ce projet, on aurait tendance à penser que l'on peut gérer cet effort d'intégration « à la main ».

  • L'UDD a également pour rôle d'automatiser l'exécution des tests, or une des particularités d'une application iPhone c'est la prédominance de l'interface graphique (réputée couteuse et compliquée à tester), de plus quels outils peut-on utiliser ?

  • Pour finir, l'iPhone souffre encore de l'image du jeune étudiant faisant fortune sur l'appstore : ce type de projet véhicule pour certains une image d'amateurisme, « c'est un travail à confier à un stagiaire ».

Pourtant l’expérience nous a montré qu'une UDD et la pratique des tests unitaires apportent des solutions à des problèmes récurrents sur le projet.

De plus, la mise en place d'une UDD avec une couverture de test conséquente et les métriques associées sont un moyen d'apporter un gage de qualité et de professionnalisme à un projet iPhone.

Dans cet article, nous couvrirons les raisons concrètes qui nous ont poussé à le faire, et comment nous y sommes parvenus.

Dans un prochain article, nous verrons quelles pratiques de test mettre en place sur un projet iPhone (les outils, les méthodes, les bonnes pratiques).

Pourquoi a-t-on mis en place l’UDD sur notre projet

Ce projet a été l’occasion pour moi de m’initier au développement iPhone. Au démarrage le ticket d’entrée paraît très élevé : il faut découvrir une nouvelle plateforme (un téléphone mobile), un nouvel environnement (Mac quand on vient de PC), un nouveau langage (Objective-C), de nouveaux outils (Xcode, interface builder, instruments), les frameworks Cocoa (UIKit, CoreGraphics, CoreAnimation, etc).

Les questions d’industrialisation auxquelles on est habitué lorsque l’on vient du monde Java paraissent bien lointaines.

Mais une des particularités d’un projet iPhone, c’est que ces difficultés techniques sont mitigées par la qualité de l'environnement de développement : les outils et les frameworks bénéficient de plus de 20 ans de développements par Apple et NeXT et sont donc très matures. L'architecture des applications iPhone s'appuie largement sur des design patterns éprouvés.

L'objective-C, dont la syntaxe peut rebuter au premier abord, se révèle être un langage objet très structuré et agréable à manipuler.

Néanmoins, nous nous sommes vite aperçus que les problèmes propres au développement informatique en équipe sont toujours présents.

Il s’agit de problèmes :

  • d’organisation du travail en équipe : douleurs d’intégration des sources entre les différents développeurs
  • de qualité du code : douleurs lors de la recette et du refactoring
  • et d’environnements : douleurs de gestion des différentes versions sur chaque poste de développement lors du déploiement sur un iPhone

Arrivé à ce stade, le risque est d’accepter la douleur : en effet, on a appris à vivre avec ces contraintes pendant les premiers temps du projet, et on hésite à investir dans ce que l’on ne ressent plus comme un frein.

A cela se rajoute le fait qu’à court terme, on a tendance à vouloir faire avancer le projet (bien plus palpitant surtout sur une plateforme « fun ») qu’industrialiser le développement (pas toujours très excitant comme perspective au premier abord).

Nous avons donc travaillé à mettre en place une usine de développement qui vise à adresser les problèmes mentionnés ci-dessus.

Nous allons aborder dans cet article une série de cas concrets, les problèmes qui en découlent, et comment les résoudre grâce à la mise en place d’une usine de développement.

I) Rationaliser le déploiement : Une version stable à la demande

Mise en situation :

Chaque développeur travaille sur le trunk du gestionnaire de source : à un instant t personne n’a de version stable sur son poste. De plus, il n'existe pas de version de référence de l'application. Ceci entraine 2 situations assez inconfortables :

  • Lorsque le chef de projet ou un manager souhaite voir l'avancement des développements, le même scénario se répète : on commit tous « en vitesse » et comme on peut, on intègre à coup de commentaires dans le code et on déploie une version de démo plus ou moins stable sur son iPhone (« tout marche sauf cet écran qui marchait hier, mais là on bosse dessus »). Ou pire, on lui demande de repasser le lendemain pour nous laisser le temps de figer une version stable.

  • Lors de la phase de recette, en fonction du poste à partir duquel on déploie, les bugs ne sont parfois pas les mêmes !

Douleur :

Incapacité à fournir rapidement une version stable de l’application.

Cause :

L’omni-environnement : chaque poste de développeur est à la fois l’environnement de développement, d’intégration et de recette.

Solution :

Automatiser la constitution d’une version issue directement du gestionnaire de sources. Cette version ne sera pas compilée en configuration Debug, mais en configuration Release, ce qui permettra de se rapprocher d’une situation de livraison.

La mise en place d’une usine de développement permet la distinction entre l’environnement de développement (le mac de chaque développeur), l’environnement d’intégration (un mac sur lequel tourne l’UDD) et l’environnement de recette (le poste mac ou PC, sur lequel on dépose l’application qui est transférée dans iTunes).

Pré-requis :

Configuration de l’UDD :

Hudson ne prend pas en charge les projets Xcode par défaut. On va donc créer un script shell qui se chargera :

  • de compiler le projet
  • d’historiser un zip de l’application dans un répertoire build-history
  • de déposer l’application compilée (.app) sur le bureau.

Pour cela, il faut créer un job de type « free style » dans Hudson et ajouter au build une étape « Exécuter un script shell »

Le SDK iPhone met à disposition une commande « xcodebuild » qui permet de lancer un build via ligne de commande.

Cette commande prend en paramètre le nom de la target à builder, la configuration à utiliser et le sdk pour lequel on build.

On utilisera la target par défaut en configuration « release » avec pour cible un device iphone 3.0

xcodebuild -target  “maTarget” -configuration "Release" -sdk iphoneos3.0

P.S : Assurez-vous :

- que le fichier build.sh a les droits nécessaires pour être exécuté (chmod 755 build.sh)

- d’ignorer le répertoire « build » du projet lors des commit: 'svn propset svn:ignore build .'

Axes d'améliorations :

  • En plus de déposer l'application sur le bureau, on pourrait automatiser l'installation de la nouvelle version dans iTunes.

  • Il semble possible de se baser sur le versionnement du bundle de l'application, pour que iTunes distingue des versions « snapshot » (qui ne seront pas mis à jour automatiquement sur l'iPhone) et des versions « release » (qui viendront remplacer automatiquement la dernière version).

II) Qualité du code :

Erreur de compilation :

Mise en situation :

Un développeur oublie de commiter un fichier ou une ressource nécessaire au build. Ou pire, on commit un fichier qui ne compile pas. Les autres développeurs qui se synchronisent avec le gestionnaire de sources ne peuvent plus compiler et sont bloqués. Plus cette situation est détectée tôt, plus elle est facile à corriger.

Douleur :

Le coût des erreurs

Cause :

Erreur de commit

Solution :

L’UDD permet de signaler les erreurs de compilation. Nous allons faire évoluer le script afin de réaliser cette tâche et envoyer un mail aux développeurs lorsqu’un bug est détecté.

Détecter les erreurs de compilation :

On va d’abord parser le résultat de la compilation afin de vérifier si celle-ci s’est bien déroulée. On va se baser sur le message 'BUILD SUCCEEDED' qui apparait à la fin d’une compilation réussie.

xcodebuild -target "${TARGET_NAME}" -configuration Release -sdk iphoneos3.0 > /tmp/buildlogIphone.log

LOGRESULT=`grep 'BUILD SUCCEEDED' /tmp/buildlogIphone.log`
if [ "$LOGRESULT" == "" ]
then
exit 1
fi

Lorsque la compilation échoue, Hudson enverra automatiquement un mail aux destinataires configurés ( http://wiki.hudson-ci.org/display/HUDSON/Meet+Hudson).

Axe d'améliorations :

  • Ne pas se baser sur le texte de la compilation qui est susceptible de changer, mais plutôt sur le code de résultat du build.

  • Depuis la version 3.0 de Xcode, des erreurs non bloquantes apparaissent dans les logs du build par ligne de commande, ce qui rend les logs moins lisibles. Ces erreurs sont mentionnées sur les mailing lists mais sans solution immédiate, il nous reste à résoudre ce problème.

  • Améliorer le contenu des mails envoyés afin de synthétiser l’information : en effet, le contenu des mails dans l’état actuel est très verbeux et assez fastidieux à exploiter. On pourrait recevoir un mail indiquant uniquement le statut du build, le nombre de leaks détectés, les tests unitaires en échec et un lien vers le détail pour chaque section (build, leaks, test). Et éventuellement mettre en place un code couleur en fonction de l’urgence du problème remonté dans le mail.

Fuites mémoire

Mise en situation :

Un développeur commit du code contenant des fuites mémoires (http://fr.wikipedia.org/wiki/Fuite_de_m%C3%A9moire). L'application devient instable et peut crasher à l'utilisation.

Douleur :

Encore le coût des erreurs

Cause :

Il est fastidieux de passer l’analyseur de code à chaque compilation, cela débouche alors sur une mauvaise pratique qui consiste à repousser le « nettoyage » du code. On voit alors parfois apparaître au sein de l'équipe des tâches techniques « suppression des leaks ». Lorsque ceux-ci sont détectés tardivement, ils deviennent couteux à corriger.

Solution :

L’UDD permet d’automatiser l’analyse statique du code. Celle-ci permet de détecter toute une série d’anomalies, allant de la fuite mémoire à la faille de sécurité. Nous allons utiliser l'outil « clang static analyzer », que l’on peut télécharger à cette adresse :

http://clang-analyzer.llvm.org/

N.B : La dernière version de Xcode intègre désormais une analyse de code basée sur le même projet mais intégrée à l’interface graphique (option « build and analyze » dans le menu build).

On récupère un fichier checker-232.tar que l’on va décompresser vers /admin/bin

Dans le dossier checker on trouve 2 exécutables :

  • « scan-build » permet de lancer une analyse de code par ligne de commande
  • « scan-view » permet de visualiser les détails des bugs trouvés sous forme de page html

On va créer 1 alias de scan-build dans /admin/bin ce qui nous évitera de spécifier le chemin complet vers l’exécutable.

Pour lancer une analyse de code il suffit de lancer la commande :

scan-build [BUILD_COMMAND]

Lorsque l’analyzeur détecte un bug il génère un fichier de rapport et affiche à la fin des logs un message du type :

scan-build: 2 bugs found. scan-build: Run 'scan-view /var/folders/Br/BrQyHE0cHHO0PuV2E4TrlU+++TI/-Tmp-/scan-build-2010-01-19-1' to examine bug reports.

On va donc parser les logs à la recherche du texte : « Run 'scan-view »

~/bin/scan-build xcodebuild -target "${TARGET_NAME}" -configuration Debug clean -sdk iphoneos3.0
~/bin/scan-build xcodebuild -target "${TARGET_NAME}" -configuration Debug -sdk iphoneos3.0  > /tmp/scanBuildlogIphone.log

SCANRESULT=`grep "Run 'scan-view" /tmp/scanBuildlogIphone.log`

if [ "$SCANRESULT" != "" ]
then
exit 1
fi

N.B : il est conseillé d’effectuer l’analyse de code en mode Debug. (http://clang-analyzer.llvm.org/scan-build.html)

Axes d'améliorations :

  • Ici encore on pourrait se baser sur le code de retour plutôt que sur le libellé.
  • Nous pourrions également optimiser le contenu des mails plutôt que de fournir les logs de l'analyse.

Tests unitaires :

Pré-Requis :

Mise en situation :

Un développeur commit un fichier qui fait échouer un ou plusieurs tests unitaires.

Douleur :

Toujours le coût des erreurs

Cause :

L’exécution des tests peut devenir longue et fastidieuse (nombreux tests d’intégration), cela n'incite pas les développeurs à les passer systématiquement.

Solution :

De la même manière qu’en Java, si les tests unitaires sont exécutés automatiquement à chaque commit, on allège le travail d’intégration des différents développements. Nous allons donc automatiser l’exécution de nos tests unitaires à chaque compilation. Si l’un des tests échoue, la compilation sortira en erreur.

Pour cela, nous allons ajouter une dépendance directe entre notre target principale et notre target de test. Ainsi, à chaque compilation de l’application, notre projet sera compilé, puis nos tests seront exécutés. Si l’une ou l’autre de ces étapes échoue, le build sortira en erreur.

Ouvrez votre target principale et dans l’onglet « General » faites « + » dans la liste « Direct dependencies » et ajoutez la target de test.

L’UDD iPhone au quotidien :

Actuellement, 2 développeurs se servent de l’UDD iPhone sur une mission réalisée par Octo. Celle-ci tourne sur un 3ème mac qui sert de machine d’intégration et de recette.

Lorsque le build casse, nous recevons un mail avec le log du build et un lien vers le rapport de l’analyse statique de code. Nous avons partagé le répertoire de log du 3ème mac ce qui nous permet de les consulter depuis notre poste de développement.

Le mac sur lequel tourne l’UDD sert également d’environnement de recette : sur le bureau se trouve en permanence la dernière version stable de l’application (ainsi qu’un historique des build de l’application), on peut la déployer sur un iPhone via iTunes par un simple drag and drop.

Conclusion :

La simple mise en place d'une UDD ne règle pas d'elle même les problèmes : il faut des règles partagées par l'équipe, notamment concernant la politique de commit : quand et quoi commiter.

Une UDD permet par contre de mettre l'accent sur ces règles et de discipliner les pratiques de développement.

Par ailleurs, tous les problèmes évoqués peuvent être théoriquement réglés «à la main », néanmoins il s'agit de problèmes liés à la partie « pénible » du travail : lancer les tests régulièrement, archiver des versions stables, intégrer en permanence, etc. L'UDD permet de rendre cet effort transparent.

--

Lien vers le script de build complet :

BUILD.SH