打包(macOS,Rust)
分发 macOS 应用有两条主要途径:Mac App Store 和直接分发。 Slint 通过生成可与 Apple 构建和分发工具配合使用的标准 Xcode 项目来支持这两条途径。
- 一台已安装 Xcode 的 Mac。
- XcodeGen。
- Rust 以及你为其构建的 macOS 目标,例如
aarch64-apple-darwin。 - 用于签名、公证和 Mac App Store 分发的 Apple Developer Program 会员资格。
原生 Xcode 项目支持
Section titled “原生 Xcode 项目支持”要为 Apple 生态系统构建和分发,你需要一个 Xcode 项目。 使用 XcodeGen 从 YAML 规范生成一个, 这样你就不必同时维护 Xcode 项目和你自己的项目文件。 它会创建一个与 Apple 原生构建和分发工具兼容的项目。
不要将 Xcode 项目文件提交到版本控制。 改为从 YAML 文件生成它,让 XcodeGen 承担保持其更新的繁重工作。
一个预配置的 Cargo 构建脚本会构建应用,并把二进制文件放到 Xcode 期望的位置。
项目规范还会配置应用图标、entitlements、启动资源和捆绑资源。
生成 Xcode 项目
Section titled “生成 Xcode 项目”为 XcodeGen 创建一个 project.yml 文件。
此示例创建一个 macOS 应用目标,并把 Rust 构建委托给一个 shell 脚本:
name: My Slint Appoptions: deploymentTarget: macOS: '12.0'settings: ENABLE_HARDENED_RUNTIME: YEStargets: My Slint App: type: application platform: macOS deploymentTarget: '12.0' scheme: {} settings: base: PRODUCT_BUNDLE_IDENTIFIER: com.example.my-slint-app DEVELOPMENT_TEAM: ABCDE12345 ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon info: path: Info.plist properties: CFBundleDisplayName: My Slint App CFBundleExecutable: '$(EXECUTABLE_NAME)' CFBundleIdentifier: '$(PRODUCT_BUNDLE_IDENTIFIER)' CFBundleInfoDictionaryVersion: '6.0' CFBundleName: My Slint App CFBundlePackageType: APPL CFBundleShortVersionString: '1.0.0' CFBundleVersion: '1' LSMinimumSystemVersion: '12.0' NSHighResolutionCapable: true sources: - path: Packaging/AppIcon.xcassets buildPhase: resources postCompileScripts: - name: Build with Cargo script: | ./scripts/build_macos_app_with_cargo.bash --bin my-slint-app --profile release outputFiles: - $(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)然后生成 Xcode 项目:
xcodegen generate --spec project.yml --use-cache生成的 .xcodeproj 是一个普通的 Xcode 项目。
把它加入你的 .gitignore 文件,以免提交到版本控制。
你可以在 Xcode 中打开它、用 xcodebuild 构建它,或在 Xcode Cloud 中使用它。
要更改应用目标、Info.plist 值、资源、entitlements、构建阶段或签名设置,请编辑 project.yml 并重新生成项目。
project.yml 只需编写一次。
由于你并不提交项目文件,请在每次构建前(包括在 CI 中)重新生成它。
使用 --use-cache 可以让这一步开销很低:
当规范中没有任何变化时,XcodeGen 会保持项目原样不动。
Cargo 构建脚本必须把最终可执行文件复制到 Xcode 期望的路径。
这就是 Slint 仓库以 scripts/build_macos_app_with_cargo.bash 提供的脚本:
#!/usr/bin/env bash# Copyright © SixtyFPS GmbH <info@slint.dev># SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
set -euo pipefail
usage() { echo "Usage: $0 --bin <binary name> | --example <example name> [--profile <profile>] [--] [cargo build args...]" >&2}
if [ "$#" -lt 2 ]; then usage exit 2fi
CARGO_TARGET_NAME=""CARGO_TARGET_KIND=--binCARGO_PROFILE=dev
while [[ $# -gt 0 ]]; do case "$1" in --bin|--example) CARGO_TARGET_KIND="$1" CARGO_TARGET_NAME="$2" shift shift ;; --profile) CARGO_PROFILE="$2" shift shift ;; --*) break ;; *) usage exit 2 ;; esacdone
if [ -z "${CARGO_TARGET_NAME}" ]; then usage exit 2fi
if [ -z "${TARGET_BUILD_DIR:-}" ] || [ -z "${EXECUTABLE_PATH:-}" ]; then echo "error: TARGET_BUILD_DIR and EXECUTABLE_PATH must be provided by Xcode" >&2 exit 1fi
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH:$HOME/.cargo/bin"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
if [ "${CARGO_PROFILE}" = "dev" ]; then CARGO_PROFILE_DIR=debugelse CARGO_PROFILE_DIR="$CARGO_PROFILE"fi
TARGET_DIR_NAME="${MACOS_CARGO_TARGET_DIR_NAME:-${PRODUCT_BUNDLE_IDENTIFIER:-$CARGO_TARGET_NAME}}"TARGET_DIR_NAME="$(printf "%s" "$TARGET_DIR_NAME" | tr -c 'A-Za-z0-9_.-' '-')"export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$REPO_ROOT/target/xcode-cargo/$TARGET_DIR_NAME}"
RUST_TARGET=aarch64-apple-darwin
cargo build \ --target "$RUST_TARGET" \ "$CARGO_TARGET_KIND" "$CARGO_TARGET_NAME" \ --profile "$CARGO_PROFILE" \ "$@"
# Cargo puts examples in an examples/ sub-directory, binaries in the profile root.if [ "$CARGO_TARGET_KIND" = "--example" ]; then EXECUTABLE="$CARGO_TARGET_DIR/$RUST_TARGET/$CARGO_PROFILE_DIR/examples/$CARGO_TARGET_NAME"else EXECUTABLE="$CARGO_TARGET_DIR/$RUST_TARGET/$CARGO_PROFILE_DIR/$CARGO_TARGET_NAME"fi
mkdir -p "$(dirname "$TARGET_BUILD_DIR/$EXECUTABLE_PATH")"rm -f "$TARGET_BUILD_DIR/$EXECUTABLE_PATH"echo "Copying $EXECUTABLE to $TARGET_BUILD_DIR/$EXECUTABLE_PATH"cp "$EXECUTABLE" "$TARGET_BUILD_DIR/$EXECUTABLE_PATH"chmod +x "$TARGET_BUILD_DIR/$EXECUTABLE_PATH"在你的 Cargo 项目根目录中、project.yml 旁边创建一个 scripts/ 目录。
把构建脚本复制到那里,并用 chmod +x scripts/build_macos_app_with_cargo.bash 使其可执行。
该脚本会使用其父目录作为项目根目录,来作为默认的 Cargo 目标目录。
Xcode 会把 TARGET_BUILD_DIR 和 EXECUTABLE_PATH 传给该脚本。
--profile 标志让 Cargo profile 不写死在脚本里,因此同一个脚本可用于 debug 和 release 构建。
识别出的标志之后的所有内容都会传给 cargo build,因此你可以按目标选择 feature。
构建产物位于 target/xcode-cargo/,目录名取自 MACOS_CARGO_TARGET_DIR_NAME、bundle identifier 或二进制文件名,这样 Xcode 构建就不会和常规的 cargo build 争用同一个目标目录。
在 Mac App Store 之外分发 DMG
Section titled “在 Mac App Store 之外分发 DMG”当用户直接从你的网站下载应用时,使用这条途径。 应用必须使用 Developer ID Application 证书签名,并在分发前由 Apple 公证。
一次性设置:
- 创建 Developer ID Application 证书,并将其安装到你用于构建的钥匙串中。
- 使用
xcrun notarytool store-credentials为notarytool存储公证凭据。 - 编写 XcodeGen 规范、Cargo 构建脚本,以及用于布置 DMG 的脚本。
每次发布重复以下步骤:
- 使用 XcodeGen 生成 Xcode 项目。
- 使用 Xcode 或
xcodebuild构建或归档应用。 - 在启用 hardened runtime 的情况下对应用 bundle 签名。
- 将应用提交到 Apple 的公证服务。
- 在应用上装订(staple)并校验已接受的公证票据。
- 创建包含已公证应用 bundle 的 DMG。
- 使用 Developer ID Application 证书对 DMG 签名。
- 将 DMG 提交到 Apple 的公证服务。
- 在 DMG 上装订(staple)并校验已接受的公证票据。
- 验证最终的 DMG、其代码签名以及挂载后的应用。
核心命令行工具是 xcodebuild、codesign、xcrun notarytool、xcrun stapler、hdiutil 和 spctl。
Apple 的公证指南在这里:
Notarizing macOS software before distribution。
直接分发 DMG 让你可以控制托管和更新,但这也意味着签名证书、CI 钥匙串、公证凭据、DMG 布局和验证步骤都由你负责。 让这套自动化保持小巧、明确且可重复。
用 TestFlight 测试 Beta
Section titled “用 TestFlight 测试 Beta”使用 TestFlight 分发 beta 构建、管理测试人员,并在发布前收集反馈。 TestFlight 不是生产分发渠道。 它使用 App Store Connect 和 Apple 的商店签名流程,而不是用于直接 DMG 下载的 Developer ID 签名和公证流程。
一次性设置:
- 在 YAML 规范中配置 bundle identifier、team、entitlements 和资源。
- 在 App Store Connect 中为该 bundle identifier 创建应用记录。
- 创建内部或外部测试组并邀请测试人员。
每个 beta 重复以下步骤:
- 使用
xcodegen generate生成 Xcode 项目。 - 使用 Xcode 或 Xcode Cloud 构建、归档、签名并上传 beta。
- 将构建添加到测试组,并通过 TestFlight 收集反馈。
通过 Mac App Store 分发
Section titled “通过 Mac App Store 分发”当你希望用户通过 Mac App Store 安装和更新应用时,使用这条途径。 你仍然要使用 XcodeGen,因为 App Store 提交需要真正的 Xcode 项目或归档。 Mac App Store 分发使用 Apple 的商店签名和 App Store Connect 上传流程,而不是用于直接下载的 Developer ID 签名流程。
一次性设置:
- 在 YAML 规范中配置 bundle identifier、team、应用类别、entitlements 和资源。
- 在 App Store Connect 中为该 bundle identifier 创建应用记录。
每次发布重复以下步骤:
- 使用
xcodegen generate生成 Xcode 项目。 - 使用 Xcode 或 Xcode Cloud 构建、归档、签名并上传应用。
- 在 App Store Connect 中提交上传的构建以供审核。
你也可以在 Xcode 中打开生成的项目,并从本地 Mac 提交。 当你想手动管理发布时,这种方式很合适。 当你想使用托管式 CI 工作流时,Xcode Cloud 可以自动执行相同的归档和上传步骤。
Xcode Cloud 内置于 Xcode 和 App Store Connect 中。 Apple Developer Program 会员资格包含一些免费的计算时长,可用于构建你的应用。
重要的 Xcode Cloud 信息
Section titled “重要的 Xcode Cloud 信息”Xcode Cloud 期望你的应用有一个 Xcode 项目文件。
你可以在不提交所生成项目文件的情况下满足这一要求。
在 Xcode 中打开项目文件,并遵循
Apple 的说明来设置 Xcode Cloud。
生成的项目让 Xcode Cloud 能够完成构建和归档步骤。
使用 ci_scripts/ci_post_clone.sh 脚本在 Xcode Cloud 中安装依赖项并生成项目文件:
#!/usr/bin/env bashset -euo pipefail
brew install xcodegen
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ sh -s -- -y --default-toolchain stable --profile minimal --no-modify-pathexport PATH="$HOME/.cargo/bin:$PATH"rustup target add aarch64-apple-darwin
cd "$CI_PRIMARY_REPOSITORY_PATH"xcodegen generate --spec project.yml --use-cacheXcode Cloud 会从 ci_scripts 目录运行该脚本,因此请使用 CI_PRIMARY_REPOSITORY_PATH 到达仓库根目录。
应该提交哪些内容
Section titled “应该提交哪些内容”提交:
project.yml,或者你给 XcodeGen 项目规范起的任何名字。- Xcode 目标所调用的 Cargo 构建脚本。
- 应用图标、entitlements、启动资源和捆绑资源。
- 任何用于打包、签名、公证或验证直接下载产物的脚本。
除非有特定于项目的原因,否则不要提交生成的 .xcodeproj。
改为从 YAML 规范重新生成它。
XcodeGen 在此记录了 YAML 项目格式: Project Spec。
© 2026 SixtyFPS GmbH