Ant est un peu un sac mystère. Son comportement est souvent obscur jusqu’à ce que vous veniez à regarder son code. Ensuite, vous constatez qu’il se compose d’un certain nombre d’installations assez simples qui sont souvent expliquées d’un point de vue détaillé et technique ascendant et non d’un point de vue architectural descendant. Cet article vise à fournir la vue descendante manquante. Il s’adresse à un public d’ingénieurs logiciels. Armé de cet article et de quelques avis solides sur quand et quand ne pas utiliser cet outil, vous devriez être capable de trouver votre chemin dans la fourmilière.
Histoire, héritage et impact des fourmis
Si vous n’avez jamais entendu parler d’Ant, ne vous inquiétez pas. Ant est encore un autre outil dans le domaine de la création de logiciels. C’était la première tentative d’un outil de construction pour Java. Lorsqu’il a été conçu, XML était à la mode et C était le moyen d’exprimer un logiciel que Java imitait. Par conséquent, Ant a été influencé par la pensée de l’outil make de C. Combinant ces deux tendances, Ant est un hybride curieux qui n’a pas la concision ou la reproductibilité stricte de la marque, mais qui a la syntaxe verbeuse XML. Parlez du meilleur des deux mondes.
Cependant, le plus grand défaut d’Ant est probablement son manque d’opinion et d’idées fermes sur les exigences d’une construction et d’un cycle de vie de projet basés sur Java, ou même de n’importe quel autre. Cela laisse aux développeurs beaucoup de liberté pour créer de nombreux bogues de construction insidieux et difficiles à trouver. À certains égards, Ant est le C++ des outils de construction : un petit cœur avec une surcharge de rémunération faible, une puissance possible substantielle et un potentiel explosif de catastrophe nécessitant des heures de travail.
Avec la boîte à savon à l’écart, plongeons-nous dans les concepts et l’architecture d’Ant.
Architecture de haut niveau : une forêt de cibles
À son sommet conceptuel se trouve un mécanisme de dépendance qui produit une forêt (formellement, une arborescence multiple) d’objectifs possibles que vous, en tant que développeur, souhaitez atteindre, appelés cibles.
Chaque projet Ant est décomposé en une séquence d’éléments appelés « cibles » qui ont des identifiants comme noms. Il s’agit généralement d’activités conventionnelles telles que « compiler », « tester », « archiver », etc.
Chaque cible peut déclarer des dépendances sur d’autres cibles. En conséquence, chaque projet contient un certain nombre d’arbres d’exécution. Soit l’utilisateur peut en sélectionner un pour l’exécution par son nom, soit un marqueur ‘par défaut’ spécial est exécuté.
Cela signifie également qu’au niveau des cibles, Ant n’est pas un langage de programmation. Quoi qu’il en soit, pour une seule exécution Ant, il ne peut parcourir qu’en avant dans l’arbre de dépendance cible.
Vous trouverez ci-dessous un diagramme de classe de ces concepts de base. Il existe de nombreux outils pour rendre visible l’arbre de dépendance des cibles :
Analyse
Notez que la section précédente parlait de projets, pas de fichiers de build. Alors que les projets Ant sont presque toujours écrits en XML et analysés par un analyseur XML, cela ne doit pas nécessairement être le cas. L’analyseur est remplaçable en remplaçant l’interface Ant connue sous le nom de ProjectHelper. En effet, l’arbre des dépendances réelles du projet peut être construit en mémoire à la suite de l’exécution du programme.
Gradle est une fourmi sous stéroïdes
Cette idée se reflète dans certaines parties du système de construction Gradle. Dans Gradle, l’équivalent d’un ProjectHelper spécifique est généré en tant que classe compilée avec des entrées définies. En conséquence, ce programme compilé construit très rapidement un arbre d’exécution minimal qui est ensuite exécuté d’une manière bien définie. Bien sûr, Gradle a utilisé sa propre infrastructure il y a longtemps, mais les origines sont toujours visibles dans son mécanisme d’exécution en deux phases, sa facilité de réutilisation des classes Ant Task et sa prise en charge du gestionnaire de référentiel ivy d’Ant.
Anecdotes sur l’analyse des fourmis
Il y a deux points intéressants sur le processus d’analyse :
- Ant a un concept de « prélude » ou par défaut : comme indiqué ci-dessus, ses opérations sont généralement définies dans des « cibles ». Cependant, il existe un niveau supérieur d’opérations qui ne sont pas étendues. Comme l’indique le manuel, depuis Ant 1.6.0, chaque projet inclut une cible implicite qui contient toutes les tâches de niveau supérieur […]. Cette cible sera toujours exécutée dans le cadre de l’initialisation du projet […].
- Ant est théoriquement « polyglotte » : il peut « assembler » un projet à partir d’une source avec différentes représentations syntaxiques. Il peut « importer » d’autres sources du Prélude et le ProjectHelper correspondant les analysera.
Effets secondaires via les tâches
Les cibles Ant s’exécutent en appelant des séquences d’outils Java regroupés via une interface appelée Task. Il y en a beaucoup dans la distribution, certains plus contribués et encore plus flottants sur Internet. Il n’est pas difficile de pirater le vôtre, mais il peut être difficile de le faire correctement. Plug sans vergogne ici : La partie 2 de cette série montre comment le faire.
L’analyseur instancie chaque tâche de la cible, à son tour, lui fournit des paramètres, généralement sous forme de chaînes, puis déclenche la méthode d’exécution des tâches.
Objets dans Ant : composants personnalisés
La section précédente a contourné une définition solide des types de paramètres. Il a dit que les paramètres sont généralement des chaînes. Cela signifie que parfois ils ne le sont pas.
Prenons un exemple : comment coordonneriez-vous une chaîne d’outils de traitement de fichiers vidéo ? Vous pouvez écrire les paramètres des fichiers plusieurs fois, chaque fois que vous les fournissez à un outil. Et puis vous pouvez ajouter les paramètres pour chaque exécution d’outil. Mélangé à la syntaxe de XML, ce serait si moche et long que vous voudriez revenir à un script bash ou à Powershell.
Pour éviter cela, Ant nous permet de définir des objets Java en tant que paramètres. Pour clarifier leurs propriétés possibles, ceux-ci doivent avoir des types associés. Ces types supplémentaires sont déclarés à l’aide d’une instruction typedef. Dans Ant, ces types spéciaux sont appelés « composants personnalisés », mais le manuel précise qu’en fait, ce sont de simples Java Beans :
Un composant personnalisé est une classe Java normale qui implémente une interface particulière ou étend une classe particulière ou a été adaptée à l’interface ou à la classe. […] On définit des attributs et des éléments imbriqués en écrivant des méthodes setter et en ajoutant des méthodes.
Donc, en supposant que nous créions le type ImageFile, vous pouvez maintenant créer :
Mais en quoi cela aide-t-il, à moins que vous ne puissiez passer cet objet par référence d’une manière ou d’une autre ?
Références d’objets et modèle de mémoire
En effet, le projet Ant a une forme de mémoire d’objet principal ou de tas. Il s’agit d’un Mapid
‘attribut.
Désormais, tout endroit qui utilise ces éléments peut les référencer. Les pointeurs peuvent provenir de plusieurs endroits, en utilisant un ‘refid
‘notation. Donc, en supposant que nous ayons défini :
Nous pourrions maintenant extraire l’audio ailleurs par référence :
Reproductibilité et état de fond
Bien entendu, rien n’exige que ces objets soient immuables. Ils peuvent fonctionner comme des entrées en direct, des canaux de communication, des tampons de stockage ou tout ce que vous souhaitez, avec les conséquences correspondantes basées sur l’état.
Le comportement de votre build n’est reproductible que si tous les types utilisés dans le build sont basés sur des classes immuables et que votre environnement aux tâches utilisées est identique. Si vous rencontrez un comportement inhabituel, c’est l’aspect formel que vous devez contrôler.
Composants personnalisés polymorphes
Bien sûr, vous avez déjà repéré un autre aspect de cette approche : si ces composants personnalisés ne sont en effet que des classes Java, ils peuvent également être basés sur des interfaces et des classes abstraites. Les « objets fonctionnels » associés peuvent être définis sur la base d’une API commune et acceptés par un ensemble de tâches.
Pour notre exemple de média, vous pourriez penser à quelque chose comme des « paramètres de codec » qui combinent un codec avec sa configuration et peuvent être référencés par un certain nombre d’outils de traitement dans la construction.
Composants personnalisés stéréotypés : intégrés
Il existe un grand nombre de types personnalisés disponibles et la description dans le manuel ne révèle pas vraiment la structure ou l’intention. Je n’ai vraiment compris ce qui se passait que lorsque j’ai répertorié toutes les classes dérivées de DataType dans mon IDE Eclipse. Vous trouverez ci-dessous une capture d’écran qui montre la hiérarchie des classes du FileSet, un objet utilitaire pour décrire et accéder à un groupe de fichiers qui est utilisé comme entrée par plusieurs tâches de traitement de fichiers.
Et juste au cas où vous penseriez que mon exemple de traitement vidéo a été inventé, voici l’ImageOperation intégrée qui vous permet de dessiner des ellipses et des carrés colorés avec Ant, puis de les faire pivoter et de les mettre à l’échelle. Parce que pourquoi devriez-vous utiliser SVG quand vous pouvez avoir Ant ?
Je ne vais pas approfondir ici la sémantique des différents types et tâches associées, car il s’agit plus d’une question de design patterns que d’architecture de l’outil lui-même.
Attributs, propriétés et interpolation
Alors que les tâches peuvent prendre tous les types d’objets comme paramètres, soit via une construction d’imbrication, soit via une référence à des objets existants, le type le plus courant, et celui qui se suggère dans la syntaxe XML est un objet de type String qui apparaît dans la syntaxe XML d’Ant comme un Attribut d’un élément.
Ce type de paramètre est extrêmement courant et se définit également facilement. Cela semble également suggérer que ces paramètres sont généralement du texte lorsqu’ils sont transmis aux tâches, ce qui est généralement vrai, et nous reviendrons sur les différences plus tard.
Les propriétés de Ant ont l’air simples, mais sont facilement la partie la plus complexe du cadre lorsqu’une lentille est appliquée aux détails. Ils peuvent aussi être totalement déroutants. Pour que l’histoire reste cohérente, nous décrirons ci-dessous la conception historique d’origine, puis nous examinerons les forces qui ont incité le changement et enfin nous présenterons le système hautement flexible d’aujourd’hui.
Conception traditionnelle
Dans la conception originale de Ant, les paramètres de texte ont reçu un traitement spécial pour éviter les répétitions avec un modèle de mémoire appelé Propriétés. Les propriétés sont un magasin d’ajout uniquement de type Map
La grande fête de la propriété
La conception originale d’Ant utilisait l’interpolation de chaîne pour remplacer les références aux clés par des valeurs basées sur la simple notation de remplacement du shell bash Unix. Voici un exemple:
Dans cet exemple, une tâche ou un composant personnalisé ‘elem
‘ est construit en utilisant un paramètre ‘param’ qui est tiré de ce que Ant a stocké dans sa carte sous la clé ‘key
.’
Propriétés de l’auto-boxing
Toutes les tâches ne prennent pas en entrée les paramètres de chaîne. En théorie, il n’y a aucune raison pour qu’un paramètre soit différent d’un élément imbriqué. Pour résoudre ce problème, les premières versions d’Ant fournissaient déjà une analyse implicite à un ensemble fixe de types, principalement des analyses de types primitifs, des notations de chemin et des énumérations.
Bootstrap et valeurs par défaut
L’ensemble de propriétés qui était présent dans un projet au démarrage, le « property boot-strap », était composé de deux sources :
Après ce point, n’importe quelle tâche peut faire un appel à son projet setUserProperty()
méthode et définir une nouvelle propriété. La tâche la plus courante à utiliser à cette fin est la tâche de propriété.
« Certainement pas des variables ».
La documentation de la tâche de propriété indique clairement que les propriétés utilisateur Ant et les propriétés Java ne sont pas les mêmes :
« Les propriétés sont immuables : celui qui définit une propriété en premier la gèle pour le reste de la construction ; ce ne sont certainement pas des variables. »
Si jamais vous vous demandez quelles propriétés vous pouvez voir depuis l’endroit où vous vous trouvez dans la construction, la tâche Echoproperties vous montrera ce qui est disponible. Veuillez noter que cela peut varier considérablement selon la façon dont vous y êtes arrivé, car la définition des propriétés peut être conditionnelle et variera en fonction des cibles traversées jusqu’au point d’exécution actuel.
La refonte de tout est possible
Les…