Introduction aux modules Go

La prochaine version de la version 1.11 du langage de programmation Go apportera un support expérimental pour les modules - un nouveau système de gestion des dépendances pour Go. (note traduction: la libération a eu lieu )


Récemment, j'ai déjà écrit un petit article à ce sujet . Depuis lors, quelque chose a légèrement changé et nous nous rapprochons de la sortie, il me semble donc que le moment est venu pour un nouvel article - ajoutons plus de pratique.


Alors, voici ce que nous allons faire: créer un nouveau package puis faire quelques versions pour voir comment cela fonctionne.


Création de module


Créez d'abord notre package. Appelons cela testmod. Détail important: le répertoire du package doit être placé en dehors de votre $GOPATH , car, à l'intérieur, le support du module est désactivé par défaut . Les modules Go sont la première étape vers un abandon complet de $GOPATH à l'avenir.


 $ mkdir testmod $ cd testmod 

Notre forfait est assez simple:


 package testmod import "fmt" // Hi returns a friendly greeting func Hi(name string) string { return fmt.Sprintf("Hi, %s", name) } 

Le package est prêt, mais ce n'est pas encore un module . Corrigeons-le.


 $ go mod init github.com/robteix/testmod go: creating new go.mod: module github.com/robteix/testmod 

Nous avons un nouveau fichier appelé go.mod dans le répertoire du package avec le contenu suivant:


 module github.com/robteix/testmod 

Un peu, mais c'est ce qui transforme notre package en module .


Maintenant, nous pouvons pousser ce code dans le référentiel:


 $ git init $ git add * $ git commit -am "First commit" $ git push -u origin master 

Jusqu'à présent, toute personne souhaitant utiliser notre forfait postulerait:


 $ go get github.com/robteix/testmod 

Et cette commande apporterait le dernier code de la branche principale. Cette option fonctionne toujours, mais il serait préférable que nous ne le fassions plus, car maintenant "il y a une meilleure façon". Prendre du code directement de la branche master est, en fait, dangereux, car nous ne savons jamais avec certitude que les auteurs du package n'ont pas apporté de modifications qui «casseraient» notre code. Pour résoudre ce problème, des modules Go ont été inventés.


Une petite digression sur les modules de version


Les modules Go sont versionnés, plus il y a une certaine spécificité des versions individuelles. Vous devrez vous familiariser avec les concepts sous-jacents au versioning sémantique .


De plus, Go utilise des balises de référentiel lors de la recherche de versions, et certaines versions diffèrent des autres: par exemple, les versions 2 et plus doivent avoir un chemin d'importation différent de celui des versions 0 et 1 (nous y reviendrons).


Par défaut, Go télécharge la dernière version, qui a une balise disponible dans le référentiel.
Il s'agit d'une caractéristique importante, car elle peut être utilisée lorsque vous travaillez avec la branche principale.


Pour nous maintenant, il est important que lors de la création de la version de notre package, nous devions mettre une étiquette avec la version dans le référentiel.


Faisons-le.


Faire votre première version


Notre package est prêt et nous pouvons le "déployer" dans le monde entier. Nous le faisons en utilisant des étiquettes versionnées. Soit le numéro de version 1.0.0:


 $ git tag v1.0.0 $ git push --tags 

Ces commandes créent une balise dans mon référentiel Github qui marque la validation actuelle comme version 1.0.0.


Go n'insiste pas là-dessus, mais c'est une bonne idée de créer une nouvelle branche supplémentaire ("v1") à laquelle nous pouvons envoyer des patchs.


 $ git checkout -b v1 $ git push -u origin v1 

Maintenant, nous pouvons travailler dans la branche principale sans craindre de casser notre version.


Utiliser notre module


Utilisons le module créé. Nous allons écrire un programme simple qui importe notre nouveau package:


 package main import ( "fmt" "github.com/robteix/testmod" ) func main() { fmt.Println(testmod.Hi("roberto")) } 

Jusqu'à présent, vous devez exécuter go get github.com/robteix/testmod pour télécharger le package, mais avec les modules, cela devient plus intéressant. Tout d'abord, nous devons activer la prise en charge des modules dans notre nouveau programme.


 $ go mod init mod 

Comme vous vous en go.mod probablement, sur la base de ce que vous avez lu précédemment, un nouveau fichier go.mod est apparu dans le répertoire avec le nom du module à l'intérieur:


 module mod 

La situation devient encore plus intéressante lorsque nous essayons de préparer notre programme:


 $ go build go: finding github.com/robteix/testmod v1.0.0 go: downloading github.com/robteix/testmod v1.0.0 

Comme vous pouvez le voir, la commande go automatiquement trouvé et téléchargé le package importé par notre programme.
Si nous vérifions notre fichier go.mod , nous verrons que quelque chose a changé:


 module mod require github.com/robteix/testmod v1.0.0 

Et nous avons obtenu un autre nouveau fichier appelé go.sum , qui contient les hachages des packages pour vérifier la version et les fichiers corrects.


 github.com/robteix/testmod v1.0.0 h1:9EdH0EArQ/rkpss9Tj8gUnwx3w5p0jkzJrd5tRAhxnA= github.com/robteix/testmod v1.0.0/go.mod h1:UVhi5McON9ZLc5kl5iN2bTXlL6ylcxE9VInV71RrlO8= 

Créer une version de correctif de bogue


Maintenant, disons que nous avons trouvé un problème dans notre colis: il n'y a pas de ponctuation dans le message d'accueil!
Certaines personnes seront furieuses, car notre salut amical n'est plus aussi amical.
Corrigeons cela et publions une nouvelle version:


 // Hi returns a friendly greeting func Hi(name string) string { - return fmt.Sprintf("Hi, %s", name) + return fmt.Sprintf("Hi, %s!", name) } 

Nous avons fait ce changement directement dans la branche v1 , car cela n'a rien à voir avec ce que nous ferons ensuite dans la branche v2 , mais dans la vraie vie, vous devriez peut-être apporter ces modifications à master et les rétroporter vers v1 . Dans tous les cas, le correctif devrait être dans la branche v1 et nous devons le marquer comme une nouvelle version.


 $ git commit -m "Emphasize our friendliness" testmod.go $ git tag v1.0.1 $ git push --tags origin v1 

Mise à jour des modules


Par défaut, Go ne met pas à jour les modules sans demande. «Et c'est bien», car nous souhaitons tous la prévisibilité de nos versions. Si les modules Go étaient mis à jour automatiquement chaque fois qu'une nouvelle version est publiée, nous reviendrions aux "âges sombres avant-Go1.11". Mais non, nous devons dire à Go de mettre à jour les modules pour nous.


Et nous le ferons avec l'aide de notre vieil ami - go get :


  • lancez go get -u pour utiliser la dernière version mineure ou corrective (c'est-à-dire que la commande sera mise à jour de 1.0.0 vers, disons, 1.0.1 ou vers 1.1.0, si une telle version est disponible)


  • exécutez go get -u=patch pour utiliser la dernière version du patch (c'est-à-dire que le package sera mis à jour vers 1.0.1, mais pas vers 1.1.0)


  • exécutez go get package@version pour effectuer une mise à niveau vers une version spécifique (par exemple, github.com/robteix/testmod@v1.0.1 )



Il n'y a aucun moyen dans cette liste de passer à la dernière version majeure . Il y a une bonne raison à cela, comme nous le verrons bientôt.


Puisque notre programme a utilisé la version 1.0.0 de notre package et que nous venons de créer la version 1.0.1, l' une des commandes suivantes nous mettra à jour vers 1.0.1:


 $ go get -u $ go get -u=patch $ go get github.com/robteix/testmod@v1.0.1 

Après le démarrage (disons go get -u ), notre go.mod a changé:


 module mod require github.com/robteix/testmod v1.0.1 

Version majeure


Selon la spécification du versionnage sémantique, la version principale diffère de la version mineure. Les versions majeures peuvent rompre la compatibilité descendante. Du point de vue des modules Go, la version principale est un package complètement différent .


Cela peut sembler sauvage au premier abord, mais cela a du sens: deux versions de la bibliothèque qui sont incompatibles sont deux bibliothèques différentes.


Faisons un changement majeur dans notre package. Supposons qu'au fil du temps, il soit devenu clair pour nous que notre API est trop simple, trop limitée pour les cas d'utilisation de nos utilisateurs, nous devons donc changer la fonction Hi() pour accepter la langue de bienvenue comme paramètre:


 package testmod import ( "errors" "fmt" ) // Hi returns a friendly greeting in language lang func Hi(name, lang string) (string, error) { switch lang { case "en": return fmt.Sprintf("Hi, %s!", name), nil case "pt": return fmt.Sprintf("Oi, %s!", name), nil case "es": return fmt.Sprintf("¡Hola, %s!", name), nil case "fr": return fmt.Sprintf("Bonjour, %s!", name), nil default: return "", errors.New("unknown language") } } 

Les programmes existants utilisant notre API vont se casser car ils a) ne transmettent pas la langue en tant que paramètre et b) ne s'attendent pas à un retour d'erreur. Notre nouvelle API n'est plus compatible avec la version 1.x, alors rencontrez la version 2.0.0.


J'ai mentionné plus tôt que certaines versions ont des fonctionnalités, et maintenant c'est le cas.
La version 2 ou ultérieure doit modifier le chemin d'importation. Maintenant, ce sont des bibliothèques différentes.


Nous le ferons en ajoutant un nouveau chemin versionné au nom de notre module.


 module github.com/robteix/testmod/v2 

Tout le reste est le même: pousser, mettre une étiquette que c'est v2.0.0 (et éventuellement soder une branche v2)


 $ git commit testmod.go -m "Change Hi to allow multilang" $ git checkout -b v2 # optional but recommended $ echo "module github.com/robteix/testmod/v2" > go.mod $ git commit go.mod -m "Bump version to v2" $ git tag v2.0.0 $ git push --tags origin v2 # or master if we don't have a branch 

Mise à jour de la version majeure


Même si nous avons publié une nouvelle version incompatible de notre bibliothèque, les programmes existants ne se sont pas cassés , car ils continuent d'utiliser la version 1.0.1.
go get -u ne téléchargera pas la version 2.0.0.


Mais à un moment donné, en tant qu'utilisateur de bibliothèque, je souhaiterai peut-être passer à la version 2.0.0, car, par exemple, je fais partie de ces utilisateurs qui ont besoin de la prise en charge de plusieurs langues.


Pour mettre à jour, je dois modifier mon programme en conséquence:


 package main import ( "fmt" "github.com/robteix/testmod/v2" ) func main() { g, err := testmod.Hi("Roberto", "pt") if err != nil { panic(err) } fmt.Println(g) } 

Maintenant, quand je lance go build , il "se ferme" et télécharge la version 2.0.0 pour moi. Notez que bien que le chemin d'importation se termine désormais par "v2", Go fait toujours référence au module par son vrai nom ("testmod").


Comme je l'ai dit, la version principale est à tous égards un package différent. Ces deux modules Go ne sont en aucun cas connectés. Cela signifie que nous pouvons avoir deux versions incompatibles dans un binaire:


 package main import ( "fmt" "github.com/robteix/testmod" testmodML "github.com/robteix/testmod/v2" ) func main() { fmt.Println(testmod.Hi("Roberto")) g, err := testmodML.Hi("Roberto", "pt") if err != nil { panic(err) } fmt.Println(g) } 

Et cela élimine le problème commun avec la gestion des dépendances lorsque les dépendances dépendent de différentes versions de la même bibliothèque.



Revenons à la version précédente, qui utilise uniquement testmod 2.0.0 - si nous vérifions le contenu de go.mod , nous remarquerons quelque chose:


 module mod require github.com/robteix/testmod v1.0.1 require github.com/robteix/testmod/v2 v2.0.0 

Par défaut, Go ne supprime pas les dépendances de go.mod jusqu'à ce que vous le demandiez. Si vous avez des dépendances qui ne sont plus nécessaires et que vous souhaitez les nettoyer, vous pouvez utiliser la nouvelle commande tidy :


 $ go mod tidy 

Maintenant, nous n'avons que les dépendances que nous utilisons vraiment.


Vente


Les modules Go par défaut ignorent le répertoire vendor/ . L'idée est de se débarrasser progressivement du vending 1 . Mais si nous voulons toujours ajouter les dépendances "détachées" à notre contrôle de version, nous pouvons le faire:


 $ go mod vendor 

L'équipe créera le répertoire vendor/ à la racine de notre projet, contenant le code source de toutes les dépendances.


Cependant, go build par défaut ignore toujours le contenu de ce répertoire. Si vous souhaitez collecter des dépendances à partir du répertoire vendor/ , vous devez le demander explicitement.


 $ go build -mod vendor 

Je suppose que de nombreux développeurs qui souhaitent utiliser la distribution s'exécuteront comme d'habitude sur leurs machines et utiliseront -mod vendor sur leur CI.


Encore une fois, les modules Go s'éloignent de l'idée de vendre pour utiliser des proxys pour les modules pour ceux qui ne veulent pas dépendre directement des services de contrôle de version en amont.


Il existe des moyens de garantir que go réseau n'est pas disponible (par exemple, en utilisant GOPROXY=off ), mais c'est le sujet du prochain article.


Conclusion


L'article peut sembler compliqué à quelqu'un, mais c'est parce que j'ai essayé d'expliquer beaucoup de choses à la fois. La réalité est que les modules Go sont généralement simples aujourd'hui - nous, comme d'habitude, importons le package dans notre code, et l'équipe go fait le reste pour nous. Les dépendances sont automatiquement chargées lors de l'assemblage.


Les modules éliminent également le besoin de $GOPATH , qui était une pierre d'achoppement pour les nouveaux développeurs Go qui avaient des problèmes à comprendre pourquoi ils devraient mettre quelque chose dans un répertoire spécifique.


La vente (officieusement) a été dépréciée au profit de l'utilisation d'un proxy. 1
Je peux faire un article séparé sur les proxys pour les modules Go.


Remarques:


1 Je pense que c'est une expression trop bruyante et certains peuvent avoir l'impression que la vente est supprimée en ce moment. Ce n'est pas le cas. La vente fonctionne toujours, quoique légèrement différemment qu'auparavant. Apparemment, il y a un désir de remplacer la vente par quelque chose de mieux, par exemple, un proxy (pas un fait). Jusqu'à présent, il s'agit simplement de rechercher une meilleure solution. La vente ne disparaîtra pas jusqu'à ce qu'un bon remplacement soit trouvé (le cas échéant).

Source: https://habr.com/ru/post/fr421411/


All Articles