注意:这仅适用于OS X 安装程序包,提交到Mac App Store的包遵循不同的规则。
由于 Mountain Lion 的Gatekeeper,我最终不得不将我的PackageMaker构建脚本带到谷仓后面并拍摄它。PackageMaker 已从 Xcode 中删除并移至“Xcode 辅助工具”,因此希望它很快就会被遗忘。
问题是如何使用pkgbuild
,productbuild
和pkgutil
替换它?
注意:这仅适用于OS X 安装程序包,提交到Mac App Store的包遵循不同的规则。
由于 Mountain Lion 的Gatekeeper,我最终不得不将我的PackageMaker构建脚本带到谷仓后面并拍摄它。PackageMaker 已从 Xcode 中删除并移至“Xcode 辅助工具”,因此希望它很快就会被遗忘。
问题是如何使用pkgbuild
,productbuild
和pkgutil
替换它?
我们的示例项目有两个构建目标:HelloWorld.app 和 Helper.app。我们为每个组件制作一个组件包,并将它们组合成一个产品档案。
组件包包含要由 OS X 安装程序安装的有效负载。尽管可以单独安装组件包,但它通常被合并到产品存档中。
成功“构建和存档”后,在终端中打开 $BUILT_PRODUCTS_DIR。
$ cd ~/Library/Developer/Xcode/DerivedData/.../InstallationBuildProductsLocation
$ pkgbuild --analyze --root ./HelloWorld.app HelloWorldAppComponents.plist
$ pkgbuild --analyze --root ./Helper.app HelperAppComponents.plist
这给了我们组件-plist,您可以在“组件属性列表”部分找到值描述。pkgbuild -root生成组件包,如果您不需要更改任何默认属性,您可以在以下命令中省略--component-plist参数。
productbuild --synthesize产生一个分布定义。
$ pkgbuild --root ./HelloWorld.app \
--component-plist HelloWorldAppComponents.plist \
HelloWorld.pkg
$ pkgbuild --root ./Helper.app \
--component-plist HelperAppComponents.plist \
Helper.pkg
$ productbuild --synthesize \
--package HelloWorld.pkg --package Helper.pkg \
Distribution.xml
在Distribution.xml中,您可以更改标题、背景、欢迎、自述文件、许可证等内容。您可以使用此命令将组件包和分发定义转换为产品存档:
$ productbuild --distribution ./Distribution.xml \
--package-path . \
./Installer.pkg
我建议查看iTunes Installers Distribution.xml 以了解可能的情况。您可以使用以下方法提取“安装 iTunes.pkg”:
$ pkgutil --expand "Install iTunes.pkg" "Install iTunes"
我的项目中通常有一个名为 Package 的文件夹,其中包含 Distribution.xml、组件列表、资源和脚本等内容。
添加一个名为“Generate Package”的运行脚本构建阶段,设置为仅在安装时运行脚本:
VERSION=$(defaults read "${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}/Contents/Info" CFBundleVersion)
PACKAGE_NAME=`echo "$PRODUCT_NAME" | sed "s/ /_/g"`
TMP1_ARCHIVE="${BUILT_PRODUCTS_DIR}/$PACKAGE_NAME-tmp1.pkg"
TMP2_ARCHIVE="${BUILT_PRODUCTS_DIR}/$PACKAGE_NAME-tmp2"
TMP3_ARCHIVE="${BUILT_PRODUCTS_DIR}/$PACKAGE_NAME-tmp3.pkg"
ARCHIVE_FILENAME="${BUILT_PRODUCTS_DIR}/${PACKAGE_NAME}.pkg"
pkgbuild --root "${INSTALL_ROOT}" \
--component-plist "./Package/HelloWorldAppComponents.plist" \
--scripts "./Package/Scripts" \
--identifier "com.test.pkg.HelloWorld" \
--version "$VERSION" \
--install-location "/" \
"${BUILT_PRODUCTS_DIR}/HelloWorld.pkg"
pkgbuild --root "${BUILT_PRODUCTS_DIR}/Helper.app" \
--component-plist "./Package/HelperAppComponents.plist" \
--identifier "com.test.pkg.Helper" \
--version "$VERSION" \
--install-location "/" \
"${BUILT_PRODUCTS_DIR}/Helper.pkg"
productbuild --distribution "./Package/Distribution.xml" \
--package-path "${BUILT_PRODUCTS_DIR}" \
--resources "./Package/Resources" \
"${TMP1_ARCHIVE}"
pkgutil --expand "${TMP1_ARCHIVE}" "${TMP2_ARCHIVE}"
# Patches and Workarounds
pkgutil --flatten "${TMP2_ARCHIVE}" "${TMP3_ARCHIVE}"
productsign --sign "Developer ID Installer: John Doe" \
"${TMP3_ARCHIVE}" "${ARCHIVE_FILENAME}"
如果您不必在使用productbuild生成包后更改包,则可以摆脱pkgutil --expand
和pkgutil --flatten
步骤。您也可以在productbuild上使用--sign参数,而不是运行productsign。
软件包使用Developer ID Installer证书进行签名,您可以从Developer Certificate Utility下载该证书。
他们的签名是使用pkgbuild、productbuild或productsign--sign "Developer ID Installer: John Doe"
的参数完成的。
请注意,如果您要使用 productbuild 创建已签名的产品存档,则没有理由对组件包进行签名。
要将某些内容复制到 Xcode 存档中,我们不能使用Run Script Build Phase。为此,我们需要使用方案动作。
编辑方案并展开存档。然后单击 post-actions 并添加一个New Run Script Action:
在 Xcode 6 中:
#!/bin/bash
PACKAGES="${ARCHIVE_PATH}/Packages"
PACKAGE_NAME=`echo "$PRODUCT_NAME" | sed "s/ /_/g"`
ARCHIVE_FILENAME="$PACKAGE_NAME.pkg"
PKG="${OBJROOT}/../BuildProductsPath/${CONFIGURATION}/${ARCHIVE_FILENAME}"
if [ -f "${PKG}" ]; then
mkdir "${PACKAGES}"
cp -r "${PKG}" "${PACKAGES}"
fi
在 Xcode 5 中,使用这个值PKG
代替:
PKG="${OBJROOT}/ArchiveIntermediates/${TARGET_NAME}/BuildProductsPath/${CONFIGURATION}/${ARCHIVE_FILENAME}"
如果您的版本控制不存储 Xcode 方案信息,我建议将其作为 shell 脚本添加到您的项目中,这样您就可以通过将脚本从工作区拖到后期操作中来简单地恢复操作。
有两种不同类型的脚本:分发定义文件中的 JavaScript和 Shell 脚本。
我在WhiteBox - PackageMaker How-to中找到了关于 Shell 脚本的最佳文档,但请谨慎阅读,因为它引用了旧的包格式。
为了使包作为 arm64 运行,分发文件必须在其hostArchitectures
部分中指定它支持arm64
除了x86_64
:
<options hostArchitectures="arm64,x86_64" />
目的地选择窗格
向用户呈现的目的地选择选项只有一个选项——“为此计算机的所有用户安装”。该选项在视觉上显示为选中,但用户需要单击它才能继续安装,这会造成一些混乱。
Apples 文档建议使用<domains enable_anywhere ... />
,但这会触发新的更多错误目的地选择窗格,Apple 不会在其任何包中使用该窗格。
使用弃用<options rootVolumeOnly="true" />
会为您提供旧的目标选择窗格。
您想将项目安装到当前用户的主文件夹中。
简短的回答:不要尝试!
长答案:真的;不要尝试!阅读安装程序问题和解决方案。你知道我在读完这篇文章后做了什么吗?我傻到尝试了。告诉自己我确定他们在 10.7 或 10.8 中解决了这些问题。
首先,我不时看到上面提到的目的地选择窗格错误。那应该阻止我,但我忽略了它。如果您不想在发布软件后的一周内回复支持电子邮件,他们必须单击漂亮的蓝色选项,不要使用此选项。
您现在认为您的用户足够聪明,可以弄清楚面板,不是吗?那么这是关于主文件夹安装的另一件事,它们不起作用!
我在大约 10 台具有不同操作系统版本的不同机器上对它进行了两周的测试,并且从未失败过。所以我发货了。在发布后的一个小时内,我从无法安装它的用户那里得到了回复。日志提示您无法修复的权限问题。
所以让我们再重复一遍:我们不使用安装程序来安装主文件夹!
用于欢迎、自述、许可和结论的 RTFD 不被productbuild
.
安装程序从一开始就支持 RTFD 文件,以制作带有图像的漂亮欢迎屏幕,但 productbuild 不接受它们。
解决方法:使用虚拟 rtf 文件并在包中将其替换为 after productbuild
is done。
注意:您还可以在 RTFD 文件中包含 Retina 图像。为此使用多图像 tiff 文件:tiffutil -cat Welcome.tif Welcome_2x.tif -out FinalWelcome.tif
. 更多细节。
使用BundlePostInstallScriptPath脚本完成安装后启动应用程序:
#!/bin/bash
LOGGED_IN_USER_ID=`id -u "${USER}"`
if [ "${COMMAND_LINE_INSTALL}" = "" ]
then
/bin/launchctl asuser "${LOGGED_IN_USER_ID}" /usr/bin/open -g PATH_OR_BUNDLE_ID
fi
exit 0
以登录用户身份运行应用程序很重要,而不是以安装程序用户身份运行。这是通过launchctl asuser uid path完成的。此外,我们仅在不是命令行安装时才运行它,使用安装程序工具或Apple Remote Desktop完成。
Stéphane Sudre 有一个非常有趣的应用程序,它可以为您完成所有这些工作,可编写脚本/支持从命令行构建,具有超级漂亮的 GUI 并且是免费的。可悲的是:它被称为“包”,这使得它无法在谷歌中找到。
http://s.sudre.free.fr/Software/Packages/about.html
我希望我在开始手工制作自己的脚本之前就已经知道了。
对于那些试图为包或插件创建包安装程序的人来说,这很容易:
pkgbuild --component "Color Lists.colorPicker" --install-location ~/Library/ColorPickers ColorLists.pkg
A +1 接受答案:
安装程序中的目标选择
如果在用户域和系统域之间需要域(又名目标)选择,那么不要尝试<domains enable_anywhere="true">
使用以下内容:
<domains enable_currentUserHome="true" enable_localSystem="true"/>
enable_currentUserHome安装应用app下~/Applications/
,enable_localSystem
允许应用安装在下/Application
我已经在 El Capitan 10.11.6 (15G1217) 中尝试过这个,它似乎在我尝试的 1 台开发机器和 2 台不同的 VM 中运行良好。
这是一个构建脚本,它从构建根目录中创建一个签名的安装程序包。
#!/bin/bash
# TRIMCheck build script
# Copyright Doug Richardson 2015
# Usage: build.sh
#
# The result is a disk image that contains the TRIMCheck installer.
#
DSTROOT=/tmp/trimcheck.dst
SRCROOT=/tmp/trimcheck.src
INSTALLER_PATH=/tmp/trimcheck
INSTALLER_PKG="TRIMCheck.pkg"
INSTALLER="$INSTALLER_PATH/$INSTALLER_PKG"
#
# Clean out anything that doesn't belong.
#
echo Going to clean out build directories
rm -rf build $DSTROOT $SRCROOT $INSTALLER_PATH
echo Build directories cleaned out
#
# Build
#
echo ------------------
echo Installing Sources
echo ------------------
xcodebuild -project TRIMCheck.xcodeproj installsrc SRCROOT=$SRCROOT || exit 1
echo ----------------
echo Building Project
echo ----------------
pushd $SRCROOT
xcodebuild -project TRIMCheck.xcodeproj -target trimcheck -configuration Release install || exit 1
popd
echo ------------------
echo Building Installer
echo ------------------
mkdir -p "$INSTALLER_PATH" || exit 1
echo "Runing pkgbuild. Note you must be connected to Internet for this to work as it"
echo "has to contact a time server in order to generate a trusted timestamp. See"
echo "man pkgbuild for more info under SIGNED PACKAGES."
pkgbuild --identifier "com.delicioussafari.TRIMCheck" \
--sign "Developer ID Installer: Douglas Richardson (4L84QT8KA9)" \
--root "$DSTROOT" \
"$INSTALLER" || exit 1
echo Successfully built TRIMCheck
open "$INSTALLER_PATH"
exit 0