מודול הוא עץ תיקיות של חבילות Go עם קובץ go.mod בשורש שלו. go.mod נותן שם למודול ומפרט את הגרסאות של כל מודול אחר שהוא תלוי בו. יוצרים מודול עם go mod init:
mkdir myapp
cd myapp
go mod init example.com/myapp
go: creating new go.mod: module example.com/myapp
ה-go.mod שנוצר:
module example.com/myapp
go 1.24.5
זה מספיק כדי לבנות. כל פרויקט Go החל מ-Go 1.16 הוא מודול, ו-go build, go run . ו-go test מסרבים לעבוד בלעדיו:
go: go.mod file not found in current directory or any parent directory; see 'go help modules'
בחירת נתיב מודול
נתיב המודול הוא הקידומת של כל נתיב import בתוך המודול. עם המודול example.com/myapp, חבילה בתיקייה internal/store מיובאת כ-example.com/myapp/internal/store.
| מצב | נתיב מודול |
|---|---|
| קוד שמתארח ב-GitHub ואחרים עשויים לייבא | github.com/yourname/project |
| הקוד של החברה שלכם | yourcompany.com/project או ה-URL של המאגר |
| תוכנית פרטית או תרגיל | example.com/myapp או פשוט myapp |
| גרסה 2 ומעלה של מודול שפורסם | github.com/yourname/project/v2 |
בספרייה, הנתיב חייב להתאים למקום שבו הקוד נמצא, כי go get משתמשת בו כדי למצוא את המאגר. בתוכנית שרק אתם מריצים, הנתיב הוא רק שם. הימנעו ממילה בודדת שזהה לשם של חבילה בספרייה הסטנדרטית, כמו go mod init fmt או go mod init strings: הבנייה נכשלת אז עם ambiguous import: found package fmt in multiple modules.
הרצת go mod init בלי ארגומנט נכשלת מחוץ למבנה ה-GOPATH הישן:
go: cannot determine module path for source directory /home/ana/myapp (outside GOPATH, module path must be specified)
תנו לה נתיב.
הוספת תלויות
כתבו את ה-import, ואז תנו ל-Go להוריד אותו. נניח ש-main.go מייבא את github.com/google/uuid. בנייה לפני שהמודול מכיר אותו נותנת הוראה ברורה:
main.go:6:2: no required module provides package github.com/google/uuid; to add it:
go get github.com/google/uuid
כל אחת מהפקודות האלה פותרת את זה:
go get github.com/google/uuid
# or, to sync go.mod with every import in the module:
go mod tidy
go get מדווחת מה היא שינתה:
go: downloading github.com/google/uuid v1.6.0
go: added github.com/google/uuid v1.6.0
go mod tidy מדווחת איך היא פתרה את ה-import:
go: finding module for package github.com/google/uuid
go: downloading github.com/google/uuid v1.6.0
go: found github.com/google/uuid in github.com/google/uuid v1.6.0
ב-go.mod יש עכשיו שורת require:
module example.com/myapp
go 1.24.5
require github.com/google/uuid v1.6.0
go get עם גרסאות
| פקודה | השפעה |
|---|---|
go get pkg | הוספת pkg בגרסה האחרונה שלו, או שמירה על הגרסה הנוכחית אם הוא כבר נדרש |
go get pkg@v1.5.0 | שימוש בדיוק ב-v1.5.0 (שדרוג או שנמוך) |
go get pkg@latest | מעבר לגרסה האחרונה |
go get pkg@abc1234 | שימוש ב-commit מסוים (נרשם כ-pseudo-version) |
go get -u ./... | שדרוג כל תלות לגרסת ה-minor או ה-patch האחרונה שלה |
go get -u=patch ./... | שדרוג לגרסאות ה-patch האחרונות בלבד |
go get pkg@none | הסרת הדרישה |
go get go@1.24 | העלאת גרסת Go המינימלית של המודול |
go get משנה את go.mod. היא כבר לא בונה או מתקינה תוכניות; החל מ-Go 1.18 זה התפקיד של go install pkg@version.
תלויות עקיפות
דרישות שמסומנות // indirect הן מודולים שהקוד שלכם לא מייבא ישירות אבל הבנייה צריכה. החל מ-Go 1.17, go.mod מפרט כל מודול שמספק חבילה לבנייה, כך שהתלויות של התלויות שלכם מופיעות כאן עם הסימון הזה. go mod tidy מנהלת את הסימונים האלה; אתם לא מוסיפים אותם בעצמכם.
go mod tidy
הריצו go mod tidy בכל פעם שאתם מוסיפים או מסירים imports. הפקודה:
- מוסיפה דרישות עבור חבילות מיובאות שחסרות,
- מסירה דרישות ששום דבר כבר לא מייבא,
- מוסיפה את רשומות ה-
go.sumשהבנייה צריכה ומסירה רשומות ישנות.
היא בודקת כל חבילה במודול, כולל בדיקות וכל שילוב של build tags, ולכן לפעמים היא שומרת תלות שאתם לא רואים שימוש בה בפלטפורמה שלכם. הרצת go mod tidy לפני כל commit, ובדיקה ב-CI שהיא לא מייצרת diff, שומרות על go.mod מדויק.
go.sum
go.sum רושם hash לכל גרסת מודול שהבנייה משתמשת בה:
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
השורה הראשונה היא hash של הקבצים של המודול, והשנייה רק של ה-go.mod שלו. כשהפקודה go מורידה מודול, היא בודקת את ה-hash מול go.sum ומול מסד ה-checksums הציבורי (sum.golang.org), כך שגרסה ששונתה בזדון או בשקט מכשילה את הבנייה. עשו commit ל-go.sum לצד go.mod, ואף פעם אל תערכו אותו ידנית.
איך Go בוחרת גרסאות
Go משתמשת ב-minimal version selection. כל מודול מפרט את הגרסה המינימלית של כל תלות שהוא צריך, והבנייה משתמשת בגבוהה מבין המינימומים האלה, אף פעם לא במשהו חדש יותר. אם המודול שלכם דורש uuid v1.5.0 ותלות דורשת uuid v1.6.0, הבנייה משתמשת ב-v1.6.0, גם אם v1.7.0 קיימת. שום דבר לא משתדרג אלא אם מישהו מבקש זאת עם go get.
התוצאה היא שבניות ניתנות לשחזור בלי קובץ lock: go.mod יחד עם גרף התלויות קובעים לחלוטין כל גרסה. go list -m all מדפיסה את התוצאה:
go list -m all
example.com/myapp
github.com/google/uuid v1.6.0
גרסאות major
מודול בגרסה v2 ומעלה חייב לכלול את גרסת ה-major בנתיב שלו: github.com/yourname/project/v2. נתיב ה-import משתנה יחד איתו, כך ש-project ו-project/v2 הם מודולים שונים ויכולים להיות שניהם באותה בנייה. זה הכלל של Go לשינויים שוברים, ובגלל זה רואים imports כמו github.com/jackc/pgx/v5.
replace: עבודה מקומית על תלות
כדי לבדוק שינויים בתלות לפני שמפרסמים אותם, הפנו את הנתיב שלה לתיקייה מקומית:
module example.com/myapp
go 1.24.5
require example.com/mylib v1.2.0
replace example.com/mylib => ../mylib
או משורת הפקודה:
go mod edit -replace example.com/mylib=../mylib
go mod tidy
התיקייה חייבת להכיל go.mod משלה. replace חל רק כשבונים את המודול הזה ישירות, לא כשמישהו אחר תלוי בו, אז זכרו להסיר אותו לפני שאתם מתייגים גרסה.
כדי לערוך כמה מודולים בבת אחת בלי לגעת בקובצי ה-go.mod שלהם, Go 1.18 הוסיפה workspaces:
go work init . ../mylib
זה כותב קובץ go.work שגורם ל-mylib המקומי לקבל עדיפות. השאירו את go.work מחוץ לניהול הגרסאות, אלא אם כל הצוות משתמש באותו מבנה.
תלויות של כלים (Go 1.24)
Go 1.24 הוסיפה הנחיית tool, כך שמחוללי קוד ו-linters יכולים להיות מנוהלי גרסאות ב-go.mod במקום להיות מותקנים גלובלית:
go get -tool golang.org/x/tools/cmd/stringer
go tool stringer -type=Color
func init זה משהו אחר
חיפושים של "golang init" מתכוונים לעיתים קרובות לפונקציה init, שאין לה קשר ל-go mod init. כל חבילה יכולה להצהיר על func init(). היא לא מקבלת ארגומנטים, הקוד שלכם לא יכול לקרוא לה, והיא רצה פעם אחת, אוטומטית, אחרי שהמשתנים ברמת החבילה מוצבים ולפני main:
בקובץ יכולות להיות כמה פונקציות init, והן רצות לפי סדר הופעתן. חבילות מיובאות מסיימות קודם את האתחול שלהן. שמרו על init קטנה: עבודה שיכולה להיכשל קלה יותר לטיפול ולבדיקה כפונקציה רגילה שנקראת מ-main.
מודולים פרטיים ו-proxies
כברירת מחדל go מורידה מודולים דרך proxy.golang.org. ה-proxy הזה לא רואה מאגרים פרטיים, ולכן אמרו ל-Go אילו נתיבים פרטיים:
go env -w GOPRIVATE=github.com/yourcompany/*
מודולים שמתאימים ל-GOPRIVATE מורדים ישירות מהמאגר עם הרשאות ה-git שלכם ומדלגים על מסד ה-checksums.
שאלות נפוצות
מה עושה go mod init?
הפקודה יוצרת קובץ go.mod בתיקייה הנוכחית, מה שהופך את התיקייה הזאת לשורש של מודול. go mod init example.com/myapp כותבת את נתיב המודול ואת גרסת Go:
module example.com/myapp
go 1.24.5
הריצו אותה פעם אחת לכל פרויקט, לפני go build או go get.
במה להשתמש כנתיב המודול ב-go mod init?
בכתובת שממנה הקוד יורד אם אחרים מייבאים אותו, בדרך כלל נתיב המאגר: go mod init github.com/yourname/project. לתוכנית שאף אחד לא ייבא, כל שם עובד (go mod init myapp), אבל נתיב בסגנון דומיין עם נקודה כמו example.com/myapp מונע התנגשות עם שמות של חבילות בספרייה הסטנדרטית.
מה ההבדל בין go get ל-go mod tidy?
go get pkg@version מוסיפה תלות או משנה את הגרסה שלה. go mod tidy קוראת את קובצי המקור שלכם, מוסיפה כל מודול שה-imports שלכם צריכים וחסר ב-go.mod, מסירה דרישות ששום דבר לא מייבא, ומעדכנת את go.sum. זרימה נפוצה היא לכתוב את ה-import ואז להריץ go mod tidy.
האם לעשות commit ל-go.sum?
כן. go.sum מחזיק hashes קריפטוגרפיים של כל גרסת מודול שהבנייה שלכם משתמשת בה. commit שלו מאפשר לפקודה go לוודא שכולם מורידים תלויות זהות עד לרמת הבית. אף פעם אל תערכו אותו ידנית; go mod tidy מתחזקת אותו.
האם "golang init" זה אותו דבר כמו go mod init?
לא, אלה שני דברים שונים שחולקים מילה. go mod init היא פקודת טרמינל שיוצרת go.mod. func init() היא פונקציה שכותבים בקוד Go; היא רצה אוטומטית פעם אחת, לפני main, אחרי שהמשתנים של החבילה מאותחלים.