Skip to content

release: v0.4.0

release: v0.4.0 #63

Workflow file for this run

# BuSic Release CI — 在推送 v* tag 时构建全平台 release 并发布到 GitHub Releases
#
# 使用方法:
# 1. 更新 pubspec.yaml 中的 version 字段 (如 0.2.0+2)
# 2. git add . && git commit -m "release: v0.2.0"
# 3. git tag v0.2.0
# 4. git push origin main --tags
# → 自动触发此工作流, 构建全部平台并创建 GitHub Release
name: Release
on:
# 推送 v* tag 时自动触发
push:
tags:
- 'v*'
# 手动强制触发(跳过 CI 门禁)
workflow_dispatch:
inputs:
tag:
description: '要发布的 Tag(如 v0.3.4)'
required: true
type: string
# 确保同时只有一个 release 在跑
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write # 创建 Release 需要写权限
env:
FLUTTER_VERSION: '3.29.1'
jobs:
# ───────────────────── 前置检查: 等待 CI 通过 + 解析 tag ─────────────
check:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.resolve-tag.outputs.tag }}
steps:
- name: Resolve tag
id: resolve-tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
else
# push event: GITHUB_REF = refs/tags/v0.3.4
echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
fi
- name: Wait for CI to pass
if: github.event_name != 'workflow_dispatch'
uses: actions/github-script@v7
with:
script: |
const sha = context.sha;
const owner = context.repo.owner;
const repo = context.repo.repo;
const workflowName = 'CI';
const maxWait = 15 * 60 * 1000; // 最多等 15 分钟
const interval = 20 * 1000; // 每 20 秒查一次
const start = Date.now();
core.info(`Waiting for "${workflowName}" on commit ${sha} ...`);
while (Date.now() - start < maxWait) {
const runs = await github.rest.actions.listWorkflowRunsForRepo({
owner, repo,
head_sha: sha,
per_page: 10,
});
const ciRun = runs.data.workflow_runs.find(r => r.name === workflowName);
if (ciRun) {
core.info(`CI status: ${ciRun.status}, conclusion: ${ciRun.conclusion}`);
if (ciRun.status === 'completed') {
if (ciRun.conclusion === 'success') {
core.info('CI passed ✓');
return;
}
core.setFailed(`CI finished with conclusion: ${ciRun.conclusion}`);
return;
}
} else {
core.info('CI run not found yet, waiting...');
}
await new Promise(r => setTimeout(r, interval));
}
core.setFailed('Timed out waiting for CI');
# ─────────────────────────────── Linux ───────────────────────────────
build-linux:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.check.outputs.tag }}
- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y clang cmake ninja-build pkg-config \
libgtk-3-dev liblzma-dev libstdc++-12-dev \
libayatana-appindicator3-dev
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
channel: stable
cache: true
- name: Install dependencies & generate code
run: |
flutter pub get
dart run build_runner build --delete-conflicting-outputs
- name: Build Linux
run: flutter build linux --release
- name: Install Ruby and fpm
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
- name: Install fpm
run: sudo gem install fpm
- name: Prepare package directory
run: |
mkdir -p package/opt/busic
cp -r build/linux/x64/release/bundle/* package/opt/busic/
cp scripts/linux/postinst.sh package/
cp scripts/linux/prerm.sh package/
mkdir -p package/usr/share/icons/hicolor/256x256/apps
cp assets/images/app_icon.png package/usr/share/icons/hicolor/256x256/apps/busic.png
- name: Create .deb package
run: |
cd package
fpm -t deb -s dir \
-n busic \
-v ${{ needs.check.outputs.tag }} \
-p ${{ github.workspace }}/busic-linux-x64.deb \
--after-install postinst.sh \
--before-remove prerm.sh \
--depends libgtk-3-0 \
--depends libayatana-appindicator3-1 \
--description "A high-quality cross-platform music player" \
--category Audio \
--url "https://github.com/GlowLED/BuSic" \
--maintainer "BuSic Team" \
.
- name: Create .rpm package
run: |
cd package
fpm -t rpm -s dir \
-n busic \
-v ${{ needs.check.outputs.tag }} \
-p ${{ github.workspace }}/busic-linux-x64.rpm \
--after-install postinst.sh \
--before-remove prerm.sh \
--depends gtk3 \
--depends libappindicator-gtk3 \
--description "A high-quality cross-platform music player" \
--category Application:Audio \
--url "https://github.com/GlowLED/BuSic" \
.
- name: Download appimagetool
run: |
wget -q https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool-x86_64.AppImage
- name: Create AppImage
run: |
# Prepare AppDir
mkdir -p AppDir/usr/bin
mkdir -p AppDir/usr/share/applications
mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps
# Copy application files
cp -r build/linux/x64/release/bundle/* AppDir/usr/
# Create symlink for main executable
ln -sf ../lib/busic AppDir/usr/bin/busic
# Copy icon to BOTH locations (root for appimagetool, usr/share for runtime)
cp assets/images/app_icon.png AppDir/busic.png
cp assets/images/app_icon.png AppDir/usr/share/icons/hicolor/256x256/apps/busic.png
# Copy .desktop file to BOTH locations (root for appimagetool, usr/share for runtime)
cp scripts/appimage/AppDir/busic.desktop AppDir/
cp scripts/appimage/AppDir/busic.desktop AppDir/usr/share/applications/
# Download AppRun from AppImage community
wget -q -O AppRun https://github.com/AppImage/AppImageKit/releases/download/continuous/AppRun-x86_64
chmod +x AppRun
# Build AppImage
ARCH=x86_64 ./appimagetool-x86_64.AppImage --appimage-extract 2>/dev/null
ARCH=x86_64 ./squashfs-root/AppRun AppDir ${{ github.workspace }}/busic-linux-x64.AppImage
- name: Upload Linux packages
uses: actions/upload-artifact@v4
with:
name: linux-release
path: |
busic-linux-x64.deb
busic-linux-x64.rpm
busic-linux-x64.AppImage
# ─────────────────────────────── Windows ─────────────────────────────
build-windows:
needs: check
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.check.outputs.tag }}
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
channel: stable
cache: true
- name: Install dependencies & generate code
run: |
flutter pub get
dart run build_runner build --delete-conflicting-outputs
- name: Build Windows
run: flutter build windows --release
- name: Package Windows
shell: pwsh
run: |
Compress-Archive -Path build\windows\x64\runner\Release\* `
-DestinationPath busic-windows-x64.zip
- uses: actions/upload-artifact@v4
with:
name: windows-release
path: busic-windows-x64.zip
# ─────────────────────────────── macOS ───────────────────────────────
build-macos:
needs: check
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.check.outputs.tag }}
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
channel: stable
cache: true
- name: Install dependencies & generate code
run: |
flutter pub get
dart run build_runner build --delete-conflicting-outputs
- name: Build macOS
run: flutter build macos --release
- name: Package macOS
run: |
cd build/macos/Build/Products/Release
zip -r ${{ github.workspace }}/busic-macos.zip busic.app
- uses: actions/upload-artifact@v4
with:
name: macos-release
path: busic-macos.zip
# ─────────────────────────────── Android ─────────────────────────────
build-android:
needs: check
runs-on: ubuntu-latest
env:
ANDROID_NDK_VERSION: '27.0.12077973'
ANDROID_CMAKE_VERSION: '3.22.1'
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.check.outputs.tag }}
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
# Pre-install NDK & CMake via sdkmanager so Gradle doesn't download
# them on every build (~1 GB NDK + CMake, takes 5-8 min without cache).
- name: Install Android NDK & CMake
run: |
yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \
"ndk;${{ env.ANDROID_NDK_VERSION }}" \
"cmake;${{ env.ANDROID_CMAKE_VERSION }}" \
--sdk_root=$ANDROID_HOME 2>/dev/null || true
# Cache NDK + CMake across runs (keyed by version)
- name: Cache Android NDK & CMake
uses: actions/cache@v4
with:
path: |
${{ env.ANDROID_HOME }}/ndk/${{ env.ANDROID_NDK_VERSION }}
${{ env.ANDROID_HOME }}/cmake/${{ env.ANDROID_CMAKE_VERSION }}
key: android-ndk-${{ env.ANDROID_NDK_VERSION }}-cmake-${{ env.ANDROID_CMAKE_VERSION }}
# Cache Gradle wrapper, dependencies, and build cache
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
channel: stable
cache: true
- name: Install dependencies & generate code
run: |
flutter pub get
dart run build_runner build --delete-conflicting-outputs
- name: Decode keystore
if: env.KEYSTORE_BASE64 != ''
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
run: |
echo "$KEYSTORE_BASE64" | base64 -d > android/upload-keystore.jks
- name: Build APK
env:
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
run: flutter build apk --release
- name: Rename APK
run: |
cp build/app/outputs/flutter-apk/app-release.apk busic-android.apk
- uses: actions/upload-artifact@v4
with:
name: android-release
path: busic-android.apk
# ──────────────────────────────── iOS ────────────────────────────────
build-ios:
needs: check
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.check.outputs.tag }}
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
channel: stable
cache: true
- name: Install dependencies & generate code
run: |
flutter pub get
dart run build_runner build --delete-conflicting-outputs
- name: Build iOS (no codesign)
run: flutter build ios --release --no-codesign
- name: Package IPA (unsigned)
run: |
mkdir -p Payload
cp -r build/ios/iphoneos/Runner.app Payload/
zip -r busic-ios-unsigned.ipa Payload
- uses: actions/upload-artifact@v4
with:
name: ios-release
path: busic-ios-unsigned.ipa
# ───────────────────────── Publish GitHub Release ────────────────────
publish:
needs: [check, build-linux, build-windows, build-macos, build-android, build-ios]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
merge-multiple: true
- name: Generate checksums
run: sha256sum busic-* > checksums.sha256
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.check.outputs.tag }}
generate_release_notes: true
files: |
busic-linux-x64.deb
busic-linux-x64.rpm
busic-linux-x64.AppImage
busic-windows-x64.zip
busic-macos.zip
busic-android.apk
busic-ios-unsigned.ipa
checksums.sha256