构buildOSX应用程序包

假设我已经做了一个osX应用程序,而不使用Xcode。 用GCC编译后,我得到一个链接到其他几个库的可执行文件。 其中一些库可能会再次dynamic链接到其他非标准系统库

是否有任何工具存在,通过首先制作所需的目录结构,然后recursion复制/检查/修复链接,以确保所有的dynamic依赖关系也在应用程序包中,使OSX应用程序包捆绑?

我想我可以尝试写这样的东西,但我想知道是否已经存在这样的事情。

有两种方法可以在MacOSX上创build应用程序包,简单和丑陋。

简单的方法就是使用XCode。 完成。

问题有时你不能。

就我而言,我正在构build一个构build其他应用程序的应用程序。 我不能假定用户已经安装了XCode。 我也使用MacPorts来build立我的应用程序所依赖的库。 我需要确保这些dylibs与我的应用程序捆绑在一起之前,我分发它。

免责声明:我完全没有资格写这篇文章,所有的东西都从苹果文档中闪现,挑选现有的应用程序和反复试验。 它适用于我,但很可能是错误的。 如果您有任何更正,请给我发电子邮件。

首先你应该知道的一个应用程序包只是一个目录。
让我们来看看一个假设的foo.app的结构。

 foo.app/
    内容/
        的Info.plist
        苹果系统/
             FOO
        资源/
             foo.icns

Info.plist是一个普通的XML文件。 您可以使用文本编辑器或与XCode捆绑在一起的“属性列表编辑器”应用程序对其进行编辑。 (它在/ Developer / Applications / Utilities /目录下)。

你需要包括的关键是:

CFBundleName – 应用程序的名称。

CFBundleIcon – 假设在目录/资源目录中的图标文件。 使用Icon Composer应用程序来创build图标。 (它也在/ Developer / Applications / Utilities /目录下)你可以拖放一个PNG到它的窗口,并自动为你生成mip-levels。

CFBundleExecutable – 可执行文件的名称假定在Contents / MacOS /子文件夹中。

还有更多的select,上面列出的只是最低限度。 以下是关于Info.plist文件和App捆绑结构的一些Apple文档。

另外,这里是一个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”>
 <字典>
   <键> CFBundleGetInfoString </键>
   <string>富</string>
   <键> CFBundleExecutable </键>
   <string> FOO </string>
   <键> CFBundleIdentifier </键>
   <string> com.your-公司name.www </string>
   <键> CFBundleName </键>
   <string> FOO </string>
   <键> CFBundleIconFile </键>
   <string> foo.icns </string>
   <键> CFBundleShortVersionString </键>
   <string> 0.01 </string>
   <键> CFBundleInfoDictionaryVersion </键>
   <string> 6.0 </string>
   <键> CFBundlePackageType </键>
   <string> APPL </string>
   <键> IFMajorVersion </键>
   <整数> 0 </整数>
   <键> IFMinorVersion </键>
   <整数> 1 </整数>
 </字典>
 </ plist中>

在一个完美的世界里,你可以把你的可执行文件放到Contents / MacOS /目录下,然后完成。 但是,如果你的应用程序有任何非标准的dylib依赖关系,它将无法正常工作。 像Windows一样,MacOS自带了一种特殊的DLL Hell 。

如果您使用MacPorts来构build链接的库,则dylib的位置将被硬编码到您的可执行文件中。 如果您在具有完全相同位置的dylib的机器上运行应用程序,它将运行正常。 但是,大多数用户不会安装它们; 当他们双击你的应用程序,它只会崩溃。

在分发可执行文件之前,您需要收集所有加载的dylib,并将其复制到应用程序包中。 您还需要编辑可执行文件,以便在正确的位置查找dylib。 即您将其复制到的位置。

手编辑可执行文件听起来很危险吗? 幸运的是有命令行工具来帮助。

 otool -L可执行文件名

这个命令将列出你的应用依赖的所有dylib。 如果您看到任何不在System / Library或usr / lib文件夹中的文件,则需要将这些文件复制到应用程序包中。 将它们复制到/ Contents / MacOS /文件夹中。 接下来,您需要编辑可执行文件以使用新的dylib。

首先,您需要确保使用-headerpad_max_install_names标志进行链接。 这只是确保如果新的dylibpath比上一个更长,那么将会有空间。

其次,使用install_name_tool来更改每个dylibpath。

 install_name_tool -change existing_path_to_dylib @ executable_path / blah.dylib可执行文件名

作为一个实际的例子,假设你的应用程序使用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标志构build它)

 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文件夹。 不像你可能期望的那样/Applications/foo.app/Contents/MacOS/。

你可以改变你的应用来解决这个问题,或者你可以使用这个神奇的小启动脚本来改变当前目录并启动你的应用程序。

 #!/斌/庆典
 cd“$ {0%/ *}”
 ./foo

确保您调整Info.plist文件,以便CFBundleExecutable指向启动脚本而不是以前的可执行文件。

好的,现在都做完了。 幸运的是,一旦你知道所有这些东西,就把它埋在一个构build脚本中。

我实际上find了一个非常方便的工具,值得信任…不 – 我没有开发这个;)

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

它将解决所有的依赖关系,并“修复”你的可执行文件以及你的dylib文件在你的应用程序包中顺利运行。

…它也将检查您的依赖dynamic库的依赖关系:D

关于这个软件包真的没什么魔法 – 只要阅读一下苹果的文档,就可以模仿它。 在基地,你需要Info.plist,Contents / MacOS /二进制文件和一个图标。

最简单的解决scheme是:一次创build一个Xcode项目而不改变任何东西(即保留Xcode为您创build的简单的单窗口应用程序),构build它,并复制它为您创build的包。 然后,编辑文件(特别是Info.plist)以适合您的内容,并将您自己的二进制文件放在Contents / MacOS /目录中。

我在我的Makefile中使用它…它创build一个应用程序包。 阅读并理解它,因为你需要在macosx /文件夹中的png图标文件以及我在这里包含的PkgInfo和Info.plist文件…

“它在我的电脑上工作”…我用它在小牛的多个应用程序…

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 

有一些开源工具可以帮助用特定的环境构build依赖库的应用程序包,例如,基于Python的应用程序的py2app 。 如果你没有find一个更一般的,也许你可以适应你的需求。

我希望我以前find这个职位….

下面是我使用Run script阶段来解决这个问题的粗略方法,每次构build我的应用程序的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) 

希望这可能对某人有用。