Non licet omnibus adire Lexpagum    —  John Duff

Discussions

Package managers

Guybrush 8351 Bob
Bonjour,

Je suis en train d'effectuer des analyses concernant les graphes de dépendances entre les paquets au sein de plusieurs écosystèmes logiciels, typiquement ceux qui sont associés à un package manager pour un langage de programmation donné. A cet égard, j'essaie de collecter également des informations plus spécifiques sur les conventions, règles implicites, etc. des différents écosystèmes et package managers. J'en connais spécifiquement certains, comme pip/PyPi, npm, etc. mais évidemment, je ne suis pas du tout spécialiste dans tous les package managers.

Ceux que l'on considère pour l'instant sont :
- Cargo (crates.io, pour Rust)
- CPAN (perl)
- CRAN (R)
- Packagist (php)
- Rubygems (Ruby)
- NuGet (.NET)
- npm (Javascript)

Pour chacun de ces écosystèmes, j'essaie de glaner des informations telles que :
- Quelle est la convention pour la numérotation des versions ? Est-ce imposé ? Est-ce juste suggéré ? Par exemple, pour npm et cargo, le semver est imposé, alors qu'il est juste suggéré pour Rubygems.
- Quelles sont les contraintes de version que l'on peut spécifier à l'installation d'un logiciel, ou pour les dépendances ? Par exemple, npm permet à peu près tout, via ~, ^, >, <, ... alors que NuGet opte pour un système de range [1.0.0, 2.0.0).
- Quelle est la politique de l'écosystème en matière de publication et de mise à jour de paquets ? Est-ce que n'importe qui peut publier un package (npm) ou est-ce qu'il y a un minimum requis (approbation par un comité, comme sur CRAN, nécessité d'avoir de la documentation + des tests comme sur CRAN, etc...)
- Comment le package manager gère-t-il l'installation d'un paquet (ou d'une dépendance) ? Par exemple, Nuget installe la plus ancienne version qui satisfait les contraintes de version, alors qu'ailleurs c'est en général la plus récente qui est utilisée.
- Comment les versions d'une même dépendance peuvent-elles être installées ? Par exemple, rubygems et pip n'autorisent pas plusieurs versions d'un même paquet au sein d'un même environnement, idem pour les dépendances, alors que npm installe les dépendances localement dans des sous-répertoires et permet l'installation de plusieurs versions d'une même dépendance au sein de paquets différents.
- Est-ce que le package manager utilise un solver type SAT pour résoudre ces problèmes ? Par exemple, pour pip, ce n'est pas le cas et c'est emmerdant dans certains cas (mais ça va être réglé), alors que pour debian, y a un outil en amont (coinst, développé notamment à Paris Diderot il y a quelques années par un gars très très sympa ^^).
- etc.

Je pense savoir qu'il y a quelques utilisateurs Rust/.NET/Js ici, alors je suis preneur de toute information que vous aurez à me fournir à ce sujet (parce que je n'ai pas grand grand chose pour l'instant, ce que je donne comme exemples ci-dessus est juste ce que j'ai pu lire rapidement en fouillant les sites) ^^

Merci ;-)
trinity 230 Wookie
Vite fait pour NuGet et sans être une pro (je consomme plus que je ne génère, et les générations sont automatisées dans notre système de build généralement):

- tout ce que j'utilise est de type semver, je ne suis pas sure si c'est une obligation ou pas mais je n'ai jamais vu quelque chose qui différait jusque là.
- pour les contraites de versions, oui je pense que ce sont des ranges et après il te met les binding redirect dans ton app.config à l'installation dans le projet (pas toujours d'ailleurs, parfois il faut le mettre à la main pcq ca pète au runtime)
- pour la politique de publication, chaque équipe en interne gère son repository nuget en fait et les règles sont propres à chaque team. Au début on avait une solution très custom et manuelle, là de plus en plus de teams basculent vers VSO par exemple pour gérer le repository.
- installation de versions + dependences, c'est très chiant quand tu as plein de packages dans un projet/solution qui dépendent d'autres packages, par exemple Newtonsoft.Json. Je suis quasi obligée de mettre des binding redirect partout pour utiliser la version la plus haute à chaque fois et forcer les anciennes à utiliser celle-là. Nuget est assez mauvais pour gérer ce genre de chose, on a beaucoup de mauvaises surprises en regardant nos csproj parfois et il faut souvent corriger à la main pcq il a foutu la merde dans tes versions. Ou parfois il peut ne pas tout avoir updaté dans ton csproj sans que tu saches trop pourquoi.
- tu peux installer plusieurs versions différentes dans une solution dans des projets différents mais pas au sein d'un même projet. Ca peut être risqué pcq suivant l'ordre de build et les references cross-project que tu peux avoir, tu peux te retrouver avec la mauvaise lib au final dans ton build output. Là où VS est très mauvais (mais ca doit être un problème assez complexe à résoudre), c'est quand un projet A reference une lib par NuGet, et que tu fais une référence à A dans B. Les references ne sont pas toujours bien gérées (même en installant parfois le package dans le projet B, ca ne marche toujours pas) et tu te retrouves souvent à devoir copier des dlls à la main dans ton projet B ou à te faire un script MsBuild qui le fait pour toi... Je n'ai pas trouvé de solution simple à ce jour, et c'est un problème récurrent sur les forums.

Donc voilà NuGet, ca m'a fait gagner beaucoup de temps, mais quand ca marche mal ca peut te faire perdre un temps monstre :-D
Guybrush 8351 Bob
trinity- tout ce que j'utilise est de type semver, je ne suis pas sure si c'est une obligation ou pas mais je n'ai jamais vu quelque chose qui différait jusque là.
Mes premières analyses suggèrent en effet que la totalité des packages sur NuGet ont adopté une syntaxe de versions compatible avec semver. Après, la question, c'est de voir si les mainteneurs respectent la sémantique associée :-D
trinity- pour les contraites de versions, oui je pense que ce sont des ranges et après il te met les binding redirect dans ton app.config à l'installation dans le projet (pas toujours d'ailleurs, parfois il faut le mettre à la main pcq ca pète au runtime)
Est-ce que tu peux m'expliquer en quelques mots ce que sont les binding redirect ?
trinity- tu peux installer plusieurs versions différentes dans une solution dans des projets différents mais pas au sein d'un même projet. Ca peut être risqué pcq suivant l'ordre de build et les references cross-project que tu peux avoir, tu peux te retrouver avec la mauvaise lib au final dans ton build output.
Quand tu parles de l'ordre de build, tu veux dire quoi exactement ?

Le cas classique dans la plupart des package managers, c'est si tu as A qui veut C=1, et B qui veut C=2. Si tu installes A puis B, tu auras C=2, si tu installes B puis A, tu auras C=1. Est-ce le cas avec NuGet, ou est-ce qu'il retient les contraintes de tout ce qui est installé, dans l'optique soit de trouver une solution, soit dans le cas présent d'indiquer qu'il y a un souci ?

Par ex, en Python, c'est le premier cas : pip ne fait rien d'intelligent. Pour npm, la question ne se pose pas, vu que les dépendances sont installées localement au projet, donc tu peux sans souci avec C dans 2 versions différentes, tant que ce n'est pas un même paquet qui *directement* (par opposition à "transitif") requiert ces deux versions.


Merci pour le retour. C'est une vraie jungle ces package managers. Je viens de regarder du coté de Go également, c'est encore pire (parce que tu ne déclares pas forcément tes dépendances, tu peux les importer à la compilation automatiquement en fonction des "import" que tu as fais dans le code, et pire : tu peux directement importer des trucs présents sur des dépôts git, par exemple).
trinity 230 Wookie
Bon j'ai tout perdu ma réponse pcq mon VPN s'est déconnecté au moment d'appuyer sur envoyer...
Je recommence brièvement:
GuybrushEst-ce que tu peux m'expliquer en quelques mots ce que sont les binding redirect ?
Ca te permet de faire beaucoup de magie noire choses, mais en gros une utilisation commune c'est de rajouter une configuration dans ta config XML app.config/web.config qui permet de dire à ton programme d'utiliser une version plus récente d'un assembly même si il s'attend à utiliser une version antérieure.
Plus d'infos ici.
GuybrushQuand tu parles de l'ordre de build, tu veux dire quoi exactement ?

Le cas classique dans la plupart des package managers, c'est si tu as A qui veut C=1, et B qui veut C=2. Si tu installes A puis B, tu auras C=2, si tu installes B puis A, tu auras C=1. Est-ce le cas avec NuGet, ou est-ce qu'il retient les contraintes de tout ce qui est installé, dans l'optique soit de trouver une solution, soit dans le cas présent d'indiquer qu'il y a un souci ?
C'est bien le cas avec NuGet, c'est juste que je parlais plutôt niveau solution, si tu as plusieurs projets qui dépendent d'assembly de différentes versions, celui qui se retrouvera dans ton build output peut dépendre de l'ordre de build etc. On essaye le plus possible d'avoir des versions uniformes dans chaque projet de nos solutions mais un oubli est vite arrivé dans des solutions énormes...
GuybrushMerci pour le retour. C'est une vraie jungle ces package managers. Je viens de regarder du coté de Go également, c'est encore pire (parce que tu ne déclares pas forcément tes dépendances, tu peux les importer à la compilation automatiquement en fonction des "import" que tu as fais dans le code, et pire : tu peux directement importer des trucs présents sur des dépôts git, par exemple).
Mais dans ce cas-là, comment il sait où aller les chercher lorsqu'il build?
Guybrush 8351 Bob
trinityC'est bien le cas avec NuGet, c'est juste que je parlais plutôt niveau solution, si tu as plusieurs projets qui dépendent d'assembly de différentes versions, celui qui se retrouvera dans ton build output peut dépendre de l'ordre de build etc. On essaye le plus possible d'avoir des versions uniformes dans chaque projet de nos solutions mais un oubli est vite arrivé dans des solutions énormes...
Ok, donc à priori, une fois que t'as installé un truc, il "oublie" les contraintes liés à ce truc, comme dans Rubygems, PyPi, etc. Il n'y a donc probablement pas un SAT solver pour la résolution des contraintes, mais juste des intersections d'intervalles de versions.
trinityMais dans ce cas-là, comment il sait où aller les chercher lorsqu'il build?
C'est dans l'import, par exemple :
import "github.com/AlexandreDecan/Lexpage"
Ouais, moi aussi je trouve ça horrible comme système :-D
Surtout que tu peux pas préciser de version, donc la convention c'est que le HEAD de ton dépôt est toujours stable, et que tu ne fais aucun changement majeur (donc pas de backward incompatible change dans l'API) sur le dépôt. Ils suggèrent officiellement de faire un dépôt git par version majeure (et je parle bien d'un nouveau dépôt, pas d'une branche sur un dépôt existant !).
Guybrush 8351 Bob
Trin, j'ai encore besoin de tes lumières à propos de NuGet.

[b][Edit: Nan, c'est bon finalement, j'ai trouvé : c'est lié à des paquets de TypeScript (DefinitelyTyped). Je ne sais pas pourquoi ces paquets sont mis à jour aussi souvent sur une aussi courte période, et pratiquement jamais en dehors, mais voilà, ça répond à ma question initiale ^^]

On observe des pics d'activité relativement importants aux alentours de juin 2014, de janvier 2015, de janvier 2016 et de mars 2016. Est-ce que tu as une idée de ce qui a pu se passer ?

Pour Juin 2014, ça pourrait correspondre à la date de sortie de NuGet 2.8 qui a "inversé" la sémantique des versions des dépendances (jusqu'alors, la version la plus récente était installée, depuis, la version la plus ancienne est installée), ce qui peut expliquer des releases de pas mal de paquets afin de corriger le manifest en conséquence. Le hic, c'est que nuget 2.8 est sorti le 29 janvier... ca fait "long" comme délai pour une telle incidence.
[Edit : pour mars 2016, la release 3.4 a apporté un breaking change important sur la normalisation des numéros de versions]

Janvier 2015 et janvier 2016 je n'ai pas vraiment 'idée :(

Grosso-modo, ce qu'on observe, c'est une augmentation du nombre de paquets ayant un "grand nombre" de mises à jour (on parle de 40 à 50 paquets avec 40 à 50 mises à jour, contre maximum 30 paquets avec 30 mises à jour en temps normal). Pour début 2016, c'est encore "pire" : on atteint les 60-70 paquets avec 60-70 mises à jour.


Ce message a été modifié 4 fois. Dernière modification : 1 mai 2017 à 12:35 par Guybrush.

trinity 230 Wookie
J'ai vu ton edit, mais je sais pas trop pourquoi ya des "rush" de temps en temps avec plein de push je t'avoue...
Après une grosse update ya peut-être pas mal de petits fix réguliers (et d'autres packages dont le package principal dépend?) ?
Guybrush 8351 Bob
J'avais d'abord en tête des changements au niveau de la sémantique du manifest, qui pousseraient les développeurs à sortir une nouvelle release en conséquence (pour adapter le manifest), mais ça n'expliquait pas pourquoi certains paquets étaient mis à jour plus de 50 fois sur le mois, alors qu'ils sont "bien calmes" en temps normal.

En fouillant les données, j'ai trouvé que le "top 10" des paquets mis à jour pour ces mois là était constitué de paquets qu'on ne voit jamais les autres mois, et qui étaient tous en lien avec Typescript. Cela me suffit comme explication (l'idée étant d'expliquer les "sauts" que l'on a identifiés, pas de rentrer dans les détails de pourquoi c'est arrivé), mais je suis vraiment curieux de voir ce qui peut pousser ces paquets à être mis à jour en "rafale" spécifiquement à ces dates-là.


C'est toujours le souci quand on travaille avec de grosses quantités de données (on traite plusieurs millions de releases logicielles, avec un peu plus de 20 millions de liens de dépendances, sur 7 systèmes différents). Parfois, y a des tendances nettes qui se dégagent et qui sont intéressantes à creuser, et dans d'autres cas, on voit des choses atypiques, et il est parfois difficile de trouver leur origine (surtout quand on n'est pas expert dans le langage/la plate-forme).

Par exemple, pour Rubygems, on a observé des pics de nouveaux paquets en aout 2014. Il nous a fallu un petit moment pour réussir à identifier que ce sont des paquets qui avaient été importés en masse depuis un autre système, et dont les dates de releases correspondaient (à tort !) à la date d'import, et non pas à la date de release sur l'autre plate-forme. Pour npm, on a trouvé plusieurs centaines de paquets qui s'amusent à dépendre de plusieurs milliers d'autres paquets, juste pour le "fun" après l'histoire de left-pad l'an passé.
Guybrush 8351 Bob
Oyez oyez, braves développeurs ! :-D

Je suis à la recherche d'exemples de paquets (si possible sur npm, packagist, cargo ou rubygems) pour lesquels vous avez connaissance d'un non-respect de semver (ou, autrement formulé : des changements incompatibles qui n'ont pas été releasés dans une version "majeure", peu importe que le paquet annonce suivre semver ou pas).

J'ai déjà identifié quelques cas, mais il m'en faudrait d'autres. Pour l'instant, j'ai :

- underscore sur NPM, en 2014 (version 1.7.0 et 1.8.0);
- typescript sur NPM, version 2.1.0 (n'existe plus, mais la 2.1.1 date de novembre 2016);
- i18n sur Rubygems, version 0.5.0 en novembre 2010;
Guybrush 8351 Bob
Bon, bah vous vous en doutez, ça n'a finalement rien donné (trop peu d'exemples pour en tirer quoique ce soit). Snif...

Vous utilisez pas ce genre d'outils, ou vous avez pas trop connaissance de ces "problèmes" de compatibilité ?

Répondre

Vous devez être inscrit et identifié.