Mokona Guu Center

Compilation et notion de projet

Publié le

Lorsque l'on programme, il y a un certain nombre de tâches qui gravitent autour de la programmation proprement dite. Des tâches qui peuvent potentiellement faire perdre du temps. Le temps est précieux (et celui qui gère votre budget ne se privera pas d'ajouter que votre temps coûte cher) et il est donc plus intéressant de le passer à faire des choses qui font progresser réellement le programme.

J'ai déjà dit récemment ce que je pensais des commentaires inutiles, voire trompeurs. La conclusion rapide est qu'il faut éviter de faire deux fois la même chose, d'autant plus si ces deux choses peuvent se retrouver désynchronisées, sauf au prix d'un effort inutile.

Dans la même veine, et c'est le sujet de cet article, se trouve la notion de projet vu par certains éditeurs intégrés de développement (IDE).

Ces éditeurs possèdent, dans leur fichier de projet, une vue des fichiers nécessaires à la compilation. Le programme sera compilé à partir de ces fichiers. J'appelle ces systèmes des fichiers de projet explicites.

À chaque nouveau fichier, chaque changement de nom, chaque suppression, chaque déplacement, l'éditeur doit être renseigné. Certaines de ces opérations (souvent, l'effacement et le changement de nom) sont optionnellement répercutées sur le disque, mais pas toujours.

Pire, certains éditeurs offrent une notion de filtre pour architecturer la vue des fichiers qui ne correspond pas vraiment à la notion de répertoire sur le disque, mais dont l'usage est, dans la plupart des cas, de refléter les répertoires du disque. Et dans les projets où les répertoires ne sont pas reflétés par ces filtres, il s'agit à chaque fois d'opérer cette séparation dans l'éditeur car elle n'est pas faite sur le disque.

Et il arrive régulièrement ce qu'il doit arriver lorsque la même information doit être maintenue à deux endroit différents, en parallèle et de manière identique : l'oubli !

Ce n'est pas de la théorie, c'est du vécu et de l'entendu. Jusqu'à hier 1.

Hier encore, donc, j'entendais la mésaventure d'un programmeur archivant son travail après avoir rigoureusement vérifié que tout fonctionnait, puis être pris d'un affreux doute une fois rentré chez lui, confirmé peu de temps après par la réception de mails des machines de compilation lui disant que son archivage avait cassé quelque chose. L'erreur ? Un fichier de projet mal archivé. Pas un fichier oublié, non, juste la description explicite que ce fichier, nouveau et présent, devait être pris en compte.

Le pire, c'est lorsque l'éditeur n'aide pas le programmeur à archiver son projet correctement. Pas d'avertissement, un connecteur avec le gestionnaire de version pas tout à fait au point, des changements au projet nécessitant une sauvegarde plutôt que de travailler sur un fichier persistant,...

Un autre soucis ? N'avez-vous jamais eu à merger deux fichiers de projets de deux branches divergentes ? Si oui, vous comprenez ce que je veux dire.

Quels sont, de l'autre côté, les avantages de travailler avec un fichier de projet ? Je n'en vois pas. Pas sur un projet physiquement bien architecturé.

D'ailleurs, d'autres éditeurs de développement 2, de leur côté, ont choisi d'offrir une vue des fichiers du projet reflétant ce qui est trouvé sur le disque. Ainsi, tous les manipulations faites à travers l'éditeur sont reflétés sur le disque et, surtout, les manipulations faites sur le disque sont reflétées dans l'éditeur. Pas de double travail inutile !

C'est sur mon disque, c'est dans le projet

Puisqu'un système de fichiers contient déjà (ou peut contenir) les informations sur la structure des fichiers sources d'un programme, l'idée est de se reposer sur lui pour cette partie. Un ou plusieurs fichiers de directives de compilation (que ce soit un projet généré par un IDE ou un Makefile) déterminent ensuite les actions à faire sur les fichiers.

Quand j'expose cette manière de faire, j'ai souvent en retour quelques inquiétudes et quelques questions sur la manière de reproduire certains comportements qui sont, dans les IDE avec fichier de explicite, paramétrés par l'application.

Comment faire un « exclude from build » ?

La première question à se poser est : si c'est sur le disque, et encore pire, dans un fichier de projet explicite, pourquoi l'exclure d'une compilation ?

Je connais les raisons : fichiers à inclure ou pas suivant la cible de compilation, la plateforme,... J'ai même déjà vu des fichiers identiques dans un même répertoire dont certains servaient à une cible et une autre sous partie servaient à une autre cible, avec parfois des fichiers communs entre les deux cibles.

Le problème, si on met de côté celui flagrant du manque d'architecture physique de tels projets, est que les IDE à fichiers de projets explicites n'offrent pas du vue globale de ces configurations. Il est extrêmement facile de s'y perdre, d'oublier quoi est compilé pour quoi, et les modifications de configuration prennent beaucoup de temps.

La solution à ce problème est d'architecturer implicitement les fichiers à compiler selon les cas. Et comme c'est une solution pour le point suivant, patientons un peu.

Multi-plateforme

Une question que l'on me pose, voisine de celle du dessus, est celle de la gestion multi-plateforme. Dans un logiciel multi-plateforme, dans un langage de type C++, plus on descend dans les couches basses, plus il existe des parties qui ne sont destinées qu'à une plateforme.

Les solutions (mauvaises car de maintenance difficile et des changement d'échelle atroce) sont le plus souvent des cas particuliers à coup de #ifdef, mélangé à de l'exclude from build. Ou encore des fichiers de projets explicites séparés pour chaque plateforme (avec le bonheur de devoir maintenir en parallèle tous les fichiers de projet à chaque changement d'existence ou nommage d'un fichier).

Une solution simple, implicite, existe pourtant : les fichiers pour une plateforme particulière se trouvent dans un répertoire particulier. Ce guide simple est ensuite à adapter au projet : un sous-répertoire dans chaque « sous-système » concerné, des architectures complètement séparés, de l'héritage entre classes communes et classes natives,... Les techniques sont nombreuses.

Mais le principe initial est simple et fonctionne aussi pour l'exclude from build : grouper les fichiers qui sont compilés ensemble.

La plupart des projets d'envergure font déjà ça pour séparer des « sous-systèmes » de l'application. Il ne faut pas hésiter à aller un cran plus loin dans la séparation des fichiers.

Ainsi, les règles de compilation peuvent être implicites et simples à lire. Une bibliothèque sur la plateforme A est constitué des répertoire « lib/src/lib » et « lib/src/A » par exemple (avec un meilleur nommage que celui-ci).

Pause

Comme on peut le voir, se reposer sur la structure du système de fichier force à réfléchir un peu plus à son architecture physique et évite des situations où tout est mélanger : ça apporte de la clarté. On pourrait rapprocher cela de l'utilisation des tests unitaires, voire du TDD : ce qui est vu en premier lieu comme une contrainte se retrouve être un excellent moyen de programmer naturellement sans introduire de fortes dépendances.

Mais je vais devoir écrire un Makefile !

C'est possible. C'est même plutôt une bonne chose. Écrire un Makefile vous affranchie de l'IDE et peut s'écrire en une fois pour plusieurs plateformes. Plus de multiples fichiers de projets à maintenir, la source est unique. Plus de merge de branches casse tête, un ou plusieurs fichiers textes de description sont plus simples que des fichiers de projets explicites (et encore, les IDE récents ont le bon goût de maintenir ces projets sous forme de texte, il fut un temps ou le format binaire non mergeable primait).

De plus, le Makefile décrivant les éléments généraux constitutifs de la construction du programme plutôt que chaque fichier explicitement, il est assez rapidement plus petit en taille et moins verbeux qu'un fichier décrivant chaque fichier.

Mais vous n'aimez pas les Makefile ? Vous n'avez jamais rien compris à sa syntaxe ? Vous êtes tout simplement allergique car vous avez été forcé de l'utiliser pendant vos études ?

Il existe bien d'autres système de construction autre que LE Makefile : CMake, SCons, la famille des BJam, Ant, Maven,... Ce n'est pas le choix qui manque. Chacun a ses particularités, ses environnements de prédilection, mais ils sont tous mieux qu'une solution intégrée et opaque. Mieux dans le sens où ils sont plus claires en offrant une vision plus simple de la construction d'un projet, mieux dans le sens plus flexible, mieux dans le sens indépendant d'une solution visuelle ou d'une plateforme, mieux dans le sens d'intégration à un robot de compilation.

Mais je veux profiter des outils de mon IDE !

Et vous avez bien raison ! Du moment que ces outils vous font gagner du temps, ne serait-ce que parce que vous les connaissez, il serait dommage de s'en passer. Et il se trouve que la plupart des outils travaillent sur le projet ouvert en court.

Qu'à cela ne tienne, la plupart des outils de compilation mentionnés dans le paragraphe ci-dessous sont capables de générer des fichiers de projets explicites pour les IDE qui en ont besoin. Vous pouvez même, par la même occasion, générer différents projets en fonction des besoins (cibles de compilation, plateforme) qui prendront en compte les fichiers particuliers à ces besoins. Et le tout avec un minimum de difficulté, et donc un minimum d'erreur.

Oui mais...

Oui, il y a encore d'autres questions. J'ai même eu droit au fameux « mais ce n'est pas comme ça qu'on fait d'habitude »... Je vous invite à me poser des questions sur la mise en place de ce genre de système de construction de projet, mais pas celle-ci.

Oui, aussi, la solution n'a pas que des avantages. Mais les inconvénients apportés me semblent faibles par rapport aux avantages.

Un inconvénient : le calcul des dépendances. Un IDE avec fichier explicite calcul les dépendances en tâche de fond. Un des nombreux mécanismes, soit dit en passant, qui fait qu'ils ne sont pas toujours très réactifs. C'est un avantage : au démarrage de la compilation, le build démarre rapidement. Avec une solution externe, les dépendances sont calculées en début de construction, et ce n'est pas toujours bien rapide. La compilation met plus de temps à démarrer.

C'est un semi-problème pour moi. D'abord car il est temporaire : j'espère voire un jour arriver, si ce n'est pas déjà fait, un service qui vérifiera les changements effectués sur le disque sur des répertoires de projet pour les notifier au système de construction, qui pourra mettre à jour sa base en tâche de fond. Ensuite, car, en regardant d'un peu plus près, je me suis aperçu que de nombreux projets utilisaient des systèmes de compilation répartie qui mettent aussi pas mal de temps à s'initialiser, que ce soit par calcul de dépendances, calcul des répartitions ou envoie des fichiers.

Mais cela peut devenir problématique avec de gros fichiers et de grosses dépendances. C'est indéniable.

Un autre inconvénient pourrait être celui d'apprendre un langage de description utilisé par le système. C'est un ticket d'entrée. Il est largement remboursé.

Je suis preneur d'autres inconvénients dans les commentaires ou par mail.

Dans les faits

Cela fait quelques années maintenant que j'ai abandonné l'idée de maintenir un fichier de projet contenant des informations redondantes avec le contenu du disque dans mes projets personnels. Le fichier de projet se contente, pour chaque bibliothèque ou chaque programme, de pointer vers un (ou plusieurs, mais généralement un seul) répertoire contenant tout ce qui doit être compilé.

Plus récemment 3, l'équipe avec laquelle je travaille professionnellement utilise la même méthode avec succès.

Au bureau, il ne nous reste plus qu'un type d'erreur sur la gestion des fichiers, le « oops j'ai oublié d'ajouter un fichier à la base ». Chez moi, je n'ai même plus cette erreur, grâce à l'utilisation de gestionnaires de version indiquant clairement les fichiers non encore ajoutés à la base.

Plus d'erreurs de ce type, c'est moins de perte de temps pour soi ou pour l'équipe entière, c'est aussi une plus grande confiance et une plus grande facilité à bouger des choses dans la base de code et donc plus de confiance à faire du remaniement, à faire vivre le programme et l'améliorer sereinement.


  1. En fait, depuis le début de l'écriture de ce billet, c'est encore arrivé dans un projet utilisant un fichier de projet explicite. 

  2. Eclipse et IntelliJ IDEA par exemple. 

  3. depuis deux ans tout de même.