feat(0.2.1): 合并 Windows 外壳 + 离线编辑, 版本 0.2.1, 正式域名 https://iboard.honor3.com
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env node
|
||||
// Windows 外壳一键构建 (开发用): 安装包 (NSIS) + 便携版 (portable zip)
|
||||
//
|
||||
// 用法:
|
||||
// npm run build:win # 完整流程
|
||||
// node scripts/build-windows.mjs --skip-check # 跳过版本一致性校验
|
||||
// node scripts/build-windows.mjs --debug # debug 构建 (快速验证, 产物带 -debug 后缀)
|
||||
//
|
||||
// 流程:
|
||||
// 1) 版本一致性校验 (scripts/ci/check-version.mjs, 外壳三件套)
|
||||
// 2) 前端构建 (build:shell, 注入 VITE_API_BASE 指向远端后端)
|
||||
// 3) tauri build --bundles nsis → 编译一次 release exe + NSIS 安装包
|
||||
// 4) 复用同一个 exe 组装便携版目录并压成 zip (不二次编译)
|
||||
//
|
||||
// 产物 (artifacts/windows/):
|
||||
// iBoard_<ver>_x64-setup.exe 安装包 (当前用户安装, 免管理员)
|
||||
// iBoard_<ver>_x64-portable.zip 便携版 (解压即用)
|
||||
// portable/ 便携版解压目录 (iBoard.exe + README.txt)
|
||||
//
|
||||
// 说明: 便携版不内置 WebView2 —— 目标机需 Win10 19041+ / Win11 (系统自带 WebView2 Runtime)。
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const args = process.argv.slice(2)
|
||||
const skipCheck = args.includes('--skip-check')
|
||||
const isDebug = args.includes('--debug')
|
||||
const profile = isDebug ? 'debug' : 'release'
|
||||
|
||||
const conf = JSON.parse(fs.readFileSync(path.join(root, 'src-tauri', 'tauri.conf.json'), 'utf8'))
|
||||
const version = String(conf.version || '0.0.0')
|
||||
const product = conf.productName || 'iBoard'
|
||||
const suffix = isDebug ? '-debug' : ''
|
||||
|
||||
const artifactsDir = path.join(root, 'artifacts', 'windows')
|
||||
const targetDir = path.join(root, 'src-tauri', 'target', profile)
|
||||
|
||||
function log(msg) {
|
||||
console.log(`\x1b[36m[build-win]\x1b[0m ${msg}`)
|
||||
}
|
||||
function fail(msg) {
|
||||
console.error(`\x1b[31m[build-win] 失败: ${msg}\x1b[0m`)
|
||||
process.exit(1)
|
||||
}
|
||||
function run(cmd, cmdArgs, opts = {}) {
|
||||
log(`$ ${cmd} ${cmdArgs.join(' ')}`)
|
||||
const res = spawnSync(cmd, cmdArgs, {
|
||||
cwd: root,
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
...opts,
|
||||
})
|
||||
if (res.status !== 0) fail(`${cmd} ${cmdArgs.join(' ')} 退出码 ${res.status}`)
|
||||
}
|
||||
|
||||
// ---------- 0. 前置检查 ----------
|
||||
if (process.platform !== 'win32') fail('本脚本仅支持 Windows')
|
||||
if (!fs.existsSync(path.join(root, 'node_modules'))) fail('缺少 node_modules, 请先运行 npm ci')
|
||||
|
||||
log(`项目 ${product} 外壳版本 ${version} (${profile})`)
|
||||
|
||||
// ---------- 1. 版本一致性 ----------
|
||||
if (!skipCheck) {
|
||||
log('校验版本一致性 (外壳 tauri.conf.json / Cargo.toml / Cargo.lock)')
|
||||
run('node', ['scripts/ci/check-version.mjs'], {
|
||||
env: { ...process.env, BRANCH: process.env.BRANCH || '' },
|
||||
})
|
||||
} else {
|
||||
log('跳过版本一致性校验 (--skip-check)')
|
||||
}
|
||||
|
||||
// ---------- 2. 前端构建 ----------
|
||||
log('构建前端 (build:shell, VITE_API_BASE 指向远端后端)')
|
||||
run('npm', ['run', 'build:shell'])
|
||||
|
||||
// ---------- 3. 安装包 (NSIS) ----------
|
||||
const tauriArgs = ['tauri', 'build']
|
||||
if (isDebug) tauriArgs.push('--debug')
|
||||
// 只出 NSIS: 避免 bundle.targets="all" 在 Windows 上拉 WiX 打 MSI
|
||||
tauriArgs.push('--bundles', 'nsis')
|
||||
log('编译外壳并生成 NSIS 安装包')
|
||||
run('npx', tauriArgs)
|
||||
|
||||
// ---------- 4. 便携版 ----------
|
||||
const exePath = path.join(targetDir, `iboard${suffix}.exe`)
|
||||
if (!fs.existsSync(exePath)) fail(`未找到构建产物 ${exePath}`)
|
||||
|
||||
fs.rmSync(artifactsDir, { recursive: true, force: true })
|
||||
fs.mkdirSync(artifactsDir, { recursive: true })
|
||||
|
||||
const portableDir = path.join(artifactsDir, 'portable')
|
||||
fs.mkdirSync(portableDir, { recursive: true })
|
||||
const portableExe = path.join(portableDir, `${product}.exe`)
|
||||
fs.copyFileSync(exePath, portableExe)
|
||||
|
||||
const readme = `${product} ${version} 便携版 (Windows x64)
|
||||
=================================================
|
||||
|
||||
免安装: 双击 ${product}.exe 即可运行。
|
||||
|
||||
系统要求
|
||||
- Windows 10 19041+ 或 Windows 11 (x64)
|
||||
- WebView2 Runtime: Win10/11 系统自带; 若提示缺失, 请安装
|
||||
https://developer.microsoft.com/microsoft-edge/webview2/
|
||||
|
||||
使用说明
|
||||
- 关闭窗口 = 隐藏到托盘, 不会退出; 退出请用托盘右键菜单「退出」。
|
||||
- 窗口贴屏幕边缘会自动收起为半圆悬浮球, 鼠标停留或单击唤回。
|
||||
- 托盘菜单可开关「开机启动」「边缘吸附」。
|
||||
- 配置保存在 %APPDATA%\\com.iboard.app\\shell-config.json (删除即恢复默认)。
|
||||
|
||||
注意: 便携版不写注册表、不装服务; 但若开启了「开机启动」, 会在
|
||||
HKCU\\...\\Run 写入自启动项, 关闭开关即可移除。
|
||||
`
|
||||
fs.writeFileSync(path.join(portableDir, 'README.txt'), readme, 'utf8')
|
||||
|
||||
const zipName = `${product}_${version}_x64-portable${suffix}.zip`
|
||||
const zipPath = path.join(artifactsDir, zipName)
|
||||
log('打包便携版 zip')
|
||||
const psZip = `Compress-Archive -Path '${portableDir}\\*' -DestinationPath '${zipPath}' -Force`
|
||||
const res = spawnSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', psZip], {
|
||||
cwd: root,
|
||||
stdio: 'inherit',
|
||||
})
|
||||
if (res.status !== 0) fail('压缩便携版失败 (Compress-Archive)')
|
||||
|
||||
// ---------- 5. 汇总 ----------
|
||||
const setupExe = path.join(
|
||||
targetDir,
|
||||
'bundle',
|
||||
'nsis',
|
||||
`${product}_${version}_x64-setup${suffix}.exe`,
|
||||
)
|
||||
const outputs = []
|
||||
if (fs.existsSync(setupExe)) {
|
||||
const dest = path.join(artifactsDir, path.basename(setupExe))
|
||||
fs.copyFileSync(setupExe, dest)
|
||||
outputs.push(['安装包 (NSIS)', dest])
|
||||
} else {
|
||||
outputs.push(['安装包 (NSIS)', `未找到: ${setupExe}`])
|
||||
}
|
||||
outputs.push(['便携版 (zip)', zipPath])
|
||||
outputs.push(['便携版 (目录)', `${portableDir} (${fs.readdirSync(portableDir).length} 个文件)`])
|
||||
|
||||
log('构建完成:')
|
||||
for (const [label, p] of outputs) {
|
||||
const size = fs.existsSync(p) ? `${(fs.statSync(p).size / 1024 / 1024).toFixed(1)} MB` : ''
|
||||
console.log(` ${label.padEnd(14)} ${p}${size ? ' (' + size + ')' : ''}`)
|
||||
}
|
||||
process.exit(0)
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env node
|
||||
// 生成托盘深浅色单色图标 (W-THE-01):
|
||||
// src-tauri/icons/tray-light.png 深色字形 —— 浅色任务栏用
|
||||
// src-tauri/icons/tray-dark.png 白色字形 —— 深色任务栏用
|
||||
//
|
||||
// 做法: 不能只用 alpha 通道做剪影 —— 源图标是「深色圆角方块 + 白色 i」,
|
||||
// 整个方块都是不透明的, 只取 alpha 会得到一块纯色方块, i 字母细节全丢。
|
||||
// 这里按**亮度**映射, 保留字母结构:
|
||||
// 深色字形: alpha = (255-L)/255 → 方块保留深色, 白色 i 镂空透出任务栏
|
||||
// 浅色字形: alpha = max(L,40)/255 → i 白色高亮, 方块留一点淡淡衬底
|
||||
// 产物已提交进仓库, 仅图标变更时需要重跑: node scripts/gen-tray-icons.mjs
|
||||
import sharp from 'sharp'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'node:path'
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const src = path.join(root, 'src-tauri', 'icons', '128x128.png')
|
||||
const outDir = path.join(root, 'src-tauri', 'icons')
|
||||
|
||||
const targets = [
|
||||
{ file: 'tray-light.png', rgb: [31, 31, 31], mode: 'dark-glyph' }, // 深色字形 (浅色任务栏)
|
||||
{ file: 'tray-dark.png', rgb: [250, 250, 250], mode: 'light-glyph' }, // 白色字形 (深色任务栏)
|
||||
]
|
||||
|
||||
for (const t of targets) {
|
||||
const { data, info } = await sharp(src)
|
||||
.resize(32, 32, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
|
||||
.ensureAlpha()
|
||||
.raw()
|
||||
.toBuffer({ resolveWithObject: true })
|
||||
const out = Buffer.alloc(info.width * info.height * 4)
|
||||
for (let i = 0; i < info.width * info.height; i++) {
|
||||
const r = data[i * 4]
|
||||
const g = data[i * 4 + 1]
|
||||
const b = data[i * 4 + 2]
|
||||
const a = data[i * 4 + 3]
|
||||
const lumi = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||
const alpha =
|
||||
t.mode === 'dark-glyph' ? (a * (255 - lumi)) / 255 : (a * Math.max(lumi, 40)) / 255
|
||||
out[i * 4] = t.rgb[0]
|
||||
out[i * 4 + 1] = t.rgb[1]
|
||||
out[i * 4 + 2] = t.rgb[2]
|
||||
out[i * 4 + 3] = Math.round(alpha)
|
||||
}
|
||||
await sharp(out, { raw: { width: info.width, height: info.height, channels: 4 } })
|
||||
.png()
|
||||
.toFile(path.join(outDir, t.file))
|
||||
console.log('generated', t.file)
|
||||
}
|
||||
+143
-143
@@ -1,143 +1,143 @@
|
||||
#!/usr/bin/env bash
|
||||
# iBoard Android APK 打包
|
||||
# 用法: bash scripts/wsl/build-apk.sh [debug|release]
|
||||
# release 模式自动构建 4 个 ABI 并出 universal APK
|
||||
set -euo pipefail
|
||||
|
||||
WIN_PROJECT='/mnt/d/workbench/iboard'
|
||||
cd "$WIN_PROJECT"
|
||||
|
||||
VARIANT="${1:-debug}"
|
||||
echo "构建变体: $VARIANT"
|
||||
|
||||
# 环境变量
|
||||
export ANDROID_HOME="${ANDROID_HOME:-$HOME/android-sdk}"
|
||||
export ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk/27.2.12479018}"
|
||||
# 关键: 把 NDK toolchain bin 加进 PATH, .cargo/config.toml 里
|
||||
# linker = "aarch64-linux-android24-clang" 这种短名才能被解析到
|
||||
NDK_TOOLCHAIN_BIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
||||
if [ ! -d "$NDK_TOOLCHAIN_BIN" ]; then
|
||||
echo "ERROR: NDK toolchain bin 不存在: $NDK_TOOLCHAIN_BIN"
|
||||
echo "请确认 ANDROID_NDK_HOME 正确指向 NDK 27.2.x 目录"
|
||||
exit 1
|
||||
fi
|
||||
export PATH="$NDK_TOOLCHAIN_BIN:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$HOME/.cargo/bin:$PATH"
|
||||
|
||||
# 0. 首次需要 android 工程
|
||||
cd src-tauri
|
||||
if [ ! -d "gen/android" ]; then
|
||||
echo "=== 0. 初始化 Android 工程 (一次性) ==="
|
||||
npx tauri android init
|
||||
fi
|
||||
cd "$WIN_PROJECT"
|
||||
|
||||
# Android 后端地址, 写到前端 bundle 里 (覆盖默认的 /api 相对路径)
|
||||
export VITE_API_BASE="${VITE_API_BASE:-http://bh.vps.honor3.com:3001}"
|
||||
echo "VITE_API_BASE=$VITE_API_BASE"
|
||||
|
||||
# 1. 先构建前端, 让 Tauri android 工程能拿到 dist/
|
||||
echo "=== 1. 构建前端 ==="
|
||||
npm run build
|
||||
|
||||
# 1.5 拷贝 dist/ 到 Android 工程 assets/, 绕过 Tauri CLI 时这步要做
|
||||
# Tauri CLI 默认在 android build 前会干这事, 我们走直 gradle 要自己补上
|
||||
ANDROID_ASSETS="$WIN_PROJECT/src-tauri/gen/android/app/src/main/assets"
|
||||
mkdir -p "$ANDROID_ASSETS"
|
||||
# 清空旧的 (避免上次的残余)
|
||||
find "$ANDROID_ASSETS" -mindepth 1 -maxdepth 1 ! -name dexopt ! -name tauri.conf.json -exec rm -rf {} +
|
||||
cp -r dist/. "$ANDROID_ASSETS/"
|
||||
echo "前端 bundle 已拷贝到 $ANDROID_ASSETS"
|
||||
ls -la "$ANDROID_ASSETS" | head -10
|
||||
|
||||
# 2. 走不同路径
|
||||
cd src-tauri
|
||||
|
||||
# 全部 4 个 Android 架构的 rustBuild 任务 (Tauri 2 默认会编 4 个)
|
||||
RUST_BUILD_TASKS=(
|
||||
":app:rustBuildArm64Release"
|
||||
":app:rustBuildArmRelease"
|
||||
":app:rustBuildX86_64Release"
|
||||
":app:rustBuildX86Release"
|
||||
)
|
||||
SKIP_ARGS=()
|
||||
for t in "${RUST_BUILD_TASKS[@]}"; do SKIP_ARGS+=(-x "${t#:}"); done
|
||||
|
||||
# 4 个 Android 目标的 cargo target triple
|
||||
ANDROID_TARGETS=(
|
||||
"aarch64-linux-android"
|
||||
"armv7-linux-androideabi"
|
||||
"x86_64-linux-android"
|
||||
"i686-linux-android"
|
||||
)
|
||||
|
||||
if [ "$VARIANT" = "release" ]; then
|
||||
echo "[release] 手工 cargo 编 lib (4 个 ABI), gradle 只跑 packaging (跳过 rustBuild* 避免 --release 重复 bug)"
|
||||
|
||||
for target in "${ANDROID_TARGETS[@]}"; do
|
||||
echo " [cargo] build lib release for $target ..."
|
||||
cargo build \
|
||||
--target "$target" \
|
||||
--features custom-protocol \
|
||||
--lib \
|
||||
--release
|
||||
done
|
||||
|
||||
echo " [gradle] assembleRelease -x rustBuild* ..."
|
||||
cd gen/android
|
||||
./gradlew --no-daemon assembleRelease "${SKIP_ARGS[@]}"
|
||||
|
||||
APK_DIR="$WIN_PROJECT/src-tauri/gen/android/app/build/outputs/apk/universal/release"
|
||||
# Gradle 配了 signingConfig 就输出 app-universal-release.apk,
|
||||
# 否则输出 app-universal-release-unsigned.apk, 按文件名判断是否已签名
|
||||
SIGNED_APK="$APK_DIR/app-universal-release.apk"
|
||||
UNSIGNED_APK="$APK_DIR/app-universal-release-unsigned.apk"
|
||||
|
||||
if [ -f "$SIGNED_APK" ]; then
|
||||
APK_PATH="$SIGNED_APK"
|
||||
APK_STATUS="已签名"
|
||||
elif [ -f "$UNSIGNED_APK" ]; then
|
||||
APK_PATH="$UNSIGNED_APK"
|
||||
APK_STATUS="未签名"
|
||||
else
|
||||
APK_PATH=""
|
||||
APK_STATUS="未找到"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== 产物 ($APK_STATUS) ==="
|
||||
if [ -n "$APK_PATH" ]; then
|
||||
ls -lh "$APK_PATH"
|
||||
else
|
||||
ls -lh "$APK_DIR"/*.apk 2>/dev/null || echo "(未找到 APK)"
|
||||
fi
|
||||
|
||||
if [ "$APK_STATUS" = "已签名" ]; then
|
||||
echo
|
||||
echo "=== 安装到设备 ==="
|
||||
echo " adb install -r $APK_PATH"
|
||||
elif [ "$APK_STATUS" = "未签名" ]; then
|
||||
echo
|
||||
echo "=== Release 签名 (按需) ==="
|
||||
echo "1. 生成 keystore (一次性):"
|
||||
echo " keytool -genkey -v -keystore ~/iboard-release.keystore -alias iboard -keyalg RSA -keysize 2048 -validity 10000"
|
||||
echo "2. 在 ~/.gradle/gradle.properties 写:"
|
||||
echo " IBOARD_UPLOAD_STORE_FILE=/home/$(whoami)/iboard-release.keystore"
|
||||
echo " IBOARD_UPLOAD_KEY_ALIAS=iboard"
|
||||
echo " IBOARD_UPLOAD_STORE_PASSWORD=..."
|
||||
echo " IBOARD_UPLOAD_KEY_PASSWORD=..."
|
||||
echo "3. 重跑本脚本 (Gradle 读 gradle.properties 里的 IBOARD_UPLOAD_* 自动用 releaseSigning)"
|
||||
echo
|
||||
echo "=== 临时用 debug keystore 手动签 (仅测试) ==="
|
||||
echo " \$ANDROID_HOME/build-tools/*/apksigner sign --ks ~/.android/debug.keystore \"
|
||||
echo " --ks-pass pass:android --key-pass pass:android \"
|
||||
echo " $APK_PATH"
|
||||
fi
|
||||
else
|
||||
echo "[debug] Tauri CLI debug 路径 (不传 --release 不会触发 bug), 直接走"
|
||||
npx tauri android build --apk
|
||||
|
||||
APK_DIR="$WIN_PROJECT/src-tauri/target/debug/bundle/android/app/build/outputs/apk/debug"
|
||||
echo
|
||||
echo "=== 产物 ==="
|
||||
ls -lh "$APK_DIR"/*.apk 2>/dev/null || echo "(未找到 APK)"
|
||||
fi
|
||||
#!/usr/bin/env bash
|
||||
# iBoard Android APK 打包
|
||||
# 用法: bash scripts/wsl/build-apk.sh [debug|release]
|
||||
# release 模式自动构建 4 个 ABI 并出 universal APK
|
||||
set -euo pipefail
|
||||
|
||||
WIN_PROJECT='/mnt/d/workbench/iboard'
|
||||
cd "$WIN_PROJECT"
|
||||
|
||||
VARIANT="${1:-debug}"
|
||||
echo "构建变体: $VARIANT"
|
||||
|
||||
# 环境变量
|
||||
export ANDROID_HOME="${ANDROID_HOME:-$HOME/android-sdk}"
|
||||
export ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk/27.2.12479018}"
|
||||
# 关键: 把 NDK toolchain bin 加进 PATH, .cargo/config.toml 里
|
||||
# linker = "aarch64-linux-android24-clang" 这种短名才能被解析到
|
||||
NDK_TOOLCHAIN_BIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
||||
if [ ! -d "$NDK_TOOLCHAIN_BIN" ]; then
|
||||
echo "ERROR: NDK toolchain bin 不存在: $NDK_TOOLCHAIN_BIN"
|
||||
echo "请确认 ANDROID_NDK_HOME 正确指向 NDK 27.2.x 目录"
|
||||
exit 1
|
||||
fi
|
||||
export PATH="$NDK_TOOLCHAIN_BIN:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$HOME/.cargo/bin:$PATH"
|
||||
|
||||
# 0. 首次需要 android 工程
|
||||
cd src-tauri
|
||||
if [ ! -d "gen/android" ]; then
|
||||
echo "=== 0. 初始化 Android 工程 (一次性) ==="
|
||||
npx tauri android init
|
||||
fi
|
||||
cd "$WIN_PROJECT"
|
||||
|
||||
# Android 后端地址, 写到前端 bundle 里 (覆盖默认的 /api 相对路径)
|
||||
export VITE_API_BASE="${VITE_API_BASE:-https://iboard.honor3.com}"
|
||||
echo "VITE_API_BASE=$VITE_API_BASE"
|
||||
|
||||
# 1. 先构建前端, 让 Tauri android 工程能拿到 dist/
|
||||
echo "=== 1. 构建前端 ==="
|
||||
npm run build
|
||||
|
||||
# 1.5 拷贝 dist/ 到 Android 工程 assets/, 绕过 Tauri CLI 时这步要做
|
||||
# Tauri CLI 默认在 android build 前会干这事, 我们走直 gradle 要自己补上
|
||||
ANDROID_ASSETS="$WIN_PROJECT/src-tauri/gen/android/app/src/main/assets"
|
||||
mkdir -p "$ANDROID_ASSETS"
|
||||
# 清空旧的 (避免上次的残余)
|
||||
find "$ANDROID_ASSETS" -mindepth 1 -maxdepth 1 ! -name dexopt ! -name tauri.conf.json -exec rm -rf {} +
|
||||
cp -r dist/. "$ANDROID_ASSETS/"
|
||||
echo "前端 bundle 已拷贝到 $ANDROID_ASSETS"
|
||||
ls -la "$ANDROID_ASSETS" | head -10
|
||||
|
||||
# 2. 走不同路径
|
||||
cd src-tauri
|
||||
|
||||
# 全部 4 个 Android 架构的 rustBuild 任务 (Tauri 2 默认会编 4 个)
|
||||
RUST_BUILD_TASKS=(
|
||||
":app:rustBuildArm64Release"
|
||||
":app:rustBuildArmRelease"
|
||||
":app:rustBuildX86_64Release"
|
||||
":app:rustBuildX86Release"
|
||||
)
|
||||
SKIP_ARGS=()
|
||||
for t in "${RUST_BUILD_TASKS[@]}"; do SKIP_ARGS+=(-x "${t#:}"); done
|
||||
|
||||
# 4 个 Android 目标的 cargo target triple
|
||||
ANDROID_TARGETS=(
|
||||
"aarch64-linux-android"
|
||||
"armv7-linux-androideabi"
|
||||
"x86_64-linux-android"
|
||||
"i686-linux-android"
|
||||
)
|
||||
|
||||
if [ "$VARIANT" = "release" ]; then
|
||||
echo "[release] 手工 cargo 编 lib (4 个 ABI), gradle 只跑 packaging (跳过 rustBuild* 避免 --release 重复 bug)"
|
||||
|
||||
for target in "${ANDROID_TARGETS[@]}"; do
|
||||
echo " [cargo] build lib release for $target ..."
|
||||
cargo build \
|
||||
--target "$target" \
|
||||
--features custom-protocol \
|
||||
--lib \
|
||||
--release
|
||||
done
|
||||
|
||||
echo " [gradle] assembleRelease -x rustBuild* ..."
|
||||
cd gen/android
|
||||
./gradlew --no-daemon assembleRelease "${SKIP_ARGS[@]}"
|
||||
|
||||
APK_DIR="$WIN_PROJECT/src-tauri/gen/android/app/build/outputs/apk/universal/release"
|
||||
# Gradle 配了 signingConfig 就输出 app-universal-release.apk,
|
||||
# 否则输出 app-universal-release-unsigned.apk, 按文件名判断是否已签名
|
||||
SIGNED_APK="$APK_DIR/app-universal-release.apk"
|
||||
UNSIGNED_APK="$APK_DIR/app-universal-release-unsigned.apk"
|
||||
|
||||
if [ -f "$SIGNED_APK" ]; then
|
||||
APK_PATH="$SIGNED_APK"
|
||||
APK_STATUS="已签名"
|
||||
elif [ -f "$UNSIGNED_APK" ]; then
|
||||
APK_PATH="$UNSIGNED_APK"
|
||||
APK_STATUS="未签名"
|
||||
else
|
||||
APK_PATH=""
|
||||
APK_STATUS="未找到"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== 产物 ($APK_STATUS) ==="
|
||||
if [ -n "$APK_PATH" ]; then
|
||||
ls -lh "$APK_PATH"
|
||||
else
|
||||
ls -lh "$APK_DIR"/*.apk 2>/dev/null || echo "(未找到 APK)"
|
||||
fi
|
||||
|
||||
if [ "$APK_STATUS" = "已签名" ]; then
|
||||
echo
|
||||
echo "=== 安装到设备 ==="
|
||||
echo " adb install -r $APK_PATH"
|
||||
elif [ "$APK_STATUS" = "未签名" ]; then
|
||||
echo
|
||||
echo "=== Release 签名 (按需) ==="
|
||||
echo "1. 生成 keystore (一次性):"
|
||||
echo " keytool -genkey -v -keystore ~/iboard-release.keystore -alias iboard -keyalg RSA -keysize 2048 -validity 10000"
|
||||
echo "2. 在 ~/.gradle/gradle.properties 写:"
|
||||
echo " IBOARD_UPLOAD_STORE_FILE=/home/$(whoami)/iboard-release.keystore"
|
||||
echo " IBOARD_UPLOAD_KEY_ALIAS=iboard"
|
||||
echo " IBOARD_UPLOAD_STORE_PASSWORD=..."
|
||||
echo " IBOARD_UPLOAD_KEY_PASSWORD=..."
|
||||
echo "3. 重跑本脚本 (Gradle 读 gradle.properties 里的 IBOARD_UPLOAD_* 自动用 releaseSigning)"
|
||||
echo
|
||||
echo "=== 临时用 debug keystore 手动签 (仅测试) ==="
|
||||
echo " \$ANDROID_HOME/build-tools/*/apksigner sign --ks ~/.android/debug.keystore \"
|
||||
echo " --ks-pass pass:android --key-pass pass:android \"
|
||||
echo " $APK_PATH"
|
||||
fi
|
||||
else
|
||||
echo "[debug] Tauri CLI debug 路径 (不传 --release 不会触发 bug), 直接走"
|
||||
npx tauri android build --apk
|
||||
|
||||
APK_DIR="$WIN_PROJECT/src-tauri/target/debug/bundle/android/app/build/outputs/apk/debug"
|
||||
echo
|
||||
echo "=== 产物 ==="
|
||||
ls -lh "$APK_DIR"/*.apk 2>/dev/null || echo "(未找到 APK)"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user