Menu

go run מול go build מול go install: קומפילציה והרצה של Go

מה כל אחת מהפקודות go run, go build ו-go install עושה, איך Go קובעת את שם הקובץ הבינארי, איך מבצעים cross-compile עם GOOS ו-GOARCH, והבדיקות go fmt ו-go vet שכדאי להריץ לפני commit.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

go run מקמפלת ומריצה תוכנית בצעד אחד ולא שומרת קובץ בינארי. go build מקמפלת ומשאירה קובץ הרצה בתיקייה הנוכחית. go install מקמפלת ושמה את קובץ ההרצה ב-$HOME/go/bin. שלושתן מקמפלות לקוד מכונה נייטיב; אף אחת מהן לא מפרשת (interprets) כלום.

פקודהמקמפלתמריצהשומרת קובץ בינארילאן הקובץ הבינארי הולך
go run .כןכןלאתיקייה זמנית, שנמחקת אחר כך
go buildכןלאכןהתיקייה הנוכחית (או -o path)
go installכןלאכן$GOBIN, אחרת $GOPATH/bin

התוכנית הבאה היא זו שמשמשת בדוגמאות בדף הזה. העורך מריץ אותה כמו ש-go run הייתה מריצה.

go run

go run מקבלת חבילה (בדרך כלל ., התיקייה הנוכחית) או רשימה של קובצי .go:

go run .
go run main.go
go run ./cmd/server

כל מה שבא אחרי החבילה מועבר לתוכנית שלכם כארגומנטים:

go run . --port 8080 verbose
version: dev
built for: linux/amd64
args: [--port 8080 verbose]

העדיפו go run . על פני go run main.go. ציון שם קובץ מקמפל רק את הקובץ הזה, כך שברגע שיש בחבילה קובץ שני, פונקציות שמוגדרות בו מדווחות כ-undefined. הדף על חבילות ו-imports מסביר את השגיאה הזאת בפירוט.

go run יכולה גם להריץ תוכנית מרוחקת בגרסה מסוימת בלי להתקין אותה, מה שנוח למחוללי קוד:

go run golang.org/x/tools/cmd/stringer@v0.30.0 -type=Color

go build

go build מקמפלת את החבילה בתיקייה הנוכחית. עבור חבילת main היא כותבת קובץ הרצה; עבור חבילת ספרייה היא מקמפלת, מדווחת על שגיאות וזורקת את התוצאה.

go mod init example.com/hello
go build
ls
go.mod  hello  main.go

השם של הקובץ הבינארי נקבע לפי הכללים האלה:

מה מריציםשם הקובץ הבינארי
go build במודול example.com/hellohello
go build ./cmd/serverserver
go build main.gomain (לפי שם הקובץ הראשון)
go build -o bin/app .bin/app
כל אחד מהנ"ל עם GOOS=windowsאותו שם ועוד .exe

עוד שתי צורות שתשתמשו בהן כל הזמן:

go build ./...      # compile every package in the module
go vet ./...        # static checks, covered below

./... פירושו "התיקייה הזאת וכל תיקייה שמתחתיה". go build ./... עם כמה חבילות main לא כותבת שום קובץ בינארי; זו בדיקה מהירה של "האם הכול מתקמפל".

דגלי בנייה שימושיים

דגלמה הוא עושה
-o nameקובץ או תיקיית פלט
-vהדפסת שמות החבילות בזמן שהן מתקמפלות
-raceבנייה עם ה-race detector (איטי יותר, קובץ גדול יותר; לבדיקות)
-trimpathהסרת נתיבים של מערכת הקבצים המקומית מהקובץ הבינארי, לבניות שניתנות לשחזור
-ldflags "-s -w"הסרת טבלת הסמלים ומידע הדיבוג DWARF, כך שהקובץ הבינארי קטן יותר
-ldflags "-X main.version=1.4.0"הצבת ערך למשתנה מחרוזת בזמן הקישור (link)
-tags nameהכללת קבצים שמוגנים באילוץ //go:build name

הדגל -X הוא הדרך שבה רוב פרויקטי Go מטביעים גרסה בקובץ הבינארי. הוא עובד רק על משתני string ברמת החבילה (לא על קבועים):

go build -o app -ldflags "-X main.version=1.4.0" .
./app
version: 1.4.0
built for: linux/amd64
args: []

הקובץ הבינארי גם רושם את גרסאות המודולים שלו, והחל מ-Go 1.18 גם את ה-commit ב-VCS שממנו הוא נבנה. go version -m ./app מדפיסה אותם, ותוכנית יכולה לקרוא אותם עם runtime/debug.ReadBuildInfo.

go install

go install בונה בדיוק כמו go build ואז מעבירה את קובץ ההרצה ל-$GOBIN, או ל-$GOPATH/bin כש-GOBIN לא מוגדר (כברירת מחדל $HOME/go/bin):

go install .
go env GOPATH

השימוש הנפוץ ביותר בה הוא התקנת כלים שכתובים ב-Go. עם @version היא מתקינה תוכנית בלי לגעת ב-go.mod שלכם:

go install golang.org/x/tools/gopls@latest
go install honnef.co/go/tools/cmd/staticcheck@latest

אם ה-shell לא מוצא כלי אחרי ההתקנה, $HOME/go/bin לא נמצא ב-PATH שלכם. הוסיפו export PATH=$PATH:$(go env GOPATH)/bin לפרופיל של ה-shell.

Cross-compile עם GOOS ו-GOARCH

Go יכולה לבנות למערכת הפעלה או למעבד אחרים מכל מכונה, בלי toolchain נוסף. הגדירו שני משתני סביבה עבור פקודת הבנייה:

GOOS=linux GOARCH=amd64 go build -o app-linux-amd64 .
GOOS=linux GOARCH=arm64 go build -o app-linux-arm64 .
GOOS=darwin GOARCH=arm64 go build -o app-macos .
GOOS=windows GOARCH=amd64 go build -o app.exe .

ב-PowerShell מגדירים משתני סביבה אחרת:

$env:GOOS = "linux"; $env:GOARCH = "amd64"; go build -o app-linux .

הצמדים הנפוצים ביותר:

GOOSGOARCHיעד
linuxamd64רוב השרתים והקונטיינרים
linuxarm64AWS Graviton, Raspberry Pi עם מערכת הפעלה של 64 סיביות
darwinarm64מחשבי Mac עם Apple Silicon
darwinamd64מחשבי Mac עם Intel
windowsamd64Windows של 64 סיביות
jswasmWebAssembly בדפדפן

go tool dist list מדפיסה כל צמד נתמך (48 ב-Go 1.24).

cross-compile קל כל כך רק לקוד Go טהור. חבילה שמשתמשת ב-cgo (קוד C, למשל חלק מהדרייברים של SQLite) צריכה cross-compiler של C עבור היעד. בזמן cross-compile, cgo מושבת כברירת מחדל, והגדרה מפורשת של CGO_ENABLED=0 נותנת גם קובץ בינארי סטטי לחלוטין ל-Linux, וזה מה שרוצים ב-image מינימלי של קונטיינר מסוג scratch או distroless.

go fmt ו-go vet

שתי בדיקות שייכות לכל תהליך עבודה, וגם ל-CI.

go fmt משכתבת קבצים בסגנון הרשמי האחד: טאבים, שדות מיושרים, ריווח אחיד. היא עוטפת את gofmt -l -w:

go fmt ./...
gofmt -l .     # list files that are not formatted; empty output means clean

go vet מדווחת על קוד שמתקמפל אבל כמעט בוודאות שגוי. אי התאמה בפורמט של Printf היא המקרה הקלאסי:

package main

import "fmt"

func main() {
	count := 3
	fmt.Printf("%s items\n", count)
}
go vet ./...
# example.com/hello
# [example.com/hello]
./main.go:7:2: fmt.Printf format %s has arg count of wrong type int

התוכנית מתקמפלת ומדפיסה %!s(int=3) items, וזה בדיוק סוג הבאג ש-vet קיימת כדי לתפוס. בדיקות vet נוספות כוללות העתקה של sync.Mutex לפי ערך, קוד שאי אפשר להגיע אליו, struct tags עם תחביר שגוי, ו-context.CancelFunc שאף פעם לא נקראת. go test מריצה אוטומטית חלק מהבדיקות האלה; go build לא מריצה אף אחת.

פקודות go נוספות

פקודהמטרה
go test ./...הרצת בדיקות
go mod tidyהוספת תלויות חסרות והסרת תלויות שלא בשימוש
go get pkg@versionהוספה או שינוי של תלות
go clean -cacheריקון ה-build cache
go envהדפסת הקונפיגורציה של Go
go doc fmt.Printlnהצגת תיעוד בטרמינל
go list -m allרשימת כל המודולים בבנייה

למה בניות מהירות בפעם השנייה

Go שומרת חבילות מקומפלות ב-build cache (go env GOCACHE). בנייה מחדש מקמפלת רק חבילות שקוד המקור או התלויות שלהן השתנו, כך ש-go run . על פרויקט שלא השתנה עולה כמעט מיד. אם בנייה אי פעם מתנהגת מוזר אחרי החלפת גרסת Go או משתני סביבה, go clean -cache מנקה אותו; לעיתים רחוקות תצטרכו את זה.

שאלות נפוצות

מה ההבדל בין go run ל-go build?

go run מקמפלת את התוכנית לתיקייה זמנית, מריצה אותה וזורקת את הקובץ הבינארי. go build מקמפלת את התוכנית וכותבת את הקובץ הבינארי לתיקייה הנוכחית, כך שאפשר להריץ אותו שוב או להעתיק אותו למקום אחר. השתמשו ב-go run בזמן הפיתוח וב-go build כשאתם צריכים את קובץ ההרצה.

איך קובעים את שם קובץ הפלט של go build?

השתמשו ב--o: go build -o myapp . כותבת את myapp (ב-Windows, כתבו בעצמכם -o myapp.exe). בלי -o, go build קוראת לקובץ הבינארי לפי האלמנט האחרון בנתיב ה-import של החבילה, או לפי הקובץ הראשון כשמעבירים קובצי .go, ומוסיפה .exe כשבונים עבור Windows.

איך מבצעים cross-compile של Go ל-Linux או ל-Windows?

הגדירו את GOOS ואת GOARCH עבור הבנייה: GOOS=linux GOARCH=amd64 go build -o app-linux . או GOOS=windows GOARCH=amd64 go build . (שמייצרת .exe). לקוד Go טהור לא צריך שום toolchain נוסף. go tool dist list מדפיסה כל צמד נתמך.

לאן go install שמה את הקובץ הבינארי?

ב-$GOBIN אם הוא מוגדר, אחרת ב-$GOPATH/bin, שהוא $HOME/go/bin כברירת מחדל. הוסיפו את התיקייה הזאת ל-PATH שלכם כדי להריץ כלים מותקנים לפי השם שלהם. go install example.com/tool@latest מתקינה כלי בלי להוסיף אותו למודול שלכם.

האם go build מריצה את go vet?

לא. go build רק מקמפלת. go test מריצה אוטומטית חלק מהבדיקות של go vet, אבל כדי לקבל את כולן הריצו go vet ./... בעצמכם, בדרך כלל ב-CI לצד gofmt -l ..

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל