假设我已经制作了一个不使用Xcode的osX应用程序。用GCC编译后,我得到了一个链接到其他几个库的可执行文件。其中一些库可能会再次动态链接到其他非标准系统库
是否有任何工具可以通过首先制作所需的目录结构,然后递归地复制/检查/修复链接以确保所有动态依赖项也包含在应用程序包中来制作OSX应用程序包?
我想我可以尝试编写类似的内容,但是我想知道是否已经存在这样的内容。
假设我已经制作了一个不使用Xcode的osX应用程序。用GCC编译后,我得到了一个链接到其他几个库的可执行文件。其中一些库可能会再次动态链接到其他非标准系统库
是否有任何工具可以通过首先制作所需的目录结构,然后递归地复制/检查/修复链接以确保所有动态依赖项也包含在应用程序包中来制作OSX应用程序包?
我想我可以尝试编写类似的内容,但是我想知道是否已经存在这样的内容。
Answers:
在MacOSX上有两种创建应用程序包的方法,即Easy和Ugly。
简单的方法就是使用XCode。做完了
问题是有时候你做不到。
就我而言,我正在构建一个可构建其他应用程序的应用程序。我不能认为用户已安装XCode。我还使用MacPorts构建我的应用程序依赖的库。在分发它之前,我需要确保这些dylib与该应用程序捆绑在一起。
免责声明:我完全没有资格撰写这篇文章,其中的所有内容都已从Apple文档中闪闪发光,将现有的应用程序和反复试验分开。它对我有用,但很可能是错误的。如果您有任何更正,请给我发电子邮件。
您应该知道的第一件事是应用程序捆绑包只是一个目录。
让我们检查一个假设的foo.app的结构。
foo.app/ 内容/ 信息清单 苹果系统/ 富 资源/ foo.icns
Info.plist是一个纯XML文件。您可以使用文本编辑器或XCode附带的属性列表编辑器应用程序对其进行编辑。(位于/ Developer / Applications / Utilities /目录中)。
您需要包括的关键内容是:
CFBundleName-应用程序的名称。
CFBundleIcon-图标文件,假定位于目录/资源目录中。使用Icon Composer应用程序创建图标。(它也位于/ Developer / Applications / Utilities /目录中),您只需将png拖放到其窗口中,即可自动为您生成mip级别。
CFBundleExecutable-假定位于Contents / MacOS /子文件夹中的可执行文件的名称。
还有更多选择,上面列出的只是最低限度的选择。这是有关Info.plist 文件和 App bundle结构的一些Apple文档 。
另外,这是示例Info.plist。
<?xml版本=“ 1.0”编码=“ 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>
在理想情况下,您只需将可执行文件拖放到Contents / MacOS /目录中即可。但是,如果您的应用程序具有任何非标准的dylib依赖关系,它将无法正常工作。像Windows一样,MacOS也带有它自己的特殊类型的DLL Hell。
如果您使用MacPorts构建要链接的库,则dylib的位置将被硬编码到可执行文件中。如果您在具有完全相同的dylib的计算机上运行该应用程序,它将运行良好。但是,大多数用户不会安装它们。当他们双击您的应用程序时,它将崩溃。
在分发可执行文件之前,您需要收集其加载的所有Dylib,并将其复制到应用程序捆绑包中。您还需要编辑可执行文件,以便它将在正确的位置查找dylib。即您将它们复制到的位置。
手工编辑可执行文件听起来很危险,对吗?幸运的是,有命令行工具可以提供帮助。
otool -L可执行文件名
此命令将列出您的应用程序依赖的所有dylib。如果您看到不在System / Library或usr / lib文件夹中的任何文件,则需要将其复制到应用程序捆绑包中。将它们复制到/ Contents / MacOS /文件夹中。接下来,您需要编辑可执行文件以使用新的dylib。
首先,您需要确保使用-headerpad_max_install_names标志进行链接。这只是确保如果新的dylib路径长于前一个,则将有空间。
其次,使用install_name_tool更改每个dylib路径。
install_name_tool-更改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标志构建了该可执行文件)
install_name_tool -change /opt/local/lib/libSDL-1.2.0.dylib @ executable_path / libSDL-1.2.0.dylib foo.app/Contents/MacOS/foo
ew,我们快完成了。现在,当前工作目录存在一个小问题。
启动应用程序时,当前目录将是该应用程序所在目录的上方。例如:如果将foo.app放在/ Applcations文件夹中,则启动应用程序时的当前目录将是/ Applications文件夹。并非您所期望的/Applications/foo.app/Contents/MacOS/。
您可以更改您的应用程序以解决此问题,也可以使用这个神奇的小启动器脚本来更改当前目录并启动您的应用程序。
#!/ bin / bash cd“ $ {0%/ *}” ./foo
确保调整Info.plist文件,以使CFBundleExecutable指向启动脚本,而不指向先前的可执行文件。
好的,现在全部完成。幸运的是,一旦您知道所有这些东西,便将其埋入构建脚本中。
实际上,我发现了一个非常方便的工具,值得您称颂...不,我没有开发此工具;)
https://github.com/auriamg/macdylibbundler/
它将解决所有依赖关系,并“修复”可执行文件以及dylib文件,以便在您的应用程序包中顺利运行。
...它还将检查依赖的动态库的依赖关系:D
我在我的Makefile中使用它...它创建了一个应用程序捆绑包。阅读并理解它,因为您将需要一个macosx /文件夹中的png图标文件以及我在此处包括的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
信息清单
<?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>
信息包
APPLMyAp
最简单的解决方案是:创建一个Xcode项目而不更改任何内容(即保留Xcode为您创建的简单的单窗口应用程序),构建它并复制它为您创建的包。然后,编辑文件(尤其是Info.plist)以适合您的内容,然后将自己的二进制文件放在Contents / MacOS /目录中。
我希望我能早些找到这篇文章。
这是我在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)
希望这对某人有用。