🔀 Merge pull request #18 from ChenXu233/dev #35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Flutter Multi-Platform Release | |
| on: | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| env: | |
| FLUTTER_VERSION: "3.38.4" | |
| BUILD_DIR: "build" | |
| ANDROID_KEYSTORE_PATH: "android/app/keystore.jks" | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| jobs: | |
| bump-version: | |
| name: Auto-Generate Version Tag | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # 允许删除/推送标签 | |
| outputs: | |
| new_tag: ${{ steps.tag-generation.outputs.NEW_TAG }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # 需要完整历史记录检查标签 | |
| persist-credentials: true | |
| - name: Setup Git user | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| - name: Get Flutter project version | |
| id: get-version | |
| run: | | |
| VERSION=$(grep '^version:' pubspec.yaml | awk '{print $2}' | tr -d '\n') | |
| echo "PROJECT_VERSION=$VERSION" >> $GITHUB_OUTPUT | |
| - name: Generate and push Git tag | |
| id: tag-generation | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -e | |
| NEW_TAG="v${{ steps.get-version.outputs.PROJECT_VERSION }}" | |
| echo "Generating tag: $NEW_TAG" | |
| # Delete local tag if exists | |
| if git rev-parse "$NEW_TAG" >/dev/null 2>&1; then | |
| git tag -d "$NEW_TAG" || true | |
| fi | |
| # Delete remote tag if exists | |
| if git ls-remote --tags origin "$NEW_TAG" | grep -q "$NEW_TAG"; then | |
| git push origin --delete "refs/tags/$NEW_TAG" || true | |
| fi | |
| # Create and push new tag | |
| git tag "$NEW_TAG" | |
| git push origin "$NEW_TAG" | |
| echo "NEW_TAG=$NEW_TAG" >> $GITHUB_OUTPUT | |
| build-ios: | |
| name: Build iOS | |
| runs-on: macos-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| flutter-version: ${{ env.FLUTTER_VERSION }} | |
| cache: true | |
| - name: Verify Flutter & Dart | |
| run: | | |
| flutter --version | |
| dart --version | |
| - name: Build iOS | |
| run: | | |
| cd . | |
| flutter pub get | |
| flutter build ios --release --no-codesign | |
| cd build/ios/iphoneos | |
| zip -r ios-release.zip Runner.app | |
| - name: Upload iOS Artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ios-artifacts | |
| path: build/ios/iphoneos/ios-release.zip | |
| build-macos: | |
| name: Build macOS | |
| runs-on: macos-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| flutter-version: ${{ env.FLUTTER_VERSION }} | |
| cache: true | |
| - name: Verify Flutter & Dart | |
| run: | | |
| flutter --version | |
| dart --version | |
| - name: Build macOS | |
| run: | | |
| cd . | |
| flutter pub get | |
| flutter build macos --release | |
| cd build/macos/Build/Products/Release | |
| zip -r macos-release.zip *.app | |
| - name: Upload macOS Artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-artifacts | |
| path: build/macos/Build/Products/Release/macos-release.zip | |
| build-android-with-impeller: | |
| name: Build Android (With Impeller) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| env: | |
| JAVA_VERSION: "17" | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Java | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: "zulu" | |
| java-version: ${{ env.JAVA_VERSION }} | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| flutter-version: ${{ env.FLUTTER_VERSION }} | |
| cache: true | |
| - name: Verify Flutter & Dart | |
| run: | | |
| flutter --version | |
| dart --version | |
| - name: Install Dependencies | |
| run: | | |
| sudo apt update -y | |
| sudo apt install -y ninja-build libgtk-3-dev clang cmake libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio | |
| - name: Configure Android Signing | |
| env: | |
| KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| STORE_PASS: ${{ secrets.ANDROID_STORE_PASSWORD }} | |
| KEY_PASS: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| run: | | |
| set -e | |
| echo "Decoding keystore..." | |
| if [ ! -f android/app/keystore.jks ] || ! cmp -s <(echo "$KEYSTORE_BASE64") <(base64 -w0 android/app/keystore.jks 2>/dev/null); then | |
| echo "$KEYSTORE_BASE64" | base64 -d > android/app/keystore.jks | |
| chmod 600 android/app/keystore.jks | |
| echo "Keystore decoded and permissions set." | |
| else | |
| echo "Keystore already exists and matches secret." | |
| fi | |
| echo "Generating keystore.properties..." | |
| { | |
| echo "storePassword=$STORE_PASS" | |
| echo "keyPassword=$KEY_PASS" | |
| echo "keyAlias=$KEY_ALIAS" | |
| echo "storeFile=keystore.jks" | |
| } > android/keystore.properties | |
| chmod 600 android/keystore.properties | |
| echo "keystore.properties generated." | |
| - name: Build APK with Impeller | |
| run: | | |
| cd . | |
| flutter pub get | |
| flutter build apk --release --flavor with_impeller | |
| - name: Upload Impeller Artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-with-impeller | |
| path: build/app/outputs/flutter-apk/app-with_impeller-release.apk | |
| build-android-without-impeller: | |
| name: Build Android (Without Impeller) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| env: | |
| JAVA_VERSION: "17" | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Java | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: "zulu" | |
| java-version: ${{ env.JAVA_VERSION }} | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| flutter-version: ${{ env.FLUTTER_VERSION }} | |
| cache: true | |
| - name: Verify Flutter & Dart | |
| run: | | |
| flutter --version | |
| dart --version | |
| - name: Install Dependencies | |
| run: | | |
| sudo apt update -y | |
| sudo apt install -y ninja-build libgtk-3-dev clang cmake libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio | |
| - name: Configure Android Signing | |
| env: | |
| KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| STORE_PASS: ${{ secrets.ANDROID_STORE_PASSWORD }} | |
| KEY_PASS: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| run: | | |
| set -e | |
| echo "Decoding keystore..." | |
| if [ ! -f android/app/keystore.jks ] || ! cmp -s <(echo "$KEYSTORE_BASE64") <(base64 -w0 android/app/keystore.jks 2>/dev/null); then | |
| echo "$KEYSTORE_BASE64" | base64 -d > android/app/keystore.jks | |
| chmod 600 android/app/keystore.jks | |
| echo "Keystore decoded and permissions set." | |
| else | |
| echo "Keystore already exists and matches secret." | |
| fi | |
| echo "Generating keystore.properties..." | |
| { | |
| echo "storePassword=$STORE_PASS" | |
| echo "keyPassword=$KEY_PASS" | |
| echo "keyAlias=$KEY_ALIAS" | |
| echo "storeFile=keystore.jks" | |
| } > android/keystore.properties | |
| chmod 600 android/keystore.properties | |
| echo "keystore.properties generated." | |
| - name: Build APK without Impeller | |
| run: | | |
| cd . | |
| flutter pub get | |
| flutter build apk --release --flavor without_impeller | |
| - name: Upload Non-Impeller Artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-without-impeller | |
| path: build/app/outputs/flutter-apk/app-without_impeller-release.apk | |
| build-web: | |
| name: Build Web | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| flutter-version: ${{ env.FLUTTER_VERSION }} | |
| cache: true | |
| - name: Verify Flutter & Dart | |
| run: | | |
| flutter --version | |
| dart --version | |
| - name: Build Web | |
| run: | | |
| cd . | |
| flutter pub get | |
| flutter build web --release | |
| cd build/web | |
| zip -r web-release.zip . | |
| - name: Upload Web Artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: web-artifacts | |
| path: build/web/web-release.zip | |
| build-linux: | |
| name: Build Linux | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| flutter-version: ${{ env.FLUTTER_VERSION }} | |
| cache: true | |
| - name: Verify Flutter & Dart | |
| run: | | |
| flutter --version | |
| dart --version | |
| - name: Install Dependencies | |
| run: | | |
| sudo apt update -y | |
| sudo apt install -y ninja-build libgtk-3-dev clang cmake libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio # 跨平台编译依赖 | |
| - name: Build Linux | |
| run: | | |
| cd . | |
| flutter pub get | |
| flutter build linux --release | |
| chmod +x build/linux/x64/release/bundle/* | |
| - name: Package Linux bundle (zip & tar.gz & AppImage) | |
| run: | | |
| cd build/linux/x64/release | |
| set +e | |
| echo "Creating zip archive..." | |
| zip -r linux-x64-release.zip bundle | |
| set -e | |
| echo "Creating tar.gz archive..." | |
| tar -czf linux-x64-release.tar.gz bundle | |
| echo "Archive sizes:" | |
| ls -lh linux-x64-release.* || true | |
| # Create a minimal AppDir and AppImage | |
| APPDIR=AppDir | |
| rm -rf "$APPDIR" | |
| mkdir -p "$APPDIR/usr/bin" | |
| cp -r bundle/* "$APPDIR/usr/bin/" | |
| # find an executable binary inside AppDir (first match) | |
| EXE=$(find "$APPDIR/usr/bin" -maxdepth 1 -type f -executable | head -n 1 || true) | |
| if [ -z "$EXE" ]; then | |
| # try to find any ELF file | |
| EXE=$(find "$APPDIR/usr/bin" -maxdepth 1 -type f -exec file --brief --mime-type {} \; | awk -F: '/application\/x-executable|application\/x-mach-binary|application\/x-pie-executable/{print NR}' | head -n1 || true) | |
| fi | |
| # Create AppRun wrapper (use printf to avoid YAML parsing issues) | |
| printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'exec "$HERE/usr/bin/"* "$@"' > "$APPDIR/AppRun" | |
| chmod +x "$APPDIR/AppRun" | |
| # Minimal .desktop (use printf to avoid YAML/heredoc issues) | |
| printf '%s\n' '[Desktop Entry]' 'Name=ToDoTimeSquare' 'Exec=AppRun' 'Type=Application' 'Categories=Utility;' > "$APPDIR/ToDoTimeSquare.desktop" | |
| # Download appimagetool and build AppImage | |
| APPIMAGE_TOOL=appimagetool-x86_64.AppImage | |
| wget -q -O "$APPIMAGE_TOOL" "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage" | |
| chmod +x "$APPIMAGE_TOOL" | |
| ./"$APPIMAGE_TOOL" "$APPDIR" ToDoTimeSquare.AppImage || true | |
| # 确保总是成功退出 | |
| exit 0 | |
| shell: bash | |
| - name: Upload Linux Artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-artifacts | |
| path: | | |
| build/linux/x64/release/linux-x64-release.zip | |
| build/linux/x64/release/linux-x64-release.tar.gz | |
| build/linux/x64/release/ToDoTimeSquare.AppImage | |
| build-windows: | |
| name: Build Windows | |
| runs-on: windows-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| flutter-version: ${{ env.FLUTTER_VERSION }} | |
| cache: true | |
| - name: Verify Flutter & Dart | |
| run: | | |
| flutter --version | |
| dart --version | |
| - name: Build Windows | |
| run: | | |
| cd . | |
| flutter pub get | |
| flutter build windows --release | |
| - name: Zip Windows bundle | |
| run: | | |
| pwsh -Command "Compress-Archive -Path 'build/windows/x64/runner/Release/*' -DestinationPath 'build/windows/x64/runner/Release/windows-release.zip' -Force" | |
| shell: pwsh | |
| - name: Upload Windows ZIP | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-artifacts | |
| path: build/windows/x64/runner/Release/windows-release.zip | |
| - name: Install Inno Setup | |
| run: | | |
| choco install innosetup -y | |
| - name: Generate Inno Setup Script | |
| shell: pwsh | |
| run: | | |
| $version = (Select-String '^version:' pubspec.yaml).Line.Split(':')[1].Trim() | |
| Set-Content -Path installer.iss -Value @" | |
| [Setup] | |
| AppName=ToDoTimeSquare | |
| AppVersion=$version | |
| DefaultDirName={pf}\ToDoTimeSquare | |
| DefaultGroupName=ToDoTimeSquare | |
| OutputDir=build\windows\installer | |
| OutputBaseFilename=ToDoTimeSquareInstaller | |
| Compression=lzma | |
| SolidCompression=yes | |
| SetupIconFile=windows\runner\resources\app_icon.ico | |
| [Files] | |
| Source: "build\windows\x64\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs | |
| [Icons] | |
| Name: "{group}\ToDoTimeSquare"; Filename: "{app}\ToDoTimeSquare.exe" | |
| Name: "{group}\Uninstall ToDoTimeSquare"; Filename: "{uninstallexe}" | |
| [Run] | |
| Filename: "{app}\todo_time_square.exe"; Description: "Launch ToDoTimeSquare"; Flags: nowait postinstall skipifsilent | |
| "@ | |
| - name: Generate Installer | |
| run: | | |
| iscc installer.iss | |
| shell: cmd | |
| - name: Upload Installer | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-installer | |
| path: build/windows/installer/ToDoTimeSquareInstaller.exe | |
| release: | |
| name: Create GitHub Release | |
| needs: | |
| - bump-version | |
| - build-android-with-impeller | |
| - build-android-without-impeller | |
| - build-web | |
| - build-linux | |
| - build-windows | |
| - build-ios | |
| - build-macos | |
| if: ${{ always() && needs.bump-version.result == 'success' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download All Artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: release-artifacts | |
| - name: List files for debugging | |
| run: ls -R release-artifacts | |
| - name: Publish Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.bump-version.outputs.new_tag }} | |
| name: 🔖${{ needs.bump-version.outputs.new_tag }} | |
| body: ${{ github.event.head_commit.message }} | |
| files: | | |
| release-artifacts/**/*.apk | |
| release-artifacts/**/*.zip | |
| release-artifacts/**/*.tar.gz | |
| release-artifacts/**/*.AppImage | |
| release-artifacts/**/*.exe |