Files
ChuSQL/scripts/package.sh
T

257 lines
11 KiB
Bash

#!/bin/sh
# sh scripts/package.sh # 本机平台,web + cli 合成一个包
# sh scripts/package.sh --no-build # 用已有编译产物(快)
# sh scripts/package.sh --no-verify # 跳过「装一遍试试」
# sh scripts/package.sh --out-dir /tmp/rel # 换输出目录
# sh scripts/package.sh --platform linux-x86_64 # 覆盖平台标签(默认按 uname 猜)
# sh scripts/package.sh --version v0.1.0 # 只用于发布提示,不写进文件名
#
# 打完发出去(资源名必须跟这里一致,install.sh 才认得):
# gh release create v0.1.0 releases/* --generate-notes
#
# 收尾会按「只要 cli」「只要 web」各装一遍:核对落盘文件,也核对没选的组件没被释放。
# Windows 包用 scripts/package.ps1 打(这个脚本只出 tar.gz)。
set -eu
# 本机打发行包:构建、装配、打 tar.gz、出 sha256,产物落 releases/,命名与 CI 一致。
repo_root=$(cd "$(dirname "$0")/.." && pwd)
out_dir=$repo_root/releases
platform=''
version=''
build=yes
verify=yes
# 打印一行输出。
Say() { printf '%s\n' "$*"; }
# 打印步骤标题。
Step() { printf '\n== %s\n' "$*"; }
# 报错到 stderr 并退出 1。
Fail() {
printf 'error: %s\n' "$*" >&2
exit 1
}
while [ "$#" -gt 0 ]; do
case "$1" in
--platform)
shift; [ "$#" -gt 0 ] || Fail '--platform needs a label like linux-x86_64'
platform=$1; shift ;;
--version)
shift; [ "$#" -gt 0 ] || Fail '--version needs a tag like v0.1.0'
version=$1; shift ;;
--out-dir)
shift; [ "$#" -gt 0 ] || Fail '--out-dir needs a path'
out_dir=$1; shift ;;
--no-build) build=no; shift ;;
--no-verify) verify=no; shift ;;
-h|--help) usage=yes; shift ;;
*) Fail "unknown option: $1 (try --no-build, --out-dir DIR, --platform LABEL)" ;;
esac
done
if [ "${usage:-no}" = 'yes' ]; then
sed -n '2,/^set -eu$/p' "$0" | sed -e 's/^# \{0,1\}//' -e '$d'
exit 0
fi
if [ -z "$platform" ]; then
case "$(uname -s)" in
Linux) os=linux ;;
Darwin) os=macos ;;
*) Fail "cannot tell the platform from $(uname -s); pass --platform linux-x86_64" ;;
esac
case "$(uname -m)" in
x86_64|amd64) arch=x86_64 ;;
arm64|aarch64) arch=arm64 ;;
*) Fail "cannot tell the architecture from $(uname -m); pass --platform $os-x86_64" ;;
esac
platform=$os-$arch
fi
case "$platform" in
windows-*) Fail 'the Windows package is scripts/package.ps1; this script only makes tar.gz archives' ;;
esac
# 用可用的工具算文件 sha256。
sha256_of() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$1" | awk '{print $1}'
else
openssl dgst -sha256 "$1" | awk '{print $NF}'
fi
}
# 核对归档里必需的文件都在。
check_stage() {
stage=$1
ls "$stage/bin/"libchusql_core_storage.* >/dev/null 2>&1 || Fail 'bin/libchusql_core_storage.* is missing from the package'
[ -f "$stage/bin/csql" ] || Fail 'bin/csql is missing from the package'
[ -f "$stage/bin/chusql-web" ] || Fail 'bin/chusql-web is missing from the package'
[ -f "$stage/bin/chusql-server" ] || Fail 'bin/chusql-server is missing from the package'
[ -f "$stage/bin/csql-bootstrap" ] || Fail 'bin/csql-bootstrap is missing from the package'
[ -f "$stage/static/index.html" ] || Fail 'static/index.html is missing from the package'
[ -f "$stage/csql-web.sh" ] || Fail 'csql-web.sh is missing from the package'
[ -f "$stage/resources/settings.toml.linux" ] || Fail 'resources/settings.toml.linux is missing from the package'
[ -f "$stage/install.sh" ] || Fail 'install.sh is missing from the package'
[ -f "$stage/install.ps1" ] || Fail 'install.ps1 is missing from the package'
[ -f "$stage/uninstall.sh" ] || Fail 'uninstall.sh is missing from the package'
[ -f "$stage/uninstall.ps1" ] || Fail 'uninstall.ps1 is missing from the package'
# bin 里带 chusql 名字的文件只能是已知产物;改名前的残留或别的垃圾都算装配错误
for f in "$stage"/bin/chusql* "$stage"/bin/libchusql*; do
[ -e "$f" ] || continue
case "$(basename "$f")" in
libchusql_core_storage.so|libchusql_core_storage.dylib|libchusql_core_storage.dll|chusql-server|chusql-web|csql-bootstrap) ;;
*) Fail "unexpected artifact in bin/: $(basename "$f")" ;;
esac
done
}
# 按 cli / web 各装一遍,核对落地文件。
verify_package() {
name=$1
archive="$out_dir/$name.tar.gz"
for comp in cli web; do
vtmp=$(mktemp -d)
mkdir -p "$vtmp/pkg" "$vtmp/home" "$vtmp/opt"
if ! tar -xzf "$archive" -C "$vtmp/pkg"; then
rm -rf "$vtmp"
Fail "$name: the archive does not unpack"
fi
if ! ( cd "$vtmp/pkg" && HOME="$vtmp/home" CHUSQL_ADMIN_PASSWORD='verify-package' \
sh install.sh --component "$comp" \
--install-dir "$vtmp/opt" --data-dir "$vtmp/data" --user root \
--no-start ) \
>"$vtmp/log" 2>&1; then
sed -n '1,120p' "$vtmp/log" >&2
rm -rf "$vtmp"
Fail "$name: installing the $comp part from the package failed (log above)"
fi
ok=yes
ls "$vtmp/opt/bin/"libchusql_core_storage.* >/dev/null 2>&1 || ok=no
[ -f "$vtmp/opt/bin/chusql-server" ] || ok=no
[ -f "$vtmp/opt/bin/csql-bootstrap" ] || ok=no
[ -f "$vtmp/data/system/catalog.json" ] || ok=no
[ -f "$vtmp/data/system/__system_identities.db" ] || ok=no
[ -f "$vtmp/data/system/__system_users.db" ] || ok=no
[ -f "$vtmp/data/system/__system_types.db" ] || ok=no
[ -f "$vtmp/home/.config/ChuSQL/settings.toml" ] || ok=no
grep -q "data_dir = \"$vtmp/data\"" "$vtmp/home/.config/ChuSQL/settings.toml" || ok=no
if [ "$comp" = web ]; then
[ -f "$vtmp/opt/bin/chusql-web" ] || ok=no
[ -f "$vtmp/opt/static/index.html" ] || ok=no
[ -f "$vtmp/opt/csql-web.sh" ] || ok=no
[ -f "$vtmp/opt/csql-web" ] || ok=no
# 没要 cli,就不许把 csql 释放出去
if [ -f "$vtmp/opt/bin/csql" ] || [ -f "$vtmp/opt/csql" ]; then ok=no; fi
else
[ -f "$vtmp/opt/bin/csql" ] || ok=no
[ -f "$vtmp/opt/csql" ] || ok=no
# 没要 web,就不许把 chusql-web / static / 启动器释放出去
if [ -f "$vtmp/opt/bin/chusql-web" ] || [ -e "$vtmp/opt/static" ] || [ -f "$vtmp/opt/csql-web" ]; then
ok=no
fi
fi
if [ "$ok" != yes ]; then
sed -n '1,120p' "$vtmp/log" >&2
rm -rf "$vtmp"
Fail "$name: the $comp part is not what the installer promises (log above)"
fi
rm -rf "$vtmp"
Say " ok $name installs the $comp part, and only the $comp part"
done
}
# ---------- 构建 ----------
if [ "$build" = yes ]; then
Step 'Building storage (Rust)'
( cd "$repo_root/chusql-core/storage" && cargo build --release )
Step 'Building the web front end (Haskell)'
( cd "$repo_root/chusql-cli" && stack build --fast chusql-web:exe:chusql-web )
Step 'Building the command line client (Haskell)'
( cd "$repo_root/chusql-cli" && stack build --fast chusql-cli:exe:csql )
Step 'Building the TCP database server (Haskell)'
( cd "$repo_root/chusql-cli" && stack build --fast chusql-server:exe:chusql-server )
Step 'Building the bootstrap program (Haskell)'
( cd "$repo_root/chusql-bootstrap" && stack build --fast chusql-bootstrap:exe:csql-bootstrap )
fi
# ---------- 装配 + 打归档 ----------
name="chusql-$platform"
stage="$out_dir/.stage/$name"
archive="$out_dir/$name.tar.gz"
mkdir -p "$out_dir"
trap 'rm -rf "$out_dir/.stage"' EXIT INT TERM
Step "Packing $name"
rm -rf "$out_dir/.stage"
mkdir -p "$stage/bin" "$stage/resources"
cp "$repo_root/chusql-core/storage/target/release/"libchusql_core_storage.* "$stage/bin/" \
|| Fail "chusql-core/storage/target/release/libchusql_core_storage.* is not built (run without --no-build)"
stack_root=$(cd "$repo_root/chusql-cli" && stack path --local-install-root | tail -n 1 | tr -d '\r')
for front_name in chusql-web chusql-server csql; do
front="$stack_root/bin/$front_name"
if [ ! -f "$front" ]; then
front=$(find "$repo_root/chusql-web/.stack-work" "$repo_root/chusql-cli/.stack-work" "$repo_root/chusql-server/.stack-work" \
-name "$front_name" -type f 2>/dev/null | head -n 1 || true)
fi
[ -n "$front" ] && [ -f "$front" ] || Fail "$front_name is not built (run without --no-build)"
cp "$front" "$stage/bin/"
done
# 引导程序住在自己的工程里,安装根也单独问它要
bootstrap_root=$(cd "$repo_root/chusql-bootstrap" && stack path --local-install-root | tail -n 1 | tr -d '\r')
bootstrap_bin="$bootstrap_root/bin/csql-bootstrap"
if [ ! -f "$bootstrap_bin" ]; then
bootstrap_bin=$(find "$repo_root/chusql-bootstrap/.stack-work" -name csql-bootstrap -type f 2>/dev/null | head -n 1 || true)
fi
[ -n "$bootstrap_bin" ] && [ -f "$bootstrap_bin" ] || Fail 'csql-bootstrap is not built (run without --no-build)'
cp "$bootstrap_bin" "$stage/bin/"
# Haskell 一般静态链接,靶目录里真有共享库就一起带上;改名前的旧库(libchusql_storage.*)不能进包
for lib in "$repo_root"/chusql-core/storage/target/release/*.so "$repo_root"/chusql-core/storage/target/release/*.dylib; do
[ -f "$lib" ] || continue
case "$(basename "$lib")" in
libchusql_storage.*|libchusql_storage-*) continue ;;
esac
cp "$lib" "$stage/bin/"
done
cp "$repo_root/resources/settings.toml.linux" "$stage/resources/"
cp "$repo_root/scripts/install.sh" "$repo_root/scripts/install.ps1" "$repo_root/scripts/uninstall.sh" "$repo_root/scripts/uninstall.ps1" "$stage/"
mkdir -p "$stage/static"
cp -R "$repo_root"/chusql-web/static/. "$stage/static/"
cp "$repo_root/scripts/csql-web.sh" "$stage/"
if [ -f "$repo_root/scripts/csql-web.ps1" ]; then cp "$repo_root/scripts/csql-web.ps1" "$stage/"; fi
chmod +x "$stage/install.sh" "$stage/uninstall.sh" "$stage/csql-web.sh"
check_stage "$stage"
rm -f "$archive" "$archive.sha256"
( cd "$stage" && tar -czf "$archive" . )
rm -rf "$out_dir/.stage"
hash=$(sha256_of "$archive")
printf '%s %s\n' "$hash" "$name.tar.gz" > "$archive.sha256"
Say " wrote $archive"
Say " checksum $hash"
if [ "$verify" = yes ]; then
verify_package "$name"
fi
# ---------- 收尾 ----------
Step 'Done'
ls -l "$out_dir/$name.tar.gz" "$out_dir/$name.tar.gz.sha256"
if [ -n "$version" ]; then
Say ''
Say "release $version with (tag 必须是真实存在的 git tag):"
Say " gh release create $version \"$out_dir\"/* --generate-notes"
else
Say ''
Say 'publish with (先用 --version 指定 tag):'
Say " gh release create <tag> \"$out_dir\"/* --generate-notes"
fi