Flutter Nightly Build CI/CD #80
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 Nightly Build CI/CD | |
| # 触发条件配置 | |
| on: | |
| workflow_dispatch: # 允许手动触发工作流,便于管理和测试 | |
| schedule: | |
| - cron: "0 0 * * *" # 每天UTC时间0点触发(即北京时间8点),实现夜间构建 | |
| # 授予 contents 写权限 | |
| permissions: | |
| contents: write | |
| jobs: | |
| # 在 Ubuntu 环境中运行,避免了 macOS 的 sed 问题 | |
| # 更新 pubspec.yaml 中的版本号 | |
| # 将新版本号作为输出,供其他 job 使用 | |
| build_env: # 更新pubspec.yaml中的版本号: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| changes_detected: ${{ steps.check_changes.outputs.changes_detected || 'true' }} | |
| steps: | |
| # 检出代码 | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # 获取全部历史,以便计算版本号 | |
| # 检查是否有代码变更(仅适用于夜间构建) | |
| - name: Check for code changes (nightly only) | |
| id: check_changes | |
| if: ${{ github.event_name == 'schedule' }} | |
| run: | | |
| # 获取24小时前的时间 | |
| YESTERDAY=$(date -d "-24 hours" --iso-8601=seconds) | |
| echo "Checking changes since $YESTERDAY" | |
| # 检查是否有提交 | |
| if git log --since="$YESTERDAY" --oneline | grep -q .; then | |
| echo "Found code changes" | |
| echo "changes_detected=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "No code changes detected since yesterday" | |
| echo "changes_detected=false" >> $GITHUB_OUTPUT | |
| fi | |
| # 生成版本号 | |
| - name: Generate Version Number | |
| id: generate_version | |
| run: | | |
| # 检查是否是定时触发(夜间构建) | |
| IS_NIGHTLY=false | |
| if [[ "${{ github.event_name }}" == "schedule" ]]; then | |
| IS_NIGHTLY=true | |
| echo "This is a nightly build triggered by schedule" | |
| fi | |
| # 标签触发 (e.g. v1.0或1.0) 移除 refs/tags/ 前缀和可选的 'v' 前缀 | |
| if [[ ${{ github.ref }} == refs/tags/* ]]; then | |
| TAG_VERSION=$(echo ${{ github.ref }} | sed -E 's/^refs\/tags\/(v)?//') | |
| echo "This is a tag release: $TAG_VERSION" | |
| # 分支触发 (e.g. main或feature/new-feature) 移除 refs/heads/ 前缀 | |
| elif [[ ${{ github.ref }} == refs/heads/* ]]; then | |
| TAG_VERSION=$(echo ${{ github.ref }} | sed -E 's/^refs\/heads\///' | sed 's/\//-/g') | |
| echo "This is a branch push: $TAG_VERSION" | |
| # Pull Request触发 (e.g. pr-123) 提取PR的编号 pr-<number> | |
| elif [[ ${{ github.ref }} == refs/pull/* ]]; then | |
| PR_NUMBER=$(echo ${{ github.ref }} | sed -E 's/^refs\/pull\/([0-9]+)\/merge$/\1/') | |
| TAG_VERSION="pr-$PR_NUMBER" | |
| echo "This is a Pull Request: $TAG_VERSION" | |
| # 其他情况:直接使用 github.ref 的值 包含任何 "/"会被替换为 "-" | |
| else | |
| TAG_VERSION=$(echo ${{ github.ref }} | sed 's/\//-/g') | |
| echo "This is another trigger: $TAG_VERSION" | |
| fi | |
| echo "TAG_VERSION=$TAG_VERSION" | |
| COMMIT_COUNT=$(git rev-list --count HEAD) # 计算提交数量 | |
| SHORT_HASH=$(git rev-parse --short HEAD) # 获取最近一次提交的短哈希 | |
| # 生成夜间构建版本号:nightly-YYYYMMDD-commitcount | |
| if $IS_NIGHTLY; then | |
| NIGHTLY_DATE=$(date +%Y%m%d) | |
| BUILD_VERSION="nightly-${NIGHTLY_DATE}-${COMMIT_COUNT}" | |
| else | |
| BUILD_VERSION="${COMMIT_COUNT}" | |
| fi | |
| echo "BUILD_VERSION=${BUILD_VERSION}" >> $GITHUB_OUTPUT # 设置输出变量 | |
| echo "Generated BUILD_VERSION: ${BUILD_VERSION}" # 打印完整的构建版本号 | |
| shell: bash | |
| # 更新pubspec.yaml中的版本号 | |
| - name: Update version in pubspec.yaml | |
| id: update_version_in_pubspec | |
| run: | | |
| # 从pubspec.yaml中提取主版本号,(e.g. version: 1.0.0+1,提取1.0.0) | |
| MAIN_VERSION=$(grep "^version:" pubspec.yaml | sed -E 's/version: ([0-9]+\.[0-9]+\.[0-9]+).*/\1/') | |
| echo "MAIN_VERSION=${MAIN_VERSION}" | |
| # 组合新的完整版本号 (e.g. 1.0.0+34) | |
| FULL_VERSION="${MAIN_VERSION}+${{ steps.generate_version.outputs.BUILD_VERSION }}" | |
| echo "FULL_VERSION=${FULL_VERSION}" | |
| # 更新新版本号到pubspec.yaml文件 | |
| sed -i "s/^version: .*/version: ${FULL_VERSION}/" pubspec.yaml | |
| echo "Updated pubspec.yaml content:" | |
| cat pubspec.yaml | |
| # 验证更新 | |
| if grep -q "^version: ${FULL_VERSION}" pubspec.yaml; then | |
| echo "Version updated successfully to ${FULL_VERSION}" | |
| else | |
| echo "Failed to update version" | |
| echo "Current version line:" | |
| grep "^version:" pubspec.yaml | |
| exit 1 | |
| fi | |
| echo "FULL_VERSION=${FULL_VERSION}" > _build_env.config | |
| shell: bash | |
| - name: Upload updated pubspec.yaml | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build_env_files | |
| path: | | |
| pubspec.yaml | |
| _build_env.config | |
| build_app: | |
| needs: build_env | |
| runs-on: ${{ matrix.os }} # 使用矩阵策略中指定的操作系统进行构建 | |
| if: ${{ github.event_name != 'schedule' || needs.build_env.outputs.changes_detected == 'true' }} | |
| strategy: | |
| matrix: | |
| include: | |
| # 定义不同的构建任务,每个任务对应一个平台 | |
| - name: android | |
| os: ubuntu-latest | |
| - name: android-aab | |
| os: ubuntu-latest | |
| # - name: web | |
| # os: ubuntu-latest | |
| - name: linux | |
| os: ubuntu-latest | |
| - name: windows | |
| os: windows-latest | |
| # - name: ios | |
| # os: macos-latest | |
| - name: macos | |
| os: macos-latest | |
| steps: | |
| # 检出代码 | |
| - uses: actions/checkout@v4 | |
| - name: Download updated pubspec.yaml | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: build_env_files | |
| - name: Replace pubspec.yaml | |
| run: | | |
| ls | |
| cat pubspec.yaml | |
| pwd | |
| cat _build_env.config | |
| shell: bash | |
| - name: Read FULL_VERSION from _build_env.config | |
| id: read_build_env | |
| run: | | |
| echo "Reading version from _build_env.config..." | |
| FULL_VERSION=$(grep 'FULL_VERSION=' _build_env.config | cut -d '=' -f2) | |
| echo "FULL_VERSION=${FULL_VERSION}" >> $GITHUB_OUTPUT # 设置输出变量 | |
| shell: bash | |
| # 设置Flutter环境 | |
| - name: Set up Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable # 使用Flutter的稳定版 | |
| # 显示Flutter版本信息 | |
| - name: Check Flutter version | |
| run: flutter --version | |
| # 安装Flutter依赖 | |
| - name: Install dependencies | |
| run: flutter pub get | |
| # [New Step] Generate signing file and configuration file | |
| - name: Create Keystore and Properties file | |
| if: matrix.name == 'android' || matrix.name == 'android-aab' | |
| env: | |
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| PROPS_STORE_PASS: ${{ secrets.ANDROID_STORE_PASSWORD }} | |
| PROPS_KEY_PASS: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| PROPS_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| run: | | |
| # 1. Decode Base64 to restore the .jks file to the android/app directory | |
| echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > android/app/upload-keystore.jks | |
| # 2. Generate the key.properties file for Gradle to read | |
| printf "storePassword=%s\n" "$PROPS_STORE_PASS" > android/key.properties | |
| printf "keyPassword=%s\n" "$PROPS_KEY_PASS" >> android/key.properties | |
| printf "keyAlias=%s\n" "$PROPS_KEY_ALIAS" >> android/key.properties | |
| printf "storeFile=upload-keystore.jks\n" >> android/key.properties | |
| shell: bash | |
| # 根据矩阵配置,为不同平台执行构建命令 | |
| - name: Build Android APK | |
| if: matrix.name == 'android' | |
| run: | | |
| flutter build apk | |
| flutter build apk --split-per-abi | |
| - name: Build Android App Bundle | |
| if: matrix.name == 'android-aab' | |
| run: flutter build appbundle | |
| - name: Build Web | |
| if: matrix.name == 'web' | |
| run: flutter build web --base-href=/FlutterHub/ | |
| - name: Build Linux | |
| if: matrix.name == 'linux' | |
| run: | | |
| sudo apt-get update -y | |
| sudo apt-get install -y ninja-build libgtk-3-dev libasound2-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-good1.0-dev libsecret-1-dev | |
| flutter build linux | |
| - name: Build Windows | |
| if: matrix.name == 'windows' | |
| run: flutter build windows | |
| - name: Build iOS | |
| if: matrix.name == 'ios' | |
| run: flutter build ios --release --no-codesign | |
| - name: Build macOS | |
| if: matrix.name == 'macos' | |
| run: flutter build macos | |
| # 部署Web平台构建产物到GitHub Pages | |
| - name: Deploy to GitHub Pages | |
| if: matrix.name == 'web' | |
| uses: peaceiris/actions-gh-pages@v4 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| publish_dir: ./build/web | |
| publish_branch: gh-pages | |
| user_name: "github-actions[bot]" | |
| user_email: "github-actions[bot]@users.noreply.github.com" | |
| commit_message: "Deploy to GitHub Pages: ${{ steps.read_build_env.outputs.FULL_VERSION }}" | |
| # 根据平台压缩构建产物,为上传做准备 | |
| # 使用不同的命令和文件路径根据平台进行压缩 | |
| # 这里使用了不同的shell命令和条件判断来处理不同的操作系统和构建产物 | |
| - name: Compress Build | |
| run: | | |
| # 构建产物前缀 | |
| PREFIX="nightly" | |
| if [ "${{ matrix.name }}" = "android" ]; then | |
| mv build/app/outputs/flutter-apk/app-release.apk ./app-${PREFIX}-all-${{ steps.read_build_env.outputs.FULL_VERSION }}.apk | |
| mv build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk ./app-${PREFIX}-armeabi-v7a-${{ steps.read_build_env.outputs.FULL_VERSION }}.apk | |
| mv build/app/outputs/flutter-apk/app-arm64-v8a-release.apk ./app-${PREFIX}-arm64-v8a-${{ steps.read_build_env.outputs.FULL_VERSION }}.apk | |
| mv build/app/outputs/flutter-apk/app-x86_64-release.apk ./app-${PREFIX}-x86_64-${{ steps.read_build_env.outputs.FULL_VERSION }}.apk | |
| elif [ "${{ matrix.name }}" = "android-aab" ]; then | |
| mv build/app/outputs/bundle/release/app-release.aab ./app-${PREFIX}-${{ steps.read_build_env.outputs.FULL_VERSION }}.aab | |
| elif [ "${{ matrix.name }}" = "web" ]; then | |
| zip -r web-${PREFIX}-${{ steps.read_build_env.outputs.FULL_VERSION }}.zip build/web | |
| elif [ "${{ matrix.name }}" = "linux" ]; then | |
| zip -r linux-${PREFIX}-${{ steps.read_build_env.outputs.FULL_VERSION }}.zip build/linux/x64/release/bundle | |
| elif [ "${{ matrix.name }}" = "windows" ]; then | |
| powershell Compress-Archive build/windows/x64/runner/Release windows-${PREFIX}-${{ steps.read_build_env.outputs.FULL_VERSION }}.zip | |
| elif [ "${{ matrix.name }}" = "ios" ]; then | |
| mkdir Payload | |
| cp -r build/ios/iphoneos/Runner.app Payload/ | |
| zip -r ios-${PREFIX}-${{ steps.read_build_env.outputs.FULL_VERSION }}.ipa Payload/ | |
| zip -r ios-${PREFIX}-${{ steps.read_build_env.outputs.FULL_VERSION }}.zip build/ios/iphoneos | |
| elif [ "${{ matrix.name }}" = "macos" ]; then | |
| zip -r macos-${PREFIX}-${{ steps.read_build_env.outputs.FULL_VERSION }}.zip build/macos/Build/Products/Release | |
| fi | |
| shell: bash | |
| - name: Upload Build Artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.name }}-artifact | |
| path: | | |
| *.apk | |
| *.aab | |
| *.ipa | |
| *.zip | |
| if-no-files-found: error | |