fix: SVG benchmark chart mislabeling Python as Rust #77
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: Build & Release | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| permissions: | |
| contents: write | |
| packages: write | |
| jobs: | |
| # ── 判断是否需要构建 / 发布 ────────────────────────────── | |
| check: | |
| name: Check commit message | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_build: ${{ steps.flags.outputs.should_build }} | |
| should_release: ${{ steps.flags.outputs.should_release }} | |
| should_publish: ${{ steps.flags.outputs.should_publish }} | |
| should_publish_pypi: ${{ steps.flags.outputs.should_publish_pypi }} | |
| should_publish_crates: ${{ steps.flags.outputs.should_publish_crates }} | |
| should_benchmark: ${{ steps.flags.outputs.should_benchmark }} | |
| version: ${{ steps.flags.outputs.version }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Parse commit message | |
| id: flags | |
| run: | | |
| MSG="${{ github.event.head_commit.message }}" | |
| # 从 Cargo.toml 提取版本号 | |
| VERSION="v$(grep '^version' rust/Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')" | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "📦 Version: $VERSION" | |
| # PR 始终构建 | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| echo "should_build=true" >> "$GITHUB_OUTPUT" | |
| echo "should_release=false" >> "$GITHUB_OUTPUT" | |
| echo "should_publish=false" >> "$GITHUB_OUTPUT" | |
| echo "should_benchmark=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # 初始化标志 | |
| BUILD=false | |
| RELEASE=false | |
| PUBLISH=false | |
| PUBLISH_PYPI=false | |
| PUBLISH_CRATES=false | |
| BENCHMARK=false | |
| # "build publish" = 构建 + Release + 发布到包管理器 (全套) | |
| if echo "$MSG" | grep -qi "build publish"; then | |
| BUILD=true | |
| RELEASE=true | |
| PUBLISH=true | |
| # "build release" = 构建 + GitHub Release | |
| elif echo "$MSG" | grep -qi "build release"; then | |
| BUILD=true | |
| RELEASE=true | |
| # "build action" = 仅构建 | |
| elif echo "$MSG" | grep -qi "build action"; then | |
| BUILD=true | |
| fi | |
| # "publish from release" = 从已有 Release 发布到包管理器 (不构建) | |
| if echo "$MSG" | grep -qi "publish from release"; then | |
| PUBLISH=true | |
| fi | |
| # "pypi publish" / "publish pypi" = 发布到 PyPI | |
| if echo "$MSG" | grep -qiE "pypi publish|publish pypi"; then | |
| PUBLISH_PYPI=true | |
| fi | |
| # "crates publish" / "publish crates" = 发布到 crates.io | |
| if echo "$MSG" | grep -qiE "crates publish|publish crates"; then | |
| PUBLISH_CRATES=true | |
| fi | |
| # "run benchmark" = 运行基准测试 | |
| if echo "$MSG" | grep -qi "run benchmark"; then | |
| BENCHMARK=true | |
| fi | |
| echo "should_build=$BUILD" >> "$GITHUB_OUTPUT" | |
| echo "should_release=$RELEASE" >> "$GITHUB_OUTPUT" | |
| echo "should_publish=$PUBLISH" >> "$GITHUB_OUTPUT" | |
| echo "should_publish_pypi=$PUBLISH_PYPI" >> "$GITHUB_OUTPUT" | |
| echo "should_publish_crates=$PUBLISH_CRATES" >> "$GITHUB_OUTPUT" | |
| echo "should_benchmark=$BENCHMARK" >> "$GITHUB_OUTPUT" | |
| echo "📋 Flags: build=$BUILD release=$RELEASE publish=$PUBLISH publish_pypi=$PUBLISH_PYPI publish_crates=$PUBLISH_CRATES benchmark=$BENCHMARK" | |
| # ── Benchmark ─────────────────────────────────────────── | |
| benchmark: | |
| name: Run Benchmark | |
| needs: check | |
| if: needs.check.outputs.should_benchmark == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install dependencies | |
| run: sudo apt-get update && sudo apt-get install -y time | |
| - name: Run benchmark | |
| run: | | |
| chmod +x benchmark_go/benchmark.sh | |
| cd benchmark_go | |
| ./benchmark.sh | |
| - name: Commit and push benchmark results | |
| run: | | |
| git config --global user.name "github-actions[bot]" | |
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | |
| git add docs/benchmark/benchmark.svg | |
| git commit -m "docs: update benchmark results [skip ci]" || echo "No changes to commit" | |
| git push origin main | |
| # ── 多平台构建 ─────────────────────────────────────────── | |
| build: | |
| name: Build ${{ matrix.name }} | |
| needs: check | |
| if: needs.check.outputs.should_build == 'true' | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # ── Windows x64 (MSVC, native) ── | |
| - target: x86_64-pc-windows-msvc | |
| os: windows-latest | |
| name: windows-x86_64 | |
| binary: winload.exe | |
| asset: winload-windows-x86_64.exe | |
| # ── Linux x64 (musl 静态链接, 兼容所有 Linux) ── | |
| - target: x86_64-unknown-linux-musl | |
| os: ubuntu-latest | |
| name: linux-x86_64 | |
| binary: winload | |
| asset: winload-linux-x86_64 | |
| cross_packages: musl-tools | |
| # ── Windows ARM64 (cross-compile on x64 runner) ── | |
| - target: aarch64-pc-windows-msvc | |
| os: windows-latest | |
| name: windows-aarch64 | |
| binary: winload.exe | |
| asset: winload-windows-aarch64.exe | |
| # ── Linux ARM64 (ubuntu-22.04 降低 GLIBC 要求) ── | |
| - target: aarch64-unknown-linux-gnu | |
| os: ubuntu-22.04 | |
| name: linux-aarch64 | |
| binary: winload | |
| asset: winload-linux-aarch64 | |
| cross_linker: aarch64-linux-gnu-gcc | |
| cross_packages: gcc-aarch64-linux-gnu | |
| # ── macOS x64 (build on Apple Silicon runner) ── | |
| - target: x86_64-apple-darwin | |
| os: macos-latest | |
| name: macos-x86_64 | |
| binary: winload | |
| asset: winload-macos-x86_64 | |
| # ── macOS ARM64 (Apple Silicon runner) ── | |
| - target: aarch64-apple-darwin | |
| os: macos-latest | |
| name: macos-aarch64 | |
| binary: winload | |
| asset: winload-macos-aarch64 | |
| # ── Android aarch64 (Termux ARM64) ── | |
| - target: aarch64-linux-android | |
| os: ubuntu-latest | |
| name: android-aarch64 | |
| binary: winload | |
| asset: winload-android-aarch64 | |
| # ── Android x86_64 (Termux x86_64, 模拟器 / Chromebook) ── | |
| - target: x86_64-linux-android | |
| os: ubuntu-latest | |
| name: android-x86_64 | |
| binary: winload | |
| asset: winload-android-x86_64 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: rust -> target | |
| cache-on-failure: true | |
| key: ${{ matrix.target }} | |
| - name: Install platform toolchain | |
| if: matrix.cross_packages | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y ${{ matrix.cross_packages }} | |
| # Android: 配置 NDK 交叉编译工具链 (Termux 最低 API 24 = Android 7.0) | |
| - name: Setup Android NDK | |
| if: contains(matrix.target, 'android') | |
| shell: bash | |
| run: | | |
| NDK_HOME="${ANDROID_NDK_LATEST_HOME:-$ANDROID_NDK_HOME}" | |
| echo "📦 Using NDK: $NDK_HOME" | |
| TOOLCHAIN="$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" | |
| ls "$TOOLCHAIN" | head -20 | |
| if [[ "${{ matrix.target }}" == "aarch64-linux-android" ]]; then | |
| echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=${TOOLCHAIN}/aarch64-linux-android24-clang" >> "$GITHUB_ENV" | |
| echo "CC_aarch64_linux_android=${TOOLCHAIN}/aarch64-linux-android24-clang" >> "$GITHUB_ENV" | |
| echo "AR_aarch64_linux_android=${TOOLCHAIN}/llvm-ar" >> "$GITHUB_ENV" | |
| elif [[ "${{ matrix.target }}" == "x86_64-linux-android" ]]; then | |
| echo "CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER=${TOOLCHAIN}/x86_64-linux-android24-clang" >> "$GITHUB_ENV" | |
| echo "CC_x86_64_linux_android=${TOOLCHAIN}/x86_64-linux-android24-clang" >> "$GITHUB_ENV" | |
| echo "AR_x86_64_linux_android=${TOOLCHAIN}/llvm-ar" >> "$GITHUB_ENV" | |
| fi | |
| echo "✅ Android NDK configured for ${{ matrix.target }}" | |
| # Windows: 下载 Npcap SDK,pcap crate 链接 wpcap.lib 需要 | |
| - name: Install Npcap SDK (Windows only) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $sdkUrl = "https://npcap.com/dist/npcap-sdk-1.13.zip" | |
| $zipPath = "$env:RUNNER_TEMP\npcap-sdk.zip" | |
| $extractPath = "$env:RUNNER_TEMP\npcap-sdk" | |
| Write-Host "📥 Downloading Npcap SDK..." | |
| Invoke-WebRequest -Uri $sdkUrl -OutFile $zipPath | |
| Write-Host "📦 Extracting Npcap SDK..." | |
| Expand-Archive -Path $zipPath -DestinationPath $extractPath -Force | |
| # 根据 target 选择正确的 lib 目录 | |
| $target = "${{ matrix.target }}" | |
| if ($target -like "*aarch64*") { | |
| $arch = "ARM64" | |
| } else { | |
| $arch = "x64" | |
| } | |
| $libDir = "$extractPath\Lib\$arch" | |
| Write-Host "📂 Npcap SDK lib dir ($arch): $libDir" | |
| Get-ChildItem $libDir | |
| echo "LIB=$libDir;$env:LIB" >> $env:GITHUB_ENV | |
| Write-Host "✅ Npcap SDK ready" | |
| - name: Install Linux packaging tools | |
| if: runner.os == 'Linux' && !contains(matrix.target, 'android') | |
| run: | | |
| cargo install cargo-deb cargo-generate-rpm | |
| - name: Build release binary | |
| shell: bash | |
| working-directory: rust | |
| env: | |
| # 交叉编译时指定链接器 (仅 aarch64-linux-gnu 需要) | |
| CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: ${{ matrix.cross_linker || '' }} | |
| run: | | |
| cargo build --release --target ${{ matrix.target }} | |
| # 验证 Linux musl 产物是否真的是静态链接 | |
| if [[ "${{ matrix.target }}" == *musl* ]]; then | |
| echo "🔍 Verifying static linking..." | |
| file target/${{ matrix.target }}/release/${{ matrix.binary }} | |
| ldd target/${{ matrix.target }}/release/${{ matrix.binary }} 2>&1 || echo "✅ Statically linked (no dynamic dependencies)" | |
| fi | |
| # Linux targets: 额外构建 DEB + RPM 包 (排除 Android) | |
| - name: Build DEB & RPM packages | |
| if: contains(matrix.target, 'linux') && !contains(matrix.target, 'android') | |
| shell: bash | |
| working-directory: rust | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| echo "📦 Building DEB package for ${{ matrix.target }}..." | |
| cargo deb --target ${{ matrix.target }} --no-build | |
| echo "📦 Building RPM package for ${{ matrix.target }}..." | |
| cargo generate-rpm --target ${{ matrix.target }} | |
| echo "✅ Packages built (original names):" | |
| find target -name '*.deb' -o -name '*.rpm' | head -20 | |
| # 重命名为统一格式: winload-linux-<arch>-<version>.deb / .rpm | |
| mkdir -p renamed | |
| for f in $(find target -name '*.deb'); do | |
| cp "$f" "renamed/winload-${{ matrix.name }}-${VERSION}.deb" | |
| done | |
| for f in $(find target -name '*.rpm'); do | |
| cp "$f" "renamed/winload-${{ matrix.name }}-${VERSION}.rpm" | |
| done | |
| echo "📦 Renamed packages:" | |
| ls -lh renamed/ | |
| - name: Upload DEB artifact | |
| if: contains(matrix.target, 'linux') && !contains(matrix.target, 'android') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: winload-${{ matrix.name }}-${{ needs.check.outputs.version }}.deb | |
| path: rust/renamed/*.deb | |
| - name: Upload RPM artifact | |
| if: contains(matrix.target, 'linux') && !contains(matrix.target, 'android') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: winload-${{ matrix.name }}-${{ needs.check.outputs.version }}.rpm | |
| path: rust/renamed/*.rpm | |
| - name: Prepare artifact | |
| shell: bash | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| ASSET_BASE="${{ matrix.asset }}" | |
| # 在扩展名前插入版本号 | |
| # winload-linux-x86_64 -> winload-linux-x86_64-v0.1.0 | |
| # winload-windows-x86_64.exe -> winload-windows-x86_64-v0.1.0.exe | |
| if [[ "$ASSET_BASE" == *.* ]]; then | |
| # 有扩展名 | |
| BASE_NAME="${ASSET_BASE%.*}" | |
| EXTENSION="${ASSET_BASE##*.}" | |
| ASSET_WITH_VERSION="${BASE_NAME}-${VERSION}.${EXTENSION}" | |
| else | |
| # 无扩展名 | |
| ASSET_WITH_VERSION="${ASSET_BASE}-${VERSION}" | |
| fi | |
| echo "📦 Output: $ASSET_WITH_VERSION" | |
| cp "rust/target/${{ matrix.target }}/release/${{ matrix.binary }}" "$ASSET_WITH_VERSION" | |
| echo "asset_name=$ASSET_WITH_VERSION" >> "$GITHUB_ENV" | |
| - name: Upload build artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ env.asset_name }} | |
| path: ${{ env.asset_name }} | |
| # ── 发布到 GitHub Releases ───────────────────────────── | |
| release: | |
| name: Publish Release | |
| needs: [check, build] | |
| if: needs.check.outputs.should_release == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| merge-multiple: true | |
| - name: List downloaded files | |
| run: | | |
| echo "Downloaded artifacts:" | |
| ls -lh | |
| - name: Delete existing release & tag (force fresh release) | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| echo "🗑️ Cleaning up existing release/tag: $VERSION" | |
| gh release delete "$VERSION" --yes --cleanup-tag 2>/dev/null || echo "No existing release to delete" | |
| # 等待 GitHub API 同步 | |
| sleep 3 | |
| - name: Generate release notes from commits | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| REPO="${{ github.repository }}" | |
| BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}" | |
| # 查找上一个 release tag(排除当前版本) | |
| PREV_TAG=$(git tag --sort=-v:refname | grep -v "^${VERSION}$" | head -1) | |
| { | |
| echo "## 📦 winload ${VERSION}" | |
| echo "" | |
| echo "### ⬇️ Downloads" | |
| echo "" | |
| echo "| Arch | Windows | Linux | macOS |" | |
| echo "|------|---------|-------|-------|" | |
| echo "| **x86_64** | [exe](${BASE_URL}/winload-windows-x86_64-${VERSION}.exe) | [binary](${BASE_URL}/winload-linux-x86_64-${VERSION}) · [deb](${BASE_URL}/winload-linux-x86_64-${VERSION}.deb) · [rpm](${BASE_URL}/winload-linux-x86_64-${VERSION}.rpm) | [binary](${BASE_URL}/winload-macos-x86_64-${VERSION}) |" | |
| echo "| **ARM64** | [exe](${BASE_URL}/winload-windows-aarch64-${VERSION}.exe) | [binary](${BASE_URL}/winload-linux-aarch64-${VERSION}) · [deb](${BASE_URL}/winload-linux-aarch64-${VERSION}.deb) · [rpm](${BASE_URL}/winload-linux-aarch64-${VERSION}.rpm) | [binary](${BASE_URL}/winload-macos-aarch64-${VERSION}) |" | |
| echo "" | |
| # 提取不带 v 前缀的版本号 | |
| PLAIN_VER="${VERSION#v}" | |
| # PyPI 版本号: PEP 440 规范化 (0.1.7-rc.10 -> 0.1.7rc10) | |
| PYPI_VER=$(echo "$PLAIN_VER" | sed 's/-rc\./rc/') | |
| echo "### 📥 Quick Install" | |
| echo "" | |
| echo "**Python (pip):**" | |
| echo '```bash' | |
| echo "pip install winload==${PYPI_VER}" | |
| echo "# or with uv" | |
| echo "uv pip install winload==${PYPI_VER}" | |
| echo '```' | |
| echo "> 📄 [PyPI](https://pypi.org/project/winload/${PYPI_VER}/)" | |
| echo "" | |
| echo "**npm (cross-platform):**" | |
| echo '```bash' | |
| echo "npm install -g @vincentzyuapps/winload@${PLAIN_VER}" | |
| echo '```' | |
| echo "> 📄 [npm](https://www.npmjs.com/package/@vincentzyuapps/winload/v/${PLAIN_VER})" | |
| echo "" | |
| echo "**Cargo (build from source):**" | |
| echo '```bash' | |
| echo "cargo install winload@${PLAIN_VER}" | |
| echo '```' | |
| echo "> 📄 [crates.io](https://crates.io/crates/winload/${PLAIN_VER})" | |
| echo "" | |
| echo "**Windows (Scoop):**" | |
| echo '```powershell' | |
| echo "scoop bucket add vincentzyu https://github.com/VincentZyuApps/scoop-bucket" | |
| echo "scoop install winload" | |
| echo '```' | |
| echo "> 📄 [Scoop bucket](https://github.com/VincentZyuApps/scoop-bucket)" | |
| echo "" | |
| echo "**Arch Linux (AUR):**" | |
| echo '```bash' | |
| echo "paru -S winload-rust-bin" | |
| echo '```' | |
| echo "> 📄 [winload-rust-bin](https://aur.archlinux.org/packages/winload-rust-bin)" | |
| echo "" | |
| echo "**One-liner install for some Linux distros (Debian/Ubuntu/RHEL/Fedora and derivatives):**" | |
| echo '```bash' | |
| echo "curl -fsSL https://raw.githubusercontent.com/${REPO}/main/docs/install_scripts/install.sh | bash" | |
| echo "# or install this specific version:" | |
| echo "WINLOAD_VERSION=${VERSION} bash -c \"\\\$(curl -fsSL https://raw.githubusercontent.com/${REPO}/main/docs/install_scripts/install.sh)\"" | |
| echo '```' | |
| echo "> 📄 [View install script source](https://github.com/${REPO}/blob/main/docs/install_scripts/install.sh)" | |
| echo "" | |
| echo "---" | |
| echo "" | |
| echo "### What's Changed" | |
| echo "" | |
| if [ -n "$PREV_TAG" ]; then | |
| echo "Changes since **${PREV_TAG}**:" | |
| echo "" | |
| git log "${PREV_TAG}..HEAD" --pretty=format:"- %s (\`%h\`) — @%an" --reverse | |
| else | |
| echo "All changes since repository creation:" | |
| echo "" | |
| git log --pretty=format:"- %s (\`%h\`) — @%an" --reverse | |
| fi | |
| echo "" | |
| echo "" | |
| echo "---" | |
| echo "" | |
| echo "### 📊 Build Info" | |
| echo "- **Build date**: $(date -u '+%Y-%m-%d %H:%M UTC')" | |
| echo "- **Commit**: [\`${GITHUB_SHA::7}\`](https://github.com/${REPO}/commit/${GITHUB_SHA})" | |
| echo "" | |
| if [ -n "$PREV_TAG" ]; then | |
| echo "**Full Changelog**: https://github.com/${REPO}/compare/${PREV_TAG}...${VERSION}" | |
| else | |
| echo "**Full Changelog**: https://github.com/${REPO}/commits/${VERSION}" | |
| fi | |
| } > release_notes.md | |
| echo "📝 Generated release notes:" | |
| cat release_notes.md | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| # 收集所有要上传的文件(二进制 + DEB + RPM,统一 winload-*-VERSION* 格式) | |
| shopt -s nullglob | |
| FILES=(winload-*-${VERSION}*) | |
| echo "📤 Uploading ${#FILES[@]} files:" | |
| printf ' - %s\n' "${FILES[@]}" | |
| gh release create "$VERSION" \ | |
| --title "winload ${VERSION}" \ | |
| --notes-file release_notes.md \ | |
| --latest \ | |
| "${FILES[@]}" | |
| # ── 更新 Scoop Bucket ───────────────────────────────── | |
| # 触发方式: | |
| # - "publish from release" → 从已有 Release 拉取二进制,仅更新 Scoop | |
| # - "build publish" → 构建 + Release + 更新 Scoop(全套) | |
| publish-scoop: | |
| name: Update Scoop Bucket | |
| needs: [check, release] | |
| # always() 让此 job 即使 release 被跳过也能运行 | |
| if: always() && needs.check.outputs.should_publish == 'true' && (needs.release.result == 'success' || needs.release.result == 'skipped') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Fetch latest release info | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| REPO="${{ github.repository }}" | |
| BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}" | |
| echo "📦 Target version: ${VERSION}" | |
| echo "📥 Downloading Windows binaries from release..." | |
| # 下载 Windows x64 | |
| curl -fSL -o winload-windows-x86_64.exe \ | |
| "${BASE_URL}/winload-windows-x86_64-${VERSION}.exe" \ | |
| || { echo "❌ Failed to download x64 binary"; exit 1; } | |
| # 下载 Windows ARM64 | |
| curl -fSL -o winload-windows-aarch64.exe \ | |
| "${BASE_URL}/winload-windows-aarch64-${VERSION}.exe" \ | |
| || { echo "❌ Failed to download arm64 binary"; exit 1; } | |
| echo "✅ Downloaded:" | |
| ls -lh winload-windows-*.exe | |
| - name: Generate Scoop manifest | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| SCOOP_VERSION="${VERSION#v}" | |
| REPO="${{ github.repository }}" | |
| BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}" | |
| # 计算 sha256 | |
| HASH_X64=$(sha256sum winload-windows-x86_64.exe | awk '{print $1}') | |
| HASH_ARM64=$(sha256sum winload-windows-aarch64.exe | awk '{print $1}') | |
| echo "📦 Version: $SCOOP_VERSION" | |
| echo "🔑 x64 hash: $HASH_X64" | |
| echo "🔑 arm64 hash: $HASH_ARM64" | |
| cat > winload.json << EOF | |
| { | |
| "version": "${SCOOP_VERSION}", | |
| "description": "Network Load Monitor - nload for Windows/Linux/macOS", | |
| "homepage": "https://github.com/${REPO}", | |
| "license": "MIT", | |
| "architecture": { | |
| "64bit": { | |
| "url": "${BASE_URL}/winload-windows-x86_64-${VERSION}.exe", | |
| "hash": "${HASH_X64}", | |
| "bin": [["winload-windows-x86_64-${VERSION}.exe", "win-nload"]] | |
| }, | |
| "arm64": { | |
| "url": "${BASE_URL}/winload-windows-aarch64-${VERSION}.exe", | |
| "hash": "${HASH_ARM64}", | |
| "bin": [["winload-windows-aarch64-${VERSION}.exe", "win-nload"]] | |
| } | |
| }, | |
| "checkver": { | |
| "github": "https://github.com/${REPO}" | |
| }, | |
| "autoupdate": { | |
| "architecture": { | |
| "64bit": { | |
| "url": "https://github.com/${REPO}/releases/download/v\$version/winload-windows-x86_64-v\$version.exe" | |
| }, | |
| "arm64": { | |
| "url": "https://github.com/${REPO}/releases/download/v\$version/winload-windows-aarch64-v\$version.exe" | |
| } | |
| } | |
| } | |
| } | |
| EOF | |
| echo "📝 Generated winload.json:" | |
| cat winload.json | |
| - name: Push to scoop-bucket repo | |
| env: | |
| SCOOP_TOKEN: ${{ secrets.SCOOP_BUCKET_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| git clone https://x-access-token:${SCOOP_TOKEN}@github.com/VincentZyuApps/scoop-bucket.git scoop-repo | |
| cp winload.json scoop-repo/bucket/winload.json | |
| cd scoop-repo | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add bucket/winload.json | |
| git commit -m "🍺 Update winload to ${VERSION}" || echo "No changes to commit" | |
| git push | |
| echo "✅ Scoop bucket updated!" | |
| # ── 更新 AUR 包(winload-rust-bin: 预编译二进制)───────────── | |
| publish-aur-bin: | |
| name: Update AUR Package (winload-rust-bin) | |
| needs: [check, release] | |
| if: always() && needs.check.outputs.should_publish == 'true' && (needs.release.result == 'success' || needs.release.result == 'skipped') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Download Linux binaries from release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| REPO="${{ github.repository }}" | |
| BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}" | |
| echo "📥 Downloading x86_64 binary..." | |
| curl -fSL -o winload-linux-x86_64 "${BASE_URL}/winload-linux-x86_64-${VERSION}" | |
| SHA256_X86=$(sha256sum winload-linux-x86_64 | awk '{print $1}') | |
| echo "🔑 x86_64 SHA256: $SHA256_X86" | |
| echo "SHA256_X86=$SHA256_X86" >> "$GITHUB_ENV" | |
| echo "📥 Downloading aarch64 binary..." | |
| curl -fSL -o winload-linux-aarch64 "${BASE_URL}/winload-linux-aarch64-${VERSION}" | |
| SHA256_ARM=$(sha256sum winload-linux-aarch64 | awk '{print $1}') | |
| echo "🔑 aarch64 SHA256: $SHA256_ARM" | |
| echo "SHA256_ARM=$SHA256_ARM" >> "$GITHUB_ENV" | |
| - name: Generate PKGBUILD | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| TAGVER="${VERSION#v}" # 0.1.6-beta.3 (for download URL) | |
| PKGVER="${TAGVER//-/.}" # 0.1.6.beta.3 (AUR compliant) | |
| cat > PKGBUILD << HEREDOC_END | |
| # Maintainer: VincentZyu <vincentzyu233@gmail.com> | |
| pkgname=winload-rust-bin | |
| pkgver=${PKGVER} | |
| pkgrel=1 | |
| pkgdesc="A lightweight, real-time CLI tool for monitoring network bandwidth and traffic" | |
| arch=('x86_64' 'aarch64') | |
| url="https://github.com/VincentZyuApps/winload" | |
| license=('MIT') | |
| provides=('winload') | |
| conflicts=('winload' 'winload-rust') | |
| _tagver=${TAGVER} | |
| _base_url="https://github.com/VincentZyuApps/winload/releases/download/v\${_tagver}" | |
| source_x86_64=("winload-linux-x86_64-v\${_tagver}::\${_base_url}/winload-linux-x86_64-v\${_tagver}") | |
| source_aarch64=("winload-linux-aarch64-v\${_tagver}::\${_base_url}/winload-linux-aarch64-v\${_tagver}") | |
| noextract=() | |
| sha256sums_x86_64=('${SHA256_X86}') | |
| sha256sums_aarch64=('${SHA256_ARM}') | |
| package() { | |
| if [[ "\$CARCH" == "x86_64" ]]; then | |
| install -Dm755 "\$srcdir/winload-linux-x86_64-v\${_tagver}" "\$pkgdir/usr/bin/winload" | |
| elif [[ "\$CARCH" == "aarch64" ]]; then | |
| install -Dm755 "\$srcdir/winload-linux-aarch64-v\${_tagver}" "\$pkgdir/usr/bin/winload" | |
| fi | |
| } | |
| HEREDOC_END | |
| # Remove leading whitespace (heredoc indentation) | |
| sed -i 's/^ //' PKGBUILD | |
| echo "📝 Generated PKGBUILD:" | |
| cat PKGBUILD | |
| - name: Generate .SRCINFO | |
| uses: docker://archlinux:latest | |
| with: | |
| args: bash -c "pacman -Sy --noconfirm pacman-contrib base-devel && useradd -m builder && cp PKGBUILD /home/builder/ && cd /home/builder && sudo -u builder makepkg --printsrcinfo > .SRCINFO && cp .SRCINFO /github/workspace/" | |
| - name: Push to AUR | |
| env: | |
| AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }} | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| TAGVER="${VERSION#v}" # 0.1.6-beta.3 (for download URL) | |
| PKGVER="${TAGVER//-/.}" # 0.1.6.beta.3 (AUR compliant) | |
| # Setup SSH for AUR | |
| mkdir -p ~/.ssh | |
| echo "$AUR_SSH_KEY" > ~/.ssh/aur | |
| chmod 600 ~/.ssh/aur | |
| cat >> ~/.ssh/config << 'SSHEOF' | |
| Host aur.archlinux.org | |
| IdentityFile ~/.ssh/aur | |
| User aur | |
| StrictHostKeyChecking no | |
| SSHEOF | |
| # Clone AUR repo | |
| git clone ssh://aur@aur.archlinux.org/winload-rust-bin.git aur-repo || { | |
| echo "⚠️ AUR repo doesn't exist yet. Please create it manually first." | |
| echo " Run: ssh aur@aur.archlinux.org setup-repo winload-rust-bin" | |
| exit 1 | |
| } | |
| # Copy files | |
| cp PKGBUILD aur-repo/ | |
| cp .SRCINFO aur-repo/ | |
| # Commit and push | |
| cd aur-repo | |
| git config user.name "VincentZyu" | |
| git config user.email "vincentzyu233@gmail.com" | |
| git add PKGBUILD .SRCINFO | |
| git commit -m "Update to ${PKGVER}" || echo "No changes to commit" | |
| git push | |
| echo "✅ winload-rust-bin updated!" | |
| # ── 发布到 npm(@vincentzyuapps/winload: 预编译二进制)──────────── | |
| # 原理同 esbuild / @biomejs/biome / turbo: | |
| # 1. 为每个平台发布一个只含二进制的包 (os/cpu 字段让 npm 自动筛选) | |
| # 2. 主包 @vincentzyuapps/winload 通过 optionalDependencies 引用它们 | |
| # 3. 用户 npm install 时只下载匹配当前平台的那一个 | |
| # 4. 同时发布到 GitHub Packages (npm.pkg.github.com) | |
| publish-npm: | |
| name: Publish to npm | |
| needs: [check, release] | |
| if: always() && needs.check.outputs.should_publish == 'true' && (needs.release.result == 'success' || needs.release.result == 'skipped') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Download binaries from release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| REPO="${{ github.repository }}" | |
| BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}" | |
| mkdir -p artifacts | |
| echo "📥 Downloading binaries for npm publish..." | |
| curl -fSL -o artifacts/winload-windows-x86_64.exe "${BASE_URL}/winload-windows-x86_64-${VERSION}.exe" | |
| curl -fSL -o artifacts/winload-windows-aarch64.exe "${BASE_URL}/winload-windows-aarch64-${VERSION}.exe" | |
| curl -fSL -o artifacts/winload-linux-x86_64 "${BASE_URL}/winload-linux-x86_64-${VERSION}" | |
| curl -fSL -o artifacts/winload-linux-aarch64 "${BASE_URL}/winload-linux-aarch64-${VERSION}" | |
| curl -fSL -o artifacts/winload-macos-x86_64 "${BASE_URL}/winload-macos-x86_64-${VERSION}" | |
| curl -fSL -o artifacts/winload-macos-aarch64 "${BASE_URL}/winload-macos-aarch64-${VERSION}" | |
| echo "✅ Downloaded:" | |
| ls -lh artifacts/ | |
| - name: Publish platform packages | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| NPM_VERSION="${VERSION#v}" | |
| NPM_TAG="latest" | |
| echo "📦 npm version: ${NPM_VERSION} (tag: ${NPM_TAG})" | |
| # 平台定义: npm包名|os|cpu|源文件|二进制名 | |
| PLATFORMS=( | |
| "@vincentzyuapps/winload-win32-x64|win32|x64|artifacts/winload-windows-x86_64.exe|winload.exe" | |
| "@vincentzyuapps/winload-win32-arm64|win32|arm64|artifacts/winload-windows-aarch64.exe|winload.exe" | |
| "@vincentzyuapps/winload-linux-x64|linux|x64|artifacts/winload-linux-x86_64|winload" | |
| "@vincentzyuapps/winload-linux-arm64|linux|arm64|artifacts/winload-linux-aarch64|winload" | |
| "@vincentzyuapps/winload-darwin-x64|darwin|x64|artifacts/winload-macos-x86_64|winload" | |
| "@vincentzyuapps/winload-darwin-arm64|darwin|arm64|artifacts/winload-macos-aarch64|winload" | |
| ) | |
| for entry in "${PLATFORMS[@]}"; do | |
| IFS='|' read -r PKG_NAME PKG_OS PKG_CPU SOURCE_BIN BIN_NAME <<< "$entry" | |
| echo "" | |
| echo "📦 Publishing ${PKG_NAME}@${NPM_VERSION}..." | |
| PKG_DIR="npm-platforms/${PKG_NAME}" | |
| mkdir -p "${PKG_DIR}/bin" | |
| cp "${SOURCE_BIN}" "${PKG_DIR}/bin/${BIN_NAME}" | |
| chmod +x "${PKG_DIR}/bin/${BIN_NAME}" | |
| cat > "${PKG_DIR}/package.json" << PKGJSON | |
| { | |
| "name": "${PKG_NAME}", | |
| "version": "${NPM_VERSION}", | |
| "description": "winload binary for ${PKG_OS}-${PKG_CPU}", | |
| "license": "MIT", | |
| "repository": { | |
| "type": "git", | |
| "url": "https://github.com/${{ github.repository }}" | |
| }, | |
| "os": ["${PKG_OS}"], | |
| "cpu": ["${PKG_CPU}"], | |
| "files": ["bin/"] | |
| } | |
| PKGJSON | |
| cd "${PKG_DIR}" | |
| npm publish --access public --tag "${NPM_TAG}" 2>&1 || echo "⚠️ Failed to publish ${PKG_NAME} (may already exist)" | |
| cd "$GITHUB_WORKSPACE" | |
| done | |
| - name: Publish main package | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| NPM_VERSION="${VERSION#v}" | |
| NPM_TAG="latest" | |
| # 复制项目根目录的 readme.md 作为 npm 页面展示 | |
| cp readme.md npm/winload-rust-bin/readme.md | |
| cd npm/winload-rust-bin | |
| # 动态更新 package.json 中的版本号(版本以 Cargo.toml 为唯一真实源) | |
| node -e " | |
| const fs = require('fs'); | |
| const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); | |
| pkg.version = '${NPM_VERSION}'; | |
| for (const dep of Object.keys(pkg.optionalDependencies || {})) { | |
| pkg.optionalDependencies[dep] = '${NPM_VERSION}'; | |
| } | |
| fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); | |
| " | |
| echo "📦 Publishing @vincentzyuapps/winload@${NPM_VERSION} (tag: ${NPM_TAG})..." | |
| cat package.json | |
| npm publish --access public --tag "${NPM_TAG}" | |
| echo "✅ npm packages published!" | |
| # ── 同步发布到 GitHub Packages ── | |
| # 直接在每个包目录写入 .npmrc,确保 npm 能找到 npm.pkg.github.com 的认证。 | |
| # (actions/setup-node 生成的 .npmrc 只包含 registry.npmjs.org 的认证) | |
| - name: Publish platform packages to GitHub Packages | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.check.outputs.version }}" | |
| NPM_VERSION="${VERSION#v}" | |
| NPM_TAG="latest" | |
| for dir in npm-platforms/@vincentzyuapps/*/; do | |
| [ -d "$dir" ] || continue | |
| echo "📦 Publishing $(basename $dir) to GitHub Packages..." | |
| cd "$dir" | |
| echo "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" > .npmrc | |
| npm publish --registry https://npm.pkg.github.com --tag "${NPM_TAG}" 2>&1 || echo "⚠️ Failed to publish $(basename $dir) to GitHub Packages (may already exist)" | |
| cd "$GITHUB_WORKSPACE" | |
| done | |
| - name: Publish main package to GitHub Packages | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| cd npm/winload-rust-bin | |
| echo "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" > .npmrc | |
| npm publish --registry https://npm.pkg.github.com --tag "latest" | |
| echo "✅ GitHub Packages published!" | |
| # ── 发布到 PyPI (Python 包) ─────────────────────────────── | |
| publish-pypi: | |
| name: Publish to PyPI | |
| needs: check | |
| if: needs.check.outputs.should_publish_pypi == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v4 | |
| - name: Copy readme for PyPI | |
| run: cp readme.md py/readme.md | |
| - name: Build package | |
| working-directory: py | |
| run: | | |
| uv build | |
| - name: Publish to PyPI | |
| working-directory: py | |
| env: | |
| UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }} | |
| run: | | |
| uv publish --publish-url https://upload.pypi.org/legacy/ | |
| # ── 发布到 crates.io (Rust 包) ─────────────────────────────── | |
| publish-crates-io: | |
| name: Publish to crates.io | |
| needs: [check, build] | |
| if: needs.check.outputs.should_publish_crates == 'true' && needs.build.result == 'success' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Prepare README for crates.io | |
| run: | | |
| pwd | |
| ls -la | |
| cp ./readme.md ./rust/readme.md | |
| - name: Publish to crates.io | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} | |
| run: | | |
| cd rust | |
| cargo publish --allow-dirty --token "$CARGO_REGISTRY_TOKEN" | |
| echo "✅ Published to crates.io!" |