#!/usr/bin/env bash # compile-windows.sh — build Flutter Windows desktop app, package into # dist/ via the existing Inno Setup script (client/windows/installer/pangolin.iss). # # Mirrors ~/code/jiu/scripts/ci/compile-windows.sh's short-path fix, adapted: # - version derived via pangolin's ver_from_tag, not jiu's ad-hoc strip. # - pangolin's Windows client does NOT embed libbox — it bundles a # downloaded sing-box.exe + wintun.dll as a subprocess (see # client/windows/README.md, CLAUDE.md "client/ 移动端"'s sibling desktop # note). Those are fetched by app/kernel/fetch-desktop-bin.sh, NOT built # by scripts/build-libbox.sh — no JDK/NDK/gomobile needed on Windows. # - pangolin already has a working local packaging path: # client/windows/build.ps1 + client/windows/installer/pangolin.iss. This # script re-implements that flow in bash (matching the repo's # jiu-mirrored bash-script-per-platform convention) rather than shelling # out to build.ps1, so it can apply jiu's short-path fix below. # - jiu's short-path fix: the Forgejo Windows runner checks out under # C:\Windows\System32\config\systemprofile\.cache\act\...\hostexecutor, # which triggers WOW64 filesystem redirection that breaks CMake/Flutter # native paths. jiu copies client/ to C:\jiu-build\client and builds # there. pangolin's windows/CMakeLists.txt additionally resolves the # kernel binaries via a path RELATIVE to CMAKE_SOURCE_DIR # (client/windows/../../app/kernel/dist/desktop/windows-* — see # client/windows/CMakeLists.txt "Pangolin sing-box kernel" section), so # this script must copy app/kernel/dist alongside client/ preserving the # SAME relative layout (BUILD_ROOT/client + BUILD_ROOT/app/kernel/dist), # not just client/ alone like jiu does — jiu has no such cross-directory # kernel reference. # - dart-define is PANGOLIN_API_URL, not jiu's BASE_URL/PUBLIC_URL/APP_VERSION. # - pangolin.iss hardcodes `#define MyAppVersion "1.0.47"` (no #ifndef guard # like jiu's jiu-installer.iss), so ISCC's /D command-line override would # be silently ignored. This script `sed`s the version into the copied # .iss file instead of passing /DAppVer. # - output dist/pangolin-windows-x64-setup.exe (stable name — the .iss's # own OutputBaseFilename is version-suffixed "pangolin-setup-.exe", # so this script copies+renames it to the stable name deploy-client.sh # and release-client.sh expect). # - unsigned (like jiu) — first install shows a SmartScreen warning; no # code-signing cert configured (see report). set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=scripts/ci/_env.sh . "${SCRIPT_DIR}/_env.sh" TAG="${1:?usage: compile-windows.sh }" ver_file="/tmp/compile_windows_ver.$$" ver_from_tag client "$TAG" > "$ver_file" VER="" read -r VER < "$ver_file" || true # 无结尾换行的 read 退出码,见 compile-android.sh 同注释 rm -f "$ver_file" # build number 同 android/macos 规则(MAJOR*10000+MINOR*100+PATCH)。 MAJOR="${VER%%.*}" _REST="${VER#*.}" MINOR="${_REST%%.*}" PATCH="${_REST#*.}" PATCH="${PATCH%%-*}" BUILD=$(( MAJOR * 10000 + MINOR * 100 + PATCH )) echo "==> compile-windows: tag=${TAG} version=${VER} build=${BUILD}" API_URL="${PANGOLIN_API_URL:-https://api.yanmeiai.com}" # ── [1/4] fetch sing-box.exe + wintun.dll into the ORIGINAL checkout ─────── # Must run before the short-path copy below (step 2), so app/kernel/dist/ # exists to be copied alongside client/. echo "==> compile-windows: fetching sing-box.exe + wintun.dll" bash app/kernel/fetch-desktop-bin.sh windows amd64 # ── [2/4] copy client/ + app/kernel/dist to a short path ─────────────────── BUILD_ROOT="/c/pangolin-build" BUILD_CLIENT="${BUILD_ROOT}/client" echo "==> copying client/ + app/kernel/dist to ${BUILD_ROOT}" rm -rf "$BUILD_ROOT" mkdir -p "${BUILD_ROOT}/app/kernel" cp -r client "$BUILD_CLIENT" cp -r app/kernel/dist "${BUILD_ROOT}/app/kernel/dist" # `cp -r` turns Flutter's plugin symlinks (windows/flutter/ephemeral/.plugin_symlinks/*) # into real directories. flutter's createPluginSymlinks only cleans up *symlinks*, so it # then fails to create a symlink over the copied real dir (errno 183 / PathExists). # Drop all regenerable artifacts so the build below recreates them cleanly (same fix as jiu). rm -rf "${BUILD_CLIENT}/windows/flutter/ephemeral" \ "${BUILD_CLIENT}/.dart_tool" \ "${BUILD_CLIENT}/build" # 同步 Flutter app 版本到 tag(GNU sed,windows runner)。否则 package_info 卡在 # pubspec committed 值(1.0.48),设置页显示旧版 + 自动更新永远判"有新版"。 # 此前只改了 Inno 安装器 MyAppVersion(下方),App 内部版本没跟上——本次修复。 sed -i "s/^version:.*/version: ${VER}+${BUILD}/" "${BUILD_CLIENT}/pubspec.yaml" echo "==> compile-windows: pubspec version -> ${VER}+${BUILD}" # ── [3/4] build ────────────────────────────────────────────────────────────── # NOTE(controller): `flutter create` on an existing project only fills in # scaffolding it manages (ephemeral/, generated_plugins.cmake, ...) and should # not overwrite files that already exist — jiu relies on exactly this. But # pangolin's windows/ tree is more heavily customized than jiu's (CMakeLists # kernel-bundling block, runner.exe.manifest requireAdministrator + the # /MANIFESTUAC:NO link flag — see client/windows/README.md "已知坑"). Verify # on first real CI run that none of those get clobbered. pushd "$BUILD_CLIENT" > /dev/null flutter create --platforms=windows . --project-name pangolin_vpn flutter_pub_get_retry # 预取包 + 重试,防 pub.flutter-io.cn 被 GFW 抖断(见 _env.sh) flutter build windows --release "--dart-define=PANGOLIN_API_URL=${API_URL}" popd > /dev/null # ── [4/4] package with Inno Setup (existing client/windows/installer/pangolin.iss) ── ISCC="" for c in "/c/Program Files (x86)/Inno Setup 6/ISCC.exe" "/c/Program Files/Inno Setup 6/ISCC.exe"; do if [ -f "$c" ]; then ISCC="$c"; break; fi done if [ -z "$ISCC" ]; then echo "ERROR: Inno Setup (ISCC) not found. Install Inno Setup 6 on this runner first." >&2 exit 1 fi ISS="${BUILD_CLIENT}/windows/installer/pangolin.iss" # pangolin.iss hardcodes MyAppVersion (no #ifndef guard) — sed it to match the # release tag rather than relying on an ISCC /D override (which would be # shadowed by the unconditional #define — see header note above). sed -i "s/^#define MyAppVersion .*/#define MyAppVersion \"${VER}\"/" "$ISS" echo "==> building installer with Inno Setup (version ${VER})" # ISCC 是原生 Windows 程序,只认 Windows 路径(C:\...);传 MSYS 风格 /c/... 会被它 # 当成选项 → "Unknown option: /c/...pangolin.iss"。故先用 cygpath 把 .iss 转成 # Windows 路径再传(jiu 是直接写死 C:\ 字面量;这里用 cygpath 更稳)。 # 不用 $() 命令替换(仓库约定):cygpath 输出落临时文件、read 读回。 cygpath -w "$ISS" > /tmp/pangolin_iss_win.$$ ISS_WIN="" read -r ISS_WIN < /tmp/pangolin_iss_win.$$ rm -f /tmp/pangolin_iss_win.$$ # MSYS_NO_PATHCONV / MSYS2_ARG_CONV_EXCL 关掉 Git Bash 对 /-开头参数的自动路径转换 # (否则会把 .iss 里将来可能的 /D 定义也改坏)。ISS_WIN 已是 Windows 路径,原样传即可。 MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*' "$ISCC" "$ISS_WIN" # pangolin.iss has no explicit OutputDir -> Inno defaults to {src}\Output # (relative to the .iss file), filename OutputBaseFilename=pangolin-setup-. mkdir -p dist SETUP_SRC="C:\\pangolin-build\\client\\windows\\installer\\Output\\pangolin-setup-${VER}.exe" echo "==> copying installer -> dist/pangolin-windows-x64-setup.exe" powershell -NoProfile -Command \ "Copy-Item '${SETUP_SRC}' 'dist\\pangolin-windows-x64-setup.exe' -Force" echo "==> compile-windows: done — dist/ contents:" ls -lh dist/