diff --git a/.gitea/workflows/secrets.yml b/.gitea/workflows/secrets.yml index 17f9c6d..274ff40 100644 --- a/.gitea/workflows/secrets.yml +++ b/.gitea/workflows/secrets.yml @@ -24,7 +24,6 @@ env: RUST_BACKTRACE: short jobs: - # ========== 版本检查 + Release 预创建(单次运行,消除多 job 竞态)========== version: name: 版本 & Release runs-on: debian @@ -43,14 +42,17 @@ jobs: run: | version=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)".*/\1/') tag="secrets-${version}" - echo "version=${version}" >> $GITHUB_OUTPUT - echo "tag=${tag}" >> $GITHUB_OUTPUT + previous_tag=$(git tag --list 'secrets-*' --sort=-v:refname | grep -vx "$tag" | sed -n '1p') + + echo "version=${version}" >> "$GITHUB_OUTPUT" + echo "tag=${tag}" >> "$GITHUB_OUTPUT" + echo "previous_tag=${previous_tag}" >> "$GITHUB_OUTPUT" if git rev-parse "refs/tags/${tag}" >/dev/null 2>&1; then - echo "tag_exists=true" >> $GITHUB_OUTPUT + echo "tag_exists=true" >> "$GITHUB_OUTPUT" echo "版本 ${tag} 已存在" else - echo "tag_exists=false" >> $GITHUB_OUTPUT + echo "tag_exists=false" >> "$GITHUB_OUTPUT" echo "将创建新版本 ${tag}" fi @@ -62,39 +64,148 @@ jobs: git tag -a "${{ steps.ver.outputs.tag }}" -m "Release ${{ steps.ver.outputs.tag }}" git push origin "${{ steps.ver.outputs.tag }}" - - name: 预创建 Release + - name: 解析或创建 Release id: release - if: steps.ver.outputs.tag_exists == 'false' env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | if [ -z "$RELEASE_TOKEN" ]; then - echo "release_id=" >> $GITHUB_OUTPUT + echo "release_id=" >> "$GITHUB_OUTPUT" exit 0 fi - command -v jq &>/dev/null || (sudo apt-get update -qq && sudo apt-get install -y -qq jq) + command -v jq >/dev/null 2>&1 || (sudo apt-get update -qq && sudo apt-get install -y -qq jq) tag="${{ steps.ver.outputs.tag }}" - url="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases" - release_id=$(curl -sS -H "Authorization: token $RELEASE_TOKEN" \ - -H "Content-Type: application/json" -X POST "$url" \ - -d "{\"tag_name\":\"${tag}\",\"name\":\"${tag}\",\"body\":\"Release ${tag}\"}" \ - | jq -r '.id // empty') + version="${{ steps.ver.outputs.version }}" + release_api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases" + existing_release=$(curl -fsS -H "Authorization: token $RELEASE_TOKEN" \ + "${release_api}/tags/${tag}" || true) + release_id=$(printf '%s' "$existing_release" | jq -r '.id // empty') - echo "release_id=${release_id}" >> $GITHUB_OUTPUT + if [ -n "$release_id" ]; then + echo "release_id=${release_id}" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ "${{ steps.ver.outputs.tag_exists }}" = "true" ]; then + echo "release_id=" >> "$GITHUB_OUTPUT" + exit 0 + fi + + previous_tag="${{ steps.ver.outputs.previous_tag }}" + if [ -n "$previous_tag" ]; then + changes=$(git log --pretty=format:'- %s (%h)' "${previous_tag}..HEAD") + else + changes=$(git log --pretty=format:'- %s (%h)') + fi + if [ -z "$changes" ]; then + changes="- 首次发布" + fi + + cat > release-body.md <> "$GITHUB_OUTPUT" + + probe-runners: + name: 探测 Runner + runs-on: debian + outputs: + has_linux: ${{ steps.probe.outputs.has_linux }} + has_macos: ${{ steps.probe.outputs.has_macos }} + has_windows: ${{ steps.probe.outputs.has_windows }} + steps: + - name: 查询可用 Runner + id: probe + env: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + run: | + if [ -z "$RELEASE_TOKEN" ]; then + echo "has_linux=true" >> "$GITHUB_OUTPUT" + echo "has_macos=true" >> "$GITHUB_OUTPUT" + echo "has_windows=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + command -v jq >/dev/null 2>&1 || (sudo apt-get update -qq && sudo apt-get install -y -qq jq) + + runners=$(curl -fsS -H "Authorization: token $RELEASE_TOKEN" \ + "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/runners" || true) + + if [ -z "$runners" ]; then + echo "has_linux=true" >> "$GITHUB_OUTPUT" + echo "has_macos=true" >> "$GITHUB_OUTPUT" + echo "has_windows=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + has_runner() { + local label="$1" + printf '%s' "$runners" | jq -e --arg label "$label" ' + (.runners // .data // . // []) + | any( + ( + (.status // (if (.online // false) then "online" else "offline" end)) + | ascii_downcase + ) == "online" + and ( + (.labels // []) + | map(if type == "object" then (.name // .label // "") else tostring end) + | index($label) + ) != null + ) + ' >/dev/null + } + + if has_runner "debian"; then + echo "has_linux=true" >> "$GITHUB_OUTPUT" + else + echo "has_linux=false" >> "$GITHUB_OUTPUT" + fi + + if has_runner "darwin-arm64"; then + echo "has_macos=true" >> "$GITHUB_OUTPUT" + else + echo "has_macos=false" >> "$GITHUB_OUTPUT" + fi + + if has_runner "windows"; then + echo "has_windows=true" >> "$GITHUB_OUTPUT" + else + echo "has_windows=false" >> "$GITHUB_OUTPUT" + fi - # ========== 代码质量检查(只在 Linux 跑一次)========== check: name: 质量检查 (fmt / clippy / test) runs-on: debian - timeout-minutes: 1 # 超时1分钟 + timeout-minutes: 10 steps: - name: 安装 Rust run: | - if ! command -v cargo &>/dev/null; then + if ! command -v cargo >/dev/null 2>&1; then curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable - echo "$HOME/.cargo/bin" >> $GITHUB_PATH + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" fi source "$HOME/.cargo/env" 2>/dev/null || true rustup component add rustfmt clippy @@ -110,68 +221,31 @@ jobs: ~/.cargo/git/db target key: cargo-check-${{ hashFiles('Cargo.lock') }} - restore-keys: cargo-check- + restore-keys: | + cargo-check- - run: cargo fmt -- --check - run: cargo clippy --locked -- -D warnings - run: cargo test --locked - # ========== 多平台构建 ========== - build: - name: Build (${{ matrix.target }}) - needs: [version, check] - continue-on-error: true + build-linux: + name: Build (x86_64-unknown-linux-musl) + needs: [version, probe-runners, check] + if: needs.probe-runners.outputs.has_linux == 'true' + runs-on: debian timeout-minutes: 10 - strategy: - fail-fast: false - matrix: - include: - - runner: debian - target: x86_64-unknown-linux-musl - archive_suffix: x86_64-linux-musl - os: linux - - runner: darwin-arm64 - target: aarch64-apple-darwin - archive_suffix: aarch64-macos - os: macos - - runner: windows - target: x86_64-pc-windows-msvc - archive_suffix: x86_64-windows - os: windows - - runs-on: ${{ matrix.runner }} + continue-on-error: true steps: - - name: 安装依赖 (Linux) - if: matrix.os == 'linux' + - name: 安装依赖 run: | - sudo apt-get update && sudo apt-get install -y pkg-config musl-tools binutils curl - if ! command -v cargo &>/dev/null; then + sudo apt-get update + sudo apt-get install -y pkg-config musl-tools binutils curl + if ! command -v cargo >/dev/null 2>&1; then curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable fi source "$HOME/.cargo/env" 2>/dev/null || true - rustup target add ${{ matrix.target }} - echo "$HOME/.cargo/bin" >> $GITHUB_PATH - - - name: 安装依赖 (macOS) - if: matrix.os == 'macos' - run: | - if ! command -v cargo &>/dev/null; then - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable - fi - source "$HOME/.cargo/env" 2>/dev/null || true - rustup target add ${{ matrix.target }} - echo "$HOME/.cargo/bin" >> $GITHUB_PATH - - - name: 安装依赖 (Windows) - if: matrix.os == 'windows' - shell: pwsh - run: | - if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) { - Invoke-WebRequest -Uri "https://win.rustup.rs/x86_64" -OutFile rustup-init.exe - .\rustup-init.exe -y --default-toolchain stable - Remove-Item rustup-init.exe - } - rustup target add ${{ matrix.target }} + rustup target add x86_64-unknown-linux-musl + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - uses: actions/checkout@v4 @@ -183,99 +257,164 @@ jobs: ~/.cargo/registry/cache ~/.cargo/git/db target - key: cargo-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }} - restore-keys: cargo-${{ matrix.target }}- + key: cargo-x86_64-unknown-linux-musl-${{ hashFiles('Cargo.lock') }} + restore-keys: | + cargo-x86_64-unknown-linux-musl- - - name: 构建 - if: matrix.os != 'windows' - run: cargo build --release --locked --target ${{ matrix.target }} + - run: cargo build --release --locked --target x86_64-unknown-linux-musl + - run: strip target/x86_64-unknown-linux-musl/release/${{ env.BINARY_NAME }} - - name: 构建 (Windows) - if: matrix.os == 'windows' - shell: pwsh - run: cargo build --release --locked --target ${{ matrix.target }} - - - name: Strip (Linux) - if: matrix.os == 'linux' - run: strip target/${{ matrix.target }}/release/${{ env.BINARY_NAME }} - - - name: Strip (macOS) - if: matrix.os == 'macos' - run: strip -x target/${{ matrix.target }}/release/${{ env.BINARY_NAME }} - - - name: 上传产物 (Linux/macOS) - if: needs.version.outputs.tag_exists == 'false' && needs.version.outputs.release_id && matrix.os != 'windows' + - name: 上传 Release 产物 + if: needs.version.outputs.release_id != '' env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | [ -z "$RELEASE_TOKEN" ] && exit 0 tag="${{ needs.version.outputs.tag }}" - bin="target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}" - archive="${{ env.BINARY_NAME }}-${tag}-${{ matrix.archive_suffix }}.tar.gz" - tar -czf "$archive" -C "$(dirname $bin)" "$(basename $bin)" - curl -sS -H "Authorization: token $RELEASE_TOKEN" \ + bin="target/x86_64-unknown-linux-musl/release/${{ env.BINARY_NAME }}" + archive="${{ env.BINARY_NAME }}-${tag}-x86_64-linux-musl.tar.gz" + tar -czf "$archive" -C "$(dirname "$bin")" "$(basename "$bin")" + curl -fsS -H "Authorization: token $RELEASE_TOKEN" \ -F "attachment=@${archive}" \ "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ needs.version.outputs.release_id }}/assets" - echo "已上传: ${archive}" - - name: 上传产物 (Windows) - if: needs.version.outputs.tag_exists == 'false' && needs.version.outputs.release_id && matrix.os == 'windows' + build-macos: + name: Build (aarch64-apple-darwin) + needs: [version, probe-runners, check] + if: needs.probe-runners.outputs.has_macos == 'true' + runs-on: darwin-arm64 + timeout-minutes: 10 + continue-on-error: true + steps: + - name: 安装依赖 + run: | + if ! command -v cargo >/dev/null 2>&1; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable + fi + source "$HOME/.cargo/env" 2>/dev/null || true + rustup target add aarch64-apple-darwin + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + + - uses: actions/checkout@v4 + + - name: 缓存 Cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + target + key: cargo-aarch64-apple-darwin-${{ hashFiles('Cargo.lock') }} + restore-keys: | + cargo-aarch64-apple-darwin- + + - run: cargo build --release --locked --target aarch64-apple-darwin + - run: strip -x target/aarch64-apple-darwin/release/${{ env.BINARY_NAME }} + + - name: 上传 Release 产物 + if: needs.version.outputs.release_id != '' + env: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + run: | + [ -z "$RELEASE_TOKEN" ] && exit 0 + tag="${{ needs.version.outputs.tag }}" + bin="target/aarch64-apple-darwin/release/${{ env.BINARY_NAME }}" + archive="${{ env.BINARY_NAME }}-${tag}-aarch64-macos.tar.gz" + tar -czf "$archive" -C "$(dirname "$bin")" "$(basename "$bin")" + curl -fsS -H "Authorization: token $RELEASE_TOKEN" \ + -F "attachment=@${archive}" \ + "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ needs.version.outputs.release_id }}/assets" + + build-windows: + name: Build (x86_64-pc-windows-msvc) + needs: [version, probe-runners, check] + if: needs.probe-runners.outputs.has_windows == 'true' + runs-on: windows + timeout-minutes: 10 + continue-on-error: true + steps: + - name: 安装依赖 + shell: pwsh + run: | + if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) { + Invoke-WebRequest -Uri "https://win.rustup.rs/x86_64" -OutFile rustup-init.exe + .\rustup-init.exe -y --default-toolchain stable + Remove-Item rustup-init.exe + } + rustup target add x86_64-pc-windows-msvc + + - uses: actions/checkout@v4 + + - name: 缓存 Cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + target + key: cargo-x86_64-pc-windows-msvc-${{ hashFiles('Cargo.lock') }} + restore-keys: | + cargo-x86_64-pc-windows-msvc- + + - name: 构建 + shell: pwsh + run: cargo build --release --locked --target x86_64-pc-windows-msvc + + - name: 上传 Release 产物 + if: needs.version.outputs.release_id != '' shell: pwsh env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | if (-not $env:RELEASE_TOKEN) { exit 0 } $tag = "${{ needs.version.outputs.tag }}" - $bin = "target\${{ matrix.target }}\release\${{ env.BINARY_NAME }}.exe" - $archive = "${{ env.BINARY_NAME }}-${tag}-${{ matrix.archive_suffix }}.zip" - Compress-Archive -Path $bin -DestinationPath $archive + $bin = "target\x86_64-pc-windows-msvc\release\${{ env.BINARY_NAME }}.exe" + $archive = "${{ env.BINARY_NAME }}-${tag}-x86_64-windows.zip" + Compress-Archive -Path $bin -DestinationPath $archive -Force $url = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ needs.version.outputs.release_id }}/assets" Invoke-RestMethod -Uri $url -Method Post ` -Headers @{ "Authorization" = "token $env:RELEASE_TOKEN" } ` -Form @{ attachment = Get-Item $archive } - Write-Host "已上传: ${archive}" - # ========== 通知 ========== notify: name: 通知 - needs: [version, build] + needs: [version, probe-runners] if: always() && github.event_name == 'push' runs-on: debian + timeout-minutes: 1 + continue-on-error: true steps: - uses: actions/checkout@v4 - name: 发送飞书通知 - continue-on-error: true env: WEBHOOK_URL: ${{ vars.WEBHOOK_URL }} run: | [ -z "$WEBHOOK_URL" ] && exit 0 - command -v jq &>/dev/null || (sudo apt-get update -qq && sudo apt-get install -y -qq jq) + command -v jq >/dev/null 2>&1 || (sudo apt-get update -qq && sudo apt-get install -y -qq jq) tag="${{ needs.version.outputs.tag }}" tag_exists="${{ needs.version.outputs.tag_exists }}" - result="${{ needs.build.result }}" - [ "$result" = "success" ] && status="构建成功 ✅" || status="构建失败 ❌" - commit=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "N/A") url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}" - if [ "$result" != "success" ]; then - extra="" - elif [ "$tag_exists" = "false" ]; then - extra="🆕 新版本已发布 (linux / macOS / windows)" + msg="${{ env.BINARY_NAME }} 通知" + if [ "$tag_exists" = "false" ]; then + msg="${msg} + 🆕 已创建版本标签并开始构建发布" else - extra="🔄 重复构建" + msg="${msg} + 🔄 检测到已有版本标签,已开始重复构建" fi - msg="${{ env.BINARY_NAME }} ${status}" - [ -n "$extra" ] && msg="${msg} - ${extra}" msg="${msg} + Runner 可用性:linux=${{ needs.probe-runners.outputs.has_linux }}, macOS=${{ needs.probe-runners.outputs.has_macos }}, windows=${{ needs.probe-runners.outputs.has_windows }} 提交:${commit} 版本:${tag} 作者:${{ github.actor }} 详情:${url}" - payload=$(jq -n --arg text "$msg" '{msg_type:"text",content:{text:$text}}') - curl -sS -H "Content-Type: application/json" -X POST -d "$payload" "$WEBHOOK_URL" + payload=$(jq -n --arg text "$msg" '{msg_type: "text", content: {text: $text}}') + curl -fsS -H "Content-Type: application/json" -X POST -d "$payload" "$WEBHOOK_URL"