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

المهمة المحددة هي تجميع حساب شبكة قابل لإعادة الاستخدام لأداة سطح مكتب محلية على macOS. يستخدم المثال مدخلات اصطناعية وأوامر طرفية لتتمكن من رؤية التجميع والربط والتحميل كلٌّ على حدة قبل اختيار صيغة لمشروع تطبيق.

ما الذي تختاره تحديدًا؟

هناك قراران: كيفية ربط الشيفرة، وكيفية تجميع ناتجها. المكتبة الثابتة، التي ينتهي اسمها عادةً بـ .a، هي أرشيف لملفات الكائنات. أما المكتبة الديناميكية، التي ينتهي اسمها بـ .dylib، فتبقى صورة منفصلة وقت التشغيل. والإطار حزمة تنظّم مكتبة ومواد ذات صلة. تصف Apple هذه الصيغ الأساسية في مقدمة إلى مكتبات Apple.

يمكن أن يحتوي الإطار أيضًا على مكتبة ثابتة. تدعم Apple إنشاء أُطر ثابتة في Xcode 15 والإصدارات الأحدث، بما في ذلك الموارد. لذلك، لا تكفي اللاحقة .framework وحدها لتحديد ما إذا كان ربط المكتبة ثابتًا أم ديناميكيًا. راجع إنشاء إطار ثابت.

استخدم دليل القرار هذا لمساعد تملكه في التطبيق وتبنيه من المصدر:

متطلبك الخيار المبدئي المسؤولية التي يضيفها
ملف تنفيذي واحد ومساعد يُعاد بناؤهما معًا .a ثابت أعد ربط الملف التنفيذي عند تغيير المساعد
مساعد يبقى مكوّنًا منفصلًا وقت التشغيل .dylib ديناميكي وفّر المكتبة ومسار بحث صالحًا وقت التشغيل
مكوّن Xcode برؤوس عامة وموارد مجمّعة إطار اختر نوع الربط على حدة واضبط عضوية الأهداف
صيغ ثنائية لمنصات Apple متعددة XCFramework ابنِ صيغ المنصات المطلوبة وحزّمها

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

يجمع XCFramework الصيغ الثنائية؛ لكنه لا يحل محل قرار الاختيار بين الثابت والديناميكي. تسمح Apple بوجود أُطر ومكتبات ثابتة أو ديناميكية داخل صيغة التوزيع هذه. راجع إنشاء حزمة إطار ثنائي متعددة المنصات.

تحقّق من التوافق قبل اختيار حاوية

لا يكفي تطابق اسم الملف أو معمارية وحدة المعالجة المركزية. macOS وMac Catalyst وiOS منصات بناء مختلفة. لا يمكن ببساطة ربط مكتبة arm64 جُمّعت لمنصة واحدة بملف تنفيذي لمنصة أخرى. تميّز Apple صراحةً بين المنصة والمعمارية في مقدمة إلى مكتبات Apple.

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

يتطلب توافق C++ اهتمامًا خاصًا. اختيار -std=c++17 يحدد نمط اللغة، لكنه لا يضمن التوافق الثنائي بين المكوّنات المبنية بشكل مستقل. توثّق LLVM ضمانات ABI وخيارات إعداد المورّد لـ libc++، بما في ذلك الإعدادات التي تغيّر تخطيطات الأنواع. ومن الخيارات الافتراضية المفيدة لتبعية يملكها التطبيق إعادة بنائها باستخدام سلسلة أدوات التطبيق وإعدادات متسقة. راجع ضمانات ABI في libc++.

يكشف المثال دالة واحدة عبر رأس متوافق مع C، مع تنفيذها بلغة C++. يحدد التصريح المحاط بعبارة extern \"C\" ربط لغة C عند التجميع باستخدام C++. لكنه لا يحوّل أصناف C++ العشوائية إلى واجهات C، ولا يجعل الملف الثنائي قابلًا للنقل بين المنصات. تحدد مسودة C++ هذه الحدود في مواصفات الربط.

ابنِ المساعد نفسه كمكتبة ثابتة وديناميكية

تحتاج إلى جهاز Mac مزوّد بأدوات المطوّرين من Apple وحزمة تطوير macOS. يتضمن Xcode الكامل أدوات سطر الأوامر، كما توفر Apple حزمة مستقلة لأدوات سطر الأوامر. إذا لم تكن أي منهما مثبتة، شغّل xcode-select --install وأكمل التثبيت قبل المتابعة. راجع تثبيت أدوات سطر الأوامر.

ينشئ النص البرمجي الكامل أدناه مجلدًا مؤقتًا جديدًا، ولا يستخدم سوى ملفات مصدر اصطناعية. شغّله في Terminal. وهو يستهدف صراحةً معمارية بيئة الصدفة الحالية والحد الأدنى التوضيحي للنشر macOS 11.0. هذا تمرين لمعمارية واحدة؛ استخدم حدًا أدنى مختلفًا إذا كان مشروعك يتطلب ذلك.

راجعت المثال، لكن لم أتمكن من تنفيذ بناء Mac: بيئة البحث تعمل على Linux ولا تتضمن clang++. النتائج المعروضة نتائج متوقعة وليست ناتجة عن تشغيل اختبار فعلي.

bash <<'SH'
set -eu

work_dir=$(mktemp -d "${TMPDIR:-/tmp}/mac-library-demo.XXXXXX")
cd "$work_dir"
printf 'Example directory: %s\n' "$work_dir"

mkdir include src build
mkdir -p build/static build/dynamic/lib

cat > include/grid.h <<'EOF'
#ifndef GRID_H
#define GRID_H

#ifdef __cplusplus
extern "C" {
#endif

/* Inputs: positive dimensions whose product fits in int. */
int grid_cells(int rows, int columns);

#ifdef __cplusplus
}
#endif

#endif
EOF

cat > src/grid.cpp <<'EOF'
#include "grid.h"

int grid_cells(int rows, int columns) {
    return rows * columns;
}
EOF

cat > src/main.cpp <<'EOF'
#include "grid.h"
#include <cstdio>

int main() {
    const int cells = grid_cells(3, 4);
    std::printf("Grid cells: %d\n", cells);
    return cells == 12 ? 0 : 1;
}
EOF

sdk_path=$(xcrun --sdk macosx --show-sdk-path)
build_arch=$(uname -m)
case "$build_arch" in
    arm64|x86_64) ;;
    *) printf 'Unexpected architecture: %s\n' "$build_arch" >&2; exit 1 ;;
esac

xcrun --sdk macosx clang++ \
    -std=c++17 -arch "$build_arch" -isysroot "$sdk_path" \
    -mmacosx-version-min=11.0 -Iinclude \
    -c src/grid.cpp -o build/grid.o

xcrun --sdk macosx ar rcs build/static/libGrid.a build/grid.o

xcrun --sdk macosx clang++ \
    -std=c++17 -arch "$build_arch" -isysroot "$sdk_path" \
    -mmacosx-version-min=11.0 -Iinclude \
    src/main.cpp build/static/libGrid.a -o build/static/grid-demo

xcrun --sdk macosx clang++ \
    -arch "$build_arch" -isysroot "$sdk_path" \
    -mmacosx-version-min=11.0 -dynamiclib build/grid.o \
    -Wl,-install_name,@rpath/libGrid.dylib \
    -o build/dynamic/lib/libGrid.dylib

xcrun --sdk macosx clang++ \
    -std=c++17 -arch "$build_arch" -isysroot "$sdk_path" \
    -mmacosx-version-min=11.0 -Iinclude \
    src/main.cpp build/dynamic/lib/libGrid.dylib \
    -Wl,-rpath,@loader_path/lib -o build/dynamic/grid-demo

./build/static/grid-demo
./build/dynamic/grid-demo

xcrun --sdk macosx ar t build/static/libGrid.a
xcrun --sdk macosx otool -L build/static/grid-demo
xcrun --sdk macosx otool -L build/dynamic/grid-demo
xcrun --sdk macosx otool -l build/dynamic/grid-demo
xcrun --sdk macosx lipo -archs build/dynamic/grid-demo
SH

ينبغي أن يطبع كلا التنفيذين Grid cells: 12. هذا الحساب صغير عمدًا: ثلاثة صفوف مضروبة في أربعة أعمدة. يستبعد عقد الإدخال المعلن للمساعد الأبعاد التي لا يمكن أن يتسع حاصل ضربها في int؛ ولا ينفّذ هذا المثال واجهة عامة للتحقق من صحة المدخلات.

ما الذي يوضحه البناء الثابت؟

ينشئ الاستدعاء -c ملف grid.o من دون ربط ملف تنفيذي. ويجمع أمر الأرشفة ذلك الكائن في libGrid.a؛ أما خياراته r وc وs فتطلب الإدراج أو الاستبدال، وإنشاء الأرشيف، وإنشاء فهرس الرموز. توثّق Clang مراحل التجميع في دليل أوامر المترجم، وتشرح LLVM عمليات الأرشفة في llvm-ar — أداة أرشفة LLVM.

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

ينطبق الربط الثابت هنا على مساعد Grid. وهذا لا يعني أن الملف التنفيذي الناتج مستقل عن مكتبات وقت تشغيل macOS؛ افحص الإدخالات المتبقية التي يعرضها otool -L. تميّز Apple بين الرابط وقت البناء والمحمّل وقت التشغيل في مقدمة إلى مكتبات Apple.

ما الذي يوضحه البناء الديناميكي؟

ينشئ استدعاء -dynamiclib مكتبة منفصلة. اسم التثبيت الخاص بها هو @rpath/libGrid.dylib. ويسجل الملف التنفيذي مسار تشغيل هو @loader_path/lib، والذي يشير، بالنسبة إلى إدخال مسار التشغيل لهذا الملف التنفيذي، إلى مجلد lib المجاور. تسرد Clang خيارات المشغّل ذات الصلة في مرجع وسيطات سطر الأوامر.

يتضمن ناتج otool -L المتوقع للعميل الديناميكي @rpath/libGrid.dylib؛ وينبغي أن يتضمن ناتج أوامر التحميل إدخال LC_RPATH مع @loader_path/lib. توثّق Apple كيفية تعاون أسماء التثبيت ومسارات تشغيل الملف التنفيذي في المكتبات المعتمدة على مسار التشغيل.

يوفر هذا اختبارًا عمليًا للتجميع: انقل مجلد build/dynamic بأكمله معًا، مع الحفاظ على بنيته الداخلية، ثم شغّل ملفه التنفيذي من الموقع الجديد. نقل الملف التنفيذي وحده يُبقي تبعية Grid في مكانها السابق. يثبت نجاح الربط العثور على مدخلات البناء، لكنه لا يثبت اكتمال حزمة وقت التشغيل.

متى ينبغي أن يصبح المساعد إطارًا؟

اختر إطارًا عندما تخدم بنية الحزمة المشروع، مثلما يحدث عندما يحتاج المكوّن إلى رؤوس عامة وموارد تُدار معًا في Xcode. أما لهذا المساعد المخصص للحساب فقط، فيضيف الإطار عملًا على التجميع من دون تلبية متطلب إضافي.

لإنشاء إطار ثابت في Xcode 15 أو أحدث، توثّق Apple الخطوات التالية:

  1. اختر File > New > Target، وحدد macOS، ثم استخدم قالب Framework.
  2. حدد Build Settings للهدف الجديد وغيّر Mach-O Type إلى Static Library.
  3. أضف ملفات التنفيذ إلى Compile Sources والموارد إلى Copy Bundle Resources.
  4. أضف الرؤوس المُصدّرة إلى مرحلة البناء Headers، وأدرج الرؤوس العامة في رأس التجميع.
  5. أضف الإطار إلى التطبيق المستهلك واتبع إعداد التضمين الذي توثقه Apple.

بالنسبة إلى إطار ثابت يحتوي على موارد، تحدد Apple الخيار Embed & Sign. يحذف Xcode الملف الثنائي الرئيسي الذي جرى ربطه مسبقًا من الحزمة المضمّنة، مع الإبقاء على مواد الحزمة. اتبع إنشاء إطار ثابت بدلًا من التعامل مع كل إطار ثابت كأنه أرشيف لا يحتاج إلى تضمين.

إذا كنت توزع صيغ ثنائية، فاستخدم XCFramework. وبالنسبة إلى ملف .a مستقل موجود، توثّق Apple مدخلي -library و-headers لأمر xcodebuild -create-xcframework؛ كما تنصح بعدم تمويه الأرشيف يدويًا ليبدو إطارًا. يتطلب xcodebuild تثبيت Xcode الكامل. راجع إنشاء حزمة إطار ثنائي متعددة المنصات وتثبيت أدوات سطر الأوامر.

كيف يغيّر Swift القرار؟

يضيف Swift متطلبات استهلاك الوحدات وتطورها إلى قرار الربط الأساسي. فاستقرار ABI واستقرار الوحدات وتطور المكتبات تعالج شواغل مختلفة بشأن التوافق؛ ولا يفعّلها تلقائيًا وضع المحتوى داخل حاوية .framework.

تنص إرشادات Swift على أن الأُطر المبنية والموزعة مع عملائها عمومًا لا ينبغي أن تفعّل تطور المكتبات. أما الإطار الثنائي الذي يُبنى ويُحدّث بشكل منفصل، فقرّر دعم التطور منذ الإصدار الأول. في Xcode، يفعّل BUILD_LIBRARY_FOR_DISTRIBUTION تطور المكتبات واستقرار الوحدات. تؤثر هذه الخيارات في خصائص الأداء وتغييرات API المسموح بها، كما هو موضح في تطور المكتبات في Swift.

أبقِ هذه الضمانات ضمن نطاق المنصات الموثّق. تصف وثائق Swift التوافق الثنائي بين المترجمات على منصات Apple؛ فلا تعمّم هذا الوعد على كل منصة يدعمها Swift، ولا تفسّره على أنه إذن بإجراء تغييرات عشوائية على API. كما أن الرأس المتوافق مع C في هذا المثال لا يشكّل إعدادًا كاملًا لوحدة Swift.

شخّص البناء الفاشل حسب مراحل حدوثه

استخدم مرحلة الفشل لتحديد الفحص التالي:

العَرَض الفحص الأول الإجراء التالي
لا يستطيع المترجم العثور على grid.h وجود الرأس ضمن include تحقّق من مجلد العمل ومن -Iinclude
مرجع غير معرّف لدالة Grid تطابق التنفيذ والتصريح تأكد من وجود الأرشيف أو المكتبة الديناميكية المقصودة في أمر الربط
عدم توافق المعمارية أو المنصة شرائح التبعية الفعلية ومنصة البناء أعد بناء التبعية لهدف macOS الخاص بالعميل
يرتبط الملف التنفيذي لكنه يعجز عن تحميل Grid التبعية ومسار التشغيل المسجلان قارن otool -L وotool -l وتخطيط مجلد الحزمة
اختلاف سلوك بنائي Intel وApple silicon تغطية المعمارية لكل تبعية افحص كل ملف ثنائي واختبر المعمارية المقصودة

يفصل دليل أوامر المترجم من Clang بين التجميع والربط. وتشرح المكتبات المعتمدة على مسار التشغيل من Apple حل التبعيات وقت التشغيل، بينما يوثّق بناء ملف macOS ثنائي شامل فحص المعماريات باستخدام lipo -archs. تجعل هذه الفروق ترتيب التشخيص مفيدًا: فتغيير مسارات الرؤوس لا يعالج مكتبة مفقودة وقت التشغيل.

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

المصادر