注意:这仅适用于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中工作得很好。