xcode 制作准备好开发人员 ID 的 macOS 安装程序包

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11487596/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 04:29:19  来源:igfitidea点击:

Making macOS Installer Packages which are Developer ID ready

xcodepackagemakerpkgbuildproductbuild

提问by catlan

Note: This is for OS X Installerpackages only, packages for submission to the Mac App Storefollow different rules.

注意:这仅适用于OS X 安装程序包,提交到Mac App Store 的包遵循不同的规则。

Because of Mountain Lion's GatekeeperI finally had to take my PackageMakerbuild script behind the barn and shoot it. PackageMaker was already removed from Xcode and moved into "Auxiliary Tools for Xcode", so hopefully it will be soon forgotten.

由于 Mountain Lion 的守门人,我最终不得不将我的PackageMaker构建脚本带到谷仓后面并拍摄。PackageMaker 已经从 Xcode 中删除并移到“Xcode 的辅助工具”中,所以希望它很快就会被遗忘。

The question is how do I use pkgbuild, productbuild, and pkgutilto replace it?

现在的问题是我怎么用pkgbuildproductbuildpkgutil来代替它?

回答by catlan

Our example project has two build targets: HelloWorld.app and Helper.app. We make a component packagefor each and combine them into a product archive.

我们的示例项目有两个构建目标:HelloWorld.app 和 Helper.app。我们为每个组件制作一个组件包并将它们组合成一个产品档案

A component packagecontains payload to be installed by the OS X Installer. Although a component package can be installed on its own, it is typically incorporated into a product archive.

组件包包含净荷由OS X安装进行安装。虽然组件包可以单独安装,但它通常被合并到产品档案中

Our tools: pkgbuild, productbuild, and pkgutil

我们的工具:pkgbuildproductbuildpkgutil

After a successful "Build and Archive" open $BUILT_PRODUCTS_DIR in the Terminal.

成功“构建和存档”后,在终端中打开 $BUILT_PRODUCTS_DIR。

$ cd ~/Library/Developer/Xcode/DerivedData/.../InstallationBuildProductsLocation
$ pkgbuild --analyze --root ./HelloWorld.app HelloWorldAppComponents.plist
$ pkgbuild --analyze --root ./Helper.app HelperAppComponents.plist

This give us the component-plist, you find the value description in the "Component Property List"section. pkgbuild -rootgenerates the component packages, if you don't need to change any of the default properties you can omit the --component-plistparameter in the following command.

这为我们提供了组件 plist,您可以在“组件属性列表”部分找到值描述。pkgbuild -root生成组件包,如果您不需要更改任何默认属性,则可以省略以下命令中的--component-plist参数。

productbuild --synthesizeresults in a Distribution Definition.

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 

In the Distribution.xmlyou can change things like title, background, welcome, readme, license, and so on. You turn your component packagesand distribution definition with this command into a product archive:

Distribution.xml 中,您可以更改标题、背景、欢迎、自述、许可证等内容。您可以使用此命令将组件包和分发定义转换为产品档案

$ productbuild --distribution ./Distribution.xml \
    --package-path . \
    ./Installer.pkg

I recommend to take a look at iTunes InstallersDistribution.xml to see what is possible. You can extract "Install iTunes.pkg" with:

我建议查看iTunes InstallersDistribution.xml 以了解可能的情况。您可以使用以下命令提取“安装 iTunes.pkg”:

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

Lets put it together

让我们把它放在一起

I usually have a folder named Package in my project which includes things like Distribution.xml, component-plists, resources and scripts.

我的项目中通常有一个名为 Package 的文件夹,其中包含 Distribution.xml、component-plist、资源和脚本等内容。

Add a Run Script Build Phasenamed "Generate Package", which is set to Run script only when installing:

添加一个名为“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}"

If you don't have to change the package after it's generated with productbuildyou could get rid of the pkgutil --expandand pkgutil --flattensteps. Also you could use the --signparamenter on productbuildinstead of running productsign.

如果在使用productbuild生成包后不必更改包,则可以去掉pkgutil --expandpkgutil --flatten步骤。您也可以在productbuild上使用--sign参数而不是运行productsign

Sign an OS X Installer

签署 OS X 安装程序

Packages are signed with the Developer ID Installercertificate which you can download from Developer Certificate Utility.

软件包使用Developer ID 安装程序证书签名,您可以从Developer Certificate Utility下载该证书

They signing is done with the --sign "Developer ID Installer: John Doe"parameter of pkgbuild, productbuildor productsign.

他们的签名是使用pkgbuildproductbuildproductsign--sign "Developer ID Installer: John Doe"参数完成的。

Note that if you are going to create a signed product archiveusing productbuild, there is no reason to sign the component packages.

请注意,如果您要使用 productbuild创建签名的产品存档,则没有理由对组件包进行签名。

Developer Certificate Utility

开发人员证书实用程序

All the way: Copy Package into Xcode Archive

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

To copy something into the Xcode Archive we can't use the Run Script Build Phase. For this we need to use a Scheme Action.

要将某些内容复制到 Xcode Archive,我们不能使用Run Script Build Phase。为此,我们需要使用 Scheme Action。

Edit Scheme and expand Archive. Then click post-actions and add a New Run Script Action:

编辑方案并展开存档。然后点击 post-actions 并添加一个New Run Script Action

In Xcode 6:

在 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

In Xcode 5, use this value for PKGinstead:

在 Xcode 5 中,使用此值PKG代替:

PKG="${OBJROOT}/ArchiveIntermediates/${TARGET_NAME}/BuildProductsPath/${CONFIGURATION}/${ARCHIVE_FILENAME}"

In case your version control doesn't store Xcode Scheme information I suggest to add this as shell script to your project so you can simple restore the action by dragging the script from the workspace into the post-action.

如果您的版本控制不存储 Xcode Scheme 信息,我建议将其作为 shell 脚本添加到您的项目中,以便您可以通过将脚本从工作区拖到后期操作中来简单地恢复操作。

Scripting

脚本编写

There are two different kinds of scripting: JavaScript in Distribution Definition Filesand Shell Scripts.

有两种不同类型的脚本:分发定义文件中的 JavaScript和 Shell 脚本。

The best documentation about Shell Scripts I found in WhiteBox - PackageMaker How-to, but read this with caution because it refers to the old package format.

我在WhiteBox - PackageMaker How-to 中找到的有关 Shell 脚本的最佳文档,但请谨慎阅读,因为它指的是旧的包格式。

Additional Reading

补充阅读

Known Issues and Workarounds

已知问题和解决方法

Destination Select Pane

目的地选择窗格

The user is presented with the destination select option with only a single choice - "Install for all users of this computer". The option appears visually selected, but the user needs to click on it in order to proceed with the installation, causing some confusion.

用户将看到目标选择选项,只有一个选择 - “为此计算机的所有用户安装”。该选项在视觉上显示为选中状态,但用户需要单击它才能继续安装,这会造成一些混乱。

Example showing the installer bug

显示安装程序错误的示例

Apples Documentation recommends to use <domains enable_anywhere ... />but this triggers the new more buggy Destination Select Pane which Apple doesn't use in any of their Packages.

Apples 文档建议使用,<domains enable_anywhere ... />但这会触发新的更多错误的 Destination Select Pane,Apple 不会在其任何包中使用该窗格。

Using the deprecate <options rootVolumeOnly="true" />give you the old Destination Select Pane. Example showing old Destination Select Pane

使用 deprecate<options rootVolumeOnly="true" />为您提供旧的 Destination Select Pane。 显示旧目标选择窗格的示例



You want to install items into the current user's home folder.

您想将项目安装到当前用户的主文件夹中。

Short answer: DO NOT TRY IT!

简短回答:不要尝试!

Long answer: REALLY; DO NOT TRY IT! Read Installer Problems and Solutions. You know what I did even after reading this? I was stupid enough to try it. Telling myself I'm sure that they fixed the issues in 10.7 or 10.8.

长答案:真的;不要尝试!阅读安装程序问题和解决方案。你知道我在读完这篇文章后做了什么吗?我笨到试一试。告诉自己我确信他们在 10.7 或 10.8 中解决了这些问题。

First of all I saw from time to time the above mentioned Destination Select Pane Bug. That should have stopped me, but I ignored it. If you don't want to spend the week after you released your software answering support e-mails that they have to click once the nice blue selection DO NOT use this.

首先,我不时看到上面提到的 Destination Select Pane Bug。那应该阻止我,但我忽略了它。如果您不想在发布软件后的一周内回复支持电子邮件,他们必须在漂亮的蓝色选项中点击一次,请不要使用它。

You are now thinking that your users are smart enough to figure the panel out, aren't you? Well here is another thing about home folder installation, THEY DON'T WORK!

您现在认为您的用户足够聪明,可以找出面板,不是吗?好吧,这是关于主文件夹安装的另一件事,它们不起作用!

I tested it for two weeks on around 10 different machines with different OS versions and what not, and it never failed. So I shipped it. Within an hour of the release I heart back from users who just couldn't install it. The logs hinted to permission issues you are not gonna be able to fix.

我在大约 10 台具有不同操作系统版本的不同机器上对其进行了两周的测试,并且从未失败过。所以我发货了。在发布后的一个小时内,我收到了无法安装它的用户的回复。日志暗示了您无法修复的权限问题。

So let's repeat it one more time: We do not use the Installer for home folder installations!

所以让我们再重复一遍:我们不使用安装程序来安装主文件夹!



RTFD for Welcome, Read-me, License and Conclusion is not accepted by productbuild.

用于欢迎、自述、许可和结论的 RTFD 不被productbuild.

Installer supported since the beginning RTFD files to make pretty Welcome screens with images, but productbuild doesn't accept them.

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

Workarounds: Use a dummy rtf file and replace it in the package by after productbuildis done.

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

Note: You can also have Retina images inside the RTFD file. Use multi-image tiff files for this: tiffutil -cat Welcome.tif Welcome_2x.tif -out FinalWelcome.tif. More details.

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



Starting an application when the installation is done with a BundlePostInstallScriptPathscript:

使用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

It is important to run the app as logged in user, not as the installer user. This is done with launchctl asuser uid path. Also we only run it when it is not a command line installation, done with installertool or Apple Remote Desktop.

以登录用户的身份运行应用程序很重要,而不是安装程序用户。这是通过launchctl asuser uid path 完成的。此外,我们仅在它不是命令行安装时才运行它,使用安装程序工具或Apple Remote Desktop 完成



回答by Bram de Jong

There is one very interesting application by Stéphane Sudre which does all of this for you, is scriptable / supports building from the command line, has a super nice GUI and is FREE. Sad thing is: it's called "Packages" which makes it impossible to find in google.

Stéphane Sudre 有一个非常有趣的应用程序,它可以为您完成所有这些工作,可编写脚本/支持从命令行构建,具有超级漂亮的 GUI 并且是免费的。可悲的是:它被称为“包”,这使得它无法在谷歌中找到。

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

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

I wished I had known about it before I started handcrafting my own scripts.

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

Packages application screenshot

包应用截图

回答by gngrwzrd

FYI for those that are trying to create a package installer for a bundle or plugin, it's easy:

对于那些试图为包或插件创建包安装程序的人来说,这很简单:

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

回答by PnotNP

A +1 to accepted answer:

接受答案的+1:

Destination Selection in Installer

安装程序中的目的地选择

If domain (a.k.a destination) selection is desired between user domain and system domain then rather than trying <domains enable_anywhere="true">use following:

如果在用户域和系统域之间需要域(又名目标)选择,则不要尝试<domains enable_anywhere="true">使用以下内容:

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

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

enable_currentUserHomeinstalls application app under ~/Applications/and enable_localSystemallows the application to be installed under /Application

enable_currentUserHome 下安装应用程序~/Applications/enable_localSystem允许应用程序安装在/Application

I've tried this in El Capitan 10.11.6 (15G1217) and it seems to be working perfectly fine in 1 dev machine and 2 different VMs I tried.

我已经在 El Capitan 10.11.6 (15G1217) 中尝试过这个,它似乎在我尝试过的 1 台开发机器和 2 台不同的 VM 中工作得很好。

回答by Doug Richardson

Here is a build scriptwhich creates a signed installer package out of a build root.

这是一个构建脚本,它从构建根目录中创建一个签名的安装程序包。

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