制作准备好开发者ID的macOS安装程序包

206

注意:此处仅涉及适用于 OS X Installer 软件包,提交至 Mac App Store 的软件包需要遵循不同的规则。

由于 Mountain Lion 的 Gatekeeper 功能,我最终只好把我的 PackageMaker 构建脚本送到了垃圾堆。PackageMaker 已经从 Xcode 中删除并移到“Xcode 辅助工具”中,所以希望它会被很快忘记。

问题是如何使用 pkgbuildproductbuildpkgutil 来替代它?


所以我假设 Packagemaker 的问题在于无法正确签名 pkg 文件以在 Mountain Lion 上与 Gatekeeper 一起使用? - JasonZ
1
可能是可以的,但PackageMaker总是出现各种问题,而且在Mac OS X 10.6 Snow Leopard中已经被弃用。熟悉新工具将节省您很多时间。 - catlan
2
@carleeto:它从未被宣布为废弃,只是从Xcode中删除,并最终像缅甸抗议者一样“消失”了。 - bug
虽然 PackageMaker 不再是核心 Xcode 安装的一部分,但其他许多不被弃用的辅助工具也一样不再包含在内。PackageMaker 在我使用的 10.8.2 上仍然正常运行,而我下载了 2012 年 7 月版的“Auxiliary Tools”磁盘映像。它确实被 pbxbuildprojectbuild 逐渐替代(尤其是针对 App Store),这也许是最好的选择,但说它已经死亡并不真实。(“我还没有死!”) - Quinn Taylor
5
Xcode 4.6 发布说明:弃用 Package Maker 工具。http://adcdownload.apple.com/Developer_Tools/xcode_4.6/release_notes_xcode46gm.pdf - catlan
显示剩余2条评论
5个回答

380
我们的示例项目有两个构建目标:HelloWorld.app 和 Helper.app。我们为每个目标制作一个组件包,并将它们合并成一个产品归档文件。
一个组件包包含要由 Mac OS X 安装程序安装的载荷。虽然可以单独安装组件包,但通常将其合并到产品归档文件中。
我们使用的工具是:pkgbuildproductbuildpkgutil
在成功“构建和归档”后,在终端中打开 $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),你可以在"组件属性列表"部分找到值的描述。如果您不需要更改任何默认属性,pkgbuild -root会生成组件包(component packages),则可以在以下命令中省略--component-plist参数。 productbuild --synthesize 会生成 分发定义(Distribution Definition)
$ 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,了解可能的情况。您可以使用以下命令提取“Install iTunes.pkg”:

$ pkgutil --expand "Install iTunes.pkg" "Install iTunes"

让我们把它放在一起

我通常在我的项目中有一个名为Package的文件夹,其中包括Distribution.xml、组件清单、资源和脚本等内容。

添加一个名为“生成软件包”的运行脚本构建阶段,并将其设置为仅在安装时运行脚本

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 --expandpkgutil --flatten步骤。此外,您可以在productbuild上使用--sign参数,而不是运行productsign

签署OS X安装程序

包使用Developer ID Installer证书进行签名,您可以从Developer Certificate Utility下载该证书。

签名是通过pkgbuildproductbuildproductsign--sign "Developer ID Installer: John Doe"参数完成的。

请注意,如果要使用productbuild创建已签名的产品归档文件,则没有必要对组件包进行签名。

Developer Certificate Utility

一路走来:将包复制到Xcode存档中

要将某个东西复制到Xcode存档中,我们不能使用“运行脚本构建阶段”。这时,我们需要使用方案操作。

编辑方案并展开存档。然后单击“后操作”并添加一个“新的运行脚本操作”:

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脚本添加到你的项目中,这样你就可以通过从工作区将脚本拖入后续操作来简单恢复操作。
脚本编写
有两种不同类型的脚本:分发定义文件中的JavaScript和Shell脚本。
我在WhiteBox - PackageMaker How-to中找到了关于Shell脚本的最佳文档,但请小心阅读,因为它是针对旧的包格式。
Apple Silicon
为了使包以arm64运行,分发文件必须在其hostArchitectures部分中指定它支持arm64,除了x86_64
<options hostArchitectures="arm64,x86_64" />

额外阅读

已知问题和解决方法

目标选择窗格

用户只能看到一个选项 - "为此计算机上的所有用户安装"。该选项在视觉上被选中,但用户需要单击它才能继续安装,这可能会引起一些混淆。

Example showing the installer bug

Apples Documentation建议使用<domains enable_anywhere ... />,但这会触发新的更多错误的目标选择窗格,而苹果在任何包中都不使用它。
使用弃用的<options rootVolumeOnly="true" />可以让您获得旧的目标选择窗格。 Example showing old Destination Select Pane
您想将项目安装到当前用户的主文件夹中。
简短回答:不要尝试!
详细回答:真的,不要尝试!请阅读安装程序问题和解决方案。你知道吗?即使我看过这个,我还是愚蠢地尝试了。告诉自己我确定他们在10.7或10.8中修复了这些问题。
首先,我时不时会看到上述目标选择窗格错误。那应该阻止我,但我忽略了它。如果您不想在发布软件后花一周时间回答支持电子邮件,告诉他们不要使用漂亮的蓝色选择,请不要使用此选项。
现在您可能认为您的用户足够聪明,可以解决面板问题,对吗?那么关于主文件夹安装的另一件事情是,它们不起作用!

我在大约10台不同的机器上测试了两周,涉及不同的操作系统版本等内容,从未出现失败情况。因此我发布了它。但是,在发布后一个小时内,我收到了用户的反馈,他们无法安装它。日志提示存在权限问题,你无法解决。

所以让我们再重复一遍:我们不使用安装程序进行家目录安装!


欢迎、自述、许可证和结论的 RTFD 文件不被 productbuild 接受。

从一开始,安装程序支持使用 RTFD 文件制作带有图像的漂亮欢迎屏幕,但是 productbuild 不接受它们。

解决方法: 使用虚拟 rtf 文件,并在 productbuild 完成后替换包中的文件。

注意:您还可以在 RTFD 文件中使用 Retina 图像。为此,请使用多图像 tiff 文件:tiffutil -cat Welcome.tif Welcome_2x.tif -out FinalWelcome.tif。更多 details


使用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 来完成。此外,仅在非命令行安装时运行它,使用 installer 工具或 Apple Remote Desktop 完成安装。

10
这是一篇优秀的教程,但假设预制捆绑存在。例如,如果我想将一个单独的文件安装到/tmp,以便在后续处理脚本中使用,我该如何构建组件列表?所有可用文档似乎都假定开发人员最初使用--analyze生成它。 - bug
2
请注意,通过命令行制作软件包并试图避免打包应用程序中的所有错误是毫无意义的。相反,请参阅我下面的评论,使用Stéphane Sudre的“Packages”应用程序可以为您解决所有问题! - Bram de Jong
9
“makes no sense”这句话我不同意。苹果公司维护命令行工具,而Packages是一个第三方应用程序,不受社区支持,如果苹果公司做出重大更改,则可能会中断使用。对于我来说,我宁愿了解命令行技术,这样,如果苹果公司做出重大更改,我就可以继续使用。 - Volomike
7
$ pkgbuild --root ./HelloWorld.app 是错误的(假设 .app 是一个实际的应用程序包)。pkgbuild 操作的是目标根目录,即:包含由 Xcode 工具链生成的包的文件夹。因此,pkgbuild 的参数是我们要打包的包所在文件夹的路径。如果无法正确获取这个路径,则会导致打包的内容只包含应用程序内容文件夹。它将无法安装为实际的应用程序包。组件 plist 中的提示是 RootRelativeBundlePath 条目。如果没有指定应用程序包,则说明出现了问题。 - Jonathan Mitchell
1
@ParagBafna 可以使用安装程序插件实现,但不建议这样做。当使用安装程序插件时,会出现如此处所示的警告屏幕 https://stackoverflow.com/questions/67138560/on-macos-when-adding-an-installer-plugin-to-a-package-an-alert-appears-to-the/67313453#67313453 - catlan
显示剩余17条评论

199

有一个非常有趣的应用程序,由Stéphane Sudre开发,可以为您完成所有这些工作,支持脚本编写/命令行构建,具有超级漂亮的GUI界面,而且还是免费的。可悲的是:它被称为“Packages”,在谷歌上几乎无法搜索到。

http://s.sudre.free.fr/Software/Packages/about.html

我希望在我开始手工编写我的脚本之前就已经知道了它。

Packages application screenshot


12
我无法相信这篇帖子的分数不够高。那个软件非常出色,而且支持通过命令行进行构建。 - Cesar Mendoza
1
我添加了“支持从命令行构建”的内容以反映您的评论。 - Bram de Jong
1
有人尝试使用这个工具签署软件包吗?我无法激活“设置证书”菜单项... - GTAE86
2
@user283182: 很晚的回复,你可能已经解决了,但也许这会帮助其他人-我认为你面临的问题在Mac App Store Review Guidelines中有详细说明,规则2.14:“应用必须使用Xcode中包含的苹果打包技术进行打包和提交 - 不允许使用第三方安装程序。” - elder elder
4
我希望这款应用是付费的,并且开发者会不断更新和修补。特别是如果你想快速部署你的应用程序,Packages.app 真是太棒了。我只花了3分钟来安装、阅读概述、设置我的项目并创建可安装的软件包。非常感谢 Stéphane 的辛勤劳动。 - Nikolay Christov
显示剩余11条评论

4

如果你想为一个bundle或插件创建一个软件包安装程序,那很容易:

pkgbuild --component "Color Lists.colorPicker" --install-location ~/Library/ColorPickers ColorLists.pkg

2
请注意,创建 .pkg 和创建带有欢迎界面、许可证等真正的安装程序之间存在差异。 - catlan
是的,我知道,我把它放在这里是因为我找不到有关为插件创建pkg安装程序的参考。 - gngrwzrd
这让我开始了。只是最少的来打好基础。 - uchuugaka

3

对于已接受答案的+1:

安装程序中的目标选择

如果需要在用户域和系统域之间进行域(即目标)选择,那么不要尝试<domains enable_anywhere="true">,而应该使用以下代码:

<domains enable_currentUserHome="true" enable_localSystem="true"/>

enable_currentUserHome会将应用程序安装在~/Applications/中,而enable_localSystem则允许将应用程序安装在/Application下。

我已在El Capitan 10.11.6(15G1217)中尝试过此方法,并且在一台开发机器和两个不同的虚拟机上都运行得非常完美。


这个很好用,但有一个陷阱:如果你先安装每个用户的版本,然后再安装每台机器的版本,那么安装将在用户目录中而不是机器目录中,但具有sudo权限。反之则不成立:您可以先安装每台机器的版本,然后再安装每个用户的版本,并将其放置在两个位置。 - Terje Dahl
@TerjeDahl 是的,这是因为安装后,捆绑包会被移动到先前由安装程序安装的相同捆绑包 ID 的位置(并且安装程序知道此事)。这可以通过清单文件中的某些设置来防止,但我现在记不清了。 - JamesWebbTelescopeAlien
@ PnotNP 啊。如果您能记得那些设置并回来告诉我,那就太好了! - Terje Dahl

2
这里有一个构建脚本,可以将构建根目录打包成已签名的安装程序包。

点击此处查看脚本。

#!/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

1
是的,pkgbuild会创建一个.pkg安装程序。 - Doug Richardson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接