إنتقل إلى المحتوى الرئيسي

تطوير iOS بخوادم بناء Mac البعيدة

iOS remote Mac build setup with Cursor

يتطلب بناء تطبيق iOS وجود macOS في مكان ما من الحلقة، لكن هذا لا يعني أن محررك يجب أن يعمل على Mac. مع دعم Remote-SSH في Cursor، يمكنك الحفاظ على تجربة تحرير محلية سريعة على Windows أو Linux بينما يتولى جهاز Mac الترجمة والتوقيع وتشغيل المحاكيات. يستعرض هذا الدليل خيارات البنية، والإعداد، وأوامر البناء، والمزالق التي تضيّع أكبر قدر من الوقت.

لماذا يُشترط Mac​

تعمل سلسلة أدوات Apple على macOS فقط، وتعتمد عدة أجزاء من سير عمل iOS عليها مباشرة.

  • توقيع الكود: هويات التوقيع تعيش في سلسلة مفاتيح macOS وتُستخدم لإنتاج معطيات موقّعة.
  • ملفات تعريف التزويد: الملفات التي تربط معرّف تطبيق بأجهزة وشهادات تُدار عبر أدوات Apple.
  • محاكي iOS: المحاكي تطبيق macOS؛ لا يوجد مكافئ مدعوم على Linux أو Windows بدقة جهاز حقيقي.
  • سلسلة أدوات xcodebuild: المترجم والرابط ومجموعات SDK تُشحن داخل Xcode على macOS.
  • الإرسال إلى App Store: الرفع إلى App Store Connect يتطلب أدوات Apple، عادةً عبر Transporter أو altool/notarytool على macOS.

لأن كل هذه مرتبطة بـ macOS، فالسؤال العملي ليس ما إذا كنت تحتاج Mac، بل أين يعيش ذلك الMac.

خيارات البنية​

لا توجد إجابة واحدة أفضل؛ الاختيار الصحيح يعتمد على حجم فريقك وميزانيتك وعدد مرات بنائك.

الخياركيف يعملالمزاياالعيوب
Remote-SSH إلى Macيتصل Cursor عبر SSH؛ الملفات والطرفية تعيش على Macمصدر حقيقة واحد، وصول كامل لسلسلة الأدوات، محاكي حقيقييحتاج Mac دائمًا وقابلاً للوصول
rsync / git push إلى Macحرّر محليًا، وادفع إلى Mac، وابنِ هناكيعمل دون اتصال للتحرير، نموذج ذهني بسيطانحراف المزامنة، النقل الكبير بطيء، لا LSP حي
مشغّلات macOS السحابية لـ CIادفع إلى Git، والبناء في CI على مشغّل macOS مستضافلا أجهزة تملكها، قابل لإعادة الإنتاج، جيد للإصداراتغير تفاعلي، تكلفة لكل دقيقة، بدء بارد
مزوّدو Mac السحابيوناستأجر Mac (MacStadium، AWS EC2 Mac) عبر SSHأجهزة حقيقية عند الطلب، قابل للتوسعتكلفة بالساعة، عبء التزويد والتفكيك

للتطوير اليومي، عادةً ما تكون Remote-SSH إلى Mac أفضل توازن. يناسب CI السحابي خطوط إنتاج الإصدارات. ويناسب مزوّدو Mac السحابيون الفرق التي تريد أسطولاً دون امتلاك مكاتب.

طابق الأداة مع المهمة

استخدم Remote-SSH للتطوير التفاعلي، و CI السحابي للإصدارات، ونسخ Mac السحابية للتوسع أو الرشقات القصيرة. تشغّل معظم الفرق الناضجة اثنتين منها على الأقل بدلاً من إجبار خيار واحد على فعل كل شيء.

إعداد Mac بعيد​

تُشغَّل هذه الخطوات مرة واحدة على الMac الذي سيعمل كخادم بناء. تحتاج صلاحيات مسؤول واتصال شبكة مستقر.

1. ثبّت Xcode وأدوات سطر الأوامر​

ثبّت Xcode من Mac App Store، ثم ثبّت أدوات سطر الأوامر حتى يتوفر xcodebuild والملفات الثنائية المرتبطة به:

xcode-select --install

# Point the toolchain at the full Xcode app, not the standalone CLT
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

2. اقبل الترخيص​

عند تثبيت جديد، يرفض xcodebuild العمل حتى يُقبل الترخيص:

sudo xcodebuild -license accept

3. أنشئ مستخدم بناء​

تجنّب البناء بحساب شخصي. أنشئ مستخدمًا مخصصًا لتبقى بيانات الاعتماد وسلاسل المفاتيح معزولة:

# Create a build user (adjust the short name and full name)
sudo dscl . -create /Users/builduser
sudo dscl . -create /Users/builduser UserShell /bin/zsh
sudo dscl . -create /Users/builduser RealName "Build User"
sudo dscl . -create /Users/builduser UniqueID 510
sudo dscl . -create /Users/builduser PrimaryGroupID 20
sudo dscl . -create /Users/builduser NFSHomeDirectory /Users/builduser
sudo passwd builduser

4. فعّل تسجيل الدخول البعيد​

شغّل SSH حتى يقبل الMac الاتصالات:

sudo systemsetup -setremotelogin on

5. ثبّت مفتاح SSH العام​

أضف مفتاحك العام المحلي إلى authorized_keys لمستخدم البناء حتى تتصل دون كلمة مرور:

mkdir -p /Users/builduser/.ssh
# Paste your public key (id_ed25519.pub) into authorized_keys
chmod 700 /Users/builduser/.ssh
chmod 600 /Users/builduser/.ssh/authorized_keys
chown -R builduser:staff /Users/builduser/.ssh
أحكم إغلاق حساب SSH

الMac البناء المكشوف على الإنترنت لا ينبغي أن يسمح بتسجيل الدخول بكلمة مرور لمستخدم البناء. عطّل مصادقة كلمة المرور، وقيّد عناوين المصدر المسموح بها حيث أمكن، وأبقِ المفتاح الخاص على جهازك المحلي فقط.

توصيل Cursor عبر Remote-SSH​

بمجرد أن يصبح الMac قابلاً للوصول، وصّل Cursor به. تصبح البيئة البعيدة هي حيث تعمل الإضافات والطرفيات وعمليات البناء.

1. ثبّت إضافة Remote-SSH​

افتح Cursor، وانتقل إلى عرض الإضافات (Ctrl+Shift+X)، وثبّت Remote - SSH (الإضافة المغلفة من Microsoft متاحة في سوق Cursor).

2. أضف إدخال تكوين SSH​

حرّر تكوين SSH المحلي لديك، عادةً في ~/.ssh/config، وأضف اسمًا مستعارًا للمضيف:

# ~/.ssh/config
Host mac-build
HostName 203.0.113.10
User builduser
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 30
ServerAliveCountMax 4
TCPKeepAlive yes

تقلل إعدادات الإبقاء على الاتصال الانقطاعات على الشبكات المتقلبة. لاحظ أن OpenSSH الأصلي في macOS لا يدعم mosh، لذا فالإبقاء على الاتصال هو خط دفاعك الأول.

3. اتصل​

افتح لوحة الأوامر (Ctrl/Cmd+Shift+P) وشغّل Remote-SSH: Connect to Host، ثم اختر mac-build. بدلاً من ذلك، شغّله في خطوة واحدة:

cursor --remote ssh-remote+mac-build /Users/builduser/projects/MyiOSApp

4. تُعاد النافذة تحميلها​

يُعاد تحميل Cursor إلى نافذة بعيدة. يعرض شريط الحالة اسم المضيف البعيد. تنفَّذ الآن كل الطرفيات والمهام وخوادم اللغة على الMac، بينما تبقى واجهة المستخدم والعرض محليين.

5. الإضافات تُثبَّت على البعيد​

عندما تفتح ملف Swift، يطلب منك Cursor تثبيت إضافات اللغة على البعيد. اقبل ذلك. أدوات Swift، ومهايئات التنقيح، وإضافة Swift تنتمي إلى الMac، لا إلى جهازك المحلي.

الإضافات المحلية مقابل البعيدة

الإضافات الخاصة بواجهة المستخدم فقط مثل السمات تعمل محليًا. أما الإضافات التي تنشئ عمليات، بما في ذلك خادم لغة Swift وأي مهايئ تنقيح، فتعمل على الMac البعيد. يدير Cursor هذا الانقسام نيابة عنك إذا تركته.

التحرير محليًا والبناء عن بُعد​

النمط المنتج هو: حرّر في Cursor، وشغّل البناء في الطرفية المدمجة، وهي طرفية على الMac. تعيش الملفات دائمًا على الMac، فلا توجد خطوة مزامنة قد تنحرف.

بناء أساسي​

ابنِ مخططًا لوجهة iOS العامة، مما يتجنب الحاجة إلى جهاز محدد:

xcodebuild \
-project MyApp.xcodeproj \
-scheme MyApp \
-configuration Debug \
-destination 'generic/platform=iOS' \
build

إذا استخدمت مساحة عمل بدلاً من مشروع، فبدّل -project بـ -workspace:

xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -destination 'generic/platform=iOS' build

الأرشفة للإصدار​

تُنتج الأرشفة ملف .xcarchive تحوّله خطوة التصدير إلى .ipa للتوزيع:

xcodebuild \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath build/MyApp.xcarchive \
archive

تصدير الأرشيف​

بعد الأرشفة، صدّر باستخدام plist خيارات تصدير يصف طريقة التوزيع:

xcodebuild -exportArchive \
-archivePath build/MyApp.xcarchive \
-exportPath build/export \
-exportOptionsPlist ExportOptions.plist

يبدو ExportOptions.plist لتوزيع بنمط App Store تقريبًا هكذا:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store-connect</string>
<key>teamID</key>
<string>ABCDE12345</string>
<key>signingStyle</key>
<string>automatic</string>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
فضّل الوجهات العامة في CI

البناء لـ generic/platform=iOS يتجنب ربط البناء بمعرّف جهاز أو محاكي محدد، مما يجعل الأمر نفسه يعمل على أي جهاز بناء دون تغييرات تكوين.

تشغيل الاختبارات وقراءة السجلات​

تعمل الاختبارات بالطريقة نفسها كالبناء، فقط بإجراء test ووجهة محددة.

تشغيل الاختبارات​

xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest'

جعل السجلات قابلة للقراءة​

إخراج xcodebuild الخام ضخم ويصعب مسحه. مرّره عبر مُنسّق لطيّه إلى أسطر قابلة للقراءة:

# xcbeautify: modern, fast formatter
xcodebuild test -workspace MyApp.xcworkspace -scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16' | xcbeautify

# xcpretty: older but widely available alternative
xcodebuild test -workspace MyApp.xcworkspace -scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16' | xcpretty

لملف سجل دائم مع دفق قابل للقراءة، مرّر الإخراج عبر tee:

xcodebuild build -scheme MyApp -destination 'generic/platform=iOS' \
2>&1 | tee build.log | xcbeautify

لأن الطرفية بعيدة، يهبط ملف السجل على الMac. اقرأه من النافذة البعيدة نفسها بمستكشف ملفات Cursor، أو بثّه محليًا بجلسة SSH ثانية إذا فضّلت.

التقاط لقطات شاشة المحاكي​

لأن المحاكي يعمل على الMac، فأنت تلتقطه بـ xcrun simctl عبر SSH بدلاً من أخذ لقطة محلية.

اسرد المحاكي وشغّله​

# List available devices
xcrun simctl list devices available

# Boot a specific simulator by UDID or name
xcrun simctl boot "iPhone 16"

# Open the Simulator UI if a desktop session is available
open -a Simulator

ثبّت تطبيقك وشغّله​

# Install a built .app onto the booted simulator
xcrun simctl install booted ./build/Build/Products/Debug-iphonesimulator/MyApp.app

# Launch it by bundle identifier
xcrun simctl launch booted com.example.MyApp

التقط لقطة شاشة​

# Save a screenshot to a file on the Mac
xcrun simctl io booted screenshot /Users/builduser/shots/home.png

لأن ذلك الملف على البعيد، افتحه مباشرة في نافذة Cursor البعيدة. تعمل معاينة الصورة عبر Remote-SSH، فلا تحتاج إلى نسخها إلى الأسفل.

المحاكيات بلا رأس تحتاج جلسة

يمكن لـ simctl تشغيل محاكٍ دون واجهة رسومية، لكن بعض الاختبارات المدفوعة بواجهة المستخدم وسيناريوهات اللقطات الأغنى تتوقع جلسة سطح مكتب مسجّلة الدخول. على Mac بلا رأس، أبقِ جلسة مسجّلة الدخول دائمة، أو توقّع قيودًا على ميزات واجهة المحاكي.

توقيع الكود​

التوقيع هو الجزء الأكثر احتمالاً للفشل على جهاز جديد، لذا أعدّه بعناية.

الشهادات في سلسلة المفاتيح​

تعيش هويات التوقيع في سلسلة مفاتيح مستخدم البناء. استورد ملف .p12 مُصدَّرًا من حساب مطوّر Apple لديك:

security import /path/to/distribution.p12 \
-k /Users/builduser/Library/Keychains/login.keychain-db \
-P "p12-password" \
-T /usr/bin/codesign \
-T /usr/bin/security

تمنح أعلام -T وصول codesign و security إلى الهوية المستوردة حتى لا يتعطل البناء على مطالبة وصول.

ملفات تعريف التزويد​

ضع الملفات حيث يتوقعها Xcode لمستخدم البناء:

mkdir -p /Users/builduser/Library/MobileDevice/Provisioning\ Profiles
cp ~/profiles/MyApp_AppStore.mobileprovision \
/Users/builduser/Library/MobileDevice/Provisioning\ Profiles/

التوقيع التلقائي مقابل اليدوي​

  • التوقيع التلقائي: أبسط، يتطلب أن يكون مستخدم البناء مسجّلاً الدخول إلى حساب مطوّر Apple وأن يكون لديه وصول شبكة إلى خدمات Apple. جيد لـ Mac واحد موثوق.
  • التوقيع اليدوي: أكثر قابلية لإعادة الإنتاج في CI. تحدد CODE_SIGN_STYLE=Manual وتشير إلى هوية وملف تعريف محددين، متجنبًا السلوك المعتمد على الحساب.
xcodebuild archive \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'generic/platform=iOS' \
CODE_SIGN_STYLE=Manual \
CODE_SIGN_IDENTITY="Apple Distribution: Example Corp (ABCDE12345)" \
PROVISIONING_PROFILE_SPECIFIER="MyApp AppStore Profile" \
-archivePath build/MyApp.xcarchive
افتح سلسلة المفاتيح في CI

على Mac غير مُراقَب، افتح سلسلة مفاتيح تسجيل الدخول قبل البناء حتى يستطيع codesign الوصول إلى الهوية دون مطالبة رسومية. أبقِ كلمة مرور الفتح في مخزن أسرار، لا في سكربت مُلتزَم به أبدًا.

مقارنة التكاليف​

الأسعار تتغير، لذا تعامل مع ما يلي كنطاقات تقريبية للتخطيط لا كأسعار حالية.

الخيارالتكلفة الشهرية التقريبيةملاحظات
امتلاك Mac mini كخادم بناءشراء الأجهزة، ثم تكرار شبه صفريالأرخص عند الاستخدام الثابت؛ أنت تملك الصيانة
مزوّد Mac سحابي (MacStadium، EC2 Mac)بالساعة، غالبًا عدة مئات شهريًا عند استخدام 24/7توسّع لأعلى أو لأسفل؛ عبء التزويد
مشغّلات macOS السحابية لـ CIلكل دقيقة، تُفوتر فقط أثناء البناءالأرخص للبناءات العرضية؛ البدء البارد يضر
rsync / git إلى Mac مشتركتكلفة الMac فقطجيد للأفراد، غير ملائم للفرق

عادةً ما يفوز Mac mini مخصص لفريق صغير يبني طوال اليوم. ويفوز CI السحابي عندما تكون البناءات نادرة أو وقت الإصدار حصرًا. وتفوز نسخ Mac السحابية عندما تحتاج سعة عند الطلب ولا تريد امتلاك أجهزة.

المزالق الشائعة​

معظم ألم البناء البعيد يأتي من مجموعة صغيرة من الأخطاء المتكررة. افحصها قبل تنقيح أي شيء غير مألوف.

الترخيص غير مقبول​

يفشل xcodebuild فورًا بخطأ ترخيص. أصلحه مرة واحدة بـ:

sudo xcodebuild -license accept

اختيار إصدار Xcode خاطئ​

تثبيتات Xcode المتعددة تربك سلسلة الأدوات. اختر الإصدار المقصود صراحةً:

sudo xcode-select -s /Applications/Xcode-16.app/Contents/Developer
xcodebuild -version

هوية توقيع مفقودة​

البناء الذي يُؤرشف لكنه يفشل في التصدير عادةً لا يجد هوية أو ملف تعريف مطابقًا. تأكد مما تحتفظ به سلسلة المفاتيح فعلاً:

security find-identity -v -p codesigning

إذا كانت القائمة فارغة للهوية التي تحتاجها، فاستورد .p12 مرة أخرى وتحقق من أن ملف التعريف يطابق معرّف حزمة التطبيق.

مزامنة بطيئة​

إذا كنت تزامن الكود بدلاً من التحرير عبر SSH، ففضّل git على rsync. ينقل Git الفروق ويمنحك السجل ومعالجة التعارضات؛ بينما ينقل rsync أشجارًا كاملة وينحرف بصمت. عند التحرير مباشرة عبر Remote-SSH لا توجد مزامنة إطلاقًا، وهو الخيار الأسرع.

انقطاعات الشبكة تكسر SSH​

البناءات الطويلة على رابط متقلب قد تُسقط الاتصال وتقتل البناء. خفّف ذلك بإبقاء اتصال SSH في تكوينك، وشغّل البناءات الطويلة داخل tmux أو screen على الMac حتى تنجو من انقطاع:

tmux new-session -s build 'xcodebuild archive -workspace MyApp.xcworkspace -scheme MyApp -destination "generic/platform=iOS" -archivePath build/MyApp.xcarchive'

أعد الاتصال لاحقًا وأعد الربط بـ tmux attach -t build.

المحاكي لا يعمل بلا رأس​

تفترض بعض تدفقات الاختبار محاكيًا مرئيًا. على Mac بلا رأس، شغّل المحاكي بـ simctl، واحتفظ إما بجلسة سطح مكتب مسجّلة الدخول أو اقبل تغطية أقل لواجهة المحاكي.

غلّف البناء في tmux من اليوم الأول

إذا بنيت عبر شبكة لا تتحكم بها بالكامل، فشغّل كل xcodebuild طويل داخل tmux. يستمر البناء في العمل على الMac بغض النظر عن نجاة جلسة SSH لديك، ويمكنك إعادة الربط لمشاهدته ينتهي.

البناءات البعيدة تحوّل قيدًا صلبًا للمنصة إلى خيار في سير العمل. اختر البنية التي تناسب فريقك، وأعدّ الMac مرة واحدة، ودع Remote-SSH في Cursor يُبقي تجربة التحرير سريعة بينما يقوم الMac بالعمل الذي يتطلبه macOS.