Skip to content

完整 Workflow 示例

基础版本

适用于单平台构建的项目:

yaml
name: Build and Release

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: write
      attestations: write

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install Dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Attest Build Provenance
        uses: actions/attest-build-provenance@v2
        with:
          subject-path: 'dist/*'

      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          files: dist/*
          generate_release_notes: true

多平台构建版本

适用于需要在多个操作系统上构建的项目:

重要:多平台构建的正确方式

使用 matrix 策略构建多个平台时,不要在每个构建 job 中都创建 Release。这会导致多个 job 同时尝试创建同一个 Release,产生冲突。

正确做法:

  1. 每个构建 job 使用 upload-artifact 上传构建产物
  2. 创建一个单独的 release job,使用 needs: build 等待所有构建完成
  3. release job 中下载所有 artifacts 并统一创建 Release
yaml
name: Multi-Platform Release

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            artifact_name: app-linux
          - os: windows-latest
            artifact_name: app-windows.exe
          - os: macos-latest
            artifact_name: app-macos

    runs-on: ${{ matrix.os }}
    permissions:
      id-token: write
      contents: write
      attestations: write

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install Dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Attest Build Provenance
        uses: actions/attest-build-provenance@v2
        with:
          subject-path: 'dist/${{ matrix.artifact_name }}'

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.artifact_name }}
          path: dist/${{ matrix.artifact_name }}

  release:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - name: Download All Artifacts
        uses: actions/download-artifact@v4
        with:
          path: artifacts

      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          files: artifacts/**/*
          generate_release_notes: true

Go 项目示例

yaml
name: Release Go Application

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    strategy:
      matrix:
        include:
          - goos: linux
            goarch: amd64
          - goos: windows
            goarch: amd64
          - goos: darwin
            goarch: amd64
          - goos: darwin
            goarch: arm64

    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: write
      attestations: write

    steps:
      - uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Build
        env:
          GOOS: ${{ matrix.goos }}
          GOARCH: ${{ matrix.goarch }}
        run: |
          output="myapp-${{ matrix.goos }}-${{ matrix.goarch }}"
          if [ "${{ matrix.goos }}" = "windows" ]; then
            output="${output}.exe"
          fi
          go build -o "dist/${output}" .

      - name: Attest
        uses: actions/attest-build-provenance@v2
        with:
          subject-path: 'dist/*'

      - name: Upload
        uses: actions/upload-artifact@v4
        with:
          name: myapp-${{ matrix.goos }}-${{ matrix.goarch }}
          path: dist/*

  release:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/download-artifact@v4
        with:
          path: dist
          merge-multiple: true

      - uses: softprops/action-gh-release@v2
        with:
          files: dist/*

Python 项目示例

yaml
name: Release Python Package

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: write
      attestations: write

    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install Build Tools
        run: pip install build

      - name: Build Package
        run: python -m build

      - name: Attest
        uses: actions/attest-build-provenance@v2
        with:
          subject-path: 'dist/*'

      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          files: dist/*

Swift 项目示例 - SPM CLI 工具

关于 macOS 签名与公证

本模板面向没有 Apple 开发者账号的开发者,不做 codesign / 公证。信任建立在三点上:

  1. swift build --arch arm64 --arch x86_64 会自动为产物生成 ad-hoc 签名,满足 Apple Silicon 所需的「可执行文件必须签名」硬性要求,无需任何证书
  2. attest-build-provenance 由 GitHub + sigstore 背书,下载者可独立验证产物来源
  3. 下载者首次运行需手动解除 Gatekeeper 隔离(命令见下方「下载者使用说明」)

适用对象:Swift Package Manager 管理的可执行文件(CLI 工具),下游使用者为开发者,交付物为单个 Mach-O 二进制。macOS 应用(.app bundle / DMG 分发)请看下一节「Swift 项目示例 - macOS 应用」。iOS App / XCFramework 分发涉及代码签名与公证,不在本模板覆盖范围。

yaml
name: Release Swift Package

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: macos-latest
    permissions:
      id-token: write
      contents: write
      attestations: write

    steps:
      - uses: actions/checkout@v4

      # macOS runner 已预装 Swift 与 Xcode;如需锁定 Xcode 版本,取消下面注释:
      # - uses: maxim-lobanov/setup-xcode@v1
      #   with:
      #     xcode-version: latest-stable

      - name: Build Universal Binary
        run: swift build -c release --arch arm64 --arch x86_64 --product myapp

      - name: Package
        run: |
          mkdir -p dist
          tar -czf dist/myapp-macos-universal.tar.gz \
            -C .build/apple/Products/Release myapp

      - name: Attest Build Provenance
        uses: actions/attest-build-provenance@v2
        with:
          subject-path: 'dist/*'

      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          files: dist/*
          generate_release_notes: true

下载者使用说明

步骤命令
验证构件来源gh attestation verify myapp-macos-universal.tar.gz --owner <your-org>
解压tar -xzf myapp-macos-universal.tar.gz
解除 Gatekeeper 隔离(首次运行)xattr -d com.apple.quarantine myapp
运行./myapp

Swift 项目示例 - macOS 应用

关于无开发者账号的 macOS App 分发

本模板面向没有 Apple 开发者账号的开发者发布 macOS 应用。信任链建立在:

  1. ad-hoc 签名CODE_SIGN_IDENTITY=-)——Apple Silicon 硬性要求可执行文件必须签名,ad-hoc 是免证书的官方替代方案。xcodebuild archive 完成后再显式 codesign --force --deep --sign - 一次,确保 bundle 内所有嵌入 framework / helper 一并签上。
  2. xattr -cr 剥除构建过程附带的扩展属性,避免 DMG 内文件带 quarantine 导致用户首次打开被 Gatekeeper 直接拒。
  3. attest-build-provenance 对最终 DMG 签名,下载者可用 gh attestation verify 独立校验。
  4. 下载者首次挂载 DMG 后仍需手动解除 Gatekeeper 隔离(命令见下方「下载者使用说明」)。

适用对象:用 Xcode 工程(.xcodeproj)构建的 macOS 应用,交付物为 .app bundle 封装进 DMG。不做公证 (notarize)、不走 MAS。

AI 填充规则

本模板含 4 个占位符,按以下规则填写:

  • <PROJECT>.xcodeproj:在项目根及子目录(maxdepth 3)查找 *.xcodeproj。若 唯一 命中 Foo.xcodeproj,填 Foo.xcodeproj;若命中多个或位于 *.xcworkspace 内,保留 <PROJECT>.xcodeproj 占位符并提示用户补值。
  • <SCHEME>:默认取 <PROJECT> 同名(绝大多数项目约定如此)。若 xcodebuild -list 显示 scheme 名与 project 名不一致,保留 <SCHEME> 占位符并提示用户补值。
  • <APP_NAME>.app bundle 的名字,通常等于 <SCHEME>。若 Info.plist 的 CFBundleName 与 scheme 不同以 CFBundleName 为准。
  • <VOLUME_NAME>:DMG 挂载后的卷名,建议等于 <APP_NAME>

填完后 Archive 步骤、codesign 步骤、DMG 步骤中的 .app 路径都会自动一致。

yaml
name: Release

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      tag:
        description: 'Tag to build (e.g. v1.0.0)'
        required: true
        default: 'v1.0.0'

permissions:
  contents: write
  id-token: write
  attestations: write

jobs:
  build:
    name: Build & Release
    runs-on: macos-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      # 锁定 Xcode 版本避免 macos-latest 镜像漂移;如需特定版本改用
      # maxim-lobanov/setup-xcode@v1 with xcode-version: '15.4'
      - name: Select Xcode
        run: |
          sudo xcode-select -s /Applications/Xcode.app
          xcodebuild -version

      - name: Resolve version
        id: version
        run: |
          TAG="${{ inputs.tag }}"
          if [ -z "$TAG" ]; then TAG="$GITHUB_REF_NAME"; fi
          echo "tag=$TAG" >> "$GITHUB_OUTPUT"
          echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"

      # ad-hoc 签名 (CODE_SIGN_IDENTITY=-) 是免证书发布的关键;
      # 不能改成 CODE_SIGNING_ALLOWED=NO,那样 archive 会跳过签名,
      # 产物在 Apple Silicon 上打不开
      - name: Archive (ad-hoc signed)
        run: |
          mkdir -p build
          xcodebuild \
            -project <PROJECT>.xcodeproj \
            -scheme <SCHEME> \
            -configuration Release \
            -destination 'generic/platform=macOS' \
            -archivePath build/<APP_NAME>.xcarchive \
            CODE_SIGN_IDENTITY=- \
            CODE_SIGN_STYLE=Manual \
            CODE_SIGNING_REQUIRED=YES \
            CODE_SIGNING_ALLOWED=YES \
            DEVELOPMENT_TEAM= \
            archive

      - name: Verify signature
        run: codesign -dv build/<APP_NAME>.xcarchive/Products/Applications/<APP_NAME>.app 2>&1 || true

      # 剥除构建过程带进来的扩展属性 (xattr),再整包重签 ad-hoc
      # 否则 DMG 内的 .app 会被 Gatekeeper 因 com.apple.quarantine 拒绝
      - name: Strip extended attributes and re-sign ad-hoc
        run: |
          APP=build/<APP_NAME>.xcarchive/Products/Applications/<APP_NAME>.app
          xattr -cr "$APP"
          codesign --force --deep --sign - --options runtime "$APP"
          codesign -dv "$APP" 2>&1 | head -5
          xattr -lr "$APP" || true

      - name: Package DMG
        env:
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          STAGE=build/dmg-stage
          mkdir -p "$STAGE"
          cp -R build/<APP_NAME>.xcarchive/Products/Applications/<APP_NAME>.app "$STAGE/"
          xattr -cr "$STAGE/<APP_NAME>.app"
          # 在 DMG 内加 Applications 软链,用户挂载后直接拖拽即可安装
          ln -s /Applications "$STAGE/Applications"
          hdiutil create \
            -volname "<VOLUME_NAME>" \
            -srcfolder "$STAGE" \
            -ov -format UDZO \
            "build/<APP_NAME>-${VERSION}.dmg"
          xattr -cr "build/<APP_NAME>-${VERSION}.dmg"
          ls -la "build/<APP_NAME>-${VERSION}.dmg"

      - name: Attest build provenance
        uses: actions/attest-build-provenance@v2
        with:
          subject-path: build/<APP_NAME>-*.dmg

      # 使用 gh CLI 而非 softprops/action-gh-release,方便实现
      # "首次 create、后续 upload --clobber" 的幂等发布
      - name: Create or update GitHub Release
        env:
          GH_TOKEN: ${{ github.token }}
          TAG: ${{ steps.version.outputs.tag }}
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          NOTES_FILE=release_notes.md
          if [ ! -f "$NOTES_FILE" ]; then
            echo "# <APP_NAME> ${VERSION}" > "$NOTES_FILE"
            echo "" >> "$NOTES_FILE"
            echo "Auto-generated release from tag ${TAG}." >> "$NOTES_FILE"
          fi
          if gh release view "$TAG" >/dev/null 2>&1; then
            gh release upload "$TAG" "build/<APP_NAME>-${VERSION}.dmg" --clobber
          else
            gh release create "$TAG" \
              --title "<APP_NAME> ${VERSION}" \
              --notes-file "$NOTES_FILE" \
              "build/<APP_NAME>-${VERSION}.dmg"
          fi

下载者使用说明

DMG 分发的 macOS 应用安装步骤:

步骤命令 / 操作
验证构件来源gh attestation verify <APP_NAME>-<VERSION>.dmg --owner <your-org>
挂载 DMG双击 DMG 文件,Finder 自动挂载
安装<APP_NAME>.app 拖拽到同窗口内的 Applications 软链
首次启动被 Gatekeeper 拦截系统设置 → 隐私与安全性 点击"仍要打开";或终端执行 xattr -cr /Applications/<APP_NAME>.app
运行Launchpad / Spotlight 打开 <APP_NAME>

本地验证 workflow 语法

推送 tag 前可本地预跑:

bash
# 方式 1:检查 yml 语法(需先 brew install actionlint)
actionlint .github/workflows/release.yml

# 方式 2:用 act 本地模拟执行(需先 brew install act,且本机需 Docker)
# 注意:macOS 任务无法在 act 中真实跑,只能到 xcode-select 步骤前
act push -W .github/workflows/release.yml --container-architecture linux/amd64

# 方式 3:push 一个 test tag 到远程后立即删,触发远程构建验证
git tag v0.0.0-test && git push origin v0.0.0-test
# 查看运行日志后删除
git tag -d v0.0.0-test && git push origin :refs/tags/v0.0.0-test

CodeQL workflow(Swift - Xcode 工程)

本节模板与上面「Swift 项目示例」职责完全不同:上面那份 release workflow 由 tag 触发,用 swift build 打包 CLI 上线;这里的 CodeQL workflow 由 push / pull_request / schedule 触发,用 xcodebuild 完成 CodeQL 扫描所需的编译追踪(trace),产物本身不会被发布。

为什么 Xcode 16+ 工程不能继续走 GitHub Default CodeQL:Default 模式背后是 codeql-action/autobuild,它通过解析 .xcodeprojPBXSourcesBuildPhase.files 定位 Swift 源。Xcode 16 起新建工程默认使用 PBXFileSystemSynchronizedRootGroup 由目录同步推导源文件,PBXSourcesBuildPhase.files 为空数组,autobuild 直接报 no-swift-target: All targets contain no Swift source files 失败。落地此 workflow 后,必须在 GitHub 仓库 Settings → Code security → Code scanning 中把 CodeQL 切换到 Advanced 模式,让 GitHub 承认本文件;Default 与 Advanced 二选一,不能共存。

AI 填充规则

按以下规则填写 <PROJECT><SCHEME> 占位符:

  • 在项目根及子目录(maxdepth 3)查找 *.xcodeproj:若 唯一 命中 Foo.xcodeproj,则 -project Foo.xcodeproj -scheme Foo(同名启发式)。
  • 若命中 多个 *.xcodeproj,或项目位于 *.xcworkspace 内,保留 <PROJECT>.xcodeproj / <SCHEME> 占位符不要自作主张,并在生成结果旁提示用户根据 xcodebuild -list 输出补值。
yaml
name: CodeQL

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'

jobs:
  analyze:
    name: Analyze (Swift)
    runs-on: macos-latest
    timeout-minutes: 30
    permissions:
      security-events: write
      contents: read
      actions: read
      packages: read

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v4
        with:
          languages: swift
          build-mode: manual

      # macOS runner 已预装 Xcode;如需锁定版本,取消下面注释:
      # - uses: maxim-lobanov/setup-xcode@v1
      #   with:
      #     xcode-version: latest-stable

      - name: Build with xcodebuild
        run: |
          xcodebuild build \
            -project <PROJECT>.xcodeproj \
            -scheme <SCHEME> \
            -configuration Debug \
            CODE_SIGNING_ALLOWED=NO \
            CODE_SIGNING_REQUIRED=NO \
            CODE_SIGN_IDENTITY=""

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v4
        with:
          category: "/language:swift"

本地 / 远程预跑

提交前可以用 act 在本地空跑一次 workflow 语法(不会真正执行 CodeQL 上传),或者推送到一个独立分支后用 gh CLI 远程触发:

bash
# 本地预跑(需要 Docker;CodeQL 步骤会被 mock 但能验证 yml 语法和 xcodebuild 参数)
act -W .github/workflows/codeql.yml -j analyze --container-architecture linux/amd64 --dryrun

# 远程触发(推送到分支后)
gh workflow run codeql.yml --ref <your-branch>
gh run watch

CodeQL workflow(Swift - SPM)

适用于仅有 Package.swift、没有 .xcodeproj 的纯 Swift Package Manager 项目。SPM 的 Default CodeQL 多数情况下能跑通,但 CodeQL swift extractor 在 2.25.x 对 SPM 的 Package.resolved / 依赖解析仍偶发问题;为统一判定规则、保证扫描结果的确定性,含 .swift 的 SPM 项目同样走 Advanced + 自带 workflow。

与上面 Xcode 工程模板的差异:构建命令换成 swift build,不需要任何 CODE_SIGN* 环境变量(SPM 产物默认 ad-hoc 签名,CodeQL trace 不要求真实证书);其余触发条件、runner、权限、timeout 完全一致。

AI 填充规则

按以下规则决定 swift build 是否带 --product

  • Package.swift 定义 单个 executable product mycliswift build --product mycli
  • Package.swift 定义 library products(无 executable):swift build不要--product
  • Package.swift 定义 多个 executable products:取 Package.swift 中第一个出现的 executable product 名作为 --product 的值。
yaml
name: CodeQL

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'

jobs:
  analyze:
    name: Analyze (Swift)
    runs-on: macos-latest
    timeout-minutes: 30
    permissions:
      security-events: write
      contents: read
      actions: read
      packages: read

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v4
        with:
          languages: swift
          build-mode: manual

      - name: Build with Swift Package Manager
        # 含 executable 时:swift build -c debug --product <PRODUCT>
        # 仅 library 时:  swift build -c debug   (删去 --product 段)
        run: swift build -c debug --product <PRODUCT>

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v4
        with:
          category: "/language:swift"

本地 / 远程预跑

bash
# 本地校验 yml 语法
act -W .github/workflows/codeql.yml -j analyze --container-architecture linux/amd64 --dryrun

# 推到分支后远程触发并实时观看日志
gh workflow run codeql.yml --ref <your-branch>
gh run watch

PyInstaller 多平台可执行文件

使用 PyInstaller 构建跨平台桌面应用时,需要注意各平台的打包差异:

macOS 注意事项

  • macOS GUI 应用必须使用 --onedir 生成 .app
  • 使用 --onefile 只会生成裸二进制文件,下载后可能无法直接运行
  • 建议将产物打包为 .zip 以保留文件权限和目录结构
yaml
name: Build and Release

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            artifact_name: myapp-linux.zip
          - os: windows-latest
            artifact_name: myapp-windows.exe
          - os: macos-latest
            artifact_name: myapp-macos.zip

    runs-on: ${{ matrix.os }}
    permissions:
      id-token: write
      contents: write
      attestations: write

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install Dependencies
        run: pip install pyinstaller

      # Linux: 单文件可执行,打包为 zip 保留权限
      - name: Build Executable (Linux)
        if: runner.os == 'Linux'
        run: |
          pyinstaller --onefile --name "myapp-linux" app.py
          mkdir -p dist_final
          chmod +x dist/myapp-linux
          cd dist && zip ../dist_final/myapp-linux.zip myapp-linux

      # macOS: 必须用 --onedir 生成 .app 包,再打包为 zip
      - name: Build Executable (macOS)
        if: runner.os == 'macOS'
        run: |
          pyinstaller --onedir --windowed --name "myapp" app.py
          mkdir -p dist_final
          cd dist && zip -r ../dist_final/myapp-macos.zip myapp.app

      # Windows: 直接生成 .exe
      - name: Build Executable (Windows)
        if: runner.os == 'Windows'
        run: |
          pyinstaller --onefile --windowed --name "myapp-windows" app.py
          mkdir dist_final
          copy dist\myapp-windows.exe dist_final\

      - name: Attest Build Provenance
        uses: actions/attest-build-provenance@v2
        with:
          subject-path: 'dist_final/*'

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.artifact_name }}
          path: dist_final/*

  release:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - name: Download All Artifacts
        uses: actions/download-artifact@v4
        with:
          path: artifacts
          merge-multiple: true

      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          files: artifacts/*
          generate_release_notes: true

用户使用说明

平台下载文件使用方式
macOSmyapp-macos.zip解压后双击 myapp.app
Linuxmyapp-linux.zip解压后运行 ./myapp-linux
Windowsmyapp-windows.exe直接双击运行

Electron 应用示例

yaml
name: Release Electron App

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]

    runs-on: ${{ matrix.os }}
    permissions:
      id-token: write
      contents: write
      attestations: write

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install Dependencies
        run: npm ci

      - name: Build Electron App
        run: npm run electron:build

      - name: Attest
        uses: actions/attest-build-provenance@v2
        with:
          subject-path: |
            dist/*.exe
            dist/*.dmg
            dist/*.AppImage

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: electron-${{ matrix.os }}
          path: |
            dist/*.exe
            dist/*.dmg
            dist/*.AppImage

  release:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/download-artifact@v4
        with:
          path: dist
          merge-multiple: true

      - uses: softprops/action-gh-release@v2
        with:
          files: dist/*

使用说明

  1. 选择适合你项目的模板
  2. 复制到 .github/workflows/release.yml
  3. 根据项目需要修改构建步骤
  4. 推送代码并创建 tag 触发构建