62 votes

Quelle est une bonne façon d'organiser les projets avec des dépendances partagées dans Mercurial ?

Actuellement, je suis en train de quitter un ancien système de contrôle de version et de transférer le projet de mon groupe vers mercurial. Pour illustrer le type de code que je déplace, j'ai une solution Visual Studio de plus de 25 projets, contenant plusieurs domaines d'application distincts qui dépendent tous d'un code commun. En regardant sur Stack Overflow, la question la plus proche que j'ai trouvée est la suivante celui-ci mais il ne fait que mentionner le contrôle de version. Je cherche un conseil un peu plus poussé, sur les techniques spécifiques de mise en œuvre de l'utilisation de Mercurial pour gérer ces dépendances.

Une vue simplifiée des dépendances ressemble à ce qui suit. (Ceci n'est qu'une illustration et un exemple ; les dépendances réelles sont nettement plus complexes mais de nature similaire).

                     Common Lib 1
                    /      |      \
                ----       |       -----   
               /           |        \   \
            App 1    Common Lib 2    \ App 2
                       /   |   \      \
                -------    |    ------ |
               /           |          \|
             App 3       App 4      App 5

Les modules de la bibliothèque commune seraient du code partagé - il s'agirait d'une DLL, d'une SO ou d'une autre bibliothèque utilisée simultanément par toutes les applications, tant au moment de la compilation que de l'exécution. Sinon, les applications pourraient fonctionner indépendamment les unes des autres.

J'ai quelques objectifs avec la mise en place de mes dépôts mercuriens :

  • Donnez à chaque application ou groupe de composants importants son propre référentiel.
  • Faites en sorte que chaque dépôt soit autonome.
  • Faire en sorte que la totalité du projet soit autonome.
  • Faciliter la construction de l'ensemble du code en une seule fois. (Au final, tous ces programmes et bibliothèques se retrouvent dans un seul installateur).
  • Restez simple.

Un autre point est que j'ai un serveur configuré où j'ai des dépôts séparés pour chacun de ces projets.

Je vois deux façons d'organiser ces projets.

1. Créez un référentiel "Shell" qui contient tout.

Cela utiliserait des sous-références basées sur des url (par exemple, dans le .hgsub, je ferais quelque chose comme App1 = [https://my.server/repo/app1](https://my.server/repo/app1) .) Voici comment cela se présente :

+---------------------------+
| Main Repository           |
| | +---------------------+ |
| +-| Build               | |
| | +---------------------+ |
| | +---------------------+ |
| +-| Common Lib 1        | |
| | +---------------------+ |
| | +---------------------+ |
| +-| Common Lib 2        | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 1               | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 2               | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 3               | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 4               | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 5               | |
|   +---------------------+ |
+---------------------------+

Chaque dossier principal du référentiel shell contiendrait un sous-répertoire, un pour chaque zone de projet. Les dépendances seraient relatives : Par exemple, si l'application 4 a besoin de la bibliothèque commune 2, elle utilisera simplement des chemins relatifs pour référencer cette bibliothèque commune.

Les avantages de cette approche :

  • Chaque bibliothèque est tirée vers le bas une fois et une seule.
  • Les sous-références de Mercurial garantissent que la même version de la bibliothèque est utilisée dans tous les projets automatiquement, puisqu'une seule version de cette sous-référence existe dans le projet.
  • Il est facile de trouver chaque ressource.

Inconvénients de cette approche :

  • Je ne peux pas travailler sur une application indépendamment. Par exemple, si je travaille sur l'application 2, et qu'elle a besoin d'un changement dans les bibliothèques communes, toutes les autres applications devront prendre ces changements maintenant.
  • Si je tire un dépôt d'applications par lui-même, je dois découvrir (ou connaître) les autres dépôts dépendants dont il a besoin si je veux le construire.
  • Les dépendances ne sont pas fortement séparées - il serait tentant d'insérer une nouvelle fonctionnalité n'importe où puisqu'il est facile d'accéder à toutes les fonctionnalités.

2. Faire en sorte que les sous-prépositions dépendantes soient entièrement contenues.

Dans cette approche, chaque application aurait son propre dépôt (comme auparavant) mais cette fois-ci, il contiendrait également des sous-dépôts : un pour ses propres sources, et un pour chaque sous-dépôt dépendant. Un référentiel global contiendrait alors chacun de ces référentiels de projet, et saurait comment construire la solution entière. Cela ressemblerait à ce qui suit :

+-----------------------------------------------------------------------+
| Main Repository                                                       |
| +--------------------+ +--------------------+ +--------------------+  |
| | Build              | | Common Lib 1       | | Common Lib 2       |  |
| +--------------------+ | | +--------------+ | | | +--------------+ |  |
|                        | +-| Lib 1 Source | | | +-| Common Lib 1 | |  |
|                        |   +--------------+ | | | +--------------+ |  |
|                        |                    | | | +--------------+ |  |
|                        |                    | | +-| Lib 2 Source | |  |
|                        |                    | |   +--------------+ |  |
|                        +--------------------+ +--------------------+  |
| +--------------------+ +--------------------+ +---------------------+ |
| | App 1              | | App 2              | |  App 3              | |
| | | +--------------+ | | | +--------------+ | |  | +--------------+ | |
| | +-| Common Lib 1 | | | +-| Common Lib 1 | | |  +-| Common Lib 2 | | |
| | | +--------------+ | | | +--------------+ | |  | +--------------+ | |
| | | +--------------+ | | | +--------------+ | |  | +--------------+ | |
| | +-| App 1 Source | | | +-| App 2 Source | | |  +-| App 3 Source | | |
| |   +--------------+ | |   +--------------+ | |    +--------------+ | |
| +--------------------+ +--------------------+ +---------------------+ |
| +--------------------+ +--------------------+                         |
| | App 4              | | App 5              |                         |
| | | +--------------+ | | | +--------------+ |                         |
| | +-| Common Lib 2 | | | +-| Common Lib 1 | |                         |
| | | +--------------+ | | | +--------------+ |                         |
| | | +--------------+ | | | +--------------+ |                         |
| | +-| App 4 Source | | | +-| Common Lib 2 | |                         |
| |   +--------------+ | | | +--------------+ |                         |
| +--------------------+ + | +--------------+ |                         |
|                        | +-| App 5 Source | |                         |
|                        |   +--------------+ |                         |
|                        +--------------------+                         |
+-----------------------------------------------------------------------+

Pour :

  • Chaque application peut être construite par elle-même, indépendamment les unes des autres.
  • Les versions dépendantes des bibliothèques peuvent être suivies par application, plutôt que globalement. Il faut un acte explicite d'insertion d'un subrepo dans le projet pour ajouter une nouvelle dépendance.

Cons :

  • Lors de la construction finale, chaque application peut utiliser une version différente d'une bibliothèque partagée. (Il faudra peut-être écrire des outils pour synchroniser les sous-répertoires de la bibliothèque commune. Beurk).
  • Si je veux construire l'ensemble des sources, je finis par tirer plusieurs fois sur les bibliothèques partagées. Dans le cas de Common Lib 1, je devrais le faire huit ( !) fois.

3. N'incluez pas du tout les dépendances en tant que sous-prépositions - apportez-les dans le cadre de la construction.

Cette approche ressemblerait beaucoup à l'approche 1, sauf que les bibliothèques communes ne seraient extraites que lors de la construction. Chaque application saurait de quels dépôts elle a besoin, et les placerait dans l'emplacement commun.

Pour :

  • Chaque application pourrait se construire toute seule.
  • Les bibliothèques communes n'auraient besoin d'être tirées qu'une seule fois.

Cons :

  • Nous devrions garder la trace des versions des bibliothèques actuellement utilisées par chaque application. Cela fait double emploi avec les fonctionnalités du subrepo.
  • Nous devrions construire une infrastructure pour supporter cela, ce qui signifie plus de choses dans les scripts de construction. Ugh.

4. Quoi d'autre ?

Y a-t-il une autre façon de procéder ? Une meilleure façon ? Quels moyens avez-vous essayés et réussis, quels moyens avez-vous essayés mais détestés ? Je penche actuellement pour la 1, mais le manque d'indépendance des applications, alors qu'elles devraient en être capables, me dérange vraiment. Y a-t-il un moyen d'obtenir la séparation agréable de la méthode 2 sans le cauchemar de la duplication massive du code et de la maintenance des dépendances, tout en n'ayant pas à écrire de scripts pour le gérer (comme dans l'option 3) ?

6voto

barjak Points 4682

La gestion des dépendances est un aspect important de l'organisation d'un projet, à mes yeux. Vous avez exposé en détail différentes solutions, basées sur la fonctionnalité subrepos de Mercurial, et je suis d'accord avec tous les avantages et inconvénients que vous avez donnés.

Je pense que les SCM ne sont pas bien adaptés à la gestion des dépendances. Je préfère avoir un outil dédié pour cela (ce serait votre solution n°3).

Mon projet actuel est en Java. Il a été construit avec Apache Ant et j'ai d'abord mis en place Apache Ivy comme outil de gestion des dépendances. Au final, l'installation consiste en quelques fichiers de configuration d'Ivy dans un répertoire partagé, et un fichier XML listant les dépendances pour chaque module du projet. Ivy peut être invoqué par des cibles Ant, j'ai donc ajouté deux nouvelles actions dans chaque module : "résoudre les dépendances", et "déployer l'artefact construit". Le déploiement ajoute le résultat de la construction (appelé artefact) dans le répertoire partagé. La résolution des dépendances signifie la résolution transitive des dépendances du module, et la copie des artefacts résolus dans le dossier "lib" des sources du module.

Cette solution est applicable à un projet C++, car Ivy n'est pas spécifique à la gestion des dépendances Java : les artefacts peuvent être n'importe quoi. En C++, les artefacts produits par un module seraient :

  1. un so/dll au moment de l'exécution
  2. les fichiers d'en-tête au moment de la compilation.

Ce n'est pas une solution parfaite : Ivy n'est pas facile à mettre en place, vous devez toujours indiquer à votre build script quelles dépendances utiliser, et vous n'avez pas d'accès direct aux sources des dépendances à des fins de débogage. Mais vous vous retrouvez avec des dépôts SCM indépendants.

Dans notre projet, nous sommes ensuite passés de Ant+Ivy à Apache Maven qui s'occupe à la fois de la construction et de la gestion des dépendances. Les artefacts sont déployés dans un Apache Archiva au lieu d'un dossier partagé. Il s'agit d'une amélioration considérable, mais elle ne fonctionnera que pour les projets Java.

2voto

Jiri Klouda Points 902

Ce que vous voulez faire est d'avoir chaque projet dans son propre répertoire comme dans (1). Ensuite, vous marquez les versions de travail de vos dépendances et sauvegardez la marque dans un fichier pour la construction, par exemple :

App1/.dependencies:
CommonLib1 tag-20100515
CommonLib2 tag-20100510

App2/.dependencies:
CommonLib1 tag-20100510
CommonLib2 tag-20100510

Ensuite, vous utilisez vos scripts de construction pour construire les bibliothèques en fonction de l'étiquette spécifique et vous incluez ces bibliothèques construites comme objets dérivés pour vos applications. Si le temps de construction est un problème, vous pouvez avoir la version étiquetée qui est utilisée pour ces bibliothèques préconstruites et enregistrées quelque part.

Note (les principes de conception sont les mêmes si l'on conçoit un schéma de base de données, un modèle d'objet ou un produit) :

  • Ne pas faire de liens vers le code d'autres projets (rupture de l'encapsulation).
  • Ne pas avoir de copies multiples des bibliothèques dans votre dépôt (modularité)

0voto

k3b Points 5381

Nous avons résolu un problème similaire en utilisant subversion.

Chaque application et chaque Common Lib ont leurs propres référentiels.

Chaque application a un répertoire Libs qui contiennent les dlls dépendantes.

pour que l'application ne reçoive qu'une mise à jour de Common Lib si un nouveau jeu de dlls est fourni.

Cependant, la mise à jour du dossier lib n'est pas triviale car les sous-dlls dépendants doivent correspondre à la bonne version.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X