Mouviciel

Création d'applications pour Mac OS X

Les coulisses d'un développeur Mac

Krilleye, ma première application Mac

Publié le dimanche 5 avril 2009, 2 commentaires

Après de longues semaines de silence, je reviens ici avec ma première application Mac. Il ne s'agit encore que d'une maquette: elle fait ce que je souhaitais qu'elle fasse mais de nombreuses améliorations restent possibles.

De quoi s'agit-il ?

En quelques mots, Krilleye est un convertisseur de séquences AVCHD issues de certains camescopes numériques récents.

Interface graphique de Krilleye

Krilleye offre deux fonctions principales. La première est l'archivage du contenu du camescope dans une image disque de type sparseimage. Cela permet de retrouver toutes les séquences et toutes les photos dans leur qualité d'origine. De plus lorsque l'image est montée sur le bureau, l'importation des séquences dans iMovie ou des photos dans iPhoto est aussi simple que si le camescope était branché.

La deuxième fonction est la conversion des séquences choisies en un petit format, avec une résolution de 480x270 pixels et une taille environ dix fois moindre. Cela permet une manipulation beaucoup plus facile, notamment pour le web ou le mail, avec une qualité qui reste acceptable pour une utilisation familiale.

Pour télécharger la version 0.1, cliquez sur ce lien : Krilleye-0.1.zip.

Le code source est disponible sur Google Code à : http://krilleye.googlecode.com/.

Du besoin à la solution

Il y a quelques mois, j'ai fait l'acquisition d'un camescope AVCHD. Je savais que l'application iMovie savait manipuler ce format. Après quelques essais, j'ai été assez déçu. Voici quelques raisons :

Peu à peu, en apprivoisant le camescope en en observant ce que j'en faisais, j'ai affiné mes deux besoins principaux :

Les premiers mois, je réalisais l'archivage et la conversion manuellement : D'abord j'ouvrais l'utilitaire de disque pour créer une image de disque FAT32, puis j'y copiais le contenu du camescope par glisser-déposer. Ensuite je réalisais la conversion en petit format (parmi les applications qui savent faire cela, il y a MovieConverter qui inclut un module AVCHD gratuit nommé SuntaicHD), enfin j'importais les séquences converties dans iPhoto.

Mais cela ne me satisfaisait pas, il y avait beaucoup trop de manipulations à faire. Je voulais quelque chose de plus facile, intuitif et automatique qui pourrait être utilisé par n'importe qui. J'ai donc cherché comment faire une application Cocoa qui réalise toute seule ce que je faisais à la main. Voici donc ce que j'ai trouvé :

C'est en mélangeant toutes ces informations et en les combinant avec d'autres ressources sur Cocoa que je suis finalement arrivé à produire Krilleye ! Maintenant, j'attends vos retours avec impatience. L'aventure ne fait que commencer.

Logo de Krilleye

Télécharger : Krilleye-0.1.zip
Accès au code source : http://krilleye.googlecode.com/

Bien documenter son code avec Doxygen et Xcode

Publié le samedi 7 février 2009, 3 commentaires

Dans cet article, je vous propose de rationaliser la documentation de votre code grâce à Doxygen. Cette documentation sera produite automatiquement par Xcode et apparaîtra comme DOC SET dans le menu Documentation de Xcode.

Un petit état des lieux

Il m'est arrivé d'écrire du code dans un état d'excitation créative tel que tout ce dont j'avais envie, c'était de voir fonctionner la réalisation de mes idées. Je voulais savoir le plus vite possible si ce que j'imaginais allait marcher comme je le voulais. C'est dans ces moments-là qu'on ne voit pas l'heure tourner et qu'on n'entend pas l'estomac demander une pause. Bien entendu, seul le code était important. Les variables s'appelaient a, b, ou c, parfois i, j, k. Les commentaires seraient pour plus tard, lorsque le premier jet serait au point.

Il m'est aussi arrivé de reprendre ce code quelques mois, voire quelques années plus tard... Inutile de vous faire un dessin : je ne comprenais plus rien au plat de spaghetti que j'avais écrit. Il serait plus facile de tout reprendre de zéro que d'essayer de retrouver ce que tout ce fatras voulait dire.

Avec l'age, j'arrive beaucoup plus facilement à me dominer dans ces situations et à commenter mon code le plus clairement possible. En écrivant les commentaires, je pense à mon lectorat: ce ne sont pas d'autres développeurs, c'est moi dans six mois. Mais même ainsi, il m'arrive d'avoir des difficultés à m'y retrouver. J'y vois plusieurs raisons :

Doxygen pour y voir plus clair ?

Mauvaise nouvelle, Doxygen ne résoud pas tout ces problèmes.

Mais il y a une bonne nouvelle, Doxygen est capable d'extraire de la documentation pour un code sans aucun commentaire ! Il fait l'inventaire de toutes les classes, de toutes les variables et méthodes dans chaque classe, des dépendances entre tout cela, il trace des graphes de dépendances entre les fichiers et des graphes d'héritage entre les classes. J'en ai peut-être oublié.

Voyons cela sur un exemple simple. Nous allons d'abord installer Doxygen, puis créer un projet Cocoa qui servira de support

Téléchargez Doxygen pour Mac et installez-le par exemple dans votre dossier ~/Applications. Nous ne nous servirons pas de l'application Mac OS X, seulement de l'application doxygen en ligne de commande qui y est incluse. Pour cela, j'ai ajouté les lignes suivantes dans mon fichier .bash_profile :

#
# Ajout du chemin pour Doxygen
#
export PATH=${PATH}:${HOME}/Applications/Doxygen.app/Contents/Resources

Éditez le fichier avec l'application Terminal, ensuite fermez la fenêtre qui vous a servi à éditer puis ouvrez-en une autre. Cette procédure permet que les modifications soient prises en compte.

Maintenant, ouvrez Xcode et créez un nouveau projet d'application Cocoa, nommez-le par exemple DoxygenTutorial. Dans son état initial, ce projet contient le strict minimum pour qu'une application Cocoa puisse être compilée et lancée sans erreur. Il y a principalement deux fichiers : main.m et MainMenu.xib.

Avec l'application Terminal, rendez-vous dans le dossier du projet DoxygenTutorial. La première opération, à réaliser une fois pour toutes, est de créer la configuration Doxygen pour le projet. Il s'agit d'un fichier nommé Doxyfile. La commande suivante permet de le créer (dans la fenêtre du Terminal, dans le dossier du projet) :

doxygen -g

Nous allons changer un tout petit peu la configuration. Ouvrez le fichier Doxyfile avec n'importe quel éditeur et modifiez les points suivants (le texte modifié est en gras, les ellipses indiquent les portions du fichier à ne pas changer) :

...
PROJECT_NAME           = DoxygenTutorial
...
OUTPUT_LANGUAGE        = French
...
EXTRACT_ALL            = YES
...
EXTRACT_PRIVATE        = YES
...
EXTRACT_STATIC         = YES
...
EXTRACT_LOCAL_METHODS  = YES
...
GENERATE_LATEX         = NO
...
HAVE_DOT               = YES
...

Ensuite, pour créer la documentation du projet, la commande est simplement la suivante :

doxygen

La documentation est créée dans un dossier nommé html. Ouvrez-là avec la commande :

open html/index.html

Vous devriez obtenir quelque chose comme cela :

Amorce de documentation

Si vous cliquez sur l'onglet Fichiers (pas celui ci-dessus, celui que vous avez généré chez vous) vous devriez obtenir la liste de tous les fichiers sources du projet, à savoir main.m. Vous devriez aussi avoir accès à tous les éléments que ce fichier déclare, à savoir la fonction main() avec la liste de ses arguments et un graphe de dépendances des fichiers inclus :

Documentation de main.m

Souvenez-vous que nous avons obtenu cela sans rien ajouter au code initial ! voyons maintenant quelques petits exemples de ce qu'on peut obtenir en documentant vraiment.

La documentation bien comprise

Documenter avec Doxygen signifie ajouter des commentaires spéciaux dans le code que l'outil reconnaitra et utilisera pour élaborer la documentation. Ces commentaires spéciaux sont introduits avec le signe de commentaire traditionnel auquel on ajoute une barre supplémentaire : ///. Dans ces sections de commentaires Doxygen, la documentation est écrite à l'aide de mots-clés qui servent à identifier la structure des cmmentaires.

Le premier exemple que je vous propose consiste à écrire une petite introduction destinée à la page de présentation de la documentation. Ajoutez les commentaires suivants dans le fichier main.m :

//
//  main.m
//  DoxygenTutorial
//
//  Created by mouviciel on 25/01/09.
//  Copyright Mouviciel 2009. All rights reserved.
//

/// \file
/// \brief Point d'entrée de l'application

/// \mainpage
///
/// Cette application a pour vocation d'illustrer l'utilisation de
/// Doxygen dans un projet Xcode/Cocoa.
///
/// \section Présentation
///
/// D'abord, quelques tags d'usage général sont introduits, tels que
/// \\file, \\brief, \\mainpage et \\section.

#import 

int main(int argc, char *argv[])
{
    return NSApplicationMain(argc,  (const char **) argv);
}

Relancez la commande doxygen, puis ouvrez à nouveau la page principale de la documentation et observez. Faites aussi un petit tour dans l'onglet Fichiers. Je vous laisse le soin d'ajouter la documentation que vous souhaitez.

Le deuxième exemple que je vous propose est la documentation de la fonction main(), ses arguments, ses valeurs de retour et son contenu. Voici une suggestion :

/// \brief Fonction principale de l'application
///
/// La fonction main() est invoquée au lancement de l'application.
/// Ses arguments permettent d'accéder aux paramètres de la
/// ligne de commande.
///
/// \param argc
///     Le nombre de paramètres présents sur la ligne de commande.
///     Il y a toujours au moins un paramètre.
/// \param argv
///     Liste des paramètres présents sur la ligne de commande.
///     Le premier paramètre est la commande elle-même.
///
/// \return
///     Le code de retour de la fonction, transmis au processus parent
///     sous forme de status de retour (accessible en shell via la
///     variable $?).
///
/// \section Implémentation
///
/// La fonction main() réalise les opérations suivantes:

int main(int argc, char *argv[])
{
    /// - Lancement de l'application Cocoa.
    return NSApplicationMain(argc,  (const char **) argv);
}

Après avoir lancé à nouveau doxygen, je vous invite à consulter la page de la documentation consacrée au fichier main.m. Remarquez par exemple l'hyperlien créé automatiquement sur chaque référence à la fonction main(). Ici c'est trivial et à peu près sans intérêt, mais dans des codes plus gros ce genre de référence entre les fichiers ou entre les classes devient très utile pour se retrouver.

Pour aller plus loin, je vous invite à consulter le manuel de Doxygen.

Intégrer Doxygen dans Xcode

L'intégration de Doxygen dans Xcode se passe à deux niveaux. D'une part, il est utile de pouvoir produire la documentation d'un projet depuis Xcode, soit en créant une target spécifique, soit en l'intégrant directement dans la compilation du projet. D'autre part, il est possible de produire une documentation accessible directement depuis le menu Help — Documentation de Xcode.

Dans ce qui suit, je vous montre comment intégrer l'étape de génération de documentation à la compilation. Il s'agit simplement d'exécuter un script shell à la fin de la construction de la cible. Voici la démarche.

Dans la fenêtre Xcode du projet DoxygenTutorial, dans la colonne Groups & Files, déployez la rubrique Targets ainsi que la cible DoxygenTutorial à l'intérieur. Cette cible contient les éléments Copy Bundle Resources, Compile Sources et Link Binary With Libraries. Nous allons ajouter un quatrième élément Run Script. Pour cela, sélectionnez la cible DoxygenTutorial puis activez l'élément de menu Project — New Build Phase — New Run Script Build Phase. Une fenêtre s'ouvre, dans laquelle vous allez écrire le script shell suivant :

# Localisation de doxygen et dot
DOXYGEN_PATH=${HOME}/Applications/Doxygen.app/Contents/Resources
PATH=${DOXYGEN_PATH}:${PATH}
export PATH

# Génération de la documentation
doxygen || exit 1

Voilà c'est tout ! Maintenant, chaque fois que vous cliquerez sur un des petits marteaux Build ou Build and Go, la documentation de votre logiciel sera produite.

Mais l'étape la plus intéressante est à venir : vous pouvez intégrer la documentation de votre propre logiciel dans le navigateur de documentation de Xcode. Bien sûr la présentation sera un peu différente, mais vous aurez toutes les facilités du navigateur telle la recherche rapide. Pour y parvenir, il suffit de modifier le fichier Doxyfile de configuration de Doxygen ainsi que le petit script que nous venons d'écrire.

D'abord, voici les modifications à apporter à Doxyfile :

...
GENERATE_DOCSET        = YES 
...
DOCSET_FEEDNAME        = "Documentation de DoxygenTutorial"
...
DOCSET_BUNDLE_ID       = fr.exemple.doxygentutorial
...

Ces modifications indiquent à Doxygen qu'il doit produire des fichiers d'index supplémentaires ainsi qu'un Makefile qui sera utilisé pour placer la documentation au bon endroit avec le bon format.

Ensuite, voici les modifications à apporter au script que nous venons d'écrire pour produire la documentation:

# Localisation de doxygen et dot
DOXYGEN_PATH=${HOME}/Applications/Doxygen.app/Contents/Resources
PATH=${DOXYGEN_PATH}:${PATH}
export PATH

# Génération de la documentation
doxygen || exit 1

# Génération du Documentation Set
make -C html install || exit 1

# Ouverture du Documentation Set dans Xcode
DOCSET_PATH=Library/Developer/Shared/Documentation/DocSets
open $HOME/$DOCSET_PATH/fr.exemple.doxygentutorial.docset || exit 1

Maintenant, cliquez sur le petit marteau de Build, et observez le résultat. Bravo ! Vous venez de documenter votre code et d'installer cette documentation aux côtés de la documentation Apple !

À partir d'ici, je vous laisse potasser la documentation de Doxygen et vous approprier cet outil très utile.

Une petite introduction au test de logiciel

Publié le dimanche 4 janvier 2009, aucun commentaire

Je l'avais évoqué dans mon premier article sur le contrôle de version, le test de logiciel fait partie des outils indispensables du développeur. C'est pourtant l'un des plus mal connus, des plus mal enseignés, et je dirais même l'un de ceux qui ont la plus mauvaise réputation parmi les développeurs.

Qu'est-ce que le test de logiciel ?

Pour commencer, je vous propose une petite question : Comment définiriez-vous le test de logiciel ? La réponse la plus courante est quelque chose du genre : Le test est le processus qui permet de vérifier que le logiciel réalise correctement sa fonction. Tel que je le présente, vous vous doutez que ce n'est pas la bonne réponse. Voici pourquoi.

Imaginez-vous dans la peau d'un testeur à qui on confie un logiciel à tester. Selon la définition du test ci-dessus, votre objectif est de vérifier que ce logiciel est correct. Vous passez donc en revue chaque fonction élémentaire du logiciel. Vous écrivez un test pour chacune qui montre qu'elle se comporte correctement. Consciemment ou non, délibérément ou non, vous choisissez vos données de test de façon à attendre votre objectif : vérifier qu'il n'y a pas d'erreur. Vos données ont alors une faible probabilité de provoquer une défaillance du logiciel.

Maintenant, je vais vous proposer une autre définition du test et je vous invite à répondre en toute honnêteté à la question suivante : Avec la définition ci-dessous, est-ce que vous choisiriez vos entrées de test de la même manière ? Voici :

Le test de logiciel est le processus qui, par l'exécution du programme, permet de découvrir des erreurs.

À première vue, il ne s'agit que d'une subtilité sémantique : au lieu de vérifier que le logiciel est correct, on s'attache à y chercher des erreurs. Cependant, se donner pour objectif de faire échouer le programme plutôt que de le faire réussir a un effet psychologique très important. Cela induit un état d'esprit très différent : Le testeur cherche à casser le programme. Pour lui, un test qui réussit est un test qui a trouvé une erreur et un test qui échoue n'a pas su trouver d'erreur.

Le deuxième point fondamental du test de logiciel, celui-ci est plus connu, est qu'il est impossible de démontrer que toutes les erreurs d'un programme ont été trouvées. Quel que soit l'effort consacré au test, personne ne peut garantir que cet effort est suffisant. Le test exhaustif de toutes les entrées possibles d'un programme est impossible, même pour un programme simple.

Imaginez par exemple un logiciel dont la fonction est de multiplier un nombre par deux. Un test exhaustif devrait d'abord essayer tous les nombres possibles. Il y en a une infinité. Supposons que nous sachions que le programme est limité aux nombres entiers dont la représentation binaire tient sur 32 bits. Il y en a plus de quatre milliards. Après avoir épuisé tous ces nombres, le test exhaustif devrait s'intéresser à tous les autres nombres pour bien vérifier que le programme produit une erreur. Puis ce serait au tour de tous les caractères et chaînes de caractères : je veux m'assurer que le programme ne répond pas 14 à l'opération A*2, ni 32 à SEIZE*2. Je vous laisse imaginer un test exhaustif du compilateur gcc, pour lequel il faudrait essayer non seulement tous les fichiers sources corrects mais aussi tous les fichiers sources incorrects...

Ainsi pour minimiser l'effort, l'objectif du test de logiciel ne peut-être que de trouver le plus d'erreurs possibles avec le moins de tests possibles, autrement dit d'avoir des tests efficaces et d'éviter les tests qui ont peu de chances de trouver des erreurs. Cela signifie que le test de logiciel demande un certain nombre de techniques et une bonne dose de créativité. Un bon testeur est un artiste dans son domaine.

Couverture du test et critères de test

Une notion dont les développeurs parlent souvent à propos du test est celle de couverture : Est-ce que je dois atteindre une couverture de 100% ? Est-ce que cet objectif est simplement réaliste ? Comment faire ? ...

Le plus couramment, les éléments qu'on cherche à couvrir sont les instructions du code. On essaie de s'assurer que toutes les branches then et toutes les branches else, toutes les alternatives case d'un switch, tous les corps de boucles while ou for, ... sont exécutés lors d'une campagne de test.

La première question qui se pose est : Est-ce que cet objectif est atteignable ? Ma réponse à cette question est oui. À condition de se donner les moyens. Si je me suis donné la peine d'écrire une portion de code pour gérer une certaine situation, alors je dois reproduire cette situation pour vérifier que mon code se comporte correctement. Cela peut exiger un gros effort de mise en place de l'environnement de test, par exemple la création d'une base de données complexe ou bien d'appareils divers simulant le contexte du logiciel et d'autres observant son comportement. J'ai deux remarques pour tempérer mon affirmation. D'abord, tout cela n'est pas toujours simple. Il se peut même que le jeu n'en vaille pas la chandelle si par exemple le coût du test est très supérieur au coût occasionné par une défaillance du logiciel. Une option possible est alors de pratiquer une vérification statique du code par inspection. Ensuite il est parfois possible d'identifier des parties du code qui ne sont jamais atteignables : il s'agit de code mort, qu'on pourra alors choisir de supprimer.

La deuxième question qui se pose est : Est ce que cette couverture est suffisante ? Ma réponse à cette question est non. Supposons par exemple que j'ai un code avec une boucle for. Ce code est couvert à 100% dès que j'ai un test qui passe une fois dans la boucle. Les bugs étant ce qu'ils sont, il est très possible qu'il y en ait un qui ne soit activé qu'à la septième itération. Pour pouvoir être complet, je devrais couvrir non seulement chaque instruction du code mais aussi chaque chemin possible dans le code. Un cas de test couvrirait par exemple le chemin suivant : sept itérations dans cette boucle for suivi du else de cette condition puis quatre itérations là, etc. Bien entendu le test de tous les chemins d'un programme est irréalisable de la même façon que le test de toutes ses entrées. Pire que cela, le test de tous les chemins ne couvre que les chemins qui existent : comment couvrir le cas où le développeur a oublié un chemin ?

On le voit, l'art du testeur est de choisir les cas de test qui feront échouer le programme le plus efficacement. Dans le cas du test des chemins, il peut s'agir de ne s'attacher qu'à certains chemins : un classique est l'ensemble des chemins qui existent entre la déclaration d'une variable et l'utilisation de sa valeur.

On voit aussi se dessiner différents objectifs pour le test, on parle de critères de test. Il s'agit de définir les cas de test selon les éléments du logiciel qu'on souhaite exercer. On a déjà évoqué :

On peut ajouter à cette liste non exhaustive :

Ainsi, le testeur a tout un ensemble de flèches à son arc qui lui permettent d'atteindre son but selon des points de vues multiples. Bien entendu, la théorie du test est un domaine bien plus vaste que ce que je viens de vous brosser. Il y a cependant un dernier aspect que je voudrais évoquer avec vous. Il s'agit du développement guidé par les tests.

TDD : le développement guidé par les tests

TDD est l'acronyme de l'anglais Test Driven Development (ou Design). En français cela donne quelque chose comme développement (ou conception) guidé (ou dirigé) par les tests. C'est une technique beaucoup pratiquée dans les méthodes agiles de développement, en particulier l'extreme programming.

En pratique, le critère de test est le besoin de l'utilisateur décliné en un certain nombre d'histoires qui décrivent chacune une fonctionnalité du logiciel. À chaque histoire correspond un cas de test.

La particularité du TDD est que le cas de test est écrit d'abord, c'est-à-dire avant le code qui réalise la fonctionnalité. Il y a deux idées derrière cela. La première est qu'en écrivant le test, le développeur crée un scénario qui utilise la fonctionnalité. Il a donc besoin d'avoir les idées claires sur ce qu'il attend de la fonctionnalité en tant qu'utilisateur, sinon il demande des précisions à son client. La deuxième idée est la suivante. Lorsque le test est écrit, il est exécuté. Normalement, il doit provoquer une défaillance du logiciel puisque la fonctionnalité n'est pas encore écrite. Il s'agit ici de s'assurer que le test est bien capable de provoquer une défaillance. En d'autres termes, le développeur vérifie que son test n'est pas une variante de printf("OK\n");.

Ensuite, le développeur écrit la fonctionnalité en codant juste ce qu'il faut pour que le test ne provoque plus de défaillance. Il ne doit pas ajouter de complexité dans le but de pouvoir étendre la fonctionnalité plus tard, il doit aller au plus simple.

Ce n'est que plus tard, lorsque son client lui demandera une extension ou une autre fonctionnalité susceptible de modifier le code de la première, qu'il pourra changer son code initial. À ce moment-là, le test qu'il a écrit lui servira à vérifier que ses changements n'introduisent pas de régression. La capacité à ce test de non régression est un apport très important du TDD. En offrant au développeur un moyen de s'assurer que ses modifications n'ont pas d'impact sur l'existant, elle lui permet d'améliorer sans cesse le code sans crainte de tout casser.

Le leitmotiv du TDD est ainsi tester, coder, améliorer. Enfin, pour un maximum d'efficacité, il est nécessaire que l'exécution des tests soit entièrement automatique, voire intégrée à la phase de compilation. Ainsi cette confiance des développeurs dans le code qu'ils ont produit est constamment renouvelée.

Voilà donc un petit aperçu du test de logiciel, dans lequel je vous ai proposé une définition du test, quelques éléments pour guider le choix des tests et une démarche de test qui permet de mieux maîtriser le besoin, le développement et la communication entre client et développeur. Comment mettre tout cela en pratique dans Xcode est une autre histoire que je vous raconterai peut-être un jour prochain...

Combiner plusieurs séquences QuickTime en une seule

Publié le dimanche 9 novembre 2008, aucun commentaire

Cet article s'éloigne un peu de Cocoa et parle de vidéo interactive et de scripts Unix.

Je vous propose de créer une séquence vidéo lisible par QuickTime qui joue plusieurs vidéos contenues dans un dossier.

Les points de départ et d'arrivée

Au début, j'ai un dossier, par exemple DCIM/100MOUVI (vous aurez peut-être reconnu la structure des dossiers d'un appareil photo numérique), qui contient quelques vidéos .MOV en plus des photos .JPG.

J'aimerais regrouper toutes les vidéos ensemble de manière à pouvoir les visionner en séquence confortablement installé dans mon canapé, sans avoir à faire quelque manipulation pour passer d'une vidéo à l'autre. Bien sûr, je voudrais que l'opération de regroupement soit réalisée avec le moins de manipulations possibles. Je n'ai pas envie d'y consacrer trop de temps, surtout lorsque j'ai plusieurs dizaines de vidéos à monter.

La solution proposée : SMIL

Pour parvenir à cet objectif, je vous propose d'utiliser SMIL, un langage qui permet de décrire des assemblages de fichiers multimédia tels que photos, sons, vidéos ou texte. Un fichier SMIL est semblable à un fichier HTML par sa structure à balises. Il peut être ouvert avec QuickTime, Realplayer et quelques autres applications.

Pour parler concrètement, voici un petit exemple de fichier SMIL que vous pouvez ouvrir avec QuickTime :

<smil>
<head>

<layout>
    <root-layout width="800" height="450" background-color="gray" />
    <region id="main" width="100%" height="100%" fit="fill" />
</layout>

</head>
<body>

<seq>
<video region="main" src="/System/Library/CoreServices/Setup%20Assista
nt.app/Contents/Resources/TransitionSection.bundle/Contents/Resources/
intro.mov" />
</seq>

</body>
</smil>

Attention, le chemin du fichier vidéo doit être écrit sur une seule ligne. J'ai choisi celui-ci parce que je suis à peu près certain qu'il est présent sur n'importe quel système Leopard. Mais vous pouvez bien entendu choisir un de vos propres fichiers vidéo et donner son chemin soit depuis la racine comme je l'ai fait, soit de façon relative à partir de l'emplacement de votre fichier SMIL.

Un fichier SMIL est ainsi composé de deux parties, l'en-tête marqué par la balise <head>, et le corps marqué par la balise <body>.

L'en-tête contient toutes les informations de mise en page ou plutôt de mise en écran. Il définit la taille de l'écran et sa couleur de fond, avec la balise <root-layout>. Il définit aussi des régions de l'écran (balise <region>) dans lesquelles n'importe quel objet multimédia pourra prendre place.

Le corps décrit quels sont les objets multimédia qui sont à jouer (avec les balises <video>, <audio>, <image>, <text>, <textstream> et <animation>) et de quelle manière les synchroniser (avec les balises <seq> pour un enchaînement en séquence, et <par> pour jouer plusieurs objets simultanément : plusieurs vidéos dans plusieurs régions ou une image accompagnée d'une musique).

La construction de la séquence

Pour combiner plusieurs séquences en une seule, nous n'aurons besoin que de la balise <seq> pour définir une séquence et de la balise <video> pour identifier chaque séquence. Nous avons une seule région à définir et l'écran a des proportions 4/3 comme la plupart des appareils photo numériques et des webcams.

Je confie à un script shell la localisation des fichiers de séquence et la création du fichier SMIL associé. Ce script recherchera tous les fichiers .MOV présents dans un APN branché au Mac et créera un fichier SMIL LISTE.SMI à la racine de l'APN (c'est-à-dire dans le même dossier que le dossier DCIM).

Voici le script que je vous propose :

#!/bin/sh

# dcim2smil.sh
# dcim2smil
#
# Created by mouviciel on 07/11/08.
# Copyright 2008 Mouviciel. All rights reserved.


# Spécification du ou des dossiers à explorer. Ici il s'agit
# d'appareils photos numériques, vus par le Mac comme des volumes
# contenant un dossier DCIM à leur racine. Si aucun des dossiers
# spécifiés n'existe, le script se termine.

dossiers="/Volumes/*/DCIM"
ls ${dossiers}/. > /dev/null 2>&1 || exit


# Il est possible que plusieurs APN soient branchés en même temps.
# Chacun aura droit à sa LISTE.SMI. La liste tient compte de la
# possibilité d'espaces dans le nom de l'APN, comme par exemple
# le traditionnel "NO NAME".

listeAPN=`ls -d -1 ${dossiers} | sed 's/%/%25/g;s/ /%20/g'`

for apn in ${listeAPN}
do

    # Reconversion des espaces pour avoir des noms en clair
    dcim=`echo ${apn} | sed 's/%20/ /g;s/%25/%/g'`
    
    # Recherche de toutes les vidéos dans l'APN.
    cd "${dcim}"
    listeMOV=`find . -name '*.MOV' | sort`
    
    # Construction du fichier LISTE.SMI lorsque des MOV sont
    # présents dans l'APN. Si un fichier LISTE.SMI existe déjà,
    # le script abandonne et passe à l'APN suivant.
    test -f "LISTE.SMI" && continue
    test -z "${listeMOV}" && continue

    cat << EOF_SMIL > LISTE.SMI
<smil>
<head>
<layout>
<root-layout id="root"
 width="640" height="480"
 background-color="black" />
<region id="r"
 width="100%" height="100%"
 fit="fill" />
</layout>
</head>
<body>
<seq>
    
`echo "${listeMOV}" | sed 's:^:<video region="r" src=":;s:$:" />:'`
    
</seq>
</body>
</smil>
EOF_SMIL

done

En lançant ce script, vous parcourez l'ensemble des appareils photo numériques branchés à votre Mac à la recherche de séquences vidéo, et pour chacun vous créez un fichier LISTE.SMI que vous pouvez glisser sur l'icône de QuickTime Player pour jouer l'enchaînement de toutes les vidéos présentes dans l'appareil.

Vous pouvez bien entendu changer le dossier à explorer en modifiant la valeur de la variable dossiers. Par exemple, si vous voulez rechercher les vidéos qui sont sur votre bureau et dans les dossiers présents sur celui-ci, modifiez le script de la façon suivante :

dossiers="${HOME}/Desktop"
ls ${dossiers}/. > /dev/null 2>&1 || exit

Dans ce cas, il sera alors peut-être utile de prendre en compte les fichiers .mov en plus des .MOV voire d'autres formats tels que .m4v ou .avi. De même, il faudra peut-être aussi s'occuper des caractères spéciaux dans les noms des fichiers et des dossiers. La modification suivante illustre l'ajout de .mov et le traitement des espaces :

    # Recherche de toutes les vidéos dans l'APN.
    cd "${dcim}"
    listeMOV=`find . -name '*.MOV' -or -name '*.mov' \
              | sed 's/%/%25/g;s/ /%20/g' | sort`

Attention, la barre descendante est immédiatement suivie d'un retour à la ligne, sans espace ni tabulation. Elle indique que la commande n'est pas finie et se poursuit sur la ligne suivante.

QuickTime et SMIL

QuickTime ne connaît que la première version de SMIL, qui en est à la troisième à ce jour. C'est suffisant pour répondre à notre besoin présent. En contrepartie, QuickTime propose quelques extensions à cette première version, par exemple la possibilité de lancer la séquence automatiquement à l'ouverture ou bien celle de la jouer en plein écran.

Par exemple, si vous souhaitez faire démarrer la séquence dès son ouverture, vous pouvez modifier le script de la façon suivante :

    cat << EOF_SMIL > LISTE.SMI
<smil
xmlns:qt="http://www.apple.com/quicktime/resources/smilextensions"
qt:autoplay="true" >
<head>

La première ligne en gras active les extensions de QuickTime. Elle doit être présente chaque fois que vous souhaitez utiliser une de ces extensions. La deuxième ligne spécifie que la séquence vidéo doit être démarrée dès son ouverture. Si vous souhaitez en plus ouvrir le film en plein écran, voici la modification à ajouter aux lignes précédentes :

    cat << EOF_SMIL > LISTE.SMI
<smil
xmlns:qt="http://www.apple.com/quicktime/resources/smilextensions"
qt:fullscreen="full"
qt:autoplay="true" >
<head>

Ainsi, le film se lancera automatiquement à son ouverture, qui plus est en plein écran.

Pour aller plus loin

À propos du script shell, parmi les améliorations possibles vous pouvez par exemple intégrer les images .JPG en leur donnant une durée d'affichage. De cette façon, vous obtenez un diaporama.

À propos de SMIL, j'ai évoqué la possibilité de faire jouer des vidéos en parallèle dans plusieurs régions de l'écran. Il est possible aussi d'ajouter de l'interactivité en cliquant sur une région de l'écran pour faire jouer une partie prédéfinie du film, comme une sorte de DVD rudimentaire, ou bien pour ouvrir une application tierce qui ouvrira un autre fichier, du HTML avec Safari ou bien un flux vidéo avec QuickTime Player. Même si les possibilités restent limitées, il y a beaucoup de petits trucs qui peuvent être réalisés avec SMIL et QuickTime.

Initiation à CoreAnimation — Interlude

Publié le vendredi 24 octobre 2008, un commentaire

Dans la première partie, nous avons installé la structure pour travailler avec CoreAnimation et nous avons affiché une image dans une CALayer. Avant de vous proposer de poursuivre avec ces bases en altérant l'image par le biais de CoreAnimation, je voudrais faire évoluer l'interface de l'application CoreAnimationTutorial de façon à ce qu'on puisse modifier divers paramètres sans avoir à recompiler.

En fait, cet article n'est pas vraiment spécifique de CoreAnimation. C'est plutôt une initiation à Interface Builder. Mais comme les prochaines étapes nécessiteront de toucher à l'interface graphique, je voudrais détailler la modification de l'interface une première fois afin d'être plus concis les fois suivantes.

Troisième figure — Un pupitre de contrôle

Pour commencer sur des bases communes, et après vous avoir laissé expérimenter les propriétés de placement (contentsGravity) et de cadrage (contentsRect) de l'image, je vous invite à modifier les dernières lignes de la méthode -awakeFromNib de la façon suivante :

    // Placement de l'image
    [[animationView layer] setContentsGravity:kCAGravityResize];

    // Cadrage de l'image
    [[animationView layer]
     setContentsRect:CGRectMake(0, 0, 1, 1)];
}

Cela nous ramène aux valeurs par défaut du placement et du cadrage de l'image.

Ensuite, nous allons ajouter sous l'image un pupitre de contrôle qui nous permettra de modifier les paramètres d'affichage sans recompiler. Cela se passe d'abord dans Xcode puis dans Interface Builder. L'objectif de ces préliminaires est d'arriver à une fenêtre qui ressemble à ceci :

Apparence de la fenêtre à la fin des préliminaires

Dans Xcode, ajoutez un IBOutlet et une IBAction dans le fichierAppController.h qui serviront à contrôler les boutons radio :

@interface AppController : NSObject {
    IBOutlet NSView * animationView;
    IBOutlet NSMatrix * gravityMatrix;
}

-(IBAction)gravityMatrixDidChange:(id)sender;

@end

Maintenant ouvrez MainMenu.xib dans Interface Builder.

Ajoutez un Radio Group (dans la palette Library, shift-cmd-L, rubrique Cocoa — Views & Cells — Inputs & Values) en bas à gauche de la fenêtre de l'application. Ajustez les propriétés de ce Radio Group de la façon suivante (cela se passe dans la palette Inspector, shift-cmd-I) :

Maintenant, changez le label de chacun des boutons radio : double-cliquez sur le label Radio et remplacez sa valeur par la valeur que je vous ai donnée dans l'image ci-dessus.

Mettez ce groupe de boutons radio dans une boîte : Vérifiez que le groupe est toujours sélectionné (le titre de la palette Inspector commence par Matrix), puis menu Layout — Embed Objects In — Box. Déplacez la Box à distance juste suffisante pour répondre aux préconisations d'Apple : des lignes pointillées bleues apparaissent lorsque c'est le cas. Changez le titre en Contents Gravity.

Dans la palette Inspector, rubrique Size de la boîte (son titre est Box Size) Ajustez l'Autosizing de façon à ce que la boîte ne change pas de dimensions et reste collée en bas au centre de la fenêtre :

Réglage Autosizing du Contents Gravity

Ajoutez maintenant une Custom View en haut de la fenêtre (dans la palette Library, shift-cmd-L, rubrique Cocoa — Views & Cells — Layout Views). Ajustez ses dimensions à Height: 216 et Width: 384. Placez-la en haut à gauche de la fenêtre sans laisser de marge avec le bord. Déplacez la boîte Contents Gravity juste sous la Custom View (jusqu'à ce que le trait bleu apparaisse). Ajustez la taille de la fenêtre pour être aussi large que la Custom View et juste assez haute pour laisser apparaître la boîte Contents Gravity (encore une fois avec le trait bleu). Le résultat doit être celui de la première image.

Ajustez l'Autosizing de la Custom View de la façon suivante pour indiquer qu'elle doit occuper tout l'espace dont elle dispose :

Réglage Autosizing de la Custom View

Sélectionnez la fenêtre en cliquant sur sa barre de titre. Dans palette Inspector, rubrique Size (titre Window Size), cochez la case Has Minimum Size et cliquez sur le bouton Use Current. Les dimensions minimales de la fenêtre devraient être 384 par 358.

Sauvez (cmd-S). Simulez (cmd-R). Les aspects graphiques sont prêts.

Maintenant, il s'agit de mettre en place les connexions entre les éléments graphiques et l'App Controller. Dans la fenêtre principale MainMenu.xib (English) faites ctrl-clic sur l'objet App Controller pour faire apparaître sa palette des connexions. Les éléments importants sont les deux Outlets animationView et gravityMatrix, ainsi que dans la rubrique Received Actions l'action gravityMatrixDidChange:.

À droite de chacun de ces éléments il y a un petit cercle qui devient un signe + lorsque la souris passe par dessus. Si vous cliquez et faites glisser la souris depuis un de ces cercles, une ligne bleue se dessine et suit le pointeur de la souris. Lorsque la souris passe au dessus d'un élément de l'interface graphique approprié, celui-ci est entouré d'un rectangle bleu. Si vous relâchez le bouton de la souris à cet instant, l'élément de l'App Controller est alors connecté à l'élément de l'interface graphique. Réalisez l'opération pour les trois couples d'éléments suivants :

Sauvez (cmd-S). Nous en avons fini avec Interface Builder.

Enfin, la dernière étape consiste à écrire l'action gravityMatrixDidChange: pour qu'elle modifie la propriété contentsGravity de la CALayer selon le choix que l'utilisateur aura saisi dans le groupe de boutons radio. Je vous propose le code suivant, à insérer dans AppController.m juste avant le @end :

-(IBAction)gravityMatrixDidChange:(id)sender
{
    NSDictionary *gravityNamesAndValues =
    [NSDictionary dictionaryWithObjectsAndKeys:
     kCAGravityCenter, @"Center",
     kCAGravityTop, @"Top",
     kCAGravityLeft, @"Left",
     kCAGravityBottom, @"Bottom",
     kCAGravityRight, @"Right",
     kCAGravityTopLeft, @"Top Left",
     kCAGravityTopRight, @"Top Right",
     kCAGravityBottomLeft, @"Bottom Left",
     kCAGravityBottomRight, @"Bottom Right",
     kCAGravityResize, @"Resize",
     kCAGravityResizeAspect, @"Resize Aspect",
     kCAGravityResizeAspectFill, @"Resize Aspect Fill",
     nil];
    
    NSString * gravity = [gravityNamesAndValues
                          objectForKey:[[sender selectedCell] title]];
    
    [[animationView layer] setContentsGravity:gravity];
}

@end

Compilez et lancez (cmd-R). Changez la gravité du contenu, redimensionnez la fenêtre. Voilà, nous avons le cadre qui nous permettra d'interagir avec CoreAnimation ! Bien sûr, pour l'instant nous ne pouvons manipuler que le placement de l'image. Mais je voulais entrer dans le détail de la construction d'une interface graphique de façon à être plus succint lorsque nous ajouterons d'autres paramètres à modifier.

Initiation à CoreAnimation — Première partie

Publié le vendredi 17 octobre 2008, aucun commentaire

CoreAnimation est un mécanisme de Leopard qui permet de donner du mouvement aux éléments de l'interface en exploitant le plus possible le processeur graphique. Le meilleur exemple est l'animation de TimeMachine.

J'écris cet article, et d'autres qui suivront, alors que je découvre CoreAnimation. Il s'agit d'une espèce de carnet de notes sur lequel j'enregistre ce que j'apprends. En le publiant ici j'espère que d'autres partageront cette initiation à CoreAnimation en même temps que moi.

D'autres articles existent sur le sujet. Il y a d'abord la documentation d'Apple, notamment le Core Animation Programming Guide qui peut être un bon point d'entrée au reste des documents officiels. Ensuite une série de cours sur CoreAnimation existe en français chez Saint Sylvain's Ranch. Enfin, plusieurs cas d'école sont disponibles en anglais chez Theocacao en particulier NanoLife et ArtGallery.

Première figure — Mettre CoreAnimation dans une fenêtre

Interface Builder propose un certain nombre de vues spécialisées, par exemple NSOpenGLView pour l'OpenGL, QTMovieView pour une séquence QuickTime, WebView pour un navigateur web, etc. Cependant, rien n'est proposé en standard pour CoreAnimation. Heureusement, la procédure n'est pas difficile.

Pour illustrer cela, je vous propose de créer un nouveau projet d'application Cocoa dans Xcode. Nommez-le CoreAnimationTutorial. Ajoutez une classe à ce projet : Menu File—New File..., puis Objective-C class. Nommez-la AppController.m et choisissez de créer aussi AppController.h. Il s'agit du contrôleur de l'application qui sera le cadre de nos expérimentations.

Ensuite, ouvrez le fichier qui décrit l'interface graphique MainMenu.xib. Nous allons y ajouter le controleur que nous venons de créer. Si ce n'est pas déjà fait, ouvrez la palette Library (cmd-shift-L) et la palette Inspector (cmd-shift-I).

Dans la palette Library trouvez l'élément Object de classe NSObject et faites-le glisser dans la fenêtre nommée MainMenu.xib (English). Cette fenêtre contient la description complète de l'interface graphique de l'application.

Assurez-vous que l'objet que vous venez de faire glisser est sélectionné, puis dans la palette Inspector choisissez l'onglet Identity. Dans la rubrique Class Identity Choisissez la classe AppController (c'est la classe que nous avons créée).

À ce stade, les fondations de notre application sont posées. Nous pouvons commencer les spécificités de CoreAnimation.

La base de CoreAnimation est la classe CALayer qui est analogue dans son rôle et sa structure à la classe NSView : Elle permet de faire des affichages à l'écran et de définir une arborescence d'objets CALayer. Concrètement, cela signifie que nous travaillerons dans des CALayer au lieu des NSView. Il faut donc trouver un moyen de placer une CALayer dans une fenêtre.

La technique est d'associer une CALayer à une des views de la fenêtre de l'application, grâce à la méthode setLayer: de NSView. Il s'agira de la racine de l'arborescence des CALayer. Ses dimensions sont celles de la view qui l'héberge. Voici la procédure.

D'abord, il faut que l'AppController ait connaissance de la view qu'on souhaite utiliser pour CoreAnimation. Cela se fait en deux étapes, la première dans Xcode et la deuxième dans Interface Builder. Pour la première étape, ajoutez une ligne de code à AppController.h :

//
//  AppController.h
//  CoreAnimationTutorial
//
//  Created by mouviciel on 03/10/08.
//  Copyright 2008 Mouviciel. All rights reserved.
//

#import <Cocoa/Cocoa.h>


@interface AppController : NSObject {
    IBOutlet NSView * animationView;
}

@end

Pour la deuxième étape, reprenez Interface Builder, puis ctrl-clic sur l'objet appController pour faire apparaître ses interfaces. Cliquez dans le petit cercle à droite de l'outlet animationView et faites glisser la souris en maintenant le bouton enfoncé jusqu'à la contentView de la fenêtre.

Maintenant que l'AppController a connaissance de la view à utiliser pour CoreAnimation, il reste à associer à cette vue une CALayer racine. Cela se passe dans Xcode : un framework à ajouter au projet et une méthode à ajouter à AppController.m. CoreAnimation est disponible via le framework QuartzCore. Il suffit de l'ajouter aux Frameworks déjà dans le projet (normalement, il y a seulement Cocoa dans Frameworks/Linked Frameworks). Pour cela, faites ctrl-clic sur Frameworks/Linked Frameworks puis choisissez dans le menu qui s'affiche Add—Existing Frameworks.... La fenêtre qui s'affiche vous propose de choisir dans le dossier Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks. Dans la liste, choisissez QuartzCore.framework.

Cela fait, vous pouvez modifier AppController.m de la façon suivante :

//
//  AppController.m
//  CoreAnimationTutorial
//
//  Created by mouviciel on 03/10/08.
//  Copyright 2008 Mouviciel. All rights reserved.
//

#import <QuartzCore/QuartzCore.h>
#import "AppController.h"


@implementation AppController

-(void)awakeFromNib
{
    // Ajout et activation d'une CALayer
    [animationView setLayer:[CALayer layer]];
    [animationView setWantsLayer:YES];
}

@end

Voilà, votre fenêtre est prête à recevoir des animations ! Pour l'instant ce n'est pas vraiment spectaculaire, mais la suite vous montre ce qu'on peut faire à partir de ça.

Deuxième figure — Afficher une image dans une CALayer

Dans son Core Animation Programming Guide, Apple indique qu'il y a trois façons d'ajouter du contenu à une CALayer :

Pour la présente initiation, je me contente d'aborder la première technique, à savoir fournir directement une image à la CALayer.

Pour créer une CGImageRef à partir d'un fichier ou d'une URL représentant une image, le plus simple est d'utiliser une CGImageSourceRef qui sert d'intermédiaire entre une CGImageRef et une URL :

-(void)awakeFromNib
{
    // Ajout et activation d'une CALayer
    [animationView setLayer:[CALayer layer]];
    [animationView setWantsLayer:YES];
    
    // Ajout d'une image dans la CALayer
    NSString * imagePath =
        @"/System/Library/CoreServices/DefaultDesktop.jpg";
    CFURLRef imageURL = (CFURLRef)[NSURL fileURLWithPath:imagePath];
    CGImageSourceRef imageSource =
        CGImageSourceCreateWithURL(imageURL, NULL);
    CGImageRef image =
        CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
    CFRelease(imageSource);
    [[animationView layer] setContents:(id)image];
}

Compilez et lancez l'application... Voilà ! Vous savez afficher une image qui occupe toute la fenêtre en se déformant si nécessaire. Si vous souhaitez éviter les déformations, vous pouvez modifier la valeur de la propriété contentsGravity. Par exemple, ajoutez les lignes suivantes à la fin de -awakeFromNib :

    [[animationView layer] setContents:(id)image];

    // Placement de l'image
    [[animationView layer] setContentsGravity:kCAGravityResizeAspect];
}

Cette fois, l'image apparaît en entier et sans déformation, avec des marges sur deux des côtés lorsque la fenêtre n'a pas les mêmes proportions que l'image. Si vous préférez supprimer les marges et acceptez que l'image soit tronquée, la valeur à donner est kCAGravityResizeAspectFill. La valeur par défaut, qui donne le comportement que nous avons observé au début, est kCAGravityResize.

Il est aussi possible d'afficher l'image dans sa dimension naturelle. Il suffit de donner à la même propriété contentsGravity une autre valeur que kCAGravityResize.... Les valeurs possibles spécifient une position :

Enfin, si vous ne désirez pas travailler l'image entière mais seulement avec une partie recadrée, vous pouvez modifier la valeur de la propriété contentsRect. Il s'agit d'un rectangle qui spécifie la portion de l'image à utiliser. Les valeurs qui définissent ce rectangle (abscisse et ordonnée de l'origine, largeur et hauteur du rectangle) sont exprimées en fractions de la taille de l'image, c'est-à-dire entre 0 et 1. Essayez par exemple d'ajouter ces lignes à la fin de -awakeFromNib :

    // Placement de l'image
    [[animationView layer] setContentsGravity:kCAGravityResizeAspect];

    // Cadrage de l'image
    [[animationView layer]
     setContentsRect:CGRectMake(0.3, 0.2, 0.3, 0.3)];
}

À suivre...

Avec cet exercice, nous avons fait un tout premier pas dans CoreAnimation. Comme je l'ai déjà dit, il s'agit d'une auto-initiation que je souhaite partager avec vous. La prochaine étape consistera à modifier d'autres paramètres de l'image affichée et de voir comment CoreAnimation assure la transition progressive d'un réglage à l'autre.

Contrôle de versions et Xcode — Deuxième partie

Publié le dimanche 5 octobre 2008, 3 commentaires

Dans la première partie sur le contrôle de versions, j'avais parlé des bénéfices qu'un développeur pouvait tirer du contrôle de versions en général. Dans cette partie j'entre dans le concret pour vous montrer les réglages à faire afin d'utiliser Subversion sur un projet Xcode.

Pourquoi Subversion avec Xcode ?

J'ai conclu la première partie en indiquant qu'il existait de nombreux outils de contrôle de version et que ce choix se limitait à trois dès qu'on travaillait avec Xcode :

Ainsi, les critères qui ont guidé mon choix de Subversion sont l'intégration dans Xcode, la disponibilité dans Leopard et la combinaison de maturité et de modernité. Il est temps maintenant de passer aux choses concrètes.

Créer un dépôt Subversion

Le principe de fonctionnement de Subversion est le suivant. Quelque part sur votre disque dur ou ailleurs existe un dépôt Subversion qui contient l'historique de tout votre code. Le développeur commence son travail en allant chercher dans le dépôt une copie du code qu'il souhaite modifier, c'est l'opération de checkout. Cette copie est placée dans un dossier qui joue le rôle d'espace de travail privé. Lorsque les modifications sont finies, le développeur les ajoute au dépôt grâce à l'opération de commit.

En général il est utile que plusieurs développeurs aient accès au dépôt. C'est pourquoi la plupart du temps ce dépôt est disponible via un serveur Subversion ; divers protocoles sont possibles, y compris http notamment via Apache. Je n'ai pas envie d'aborder ici l'installation d'un serveur Subversion, mon propos est d'illustrer le client Subversion intégré à Xcode. Mais pour cela j'ai tout de même besoin d'un dépôt Subversion. Le plus simple est donc d'en créer un sous forme d'un dossier sur votre disque dur. Cela se passe dans le Terminal, la commande est la suivante (le dépôt est créé dans ~/Subversion) :

svnadmin create $HOME/Subversion

Une autre solution presque aussi simple est de créer un dépôt Subversion auprès d'un hébergeur spécialisé. Parmi ceux qui ont une offre gratuite, il y a myVersionControl que je ne connais pas et Beanstalk que j'utilise régulièrement sans souci.

Une dernière précision : Un dépôt Subversion peut contenir plusieurs projets, il n'est pas nécessaire de créer un dépôt par projet.

Préparer Xcode

La préparation de Xcode nécessite deux opérations distinctes. La première opération consiste à informer Xcode de l'existence du dépôt Subversion que vous venez de créer.

Pour cela, choisissez le menu SCM—Configure SCM Repositories.... La fenêtre qui s'affiche est la rubrique SCM des préférences de Xcode. Choisissez le premier onglet, nommé Repositories, puis cliquez sur + en bas de la première colonne, intitulée elle aussi Repositories. Donnez un nom à votre dépôt, par exemple monDepotSubversion, puis choisissez Subversion pour SCM System. Enfin dans le champ URL: tapez le chemin d'accès au dépôt que vous venez de créer, par exemple chez moi c'est :

file:///Users/mouviciel/Subversion

Si tout se passe bien, vous devriez voir en bas du formulaire une bille verte annonçant Authenticated.

Pour la deuxième opération de préparation, j'aimerais attirer votre attention sur le fait que seules les sources sont à gérer par un système de contrôle de versions. J'utilise le terme de source dans le sens général de quelque chose à l'origine d'autre chose : cela recouvre plus que le code source et inclut toutes les ressources d'une application comme les icônes, les fichiers de configuration, les Makefiles, ... C'est-à-dire tout ce qui est nécessaire pour reconstruire l'application et seulement cela. En particulier les exécutables de l'application ne doivent pas être insérés dans le contrôle de version. Dans Xcode, il s'agit du dossier build d'un projet. Il faut donc trouver un moyen de l'exclure du contrôle de version.

Il s'agit d'une simple préférence de Xcode. Par défaut, celui-ci place le dossier build à l'intérieur du dossier du projet. Mais il est possible de choisir un autre emplacement, soit pour tous les dossiers build de tous les projets, soit pour un projet spécifique :

Préparer le projet

Ici, il s'agit de préparer l'arborescence du projet pour qu'elle contienne tout ce qu'on souhaite importer dansSubversion et seulement cela. Si votre projet n'existe pas encore, il vous suffit de le créer et de sauter le paragraphe qui suit. Sinon il y a peut-être un peu de ménage à faire.

En effet, même si vous avez modifié l'emplacement du dossier build, cela ne prendra effet qu'avec la prochaine compilation. Le dossier build qui était présent dans votre projet existe toujours. Il est donc nécessaire de le supprimer avant d'importer le projet dans Subversion. Pour cela, il vous suffit de le glisser dans la corbeille.

La deuxième étape de la préparation du projet vise à respecter l'usage recommandé par Subversion : un projet est un dossier qui contient à son tour trois autres dossiers nommés trunk (le tronc commun de votre projet, c'est-à-dire tout le contenu actuel de votre dossier de projet), branches (les diverses branches de développements, pour corriger un bug ou ajouter une fonctionnalité, ...) et tags (les versions référencées de votre application que vous souhaitez retrouver rapidement).

Pour cela, le Finder suffit. Dans votre dossier de projet, créez un dossier trunk et glissez-y tous les fichiers et dossiers du projet. Ensuite, créez les dossiers branches et tags à côté de trunk.

Importer le projet

Les opérations relatives au contrôle de versions sont réalisées dans Xcode via la fenêtre qui s'affiche en sélectionnant le menu SCM—Repositories. Dans la colonne Repositories à gauche, choisissez votre dépôt monDepotSubversion, puis cliquez sur l'outil Import à gauche dans la barre d'outils.

Localisez votre projet, le dossier qui contient trunk, branches et tags, ajoutez un commentaire qui décrit ce que vous êtes en train de faire, par exemple :

monSuperProjet: Importation du projet dans le dépôt Subversion.

Créer l'espace de travail

Voilà, le projet est importé ! Maintenant il reste une dernière précaution à prendre : le dossier du projet que vous venez d'importer n'est pas un espace de travail Subversion. Même après importation, il reste un dossier simple. Pour créer un espace de travail Subversion il faut récupérer le dossier depuis le dépôt Subversion par une opération de checkout.

D'abord, pour éviter de vous embrouiller entre le dossier simple et l'espace de travail, je vous recommande de glisser le dossier simple à la corbeille. N'ayez pas d'inquiétude, tout est dans le dépôt. Ensuite, je vous suggère de ne récupérer que le tronc commun du projet, le trunk. Voici la marche à suivre.

Dans Xcode, choisissez le menu SCM—Repositories, puis votre dépot Subversion dans la colonne de gauche. Dans le navigateur multicolonnes, choisissez le dossier de votre projet, puis le dossier trunk à l'intérieur de celui-ci. Enfin, cliquez sur l'outil Checkout de la barre d'outils. Choisissez l'emplacement de l'espace de travail de votre projet, nommez-le du nom de votre projet plutôt que la proposition trunk. Finissez l'opération en cliquant sur le bouton Checkout puis sur Open de la fenêtre modale suivante.

Il reste un tout petit détail d'importance : modifier les préférences du projet pour le lier au dépôt Subversion. Affichez les préférences du projet via le menu Project—Edit Project Settings, puis sélectionnez l'onglet General. Presque en bas de la fenêtre, il y a une liste déroulante intitulée SCM Repository: dont la valeur est None. Il suffit de changer cette valeur pour le nom du dépôt d'où vient le projet. Normalement il est indiqué avec un —Recommended à la fin.

Voilà ! vous pouvez commencer à travailler dans votre projet géré en contrôle de versions.

Enregistrer les modifications

La dernière opération, celle que vous utiliserez régulièrement, consiste à enregistrer les modifications au fur et à mesure de votre développement. Pour ma part, j'utilise le menu SCM—Commit Entire Project... qui ouvre une fenêtre dans laquelle je peux saisir le commentaire de commit. En général je décris de façon plus ou moins détaillée sur quoi ont porté les nouvelles modifications.

Récapitulons...

Voilà donc les diverses manipulations qui vous permettent d'utiliser Subversion dans Xcode. Je les reprends ici :

Maintenant, je vous laisse vous familiariser avec tout cela. Je vous invite à consulter la documentation de Subversion pour aller plus loin dans votre prise en main. Et bien sûr, les commentaires sont les bienvenus !

Archive

Rubriques

Abonnement