macos 构建 OSX 应用程序包
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1596945/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Building OSX App Bundle
提问by Yogi
Suppose I have have made a an osX app without using Xcode. After compiling with GCC I get an executable which is linked to several other libraries. Some of those libraries might again be dynamically linked to other non-standard system libraries
假设我在不使用 Xcode 的情况下制作了一个 osX 应用程序。用 GCC 编译后,我得到一个链接到其他几个库的可执行文件。其中一些库可能会再次动态链接到其他非标准系统库
Is there any tool which exists which makes an OSX App bundle by first making the required directory structures and then recursively copying/checking/fixing links to make sure all the dynamic dependencies are also in the app bundle?
是否有任何工具可以通过首先创建所需的目录结构然后递归复制/检查/修复链接以确保所有动态依赖项也在应用程序包中来制作 OSX 应用程序包?
I guess I can try writing something like this but I was wondering if something like this exists already.
我想我可以尝试写这样的东西,但我想知道是否已经存在这样的东西。
回答by hyperlogic
There are two ways to create an app bundle on MacOSX, the Easy and the Ugly.
有两种方法可以在 MacOSX 上创建应用程序包,Easy 和 Ugly。
The easy way is just to use XCode. Done.
简单的方法就是使用 XCode。完毕。
The problem is sometimes you can't.
问题是有时你不能。
In my case I'm building an app that builds other apps. I can't assume the user has XCode installed. I'm also using MacPortsto build the libraries my app depends on. I need to make sure that these dylibs get bundled with the app before I distribute it.
就我而言,我正在构建一个构建其他应用程序的应用程序。我不能假设用户安装了 XCode。我还使用MacPorts来构建我的应用程序所依赖的库。在分发应用程序之前,我需要确保这些 dylib 与应用程序捆绑在一起。
Disclaimer:I'm totally unqualified to write this post, everything in is has been gleamed from Apple docs, picking apart existing apps and trial and error. It works for me, but is most likely wrong. Please email me if you have any corrections.
免责声明:我完全没有资格写这篇文章,所有内容都来自 Apple 文档,挑选了现有的应用程序和反复试验。它对我有用,但很可能是错误的。如果您有任何更正,请给我发电子邮件。
First thing you should know is that an app bundle is just a directory.
Let's examine the structure of a hypothetical foo.app.
您应该知道的第一件事是应用程序包只是一个目录。
让我们检查一个假设的 foo.app 的结构。
foo.app/ Contents/ Info.plist MacOS/ foo Resources/ foo.icns
Info.plist is a plain XML file. You can edit it with a text editor or the Property List Editor app that comes bundled with XCode. (It's in /Developer/Applications/Utilities/ directory).
Info.plist 是一个纯 XML 文件。您可以使用文本编辑器或与 XCode 捆绑在一起的属性列表编辑器应用程序对其进行编辑。(它在 /Developer/Applications/Utilities/ 目录中)。
The key things you need to include are:
您需要包括的关键内容是:
CFBundleName- The name of the app.
CFBundleName- 应用程序的名称。
CFBundleIcon- An Icon file assumed to be in Contents/Resources dir. Use the Icon Composer app to create the icon. (It's also in the /Developer/Applications/Utilities/ directory) You can just drag and drop a png onto it's window and should automatically generate the mip-levels for you.
CFBundleIcon- 假定位于 Contents/Resources 目录中的图标文件。使用 Icon Composer 应用程序创建图标。(它也在 /Developer/Applications/Utilities/ 目录中)您只需将一个 png 拖放到它的窗口中,就会自动为您生成 mip 级别。
CFBundleExecutable- Name of the executable file assumed to be in Contents/MacOS/ sub-folder.
CFBundleExecutable- 假定位于 Contents/MacOS/ 子文件夹中的可执行文件的名称。
There are lots more options, the ones listed above are only the bare minimum. Here's some Apple documentation on the Info.plistfile and App bundle structure.
还有更多的选择,上面列出的只是最低限度的选择。这是有关Info.plist文件和 App bundle structure的一些 Apple 文档 。
Also, Here's a sample 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>
In a perfect world you could just drop your executable into the Contents/MacOS/ dir and be done. However, if your app has any non-standard dylib dependencies it won't work. Like Windows, MacOS comes with it's own special kind of DLL Hell.
在完美的世界中,您只需将可执行文件放入 Contents/MacOS/ 目录即可完成。但是,如果您的应用程序有任何非标准的 dylib 依赖项,它将无法工作。与 Windows 一样,MacOS 也带有它自己特殊的DLL Hell 类型。
If you're using MacPortsto build libraries that you link against, the locations of the the dylibs will be hard-coded into your executable. If you run the app on a machine that has the dylibs in the exact same location, it will run fine. However, most users won't have them installed; when they double-click your app it will just crash.
如果您使用MacPorts构建链接的库,则 dylib 的位置将被硬编码到您的可执行文件中。如果您在具有完全相同位置的 dylib 的机器上运行该应用程序,它将运行良好。但是,大多数用户不会安装它们;当他们双击您的应用程序时,它只会崩溃。
Before you distribute you executable you'll need to collect all the dylibs it loads and copy them into the app bundle. You will also need to edit the executable so that it will look for the dylibs in the correct place. i.e. where you copied them to.
在分发可执行文件之前,您需要收集它加载的所有 dylib 并将它们复制到应用程序包中。您还需要编辑可执行文件,以便它在正确的位置查找 dylib。即您将它们复制到的位置。
Hand editing an executable sounds dangerous right? Luckily there are command line tools to help.
手动编辑可执行文件听起来很危险吧?幸运的是,有命令行工具可以提供帮助。
otool -L executable_name
This command will list all the dylibs that your app depends on. If you see any that are NOT in the System/Library or usr/lib folder, those are the ones you'll need to copy into the app bundle. Copy them into the /Contents/MacOS/ folder. Next you'll need to edit the executable to use the new dylibs.
此命令将列出您的应用程序依赖的所有 dylib。如果您看到不在 System/Library 或 usr/lib 文件夹中的任何内容,则您需要将这些内容复制到应用程序包中。将它们复制到 /Contents/MacOS/ 文件夹中。接下来,您需要编辑可执行文件以使用新的 dylib。
First, you need to make sure that you link using the -headerpad_max_install_names flag. This just makes sure that if the new dylib path is longer then the previous one, there will be room for it.
首先,您需要确保使用 -headerpad_max_install_names 标志进行链接。这只是确保如果新的 dylib 路径比前一个路径长,就会有空间。
Second, use the install_name_tool to change each dylib path.
其次,使用 install_name_tool 更改每个 dylib 路径。
install_name_tool -change existing_path_to_dylib @executable_path/blah.dylib executable_name
As a practical example, Let's say your app uses libSDL, and otool lists it's location as "/opt/local/lib/libSDL-1.2.0.dylib".
作为一个实际示例,假设您的应用程序使用libSDL,并且 otool 将其位置列为“/opt/local/lib/libSDL-1.2.0.dylib”。
First copy it into the app bundle.
首先将其复制到应用程序包中。
cp /opt/local/lib/libSDL-1.2.0.dylib foo.app/Contents/MacOS/
Then edit the executable to use the new location (NOTE: make sure you built it with the -headerpad_max_install_names flag)
然后编辑可执行文件以使用新位置(注意:确保使用 -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
Whew, we're almost done. Now there's a small issue with the current working directory.
哇,我们快完成了。现在当前工作目录有一个小问题。
When you start your app the current directory will be the directory above where the application is located. For example: If you place the foo.app in the /Applcations folder then the current directory when you launch the app will be the /Applications folder. Not the /Applications/foo.app/Contents/MacOS/ as you might expect.
当您启动应用程序时,当前目录将是应用程序所在的目录。例如:如果您将 foo.app 放在 /Applcations 文件夹中,则启动应用程序时的当前目录将是 /Applications 文件夹。不是您所期望的 /Applications/foo.app/Contents/MacOS/。
You can alter your app to account for this, or you can use this magic little launcher script that will change the current directory and launch your app.
您可以更改您的应用程序以解决此问题,或者您可以使用这个神奇的小启动器脚本来更改当前目录并启动您的应用程序。
#!/bin/bash cd "${0%/*}" ./foo
Make sure you adjust the Info.plist file so that CFBundleExecutablepoints to the launch script and not to the previous executable.
确保调整 Info.plist 文件,使CFBundleExecutable指向启动脚本而不是前一个可执行文件。
Ok, all done now. Luckily, once you know all this stuff you bury it in a build script.
好了,现在一切都完成了。幸运的是,一旦您了解了所有这些内容,您就可以将其隐藏在构建脚本中。
回答by Chris
I actually found a very handy tool that deserves some credit ... NO - I did not develop this ;)
我实际上找到了一个非常方便的工具,值得称赞......不 - 我没有开发这个;)
https://github.com/auriamg/macdylibbundler/
https://github.com/auriamg/macdylibbundler/
It will resolve all the dependencies and "fix" your executable as well as your dylib files to work smoothly in your app bundle.
它将解决所有依赖项并“修复”您的可执行文件以及您的 dylib 文件,以便在您的应用程序包中顺利运行。
... it will also check for the dependencies of your dependent dynamic libs :D
...它还将检查您依赖的动态库的依赖项:D
回答by subatomic
I use this in my Makefile... It creates an app bundle. Read it and understand it, because you'll need a png icon file in a macosx/ folder along with the PkgInfo and Info.plist files i include here...
我在我的 Makefile 中使用它...它创建了一个应用程序包。阅读并理解它,因为您需要 macosx/ 文件夹中的 png 图标文件以及我在此处包含的 PkgInfo 和 Info.plist 文件...
"it works on my computer"... I use this for multiple apps on Mavericks...
“它适用于我的电脑”......我将它用于 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/[email protected]
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/[email protected]
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/[email protected]
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/[email protected]
sips -z 512 512 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_512x512.png
cp macosx/$(APPNAME)Icon.png macosx/$(APPNAME).iconset/[email protected]
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
回答by F'x
The simplest solution is: create once an Xcode project without changing anything (i.e. keep the simple one-window app that Xcode creates for you), build it, and copy the bundle it created for you. Then, edit the files (notably the Info.plist) to suit your content, and put your own binary in the Contents/MacOS/ directory.
最简单的解决方案是:在不更改任何内容的情况下创建一次 Xcode 项目(即保留 Xcode 为您创建的简单单窗口应用程序),构建它,然后复制它为您创建的包。然后,编辑文件(特别是 Info.plist)以适合您的内容,并将您自己的二进制文件放在 Contents/MacOS/ 目录中。
回答by Ned Deily
There are some open source tools to help build app bundles with dependent libraries for specific environments, for example, py2appfor Python-based applications. If you don't find a more general one, perhaps you can adapt it to your needs.
有一些开源工具可以帮助构建具有特定环境依赖库的应用程序包,例如,用于基于 Python 的应用程序的py2app。如果您没有找到更通用的,也许您可以根据自己的需要进行调整。
回答by peetonn
I wish I've found this post earlier....
我希望我早点找到这篇文章......
Here's my sketchy way of solving this problem using a Run script
phase which is invoked every time I build a Release
version of my app:
这是我使用一个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=
local prefix=
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=
local prefix=
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)
Hope this might be useful to someone.
希望这可能对某人有用。