أنظمة CI لتطوير iOS: تحويل Intel إلى ARM
نشرت: 2024-02-14في مشهد التكنولوجيا دائم التطور، يجب على الشركات التكيف مع رياح التغيير لتبقى ذات صلة وقادرة على المنافسة. أحد هذه التحولات التي أحدثت ثورة في عالم التكنولوجيا هو الانتقال من بنية Intel x86_64 إلى بنية iOS ARM، والتي تتمثل في شريحة Apple M1 الرائدة من Apple. وفي هذا السياق، أصبحت أنظمة CI لنظام التشغيل iOS أحد الاعتبارات الحاسمة للشركات التي تتنقل في هذا التحول، مما يضمن بقاء عمليات تطوير البرمجيات واختبارها فعالة ومحدثة بأحدث المعايير التكنولوجية.
أعلنت شركة Apple عن شرائح M1 الخاصة بها منذ ما يقرب من ثلاث سنوات، ومنذ ذلك الحين، أصبح من الواضح أن الشركة ستتبنى بنية ARM وستتخلى في النهاية عن دعم البرامج المستندة إلى Intel. للحفاظ على التوافق بين البنيات، طرحت شركة Apple إصدارًا جديدًا من Rosetta، وهو إطار الترجمة الثنائية الخاص بها، والذي أثبت موثوقيته في الماضي أثناء التحول الهندسي الكبير من PowerPC إلى Intel في عام 2006. ولا يزال التحول مستمرًا، وقد شهدنا يفقد Xcode دعم Rosetta في الإصدار 14.3.
في Miquido، أدركنا الحاجة إلى الانتقال من Intel إلى ARM منذ بضع سنوات. لقد بدأنا الاستعدادات في منتصف عام 2021. باعتبارنا دار برمجيات لديها العديد من العملاء والتطبيقات والمشاريع الجارية بشكل متزامن، واجهنا بعض التحديات التي كان علينا التغلب عليها. يمكن أن تكون هذه المقالة دليلك الإرشادي إذا كانت شركتك تواجه مواقف مماثلة. يتم وصف المواقف والحلول الموصوفة من منظور تطوير iOS - ولكن قد تجد رؤى مناسبة لتقنيات أخرى أيضًا. أحد العناصر المهمة في استراتيجية النقل لدينا يتضمن ضمان تحسين نظام CI الخاص بنا لنظام iOS بالكامل للبنية الجديدة، مما يسلط الضوء على أهمية تساعد أنظمة CI لنظام التشغيل iOS في الحفاظ على سير عمل فعال ومخرجات عالية الجودة وسط هذا التغيير الكبير.
المشكلة: ترحيل بنية Intel إلى iOS ARM
تم تقسيم عملية الهجرة إلى فرعين رئيسيين.
1. استبدال أجهزة كمبيوتر المطورين القائمة على Intel بأجهزة M1 Macbook الجديدة
وكان من المفترض أن تكون هذه العملية بسيطة نسبيا. لقد وضعنا سياسة لاستبدال جميع أجهزة Intel Macbook الخاصة بالمطورين تدريجيًا على مدار عامين. في الوقت الحالي، يستخدم 95% من موظفينا أجهزة Macbook المستندة إلى ARM.
ومع ذلك، واجهنا بعض التحديات غير المتوقعة خلال هذه العملية. في منتصف عام 2021، أدى النقص في أجهزة M1 Mac إلى إبطاء عملية الاستبدال لدينا. بحلول نهاية عام 2021، لم نتمكن سوى من استبدال عدد قليل من أجهزة Macbook من بين ما يقرب من 200 جهاز قيد الانتظار. قدرنا أن الأمر سيستغرق حوالي عامين لاستبدال جميع أجهزة Intel Mac التابعة للشركة بالكامل بأجهزة M1 Macbook، بما في ذلك المهندسين الذين لا يعملون بنظام iOS.
ولحسن الحظ، أصدرت شركة Apple شرائح M1 Pro وM2 الجديدة. ونتيجة لذلك، قمنا بتحويل تركيزنا من استبدال Intels بأجهزة M1 Mac إلى استبدالها بشرائح M1 Pro وM2.
تسبب البرنامج غير الجاهز للتبديل في إحباط المطورين
واجه المهندسون الأوائل الذين حصلوا على أجهزة M1 Macbook الجديدة وقتًا عصيبًا لأن معظم البرامج لم تكن جاهزة للتبديل إلى بنية iOS ARM الجديدة من Apple. وكانت أدوات الطرف الثالث مثل Rubygems وCocoapods، وهي أدوات إدارة التبعية التي تعتمد على العديد من Rubygems الأخرى، هي الأكثر تأثراً. لم يتم تجميع بعض هذه الأدوات لبنية iOS ARM في ذلك الوقت، لذلك كان لا بد من تشغيل معظم البرامج باستخدام Rosetta، مما تسبب في مشكلات في الأداء والإحباط.
ومع ذلك، عمل منشئو البرامج على حل معظم هذه المشكلات عند ظهورها. وجاءت لحظة الاختراق مع إصدار Xcode 14.3، الذي لم يعد يحظى بدعم Rosetta. كانت هذه إشارة واضحة لجميع مطوري البرامج بأن شركة Apple كانت تضغط من أجل ترحيل بنية Intel إلى iOS ARM. أجبر هذا معظم مطوري برامج الطرف الثالث الذين اعتمدوا سابقًا على Rosetta على ترحيل برامجهم إلى ARM. في الوقت الحاضر، 99% من برامج الطرف الثالث المستخدمة في Miquido بشكل يومي تعمل بدون Rosetta.
2. استبدال نظام Miquido CI لنظام iOS
أثبتت عملية استبدال نظام iOS للتكامل المستمر في Miquido أنها مهمة أكثر تعقيدًا من مجرد تبديل الأجهزة. أولاً، يرجى إلقاء نظرة على بنيتنا التحتية في ذلك الوقت:
كان لدينا مثيل سحابي لـ Gitlab و9 أجهزة Mac Minis مستندة إلى Intel متصلة به. كانت هذه الآلات بمثابة مشغلي المهام وكان Gitlab مسؤولاً عن التنسيق. كلما تم وضع مهمة CI في قائمة الانتظار، قام Gitlab بتعيينها لأول مشغل متاح يلبي متطلبات المشروع المحددة في ملف gitlab-ci.yml. سيقوم Gitlab بإنشاء برنامج نصي وظيفي يحتوي على جميع أوامر البناء والمتغيرات والمسارات وما إلى ذلك. ثم يتم نقل هذا البرنامج النصي إلى العداء وتنفيذه على هذا الجهاز.
على الرغم من أن هذا الإعداد قد يبدو قويًا، فقد واجهنا مشكلات في المحاكاة الافتراضية بسبب ضعف دعم معالجات Intel. ونتيجة لذلك، قررنا عدم استخدام المحاكاة الافتراضية مثل Docker وتنفيذ المهام على الأجهزة الفعلية نفسها. لقد حاولنا إعداد حل فعال وموثوق يعتمد على Docker، ولكن قيود المحاكاة الافتراضية، مثل عدم وجود تسريع GPU، أدت إلى استغراق المهام وقتًا أطول مرتين للتنفيذ مقارنة بالأجهزة المادية. وقد أدى ذلك إلى زيادة النفقات العامة وملء قوائم الانتظار بسرعة.
نظرًا لاتفاقية مستوى الخدمة لنظام التشغيل macOS، لم نتمكن إلا من إعداد جهازين افتراضيين في وقت واحد. لذلك، قررنا توسيع مجموعة المتسابقين الفعليين وإعدادهم لتنفيذ مهام Gitlab مباشرة على نظام التشغيل الخاص بهم. ومع ذلك، كان لهذا النهج أيضا بعض العيوب.
التحديات في عملية البناء وإدارة العداء
- لا يوجد عزل للبنيات خارج وضع الحماية لدليل البناء.
ينفذ العداء كل بناء على جهاز فعلي، مما يعني أن البنيات ليست معزولة عن صندوق حماية دليل البناء. وهذا له مزاياه وعيوبه. من ناحية، يمكننا استخدام ذاكرة التخزين المؤقت للنظام لتسريع عمليات البناء نظرًا لأن معظم المشاريع تستخدم نفس مجموعة تبعيات الطرف الثالث.
من ناحية أخرى، تصبح ذاكرة التخزين المؤقت غير قابلة للصيانة نظرًا لأن بقايا مشروع واحد يمكن أن تؤثر على كل مشروع آخر. يعد هذا مهمًا بشكل خاص لذاكرة التخزين المؤقت على مستوى النظام، حيث يتم استخدام نفس البرامج المتسابقة لتطوير كل من Flutter وReact Native. يتطلب React Native، على وجه الخصوص، العديد من التبعيات المخزنة مؤقتًا من خلال NPM.
- الفوضى المحتملة لأداة النظام.
على الرغم من عدم تنفيذ أي من الوظيفتين بامتيازات Sudo، إلا أنه لا يزال من الممكن لهم الوصول إلى بعض أدوات النظام أو المستخدم، مثل Ruby. ويشكل هذا تهديدًا محتملاً باختراق بعض هذه الأدوات، خاصة وأن نظام التشغيل macOS يستخدم لغة Ruby لبعض برامجه القديمة، بما في ذلك بعض ميزات Xcode القديمة. إصدار النظام من Ruby ليس شيئًا قد ترغب في العبث به.
ومع ذلك، فإن تقديم rbenv يخلق طبقة أخرى من التعقيد للتعامل معها. من المهم ملاحظة أنه يتم تثبيت Rubygems لكل إصدار Ruby، وبعض هذه الأحجار الكريمة تتطلب إصدارات معينة من Ruby. كانت جميع أدوات الطرف الثالث التي كنا نستخدمها تقريبًا تعتمد على روبي، مع كون Cocoapods وFastlane الجهات الفاعلة الرئيسية.
- إدارة هويات التوقيع.
يمكن أن تشكل إدارة هويات التوقيع المتعددة من حسابات تطوير العملاء المختلفة تحديًا عندما يتعلق الأمر بسلاسل مفاتيح النظام الموجودة على المتسابقين. تعد هوية التوقيع جزءًا من البيانات حساسة للغاية لأنها تمكننا من تصميم التطبيق، مما يجعله عرضة للتهديدات المحتملة.
لضمان الأمان، يجب وضع الحماية للهويات عبر المشاريع وحمايتها. ومع ذلك، يمكن أن تصبح هذه العملية كابوسًا نظرًا للتعقيد الإضافي الذي يقدمه نظام التشغيل macOS في تنفيذ سلسلة المفاتيح الخاصة به.
- التحديات في بيئات متعددة المشاريع.
لم يتم إنشاء جميع المشاريع باستخدام نفس الأدوات، وخاصة Xcode. تمت صيانة بعض المشاريع، خاصة تلك التي في مرحلة الدعم، باستخدام الإصدار الأخير من Xcode الذي تم تطوير المشروع به. وهذا يعني أنه إذا كان هناك أي عمل مطلوب في تلك المشاريع، فيجب أن تكون CI قادرة على بنائها. ونتيجة لذلك، كان على المتسابقين دعم إصدارات متعددة من Xcode في نفس الوقت، مما أدى إلى تقليص عدد المتسابقين المتاحين لوظيفة معينة.
5. مطلوب جهد إضافي.
يجب إجراء أي تغييرات يتم إجراؤها عبر المتسابقين، مثل تثبيت البرنامج، على جميع المتسابقين في وقت واحد. على الرغم من أنه كان لدينا أداة أتمتة لهذا الغرض، فقد تطلب الأمر جهدًا إضافيًا للحفاظ على البرامج النصية للأتمتة.
حلول البنية التحتية المخصصة لاحتياجات العملاء المتنوعة
Miquido هو بيت برمجيات يعمل مع العديد من العملاء ذوي الاحتياجات المختلفة. نقوم بتخصيص خدماتنا لتلبية المتطلبات المحددة لكل عميل. غالبًا ما نستضيف قاعدة التعليمات البرمجية والبنية التحتية اللازمة للشركات الصغيرة أو الشركات الناشئة نظرًا لأنها قد تفتقر إلى الموارد أو المعرفة اللازمة للحفاظ عليها.
عادةً ما يكون لدى عملاء المؤسسات البنية التحتية الخاصة بهم لاستضافة مشاريعهم. ومع ذلك، لا يملك البعض القدرة على القيام بذلك أو أنهم ملزمون بموجب لوائح الصناعة باستخدام البنية التحتية الخاصة بهم. كما أنهم يفضلون عدم استخدام أي خدمات SaaS تابعة لجهات خارجية مثل Xcode Cloud أو Codemagic. وبدلاً من ذلك، يريدون حلاً يناسب بنيتهم الحالية.
لاستيعاب هؤلاء العملاء، غالبًا ما نستضيف المشاريع على بنيتنا التحتية أو نقوم بإعداد نفس تكوين iOS للتكامل المستمر على البنية التحتية الخاصة بهم. ومع ذلك، فإننا نولي المزيد من الحذر عند التعامل مع المعلومات والملفات الحساسة، مثل هويات التوقيع.
الاستفادة من Fastlane لإدارة البناء بكفاءة
هنا يأتي Fastlane كأداة سهلة الاستخدام. وهو يتألف من وحدات مختلفة تسمى الإجراءات التي تساعد على تبسيط العملية وفصلها بين العملاء المختلفين. يساعد أحد هذه الإجراءات، والذي يسمى المطابقة، في الحفاظ على هويات توقيع التطوير والإنتاج، بالإضافة إلى توفير ملفات التعريف. كما أنه يعمل على مستوى نظام التشغيل لفصل تلك الهويات في سلاسل مفاتيح منفصلة طوال وقت الإنشاء وإجراء عملية تنظيف بعد الإنشاء، وهو أمر مفيد للغاية لأننا ندير جميع تصميماتنا على أجهزة فعلية.
لقد لجأنا في البداية إلى Fastlane لسبب محدد، لكننا اكتشفنا أنه يحتوي على ميزات إضافية قد تكون مفيدة لنا.
- بناء التحميل إلى Testflight
في الماضي، لم تكن واجهة برمجة تطبيقات AppStoreConnect متاحة للعامة للمطورين. وهذا يعني أن الطريقة الوحيدة لتحميل الإصدار إلى Testflight كانت من خلال Xcode أو باستخدام Fastlane. كانت Fastlane عبارة عن أداة قامت بشكل أساسي بإلغاء ASC API وتحويلها إلى إجراء يسمى الطيار . ومع ذلك، غالبًا ما تنقطع هذه الطريقة مع تحديث Xcode التالي. إذا أراد أحد المطورين تحميل نسخته إلى Testflight باستخدام سطر الأوامر، فإن Fastlane هو أفضل خيار متاح.
- سهولة التبديل بين إصدارات Xcode
بوجود أكثر من مثيل Xcode على جهاز واحد، كان من الضروري تحديد Xcode الذي سيتم استخدامه للإنشاء. لسوء الحظ، جعلت Apple من غير المناسب التبديل بين إصدارات Xcode، حيث يتعين عليك استخدام "xcode-select" للقيام بذلك، الأمر الذي يتطلب أيضًا امتيازات Sudo. يغطي Fastlane ذلك أيضًا.
- أدوات مساعدة إضافية للمطورين
يوفر Fastlane العديد من الأدوات المساعدة المفيدة الأخرى بما في ذلك الإصدار والقدرة على إرسال نتائج البناء إلى خطافات الويب.
عيوب الفاست لين
كان تكييف Fastlane مع مشاريعنا أمرًا سليمًا ومتينًا، لذلك ذهبنا في هذا الاتجاه. لقد استخدمناها بنجاح لعدة سنوات. ومع ذلك، خلال هذه السنوات، حددنا بعض المشاكل:
- يتطلب Fastlane معرفة روبي.
Fastlane هي أداة مكتوبة بلغة Ruby، وتتطلب معرفة جيدة بلغة Ruby لاستخدامها بفعالية. عندما تكون هناك أخطاء في تكوين Fastlane الخاص بك أو في الأداة نفسها، فإن تصحيح الأخطاء باستخدام irb أو pry يمكن أن يكون أمرًا صعبًا للغاية.
- الاعتماد على العديد من الأحجار الكريمة.
يعتمد Fastlane نفسه على ما يقرب من 70 جوهرة. للتخفيف من مخاطر تفكيك نظام روبي، كانت المشاريع تستخدم جواهر المجمعات المحلية. أدى جلب كل هذه الأحجار الكريمة إلى خلق الكثير من الوقت.
- مشاكل نظام روبي و Rubygems.
ونتيجة لذلك، فإن جميع المشكلات المتعلقة بنظام Ruby و Rubygems المذكورة سابقًا تنطبق هنا أيضًا.
- التكرار لمشاريع الرفرفة.
اضطرت مشاريع Flutter أيضًا إلى استخدام مطابقة Fastlane فقط للحفاظ على التوافق مع مشاريع iOS وحماية سلاسل مفاتيح العداء. كان ذلك غير ضروري على الإطلاق، حيث أن Flutter لديها نظام بناء خاص بها، وتم تقديم النفقات العامة المذكورة سابقًا فقط لإدارة هويات التوقيع وتوفير الملفات الشخصية.
تم إصلاح معظم هذه المشكلات على طول الطريق، ولكننا كنا بحاجة إلى حل أكثر قوة وموثوقية.
الفكرة: تكييف أدوات تكامل مستمر جديدة وأكثر قوة لنظام iOS
والخبر السار هو أن Apple اكتسبت سيطرة كاملة على بنية الرقائق الخاصة بها وطورت إطار عمل افتراضيًا جديدًا لنظام التشغيل macOS. يسمح إطار العمل هذا للمستخدمين بإنشاء وتكوين وتشغيل الأجهزة الافتراضية Linux أو macOS التي تبدأ بسرعة وتتميز بأداء يشبه الأجهزة الأصلية - وأعني حقًا الأجهزة الأصلية.
بدا ذلك واعدًا ويمكن أن يكون حجر الزاوية لأدوات التكامل المستمر الجديدة لنظام التشغيل iOS. ومع ذلك، كان مجرد شريحة من الحل الكامل. نظرًا لوجود أداة لإدارة الأجهزة الافتراضية، فقد كنا بحاجة أيضًا إلى شيء يمكنه استخدام إطار العمل هذا بالتنسيق مع مشغلي Gitlab.
بعد ذلك، فإن معظم مشاكلنا المتعلقة بأداء المحاكاة الافتراضية الضعيفة ستصبح قديمة. وسيتيح لنا أيضًا حل معظم المشكلات التي كنا نعتزم حلها باستخدام Fastlane تلقائيًا.
تطوير حل مخصص لإدارة هوية التوقيع عند الطلب
كانت لدينا مشكلة أخيرة يجب حلها، وهي إدارة هوية التوقيع. لم نرغب في استخدام Fastlane لهذا الغرض لأنه بدا زائدًا عن احتياجاتنا. وبدلاً من ذلك، كنا نبحث عن حل أكثر ملاءمة لمتطلباتنا. كانت احتياجاتنا واضحة: يجب أن تتم عملية إدارة الهوية عند الطلب، وحصريًا خلال وقت الإنشاء، دون أي هويات مثبتة مسبقًا على سلسلة المفاتيح، وأن تكون متوافقة مع أي جهاز يمكن تشغيله عليه.
أصبحت مشكلة التوزيع والافتقار إلى واجهة برمجة التطبيقات AppstoreConnect API المستقرة قديمة عندما أصدرت شركة Apple "الأداة البديلة" الخاصة بها، والتي سمحت بالاتصال بين المستخدمين وASC.
لذلك كانت لدينا فكرة وكان علينا إيجاد طريقة لربط هذه الجوانب الثلاثة معًا:
- العثور على طريقة للاستفادة من إطار عمل Apple الافتراضي.
- جعلها تعمل مع عدائي Gitlab.
- العثور على حل لتوقيع إدارة الهوية عبر العديد من المشاريع والمتسابقين.
الحل: لمحة عن نهجنا (الأدوات متضمنة)
بدأنا بالبحث عن حلول لمعالجة كافة المشاكل المذكورة سابقاً.
- الاستفادة من إطار عمل المحاكاة الافتراضية لشركة Apple.
بالنسبة للعقبة الأولى، وجدنا حلًا سريعًا جدًا: لقد عثرنا على أداة تورتة Cirrus Labs. منذ اللحظة الأولى، عرفنا أن هذا سيكون اختيارنا.
أهم مزايا استخدام أداة التارت التي تقدمها Cirrus Lab هي:
- إمكانية إنشاء vms من الصور الخام .ipsw.
- إمكانية إنشاء أجهزة افتراضية باستخدام قوالب معبأة مسبقًا (مع تثبيت بعض الأدوات المساعدة، مثل Brew أو Xcode)، المتوفرة على صفحة Cirrus Labs GitHub.
- تستخدم أداة Tart أداة التعبئة لدعم بناء الصور الديناميكية.
- تدعم أداة Tart صور Linux وMacOS.
- تستخدم الأداة ميزة رائعة لنظام الملفات APFS والتي تتيح تكرار الملفات دون حجز مساحة القرص لها فعليًا. بهذه الطريقة، لن تحتاج إلى تخصيص مساحة على القرص لثلاثة أضعاف حجم الصورة الأصلية. تحتاج فقط إلى مساحة قرص كافية للصورة الأصلية، بينما تشغل النسخة المستنسخة فقط المساحة التي تمثل فرقًا بينها وبين الصورة الأصلية. يعد هذا مفيدًا بشكل لا يصدق، خاصة وأن صور macOS تميل إلى أن تكون كبيرة جدًا.
على سبيل المثال، تتطلب صورة macOS Ventura التشغيلية مع تثبيت Xcode وأدوات مساعدة أخرى ما لا يقل عن 60 جيجابايت من مساحة القرص. في الظروف العادية، قد تشغل الصورة ونسختان منها ما يصل إلى 180 جيجابايت من مساحة القرص، وهي كمية كبيرة. وهذه مجرد البداية، حيث قد ترغب في الحصول على أكثر من صورة أصلية واحدة أو تثبيت إصدارات Xcode متعددة على جهاز افتراضي واحد، مما سيؤدي إلى زيادة الحجم.
- تسمح الأداة بإدارة عنوان IP للأجهزة الافتراضية الأصلية والمستنسخة، مما يسمح بوصول SSH إلى الأجهزة الافتراضية.
- القدرة على تحميل الدلائل بشكل متقاطع بين الجهاز المضيف وأجهزة VM.
- الأداة سهلة الاستخدام، وتحتوي على واجهة سطر الأوامر (CLI) بسيطة جدًا.
لا يكاد يوجد أي شيء تفتقر إليه هذه الأداة فيما يتعلق باستخدامها لإدارة الأجهزة الافتراضية. لا يكاد يكون هناك أي شيء، باستثناء شيء واحد: على الرغم من كونه واعدًا، إلا أن المكون الإضافي للحزم لإنشاء الصور أثناء التنقل كان يستهلك الكثير من الوقت، لذلك قررنا عدم استخدامه.
لقد جربنا التارت، وقد نجح بشكل رائع. كان أداؤها مشابهًا للأداء الأصلي، وكانت إدارتها سهلة.
بعد أن نجحنا في دمج التارت مع نتائج مبهرة، ركزنا بعد ذلك على معالجة التحديات الأخرى.
- العثور على طريقة للجمع بين التورتة وعداءات Gitlab.
بعد حل المشكلة الأولى، واجهنا سؤالًا حول كيفية دمج التورتة مع متسابقي Gitlab.
لنبدأ بوصف ما يفعله متسابقو Gitlab فعليًا:
كنا بحاجة إلى تضمين لغز إضافي في الرسم التخطيطي، والذي يتضمن تخصيص المهام من المضيف العداء إلى الجهاز الافتراضي. وظيفة GitLab عبارة عن برنامج نصي shell يحتوي على المتغيرات المهمة وإدخالات PATH والأوامر.
كان هدفنا هو نقل هذا البرنامج النصي إلى الجهاز الافتراضي وتشغيله.
ومع ذلك، فقد ثبت أن هذه المهمة أكثر صعوبة مما كنا نعتقد في البداية.
العداء
تتميز برامج تشغيل Gitlab القياسية مثل Docker أو SSH بسهولة الإعداد ولا تتطلب سوى القليل من التكوين أو لا تتطلب أي تكوين على الإطلاق. ومع ذلك، كنا بحاجة إلى تحكم أكبر في التكوين، مما دفعنا إلى استكشاف المنفذين المخصصين المقدمين من GitLab.
يعد المنفذون المخصصون خيارًا رائعًا للتكوينات غير القياسية حيث يتم وصف كل خطوة من خطوات التشغيل (الإعداد والتنفيذ والتنظيف) في شكل برنامج نصي shell. الشيء الوحيد المفقود هو أداة سطر الأوامر التي يمكنها تنفيذ المهام التي نحتاجها وتنفيذها في البرامج النصية لتكوين التشغيل.
حاليًا، هناك عدد من الأدوات المتاحة التي تفعل ذلك بالضبط - على سبيل المثال، CirrusLabs Gitlab tart executor. هذه الأداة هي بالضبط ما كنا نبحث عنه في ذلك الوقت. إلا أنها لم تكن موجودة بعد، وبعد إجراء الأبحاث، لم نجد أي أداة يمكن أن تساعدنا في إنجاز مهمتنا.
كتابة الحل الخاص
وبما أننا لم نتمكن من إيجاد حل مثالي، فقد كتبنا حلاً بأنفسنا. نحن مهندسون، بعد كل شيء! بدت الفكرة صلبة، وكان لدينا كل الأدوات اللازمة لذلك، فواصلنا التطوير.
لقد اخترنا استخدام Swift واثنين من المكتبات مفتوحة المصدر التي توفرها Apple: Swift Argument Parser للتعامل مع تنفيذ سطر الأوامر وSwift NIO للتعامل مع اتصال SSH مع الأجهزة الافتراضية. بدأنا التطوير، وفي غضون يومين، حصلنا على أول نموذج أولي عملي للأداة التي تطورت في النهاية إلى MQVMRunner.
على مستوى عال، تعمل الأداة على النحو التالي:
- (الخطوة التحضيرية)
- اقرأ المتغيرات المتوفرة في gitlab-ci.yml (اسم الصورة والمتغيرات الإضافية).
- اختر قاعدة VM المطلوبة
- استنساخ قاعدة VM المطلوبة.
- قم بإعداد دليل مشترك وانسخ البرنامج النصي لمهمة Gitlab إليه، مع تحديد الأذونات اللازمة له.
- قم بتشغيل النسخ وتحقق من اتصال SSH.
- قم بإعداد أي تبعيات مطلوبة (مثل إصدار Xcode)، إذا لزم الأمر.
- (تنفيذ الخطوة)
- قم بتشغيل مهمة Gitlab لتنفيذ برنامج نصي من دليل مثبت على نسخة VM مُجهزة من خلال SSH.
- (خطوة التنظيف)
- حذف الصورة المستنسخة.
التحديات في التنمية
أثناء عملية التطوير، واجهنا العديد من المشكلات، مما جعل الأمر لا يسير بسلاسة كما كنا نرغب.
- إدارة عنوان IP.
تعد إدارة عناوين IP مهمة بالغة الأهمية يجب التعامل معها بعناية. في النموذج الأولي، تم تنفيذ معالجة SSH باستخدام أوامر SSH Shell المباشرة والمشفرة. ومع ذلك، في حالة الصدفات غير التفاعلية، يوصى بالمصادقة الرئيسية. بالإضافة إلى ذلك، يُنصح بإضافة المضيف إلى ملفknown_hosts لتجنب الانقطاعات. ومع ذلك، نظرًا للإدارة الديناميكية لعناوين IP الخاصة بالأجهزة الافتراضية، هناك إمكانية لمضاعفة الإدخال لعنوان IP معين، مما يؤدي إلى حدوث أخطاء. ولذلك، نحتاج إلى تعيينknown_hosts ديناميكيًا لمهمة معينة لمنع مثل هذه المشكلات.
- حل سويفت النقي.
بالنظر إلى ذلك، وحقيقة أن أوامر shell المشفرة في كود Swift ليست أنيقة حقًا، فقد اعتقدنا أنه سيكون من الجيد استخدام مكتبة Swift مخصصة وقررنا استخدام Swift NIO. لقد قمنا بحل بعض المشكلات ولكن في الوقت نفسه، قدمنا بعض المشكلات الجديدة مثل - على سبيل المثال - في بعض الأحيان يتم نقل السجلات الموضوعة على stdout *بعد* إنهاء قناة SSH نظرًا لانتهاء تنفيذ الأمر - وكما كنا نعتمد على هذا الناتج في العمل الإضافي، كان التنفيذ يفشل بشكل عشوائي.
- اختيار إصدار Xcode.
نظرًا لأن المكون الإضافي Packer لم يكن خيارًا لبناء الصور الديناميكية بسبب استهلاك الوقت، فقد قررنا استخدام قاعدة VM واحدة مع إصدارات Xcode متعددة مثبتة مسبقًا. كان علينا أن نجد طريقة للمطورين لتحديد إصدار Xcode الذي يحتاجونه في gitlab-ci.yml الخاص بهم - وقد توصلنا إلى متغيرات مخصصة متاحة للاستخدام في أي مشروع. سيقوم MQVMRunner بعد ذلك بتنفيذ "xcode-select" على جهاز افتراضي مستنسخ لإعداد إصدار Xcode المقابل.
المزيد المزيد أيضا
تبسيط ترحيل المشروع والتكامل المستمر لسير عمل iOS مع Mac Studios
لقد قمنا بإعداد ذلك على اثنين من استوديوهات Mac الجديدة وبدأنا في ترحيل المشاريع. أردنا أن نجعل عملية الترحيل للمطورين لدينا شفافة قدر الإمكان. لم نتمكن من جعل الأمر سلسًا تمامًا، ولكن في النهاية، وصلنا إلى النقطة حيث كان عليهم القيام ببعض الأشياء فقط في gitlab-ci.yml:
- علامات المتسابقين: لاستخدام Mac Studios بدلاً من Intels.
- اسم الصورة: معلمة اختيارية، تم تقديمها للتوافق المستقبلي في حالة حاجتنا إلى أكثر من جهاز افتراضي أساسي. في الوقت الحالي، يتم تعيينه افتراضيًا دائمًا على جهاز VM الأساسي الوحيد الذي لدينا.
- إصدار Xcode: معلمة اختيارية؛ إذا لم يتم توفيره، سيتم استخدام الإصدار الأحدث المتاح.
حصلت الأداة على ردود فعل أولية جيدة جدًا، لذلك قررنا أن نجعلها مفتوحة المصدر. لقد أضفنا برنامج نصي للتثبيت لإعداد Gitlab Custom Runner وجميع الإجراءات والمتغيرات المطلوبة. باستخدام أداتنا، يمكنك إعداد مشغل GitLab الخاص بك في غضون دقائق - الشيء الوحيد الذي تحتاجه هو جهاز VM لاذع وقاعدي سيتم تنفيذ المهام عليه.
يبدو التكامل المستمر النهائي لبنية iOS كما يلي:
3. الحل لإدارة الهوية بكفاءة
لقد كنا نكافح من أجل إيجاد حل فعال لإدارة هويات التوقيع الخاصة بعملائنا. كان هذا أمرًا صعبًا بشكل خاص، نظرًا لأن هوية التوقيع عبارة عن بيانات سرية للغاية ولا ينبغي تخزينها في مكان غير آمن لفترة أطول من اللازم.
بالإضافة إلى ذلك، أردنا تحميل هذه الهويات فقط أثناء وقت الإنشاء، دون أي حلول مشتركة بين المشاريع. وهذا يعني أنه لا ينبغي الوصول إلى الهوية خارج وضع الحماية الخاص بالتطبيق (أو الإصدار). لقد تناولنا بالفعل المشكلة الأخيرة من خلال الانتقال إلى الأجهزة الافتراضية. ومع ذلك، ما زلنا بحاجة إلى إيجاد طريقة لتخزين هوية التوقيع وتحميلها في الجهاز الافتراضي خلال وقت الإنشاء فقط.
مشاكل مع Fastlane Match
في ذلك الوقت، كنا لا نزال نستخدم مطابقة Fastlane، التي تخزن الهويات والأحكام المشفرة في مستودع منفصل، وتقوم بتحميلها أثناء عملية الإنشاء في مثيل سلسلة مفاتيح منفصل، وتزيل هذا المثيل بعد الإنشاء.
يبدو هذا النهج مناسبًا، لكن به بعض المشاكل:
- يتطلب إعداد Fastlane بالكامل للعمل.
Fastlane هو Rubygem، وجميع المشكلات المذكورة في الفصل الأول تنطبق هنا.
- الخروج من المستودع في وقت البناء.
لقد احتفظنا بهوياتنا في مستودع منفصل تم سحبه أثناء عملية الإنشاء بدلاً من عملية الإعداد. وهذا يعني أنه كان علينا إنشاء وصول منفصل إلى مستودع الهوية، ليس فقط لـ Gitlab، ولكن أيضًا للمشغلين المحددين، على غرار الطريقة التي نتعامل بها مع تبعيات الطرف الثالث الخاصة.
- من الصعب إدارتها خارج المباراة.
إذا كنت تستخدم Match لإدارة الهويات أو التزويد، فليست هناك حاجة إلى التدخل اليدوي. يعد تحرير الملفات الشخصية وفك تشفيرها وتشفيرها يدويًا حتى تتمكن المطابقات من العمل معها لاحقًا أمرًا شاقًا ويستغرق وقتًا طويلاً. عادةً ما يؤدي استخدام Fastlane لتنفيذ هذه العملية إلى مسح إعداد توفير التطبيق بالكامل وإنشاء إعداد جديد.
- من الصعب تصحيح الأخطاء.
في حالة وجود أي مشكلة في توقيع التعليمات البرمجية، قد تجد صعوبة في تحديد مطابقة الهوية والتوفير التي تم تثبيتها للتو، حيث ستحتاج إلى فك تشفيرها أولاً.
- مخاوف أمنية.
قم بمطابقة حسابات المطورين التي تم الوصول إليها باستخدام بيانات الاعتماد المقدمة لإجراء تغييرات نيابةً عنهم. على الرغم من كون Fastlane مفتوح المصدر، فقد رفضه بعض العملاء بسبب مخاوف أمنية.
- وأخيرًا وليس آخرًا، فإن التخلص من Match من شأنه أن يزيل أكبر عقبة في طريقنا للتخلص من Fastlane تمامًا.
وكانت متطلباتنا الأولية كما يلي:
- يتطلب التحميل توقيع الهوية من مكان آمن، ويفضل أن يكون ذلك في شكل غير نص عادي، ووضعها في سلسلة المفاتيح.
- يجب أن يكون الوصول إلى هذه الهوية ممكنًا بواسطة Xcode.
- ويفضل أن تكون كلمة مرور الهوية واسم سلسلة المفاتيح ومتغيرات كلمة مرور سلسلة المفاتيح قابلة للتعيين لأغراض تصحيح الأخطاء.
كان لدى Match كل ما نحتاجه، لكن تنفيذ Fastlane فقط لاستخدام Match بدا وكأنه مبالغة، خاصة بالنسبة للحلول عبر الأنظمة الأساسية مع نظام البناء الخاص بها. أردنا شيئًا مشابهًا لـ Match، ولكن بدون العبء الثقيل الذي تحمله روبي.
خلق الحل الخاص
لذلك فكرنا – دعونا نكتب ذلك بأنفسنا! لقد فعلنا ذلك باستخدام MQVMRunner، لذا يمكننا القيام بذلك هنا أيضًا. لقد اخترنا أيضًا Swift للقيام بذلك، ويرجع ذلك أساسًا إلى أنه يمكننا الحصول على الكثير من واجهات برمجة التطبيقات الضرورية مجانًا باستخدام إطار عمل Apple Security.
وبطبيعة الحال، لم تسر الأمور بسلاسة كما كان متوقعا.
- الإطار الأمني موجود.
كانت أسهل استراتيجية هي استدعاء أوامر bash كما يفعل Fastlane. ومع ذلك، مع توفر إطار الأمان، اعتقدنا أنه سيكون أكثر أناقة للاستخدام في التطوير.
- قلة الخبرة.
لم تكن لدينا خبرة كبيرة في التعامل مع إطار عمل الأمان لنظام التشغيل macOS، وتبين أنه يختلف بشكل كبير عما اعتدنا عليه في نظام التشغيل iOS. لقد أدى هذا إلى نتائج عكسية علينا في العديد من الحالات حيث لم نكن على دراية بقيود نظام التشغيل macOS أو افترضنا أنه يعمل بنفس الطريقة التي يعمل بها نظام التشغيل iOS - وكانت معظم هذه الافتراضات خاطئة.
- وثائق رهيبة.
إن وثائق إطار عمل Apple Security، بعبارة ملطفة، متواضعة. إنها واجهة برمجة تطبيقات قديمة جدًا يعود تاريخها إلى الإصدارات الأولى من OSX، وفي بعض الأحيان، كان لدينا انطباع بأنه لم يتم تحديثها منذ ذلك الحين. لم يتم توثيق جزء كبير من التعليمات البرمجية، ولكننا توقعنا كيفية عملها من خلال قراءة التعليمات البرمجية المصدر. ولحسن الحظ بالنسبة لنا، فهو مفتوح المصدر.
- الإهلاكات دون الاستبدال.
تم إهمال جزء كبير من هذا الإطار؛ تحاول شركة Apple الابتعاد عن سلسلة المفاتيح النموذجية "نمط macOS" (سلاسل مفاتيح متعددة يمكن الوصول إليها عن طريق كلمة المرور) وتنفيذ سلسلة مفاتيح "نمط iOS" (سلسلة مفاتيح واحدة، متزامنة عبر iCloud). لذا فقد قاموا بإهمالها في نظام التشغيل macOS Yosemite في عام 2014، لكنهم لم يتوصلوا إلى أي بديل لها في السنوات التسع الماضية - لذلك، تم إهمال واجهة برمجة التطبيقات الوحيدة المتاحة لنا، في الوقت الحالي، لأنه لا توجد واجهة جديدة حتى الآن.
لقد افترضنا أنه يمكن تخزين هويات التوقيع كسلاسل مشفرة باستخدام Base64 في متغيرات Gitlab لكل مشروع. إنه آمن، ويعتمد على كل مشروع، وإذا تم تعيينه كمتغير مقنع، فيمكن قراءته وعرضه في سجلات الإنشاء كنص غير عادي.
لذلك، كان لدينا بيانات الهوية. نحن بحاجة فقط لوضعه في سلسلة المفاتيح. استخدام واجهة برمجة تطبيقات الأمان بعد عدد قليل من المحاولات والجهد الحثيث في مراجعة وثائق إطار عمل الأمان، قمنا بإعداد نموذج أولي لشيء أصبح فيما بعد MQSwiftSign.
تعلم نظام macOS Security، ولكن بالطريقة الصعبة
كان علينا الحصول على فهم عميق لكيفية عمل سلسلة مفاتيح macOS لتطوير أداتنا. يتضمن ذلك البحث في كيفية إدارة سلسلة المفاتيح للعناصر، والوصول إليها والأذونات، وبنية بيانات سلسلة المفاتيح. على سبيل المثال، اكتشفنا أن سلسلة المفاتيح هي ملف macOS الوحيد الذي يتجاهل نظام التشغيل مجموعة ACL. بالإضافة إلى ذلك، علمنا أن قائمة التحكم بالوصول (ACL) الموجودة على عناصر محددة من سلسلة المفاتيح عبارة عن قائمة نصية عادية محفوظة في ملف سلسلة المفاتيح. لقد واجهنا العديد من التحديات على طول الطريق، ولكننا تعلمنا الكثير أيضًا.
كان أحد التحديات الكبيرة التي واجهناها هو المطالبات. تم تصميم أداتنا في المقام الأول لتعمل على أنظمة CI iOS، مما يعني أنها يجب أن تكون غير تفاعلية. لم نتمكن من مطالبة المستخدمين بتأكيد كلمة المرور على CI.
ومع ذلك، فإن نظام أمان macOS مصمم جيدًا، مما يجعل من المستحيل تحرير أو قراءة المعلومات السرية، بما في ذلك هوية التوقيع، دون إذن صريح من المستخدم. للوصول إلى أحد الموارد دون تأكيد، يجب تضمين برنامج الوصول في قائمة التحكم في الوصول الخاصة بالمورد. وهذا مطلب صارم لا يمكن لأي برنامج كسره، حتى برامج Apple التي تأتي مع النظام. إذا احتاج أي برنامج إلى قراءة إدخال سلسلة المفاتيح أو تحريره، فيجب على المستخدم توفير كلمة مرور سلسلة المفاتيح لإلغاء قفله، واختياريًا، إضافته إلى قائمة التحكم بالوصول (ACL) الخاصة بالإدخال.
التغلب على تحديات إذن المستخدم
لذلك، كان علينا إيجاد طريقة لـ Xcode للوصول إلى الهوية التي تم إعدادها بواسطة سلسلة المفاتيح الخاصة بنا دون طلب إذن من المستخدم باستخدام المطالبة بكلمة المرور. للقيام بذلك، يمكننا تغيير قائمة التحكم في الوصول لعنصر ما، ولكن هذا يتطلب أيضًا إذن المستخدم - وهو ما يتطلبه الأمر بالطبع. خلاف ذلك، فإنه من شأنه أن يقوض الهدف برمته من وجود الرباط الصليبي الأمامي. لقد كنا نحاول تجاوز هذه الحماية - لقد حاولنا تحقيق نفس التأثير كما هو الحال مع الأمر "security set-key-partition-list".
بعد التعمق في وثائق إطار العمل، لم نعثر على أي واجهة برمجة تطبيقات تسمح بتحرير قائمة التحكم بالوصول (ACL) دون مطالبة المستخدم بتوفير كلمة مرور. أقرب شيء وجدناه هو `SecKeychainItemSetAccess`، والذي يقوم بتشغيل مطالبة واجهة المستخدم في كل مرة. ثم قمنا بغوص آخر، ولكن هذه المرة، في أفضل الوثائق، وهي كود المصدر نفسه. كيف نفذتها أبل؟
وتبين أنهم – كما كان متوقعًا – كانوا يستخدمون واجهة برمجة تطبيقات خاصة. هناك طريقة تسمى `SecKeychainItemSetAccessWithPassword' تقوم بنفس الشيء مثل `SecKeychainItemSetAccess`، ولكن بدلاً من مطالبة المستخدم بكلمة مرور، يتم توفير كلمة المرور كوسيطة لوظيفة. بالطبع - باعتبارها واجهة برمجة تطبيقات خاصة، فهي غير مدرجة في الوثائق، لكن Apple تفتقر إلى الوثائق الخاصة بواجهات برمجة التطبيقات هذه كما لو أنها لا تستطيع التفكير في إنشاء تطبيق للاستخدام الشخصي أو المؤسسي. وبما أن الأداة كانت مخصصة للاستخدام الداخلي فقط، فلم نتردد في استخدام واجهة برمجة التطبيقات الخاصة. الشيء الوحيد الذي كان يجب القيام به هو جسر طريقة C إلى Swift.
لذلك، كان سير العمل النهائي للنموذج الأولي كما يلي:
- قم بإنشاء سلسلة مفاتيح مؤقتة غير مقفلة مع إيقاف تشغيل القفل التلقائي.
- احصل على بيانات هوية التوقيع المشفرة Base64 وفك تشفيرها من المتغيرات البيئية (التي تم تمريرها بواسطة Gitlab).
- قم باستيراد الهوية إلى سلسلة المفاتيح التي تم إنشاؤها.
- قم بتعيين خيارات الوصول المناسبة للهوية المستوردة حتى يتمكن Xcode والأدوات الأخرى من قراءتها لتصميم التعليمات البرمجية.
مزيد من الترقيات
كان النموذج الأولي يعمل بشكل جيد، لذلك حددنا بعض الميزات الإضافية التي نود إضافتها إلى الأداة. كان هدفنا هو استبدال الخط السريع في نهاية المطاف؛ لقد قمنا بالفعل بتنفيذ إجراء "المطابقة". ومع ذلك، لا يزال Fastlane يقدم ميزتين قيمتين لم نتوفر عليهما بعد - توفير تثبيت ملف التعريف وإنشاء ملف Export.plist.
توفير تثبيت الملف الشخصي
يعد تثبيت ملف تعريف التوفير أمرًا بسيطًا جدًا - فهو ينقسم إلى استخراج ملف التعريف UUID ونسخ الملف إلى `~/Library/MobileDevice/Provisioning Profiles/` مع UUID كاسم ملف - وهذا يكفي لـ Xcode لرؤيته بشكل صحيح. ليس من علم الصواريخ أن نضيف إلى أداتنا مكونًا إضافيًا بسيطًا للتكرار فوق الدليل المقدم والقيام بذلك مع كل ملف .mobileprovision يتم العثور عليه بداخله.
إنشاء Export.plist
ومع ذلك، فإن إنشاء Export.plist أصعب قليلاً. لإنشاء ملف IPA مناسب، يتطلب Xcode من المستخدمين توفير ملف plist بمعلومات محددة تم جمعها من مصادر مختلفة - ملف المشروع، وقائمة الاستحقاق، وإعدادات مساحة العمل، وما إلى ذلك. السبب وراء قدرة Xcode على جمع تلك البيانات فقط من خلال معالج التوزيع وليس من خلال CLI غير معروف بالنسبة لي. ومع ذلك، كان علينا جمعها باستخدام Swift APIs، مع وجود مراجع المشروع/مساحة العمل فقط وجرعة صغيرة من المعرفة حول كيفية إنشاء ملف مشروع Xcode.
وكانت النتيجة أفضل من المتوقع، لذلك قررنا إضافتها كمكون إضافي آخر لأداتنا. لقد أصدرناه أيضًا كمشروع مفتوح المصدر لجمهور أوسع. في الوقت الحالي، تعد MQSwiftSign أداة متعددة الأغراض يمكن استخدامها بنجاح كبديل لإجراءات Fastlane الأساسية المطلوبة لبناء وتوزيع تطبيق iOS الخاص بك ونستخدمها في كل مشروع لدينا في Miquido.
الأفكار النهائية : النجاح
كان التحول من بنية Intel إلى iOS ARM مهمة صعبة. لقد واجهنا العديد من العقبات وأمضينا وقتًا طويلاً في تطوير الأدوات بسبب نقص التوثيق. ومع ذلك، فقد أنشأنا في النهاية نظامًا قويًا:
- اثنان من المتسابقين لإدارة بدلا من تسعة؛
- تشغيل برنامج يقع بالكامل تحت سيطرتنا، دون الكثير من النفقات العامة على شكل جواهر روبي - تمكنا من التخلص من Fastlane أو أي برنامج تابع لجهة خارجية في تكوينات البناء الخاصة بنا؛
- الكثير من المعرفة والفهم للأشياء التي لا ننتبه إليها عادة - مثل أمان نظام macOS وإطار الأمان نفسه، وبنية مشروع Xcode الفعلية، وغيرها الكثير.
أود أن أشجعك بكل سرور - إذا كنت تواجه صعوبة في إعداد مشغل GitLab لإصدارات iOS، فجرّب MQVMRunner الخاص بنا. إذا كنت بحاجة إلى مساعدة في إنشاء تطبيقك وتوزيعه باستخدام أداة واحدة ولا تريد الاعتماد على أحجار الياقوت، فجرّب MQSwiftSign. يعمل بالنسبة لي، قد يعمل أيضًا من أجلك!