mirror of
https://github.com/feigeCode/navop.git
synced 2026-09-22 08:01:27 +00:00
mozilla-actions/sccache-action 从 GitHub Release CDN 下载 sccache 二进制失败 (CI run 35347089674 上连续两次 HTTP 504)会直接让整个 test job 变红, Windows 测试根本没跑到,ci-gate 随之拦截合并。sccache 只是构建加速器, 不应成为测试或发布 job 的可用性依赖。 - Setup sccache 标记为 continue-on-error: true;该 action 的 post step 复用同一个 Pipelines.ActionStep,因此继续性错误同样覆盖 post step 失败 - Initialize sccache 在 steps.sccache.outcome != 'success' 时输出 warning,并向 GITHUB_ENV 写入空 RUSTC_WRAPPER,让后续 cargo 走无缓存构建;空值与 ci.yml 中 windows-rdp-probe job 的 RUSTC_WRAPPER: "" 是同一语义 - 同步 release.yml 的同一段路径,避免发布 job 被同类瞬时故障打断 - script/test-release-packaging.mjs 的共享缓存策略契约补上 best-effort 断言, 防止后续改动把这两个步骤改回硬依赖 验证: - ruby -ryaml 解析两个 workflow:jobs 与步骤结构不变 - node --test script/test-release-packaging.mjs:26 passed / 0 failed - cargo test -p windows_rdp_host --test contract:39 passed / 0 failed - 变异验证:临时删掉 ci.yml 的 continue-on-error,共享缓存策略契约立即转红 - 本地 RUSTC_WRAPPER="" cargo check 正常,确认空值等价于不启用 wrapper
855 lines
34 KiB
YAML
855 lines
34 KiB
YAML
name: Release
|
||
on:
|
||
workflow_dispatch:
|
||
inputs:
|
||
tag:
|
||
description: "GitHub Release tag to build or repair, for example v0.8.10"
|
||
required: true
|
||
type: string
|
||
platform:
|
||
description: "Build all primary platforms or repair one platform"
|
||
required: true
|
||
type: choice
|
||
default: all
|
||
options:
|
||
- all
|
||
- macos-arm64
|
||
- macos-x64
|
||
- linux-x64
|
||
- linux-x64-portable
|
||
- linux-arm64
|
||
- linux-arm64-portable
|
||
- windows-x64
|
||
- windows-x86
|
||
build_mode:
|
||
description: "fast 用 thin LTO(构建较快、体积略大);release 用 fat LTO + panic=abort(体积最小)"
|
||
required: true
|
||
type: choice
|
||
default: release
|
||
options:
|
||
- release
|
||
- fast
|
||
|
||
permissions:
|
||
actions: write
|
||
contents: write
|
||
|
||
concurrency:
|
||
group: release-${{ inputs.tag }}
|
||
cancel-in-progress: false
|
||
|
||
env:
|
||
CARGO_TERM_COLOR: always
|
||
RUSTC_WRAPPER: sccache
|
||
SCCACHE_GHA_ENABLED: "true"
|
||
SCCACHE_IDLE_TIMEOUT: "7200"
|
||
|
||
jobs:
|
||
prepare:
|
||
name: Prepare release build
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
matrix: ${{ steps.matrix.outputs.matrix }}
|
||
tag: ${{ steps.release.outputs.tag }}
|
||
platform: ${{ steps.release.outputs.platform }}
|
||
build_mode: ${{ steps.release.outputs.build_mode }}
|
||
|
||
steps:
|
||
- name: Validate release request
|
||
id: release
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
RELEASE_TAG: ${{ inputs.tag }}
|
||
RELEASE_PLATFORM: ${{ inputs.platform }}
|
||
BUILD_MODE: ${{ inputs.build_mode }}
|
||
run: |
|
||
set -euo pipefail
|
||
if [ "$BUILD_MODE" != "release" ] && [ "$BUILD_MODE" != "fast" ]; then
|
||
echo "::error::Unsupported build mode: $BUILD_MODE"
|
||
exit 1
|
||
fi
|
||
if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then
|
||
echo "::error::Invalid release tag: $RELEASE_TAG"
|
||
exit 1
|
||
fi
|
||
gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${RELEASE_TAG}" >/dev/null
|
||
if [ "$RELEASE_PLATFORM" != "all" ]; then
|
||
gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null
|
||
fi
|
||
{
|
||
echo "tag=$RELEASE_TAG"
|
||
echo "platform=$RELEASE_PLATFORM"
|
||
echo "build_mode=$BUILD_MODE"
|
||
} >> "$GITHUB_OUTPUT"
|
||
|
||
- name: Check out release tag
|
||
uses: actions/checkout@v7
|
||
with:
|
||
ref: ${{ steps.release.outputs.tag }}
|
||
|
||
- name: Verify application version matches release tag
|
||
env:
|
||
RELEASE_TAG: ${{ steps.release.outputs.tag }}
|
||
run: |
|
||
set -euo pipefail
|
||
expected_version="${RELEASE_TAG#v}"
|
||
manifest_version="$(
|
||
awk -F'"' '
|
||
/^\[package\]$/ { in_package = 1; next }
|
||
/^\[/ { in_package = 0 }
|
||
in_package && /^version = "/ { print $2; exit }
|
||
' main/Cargo.toml
|
||
)"
|
||
if [ "$manifest_version" != "$expected_version" ]; then
|
||
echo "::error::main/Cargo.toml version ${manifest_version:-<missing>} does not match release tag ${RELEASE_TAG}"
|
||
exit 1
|
||
fi
|
||
echo "Verified Navop application version: $manifest_version"
|
||
|
||
- name: Verify changelog entry
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
RELEASE_TAG: ${{ steps.release.outputs.tag }}
|
||
run: |
|
||
set -euo pipefail
|
||
if [ -f CHANGELOG.md ] && [ -f script/changelog.py ]; then
|
||
python3 script/changelog.py extract \
|
||
--tag "$RELEASE_TAG" \
|
||
--changelog CHANGELOG.md \
|
||
--output /tmp/release-notes.md
|
||
echo "Verified tagged changelog entry: $RELEASE_TAG"
|
||
elif gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||
echo "::warning::${RELEASE_TAG} predates the tracked changelog; preserving its existing GitHub Release body for this legacy repair."
|
||
else
|
||
echo "::error::${RELEASE_TAG} does not contain CHANGELOG.md and script/changelog.py. Generate and commit the changelog entry before creating a new release tag."
|
||
exit 1
|
||
fi
|
||
|
||
- name: Resolve platform matrix
|
||
id: matrix
|
||
env:
|
||
RELEASE_PLATFORM: ${{ inputs.platform }}
|
||
run: |
|
||
set -euo pipefail
|
||
macos_arm64='{"target":"aarch64-apple-darwin","os":"macos-latest","binary":"navop","archive":"navop-aarch64-apple-darwin.tar.gz","public_label":"macos-arm64","variant":"standard","arm_linux":false,"windows_native_rdp":false,"portable_linux":false}'
|
||
macos_x64='{"target":"x86_64-apple-darwin","os":"macos-15-intel","binary":"navop","archive":"navop-x86_64-apple-darwin.tar.gz","public_label":"macos-x64","variant":"standard","arm_linux":false,"windows_native_rdp":false,"portable_linux":false}'
|
||
linux_x64='{"target":"x86_64-unknown-linux-gnu","os":"ubuntu-latest","binary":"navop","archive":"navop-x86_64-unknown-linux-gnu.tar.gz","public_label":"linux-x64","variant":"standard","arm_linux":false,"windows_native_rdp":false,"portable_linux":false}'
|
||
linux_x64_portable='{"target":"x86_64-unknown-linux-gnu","os":"ubuntu-22.04","binary":"navop","archive":"navop-x86_64-unknown-linux-gnu-portable.tar.gz","public_label":"linux-x64-portable","variant":"portable","arm_linux":false,"windows_native_rdp":false,"portable_linux":true}'
|
||
linux_arm64='{"target":"aarch64-unknown-linux-gnu","os":"ubuntu-24.04-arm","binary":"navop","archive":"navop-aarch64-unknown-linux-gnu.tar.gz","public_label":"linux-arm64","variant":"standard","arm_linux":true,"windows_native_rdp":false,"portable_linux":false}'
|
||
linux_arm64_portable='{"target":"aarch64-unknown-linux-gnu","os":"ubuntu-24.04-arm","binary":"navop","archive":"navop-aarch64-unknown-linux-gnu-portable.tar.gz","public_label":"linux-arm64-portable","variant":"portable","arm_linux":true,"windows_native_rdp":false,"portable_linux":true}'
|
||
windows_x64='{"target":"x86_64-pc-windows-msvc","os":"windows-latest","binary":"navop.exe","archive":"navop-x86_64-pc-windows-msvc.zip","public_label":"windows-x64","variant":"standard","arm_linux":false,"windows_native_rdp":true,"portable_linux":false,"windows_arch":"x64"}'
|
||
windows_x86='{"target":"i686-pc-windows-msvc","os":"windows-latest","binary":"navop.exe","archive":"navop-i686-pc-windows-msvc.zip","public_label":"win32","variant":"standard","arm_linux":false,"windows_native_rdp":true,"portable_linux":false,"windows_arch":"x86"}'
|
||
|
||
case "$RELEASE_PLATFORM" in
|
||
all) matrix="[$macos_arm64,$macos_x64,$linux_x64,$linux_x64_portable,$linux_arm64,$linux_arm64_portable,$windows_x64,$windows_x86]" ;;
|
||
macos-arm64) matrix="[$macos_arm64]" ;;
|
||
macos-x64) matrix="[$macos_x64]" ;;
|
||
linux-x64) matrix="[$linux_x64,$linux_x64_portable]" ;;
|
||
linux-x64-portable) matrix="[$linux_x64_portable]" ;;
|
||
linux-arm64) matrix="[$linux_arm64,$linux_arm64_portable]" ;;
|
||
linux-arm64-portable) matrix="[$linux_arm64_portable]" ;;
|
||
windows-x64) matrix="[$windows_x64]" ;;
|
||
windows-x86) matrix="[$windows_x86]" ;;
|
||
*) echo "::error::Unsupported platform: $RELEASE_PLATFORM"; exit 1 ;;
|
||
esac
|
||
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
|
||
|
||
build:
|
||
name: Build (${{ matrix.target }} / ${{ matrix.variant }})
|
||
needs: prepare
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include: ${{ fromJSON(needs.prepare.outputs.matrix) }}
|
||
runs-on: ${{ matrix.os }}
|
||
# fat LTO + codegen-units=1 让链接阶段更慢(Windows x64 基线已 64min),一律给 180min
|
||
timeout-minutes: 180
|
||
|
||
steps:
|
||
- uses: actions/checkout@v7
|
||
with:
|
||
ref: ${{ needs.prepare.outputs.tag }}
|
||
|
||
- name: Setup Rust toolchain
|
||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||
with:
|
||
target: ${{ matrix.target }}
|
||
cache: false
|
||
|
||
# sccache 只是构建加速器:GitHub Release CDN 偶发 504 会让下载安装失败,
|
||
# 此时必须退化成无缓存构建,而不是让整个发布 job 失败。
|
||
- name: Setup sccache
|
||
id: sccache
|
||
uses: mozilla-actions/sccache-action@v0.0.10
|
||
continue-on-error: true
|
||
|
||
- name: Initialize sccache
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
if [ "${{ steps.sccache.outcome }}" != "success" ]; then
|
||
echo "::warning::sccache 安装失败,本次构建退化为无编译器缓存"
|
||
echo "RUSTC_WRAPPER=" >> "$GITHUB_ENV"
|
||
exit 0
|
||
fi
|
||
sccache --start-server
|
||
sccache --zero-stats
|
||
sccache --show-stats
|
||
|
||
- name: Restore shared Cargo inputs
|
||
id: cargo-inputs-cache
|
||
uses: actions/cache/restore@v4
|
||
with:
|
||
path: |
|
||
~/.cargo/registry/index/
|
||
~/.cargo/registry/cache/
|
||
~/.cargo/git/db/
|
||
key: navop-cargo-inputs-v1-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
|
||
restore-keys: |
|
||
navop-cargo-inputs-v1-${{ runner.os }}-
|
||
release-cargo-inputs-${{ runner.os }}-${{ matrix.target }}-
|
||
|
||
- name: Free disk space (macOS)
|
||
if: runner.os == 'macOS'
|
||
run: |
|
||
df -h
|
||
|
||
remove_path() {
|
||
local path="$1"
|
||
if [ -e "$path" ]; then
|
||
sudo rm -rf "$path" || echo "::warning::Failed to remove $path"
|
||
fi
|
||
}
|
||
|
||
remove_directory_contents() {
|
||
local path="$1"
|
||
if [ -d "$path" ]; then
|
||
sudo find "$path" -mindepth 1 -maxdepth 1 -exec rm -rf {} + \
|
||
|| echo "::warning::Failed to remove some entries under $path"
|
||
fi
|
||
}
|
||
|
||
remove_path /usr/local/lib/node_modules
|
||
remove_path "$HOME/.npm"
|
||
remove_path "$HOME/.nvm"
|
||
remove_directory_contents "$HOME/Library/Caches"
|
||
remove_path /System/Library/Frameworks/Python.framework
|
||
|
||
df -h
|
||
|
||
- name: Install system dependencies (Linux)
|
||
if: runner.os == 'Linux'
|
||
run: script/bootstrap
|
||
|
||
- name: Install system dependencies (macOS)
|
||
if: runner.os == 'macOS'
|
||
run: script/bootstrap
|
||
|
||
- name: Install system dependencies (Windows)
|
||
if: runner.os == 'Windows'
|
||
shell: pwsh
|
||
run: |
|
||
cmake --version
|
||
choco install nasm --no-progress --yes
|
||
$nasmDir = Join-Path $env:ProgramFiles "NASM"
|
||
$nasmExe = Join-Path $nasmDir "nasm.exe"
|
||
if (-not (Test-Path -LiteralPath $nasmExe -PathType Leaf)) {
|
||
throw "NASM executable was not found at '$nasmExe'"
|
||
}
|
||
$env:Path = "$nasmDir;$env:Path"
|
||
$nasmDir | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||
nasm -v
|
||
|
||
- name: Install Zig toolchain (portable Linux)
|
||
if: matrix.portable_linux
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
python3 -m venv "$RUNNER_TEMP/ziglang"
|
||
"$RUNNER_TEMP/ziglang/bin/python" -m pip install ziglang==0.14.1
|
||
cargo install --locked cargo-zigbuild --version 0.23.0
|
||
echo "CARGO_ZIGBUILD_PYTHON_PATH=$RUNNER_TEMP/ziglang/bin/python" >> "$GITHUB_ENV"
|
||
"$RUNNER_TEMP/ziglang/bin/python" -m ziglang version
|
||
cargo-zigbuild --version
|
||
|
||
- name: Install portable packaging dependencies
|
||
if: matrix.portable_linux
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
sudo apt-get update
|
||
sudo apt-get install -y binutils musl-tools
|
||
|
||
- name: Configure MSVC environment
|
||
if: runner.os == 'Windows'
|
||
uses: ilammy/msvc-dev-cmd@v1
|
||
with:
|
||
arch: ${{ matrix.windows_arch }}
|
||
|
||
- name: Build Windows RDP toolchain probe
|
||
if: runner.os == 'Windows'
|
||
shell: pwsh
|
||
run: >-
|
||
./script/build-windows-rdp-probe.ps1
|
||
-Target "${{ matrix.target }}"
|
||
|
||
- name: Set environment variables
|
||
shell: bash
|
||
run: |
|
||
VERSION="${{ needs.prepare.outputs.tag }}"
|
||
echo "ONETCLI_VERSION=${VERSION#v}" >> "$GITHUB_ENV"
|
||
echo "PUBLIC_BASENAME=navop-${VERSION#v}-${{ matrix.public_label }}" >> "$GITHUB_ENV"
|
||
|
||
- name: Verify secrets are set
|
||
shell: bash
|
||
env:
|
||
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
|
||
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
|
||
run: |
|
||
if [ -z "$SUPABASE_URL" ]; then
|
||
echo "::error::SUPABASE_URL secret is empty or not set"
|
||
exit 1
|
||
fi
|
||
if [ -z "$SUPABASE_ANON_KEY" ]; then
|
||
echo "::error::SUPABASE_ANON_KEY secret is empty or not set"
|
||
exit 1
|
||
fi
|
||
|
||
- name: Build release binary
|
||
if: runner.os != 'Windows'
|
||
shell: bash
|
||
env:
|
||
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
|
||
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
|
||
NAVOP_PUBLIC_BASE_URL: ${{ vars.NAVOP_PUBLIC_BASE_URL }}
|
||
NAVOP_WEBSITE_BASE_URL: ${{ vars.NAVOP_WEBSITE_BASE_URL }}
|
||
ONETCLI_TEAM_MANAGEMENT_URL_TEMPLATE: ${{ vars.ONETCLI_TEAM_MANAGEMENT_URL_TEMPLATE }}
|
||
run: |
|
||
set -euo pipefail
|
||
if [ "${{ needs.prepare.outputs.build_mode }}" = "fast" ]; then
|
||
export CARGO_PROFILE_RELEASE_LTO=thin
|
||
export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16
|
||
elif [ "${{ matrix.arm_linux }}" = "true" ]; then
|
||
export CARGO_BUILD_JOBS=2
|
||
export CARGO_PROFILE_RELEASE_LTO=thin
|
||
export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16
|
||
fi
|
||
cargo --version
|
||
rustc --version
|
||
echo "CARGO_BUILD_JOBS=${CARGO_BUILD_JOBS:-}"
|
||
echo "CARGO_PROFILE_RELEASE_LTO=${CARGO_PROFILE_RELEASE_LTO:-}"
|
||
echo "CARGO_PROFILE_RELEASE_CODEGEN_UNITS=${CARGO_PROFILE_RELEASE_CODEGEN_UNITS:-}"
|
||
if [ "${{ matrix.portable_linux }}" = "true" ]; then
|
||
cargo zigbuild \
|
||
--release \
|
||
-p main \
|
||
--target "${{ matrix.target }}.2.28" \
|
||
--no-default-features \
|
||
--features wasm-components,shell-plugins
|
||
else
|
||
cargo build --release -p main --target "${{ matrix.target }}"
|
||
fi
|
||
test -x "target/${{ matrix.target }}/release/${{ matrix.binary }}"
|
||
# 体积回归在日志里可见(fat LTO + panic=abort 后约 107MiB)
|
||
ls -lh "target/${{ matrix.target }}/release/${{ matrix.binary }}"
|
||
|
||
- name: Build release binary (Windows)
|
||
if: runner.os == 'Windows'
|
||
shell: pwsh
|
||
env:
|
||
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
|
||
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
|
||
NAVOP_PUBLIC_BASE_URL: ${{ vars.NAVOP_PUBLIC_BASE_URL }}
|
||
NAVOP_WEBSITE_BASE_URL: ${{ vars.NAVOP_WEBSITE_BASE_URL }}
|
||
ONETCLI_TEAM_MANAGEMENT_URL_TEMPLATE: ${{ vars.ONETCLI_TEAM_MANAGEMENT_URL_TEMPLATE }}
|
||
run: |
|
||
if ([string]::IsNullOrWhiteSpace($env:VCToolsInstallDir)) {
|
||
throw "VCToolsInstallDir is not configured"
|
||
}
|
||
|
||
$target = "${{ matrix.target }}"
|
||
$linkerArchitecture = if ($target -eq "i686-pc-windows-msvc") {
|
||
"x86"
|
||
} else {
|
||
"x64"
|
||
}
|
||
$linker = Join-Path `
|
||
$env:VCToolsInstallDir `
|
||
"bin\Hostx64\$linkerArchitecture\link.exe"
|
||
if (-not (Test-Path -LiteralPath $linker -PathType Leaf)) {
|
||
throw "MSVC linker was not found at '$linker'"
|
||
}
|
||
|
||
$linkerEnvironmentName = "CARGO_TARGET_$($target.Replace('-', '_').ToUpperInvariant())_LINKER"
|
||
[Environment]::SetEnvironmentVariable(
|
||
$linkerEnvironmentName,
|
||
$linker,
|
||
[EnvironmentVariableTarget]::Process
|
||
)
|
||
|
||
cargo --version
|
||
rustc --version
|
||
Write-Host "Using MSVC linker for ${target}: $linker"
|
||
if ("${{ needs.prepare.outputs.build_mode }}" -eq "fast") {
|
||
$env:CARGO_PROFILE_RELEASE_LTO = "thin"
|
||
$env:CARGO_PROFILE_RELEASE_CODEGEN_UNITS = "16"
|
||
Write-Host "Fast build mode: thin LTO, codegen-units=16"
|
||
}
|
||
# 32 位 Windows 暂不支持 shell-plugins:其 gpui-shell 依赖的
|
||
# quickjs-jit 运行时代码与 cranelift 后端仅支持 64 位
|
||
# (x86_64/aarch64),i686 无法编译。win32 构建先剔除该 feature。
|
||
$features = if ($target -eq "i686-pc-windows-msvc") {
|
||
@("--no-default-features", "--features", "wasm-components,embedded-webview,windows-native-rdp")
|
||
} else {
|
||
@("--features", "windows-native-rdp")
|
||
}
|
||
cargo build `
|
||
--release `
|
||
-p main `
|
||
@features `
|
||
--target $target
|
||
|
||
$binary = "target\$target\release\${{ matrix.binary }}"
|
||
if (-not (Test-Path -LiteralPath $binary -PathType Leaf)) {
|
||
throw "Release binary was not created: '$binary'"
|
||
}
|
||
|
||
- name: Verify portable Linux glibc baseline
|
||
if: matrix.portable_linux
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
script/check-linux-glibc-baseline.sh \
|
||
"target/${{ matrix.target }}/release/${{ matrix.binary }}" \
|
||
"2.28"
|
||
|
||
- name: Save shared Cargo inputs
|
||
if: >-
|
||
${{
|
||
always() &&
|
||
steps.cargo-inputs-cache.outcome == 'success' &&
|
||
steps.cargo-inputs-cache.outputs.cache-hit != 'true' &&
|
||
(
|
||
needs.prepare.outputs.platform != 'all' ||
|
||
matrix.target == 'x86_64-apple-darwin' ||
|
||
matrix.target == 'x86_64-unknown-linux-gnu' ||
|
||
matrix.target == 'x86_64-pc-windows-msvc'
|
||
)
|
||
}}
|
||
uses: actions/cache/save@v4
|
||
with:
|
||
path: |
|
||
~/.cargo/registry/index/
|
||
~/.cargo/registry/cache/
|
||
~/.cargo/git/db/
|
||
key: ${{ steps.cargo-inputs-cache.outputs.cache-primary-key }}
|
||
|
||
- name: Show sccache statistics
|
||
if: always()
|
||
shell: bash
|
||
run: sccache --show-stats || true
|
||
|
||
- name: Create macOS app bundle
|
||
if: runner.os == 'macOS'
|
||
run: |
|
||
chmod +x script/bundle-macos.sh
|
||
chmod +x script/bundle-macos-dmg.sh
|
||
script/bundle-macos.sh ${{ matrix.target }}
|
||
# 释放磁盘:release target 目录很大,DMG 制作(在 target/ 下写入镜像)
|
||
# 会因 runner 空间不足而失败(hdiutil: No space left on device)。
|
||
df -h
|
||
rm -rf target/release/deps target/release/build target/release/incremental target/release/.fingerprint
|
||
rm -rf "target/${{ matrix.target }}/release/deps" "target/${{ matrix.target }}/release/build" "target/${{ matrix.target }}/release/incremental" "target/${{ matrix.target }}/release/.fingerprint"
|
||
df -h
|
||
script/bundle-macos-dmg.sh ${{ matrix.target }}
|
||
tar czf ${{ matrix.archive }} -C target Navop.app
|
||
cp "${{ matrix.archive }}" "${PUBLIC_BASENAME}.tar.gz"
|
||
mv "navop-${{ matrix.target }}.dmg" "${PUBLIC_BASENAME}.dmg"
|
||
|
||
- name: Package (Linux)
|
||
if: runner.os == 'Linux'
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
rm -rf package
|
||
|
||
if [ "${{ matrix.portable_linux }}" = "true" ]; then
|
||
script/package-linux-portable.sh \
|
||
--binary "target/${{ matrix.target }}/release/${{ matrix.binary }}" \
|
||
--output package \
|
||
--launcher-source script/linux-portable-launcher.c \
|
||
--target "${{ matrix.target }}" \
|
||
--glibc-baseline "2.28"
|
||
else
|
||
mkdir -p package/usr/bin
|
||
cp "target/${{ matrix.target }}/release/${{ matrix.binary }}" package/usr/bin/
|
||
fi
|
||
|
||
mkdir -p package/usr/share/applications
|
||
mkdir -p package/usr/share/mime/packages
|
||
mkdir -p package/usr/share/icons/hicolor/128x128/apps
|
||
mkdir -p package/usr/share/icons/hicolor/256x256/apps
|
||
mkdir -p package/usr/share/icons/hicolor/512x515/apps
|
||
cp resources/linux/navop.desktop package/usr/share/applications/
|
||
cp resources/linux/navop.xml package/usr/share/mime/packages/
|
||
cp resources/linux/navop-128.png package/usr/share/icons/hicolor/128x128/apps/navop.png
|
||
cp resources/linux/navop-256.png package/usr/share/icons/hicolor/256x256/apps/navop.png
|
||
cp resources/linux/navop-512.png package/usr/share/icons/hicolor/512x515/apps/navop.png
|
||
tar \
|
||
--sort=name \
|
||
--mtime='UTC 1970-01-01' \
|
||
--owner=0 \
|
||
--group=0 \
|
||
--numeric-owner \
|
||
-czf "${{ matrix.archive }}" \
|
||
-C package .
|
||
cp "${{ matrix.archive }}" "${PUBLIC_BASENAME}.tar.gz"
|
||
|
||
- name: Install Linux packaging dependencies
|
||
if: matrix.target == 'x86_64-unknown-linux-gnu' && !matrix.portable_linux
|
||
run: |
|
||
sudo apt-get update
|
||
sudo apt-get install -y rpm
|
||
|
||
- name: Package Linux installers (x86_64)
|
||
if: matrix.target == 'x86_64-unknown-linux-gnu' && !matrix.portable_linux
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
|
||
PACKAGE_VERSION="${ONETCLI_VERSION//-/.}"
|
||
DEB_NAME="navop_${PACKAGE_VERSION}_amd64.deb"
|
||
RPM_NAME="navop-${PACKAGE_VERSION}-1.x86_64.rpm"
|
||
APPIMAGE_NAME="navop_${PACKAGE_VERSION}_amd64.AppImage"
|
||
|
||
rm -rf package-deb rpmbuild Navop.AppDir linuxdeploy-x86_64.AppImage
|
||
|
||
mkdir -p package-deb/DEBIAN
|
||
cp -a package/. package-deb/
|
||
cat > package-deb/DEBIAN/control <<EOF
|
||
Package: navop
|
||
Version: ${PACKAGE_VERSION}
|
||
Section: utils
|
||
Priority: optional
|
||
Architecture: amd64
|
||
Provides: onetcli
|
||
Replaces: onetcli
|
||
Conflicts: onetcli
|
||
Maintainer: feigeCode <noreply@github.com>
|
||
Homepage: https://github.com/feigeCode/navop
|
||
Description: Navop - Database, SSH, Terminal, AI Tools
|
||
EOF
|
||
cat > package-deb/DEBIAN/postinst <<'EOF'
|
||
#!/bin/sh
|
||
set -e
|
||
if command -v update-mime-database >/dev/null 2>&1; then
|
||
update-mime-database /usr/share/mime
|
||
fi
|
||
if command -v update-desktop-database >/dev/null 2>&1; then
|
||
update-desktop-database /usr/share/applications
|
||
fi
|
||
EOF
|
||
cat > package-deb/DEBIAN/postrm <<'EOF'
|
||
#!/bin/sh
|
||
set -e
|
||
if command -v update-mime-database >/dev/null 2>&1; then
|
||
update-mime-database /usr/share/mime
|
||
fi
|
||
if command -v update-desktop-database >/dev/null 2>&1; then
|
||
update-desktop-database /usr/share/applications
|
||
fi
|
||
EOF
|
||
chmod 0755 package-deb/DEBIAN/postinst package-deb/DEBIAN/postrm
|
||
dpkg-deb --root-owner-group --build package-deb "${DEB_NAME}"
|
||
|
||
RPM_ROOT="${PWD}/rpmbuild"
|
||
mkdir -p "${RPM_ROOT}"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
||
tar czf "${RPM_ROOT}/SOURCES/navop-${PACKAGE_VERSION}.tar.gz" -C package .
|
||
cat > "${RPM_ROOT}/SPECS/navop.spec" <<EOF
|
||
Name: navop
|
||
Version: ${PACKAGE_VERSION}
|
||
Release: 1%{?dist}
|
||
Summary: Navop - Database, SSH, Terminal, AI Tools
|
||
License: Apache-2.0
|
||
URL: https://github.com/feigeCode/navop
|
||
BuildArch: x86_64
|
||
Provides: onetcli = %{version}-%{release}
|
||
Obsoletes: onetcli < %{version}-%{release}
|
||
Source0: %{name}-%{version}.tar.gz
|
||
|
||
%description
|
||
Navop - Database, SSH, Terminal, AI Tools.
|
||
|
||
%prep
|
||
mkdir -p %{_builddir}/%{name}-%{version}
|
||
tar -xzf %{SOURCE0} -C %{_builddir}/%{name}-%{version}
|
||
|
||
%build
|
||
|
||
%install
|
||
mkdir -p %{buildroot}
|
||
cp -a %{_builddir}/%{name}-%{version}/* %{buildroot}/
|
||
|
||
%post
|
||
update-mime-database /usr/share/mime >/dev/null 2>&1 || :
|
||
update-desktop-database /usr/share/applications >/dev/null 2>&1 || :
|
||
|
||
%postun
|
||
update-mime-database /usr/share/mime >/dev/null 2>&1 || :
|
||
update-desktop-database /usr/share/applications >/dev/null 2>&1 || :
|
||
|
||
%files
|
||
/usr/bin/navop
|
||
/usr/share/applications/navop.desktop
|
||
/usr/share/mime/packages/navop.xml
|
||
/usr/share/icons/hicolor/128x128/apps/navop.png
|
||
/usr/share/icons/hicolor/256x256/apps/navop.png
|
||
/usr/share/icons/hicolor/512x515/apps/navop.png
|
||
EOF
|
||
rpmbuild --define "_topdir ${RPM_ROOT}" -bb "${RPM_ROOT}/SPECS/navop.spec"
|
||
generated_rpm="$(find "${RPM_ROOT}/RPMS" -name "navop-${PACKAGE_VERSION}-1*.x86_64.rpm" -print -quit)"
|
||
test -n "${generated_rpm}"
|
||
cp "${generated_rpm}" "${RPM_NAME}"
|
||
|
||
mkdir -p Navop.AppDir
|
||
cp -a package/. Navop.AppDir/
|
||
curl -L -o linuxdeploy-x86_64.AppImage \
|
||
https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||
chmod +x linuxdeploy-x86_64.AppImage
|
||
VERSION="${PACKAGE_VERSION}" APPIMAGE_EXTRACT_AND_RUN=1 ./linuxdeploy-x86_64.AppImage \
|
||
--appdir Navop.AppDir \
|
||
--executable Navop.AppDir/usr/bin/navop \
|
||
--desktop-file resources/linux/navop.desktop \
|
||
--icon-file package/usr/share/icons/hicolor/512x515/apps/navop.png \
|
||
--output appimage
|
||
generated_appimage="$(find . -maxdepth 1 -name '*.AppImage' ! -name 'linuxdeploy-x86_64.AppImage' -print -quit)"
|
||
test -n "${generated_appimage}"
|
||
mv "${generated_appimage}" "${APPIMAGE_NAME}"
|
||
|
||
- name: Package (Windows)
|
||
if: runner.os == 'Windows'
|
||
shell: pwsh
|
||
run: |
|
||
New-Item -ItemType Directory -Force package | Out-Null
|
||
New-Item -ItemType Directory -Force portable-package | Out-Null
|
||
Copy-Item "target/${{ matrix.target }}/release/${{ matrix.binary }}" "package/${{ matrix.binary }}"
|
||
Copy-Item "target/${{ matrix.target }}/release/${{ matrix.binary }}" "portable-package/${{ matrix.binary }}"
|
||
Set-Content -Path "portable-package/navop.portable" -Value "" -NoNewline
|
||
Compress-Archive -Path "package/*" -DestinationPath "${{ matrix.archive }}"
|
||
Copy-Item "${{ matrix.archive }}" "${env:PUBLIC_BASENAME}.zip"
|
||
Compress-Archive -Path "portable-package/*" -DestinationPath "${env:PUBLIC_BASENAME}-portable.zip"
|
||
|
||
- name: Install WiX Toolset
|
||
if: runner.os == 'Windows'
|
||
shell: pwsh
|
||
run: |
|
||
node script/generate-windows-license.mjs
|
||
dotnet tool install --global wix --version 6.0.2
|
||
wix extension add -g WixToolset.UI.wixext/6.0.2
|
||
wix extension add -g WixToolset.BootstrapperApplications.wixext/6.0.2
|
||
|
||
- name: Build Windows installers
|
||
if: runner.os == 'Windows'
|
||
shell: pwsh
|
||
run: |
|
||
$msiVersion = ($env:ONETCLI_VERSION -split '-')[0]
|
||
if ($msiVersion -notmatch '^\d+\.\d+\.\d+$') {
|
||
$msiVersion = '0.0.0'
|
||
}
|
||
wix build installer/windows/navop.wxs `
|
||
-ext WixToolset.UI.wixext `
|
||
-culture en-US `
|
||
-loc installer/windows/navop.wxl `
|
||
-arch ${{ matrix.windows_arch }} `
|
||
-d Version="$msiVersion" `
|
||
-d SourceDir="${{ github.workspace }}\package" `
|
||
-d IconPath="${{ github.workspace }}\resources\windows\navop.ico" `
|
||
-out "${env:PUBLIC_BASENAME}.msi"
|
||
wix build installer/windows/navop-bundle.wxs `
|
||
-ext WixToolset.BootstrapperApplications.wixext `
|
||
-arch ${{ matrix.windows_arch }} `
|
||
-d Version="$msiVersion" `
|
||
-d MsiPath="${{ github.workspace }}\${env:PUBLIC_BASENAME}.msi" `
|
||
-d IconPath="${{ github.workspace }}\resources\windows\navop.ico" `
|
||
-out "${env:PUBLIC_BASENAME}.exe"
|
||
|
||
- name: Validate Windows MSI metadata
|
||
if: runner.os == 'Windows'
|
||
shell: pwsh
|
||
run: |
|
||
./script/validate-windows-msi.ps1 `
|
||
-Path "${env:PUBLIC_BASENAME}.msi" `
|
||
-ExpectedLanguage 1033
|
||
|
||
- name: Upload artifact
|
||
if: matrix.variant == 'standard'
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: ${{ matrix.archive }}
|
||
path: ${{ matrix.archive }}
|
||
|
||
- name: Upload versioned public package artifacts
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: navop-${{ matrix.public_label }}-packages
|
||
path: |
|
||
navop-*-${{ matrix.public_label }}.tar.gz
|
||
navop-*-${{ matrix.public_label }}.dmg
|
||
navop-*-${{ matrix.public_label }}.zip
|
||
navop-*-${{ matrix.public_label }}-portable.zip
|
||
navop-*-${{ matrix.public_label }}.msi
|
||
navop-*-${{ matrix.public_label }}.exe
|
||
if-no-files-found: error
|
||
|
||
- name: Upload Linux installer artifacts
|
||
if: matrix.target == 'x86_64-unknown-linux-gnu' && !matrix.portable_linux
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: navop-linux-x86_64-installers
|
||
path: |
|
||
navop_*.deb
|
||
navop-*.rpm
|
||
navop_*.AppImage
|
||
|
||
release:
|
||
name: Publish Release
|
||
needs:
|
||
- prepare
|
||
- build
|
||
runs-on: ubuntu-latest
|
||
|
||
steps:
|
||
- name: Check out release tag
|
||
uses: actions/checkout@v7
|
||
with:
|
||
ref: ${{ needs.prepare.outputs.tag }}
|
||
|
||
- name: Extract release notes from changelog
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
|
||
run: |
|
||
set -euo pipefail
|
||
if [ -f CHANGELOG.md ] && [ -f script/changelog.py ]; then
|
||
python3 script/changelog.py extract \
|
||
--tag "$RELEASE_TAG" \
|
||
--changelog CHANGELOG.md \
|
||
--output release-notes.md
|
||
elif gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||
echo "::warning::${RELEASE_TAG} predates the tracked changelog; using its existing GitHub Release body for this legacy repair."
|
||
gh release view "$RELEASE_TAG" \
|
||
--repo "$GITHUB_REPOSITORY" \
|
||
--json body \
|
||
--jq .body > release-notes.md
|
||
else
|
||
echo "::error::Cannot publish ${RELEASE_TAG}: the tag has no tracked changelog entry and no legacy GitHub Release body."
|
||
exit 1
|
||
fi
|
||
if [ ! -s release-notes.md ]; then
|
||
echo "::error::Release notes for ${RELEASE_TAG} are empty"
|
||
exit 1
|
||
fi
|
||
|
||
- name: Download all artifacts
|
||
uses: actions/download-artifact@v4
|
||
with:
|
||
path: artifacts
|
||
merge-multiple: true
|
||
|
||
- name: List artifacts
|
||
run: ls -la artifacts/
|
||
|
||
- name: Merge existing assets and generate checksums
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
|
||
run: |
|
||
set -euo pipefail
|
||
mkdir -p publish
|
||
if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||
if ! gh release download "$RELEASE_TAG" \
|
||
--repo "$GITHUB_REPOSITORY" \
|
||
--pattern "navop-*" \
|
||
--pattern "navop_*" \
|
||
--dir publish; then
|
||
echo "No existing release assets found; publishing only newly built artifacts."
|
||
fi
|
||
fi
|
||
cp -a artifacts/. publish/
|
||
cd publish
|
||
shopt -s nullglob
|
||
release_files=(navop-* navop_*)
|
||
if [ "${#release_files[@]}" -eq 0 ]; then
|
||
echo "::error::No release artifacts found for checksum generation"
|
||
exit 1
|
||
fi
|
||
sha256sum "${release_files[@]}" > sha256sums.txt
|
||
cat sha256sums.txt
|
||
|
||
- name: Write release metadata
|
||
env:
|
||
RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
|
||
RELEASE_PLATFORM: ${{ needs.prepare.outputs.platform }}
|
||
run: |
|
||
node <<'NODE'
|
||
const fs = require("fs");
|
||
const releaseTag = process.env.RELEASE_TAG;
|
||
const version = releaseTag.replace(/^v/, "");
|
||
const platform = process.env.RELEASE_PLATFORM;
|
||
fs.mkdirSync("release-metadata", { recursive: true });
|
||
fs.writeFileSync(
|
||
"release-metadata/release-metadata.json",
|
||
`${JSON.stringify({ release_tag: releaseTag, version, platform }, null, 2)}\n`,
|
||
);
|
||
NODE
|
||
|
||
- name: Upload release metadata
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: release-metadata
|
||
path: release-metadata/release-metadata.json
|
||
|
||
- name: Create or update GitHub Release
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
|
||
run: |
|
||
set -euo pipefail
|
||
if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||
gh release edit "$RELEASE_TAG" \
|
||
--repo "$GITHUB_REPOSITORY" \
|
||
--title "Navop $RELEASE_TAG" \
|
||
--notes-file release-notes.md
|
||
else
|
||
gh release create "$RELEASE_TAG" \
|
||
--repo "$GITHUB_REPOSITORY" \
|
||
--verify-tag \
|
||
--title "Navop $RELEASE_TAG" \
|
||
--notes-file release-notes.md
|
||
fi
|
||
|
||
shopt -s nullglob
|
||
new_files=(artifacts/navop-* artifacts/navop_*)
|
||
if [ "${#new_files[@]}" -eq 0 ]; then
|
||
echo "::error::No newly built release assets found"
|
||
exit 1
|
||
fi
|
||
gh release upload "$RELEASE_TAG" \
|
||
--repo "$GITHUB_REPOSITORY" \
|
||
--clobber \
|
||
"${new_files[@]}" \
|
||
publish/sha256sums.txt
|
||
|
||
# Dispatch R2 explicitly after the Release assets are present.
|
||
# Relying only on workflow_run is not reliable when this workflow
|
||
# itself is dispatched by release-trigger.yml.
|
||
gh workflow run upload-r2.yml \
|
||
--repo "$GITHUB_REPOSITORY" \
|
||
--ref main \
|
||
-f tag="$RELEASE_TAG"
|
||
|
||
gh workflow run sync-cnb-release-assets.yml \
|
||
--repo "$GITHUB_REPOSITORY" \
|
||
--ref main \
|
||
-f tag="$RELEASE_TAG"
|