Loading...
Loading...
Compare original and translation side by side
convert icon.jpg -resize 512x512 icon.pngsips -z 512 512 icon.pngmkdir AppIcon.iconset && sips -z 512 512 icon.png --out AppIcon.iconset/icon_512x512.png && iconutil -c icns AppIcon.iconsetdocs/AppIcon.pngconvert icon.jpg -resize 512x512 icon.pngsips -z 512 512 icon.pngmkdir AppIcon.iconset && sips -z 512 512 icon.png --out AppIcon.iconset/icon_512x512.png && iconutil -c icns AppIcon.iconsetdocs/AppIcon.pngYourApp.app/Contents/
├── Info.plist # Application metadata
├── MacOS/
│ ├── your-gui-binary # Main executable (launches GUI)
│ └── bin/
│ └── your-cli-binary # CLI tool (accessed via PATH)
└── Resources/
└── AppIcon.icns # Application iconYourApp.app/Contents/
├── Info.plist # 应用元数据
├── MacOS/
│ ├── your-gui-binary # 主可执行文件(启动GUI)
│ └── bin/
│ └── your-cli-binary # CLI工具(可通过PATH访问)
└── Resources/
└── AppIcon.icns # 应用图标<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>YourApp</string>
<key>CFBundleDisplayName</key><string>Your App</string>
<key>CFBundleIdentifier</key><string>com.yourcompany.yourapp</string>
<key>CFBundleVersion</key><string>1.0.0</string>
<key>CFBundleShortVersionString</key><string>1.0.0</string>
<key>CFBundleExecutable</key><string>your-gui-binary</string>
<key>CFBundleIconFile</key><string>AppIcon.icns</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>LSMinimumSystemVersion</key><string>10.13</string>
<key>NSHighResolutionCapable</key><true/>
</dict>
</plist><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>YourApp</string>
<key>CFBundleDisplayName</key><string>Your App</string>
<key>CFBundleIdentifier</key><string>com.yourcompany.yourapp</string>
<key>CFBundleVersion</key><string>1.0.0</string>
<key>CFBundleShortVersionString</key><string>1.0.0</string>
<key>CFBundleExecutable</key><string>your-gui-binary</string>
<key>CFBundleIconFile</key><string>AppIcon.icns</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>LSMinimumSystemVersion</key><string>10.13</string>
<key>NSHighResolutionCapable</key><true/>
</dict>
</plist>undefinedundefined
**Result**: Users drag YourApp.app to Applications, instantly getting both GUI and CLI.
**结果**: 用户将YourApp.app拖拽到Applications文件夹,即可同时获得GUI和CLI工具。undefinedundefined
**Why the retry logic?**
- macOS Finder may still be accessing the mounted DMG even after AppleScript completes
- This causes `hdiutil: couldn't eject "disk6" - Resource busy` errors
- The retry loop with delays solves this race condition
- Affects both Intel and Apple Silicon builds in GitHub Actions
- Without this fix, DMG creation randomly fails with exit code 16
**Result**: Users drag YourApp.app to Applications, instantly getting both GUI and CLI.
**为何需要重试逻辑?**
- 即使AppleScript执行完成,macOS Finder可能仍在访问挂载的DMG
- 这会导致`hdiutil: couldn't eject "disk6" - Resource busy`错误
- 带延迟的重试循环可解决此竞争条件
- 影响GitHub Actions中的Intel和Apple Silicon构建
- 若无此修复,DMG创建会随机失败,退出码为16
**结果**: 用户将YourApp.app拖拽到Applications文件夹,即可同时获得GUI和CLI工具。hdiutil: couldn't eject "disk2" - Resource busy
hdiutil: convert failed - Resource temporarily unavailable
Error: Process completed with exit code 1hdiutil detachhdiutil convertundefinedhdiutil: couldn't eject "disk2" - Resource busy
hdiutil: convert failed - Resource temporarily unavailable
Error: Process completed with exit code 1hdiutil detachhdiutil convertundefined
**Why This Works**:
1. **Progressive escalation**: Starts with gentle unmount, escalates to force only if needed
2. **Verification between steps**: Uses `diskutil info` to check mount status before each strategy
3. **Multiple tools**: Combines `hdiutil detach` and `diskutil unmountDisk` (different unmount mechanisms)
4. **Diagnostic output**: Shows `lsof` output if unmount fails to help debug persistent issues
5. **Multiple sync calls**: Ensures filesystem writes complete before attempting unmount
6. **Longer delays**: Gives background processes time to release file handles
**Implementation Location**: `.github/workflows/release.yml` in the "Create DMG installer (macOS only)" step, lines ~328-377.
**Applies To**: Both Intel (x86_64-apple-darwin) and Apple Silicon (aarch64-apple-darwin) DMG builds.
**Testing**: After implementing this fix, DMG creation should succeed consistently even under system load. Monitor with:
```bash
gh run watch
gh run list --workflow=release.yml --limit 5
**为何此方案有效**:
1. **逐步升级**: 从温和卸载开始,仅在必要时升级到强制卸载
2. **步骤间验证**: 在每个策略前使用`diskutil info`检查挂载状态
3. **多工具结合**: 结合`hdiutil detach`和`diskutil unmountDisk`(不同的卸载机制)
4. **诊断输出**: 如果卸载失败,显示`lsof`输出以帮助调试持续存在的问题
5. **多次同步调用**: 确保文件系统写入完成后再尝试卸载
6. **更长延迟**: 给后台进程足够时间释放文件句柄
**实现位置**: `.github/workflows/release.yml`中的“Create DMG installer (macOS only)”步骤,约第328-377行。
**适用范围**: Intel(x86_64-apple-darwin)和Apple Silicon(aarch64-apple-darwin)DMG构建。
**测试**: 实施此修复后,即使在系统负载下,DMG创建也应持续成功。使用以下命令监控:
```bash
gh run watch
gh run list --workflow=release.yml --limit 5undefinedundefinedundefinedundefinedfn main() {
#[cfg(windows)] // Only run on Windows builds
{
let mut res = winres::WindowsResource::new();
res.set_icon("assets/app.ico"); // Path to multi-resolution ICO file
res.set("ProductName", "YourApp"); // Shown in Task Manager
res.set("FileDescription", "Description"); // Shown in file properties
res.set("LegalCopyright", "Copyright (c) 2025");
res.compile().expect("Failed to compile Windows resources");
}
}cargo buildfn main() {
#[cfg(windows)] // 仅在Windows构建时运行
{
let mut res = winres::WindowsResource::new();
res.set_icon("assets/app.ico"); // 多分辨率ICO文件路径
res.set("ProductName", "YourApp"); // 任务管理器中显示的名称
res.set("FileDescription", "Description"); // 文件属性中显示的描述
res.set("LegalCopyright", "Copyright (c) 2025");
res.compile().expect("Failed to compile Windows resources");
}
}cargo buildundefinedundefined
**Why multi-resolution?** Windows picks different icon sizes depending on context (taskbar, file explorer, system tray). Single-size ICOs look pixelated when scaled.
**为何需要多分辨率?** Windows会根据不同场景(任务栏、文件资源管理器、系统托盘)选择不同的图标尺寸。单尺寸ICO缩放后会出现像素化。cargo packageinclude_bytes!("../../assets/")cargo packageinclude_bytes!("../../assets/")// Fails during cargo package because ../../ escapes the package boundary
let logo = include_bytes!("../../assets/logo.png");couldn't read src/../../assets/logo.png// 在cargo package时失败,因为../../超出了包边界
let logo = include_bytes!("../../assets/logo.png");couldn't read src/../../assets/logo.pngundefinedundefined
```rust
// Update code to use local path (within package)
let logo = include_bytes!("../assets/logo.png"); // ../assets relative to src/undefined
```rust
// 更新代码以使用本地路径(包内)
let logo = include_bytes!("../assets/logo.png"); // ../assets相对于src/目录undefined
**Why this works**: Each package becomes self-contained with its own assets, allowing `cargo package` and `cargo publish` to succeed.
**为何有效**: 每个包都成为包含自身资产的独立单元,使`cargo package`和`cargo publish`能够成功执行。undefinedundefined
**Expected output**: Should show `assets/logo.png` in the package contents.
**预期输出**: 应在包内容中显示`assets/logo.png`。See also: For bundling external dependencies (FFmpeg, ExifTool, Tesseract, etc.) in your MSI with unified detection/execution code paths, see theskill.robust-dependency-installation
另请参阅: 如需在MSI中捆绑外部依赖项(FFmpeg、ExifTool、Tesseract等)并使用统一的检测/执行代码路径,请查看技能。robust-dependency-installation
dotnet tool install --global wix # Microsoft's official installer frameworkdotnet tool install --global wix # 微软官方安装程序框架| v3/v4 | v6 | Reason |
|---|---|---|
| | Clearer 64-bit intent |
| | Condition is an attribute now |
| v3/v4 | v6 | 原因 |
|---|---|---|
| | 明确64位意图 |
| | 条件现在是属性 |
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<!-- Package metadata -->
<Package Name="YourApp" Version="1.0.0" Manufacturer="Your Company"
UpgradeCode="GUID-HERE" Language="1033"> <!-- UpgradeCode: NEVER CHANGE! Used to detect previous installations -->
<MajorUpgrade DowngradeErrorMessage="Newer version installed." /> <!-- Auto-uninstall old version -->
<MediaTemplate EmbeddingCompressionLevel="high" /> <!-- Compress to reduce file size -->
<!-- What to install -->
<Feature Id="ProductFeature" Title="YourApp" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
<!-- Where to install -->
<StandardDirectory Id="ProgramFiles6432Folder"> <!-- C:\Program Files\ -->
<Directory Id="INSTALLFOLDER" Name="YourApp"> <!-- C:\Program Files\YourApp\ -->
<Component Id="MainExecutable" Bitness="always64">
<File Id="GUI" Source="target\release\yourapp-gui.exe" KeyPath="yes">
<Shortcut Id="StartMenu" Directory="ProgramMenuFolder" Name="YourApp" /> <!-- Start Menu shortcut -->
</File>
<File Id="CLI" Source="target\release\yourapp.exe" /> <!-- CLI tool -->
</Component>
<Component Id="PathEnvironment" Bitness="always64">
<!-- Add install dir to system PATH so CLI is accessible from any terminal -->
<Environment Id="PATH" Name="PATH" Value="[INSTALLFOLDER]"
Permanent="no" Part="last" Action="set" System="yes" />
</Component>
</Directory>
</StandardDirectory>
</Package>
<!-- Component registry -->
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<ComponentRef Id="MainExecutable" />
<ComponentRef Id="PathEnvironment" />
</ComponentGroup>
</Fragment>
</Wix>uuidgen<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<!-- 包元数据 -->
<Package Name="YourApp" Version="1.0.0" Manufacturer="Your Company"
UpgradeCode="GUID-HERE" Language="1033"> <!-- UpgradeCode:切勿更改!用于检测之前的安装 -->
<MajorUpgrade DowngradeErrorMessage="Newer version installed." /> <!-- 自动卸载旧版本 -->
<MediaTemplate EmbeddingCompressionLevel="high" /> <!-- 压缩以减小文件大小 -->
<!-- 要安装的内容 -->
<Feature Id="ProductFeature" Title="YourApp" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
<!-- 安装位置 -->
<StandardDirectory Id="ProgramFiles6432Folder"> <!-- C:\Program Files\ -->
<Directory Id="INSTALLFOLDER" Name="YourApp"> <!-- C:\Program Files\YourApp\ -->
<Component Id="MainExecutable" Bitness="always64">
<File Id="GUI" Source="target\release\yourapp-gui.exe" KeyPath="yes">
<Shortcut Id="StartMenu" Directory="ProgramMenuFolder" Name="YourApp" /> <!-- 开始菜单快捷方式 -->
</File>
<File Id="CLI" Source="target\release\yourapp.exe" /> <!-- CLI工具 -->
</Component>
<Component Id="PathEnvironment" Bitness="always64">
<!-- 将安装目录添加到系统PATH,使CLI可从任何终端访问 -->
<Environment Id="PATH" Name="PATH" Value="[INSTALLFOLDER]"
Permanent="no" Part="last" Action="set" System="yes" />
</Component>
</Directory>
</StandardDirectory>
</Package>
<!-- 组件注册表 -->
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<ComponentRef Id="MainExecutable" />
<ComponentRef Id="PathEnvironment" />
</ComponentGroup>
</Fragment>
</Wix>uuidgenwix build yourapp.wxs -o YourApp.msiVersion="1.0.0"wix build yourapp.wxs -o YourApp.msiVersion="1.0.0"debian/
├── control # Package metadata
├── rules # Build script
├── changelog # Version history
├── copyright # License
├── compat # Debian level (13)
├── yourapp.desktop # Desktop entry
└── source/format # "3.0 (quilt)"debian/
├── control # 包元数据
├── rules # 构建脚本
├── changelog # 版本历史
├── copyright # 许可证
├── compat # Debian兼容级别(13)
├── yourapp.desktop # 桌面入口
└── source/format # "3.0 (quilt)"your@email.commaintainer@example.comdebian/controldebian/copyrightdebian/changelogundefinedyour@email.commaintainer@example.comdebian/controldebian/copyrightdebian/changelogundefined
**Best practice**: Match email in `Cargo.toml` authors.
**最佳实践**: 与`Cargo.toml`作者中的邮箱保持一致。Source: yourapp
Section: utils
Priority: optional
Maintainer: Your Name <your@email.com>
Build-Depends: debhelper-compat (= 13), cargo, rustc, pkg-config, libleptonica-dev, libtesseract-dev
Standards-Version: 4.6.0
Homepage: https://github.com/yourname/yourapp
Package: yourapp
Architecture: amd64
Depends: ${shlibs:Depends}, ${misc:Depends}, libimage-exiftool-perl, tesseract-ocr, ffmpeg
Description: Short description
Long description.Source: yourapp
Section: utils
Priority: optional
Maintainer: Your Name <your@email.com>
Build-Depends: debhelper-compat (= 13), cargo, rustc, pkg-config, libleptonica-dev, libtesseract-dev
Standards-Version: 4.6.0
Homepage: https://github.com/yourname/yourapp
Package: yourapp
Architecture: amd64
Depends: ${shlibs:Depends}, ${misc:Depends}, libimage-exiftool-perl, tesseract-ocr, ffmpeg
Description: Short description
Long description.#!/usr/bin/make -f
export DEB_BUILD_MAINT_OPTIONS = hardening=+all # Enable security hardening (ASLR, stack protection, etc.)
%:
dh $@ # Use debhelper to handle most packaging tasks
override_dh_auto_build:
cargo build --release --workspace # Build all workspace members
override_dh_auto_install:
# Install binaries to /usr/bin with executable permissions
install -D -m 755 target/release/yourapp-gui debian/yourapp/usr/bin/yourapp-gui
install -D -m 755 target/release/yourapp debian/yourapp/usr/bin/yourapp
# Install desktop entry for application menu integration
install -D -m 644 debian/yourapp.desktop debian/yourapp/usr/share/applications/yourapp.desktop
# Install icon to BOTH locations for maximum compatibility:
# - /usr/share/pixmaps/ (legacy, used by older desktop environments)
# - /usr/share/icons/hicolor/512x512/apps/ (freedesktop standard, preferred)
install -D -m 644 yourapp-gui/assets/yourapp.png debian/yourapp/usr/share/pixmaps/yourapp.png
install -D -m 644 yourapp-gui/assets/yourapp.png debian/yourapp/usr/share/icons/hicolor/512x512/apps/yourapp.pngyourapp-gui/assets/#!/usr/bin/make -f
export DEB_BUILD_MAINT_OPTIONS = hardening=+all # 启用安全加固(ASLR、栈保护等)
%:
dh $@ # 使用debhelper处理大多数打包任务
override_dh_auto_build:
cargo build --release --workspace # 构建所有工作区成员
override_dh_auto_install:
# 将二进制文件安装到/usr/bin并设置可执行权限
install -D -m 755 target/release/yourapp-gui debian/yourapp/usr/bin/yourapp-gui
install -D -m 755 target/release/yourapp debian/yourapp/usr/bin/yourapp
# 安装桌面入口以集成到应用程序菜单
install -D -m 644 debian/yourapp.desktop debian/yourapp/usr/share/applications/yourapp.desktop
# 将图标安装到两个位置以获得最大兼容性:
# - /usr/share/pixmaps/(旧版,用于较旧的桌面环境)
# - /usr/share/icons/hicolor/512x512/apps/(freedesktop标准,推荐)
install -D -m 644 yourapp-gui/assets/yourapp.png debian/yourapp/usr/share/pixmaps/yourapp.png
install -D -m 644 yourapp-gui/assets/yourapp.png debian/yourapp/usr/share/icons/hicolor/512x512/apps/yourapp.pngyourapp-gui/assets/[Desktop Entry]
Type=Application
Name=YourApp
Comment=Description
Exec=/usr/bin/yourapp-gui
Icon=yourapp
Terminal=false
Categories=Utility;[Desktop Entry]
Type=Application
Name=YourApp
Comment=Description
Exec=/usr/bin/yourapp-gui
Icon=yourapp
Terminal=false
Categories=Utility;yourapp (1.0.0-1) unstable; urgency=medium
* Initial release
-- Your Name <your@email.com> Mon, 01 Jan 2024 12:00:00 +0000yourapp (1.0.0-1) unstable; urgency=medium
* Initial release
-- Your Name <your@email.com> Mon, 01 Jan 2024 12:00:00 +0000sudo apt-get install -y pkg-config libleptonica-dev libtesseract-dev libclang-dev clangexport PKG_CONFIG_PATH="/usr/lib/x86_64-linux-gnu/pkgconfig:$PKG_CONFIG_PATH"
cargo build --release- run: sudo apt-get install -y pkg-config libleptonica-dev libtesseract-dev libclang-dev clang
- run: |
export PKG_CONFIG_PATH="/usr/lib/x86_64-linux-gnu/pkgconfig:$PKG_CONFIG_PATH"
cargo build --releasesudo apt-get install -y pkg-config libleptonica-dev libtesseract-dev libclang-dev clangexport PKG_CONFIG_PATH="/usr/lib/x86_64-linux-gnu/pkgconfig:$PKG_CONFIG_PATH"
cargo build --release- run: sudo apt-get install -y pkg-config libleptonica-dev libtesseract-dev libclang-dev clang
- run: |
export PKG_CONFIG_PATH="/usr/lib/x86_64-linux-gnu/pkgconfig:$PKG_CONFIG_PATH"
cargo build --releasedpkg-buildpackage -us -uc -b
lintian ../yourapp_1.0.0-1_amd64.debdpkg-buildpackage -us -uc -b
lintian ../yourapp_1.0.0-1_amd64.debname: Release
on:
push:
tags: ['v*.*.*'] # Triggers when you push v1.0.0, v0.5.1, etc.
workflow_dispatch: # Manual trigger fallback if needed
inputs:
tag: {required: true, type: string}
jobs:
create-release:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }} # Share version with build jobs
steps:
- uses: actions/checkout@v4
- id: get_version
run: |
# Extract version from tag (v1.0.0 → 1.0.0)
if [ "${{ github.event_name }}" = "push" ]; then
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
else
echo "version=${{ inputs.tag }}" >> $GITHUB_OUTPUT
fi
- run: gh release create ${{ steps.get_version.outputs.version }} --draft # Create draft release
env: {GH_TOKEN: ${{ github.token }}}
build:
needs: create-release # Wait for release to be created
strategy:
matrix: # Build all platforms in parallel
include:
- {os: macos-latest, target: x86_64-apple-darwin, archive: dmg} # macOS Intel
- {os: macos-latest, target: aarch64-apple-darwin, archive: dmg} # macOS Apple Silicon
- {os: windows-latest, target: x86_64-pc-windows-msvc, archive: msi} # Windows
- {os: ubuntu-latest, target: x86_64-unknown-linux-gnu, archive: deb} # Linux
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with: {targets: ${{ matrix.target }}}
# Platform-specific builds happen here (DMG, MSI, DEB creation)
# Generate checksums with unique filenames to avoid artifact collisions
- run: sha256sum * | tee checksums-${{ matrix.target }}.txt
- uses: actions/upload-artifact@v4
with:
name: yourapp-${{ matrix.target }} # Unique artifact name per platform
path: |
*.dmg
*.msi
../*.deb
checksums-*.txtmatrix.targetname: Release
on:
push:
tags: ['v*.*.*'] # 推送v1.0.0、v0.5.1等标签时触发
workflow_dispatch: # 手动触发回退(如有需要)
inputs:
tag: {required: true, type: string}
jobs:
create-release:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }} # 与构建作业共享版本号
steps:
- uses: actions/checkout@v4
- id: get_version
run: |
# 从标签中提取版本号(v1.0.0 → 1.0.0)
if [ "${{ github.event_name }}" = "push" ]; then
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
else
echo "version=${{ inputs.tag }}" >> $GITHUB_OUTPUT
fi
- run: gh release create ${{ steps.get_version.outputs.version }} --draft # 创建草稿发布
env: {GH_TOKEN: ${{ github.token }}}
build:
needs: create-release # 等待发布创建完成
strategy:
matrix: # 并行构建所有平台
include:
- {os: macos-latest, target: x86_64-apple-darwin, archive: dmg} # macOS Intel
- {os: macos-latest, target: aarch64-apple-darwin, archive: dmg} # macOS Apple Silicon
- {os: windows-latest, target: x86_64-pc-windows-msvc, archive: msi} # Windows
- {os: ubuntu-latest, target: x86_64-unknown-linux-gnu, archive: deb} # Linux
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with: {targets: ${{ matrix.target }}}
# 平台特定构建在此处进行(DMG、MSI、DEB创建)
# 生成带有唯一文件名的校验和以避免工件冲突
- run: sha256sum * | tee checksums-${{ matrix.target }}.txt
- uses: actions/upload-artifact@v4
with:
name: yourapp-${{ matrix.target }} # 每个平台使用唯一的工件名称
path: |
*.dmg
*.msi
../*.deb
checksums-*.txtmatrix.targetundefinedundefined
**What this adds**: Cryptographic proof that the artifact was built by this repo's workflow. Users can verify authenticity:
```bash
gh attestation verify yourapp.dmg --owner yourname
**添加的内容**: 加密证明工件是由本仓库的工作流构建的。用户可以验证真实性:
```bash
gh attestation verify yourapp.dmg --owner yournameon:
workflow_dispatch:
inputs: {version: {required: true, type: string}}
repository_dispatch:
types: [update-formulae]
jobs:
update:
runs-on: ubuntu-latest
steps:
- id: get-version
run: |
if [ "${{ github.event_name }}" = "repository_dispatch" ]; then
echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT
else
echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT
fi
- uses: actions/checkout@v4
- run: gh release download "v${{ steps.get-version.outputs.version }}" --repo yourname/yourapp --pattern "checksums.txt"
env: {GH_TOKEN: ${{ github.token }}}
- id: checksums
run: |
echo "arm64_sha=$(grep aarch64-apple-darwin checksums.txt | awk '{print $1}')" >> $GITHUB_OUTPUT
echo "x86_64_sha=$(grep x86_64-apple-darwin checksums.txt | awk '{print $1}')" >> $GITHUB_OUTPUT
- run: |
sed -i "s/version \".*\"/version \"${{ steps.get-version.outputs.version }}\"/" Formula/yourapp.rb
# Update URLs and SHAs...
- run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/ Casks/
git commit -m "chore: update to v${{ steps.get-version.outputs.version }}"
git pushupdate-homebrew:
needs: [create-release, build, checksums]
runs-on: ubuntu-latest
if: ${{ !contains(needs.create-release.outputs.version, '-') }} # Skip pre-releases
steps:
- run: |
VERSION=${{ needs.create-release.outputs.version }}
curl -L -X POST \
-H "Authorization: Bearer ${{ secrets.HOMEBREW_DISPATCH_TOKEN }}" \
https://api.github.com/repos/yourname/homebrew-yourapp/dispatches \
-d "{\"event_type\":\"update-formulae\",\"client_payload\":{\"version\":\"${VERSION#v}\"}}"HOMEBREW_DISPATCH_TOKENrepoGITHUB_TOKENon:
workflow_dispatch:
inputs: {version: {required: true, type: string}}
repository_dispatch:
types: [update-formulae]
jobs:
update:
runs-on: ubuntu-latest
steps:
- id: get-version
run: |
if [ "${{ github.event_name }}" = "repository_dispatch" ]; then
echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT
else
echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT
fi
- uses: actions/checkout@v4
- run: gh release download "v${{ steps.get-version.outputs.version }}" --repo yourname/yourapp --pattern "checksums.txt"
env: {GH_TOKEN: ${{ github.token }}}
- id: checksums
run: |
echo "arm64_sha=$(grep aarch64-apple-darwin checksums.txt | awk '{print $1}')" >> $GITHUB_OUTPUT
echo "x86_64_sha=$(grep x86_64-apple-darwin checksums.txt | awk '{print $1}')" >> $GITHUB_OUTPUT
- run: |
sed -i "s/version \".*\"/version \"${{ steps.get-version.outputs.version }}\"/" Formula/yourapp.rb
# 更新URL和SHA...
- run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/ Casks/
git commit -m "chore: update to v${{ steps.get-version.outputs.version }}"
git pushupdate-homebrew:
needs: [create-release, build, checksums]
runs-on: ubuntu-latest
if: ${{ !contains(needs.create-release.outputs.version, '-') }} # 跳过预发布版本
steps:
- run: |
VERSION=${{ needs.create-release.outputs.version }}
curl -L -X POST \
-H "Authorization: Bearer ${{ secrets.HOMEBREW_DISPATCH_TOKEN }}" \
https://api.github.com/repos/yourname/homebrew-yourapp/dispatches \
-d "{\"event_type\":\"update-formulae\",\"client_payload\":{\"version\":\"${VERSION#v}\"}}"repoHOMEBREW_DISPATCH_TOKENGITHUB_TOKENcargo release patch --executecargo release patch --executepackage-v0.6.14v0.6.14v*.*.*cargo release patch --executegit tag --sort=-version:refname | head -5package-v0.6.14v0.6.14v*.*.*cargo release patch --executegit tag --sort=-version:refname | head -5
**Workaround**: Manually create and push the simple `vX.Y.Z` tag:
```bash
**解决方法**: 手动创建并推送简单的`vX.Y.Z`标签:
```bash
**Root cause**: cargo-release uses package names in tags for workspace members. The GitHub Actions workflow triggers on `v*.*.*` pattern which doesn't match `package-v*.*.*`.
**Alternative**: Configure `release.toml` to customize tag format (not yet tested).
**Checklist**:
- [ ] `on: push: tags: ['v*.*.*']` in release workflow
- [ ] Version extraction handles both `push` and `workflow_dispatch`
- [ ] Homebrew workflow listens for `repository_dispatch`
- [ ] Main workflow sends dispatch after successful build
- [ ] `release.toml` configured
- [ ] **After cargo-release, manually create `v*.*.*` tag**
**Monitor**:
```bash
gh run watch
gh run list --limit 5
**根本原因**: cargo-release会为工作区成员的标签添加包名。GitHub Actions工作流触发模式为`v*.*.*`,与`package-v*.*.*`不匹配。
**替代方案**: 配置`release.toml`自定义标签格式(尚未测试)。
**检查清单**:
- [ ] 发布工作流中包含`on: push: tags: ['v*.*.*']`
- [ ] 版本提取处理`push`和`workflow_dispatch`两种情况
- [ ] Homebrew工作流监听`repository_dispatch`
- [ ] 主工作流在构建成功后发送触发请求
- [ ] 已配置`release.toml`
- [ ] **执行cargo-release后,手动创建`v*.*.*`标签**
**监控**:
```bash
gh run watch
gh run list --limit 5