اذهب إلى المحتوى

يؤدي بناء الثنائيات أو الملفات التنفيذية Binaries جنبًا إلى جنب مع إنشاء البيانات الوصفية Metadata والمعلومات الأخرى المتعلقة بالإصدار Version عند نشر التطبيقات إلى تحسين عمليات المراقبة Monitoring والتسجيل Logging وتصحيح الأخطاء، وذلك من خلال إضافة معلومات تعريف تساعد في تتبع عمليات البناء التي تُجريها بمرور الوقت. يمكن أن تتضمن معلومات الإصدار العديد من الأشياء التي تتسم بالديناميكية، مثل وقت البناء والجهاز أو المستخدم الذي أجرى عملية بناء الملف التنفيذي ورقم المعرّف ID للإيداع Commit على نظام إدارة الإصدار VCS الذي تستخدمه غيت Git مثلًا. بما أن هذه المعلومات تتغير باستمرار، ستكون كتابة هذه المعلومات ضمن الشيفرة المصدر مباشرةً، وتعديلها في كل مرة نرغب فيها بإجراء تعديل أو بناء جديد أمرًا مملًا، وقد يُعرّض التطبيق لأخطاء. يمكن للملفات المصدرية التنقل وقد تُبدّل المتغيرات أو الثوابت الملفات خلال عملية التطوير، مما يؤدي إلى كسر عملية البناء.

إحدى الطرق المستخدمة لحل هذه المشكلة في لغة جو هي استخدام الراية ldflags- مع الأمر go build لإدراج معلومات ديناميكية في الملف الثنائي التنفيذي في وقت البناء دون الحاجة إلى تعديل التعليمات البرمجية المصدرية. تُشير Id ضمن الراية السابقة إلى الرابط linker، الذي يُمثّل البرنامج الذي يربط الأجزاء المختلفة من الشيفرة المصدرية المُصرّفة مع الملف التنفيذي النهائي. إذًا، تعني ldflags رايات الرابط linker flags؛ لأنها تمرر إشارة إلى الأداة cmd/link الخاصة بلغة جو، والتي تسمح لك بتغيير قيم الحزم المستوردة في وقت البناء من سطر الأوامر.

سنستخدم في هذه المقالة الراية ldflags- لتغيير قيمة المتغيرات في وقت البناء وإدخال المعلومات الديناميكية ضمن ملف ثنائي تنفيذي، من خلال تطبيق يطبع معلومات الإصدار على الشاشة.

المتطلبات

بناء تطبيق تجريبي

يجب أن يكون لدينا تطبيق حتى نستطيع تجريب عملية إدراج المعلومات إليه دينامكيًا من خلال الراية ldflags-. سنبني في هذه الخطوة تطبيقًا بسيطًا للتجريب عليه، بحيث يطبع المعلومات الخاصة بالإصدار. بدايةً أنشئ مجلدًا باسم app -يُمثّل اسم التطبيق- داخل المجلد src:

$ mkdir app

انتقل إلى هذا المجلد:

$ cd app

أنشئ باستخدام محرر النصوص الذي تُفضّله وليكن نانو nano الملف main.go:

$ nano main.go

ضع الشيفرة التالية بداخل هذا الملف، والتي تؤدي إلى طباعة معلومات الإصدار الحالي من التطبيق:

package main

import (
    "fmt"
)

var Version = "development"

func main() {
    fmt.Println("Version:\t", Version)
}

صرّحنا داخل الدالة main عن متغير يُدعى Version، ثم طبعنا السلسلة النصية :Version متبوعةً بإزاحة جدول واحدة tab كما يلي: t\، ثم قيمة المتغير Version. هنا أعطينا متغير الإصدار القيمة development، والتي ستكون إشارةً إلى الإصدار الافتراضي من التطبيق. سنُعدّل لاحقًا قيمة هذا المتغير، بحيث تُشير إلى الإصدار الرسمي من التطبيق، ووفقًا للتنسيق المُتبع في تسمية الإصدارات.

احفظ واغلق الملف، ثم ابنِ الملف وشغّله للتأكد من أنه يعمل:

$ go build
$ ./app

ستحصل على الخرج التالي:

Version:     development

لديك الآن تطبيق يطبع معلومات الإصدار الافتراضي، ولكن ليس لديك حتى الآن طريقةً لتمرير معلومات الإصدار الحالي في وقت البناء. ستستخدم في الخطوة التالية الراية ldflags- لحل هذه المشكلة.

استخدام ldflags مع go build

تحدّثنا سابقًا أن رايات الربط تُستخدم لتمرير الرايات إلى أداة الربط الأساسية underlying الخاصة بلغة جو. يحدث ذلك وفقًا للصيغة التالية:

$ go build -ldflags="-flag"

هنا مرّرنا flag إلى الأداة الأساسية go tool link التي تعمل بمثابة جزء من الأمر go build. هنا وضعنا علامتي اقتباس حول القيمة التي نمررها إلى ldflags، وذلك لكي نمنع حدوث التباس لدى سطر الأوامر (لكي لا يفسرها بطريقة خاطئة أو يعدها عدة محارف كل منها لغرض مختلف). يمكنك تمرير العديد من رايات الروابط، وفي هذه المقالة سنحتاج إلى استخدام الراية X- لضبط معلومات متغير الإصدار في وقت الربط link time، وسيتبعها مسار المتغير (هنا اسم الحزمة متبوعة باسم المتغير) مع قيمته الجديدة.

$ go build -ldflags="-X 'package_path.variable_name=new_value'"

لاحظ أننا وضعنا الخيار X- ضمن علامتي اقتباس وبجانبها وضعنا اسم الحزمة متبوعةً بنقطة "." متبوعةً باسم المتغير والقيمة الجديدة وأحطناهم بعلامات اقتباس مفردة لكي يُفسرها سطر الأوامر على أنها كتلة واحدة. إذًا، سنستخدم الصيغة السابقة لاستبدال قيمة متغير الإصدار Version في تطبيقنا:

$ go build -ldflags="-X 'main.Version=v1.0.0'"

تمثّل main هنا مسار الحزمة للمتغير Version، لأنه يتواجد داخل الملف main.go. هنا Version هو المتغير المطلوب تعديله، والقيمة v1.0.0 هي القيمة الجديدة التي نريد ضبطه عليها.

عندما نستخدم الراية ldflags، يجب أن تكون القيمة التي تريد تغييرها موجودة وأن يكون المتغير موجودًا ضمن مستوى الحزمة ومن نوع string. لا يُسمح بأن يكون المتغير ثابتًا const، أو أن تُضبط قيمته من خلال استدعاء دالة. يتوافق كل شيء هنا مع المتطلبات، لذا ستعمل الأمور كما هو متوقع؛ فالمتغير موجود ضمن الملف main.go والمتغير والقيمة v1.0.0 التي نريد ضبط المتغير عليها كلاهما من النوع string.

شغّل التطبيق بعد بنائه:

$./app

ستحصل على الخرج التالي:

Version:     v1.0.0

إذًا، استطعنا من خلال الراية ldflags- تعديل قيمة متغير الإصدار من development إلى v1.0.0 في وقت البناء. يمكنك تضمين تفاصيل الإصدار ومعلومات الترخيص وغيرهم من المعلومات باستخدام ldflags- جنبًا إلى جنب مع الملف التنفيذي النهائي الذي ترغب بنشره وذلك فقط من خلال سطر الأوامر. في هذا المثال: كان المتغير موجود في مسار واضح، لكن في أمثلة أخرى قد يكون العثور على مسار المتغير أمرًا معقدًا. سنناقش في الخطوة التالية هذا الموضوع، وسنرى ماهي أفضل الطرق لتحديد مسارات المتغيرات الموجودة في حزم فرعية في الحزم ذات الهيكلية الأكثر تعقيدًا.

تحديد مسار الحزمة للمتغيرات

كنا قد وضعنا في المثال السابق متغير الإصدار Version ضمن المستوى الأعلى من الحزمة في الملف main.go، لذا كان أمر الوصول إليه بسيطًا. هذه حالة مثالية، لكن في الواقع هذا لايحدث دائمًا، فأحيانًا تكون المتغيرات ضمن حزمة فرعية أخرى. عمومًا، لا يُحبذ وضع هكذا متغيرات ضمن main، لأنه حزمة غير قابلة للاستيراد، ويُفضّل وضع هكذا متغيرات ضمن حزمة أخرى. لذا سنعدل بعض الأمور في تطبيقنا، إذ سنُنشئ الحزمة app/build ونضع فيها معلومات حول وقت بناء الملف التنفيذي واسم المستخدم الذي بناه.

انشئ مجلدًا جديدًا باسم الحزمة الجديدة:

$ mkdir -p build

انشئ ملفًا جديدًا باسم build.go من أجل وضع المتغير/المتغيرات ضمنه:

$ nano build/build.go

ضع بداخله المتغيرات التالية بعد فتحه باستخدام محرر النصوص الذي تريده:

package build
var Time string # سيُخزّن وقت بناء التطبيق
var User string # سيُخزّن اسم المستخدم الذي بناه

لا يمكن لهذين المتغيرين أن يتواجدا بدون قيم، لذا لا داعٍ لوضع قيم افتراضية لهما كما فعلنا مع متغير الإصدار. احفظ وأغلق الملف.

افتح ملف main.go لوضع المتغيرات داخله:

$ nano main.go

ضع فيه المحتويات التالية:

package main

import (
    "app/build"
    "fmt"
)

var Version = "development"

func main() {
    fmt.Println("Version:\t", Version)
    fmt.Println("build.Time:\t", build.Time)
    fmt.Println("build.User:\t", build.User)
}

استوردنا الحزمة app/build، ثم طبعنا قيمة build.Time و build.User بنفس الطريقة التي طبعنا فيها Version سابقًا. احفظ وأغلِق الملف.

إذا أردت الآن الوصول إلى هذه المتغيرات عند استخدام الراية ldflags-، يمكنك استخدام اسم الحزمة app/build يتبعها Time. أو User. كوننا نعرف مسار الحزمة. سنستخدم الأمر nm بدلًا من ذلك من أجل محاكاة موقف أكثر تعقيدًا، والذي يكون فيه مسار الحزمة غير واضح.

يُنتج الأمر go tool nm الرموز المتضمنة في ملف تنفيذي أو ملف كائن أو أرشيف، إذ يشير الرمز إلى كائن موجود في الشيفرة (متغير أو دالة مُعرّفة أو مستوردة). يمكنك العثور بسرعة على معلومات المسار من خلال إنشاء جدول رموز باستخدام nm واستخدام grep للبحث عن متغير.

ملاحظة: لن يساعدك الأمر nm في العثور على مسار المتغير إذا كان اسم الحزمة يحتوي على أي محارف ليست ASCII، أو " أو ٪ (هذه قيود خاصة بالأداة).

ابنِ التطبيق أولًا لاستخدام هذا الأمر:

$ go build

وجّه الأداة nm إلى التطبيق بعد بنائه وابحث في الخرج:

$ go tool nm ./app | grep app

عند تشغيل الأمر nm ستحصل على العديد من البيانات، لذا وضعنا | لتوجيه الخرج إلى الأمر grep الذي يبحث بعد ذلك عن المسارات التي تحتوي على الاسم app في المستوى الأعلى منها. ستحصل على الخرج التالي:

 55d2c0 D app/build.Time
  55d2d0 D app/build.User
  4069a0 T runtime.appendIntStr
  462580 T strconv.appendEscapedRune
. . .

يظهر في أول سطرين مسارات المتغيرات التي تبحث عنها: app/build.Time و app/build.User.

ابنِ التطبيق الآن بعد أن تعرفت على المسارات، وعدّل متغير الإصدار إضافةً إلى المتغيرات الجديدة التي أضفناها والتي تُمثّل وقت بناء التطبيق واسم المستخدم (تذكر أنك تُعدّل هذه المتغيرات في وقت البناء). لأجل ذلك ستحتاج إلى تمرير عدة رايات X- إلى الراية ldflags-:

$ go build -v -ldflags="-X 'main.Version=v1.0.0' -X 'app/build.User=$(id -u -n)' -X 'app/build.Time=$(date)'"

هنا مررنا الأمر id -u -n لعرض المستخدم الحالي، والأمر date لعرض التاريخ الحالي.

شغّل التطبيق بعد بنائه:

$ ./app

ستحصل على الخرج التالي في حال كنت تعمل على نظام يستند إلى يونيكس Unix:

Version:     v1.0.0
build.Time:     Fri Oct  4 19:49:19 UTC 2019
build.User:     sammy

لديك الآن ملف تنفيذي يتضمن معلومات الإصدار والبناء، والتي يمكن أن توفر مساعدة حيوية في الإنتاج عند حل المشكلات.

الخاتمة

وضّحت هذه المقالة مدى قوة استخدام ldflags لإدخال معلومات في وقت البناء إذا طُبقت بطريقة سليمة. يمكنك بهذه الطريقة التحكم في رايات الميزة feature flags (هي تقنية برمجية تُمكّن الفريق البرمجي من إجراء تغييرات بدون استخدام المزيد من التعليمات البرمجية) ومعلومات البيئة ومعلومات الإصدار والأمور الأخرى دون إدخال تغييرات على الشيفرة المصدر. يمكنك الاستفادة من الخصائص التي تمنحك إياها لغة جو لعمليات النشر من خلال استخدام ldflags في عمليات البناء.

ترجمة -وبتصرف- للمقال Using ldflags to Set Version Information for Go Applications لصاحبه Gopher Guides.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...