سيجلب الإصدار القادم من الإصدار 1.11 من لغة برمجة Go دعمًا تجريبيًا للوحدات - نظام إدارة تبعية جديد لـ Go. (ترجمة ملاحظة: تم إصدارها )
في الآونة الأخيرة ، كتبت بالفعل مشاركة صغيرة حول هذا الموضوع . منذ ذلك الحين ، تغير شيء ما قليلاً ، وأصبحنا أقرب إلى الإصدار ، لذلك يبدو لي أن الوقت قد حان لمقال جديد - دعنا نضيف المزيد من الممارسة.
لذا ، إليك ما سنفعله: قم بإنشاء حزمة جديدة ثم قم بعمل بعض الإصدارات لمعرفة كيفية عملها.
إنشاء الوحدة
أولاً ، قم بإنشاء الحزمة الخاصة بنا. دعونا نسميها testmod. تفاصيل مهمة: يجب وضع دليل الحزمة خارج $GOPATH
، لأنه داخله ، يتم تعطيل دعم الوحدة بشكل افتراضي . وحدات Go هي الخطوة الأولى نحو التخلي الكامل عن $GOPATH
في المستقبل.
$ mkdir testmod $ cd testmod
مجموعتنا بسيطة للغاية:
package testmod import "fmt"
الحزمة جاهزة ، لكنها ليست وحدة نمطية بعد . دعنا نصلحه.
$ go mod init github.com/robteix/testmod go: creating new go.mod: module github.com/robteix/testmod
لدينا ملف جديد يسمى go.mod
في دليل الحزمة بالمحتويات التالية:
module github.com/robteix/testmod
قليلا ، ولكن هذا ما يحول مجموعتنا إلى وحدة نمطية .
الآن يمكننا دفع هذا الرمز إلى المستودع:
$ git init $ git add * $ git commit -am "First commit" $ git push -u origin master
حتى الآن ، أي شخص يريد استخدام الحزمة الخاصة بنا go get
:
$ go get github.com/robteix/testmod
وسيجلب هذا الأمر أحدث رمز من الفرع master
. لا يزال هذا الخيار يعمل ، ولكن سيكون من الأفضل إذا لم نعد نفعله بعد الآن ، لأنه الآن "هناك طريقة أفضل". في الواقع ، يعد أخذ الشفرة مباشرة من الفرع master
أمرًا خطيرًا ، نظرًا لأننا لا نعرف أبدًا على وجه اليقين أن مؤلفي الحزمة لم يجروا تغييرات من شأنها "كسر" شفرتنا. لحل هذه المشكلة ، تم اختراع وحدات Go.
انحراف صغير عن وحدات الإصدار
يتم نسخ وحدات Go ، بالإضافة إلى وجود بعض خصوصية الإصدارات الفردية. يجب أن تكون على دراية بالمفاهيم الكامنة وراء النسخ الدلالي .
بالإضافة إلى ذلك ، يستخدم Go علامات مستودع التخزين عند البحث عن الإصدارات ، وتختلف بعض الإصدارات عن الباقي: على سبيل المثال ، يجب أن يكون للإصدار 2 والمزيد مسار استيراد مختلف عن الإصدارين 0 و 1 (سنصل إلى هذا).
بشكل افتراضي ، يقوم Go بتنزيل أحدث إصدار ، والذي يحتوي على علامة متوفرة في المستودع.
هذه ميزة مهمة ، حيث يمكن استخدامها عند العمل مع الفرع master
.
بالنسبة لنا الآن ، من المهم أنه عند إنشاء إصدار الحزمة الخاصة بنا ، نحتاج إلى وضع ملصق بالنسخة في المستودع.
فلنفعل ذلك.
جعل الإصدار الأول الخاص بك
الحزمة الخاصة بنا جاهزة ويمكننا "طرحها" للعالم كله. نقوم بذلك باستخدام التسميات ذات الإصدارات. ليكن رقم الإصدار 1.0.0:
$ git tag v1.0.0 $ git push --tags
تنشئ هذه الأوامر علامة في مستودع Github الخاص بي والتي تحدد الالتزام الحالي كإصدار 1.0.0.
Go لا تصر على ذلك ، ولكن من الجيد إنشاء فرع جديد إضافي ("v1") يمكننا إرسال تصحيحات إليه.
$ git checkout -b v1 $ git push -u origin v1
الآن يمكننا العمل في الفرع master
دون القلق من أنه يمكننا كسر إصدارنا.
باستخدام وحدتنا
دعونا نستخدم الوحدة المنشأة. سنكتب برنامجًا بسيطًا يستورد الحزمة الجديدة:
package main import ( "fmt" "github.com/robteix/testmod" ) func main() { fmt.Println(testmod.Hi("roberto")) }
حتى الآن ، يمكنك الجري go get github.com/robteix/testmod
لتنزيل الحزمة ، ولكن مع الوحدات تصبح أكثر إثارة للاهتمام. أولاً ، نحتاج إلى تمكين دعم الوحدة النمطية في برنامجنا الجديد.
$ go mod init mod
كما توقعت على الأرجح ، بناءً على ما قرأته سابقًا ، ظهر ملف go.mod
جديد في الدليل باسم الوحدة النمطية بداخله:
module mod
يصبح الوضع أكثر إثارة للاهتمام عندما نحاول وضع برنامجنا معًا:
$ go build go: finding github.com/robteix/testmod v1.0.0 go: downloading github.com/robteix/testmod v1.0.0
كما ترى ، عثر الأمر go
تلقائيًا على الحزمة التي تم استيرادها بواسطة برنامجنا وقام بتنزيلها.
إذا go.mod
ملف go.mod
، go.mod
أن شيئًا قد تغير:
module mod require github.com/robteix/testmod v1.0.0
وقد حصلنا على ملف جديد آخر يسمى go.sum
، والذي يحتوي على تجزئات الحزم للتحقق من الإصدار والملفات الصحيحة.
github.com/robteix/testmod v1.0.0 h1:9EdH0EArQ/rkpss9Tj8gUnwx3w5p0jkzJrd5tRAhxnA= github.com/robteix/testmod v1.0.0/go.mod h1:UVhi5McON9ZLc5kl5iN2bTXlL6ylcxE9VInV71RrlO8=
جعل إصدار الإفراج إصلاح الخلل
الآن ، لنفترض أننا وجدنا مشكلة في مجموعتنا: ليس هناك علامات ترقيم في التحية!
بعض الناس سيكونون غاضبين ، لأن تحيتنا الودية لم تعد ودية للغاية.
فلنصلح هذا ونصدر إصدارًا جديدًا:
// Hi returns a friendly greeting func Hi(name string) string { - return fmt.Sprintf("Hi, %s", name) + return fmt.Sprintf("Hi, %s!", name) }
لقد أجرينا هذا التغيير في الفرع v1
، لأنه لا علاقة له بما سنفعله بعد ذلك في الفرع v2
، ولكن في الحياة الواقعية ، ربما يجب عليك إجراء هذه التغييرات master
ثم إعادة إدخالها إلى v1
. على أي حال ، يجب أن يكون الإصلاح في الفرع v1
ونحتاج إلى وضع علامة على هذا كإصدار جديد.
$ git commit -m "Emphasize our friendliness" testmod.go $ git tag v1.0.1 $ git push --tags origin v1
تحديث الوحدات
افتراضيًا ، لا يقوم Go بتحديث الوحدات النمطية بدون طلب. "وهذا أمر جيد" ، لأننا جميعًا نرغب في إمكانية التنبؤ في إصداراتنا. إذا تم تحديث وحدات Go تلقائيًا في كل مرة يتم فيها إصدار إصدار جديد ، فسنعود إلى "العصور المظلمة قبل Go1.11". ولكن لا ، نحتاج إلى إخبار Go بتحديث الوحدات النمطية لنا.
وسوف نقوم بذلك بمساعدة صديقنا القديم - go get
:
تشغيل go get -u
لاستخدام آخر إصدار ثانوي أو إصدار تصحيح (على سبيل المثال ، سيتم تحديث الأمر من 1.0.0 إلى 1.0.1 ، على سبيل المثال ، 1.1.1 أو إلى 1.1.0 ، إذا كان هذا الإصدار متاحًا)
run go get -u=patch
لاستخدام أحدث إصدار من التصحيح (أي سيتم تحديث الحزمة إلى 1.0.1 ، ولكن ليس إلى 1.1.0)
تشغيل ، go get package@version
للترقية إلى إصدار معين (على سبيل المثال ، github.com/robteix/testmod@v1.0.1
)
لا توجد طريقة في هذه القائمة للترقية إلى أحدث إصدار رئيسي . هناك سبب وجيه لذلك ، كما سنرى قريبًا.
نظرًا لأن برنامجنا استخدم الإصدار 1.0.0 من الحزمة الخاصة بنا وقمنا للتو بإنشاء الإصدار 1.0.1 ، فإن أيًا من الأوامر التالية سيحدثنا إلى 1.0.1:
$ go get -u $ go get -u=patch $ go get github.com/robteix/testmod@v1.0.1
بعد البدء (دعنا نقول go get -u
) ، تغير go.mod
بنا:
module mod require github.com/robteix/testmod v1.0.1
نسخة رئيسية
وفقًا لمواصفات الإصدار الدلالي ، تختلف النسخة الرئيسية عن الإصدار الثانوي. يمكن أن تكسر الإصدارات الرئيسية التوافق العكسي. من وجهة نظر وحدات Go ، فإن الإصدار الرئيسي عبارة عن حزمة مختلفة تمامًا.
قد يبدو الأمر جامحًا في البداية ، ولكن من المنطقي: نسختين من المكتبة غير متوافقة مع بعضها البعض هما مكتبتان مختلفتان.
دعونا نجري تغييرًا كبيرًا في مجموعتنا. لنفترض ، بمرور الوقت ، أنه أصبح من الواضح لنا أن واجهة برمجة التطبيقات الخاصة بنا بسيطة للغاية ومحدودة للغاية بالنسبة لحالات مستخدم المستخدمين لدينا ، لذلك نحتاج إلى تغيير وظيفة Hi()
لقبول لغة الترحيب كمعلمة:
package testmod import ( "errors" "fmt" )
ستنقطع البرامج الحالية التي تستخدم واجهة برمجة التطبيقات لدينا لأنها (أ) لا تجتاز اللغة كمعلمة و (ب) لا تتوقع عودة خطأ. لم تعد واجهة برمجة التطبيقات الجديدة متوافقة مع الإصدار 1.x ، لذا عليك تلبية الإصدار 2.0.0.
ذكرت في وقت سابق أن بعض الإصدارات لها ميزات ، والآن هذا هو الحال.
يجب أن يغير الإصدار 2 أو الأحدث مسار الاستيراد. الآن هذه مكتبات مختلفة.
سنقوم بذلك عن طريق إضافة مسار إصدار جديد إلى اسم الوحدة النمطية الخاصة بنا.
module github.com/robteix/testmod/v2
كل شيء آخر هو نفسه: ادفع ، ضع علامة على أنه v2.0.0 (واختار فرع v2 اختياريًا)
$ git commit testmod.go -m $ git checkout -b v2 # optional but recommended $ echo > go.mod $ git commit go.mod -m $ git tag v2.0.0 $ git push --tags origin v2 # or master if we don't have a branch
تحديث الإصدار الرئيسي
على الرغم من أننا أصدرنا إصدارًا جديدًا غير متوافق من مكتبتنا ، إلا أن البرامج الحالية لم تنقطع ، لأنها تستمر في استخدام الإصدار 1.0.1.
go get -u
لن يتم تنزيل الإصدار 2.0.0.
ولكن في مرحلة ما ، أنا ، بصفتي مستخدم مكتبة ، قد أرغب في الترقية إلى الإصدار 2.0.0 ، لأنني ، على سبيل المثال ، أنا واحد من هؤلاء المستخدمين الذين يحتاجون إلى دعم لعدة لغات.
للتحديث ، أحتاج إلى تغيير برنامجي وفقًا لذلك:
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) }
الآن ، عندما أقوم بتشغيل go build
، فإنه "ينهي" وينزّل الإصدار 2.0.0 من أجلي. لاحظ أنه على الرغم من أن مسار الاستيراد ينتهي الآن في "v2" ، لا يزال Go يشير إلى الوحدة النمطية باسمها الحقيقي ("testmod").
كما قلت ، فإن النسخة الرئيسية هي في كل شيء حزمة مختلفة. هذه الوحدات النمطية Go غير متصلة بأي شكل من الأشكال. هذا يعني أنه يمكننا الحصول على نسختين غير متوافقتين في ثنائي واحد:
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) }
وهذا يزيل المشكلة الشائعة في إدارة التبعيات عندما تعتمد التبعيات على إصدارات مختلفة من المكتبة نفسها.
نرتب الأشياء
دعنا نعود إلى الإصدار السابق ، الذي يستخدم testmod 2.0.0 فقط - إذا go.mod
من محتويات go.mod
، فسوف نلاحظ شيئًا:
module mod require github.com/robteix/testmod v1.0.1 require github.com/robteix/testmod/v2 v2.0.0
بشكل افتراضي ، لا يقوم Go بإزالة التبعيات من go.mod
حتى تطلبها. إذا كان لديك تبعيات لم تعد هناك حاجة إليها وترغب في تنظيفها ، يمكنك استخدام الأمر tidy
الجديد:
$ go mod tidy
الآن لدينا فقط التبعيات التي نستخدمها حقًا.
البيع
وحدات Go بشكل افتراضي تتجاهل الدليل vendor/
. الفكرة هي التخلص التدريجي من البيع 1 . ولكن إذا كنا لا نزال نرغب في إضافة التبعيات "المنفصلة" إلى عنصر تحكم الإصدار الخاص بنا ، فيمكننا القيام بذلك:
$ go mod vendor
سيقوم الفريق بإنشاء vendor/
الدليل في جذر مشروعنا ، الذي يحتوي على شفرة المصدر لجميع التبعيات.
ومع ذلك ، go build
بشكل افتراضي لا يزال يتجاهل محتويات هذا الدليل. إذا كنت ترغب في جمع التبعيات من vendor/
الدليل ، فيجب عليك طلبها بشكل صريح.
$ go build -mod vendor
أفترض أن العديد من المطورين الذين يرغبون في استخدام البيع سيعملون ، كالمعتاد ، على أجهزتهم ويستخدمون -mod vendor
على CI الخاص بهم.
مرة أخرى ، تبتعد وحدات Go عن فكرة البيع إلى استخدام الوكلاء للوحدات النمطية لأولئك الذين لا يرغبون في الاعتماد بشكل مباشر على خدمات التحكم في إصدار المنبع.
هناك طرق للتأكد من أن الشبكة غير متاحة (على سبيل المثال ، باستخدام GOPROXY=off
) ، ولكن هذا هو موضوع المقالة التالية.
الخلاصة
قد تبدو المقالة معقدة لشخص ما ، ولكن هذا لأنني حاولت أن أشرح الكثير في وقت واحد. الحقيقة هي أن وحدات Go بسيطة بشكل عام اليوم - كالعادة نقوم باستيراد الحزمة في الكود الخاص بنا ، وفريق go
يقوم بالباقي لنا. يتم تحميل التبعيات تلقائيًا أثناء التجميع.
تقضي الوحدات أيضًا على الحاجة إلى $GOPATH
، والذي كان حجر عثرة لمطوري Go الجدد الذين واجهوا مشاكل في فهم سبب وضع شيء ما في دليل معين.
تم إهمال البيع (بشكل غير رسمي) لصالح استخدام وكيل. 1
يمكنني عمل مقالة منفصلة حول الوكلاء لوحدات Go.
ملاحظات:
1 أعتقد أن هذا تعبير عالي جدًا وقد يكون لدى البعض انطباع بأن عملية البيع تتم إزالتها الآن. الأمر ليس كذلك. البيع لا يزال يعمل ، وإن كان بشكل مختلف قليلاً عن ذي قبل. على ما يبدو ، هناك رغبة في استبدال البيع بشيء أفضل ، على سبيل المثال ، وكيل (وليس حقيقة). حتى الآن ، هذا هو ببساطة السعي إلى حل أفضل. لن يختفي البيع حتى يتم العثور على بديل جيد (إن وجد).