Lucas F. Costa Pensées humaines sur les sciences exactes

Jan 16, 2022
admin

Salut tout le monde ! Comme vous l’avez peut-être remarqué, je m’intéresse vraiment à Go et depuis que je suis tombé amoureux de ce langage, j’aimerais écrire à son sujet plus fréquemment.

Si vous ne connaissez pas encore Go, je pense vraiment que vous devriez l’apprendre. Go est génial !

Go est un langage relativement jeune et quand il s’agit de vendre et de gérer les dépendances, il n’est pas encore vraiment mature. La communauté Go, cependant, a adopté certaines pratiques et créé des outils pour répondre à cette demande. C’est ce dont nous allons parler aujourd’hui.

Comprendre l’écosystème Go

Go vise à utiliser la simplicité pour réaliser des tâches complexes et donc à rendre le langage plus amusant et productif. Depuis le début du langage, l’équipe Go a choisi qu’elle utiliserait des littéraux de chaîne de caractères pour rendre la syntaxe d’importation arbitraire afin de décrire ce qui était importé.

C’est ce qui est écrit dans leur propre docs:

Un objectif explicite pour Go depuis le début était de pouvoir construire du code Go en utilisant uniquement les informations trouvées dans la source elle-même, sans avoir besoin d’écrire un makefile ou l’un des nombreux remplacements modernes des makefiles. Si Go avait besoin d’un fichier de configuration pour expliquer comment construire votre programme, alors Go aurait échoué.

Pour atteindre cet objectif, l’équipe Go privilégie les conventions aux configurations. Voici les conventions adoptées en matière de gestion des paquets :

  1. Le chemin d’importation est toujours dérivé de manière connue de l’URL du code source. C’est pourquoi vos importations ressemblent à : github.com/author/pkgname. En faisant cela, nous obtenons également un espace de noms géré automatiquement, puisque ces services en ligne gèrent déjà des chemins uniques pour chaque paquet.
  2. L’endroit où nous stockons les paquets dans notre système de fichiers est dérivé de manière connue du chemin d’importation. Si vous recherchez l’endroit où go stocke les paquets que vous téléchargez, vous pourrez le relier à l’URL à partir de laquelle il a été téléchargé.
  3. Chaque répertoire d’une arborescence de sources correspond à un seul paquet. Cela vous permet de trouver plus facilement le code source de certains paquets et vous aide à organiser votre code de manière standardisée. En liant la structure des dossiers à celle des paquets, nous n’avons pas besoin de nous soucier des deux en même temps, car les outils de système de fichiers deviennent des outils de gestion de paquets.
  4. Le paquet est construit en utilisant uniquement les informations du code source. Cela signifie pas de makefiles et pas de configuration et vous libère de l’obligation d’adopter des chaînes d’outils spécifiques (et probablement compliquées).

Maintenant que nous avons dit cela, il est facile de comprendre pourquoi la commande go get fonctionne comme elle le fait.

Comprendre les commandes des outils Go

Avant d’aborder ces commandes, comprenons ce qu’est le « $GOPATH« .

Le $GOPATH est une variable d’environnement qui pointe vers un répertoire qui peut être considéré comme un répertoire d’espace de travail. Il contiendra vos codes sources, vos paquets compilés et vos binaires exécutables.

go get

La commande go get est simple et fonctionne presque comme une git clone. L’argument que vous devez passer à go get est une simple URL de référentiel. Dans cet exemple, nous allons utiliser la commande : go get https://github.com/golang/oauth2.

Lorsque vous exécutez cette commande, go va simplement récupérer le paquet à partir de l’URL que vous avez fournie et le mettre dans votre répertoire $GOPATH. Si vous naviguez dans votre répertoire $GOPATH, vous verrez maintenant que vous avez un dossier dans src/github.com/golang/oauth2 qui contient les fichiers sources du paquet et un paquet compilé dans le répertoire pkg (avec ses dépendances).

Lorsque vous exécutez go get, vous devez avoir à l’esprit que tout paquet téléchargé sera placé dans un répertoire qui correspond à l’URL que vous avez utilisée pour le télécharger.

Il a également un tas d’autres drapeaux disponibles, comme -u qui met à jour un paquet ou -insecure qui vous permet de télécharger des paquets en utilisant des schémas non sécurisés comme HTTP. Vous pouvez en savoir plus sur l’utilisation « avancée » de la commande go get à ce lien.

De plus, selon go help gopath, la commande go get met également à jour les sous-modules des paquets que vous obtenez.

go install

Lorsque vous exécutez go install dans le répertoire source d’un paquet, il compilera la dernière version de ce paquet et toutes ses dépendances dans le répertoire pkg.

go build

Go build est responsable de la compilation des paquets et de leurs dépendances, mais il n’installe pas les résultats !

Comprendre la vente

Comme vous avez pu le remarquer par la façon dont Go sauvegarde ses dépendances, cette approche de la gestion des dépendances a quelques problèmes.

Tout d’abord, nous ne sommes pas en mesure de déterminer quelle version d’un paquet nous avons besoin à moins qu’il soit hébergé dans un dépôt complètement différent, sinon go get ira toujours chercher la dernière version d’un paquet. Cela signifie que si quelqu’un fait un changement de rupture à son paquet et ne le met pas dans un autre dépôt, vous et votre équipe finirez par avoir des problèmes parce que vous pourriez finir par récupérer différentes versions du même paquet et cela conduira alors à des problèmes de « fonctionne sur ma machine ».

Un autre gros problème est que, en raison du fait que go get installe les paquets à la racine de votre répertoire src, vous ne serez pas en mesure d’avoir différentes versions de vos dépendances pour chacun de vos projets. Cela signifie que vous ne pouvez pas avoir de projets qui dépendent de différentes versions du même paquet, vous devrez soit avoir une version ou l’autre à la fois.

Pour atténuer ces problèmes, depuis Go 1.5, l’équipe Go a introduit une fonctionnalité vendoring. Cette fonctionnalité vous permet de placer le code de votre dépendance pour un paquet à l’intérieur de son propre répertoire afin qu’il puisse toujours obtenir les mêmes versions pour toutes les constructions.

Disons que vous avez un projet appelé awesome-project qui dépend de popular-package. Afin de garantir que tout le monde dans votre équipe utilisera la même version de popular-package, vous pouvez mettre sa source à l’intérieur de $GOPATH/src/awesome-project/vendor/popular-package. Cela fonctionnera parce que Go essaiera alors de résoudre le chemin de vos dépendances en commençant par le répertoire vendor de son propre dossier (s’il a au moins un fichier .go) au lieu de $GOPATH/src. Cela rendra également vos constructions déterministes (reproductibles) puisqu’elles utiliseront toujours la même version de popular-package.

Il est également important de noter que la commande go get ne placera pas automatiquement les paquets téléchargés dans le dossier vendor. C’est un travail pour les outils de vendoring.

Lorsque vous utilisez le vendoring, vous pourrez utiliser les mêmes chemins d’importation que si vous ne le faisiez pas, car Go essaiera toujours de trouver les dépendances dans le répertoire vendor le plus proche. Il n’y a pas besoin de faire précéder vendor/ à aucun de vos chemins d’importation.

Pour pouvoir comprendre pleinement le fonctionnement de la vente, nous devons comprendre l’algorithme utilisé par Go pour résoudre les chemins d’importation, qui est le suivant :

  1. Rechercher l’import au niveau du répertoire local vendor (s’il y en a un)
  2. Si nous ne trouvons pas ce paquet dans le répertoire local vendor, nous montons dans le dossier parent et essayons de le trouver dans le répertoire vendor qui s’y trouve (si il y en a un)
  3. Nous répétons l’étape 2 jusqu’à ce que nous atteignions $GOPATH/src
  4. Nous cherchons le paquet importé à $GOROOT
  5. Si nous ne trouvons pas ce paquet à $GOROOT, nous le cherchons dans notre dossier $GOPATH/src

Basiquement, cela signifie que chaque paquet peut avoir ses propres dépendances résolues dans son propre répertoire vendeur. Si vous dépendez du paquet x et du paquet y, par exemple, et que le paquet x dépend également du paquet y mais a besoin d’une version différente de celui-ci, vous pourrez toujours exécuter votre code sans problème, car x cherchera y dans son propre dossier vendor tandis que votre paquet cherchera y dans le dossier vendor de votre projet.

Maintenant, un exemple pratique. Disons que vous avez cette structure de dossier :

$GOPATH src/ github.com/user/package-one/ one.go myproject main.go vendor/ github.com/user/package-one/ one.go client/ client.go vendor/ github.com/user/package-one/ server/ server.go vendor/ github.com/user/package-one/ one.go

Si nous importions github.com/user/package-one depuis l’intérieur de main.go, il se résoudrait à la version de ce paquet dans le répertoire vendor du même dossier:

$GOPATH src/ github.com/user/package-one/ one.go myproject main.go <-- Importing package-one from here vendor/ github.com/user/package-one/ <-- resolves to here one.go client/ client.go vendor/ github.com/user/package-one/ server/ server.go vendor/ github.com/user/package-one/ one.go

Maintenant si nous importons le même paquet dans client.go, il se résoudra également au dossier vendor dans son propre répertoire:

$GOPATH src/ github.com/user/package-one/ one.go myproject main.go vendor/ github.com/user/package-one/ one.go client/ client.go <-- Importing package-one from here vendor/ github.com/user/package-one/ <-- resolves to here server/ server.go vendor/ github.com/user/package-one/ one.go

Le même phénomène se produit lors de l’importation de ce paquet sur le fichier server.go :

$GOPATH src/ github.com/user/package-one/ one.go myproject main.go vendor/ github.com/user/package-one/ one.go client/ client.go vendor/ github.com/user/package-one/ server/ server.go <-- Importing package-one from here vendor/ github.com/user/package-one/ <-- resolves to here one.go

Comprendre la gestion des dépendances

Maintenant que nous avons appris toutes ces choses sur la façon dont Go gère les importations et les ventes, il est temps de parler enfin de la gestion des dépendances.

L’outil que j’utilise actuellement pour gérer les dépendances dans mes propres projets s’appelle godep. Il semble être très populaire et il fonctionne bien pour moi, donc je vous recommande fortement de l’utiliser aussi.

Il est construit autour de la façon dont vendoring fonctionne. Tout ce que vous avez à faire pour commencer à l’utiliser est d’utiliser la commande godep save chaque fois que vous voulez enregistrer vos dépendances dans votre dossier vendor.

Lorsque vous exécutez godep save, godep enregistrera une liste de vos dépendances actuelles dans Godeps/Godeps.json et copiera ensuite leur code source dans le dossier vendor. Il est également important de noter que vous devez avoir ces dépendances installées sur votre machine pour que godep puisse les copier.

Maintenant vous pouvez commiter le dossier vendor et son contenu pour vous assurer que tout le monde aura les mêmes versions des mêmes paquets chaque fois que vous exécuterez votre paquet.

Une autre commande intéressante est godep restore, qui installera les versions spécifiées dans votre fichier Godeps/Godeps.json dans votre $GOPATH.

Pour mettre à jour une dépendance, tout ce que vous devez faire est de la mettre à jour en utilisant go get -u (comme nous en avons parlé plus tôt) et ensuite exécuter godep save afin que godep mette à jour le fichier Godeps/Godeps.json et copie les fichiers nécessaires dans le répertoire vendor.

Quelques réflexions sur la façon dont Go gère les dépendances

À la fin de ce billet de blog, je voudrais également ajouter ma propre opinion sur la façon dont Go gère les dépendances.

Je pense que le choix de Go d’utiliser des dépôts externes pour gérer les espaces de noms des paquets était excellent parce qu’il rend toute la résolution des paquets beaucoup plus simple en joignant les concepts de système de fichiers avec les concepts d’espace de noms. Cela permet également à l’ensemble de l’écosystème de fonctionner de manière indépendante car nous avons maintenant un moyen décentralisé de récupérer les paquets.

Cependant, la gestion décentralisée des paquets a un coût, qui est de ne pas pouvoir contrôler tous les nœuds qui font partie de ce « réseau ». Si quelqu’un décide de retirer son paquet de github, par exemple, alors des centaines, des milliers ou même des millions de constructions peuvent commencer à échouer tout d’un coup. Les changements de noms pourraient aussi avoir le même effet.

Considérant les objectifs principaux de Go, cela est totalement logique et constitue un compromis totalement équitable. Go est tout au sujet de la convention au lieu de la configuration et il n’y a pas de façon plus simple de gérer les dépendances que la façon dont il le fait actuellement.

Bien sûr, quelques améliorations pourraient être apportées, comme l’utilisation de balises git pour récupérer des versions spécifiques de paquets et permettre aux utilisateurs de spécifier quelles versions leurs paquets utiliseront. Il serait également cool de pouvoir récupérer ces dépendances au lieu de les vérifier sur le contrôle de source. Cela nous permettrait d’éviter les dirty diffs ne contenant que des changements de code dans le répertoire vendor et de rendre l’ensemble du dépôt plus propre et plus léger.

Get in touch!

Si vous avez des doutes, des pensées ou si vous n’êtes pas d’accord avec tout ce que j’ai écrit, veuillez me le faire savoir dans les commentaires ci-dessous ou me joindre à @thewizardlucas sur twitter. J’adorerais entendre ce que vous avez à dire et faire des corrections si j’ai fait des erreurs.

Enfin, mais pas des moindres, assurez-vous de jeter un coup d’œil à cette impressionnante conférence de Wisdom Omuya sur la GopherCon 2016 dans laquelle il explique comment fonctionne le Go Vendoring et pointe également quelques détails sur son fonctionnement interne.

Merci de lire ceci !

.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.