注意:这仅适用于OS X安装程序包,提交到Mac应用程序商店的包遵循不同的规则。
因为《Mountain Lion’s Gatekeeper》,我最终不得不将PackageMaker构建脚本带到谷仓后面并拍摄它。PackageMaker已经从Xcode中移除,并移到了“Xcode的辅助工具”中,所以希望它能很快被遗忘。
问题是如何使用pkgbuild、productbuild和pkgutil来替换它?
注意:这仅适用于OS X安装程序包,提交到Mac应用程序商店的包遵循不同的规则。
因为《Mountain Lion’s Gatekeeper》,我最终不得不将PackageMaker构建脚本带到谷仓后面并拍摄它。PackageMaker已经从Xcode中移除,并移到了“Xcode的辅助工具”中,所以希望它能很快被遗忘。
问题是如何使用pkgbuild、productbuild和pkgutil来替换它?
我们的示例项目有两个构建目标:HelloWorld。app和help .app。我们为每个组件制作一个组件包,并将它们组合成一个产品存档。
组件包包含要由OS X安装程序安装的有效负载。尽管一个组件 软件包可以自行安装,它通常被合并到产品存档中。
我们的工具:pkgbuild、productbuild和pkgutil
成功地“构建和存档”后,在终端中打开$BUILT_PRODUCTS_DIR。
$ cd ~/Library/Developer/Xcode/DerivedData/.../InstallationBuildProductsLocation
$ pkgbuild --analyze --root ./HelloWorld.app HelloWorldAppComponents.plist
$ pkgbuild --analyze --root ./Helper.app HelperAppComponents.plist
这给了我们Component -plist,你可以在“Component Property List”部分找到值描述。Pkgbuild -root生成组件包,如果你不需要改变任何默认属性,你可以在下面的命令中省略——component-plist参数。
productbuild——在分布定义中合成结果。
$ 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”。包裹”:
$ pkgutil --expand "Install iTunes.pkg" "Install iTunes"
让我们把它放在一起
我的项目中通常有一个名为Package的文件夹,其中包括Distribution.xml、component-plists、资源和脚本。
添加一个名为“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。
签署一个OS X安装程序
包使用开发人员ID安装程序证书进行签名,您可以从开发人员证书实用程序下载该证书。
签名是通过pkgbuild、productbuild或productsign的——sign“Developer ID Installer: John Doe”参数完成的。
请注意,如果您打算使用productbuild创建已签名的产品存档,则没有理由对组件包进行签名。
复制包到Xcode存档
要复制一些东西到Xcode存档,我们不能使用运行脚本构建阶段。为此,我们需要使用Scheme Action。
编辑方案并展开存档。然后单击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 Scheme信息,我建议将它作为shell脚本添加到你的项目中,这样你就可以通过将脚本从工作空间拖到post-action中来简单地恢复操作。
脚本
有两种不同类型的脚本:分发定义文件中的JavaScript和Shell脚本。
我在WhiteBox - PackageMaker How-to中找到了关于Shell脚本的最好的文档,但请谨慎阅读,因为它引用了旧的包格式。
苹果硅
为了让这个包以arm64的形式运行,分发文件必须在它的hostararchitectures部分中指定它除了支持x86_64之外还支持arm64:
<options hostArchitectures="arm64,x86_64" />
更多的阅读
扁平包格式-缺少的文档 安装问题及解决方案 使用pkgbuild的愚蠢技巧 坚持过时
已知问题和解决方法
目标选择窗格
用户看到的目标选择选项只有一个选项——“为这台计算机的所有用户安装”。该选项在视觉上显示为已选中,但用户需要单击它才能继续安装,这造成了一些混乱。
苹果文档建议使用<domains enable_anywhere…/>,但这会触发新的更多错误的目的地选择窗格,苹果不会在任何他们的包中使用。
使用过时的<options rootVolumeOnly="true" />给你旧的目的地选择窗格。
您希望将项目安装到当前用户的主文件夹中。
简单的回答:不要尝试!
长话短说:真的;不要尝试!阅读安装程序问题和解决方案。你知道我看完之后做了什么吗?我蠢到去尝试了。告诉自己,我确信他们在10.7或10.8中修复了这些问题。
首先,我不时看到上面提到的目标选择窗格错误。这应该阻止我,但我没有理会。如果你不想在发布软件后花一周的时间回复支持邮件,他们必须点击一次漂亮的蓝色选择,不要使用这个。
您现在认为您的用户足够聪明,能够理解面板,是吗?好吧,这里是另一件关于home文件夹安装的事情,他们不工作!
我在大约10台不同的机器上用不同的操作系统版本测试了两周,它从未失败过。所以我把它发了出去。发布后不到一个小时,我就收到了无法安装的用户反馈。日志提示了您无法修复的权限问题。
所以让我们再重复一遍:我们不使用安装程序来安装主文件夹!
产品版本不接受欢迎、读取、许可和结论的RTFD。
安装程序从一开始就支持RTFD文件来制作带有图像的漂亮的欢迎屏幕,但productbuild不接受它们。
解决方法: 使用一个虚拟的rtf文件,并在productbuild完成后将其替换到包中。
注意:你也可以在RTFD文件中有视网膜图像。使用多图像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完成的。此外,我们只运行它时,它不是一个命令行安装,完成安装工具或苹果远程桌面。
Stéphane Sudre有一个非常有趣的应用程序,它为你做了所有这些,是可脚本化的/支持从命令行构建,有一个超级好的GUI,是免费的。可悲的是:它被称为“包”,这使得它不可能在谷歌中找到。
http://s.sudre.free.fr/Software/Packages/about.html
我希望我在开始手工编写自己的脚本之前就知道这一点。
下面是一个构建脚本,它从构建根目录创建一个签名安装包。
#!/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
对于那些试图为包或插件创建包安装程序的人来说,这很简单:
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将应用程序安装在~/Applications/下,enable_localSystem允许应用程序安装在/ application下
我已经在El Capitan 10.11.6 (15G1217)中尝试了这一点,它似乎在1台开发机器和2个不同的vm中工作得很好。