Cet article est une traduction de Comment fonctionne «go build».
Comment «go build» compile-t-il le programme Golang le plus simple?
Cet article vise à répondre à cette question.
Considérez le programme le plus simple ci-dessous.
// main.go
package main
func main() {}
Lancer go build main.go
imprime un fichier exécutable de 1,1 Mo main
et ne fait rien. Qu'a fait go build
pour créer ce binaire à ne rien faire?
La commande go build
offre des options utiles.
-work
: go build
crée un dossier temporaire pour vos fichiers de travail. Cet argument imprime l'emplacement de ce dossier et ne le supprime pas après la construction-a
: Golang met en cache les paquets précédemment construits. -a
fait que go build
ignore le cache, donc la construction imprime toutes les étapes-p 1
: Ceci définit le processus à effectuer dans un seul thread et enregistre la sortie de manière linéaire.-x
: go build
est un wrapper pour d'autres outils Golang tels que compile
. -x
affiche les commandes et arguments envoyés à ces outilsLancer go build -work -a -p 1 -x main.go
vous donnera beaucoup de journaux ainsi que main
, et ce que vous faites lorsque vous créez main
avec build
Va nous dire.
Le journal génère d'abord le contenu suivant.
WORK=/var/folders/rw/gtb29xf92fv23f0zqsg42s840000gn/T/go-build940616988
Il s'agit d'un répertoire de travail avec une structure similaire à la suivante.
├── b001
│ ├── _pkg_.a
│ ├── exe
│ ├── importcfg
│ └── importcfg.link
├── b002
│ └── ...
├── b003
│ └── ...
├── b004
│ └── ...
├── b006
│ └── ...
├── b007
│ └── ...
└── b008
└── ...
go build
définit un graphe d'action pour la tâche à accomplir.
Chaque action de ce graphe obtient son propre sous-répertoire (défini dans NewObjdir
).
Le premier nœud du graphe, «b001», est la tâche racine pour la compilation du binaire principal.
Le nombre d'actions dépendantes est grand et se termine par «b008». (Je ne sais pas où est allé b005
, mais je ne pense pas que ce soit un problème, je vais donc l'omettre.)
b008
La première action à entreprendre est «b008» à la fin du graphique.
mkdir -p $WORK/b008/
cat >$WORK/b008/importcfg << 'EOF'
# import config
EOF
cd /<..>/src/runtime/internal/sys
/<..>/compile
-o $WORK/b008/_pkg_.a
-trimpath "$WORK/b008=>"
-p runtime/internal/sys
-std
-+
-complete
-buildid gEtYPexVP43wWYWCxFKi/gEtYPexVP43wWYWCxFKi
-goversion go1.14.7
-D ""
-importcfg $WORK/b008/importcfg
-pack
-c=16
./arch.go ./arch_amd64.go ./intrinsics.go ./intrinsics_common.go ./stubs.go ./sys.go ./zgoarch_amd64.go ./zgoos_darwin.go ./zversion.go
/<..>/buildid -w $WORK/b008/_pkg_.a
cp $WORK/b008/_pkg_.a /<..>/Caches/go-build/01/01b...60a-d
Dans b008
importcfg
à utiliser avec l'outil compile
(vide)runtime / internal / sys
. Ce package contient des constantes utilisées lors de l'exécutionbuild id
pour écrire les métadonnées dans le package ( -w
) et copiez le package dans le cache go-build
(tous les packages sont mis en cache, donc cette description est omise ci-après) Faire)Décomposons cela en arguments envoyés à l'outil compile
(également expliqué dans go tool compile --help
).
-o
Fichier de destination de sortie$ WORK / b008 =>
from -trimpath
chemin du fichier source-p`` import
-std`` compilant la bibliothèque standard
(je n'étais pas sûr à ce stade)- +
compilation runtime
(je ne le savais pas non plus)-complete
produit le package complet, pas C ou l'assembly-build id
un identifiant de construction-goversion
La version requise pour le paquet compilé-D
Le chemin relatif utilisé pour l'importation locale est" "-importcfg
Référez-vous à d'autres packages pour importer le fichier de configuration-pack
comme une archive .a
au lieu du fichier objet .o
-c
Combien traiter en parallèle au moment de la constructionLa plupart de ces arguments sont les mêmes pour toutes les commandes compile
, nous allons donc omettre cette description ci-dessous.
La sortie de b008
est un fichier d'archive appelé $ WORK / b008 / _pkg_.a
qui correspond à runtime / internal / sys
.
buildid
Laissez-moi vous expliquer ce qu'est un "build id".
Le format de buildid
est <actionid> / <contentid>
.
Il est utilisé comme index pour mettre en cache les paquets et améliorer les performances de go build
.
<actionid>
est le hachage de l'action (tous les appels, arguments et fichiers d'entrée). <contentid>
est le hachage du fichier .a
de sortie.
Pour chaque action «go build», vous pouvez rechercher dans le cache du contenu créé par une autre action avec le même «
Ceci est implémenté dans buildid.go
.
Le buildid
est stocké dans le fichier sous forme de métadonnées, vous n'avez donc pas à le hacher à chaque fois pour obtenir le <contentid>
. Vous pouvez trouver cet ID avec go tool buildid <file>
(cela fonctionne également en binaire).
Dans le journal b008
ci-dessus, le buildID
est défini par l'outil compile
comme gEtYPexVP43wWYWCxFKi / gEtYPexVP43wWYWCxFKi
.
Ceci est juste un espace réservé et sera écrasé par le bon gEtYPexVP43wWYWCxFKi / b-rPboOuD0POrlJWPTEi
avec go tool buildid -w
avant d'être mis en cache plus tard.
b007
Vient ensuite b007
cat >$WORK/b007/importcfg << 'EOF'
# import config
packagefile runtime/internal/sys=$WORK/b008/_pkg_.a
EOF
cd /<..>/src/runtime/internal/math
/<..>/compile
-o $WORK/b007/_pkg_.a
-p runtime/internal/math
-importcfg $WORK/b007/importcfg
...
./math.go
importcfg
qui dit packagefile runtime / internal / sys = $ WORK / b008 / _pkg_.a
Cela indique que b007
dépend de b008
runtime / internal / math
Si vous regardez à l'intérieur math.go
, vous importez sûrement runtime / internal / sys
fait avec b008
.La sortie de b007
est un fichier d'archive appelé $ WORK / b007 / _pkg_.a
qui correspond à runtime / internal / math
.
b006
cat >$WORK/b006/go_asm.h << 'EOF'
EOF
cd /<..>/src/runtime/internal/atomic
/<..>/asm
-I $WORK/b006/
-I /<..>/go/1.14.7/libexec/pkg/include
-D GOOS_darwin
-D GOARCH_amd64
-gensymabis
-o $WORK/b006/symabis
./asm_amd64.s
/<..>/asm
-I $WORK/b006/
-I /<..>/go/1.14.7/libexec/pkg/include
-D GOOS_darwin
-D GOARCH_amd64
-o $WORK/b006/asm_amd64.o
./asm_amd64.s
cat >$WORK/b006/importcfg << 'EOF'
# import config
EOF
/<..>/compile
-o $WORK/b006/_pkg_.a
-p runtime/internal/atomic
-symabis $WORK/b006/symabis
-asmhdr $WORK/b006/go_asm.h
-importcfg $WORK/b006/importcfg
...
./atomic_amd64.go ./stubs.go
/<..>/pack r $WORK/b006/_pkg_.a $WORK/b006/asm_amd64.o
Maintenant sortons du fichier .go
normal et commençons à traiter le fichier .s` de l'assembly Go de bas niveau.
go_asm.h
runtime / internal / atomic
des fonctions de bas niveaugo tool asm
(décrit dans go tool asm --help
) pour créer le fichier symabis
"Symbol Application Binary Interfaces (ABI)", puis le fichier objetasm_amd64.o Créer
compile
pour créer un fichier _pkg_.a
avec un fichier symabis
et un en-tête contenant -asmhdr
asm_amd64.o
à _pkg_.a
avec la commande pack
L'outil asm
est appelé ici avec les arguments suivants:
-I
: Inclut les actions b007
et le dossier libexec / pkg / includes
. includes
a trois fichiers asm_ppc64x.h
, funcdata.h
et textflag.h
, tous avec des définitions de fonctions de bas niveau. Par exemple, FIXED_FRAME définit la taille de la partie fixe du cadre de pile-D
: Comprend des symboles prédéfinis-gensymabis
: Créer un fichier symabis
-o
: fichier de destination de sortieLa sortie de b006
est un fichier d'archive appelé $ WORK / b006 / _pkg_.a
qui correspond à runtime / internal / atomic
.
b004
cd /<..>/src/internal/cpu
/<..>/asm ... -o $WORK/b004/symabis ./cpu_x86.s
/<..>/asm ... -o $WORK/b004/cpu_x86.o ./cpu_x86.s
/<..>/compile ... -o $WORK/b004/_pkg_.a ./cpu.go ./cpu_amd64.go ./cpu_x86.go
/<..>/pack r $WORK/b004/_pkg_.a $WORK/b004/cpu_x86.o
«b004» est le même que «b006» sauf que la cible a changé en «internal / cpu».
Créez d'abord le symabis
et le fichier objet en assemblant cpu_x86.s
, compilez le fichier go, puis combinez-les pour créer l'archive _pkg_.a
.
La sortie de b004
est un fichier archive appelé $ WORK / b004 / _pkg_.a
correspondant à internal / cpu
.
b003
cat >$WORK/b003/go_asm.h << 'EOF'
EOF
cd /<..>/src/internal/bytealg
/<..>/asm ... -o $WORK/b003/symabis ./compare_amd64.s ./count_amd64.s ./equal_amd64.s ./index_amd64.s ./indexbyte_amd64.s
cat >$WORK/b003/importcfg << 'EOF'
# import config
packagefile internal/cpu=$WORK/b004/_pkg_.a
EOF
/<..>/compile ... -o $WORK/b003/_pkg_.a -p internal/bytealg ./bytealg.go ./compare_native.go ./count_native.go ./equal_generic.go ./equal_native.go ./index_amd64.go ./index_native.go ./indexbyte_native.go
/<..>/asm ... -o $WORK/b003/compare_amd64.o ./compare_amd64.s
/<..>/asm ... -o $WORK/b003/count_amd64.o ./count_amd64.s
/<..>/asm ... -o $WORK/b003/equal_amd64.o ./equal_amd64.s
/<..>/asm ... -o $WORK/b003/index_amd64.o ./index_amd64.s
/<..>/asm ... -o $WORK/b003/indexbyte_amd64.o ./indexbyte_amd64.s
/<..>/pack r $WORK/b003/_pkg_.a $WORK/b003/compare_amd64.o $WORK/b003/count_amd64.o $WORK/b003/equal_amd64.o $WORK/b003/index_amd64.o $WORK/b003/indexbyte_amd64.o
Faire «b003» équivaut à «b004» et «b006».
Le principal problème avec ce paquet est qu'il existe plusieurs fichiers .s
pour créer de nombreux fichiers objets .o
, dont chacun doit être ajouté au fichier _pkg_.a
.
La sortie de b003
est un fichier d'archive appelé $ WORK / b003 / _pkg_.a
qui correspond à internal / bytealg
.
b002
cat >$WORK/b002/go_asm.h << 'EOF'
EOF
cd /<..>/src/runtime
/<..>/asm
...
-o $WORK/b002/symabis
./asm.s ./asm_amd64.s ./duff_amd64.s ./memclr_amd64.s ./memmove_amd64.s ./preempt_amd64.s ./rt0_darwin_amd64.s ./sys_darwin_amd64.s
cat >$WORK/b002/importcfg << 'EOF'
# import config
packagefile internal/bytealg=$WORK/b003/_pkg_.a
packagefile internal/cpu=$WORK/b004/_pkg_.a
packagefile runtime/internal/atomic=$WORK/b006/_pkg_.a
packagefile runtime/internal/math=$WORK/b007/_pkg_.a
packagefile runtime/internal/sys=$WORK/b008/_pkg_.a
EOF
/<..>/compile
-o $WORK/b002/_pkg_.a
...
-p runtime
./alg.go ./atomic_pointer.go ./cgo.go ./cgocall.go ./cgocallback.go ./cgocheck.go ./chan.go ./checkptr.go ./compiler.go ./complex.go ./cpuflags.go ./cpuflags_amd64.go ./cpuprof.go ./cputicks.go ./debug.go ./debugcall.go ./debuglog.go ./debuglog_off.go ./defs_darwin_amd64.go ./env_posix.go ./error.go ./extern.go ./fastlog2.go ./fastlog2table.go ./float.go ./hash64.go ./heapdump.go ./iface.go ./lfstack.go ./lfstack_64bit.go ./lock_sema.go ./malloc.go ./map.go ./map_fast32.go ./map_fast64.go ./map_faststr.go ./mbarrier.go ./mbitmap.go ./mcache.go ./mcentral.go ./mem_darwin.go ./mfinal.go ./mfixalloc.go ./mgc.go ./mgcmark.go ./mgcscavenge.go ./mgcstack.go ./mgcsweep.go ./mgcsweepbuf.go ./mgcwork.go ./mheap.go ./mpagealloc.go ./mpagealloc_64bit.go ./mpagecache.go ./mpallocbits.go ./mprof.go ./mranges.go ./msan0.go ./msize.go ./mstats.go ./mwbbuf.go ./nbpipe_pipe.go ./netpoll.go ./netpoll_kqueue.go ./os_darwin.go ./os_nonopenbsd.go ./panic.go ./plugin.go ./preempt.go ./preempt_nonwindows.go ./print.go ./proc.go ./profbuf.go ./proflabel.go ./race0.go ./rdebug.go ./relax_stub.go ./runtime.go ./runtime1.go ./runtime2.go ./rwmutex.go ./select.go ./sema.go ./signal_amd64.go ./signal_darwin.go ./signal_darwin_amd64.go ./signal_unix.go ./sigqueue.go ./sizeclasses.go ./slice.go ./softfloat64.go ./stack.go ./string.go ./stubs.go ./stubs_amd64.go ./stubs_nonlinux.go ./symtab.go ./sys_darwin.go ./sys_darwin_64.go ./sys_nonppc64x.go ./sys_x86.go ./time.go ./time_nofake.go ./timestub.go ./trace.go ./traceback.go ./type.go ./typekind.go ./utf8.go ./vdso_in_none.go ./write_err.go
/<..>/asm ... -o $WORK/b002/asm.o ./asm.s
/<..>/asm ... -o $WORK/b002/asm_amd64.o ./asm_amd64.s
/<..>/asm ... -o $WORK/b002/duff_amd64.o ./duff_amd64.s
/<..>/asm ... -o $WORK/b002/memclr_amd64.o ./memclr_amd64.s
/<..>/asm ... -o $WORK/b002/memmove_amd64.o ./memmove_amd64.s
/<..>/asm ... -o $WORK/b002/preempt_amd64.o ./preempt_amd64.s
/<..>/asm ... -o $WORK/b002/rt0_darwin_amd64.o ./rt0_darwin_amd64.s
/<..>/asm ... -o $WORK/b002/sys_darwin_amd64.o ./sys_darwin_amd64.s
/<..>/pack r $WORK/b002/_pkg_.a $WORK/b002/asm.o $WORK/b002/asm_amd64.o $WORK/b002/duff_amd64.o $WORK/b002/memclr_amd64.o $WORK/b002/memmove_amd64.o $WORK/b002/preempt_amd64.o $WORK/b002/rt0_darwin_amd64.o $WORK/b002/sys_darwin_amd64.o
Vous pouvez voir pourquoi les actions précédentes étaient nécessaires en regardant b002
.
b002
contient tous les packages d'exécution
nécessaires pour exécuter les binaires de Go. Par exemple, b002
contient également une implémentation Go GC appelée mgc.go
. Il importe «b004» («internal / cpu») et «b006» («runtime / internal / atomic»).
«b002» est peut-être le paquet le plus complexe de la bibliothèque principale, mais la construction elle-même est le même processus qu'avant. En d'autres termes, le fichier produit par asm
et compile
est pack
ed to _pkg_.a
.
La sortie de b002
est un fichier d'archive appelé $ WORK / b002 / _pkg_.a
qui correspond à runtime
.
b001
cat >$WORK/b001/importcfg << 'EOF'
# import config
packagefile runtime=$WORK/b002/_pkg_.a
EOF
cd /<..>/main
/<..>/compile ... -o $WORK/b001/_pkg_.a -p main ./main.go
cat >$WORK/b001/importcfg.link << 'EOF'
packagefile command-line-arguments=$WORK/b001/_pkg_.a
packagefile runtime=$WORK/b002/_pkg_.a
packagefile internal/bytealg=$WORK/b003/_pkg_.a
packagefile internal/cpu=$WORK/b004/_pkg_.a
packagefile runtime/internal/atomic=$WORK/b006/_pkg_.a
packagefile runtime/internal/math=$WORK/b007/_pkg_.a
packagefile runtime/internal/sys=$WORK/b008/_pkg_.a
EOF
/<..>/link
-o $WORK/b001/exe/a.out
-importcfg $WORK/b001/importcfg.link
-buildmode=exe
-buildid=yC-qrh2sY_qI0zh2-NE7/owNzOBTqPO00FkqK0_lF/HPXqvMz_4PvKsQzqGWgD/yC-qrh2sY_qI0zh2-NE7
-extld=clang
$WORK/b001/_pkg_.a
mv $WORK/b001/exe/a.out main
First it builds an importcfg that includes runtime built in b002 to then compile main.go to pkg.a
importcfg
qui inclut le runtime
de b002
, puis compilez main.go
pour créer un _pkg_.a
.importcfg.link
qui contient arguments de ligne de commande = $ WORK / b001 / _pkg_.a
en plus de tous les paquets qui apparaissaient auparavant, et liez-les avec la commande link
pour exécuter le fichier. Créer un.main
et déplacez-vous vers la destination de sortie.Complétons l'argument de link
.
-buildmode
: Construisez le fichier exécutable-extld
: se référer à un éditeur de liens externeJ'ai enfin obtenu ce que je cherchais.
Le binaire «main» est né de «b001».
La création de graphiques d'action pour une mise en cache efficace est la même idée que les outils de construction que Bazel utilise pour les versions rapides.
Les "identifiants d'action" et "id de contenu" de Golang correspondent au "cache d'action" et au "magasin adressable par le contenu (CAS)" que Bazel utilise dans le cache.
Bazel est un produit Google, tout comme Golang. Il serait très raisonnable pour eux d'avoir une philosophie similaire sur la façon de créer des logiciels rapidement et avec précision.
Dans le package rules_go
de Bazel, vous pouvez voir comment réimplémenter go build
dans le code builder
.
Il s'agit d'une implémentation très propre, car les graphiques d'action, la gestion des dossiers et la mise en cache sont gérés en externe par Bazel.
go build
a fait beaucoup pour le compiler, même avec un programme à ne rien faire comme celui-ci.
Je n'ai pas donné trop de détails sur l'outil (compile`` asm
) et ses fichiers d'entrée et de sortie ( .a`` .o
.s
).
De plus, cette fois, je ne fais que compiler le programme le plus basique.
Vous pouvez rendre la compilation plus compliquée en procédant comme suit:
fmt
dans la sortie Hello world
ajoutera 23 actions supplémentaires au graphique d'action.go.mod
pour référencer des packages externesGOOS
et GOARCH
Par exemple, lors de la compilation pour wasm
, le contenu des actions et des arguments est complètement différent.Lancer go build
et inspecter les journaux est une approche descendante pour apprendre comment fonctionne le compilateur Go. Si vous voulez apprendre des bases, c'est un excellent point de départ pour plonger dans des ressources telles que:
References