سؤال
لنفترض أنني قمت بعمل تطبيق 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)
آمل أن يكون هذا مفيدًا لشخص ما.