سؤال

لنفترض أنني قمت بعمل تطبيق OSX دون استخدام Xcode. بعد التجميع مع GCC ، أحصل على قابلة للتنفيذ مرتبطة بعدة مكتبات أخرى. قد تكون بعض تلك المكتبات مرتبطة ديناميكيًا بمكتبات نظام غير قياسي أخرى

هل هناك أي أداة موجودة تجعل حزمة تطبيق OSX عن طريق إنشاء هياكل الدليل المطلوبة أولاً ثم نسخ/فحص/إصلاح متكررة للتأكد من أن جميع التبعيات الديناميكية موجودة أيضًا في حزمة التطبيق؟

أعتقد أنه يمكنني محاولة كتابة شيء كهذا ولكني كنت أتساءل عما إذا كان هناك شيء كهذا موجود بالفعل.

هل كانت مفيدة؟

المحلول

هناك طريقتان لإنشاء حزمة تطبيق على MacOSX ، السهل والقبيح.

الطريقة السهلة هي فقط لاستخدام Xcode. منتهي.

المشكلة هي في بعض الأحيان لا يمكنك ذلك.

في حالتي ، أقوم بإنشاء تطبيق يبني تطبيقات أخرى. لا أستطيع أن أفترض أن المستخدم قد تم تثبيته Xcode. أنا أيضا أستخدم macports لبناء المكتبات يعتمد تطبيقي. أحتاج إلى التأكد من أن هذه dylibs يتم تجميعها مع التطبيق قبل أن أقوم بتوزيعه.

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

أول شيء يجب أن تعرفه هو أن حزمة التطبيق هي مجرد دليل.
دعنا ندرس بنية foo.app الافتراضية.

foo.app/
    Contents/
        Info.plist
        MacOS/
            foo
        Resources/
            foo.icns

info.plist هو ملف XML عادي. يمكنك تحريره باستخدام محرر نصوص أو تطبيق محرر قائمة الممتلكات الذي يأتي مع Xcode. (إنه في/مطور/تطبيقات/مرافق/دليل).

الأشياء الرئيسية التي تحتاج إلى تضمينها هي:

cfbundlename - اسم التطبيق.

cfbundleicon - ملف أيقونة يفترض أنه في المحتويات/الموارد. استخدم تطبيق Composer Icon لإنشاء الرمز. (إنه أيضًا في/المطورين/التطبيقات/المرافق/الدليل) يمكنك فقط سحب وإسقاط PNG على نافذةه ويجب أن تنشئ مستويات MIP تلقائيًا لك.

cfbundleExecutable - اسم الملف القابل للتنفيذ المفترض أن يكون في المحتويات/ MacOS/ مجلد فرعي.

هناك الكثير من الخيارات ، والخيارات المذكورة أعلاه ليست سوى الحد الأدنى. إليك بعض وثائق Apple علىinfo.plist ملف وبنية حزمة التطبيق.

أيضا ، إليك عينة info.plist.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleGetInfoString</key>
  <string>Foo</string>
  <key>CFBundleExecutable</key>
  <string>foo</string>
  <key>CFBundleIdentifier</key>
  <string>com.your-company-name.www</string>
  <key>CFBundleName</key>
  <string>foo</string>
  <key>CFBundleIconFile</key>
  <string>foo.icns</string>
  <key>CFBundleShortVersionString</key>
  <string>0.01</string>
  <key>CFBundleInfoDictionaryVersion</key>
  <string>6.0</string>
  <key>CFBundlePackageType</key>
  <string>APPL</string>
  <key>IFMajorVersion</key>
  <integer>0</integer>
  <key>IFMinorVersion</key>
  <integer>1</integer>
</dict>
</plist>

في عالم مثالي ، يمكنك فقط إسقاطك القابل للتنفيذ في المحتويات/ MacOS/ DIR ويتم القيام به. ومع ذلك ، إذا كان تطبيقك يحتوي على أي تبعيات Dylib غير القياسية ، فلن تعمل. مثل Windows ، يأتي MacOS مع نوع خاص به DLL الجحيم.

إذا كنت تستخدم macports لإنشاء مكتبات تربطها ، سيتم ترميز مواقع Dylibs إلى قابلة للتنفيذ. إذا قمت بتشغيل التطبيق على جهاز يحتوي على dylibs في نفس الموقع بالضبط ، فسيتم تشغيله بشكل جيد. ومع ذلك ، لن يتم تثبيت معظم المستخدمين ؛ عندما ينقر نقرًا مزدوجًا على تطبيقك ، فإنه سيتعطل فقط.

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

تحرير اليدين أصوات قابلة للتنفيذ ، أليس كذلك؟ لحسن الحظ ، هناك أدوات سطر الأوامر للمساعدة.

otool -L executable_name

سوف يسرد هذا الأمر جميع dylibs التي يعتمد عليها تطبيقك. إذا رأيت أيًا غير موجود في مجلد النظام/المكتبة أو usr/lib ، فهذه هي تلك التي ستحتاج إلى نسخها في حزمة التطبيق. نسخها إلى/المحتويات/macOS/المجلد. بعد ذلك ، ستحتاج إلى تعديل القابل للتنفيذ لاستخدام Dylibs الجديد.

أولاً ، تحتاج إلى التأكد من ارتباطك باستخدام علامة -headerpad_max_install_names. هذا يتأكد فقط من أنه إذا كان مسار Dylib الجديد أطول ، فسيكون هناك مجال له.

ثانياً ، استخدم install_name_tool لتغيير كل مسار dylib.

install_name_tool -change existing_path_to_dylib @executable_path/blah.dylib executable_name

كمثال عملي ، دعنا نقول أن تطبيقك يستخدم libsdl, و otool يسرد موقعه على أنه "/opt/local/lib/libsdl-1.2.0.dylib".

قم أولاً بنسخه في حزمة التطبيق.

cp /opt/local/lib/libSDL-1.2.0.dylib foo.app/Contents/MacOS/

ثم قم بتحرير القائمة الصحيحة لاستخدام الموقع الجديد (ملاحظة: تأكد من قيامك بإنشائه باستخدام علامة -headerpad_max_install_names)

install_name_tool -change /opt/local/lib/libSDL-1.2.0.dylib @executable_path/libSDL-1.2.0.dylib foo.app/Contents/MacOS/foo

يا للعجب ، لقد انتهينا تقريبًا. الآن هناك مشكلة صغيرة مع دليل العمل الحالي.

عندما تبدأ تطبيقك ، سيكون الدليل الحالي هو الدليل أعلاه حيث يوجد التطبيق. على سبيل المثال: إذا قمت بوضع foo.app في مجلد /applcations ، فإن الدليل الحالي عند تشغيل التطبيق سيكون مجلد /تطبيقات. ليس/applications/foo.app/contents/macos/ كما قد تتوقع.

يمكنك تغيير التطبيق الخاص بك لحساب ذلك ، أو يمكنك استخدام هذا البرنامج النصي Magic Little Launcher الذي سيغير الدليل الحالي وإطلاق تطبيقك.

#!/bin/bash
cd "${0%/*}"
./foo

تأكد من ضبط ملف info.plist بحيث cfbundleExecutable يشير إلى البرنامج النصي وليس إلى السابق القابل للتنفيذ.

حسنًا ، كل شيء تم القيام به الآن. لحسن الحظ ، بمجرد أن تعرف كل هذه الأشياء التي تدفنها في نص بناء.

نصائح أخرى

لقد وجدت في الواقع أداة مفيدة للغاية تستحق بعض الائتمان ... لا - لم أقم بتطوير هذا ؛)

https://github.com/auriamg/macdylibbundler/

ستحل جميع التبعيات و "إصلاح" ملفاتك القابلة للتنفيذ وكذلك ملفات dylib الخاصة بك للعمل بسلاسة في حزمة التطبيق الخاصة بك.

... سيتحقق أيضًا من تبعيات libs الديناميكية التابعة الخاصة بك: د

أبسط الحلول هو: إنشاء مشروع XCode بمجرد تغيير أي شيء (أي احتفظ بتطبيق Window البسيط الذي ينشئه XCode لك) ، وإنشائه ، ونسخ الحزمة التي تم إنشاؤها لك. بعد ذلك ، قم بتحرير الملفات (ولا سيما info.plist) لتناسب المحتوى الخاص بك ، ووضع ثنائي خاص بك في المحتويات/ MacOS/ الدليل.

أنا استخدم هذا في Makefile الخاص بي ... إنه ينشئ حزمة تطبيق. اقرأها وفهمها ، لأنك ستحتاج إلى ملف رمز PNG في مجلد MacOSX/ مجلد مع ملفات PKGINFO و INFO.Plist التي أدرجتها هنا ...

"إنه يعمل على جهاز الكمبيوتر الخاص بي" ... أستخدم هذا لتطبيقات متعددة على Mavericks ...

APPNAME=MyApp
APPBUNDLE=$(APPNAME).app
APPBUNDLECONTENTS=$(APPBUNDLE)/Contents
APPBUNDLEEXE=$(APPBUNDLECONTENTS)/MacOS
APPBUNDLERESOURCES=$(APPBUNDLECONTENTS)/Resources
APPBUNDLEICON=$(APPBUNDLECONTENTS)/Resources
appbundle: macosx/$(APPNAME).icns
    rm -rf $(APPBUNDLE)
    mkdir $(APPBUNDLE)
    mkdir $(APPBUNDLE)/Contents
    mkdir $(APPBUNDLE)/Contents/MacOS
    mkdir $(APPBUNDLE)/Contents/Resources
    cp macosx/Info.plist $(APPBUNDLECONTENTS)/
    cp macosx/PkgInfo $(APPBUNDLECONTENTS)/
    cp macosx/$(APPNAME).icns $(APPBUNDLEICON)/
    cp $(OUTFILE) $(APPBUNDLEEXE)/$(APPNAME)

macosx/$(APPNAME).icns: macosx/$(APPNAME)Icon.png
    rm -rf macosx/$(APPNAME).iconset
    mkdir macosx/$(APPNAME).iconset
    sips -z 16 16     macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_16x16.png
    sips -z 32 32     macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_16x16@2x.png
    sips -z 32 32     macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_32x32.png
    sips -z 64 64     macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_32x32@2x.png
    sips -z 128 128   macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_128x128.png
    sips -z 256 256   macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_128x128@2x.png
    sips -z 256 256   macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_256x256.png
    sips -z 512 512   macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_256x256@2x.png
    sips -z 512 512   macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_512x512.png
    cp macosx/$(APPNAME)Icon.png macosx/$(APPNAME).iconset/icon_512x512@2x.png
    iconutil -c icns -o macosx/$(APPNAME).icns macosx/$(APPNAME).iconset
    rm -r macosx/$(APPNAME).iconset

info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>English</string>
    <key>CFBundleExecutable</key>
    <string>MyApp</string>
    <key>CFBundleGetInfoString</key>
    <string>0.48.2, Copyright 2013 my company</string>
    <key>CFBundleIconFile</key>
    <string>MyApp.icns</string>
    <key>CFBundleIdentifier</key>
    <string>com.mycompany.MyApp</string>
    <key>CFBundleDocumentTypes</key>
    <array>
    </array>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>0.48.2</string>
    <key>CFBundleSignature</key>
    <string>MyAp</string>
    <key>CFBundleVersion</key>
    <string>0.48.2</string>
    <key>NSHumanReadableCopyright</key>
    <string>Copyright 2013 my company.</string>
    <key>LSMinimumSystemVersion</key>
    <string>10.3</string>
</dict>
</plist>

PKGINFO

APPLMyAp

هناك بعض الأدوات المصدر المفتوحة للمساعدة في إنشاء حزم تطبيقات مع مكتبات تابعة لبيئات محددة ، على سبيل المثال ، PY2APP للتطبيقات القائمة على بيثون. إذا لم تجد واحدة أكثر عمومية ، فربما يمكنك تكييفها مع احتياجاتك.

أتمنى لو وجدت هذا المنشور في وقت سابق ....

ها هي طريقتي الفائقة لحل هذه المشكلة باستخدام أ Run script المرحلة التي يتم استدعاؤها في كل مرة أقوم ببناء أ Release نسخة من تطبيقي:

# this is an array of my dependencies' libraries paths 
# which will be iterated in order to find those dependencies using otool -L
libpaths=("$NDNRTC_LIB_PATH" "$BOOST_LIB_PATH" "$NDNCHAT_LIB_PATH" "$NDNCPP_LIB_PATH" "/opt/local/lib")
frameworksDir=$BUILT_PRODUCTS_DIR/$FRAMEWORKS_FOLDER_PATH
executable=$BUILT_PRODUCTS_DIR/$EXECUTABLE_PATH

#echo "libpaths $libpaths"
bRecursion=0
lRecursion=0

# this function iterates through libpaths array
# and checks binary with "otool -L" command for containment
# of dependency which has "libpath" path
# if such dependency has been found, it will be copied to Frameworks 
# folder and binary will be fixed with "install_name_tool -change" command
# to point to Frameworks/<dependency> library
# then, dependency is checked recursively with resolveDependencies function
function resolveDependencies()
{
    local binfile=$1
    local prefix=$2
    local binname=$(basename $binfile)
    local offset=$((lRecursion*20))
    printf "%s :\t%s\n" $prefix "resolving $binname..."

    for path in ${libpaths[@]}; do
        local temp=$path
        #echo "check lib path $path"
        local pattern="$path/([A-z0-9.-]+\.dylib)"
        while [[ "$(otool -L ${binfile})" =~ $pattern ]]; do
            local libname=${BASH_REMATCH[1]}
            otool -L ${binfile}
            #echo "found match $libname"
            printf "%s :\t%s\n" $prefix "fixing $libname..."
            local libpath="${path}/$libname"
            #echo "cp $libpath $frameworksDir"
            ${SRCROOT}/sudocp.sh $libpath $frameworksDir/$libname $(whoami)
            local installLibPath="@rpath/$libname"
            #echo "install_name_tool -change $libpath $installLibPath $binfile"
            if [ "$libname" == "$binname" ]; then
                install_name_tool -id "@rpath/$libname" $binfile
                printf "%s :\t%s\n" $prefix "fixed id for $libname."
            else
                install_name_tool -change $libpath $installLibPath $binfile
                printf "%s :\t%s\n" $prefix "$libname dependency resolved."
                let lRecursion++
                resolveDependencies "$frameworksDir/$libname" "$prefix>$libname"
                resolveBoostDependencies "$frameworksDir/$libname" "$prefix>$libname"
                let lRecursion--
            fi
            path=$temp
        done # while
    done # for

    printf "%s :\t%s\n" $prefix "$(basename $binfile) resolved."
} # resolveDependencies

# for some reason, unlike other dependencies which maintain full path
# in "otool -L" output, boost libraries do not - they just appear 
# as "libboost_xxxx.dylib" entries, without fully qualified path
# thus, resolveDependencies can't be used and a designated function is needed
# this function works pretty much in a similar way to resolveDependencies
# but targets only dependencies starting with "libboost_", copies them
# to the Frameworks folder and resolves them recursively
function resolveBoostDependencies()
{
    local binfile=$1
    local prefix=$2
    local binname=$(basename $binfile)
    local offset=$(((bRecursion+lRecursion)*20))
    printf "%s :\t%s\n" $prefix "resolving Boost for $(basename $binfile)..."

    local pattern="[[:space:]]libboost_([A-z0-9.-]+\.dylib)"
    while [[ "$(otool -L ${binfile})" =~ $pattern ]]; do
        local libname="libboost_${BASH_REMATCH[1]}"
        #echo "found match $libname"
        local libpath="${BOOST_LIB_PATH}/$libname"
        #echo "cp $libpath $frameworksDir"
        ${SRCROOT}/sudocp.sh $libpath $frameworksDir/$libname $(whoami)
        installLibPath="@rpath/$libname"
        #echo "install_name_tool -change $libname $installLibPath $binfile"
        if [ "$libname" == "$binname" ]; then
            install_name_tool -id "@rpath/$libname" $binfile
            printf "%s :\t%s\n" $prefix "fixed id for $libname."
        else
            install_name_tool -change $libname $installLibPath $binfile
            printf "%s :\t%s\n" $prefix "$libname Boost dependency resolved."
            let bRecursion++
            resolveBoostDependencies "$frameworksDir/$libname" "$prefix>$libname"
            let bRecursion--
        fi
    done # while

    printf "%s :\t%s\n" $prefix "$(basename $binfile) resolved."
}

resolveDependencies $executable $(basename $executable)
resolveBoostDependencies $executable $(basename $executable)

آمل أن يكون هذا مفيدًا لشخص ما.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top