Skip to content

fix: SVG benchmark chart mislabeling Python as Rust #77

fix: SVG benchmark chart mislabeling Python as Rust

fix: SVG benchmark chart mislabeling Python as Rust #77

Workflow file for this run

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!"