feat: add Kimi Code and web APIs
This commit is contained in:
Executable
+273
@@ -0,0 +1,273 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build and pack the Windows GUI (app/cst-agent-app.exe) from Linux.
|
||||
#
|
||||
# scripts/package_windows_app.sh (run it from the repo root)
|
||||
# nix-shell shell-win.nix --run scripts/package_windows_app.sh
|
||||
# --run works from anywhere; the script re-enters the shell itself when the
|
||||
# mingw toolchain is not on PATH.
|
||||
#
|
||||
# The toolchain is the same one the TUI cross build uses (shell-win.nix: mingw
|
||||
# UCRT, x86_64-w64-mingw32). Qt comes from MSYS2, prebuilt for exactly that
|
||||
# target, and the host (Linux) tools come from nixpkgs' native Qt6: both are
|
||||
# 6.11.2, which is what Qt's own version check wants.
|
||||
#
|
||||
# Why not nixpkgs' mingw Qt: it does not build in this nixpkgs. bash 5.3 fails
|
||||
# to cross-compile for mingw (`sigset_t` is not in the mingw headers), which
|
||||
# takes tcl down, which sqlite needs, which qtbase needs; before that, giflib's
|
||||
# mingw patch no longer applies to its own source. Three separate overlays got
|
||||
# as far as qtbase before that wall. MSYS2 has the same Qt prebuilt.
|
||||
#
|
||||
# MSYS2's Qt ships *Windows* tools (moc.exe, rcc.exe, qmlcachegen.exe, ...);
|
||||
# the build runs on Linux, so each one is replaced by a one-line shim that
|
||||
# calls the host tool. windeployqt cannot run here either, so the deployment
|
||||
# half of this script copies the DLLs, plugins and QML modules by hand: the
|
||||
# DLLs by walking the exe's imports, the rest from the layout Qt expects.
|
||||
#
|
||||
# Output: dist/windows-x86_64-app/ and
|
||||
# dist/cst-agent-app-VERSION-windows-x86_64.zip (+ .sha256)
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
MIRROR="${CST_MSYS_MIRROR:-https://mirror.msys2.org/mingw/ucrt64}"
|
||||
PKGS="$ROOT/third_party/msys-qt6" # MSYS2 packages, extracted
|
||||
PREFIX="$PKGS/ucrt64" # what CMAKE_PREFIX_PATH points at
|
||||
HOST="${CST_QT6_HOST:-$ROOT/third_party/prefix-qt6-host}"
|
||||
BUILD="$ROOT/build-win-app"
|
||||
OUT="$ROOT/dist/windows-x86_64-app"
|
||||
|
||||
if ! command -v x86_64-w64-mingw32-gcc >/dev/null && [[ -z "${IN_NIX_SHELL:-}" ]] &&
|
||||
command -v nix-shell >/dev/null; then
|
||||
exec nix-shell "$ROOT/shell-win.nix" --run "$(printf '%q ' "$0" "$@")"
|
||||
fi
|
||||
command -v x86_64-w64-mingw32-gcc >/dev/null || {
|
||||
echo "need the mingw UCRT toolchain (run through shell-win.nix)" >&2
|
||||
exit 1
|
||||
}
|
||||
OBJDUMP="$(command -v x86_64-w64-mingw32-objdump || echo objdump)"
|
||||
STRIP="$(command -v x86_64-w64-mingw32-strip || echo strip)"
|
||||
|
||||
# --- the MSYS2 packages ----------------------------------------------------
|
||||
# The Qt modules the app links, plus every package that provides a DLL those
|
||||
# modules load (found with x86_64-w64-mingw32-objdump -p, then against
|
||||
# ucrt64.files from the same mirror). Versions are taken from the mirror
|
||||
# listing, so a new Qt6 release only means a new run of this script.
|
||||
MSYS_PKGS=(
|
||||
mingw-w64-ucrt-x86_64-qt6-base
|
||||
mingw-w64-ucrt-x86_64-qt6-declarative
|
||||
mingw-w64-ucrt-x86_64-qt6-svg
|
||||
mingw-w64-ucrt-x86_64-libgcc
|
||||
mingw-w64-ucrt-x86_64-libstdc++
|
||||
mingw-w64-ucrt-x86_64-libwinpthread
|
||||
mingw-w64-ucrt-x86_64-libb2
|
||||
mingw-w64-ucrt-x86_64-double-conversion
|
||||
mingw-w64-ucrt-x86_64-icu
|
||||
mingw-w64-ucrt-x86_64-pcre2
|
||||
mingw-w64-ucrt-x86_64-zlib
|
||||
mingw-w64-ucrt-x86_64-zstd
|
||||
mingw-w64-ucrt-x86_64-freetype
|
||||
mingw-w64-ucrt-x86_64-harfbuzz
|
||||
mingw-w64-ucrt-x86_64-md4c
|
||||
mingw-w64-ucrt-x86_64-libpng
|
||||
mingw-w64-ucrt-x86_64-brotli
|
||||
mingw-w64-ucrt-x86_64-bzip2
|
||||
# harfbuzz reaches for these (and glib2 for libffi/gettext below): they are
|
||||
# not what the app links directly, but the shaper the font engine loads.
|
||||
mingw-w64-ucrt-x86_64-glib2
|
||||
mingw-w64-ucrt-x86_64-graphite2
|
||||
mingw-w64-ucrt-x86_64-libffi
|
||||
mingw-w64-ucrt-x86_64-gettext-runtime
|
||||
mingw-w64-ucrt-x86_64-libiconv
|
||||
)
|
||||
|
||||
listing="$PKGS/listing.html"
|
||||
mkdir -p "$PKGS"
|
||||
if [[ ! -s "$listing" || -n "${CST_MSYS_REFRESH:-}" ]]; then
|
||||
echo "==> mirror listing"
|
||||
curl -fsSL "$MIRROR/" -o "$listing"
|
||||
fi
|
||||
|
||||
# The newest file of a package (no -debug, no .sig): the mirror keeps every
|
||||
# revision, and the listing is one HTTP page, so this is the whole lookup. The
|
||||
# names come out of the links (the text of a link is elided with "..") and are
|
||||
# matched literally, because a package name may hold regex characters
|
||||
# ("libstdc++", which the listing writes as %2B and the link as a plus).
|
||||
allPkgs="$(grep -oE 'href="[^"]+\.pkg\.tar\.zst"' "$listing" |
|
||||
sed 's/^href="//; s/"$//; s/%2[bB]/+/g' | grep -v -- '-debug-' | sort -V -u)"
|
||||
newest() { # newest PKGNAME
|
||||
# ... and the name has to end there: "harfbuzz-" also prefixes
|
||||
# "harfbuzz-utils", "brotli-" prefixes "brotli-testdata", "zlib-" prefixes
|
||||
# "zlib-ng-compat". What follows the name is a version, which starts with a
|
||||
# digit.
|
||||
printf '%s\n' "$allPkgs" | awk -v pre="$1-" '
|
||||
index($0, pre) == 1 && substr($0, length(pre) + 1) ~ /^[0-9]/' |
|
||||
sort -V | tail -1
|
||||
}
|
||||
|
||||
for p in "${MSYS_PKGS[@]}"; do
|
||||
f="$(newest "$p")"
|
||||
[[ -n "$f" ]] || { echo "no package $p in the mirror listing" >&2; exit 1; }
|
||||
if [[ ! -f "$PKGS/$f" ]]; then
|
||||
echo "==> $f"
|
||||
curl -fsSL "$MIRROR/$f" -o "$PKGS/$f"
|
||||
fi
|
||||
# A package that is already extracted leaves its files in place: tar is
|
||||
# happy to overwrite, so this just refreshes whatever changed.
|
||||
tar --zstd -xf "$PKGS/$f" -C "$PKGS"
|
||||
done
|
||||
[[ -f "$PREFIX/lib/cmake/Qt6/Qt6Config.cmake" ]] || {
|
||||
echo "$PREFIX does not look like a Qt6 install" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- the host tools --------------------------------------------------------
|
||||
# nixpkgs' native Qt 6, in one prefix: moc, rcc, qmlcachegen, qmltyperegistrar,
|
||||
# qmlimportscanner, qtpaths and their CMake packages (see scripts/qt6-host.nix).
|
||||
if [[ ! -x "$HOST/libexec/moc" ]]; then
|
||||
echo "==> host Qt6 tools"
|
||||
nix build --impure -f "$ROOT/scripts/qt6-host.nix" --out-link "$HOST"
|
||||
fi
|
||||
|
||||
# Every .exe MSYS2 ships is a Windows program: replace each with a shim that
|
||||
# runs the host tool of the same name. Done in the extracted tree, so the
|
||||
# Qt6*Tools CMake packages keep pointing at the paths they record.
|
||||
shim() { # shim FILE HOSTTOOL
|
||||
printf '#!/bin/sh\nexec %s "$@"\n' "$2" >"$1"
|
||||
chmod +x "$1"
|
||||
}
|
||||
shimmed=0
|
||||
for f in "$PREFIX"/share/qt6/bin/*.exe "$PREFIX"/bin/*.exe; do
|
||||
[[ -e "$f" ]] || continue
|
||||
n="$(basename "$f" .exe)"
|
||||
for cand in "$HOST/libexec/$n" "$HOST/bin/$n"; do
|
||||
if [[ -x "$cand" ]]; then
|
||||
shim "$f" "$cand"
|
||||
shimmed=$((shimmed + 1))
|
||||
break
|
||||
fi
|
||||
done
|
||||
done
|
||||
echo "==> $shimmed Qt tools point at the host ones"
|
||||
|
||||
# --- build -----------------------------------------------------------------
|
||||
echo "==> cmake (Windows, GUI)"
|
||||
cmake -S "$ROOT" -B "$BUILD" -G Ninja \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$ROOT/cmake/mingw-w64-x86_64.cmake" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCST_BUILD_TUI=OFF -DCST_BUILD_APP=ON \
|
||||
-DCMAKE_PREFIX_PATH="$PREFIX" \
|
||||
-DQT_HOST_PATH="$HOST"
|
||||
# Only the app: the Win32 test targets are host-unrunnable and some of them are
|
||||
# POSIX (tests/test_tools_ext.c calls mkdir with two arguments), exactly as in
|
||||
# the TUI cross build.
|
||||
cmake --build "$BUILD" -j"$(nproc)" --target cst-agent-app
|
||||
EXE="$BUILD/app/cst-agent-app.exe"
|
||||
[[ -f "$EXE" ]] || { echo "no cst-agent-app.exe" >&2; exit 1; }
|
||||
|
||||
# --- the package -----------------------------------------------------------
|
||||
ver="$(sed -n 's/^#define CST_VERSION_\(MAJOR\|MINOR\|PATCH\) \([0-9]*\)$/\2/p' \
|
||||
"$ROOT/include/cst_version.h" | paste -sd.)"
|
||||
rm -rf "$OUT"
|
||||
mkdir -p "$OUT"
|
||||
cp -f "$EXE" "$OUT/cst-agent-app.exe"
|
||||
"$STRIP" "$OUT/cst-agent-app.exe" 2>/dev/null || true
|
||||
|
||||
# Which DLLs the app needs: the exe's imports, then theirs, and so on. What
|
||||
# Windows itself provides is skipped; anything else has to come out of the
|
||||
# extracted packages, which is what decided the list of packages above.
|
||||
missing=0
|
||||
isSystem() { # isSystem DLLNAME
|
||||
case "${1,,}" in
|
||||
api-ms-win-*|kernel32.dll|user32.dll|advapi32.dll|shell32.dll|ws2_32.dll|\
|
||||
iphlpapi.dll|bcrypt.dll|crypt32.dll|secur32.dll|ole32.dll|oleaut32.dll|\
|
||||
gdi32.dll|comdlg32.dll|dwmapi.dll|uxtheme.dll|winmm.dll|dnsapi.dll|\
|
||||
version.dll|shlwapi.dll|netapi32.dll|userenv.dll|mpr.dll|ntdll.dll|\
|
||||
authz.dll|d3d11.dll|d3d12.dll|dwrite.dll|dxgi.dll|winhttp.dll|\
|
||||
rpcrt4.dll|usp10.dll)
|
||||
return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
copyDeps() { # copyDeps FILE...
|
||||
local queue=("$@") f n src
|
||||
while ((${#queue[@]})); do
|
||||
f="${queue[0]}"
|
||||
queue=("${queue[@]:1}")
|
||||
while read -r n; do
|
||||
[[ -n "$n" ]] || continue
|
||||
isSystem "$n" && continue
|
||||
[[ -e "$OUT/$n" ]] && continue
|
||||
src=""
|
||||
for c in "$PREFIX/bin/$n" "$PREFIX/lib/$n"; do
|
||||
[[ -e "$c" ]] && src="$c" && break
|
||||
done
|
||||
if [[ -z "$src" ]]; then
|
||||
echo "missing DLL: $n (needed by $(basename "$f"))" >&2
|
||||
missing=1
|
||||
continue
|
||||
fi
|
||||
cp -f "$src" "$OUT/$n"
|
||||
queue+=("$OUT/$n")
|
||||
done < <("$OBJDUMP" -p "$f" | awk '/DLL Name/{print $3}')
|
||||
done
|
||||
}
|
||||
copyDeps "$OUT/cst-agent-app.exe"
|
||||
|
||||
# The plugins Qt loads by name: the window itself (and the offscreen one, so a
|
||||
# headless run is possible), the image formats a transcript can carry, the SVG
|
||||
# icon engine (the app's icons are .svg through QIcon).
|
||||
mkdir -p "$OUT/plugins"
|
||||
for p in platforms/qwindows.dll platforms/qoffscreen.dll \
|
||||
iconengines/qsvgicon.dll; do
|
||||
[[ -f "$PREFIX/share/qt6/plugins/$p" ]] || {
|
||||
echo "no plugin $p" >&2
|
||||
missing=1
|
||||
continue
|
||||
}
|
||||
mkdir -p "$OUT/plugins/$(dirname "$p")"
|
||||
cp -f "$PREFIX/share/qt6/plugins/$p" "$OUT/plugins/$p"
|
||||
done
|
||||
for p in imageformats/*.dll; do
|
||||
[[ -e "$PREFIX/share/qt6/plugins/$p" ]] || continue
|
||||
mkdir -p "$OUT/plugins/imageformats"
|
||||
cp -f "$PREFIX/share/qt6/plugins/$p" "$OUT/plugins/imageformats/"
|
||||
done
|
||||
# The QML modules the window imports (QtQuick, QtQuick.Controls and its Basic
|
||||
# style, Layouts, Window, ...): the whole tree, which is what a deployed Qt app
|
||||
# has next to its exe, and what the QML import path picks up from <exe>/qml.
|
||||
echo "==> QML modules"
|
||||
cp -r "$PREFIX/share/qt6/qml" "$OUT/qml"
|
||||
# ... and the plugins those QML modules load (a style, a helper, an image
|
||||
# provider: each is a DLL with its own imports).
|
||||
while IFS= read -r d; do
|
||||
copyDeps "$d"
|
||||
done < <(find "$OUT/qml" -name '*.dll')
|
||||
|
||||
# What the engine's shell tool wants beside it, exactly as the TUI package
|
||||
# ships it: the static busybox for Windows is the shell the engine runs.
|
||||
if [[ -f "$ROOT/third_party/busybox/busybox.exe" ]]; then
|
||||
cp -f "$ROOT/third_party/busybox/busybox.exe" "$OUT/busybox.exe"
|
||||
cp -f "$ROOT/third_party/busybox/LICENSE.busybox" "$OUT/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Qt finds its plugins and QML imports relative to the exe through this file.
|
||||
cat >"$OUT/qt.conf" <<'EOF'
|
||||
[Paths]
|
||||
Prefix = .
|
||||
Plugins = plugins
|
||||
Qml2Imports = qml
|
||||
EOF
|
||||
echo "$ver" >"$OUT/VERSION"
|
||||
|
||||
if [[ "$missing" != 0 ]]; then
|
||||
echo "the package is incomplete (see the missing entries above)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
zip="$ROOT/dist/cst-agent-app-$ver-windows-x86_64.zip"
|
||||
rm -f "$zip"
|
||||
(cd "$ROOT/dist" && zip -qr "$zip" windows-x86_64-app)
|
||||
(cd "$ROOT/dist" && sha256sum "$(basename "$zip")" >"$(basename "$zip").sha256")
|
||||
du -sh "$OUT"
|
||||
ls -l "$zip"
|
||||
echo "$zip"
|
||||
Reference in New Issue
Block a user