name: 安装包 # 同步到 GitHub 后,这里编译并打出: # 注意:Gitea Actions 也会读 .github/workflows,不想在 Gitea 跑需要在 # Gitea 仓库设置里禁用 Actions / 该工作流(详见 README「打包(安装包)」)。 # Linux deb / rpm(x86_64、aarch64) # Windows 安装包(关联 .md)+ 便携 zip # macOS arm64 / x86_64 两个 dmg(未签名,不是公证版) on: push: branches: [main, master] tags: ["v*"] pull_request: workflow_dispatch: inputs: publish: description: "发布 Release(用 v<版本> 当 tag,不必先打 tag)" type: boolean default: false permissions: contents: write concurrency: group: packages-${{ github.ref }} cancel-in-progress: true env: QT_VERSION: "5.15.2" jobs: linux: name: Linux ${{ matrix.arch }} runs-on: ${{ matrix.runs-on }} continue-on-error: ${{ matrix.optional }} timeout-minutes: 30 strategy: fail-fast: false matrix: include: - arch: x86_64 runs-on: ubuntu-22.04 deb_arch: amd64 rpm_arch: x86_64 optional: false - arch: aarch64 runs-on: ubuntu-24.04-arm deb_arch: arm64 rpm_arch: aarch64 optional: true steps: - name: 检出代码 uses: actions/checkout@v4 - name: 安装构建依赖 run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ build-essential file \ qtbase5-dev qtbase5-dev-tools qt5-qmake \ rpm dpkg-dev - name: 读取版本号 id: meta run: | version=$(sed -n 's/^VERSION[[:space:]]*=[[:space:]]*//p' VoiletMark.pro | head -n1 | tr -d '[:space:]') if [ -z "$version" ]; then echo "VoiletMark.pro 里没有 VERSION" >&2 exit 1 fi echo "version=$version" >> "$GITHUB_OUTPUT" - name: 编译 run: | qmake_bin=$(command -v qmake-qt5 || command -v qmake || true) if [ -z "$qmake_bin" ]; then echo "找不到 qmake(确认装了 qtbase5-dev / qt5-qmake)" >&2 exit 1 fi "$qmake_bin" -v mkdir -p build cd build "$qmake_bin" ../VoiletMark.pro PREFIX=/usr CONFIG+=release make -j"$(nproc)" make install INSTALL_ROOT="$PWD/stage" find stage -type f | sort - name: 打 .deb run: ./packaging/build-deb.sh build/stage "${{ steps.meta.outputs.version }}" "${{ matrix.deb_arch }}" dist - name: 打 .rpm run: ./packaging/build-rpm.sh build/stage "${{ steps.meta.outputs.version }}" "${{ matrix.rpm_arch }}" dist - name: 检查产物 run: | ls -lh dist dpkg-deb -I dist/*.deb rpm -qpi dist/*.rpm - name: 上传产物 uses: actions/upload-artifact@v4 with: name: voiletmark-linux-${{ matrix.arch }} path: dist/* if-no-files-found: error windows: name: Windows x86_64 runs-on: windows-2022 timeout-minutes: 40 steps: - name: 检出代码 uses: actions/checkout@v4 - name: 准备 MSVC 环境 uses: ilammy/msvc-dev-cmd@v1 with: arch: x64 - name: 安装 Qt ${{ env.QT_VERSION }} uses: jurplel/install-qt-action@v4 with: version: ${{ env.QT_VERSION }} arch: win64_msvc2019_64 cache: true - name: 编译并收集 Qt 依赖 shell: bash run: | set -euo pipefail version=$(sed -n 's/^VERSION[[:space:]]*=[[:space:]]*//p' VoiletMark.pro | head -n1 | tr -d '[:space:]') if [ -z "$version" ]; then echo "VoiletMark.pro 里没有 VERSION" >&2 exit 1 fi echo "APP_VERSION=$version" >> "$GITHUB_ENV" mkdir -p build cd build # Git Bash 的 /usr/bin 里也有个 link.exe(coreutils 的硬链接工具), # 必须把 MSVC 的 bin 放到 PATH 最前面,nmake 才会用真正的 link.exe cl_path=$(command -v cl.exe || command -v cl || true) if [ -z "$cl_path" ]; then echo "PATH 里找不到 cl.exe" >&2 exit 1 fi msvc_bin=$(dirname "$cl_path") export PATH="$msvc_bin:$PATH" echo "cl: $(command -v cl)" echo "link: $(command -v link)" qmake ../VoiletMark.pro CONFIG+=release nmake release || nmake if [ -f release/VoiletMark.exe ]; then exe=release/VoiletMark.exe else exe=$(find . -name VoiletMark.exe | head -n1) fi if [ -z "$exe" ]; then echo "没找到 VoiletMark.exe" >&2 exit 1 fi mkdir -p ../deploy cp "$exe" ../deploy/VoiletMark.exe cd .. windeployqt --release --no-translations --compiler-runtime deploy/VoiletMark.exe find deploy -maxdepth 1 -type f | sort - name: 打 NSIS 安装包(关联 .md) shell: bash run: | set -euo pipefail mkdir -p dist makensis='/c/Program Files (x86)/NSIS/makensis.exe' if [ ! -x "$makensis" ]; then makensis=$(find '/c/Program Files (x86)' '/c/Program Files' -maxdepth 3 -iname 'makensis.exe' 2>/dev/null | head -n1) fi if [ -z "$makensis" ]; then choco install nsis -y --no-progress makensis=$(find '/c/Program Files (x86)' '/c/Program Files' -maxdepth 3 -iname 'makensis.exe' 2>/dev/null | head -n1) fi if [ -z "$makensis" ]; then echo "找不到 makensis(NSIS)" >&2 exit 1 fi output=$(cygpath -w "$PWD/dist/VoiletMark-$APP_VERSION-win64-setup.exe") deploy=$(cygpath -w "$PWD/deploy") icon=$(cygpath -w "$PWD/packaging/voiletmark.ico") script=$(cygpath -w "$PWD/packaging/windows/installer.nsi") # Git Bash(MSYS)会把 /D... 当成 Unix 路径,转换成 # C:/Program Files/Git/DAPP_VERSION=...;这里关掉参数转换 MSYS2_ARG_CONV_EXCL='*' "$makensis" \ "/DAPP_VERSION=$APP_VERSION" \ "/DDEPLOY_DIR=$deploy" \ "/DICON_FILE=$icon" \ "/DOUTPUT_FILE=$output" \ "$script" ls -lh dist - name: 便携版 zip shell: pwsh run: | Compress-Archive -Path deploy/* -DestinationPath "dist/VoiletMark-$env:APP_VERSION-win64-portable.zip" Get-ChildItem dist | Select-Object Name, Length - name: 上传产物 uses: actions/upload-artifact@v4 with: name: voiletmark-windows-x86_64 path: dist/* if-no-files-found: error macos-arm64: name: macOS arm64 (Apple Silicon) runs-on: macos-15 timeout-minutes: 50 env: HOMEBREW_NO_AUTO_UPDATE: "1" steps: - name: 检出代码 uses: actions/checkout@v4 - name: 安装 Qt 5(Homebrew,原生 arm64) run: | set -euo pipefail brew install qt@5 qt_prefix=$(brew --prefix qt@5) echo "QT_PREFIX=$qt_prefix" >> "$GITHUB_ENV" "$qt_prefix/bin/qmake" -v - name: 编译并打包 Qt 框架 run: | set -euo pipefail version=$(sed -n 's/^VERSION[[:space:]]*=[[:space:]]*//p' VoiletMark.pro | head -n1 | tr -d '[:space:]') if [ -z "$version" ]; then echo "VoiletMark.pro 里没有 VERSION" >&2 exit 1 fi echo "APP_VERSION=$version" >> "$GITHUB_ENV" mkdir -p build cd build "$QT_PREFIX/bin/qmake" ../VoiletMark.pro CONFIG+=release make -j"$(sysctl -n hw.ncpu)" file VoiletMark.app/Contents/MacOS/VoiletMark | tee /tmp/arch.txt grep -q arm64 /tmp/arch.txt "$QT_PREFIX/bin/macdeployqt" VoiletMark.app -always-overwrite # 只留平台插件和样式插件;虚拟键盘 / 打印 / 图片格式这些用不到, # 留着只会把 Homebrew 的 dylib 拖进来 rm -rf VoiletMark.app/Contents/PlugIns/virtualkeyboard \ VoiletMark.app/Contents/PlugIns/platforminputcontexts \ VoiletMark.app/Contents/PlugIns/printsupport \ VoiletMark.app/Contents/PlugIns/iconengines \ VoiletMark.app/Contents/PlugIns/imageformats \ VoiletMark.app/Contents/PlugIns/networkinformation \ VoiletMark.app/Contents/PlugIns/tls \ VoiletMark.app/Contents/PlugIns/bearer otool -L VoiletMark.app/Contents/MacOS/VoiletMark | head -30 - name: 收 Homebrew 依赖 + ad-hoc 签名 run: | set -euo pipefail ./packaging/macos/bundle-libs.sh build/VoiletMark.app xattr -cr build/VoiletMark.app codesign --force --deep --sign - build/VoiletMark.app codesign --verify --deep --strict build/VoiletMark.app - name: 打 dmg run: ./packaging/macos/build-dmg.sh build/VoiletMark.app "$APP_VERSION" arm64 dist - name: 上传产物 uses: actions/upload-artifact@v4 with: name: voiletmark-macos-arm64 path: dist/* if-no-files-found: error macos-x86_64: name: macOS x86_64 (Intel) # Intel 的 GitHub runner;拿不到这个 runner 时这一步会失败,不影响其它产物 runs-on: macos-15-intel timeout-minutes: 40 steps: - name: 检出代码 uses: actions/checkout@v4 - name: 安装 Qt ${{ env.QT_VERSION }} uses: jurplel/install-qt-action@v4 with: version: ${{ env.QT_VERSION }} arch: clang_64 cache: true - name: 编译并打包 Qt 框架 run: | set -euo pipefail version=$(sed -n 's/^VERSION[[:space:]]*=[[:space:]]*//p' VoiletMark.pro | head -n1 | tr -d '[:space:]') if [ -z "$version" ]; then echo "VoiletMark.pro 里没有 VERSION" >&2 exit 1 fi echo "APP_VERSION=$version" >> "$GITHUB_ENV" mkdir -p build cd build qmake ../VoiletMark.pro CONFIG+=release make -j"$(sysctl -n hw.ncpu)" file VoiletMark.app/Contents/MacOS/VoiletMark | tee /tmp/arch.txt grep -q x86_64 /tmp/arch.txt macdeployqt VoiletMark.app -always-overwrite rm -rf VoiletMark.app/Contents/PlugIns/virtualkeyboard \ VoiletMark.app/Contents/PlugIns/platforminputcontexts \ VoiletMark.app/Contents/PlugIns/printsupport \ VoiletMark.app/Contents/PlugIns/iconengines \ VoiletMark.app/Contents/PlugIns/imageformats \ VoiletMark.app/Contents/PlugIns/networkinformation \ VoiletMark.app/Contents/PlugIns/tls \ VoiletMark.app/Contents/PlugIns/bearer - name: 收依赖 + ad-hoc 签名 run: | set -euo pipefail ./packaging/macos/bundle-libs.sh build/VoiletMark.app xattr -cr build/VoiletMark.app codesign --force --deep --sign - build/VoiletMark.app codesign --verify --deep --strict build/VoiletMark.app - name: 打 dmg run: ./packaging/macos/build-dmg.sh build/VoiletMark.app "$APP_VERSION" x86_64 dist - name: 上传产物 uses: actions/upload-artifact@v4 with: name: voiletmark-macos-x86_64 path: dist/* if-no-files-found: error release: name: 发布到 Release needs: [linux, windows, macos-arm64, macos-x86_64] # 打 tag 或手动勾 publish 才发;普通推送/PR 只留 artifact。 # always() 是为了某个平台失败时其它产物还能发出来。 if: >- always() && ( startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && inputs.publish) ) runs-on: ubuntu-24.04 timeout-minutes: 10 steps: - name: 检出代码 uses: actions/checkout@v4 - name: 读取版本号 id: meta run: | version=$(sed -n 's/^VERSION[[:space:]]*=[[:space:]]*//p' VoiletMark.pro | head -n1 | tr -d '[:space:]') echo "version=$version" >> "$GITHUB_OUTPUT" - name: 下载所有产物 uses: actions/download-artifact@v4 with: path: dist merge-multiple: true - name: 检查产物 run: | ls -lh dist count=$(find dist -maxdepth 1 -type f | wc -l) if [ "$count" -eq 0 ]; then echo "一个安装包都没有,不发布" >&2 exit 1 fi - name: 生成 SHA256SUMS run: | cd dist find . -maxdepth 1 -type f ! -name SHA256SUMS -printf '%f\n' | sort > /tmp/files.txt xargs sha256sum < /tmp/files.txt > SHA256SUMS cat SHA256SUMS - name: 挂到 Release uses: softprops/action-gh-release@v2 with: # 打 tag 时用那个 tag;手动发布时用 v<版本>(不存在就按当前提交新建) tag_name: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || format('v{0}', steps.meta.outputs.version) }} target_commitish: ${{ github.sha }} name: VoiletMark ${{ steps.meta.outputs.version }} files: dist/* generate_release_notes: true fail_on_unmatched_files: true