diff --git a/.github/workflows/build-et-binaries.yml b/.github/workflows/build-et-binaries.yml new file mode 100644 index 00000000..7fc78016 --- /dev/null +++ b/.github/workflows/build-et-binaries.yml @@ -0,0 +1,222 @@ +name: build-et-binaries + +# Trigger philosophy (mirrors build-mosh-binaries.yml): +# - Pushes that touch the et build pipeline + PRs run the matrix so we can +# validate workflow / script changes without tagging. Artifacts upload as +# workflow artifacts only; *no* release. +# - Manual `workflow_dispatch` with `release_tag` publishes the binaries + +# SHA256SUMS to the dedicated binary repository +# (`binaricat/Netcatty-et-bin` by default). +# +# `paths` keeps unrelated commits (UI, bridges, etc) from rebuilding the et +# binaries on every push. +on: + workflow_dispatch: + inputs: + et_ref: + description: "EternalTerminal git ref (tag/branch/commit) — see https://github.com/MisterTea/EternalTerminal" + type: string + default: "et-v6.2.10" + release_tag: + description: "Optional release tag to attach binaries to (e.g. et-bin-6.2.10-1). Empty = artifacts only." + type: string + default: "" + release_repo: + description: "Repository that stores et binary releases." + type: string + default: "binaricat/Netcatty-et-bin" + push: + branches: + - "**" + paths: + - ".github/workflows/build-et-binaries.yml" + - "electron-builder.config.cjs" + - "package.json" + - "scripts/build-et/**" + - "scripts/fetch-et-binaries.cjs" + - "scripts/et-extra-resources.cjs" + pull_request: + paths: + - ".github/workflows/build-et-binaries.yml" + - "electron-builder.config.cjs" + - "package.json" + - "scripts/build-et/**" + - "scripts/fetch-et-binaries.cjs" + - "scripts/et-extra-resources.cjs" + +concurrency: + group: build-et-binaries-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + ET_REF: ${{ inputs.et_ref || 'et-v6.2.10' }} + +jobs: + # ------------------------------------------------------------------ + # Linux x64 (manylinux2014 / glibc 2.17, broad distro compatibility). + # ------------------------------------------------------------------ + build-linux-x64: + name: build-linux-x64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build et (linux-x64) + run: | + docker run --rm \ + -e ET_REF="${ET_REF}" \ + -e OUT_DIR=/work/out \ + -e ARCH=x64 \ + -v "${GITHUB_WORKSPACE}:/work" \ + -w /work \ + quay.io/pypa/manylinux2014_x86_64 \ + bash scripts/build-et/build-linux.sh + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: et-linux-x64 + path: out/ + + build-linux-arm64: + name: build-linux-arm64 + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + - name: Build et (linux-arm64) + run: | + docker run --rm \ + -e ET_REF="${ET_REF}" \ + -e OUT_DIR=/work/out \ + -e ARCH=arm64 \ + -v "${GITHUB_WORKSPACE}:/work" \ + -w /work \ + quay.io/pypa/manylinux2014_aarch64 \ + bash scripts/build-et/build-linux.sh + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: et-linux-arm64 + path: out/ + + # ------------------------------------------------------------------ + # macOS universal2 (arm64 + x86_64 lipo). Min deployment target macOS 11. + # ------------------------------------------------------------------ + build-macos-universal: + name: build-macos-universal + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + - name: Build et (darwin-universal) + env: + ET_REF: ${{ env.ET_REF }} + OUT_DIR: ${{ github.workspace }}/out + MACOSX_DEPLOYMENT_TARGET: "11.0" + run: bash scripts/build-et/build-macos.sh + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: et-darwin-universal + path: out/ + + # ------------------------------------------------------------------ + # Windows x64 — static MSVC build (no DLL bundle). + # ------------------------------------------------------------------ + build-windows-x64: + name: build-windows-x64 + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Install ninja + run: choco install -y ninja + - name: Set up MSVC developer command prompt + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + - name: Build et (win32-x64) + env: + ET_REF: ${{ env.ET_REF }} + OUT_DIR: ${{ github.workspace }}\out + shell: pwsh + run: pwsh -File scripts/build-et/build-windows.ps1 + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: et-win32-x64 + path: out/ + + # ------------------------------------------------------------------ + # Windows arm64 — intentionally not built until a tested client exists. + # ------------------------------------------------------------------ + + # ------------------------------------------------------------------ + # Aggregate + optional release to the dedicated binary repository. + # ------------------------------------------------------------------ + release: + name: release + needs: + - build-linux-x64 + - build-linux-arm64 + - build-macos-universal + - build-windows-x64 + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' && inputs.release_tag != '' + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + - name: Stage release files + run: | + set -euo pipefail + mkdir -p release + for d in artifacts/*/; do + find "$d" -maxdepth 1 -type f -exec cp {} release/ \; + done + (cd release && find . -maxdepth 1 -type f ! -name SHA256SUMS -printf '%P\n' | sort | xargs sha256sum > SHA256SUMS) + ls -la release + cat release/SHA256SUMS + - name: Determine tag + id: tag + env: + RELEASE_TAG: ${{ inputs.release_tag }} + run: | + tag="${RELEASE_TAG}" + if [[ ! "$tag" =~ ^et-bin-[A-Za-z0-9._-]+$ ]]; then + echo "Invalid et binary release tag: $tag" >&2 + exit 1 + fi + printf 'name=%s\n' "$tag" >> "$GITHUB_OUTPUT" + - name: Create / update release + env: + GH_TOKEN: ${{ secrets.ET_BIN_RELEASE_TOKEN }} + RELEASE_REPO: ${{ inputs.release_repo }} + RELEASE_TAG: ${{ steps.tag.outputs.name }} + run: | + set -euo pipefail + if [[ -z "${GH_TOKEN:-}" ]]; then + echo "::error::ET_BIN_RELEASE_TOKEN is required to publish into ${RELEASE_REPO}." + exit 1 + fi + { + printf '%s\n' 'Pre-built EternalTerminal `et` client binaries consumed by `scripts/fetch-et-binaries.cjs` during `npm run pack`.' + printf 'Built from `MisterTea/EternalTerminal` upstream ref `%s`.\n\n' "${ET_REF}" + printf 'Source workflow: %s/%s/actions/runs/%s\n' "${GITHUB_SERVER_URL}" "${GITHUB_REPOSITORY}" "${GITHUB_RUN_ID}" + printf 'Source commit: `%s`\n\n' "${GITHUB_SHA}" + printf '%s\n' 'All artifacts are Apache-2.0; see `resources/et/README.md` for source provenance.' + } > release-notes.md + if gh release view "${RELEASE_TAG}" --repo "${RELEASE_REPO}" >/dev/null 2>&1; then + gh release edit "${RELEASE_TAG}" \ + --repo "${RELEASE_REPO}" \ + --title "${RELEASE_TAG}" \ + --notes-file release-notes.md + gh release upload "${RELEASE_TAG}" release/* \ + --repo "${RELEASE_REPO}" \ + --clobber + else + gh release create "${RELEASE_TAG}" release/* \ + --repo "${RELEASE_REPO}" \ + --title "${RELEASE_TAG}" \ + --notes-file release-notes.md + fi diff --git a/.gitignore b/.gitignore index 548aa86c..314c665d 100755 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,12 @@ build_with_vs2022.bat /resources/mosh/*/mosh-client-*-dlls/ /resources/mosh/*/*.dll /resources/mosh/*/terminfo/ + +# Bundled EternalTerminal `et` client binaries fetched at pack time by +# scripts/fetch-et-binaries.cjs. resources/et/README.md is committed; the +# actual binaries (and any DLL bundle for dynamically-linked Windows builds) +# are pulled from the dedicated et binary repository, never committed. +/resources/et/*/et +/resources/et/*/et.exe +/resources/et/*/et-*-dlls/ +/resources/et/*/*.dll diff --git a/resources/et/README.md b/resources/et/README.md new file mode 100644 index 00000000..54e9f49f --- /dev/null +++ b/resources/et/README.md @@ -0,0 +1,81 @@ +# Bundled EternalTerminal `et` client + +This directory holds the EternalTerminal **client** binary (`et`) bundled +with the Netcatty installer. Netcatty launches this bundled `et` directly +(see `electron/bridges/terminalBridge/etSession.cjs`); `et` performs its +own SSH bootstrap and EternalTerminal protocol handshake against the remote +`etserver` / `etterminal`. + +Unlike `mosh-client`, `et` is a pure network-transport client and does not +render a terminal locally, so there is **no terminfo bundle** here — only the +single `et` (`et.exe` on Windows) binary. + +## How binaries land here + +1. `.github/workflows/build-et-binaries.yml` builds `et` on relevant + pushes/PRs, or on a manual `workflow_dispatch`. It uses + `scripts/build-et/build-linux.sh` and `scripts/build-et/build-macos.sh` + for Linux/macOS, and `scripts/build-et/build-windows.ps1` for Windows: + + | target | provenance | + |-------------------|------------------------------------------------------------------| + | `linux-x64` | upstream source, manylinux2014, vcpkg static deps + glibc | + | `linux-arm64` | upstream source, manylinux2014, vcpkg static deps + glibc | + | `darwin-universal`| upstream source, lipo arm64 + x86_64, macOS system dylibs only | + | `win32-x64` | upstream source, MSVC + vcpkg `x64-windows-static` (no DLLs) | + | `win32-arm64` | (not built — add after a tested arm64 client is available) | + + ET builds with CMake + Ninja + vcpkg + (`cmake -DDISABLE_TELEMETRY=ON -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo`). + +2. When manually dispatched with `release_tag`, that workflow publishes the + binaries to the dedicated `binaricat/Netcatty-et-bin` repository. The + release gets a tag like `et-bin-6.2.10-1`, with `SHA256SUMS` attached. + +3. Release packaging runs `scripts/resolve-et-bin-release.cjs` before + `npm run fetch:et`. It uses an explicit workflow input first, then the + `ET_BIN_RELEASE` repository variable, then the latest non-draft + `et-bin-*` GitHub Release from the dedicated binary repository. The fetch + step pulls the binaries into `resources/et//`. For local + packaging, set `ET_BIN_RELEASE` yourself before running the same fetch + command. Override `ET_BIN_OWNER` / `ET_BIN_REPO` only when testing a + different binary repository. `electron-builder.config.cjs` then copies the + matching binary into `Resources/et/et[.exe]`. + + Local dev uses the same binary path: `npm run dev` runs + `npm run fetch:et:dev` first, which downloads the host platform's bundled + `et` into this gitignored directory. Netcatty does not fall back to a + system-installed `et`; if the bundled binary is missing, ET startup fails + loudly instead of using whatever happens to be installed on the developer + machine. + +The directory is otherwise empty (binaries are gitignored). + +## Licenses + +- EternalTerminal is licensed under **Apache-2.0** + (https://github.com/MisterTea/EternalTerminal). +- Netcatty is **GPL-3.0**; Apache-2.0 is one-way compatible with GPL-3.0, so + redistribution as part of the installer is permitted. +- vcpkg-managed deps (boost Boost-License, libsodium ISC, protobuf + BSD-3-Clause, gflags BSD-3-Clause) are compatible with GPL-3.0. + +## Reproducible build + +To reproduce the Linux binary locally: + +```sh +docker run --rm -v $PWD:/workspace -w /workspace \ + -e ET_REF=et-v6.2.10 -e ARCH=x64 -e OUT_DIR=/workspace/out \ + quay.io/pypa/manylinux2014_x86_64 \ + bash scripts/build-et/build-linux.sh +``` + +For macOS the build needs an Xcode toolchain; see +`scripts/build-et/build-macos.sh`. For Windows see +`scripts/build-et/build-windows.ps1`. + +## Roadmap + +- Add Windows arm64 only after a tested standalone arm64 client is available. +- Make `ET_REF` track upstream release tags automatically. diff --git a/scripts/build-et/build-linux.sh b/scripts/build-et/build-linux.sh new file mode 100644 index 00000000..8bbac6ce --- /dev/null +++ b/scripts/build-et/build-linux.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash +# Build a portable EternalTerminal `et` client inside manylinux2014. +# +# Inputs (env): +# ET_REF — git ref of MisterTea/EternalTerminal to build (e.g. et-v6.2.10) +# ARCH — x64 | arm64 (for output naming only; container is already that arch) +# OUT_DIR — directory to write et-linux-.tar.gz + sha256 +# +# Output: +# $OUT_DIR/et-linux-.tar.gz (single `et` client binary) +# $OUT_DIR/et-linux-.tar.gz.sha256 +# +# Strategy: build inside manylinux2014 (glibc 2.17) for broad distro +# compatibility. EternalTerminal vendors vcpkg under external/vcpkg and uses +# manifest mode, so its third-party deps (protobuf, libsodium, openssl, ...) +# are built as static archives by vcpkg's x64-linux / arm64-linux triplet. +# The resulting `et` still depends on baseline Linux system libraries +# (glibc family), compatible with virtually every distro since 2014. +# +# `et` is a pure network-transport client; it renders no terminal locally and +# needs no terminfo database, so the bundle ships only the binary. +set -euo pipefail + +: "${ET_REF:?missing ET_REF}" +: "${ARCH:?missing ARCH}" +: "${OUT_DIR:?missing OUT_DIR}" + +validate_et_ref() { + if [[ ! "$ET_REF" =~ ^[A-Za-z0-9][A-Za-z0-9._/-]*$ ]] \ + || [[ "$ET_REF" == *..* ]] \ + || [[ "$ET_REF" == *@\{* ]] \ + || [[ "$ET_REF" == */ ]] \ + || [[ "$ET_REF" == *.lock ]]; then + echo "ERROR: invalid ET_REF: $ET_REF" >&2 + exit 1 + fi +} +validate_et_ref + +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT +mkdir -p "$OUT_DIR" + +# manylinux2014 ships a devtoolset gcc and git, but an old cmake/ninja. +# Install modern cmake + ninja from PyPI (vcpkg requires cmake >= 3.x). +yum install -y -q zip unzip tar curl perl-IPC-Cmd >/dev/null 2>&1 || true + +# manylinux ships CPython interpreters under /opt/python//bin but puts +# none of them on PATH (a bare `python3` fails with 127). Prefer a known +# *stable* cpXY: picking "newest" would grab pre-release builds such as +# 3.15.0b1, which we don't want driving the cmake/ninja install. +if ! command -v python3 >/dev/null 2>&1; then + for tag in cp313 cp312 cp311 cp310; do + if [ -x "/opt/python/$tag-$tag/bin/python3" ]; then + export PATH="/opt/python/$tag-$tag/bin:$PATH" + break + fi + done +fi +command -v python3 >/dev/null 2>&1 \ + || { echo "ERROR: no stable python3 under /opt/python (manylinux layout changed?)" >&2; exit 1; } + +python3 -m pip install --quiet --upgrade pip +# Pin cmake < 4: ET's pinned vcpkg baseline and some ports don't configure +# cleanly under cmake 4.x. ninja is unconstrained. +python3 -m pip install --quiet "cmake>=3.25,<4" ninja +export PATH="$(python3 -c 'import sysconfig,os;print(os.path.join(sysconfig.get_path("scripts")))'):$PATH" + +cd "$WORK" + +# Fetch EternalTerminal at the requested ref, with the vendored vcpkg +# submodule. Branch names, tags, and commit SHAs all work. +git init et +git -C et remote add origin https://github.com/MisterTea/EternalTerminal.git +git -C et fetch --depth 1 origin "$ET_REF" +git -C et checkout --detach FETCH_HEAD +git -C et submodule update --init --recursive --depth 1 + +# Drop sentry-native from the vcpkg manifest. We build with +# -DDISABLE_TELEMETRY=ON, so ET's CMake never calls find_package(sentry) nor +# links it; but vcpkg's manifest mode still force-builds every listed dep +# during configure. sentry-native pulls in crashpad, is the heaviest dep, and +# fails to build on arm64-linux — dropping it fixes arm64 and speeds up all. +if ! grep -q '"sentry-native"' "$WORK/et/vcpkg.json"; then + echo "ERROR: sentry-native not in vcpkg.json (ET manifest changed?)" >&2; exit 1 +fi +grep -v '"sentry-native"' "$WORK/et/vcpkg.json" > "$WORK/et/vcpkg.json.tmp" +mv "$WORK/et/vcpkg.json.tmp" "$WORK/et/vcpkg.json" + +# Build only the Release halves of the vcpkg deps (skip the Debug pass) to +# roughly halve build time. Overlay triplets mirror ET's chosen community +# triplet but force release-only; selected via VCPKG_OVERLAY_TRIPLETS so the +# vendored vcpkg tree stays untouched. +OVERLAY="$WORK/vcpkg-overlay-triplets" +mkdir -p "$OVERLAY" +for t in x64-linux arm64-linux; do + src=$(find "$WORK/et/external/vcpkg/triplets" -name "$t.cmake" | head -1) + [ -n "$src" ] || { echo "ERROR: vcpkg triplet $t.cmake not found" >&2; exit 1; } + cp "$src" "$OVERLAY/$t.cmake" + echo 'set(VCPKG_BUILD_TYPE release)' >> "$OVERLAY/$t.cmake" +done +export VCPKG_OVERLAY_TRIPLETS="$OVERLAY" + +# Bootstrap the vendored vcpkg so CMake's vcpkg toolchain can resolve the +# manifest deps. +( cd et && ./external/vcpkg/bootstrap-vcpkg.sh -disableMetrics ) + +BUILD_DIR="$WORK/et/build" +# ET's CMake sets its own vcpkg toolchain + triplet (auto-detected from +# uname -m); we supply only the generator + build type. DISABLE_TELEMETRY=ON +# keeps ET from using Sentry, matching the manifest edit above. +# +# CMAKE_CXX_STANDARD_LIBRARIES=-lanl: ET (via cpp-httplib) references glibc's +# async DNS resolver getaddrinfo_a / gai_* (which live in libanl), but ET's +# link line omits -lanl, so linking `et` fails with "undefined reference to +# getaddrinfo_a". STANDARD_LIBRARIES is appended after all other libraries — +# exactly where the linker needs it to resolve those symbols. +cmake -S "$WORK/et" -B "$BUILD_DIR" \ + -GNinja \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DDISABLE_TELEMETRY=ON \ + -DCMAKE_CXX_STANDARD_LIBRARIES=-lanl + +cmake --build "$BUILD_DIR" --target et + +BUNDLE_DIR="$WORK/linux-$ARCH-bundle" +mkdir -p "$BUNDLE_DIR" +OUT_BIN="$BUNDLE_DIR/et" +cp "$BUILD_DIR/et" "$OUT_BIN" +strip "$OUT_BIN" + +echo "--- file ---" +file "$OUT_BIN" +echo "--- ldd ---" +ldd "$OUT_BIN" || true +echo "--- size ---" +ls -lh "$OUT_BIN" + +# Sanity check: must not link any non-system shared libraries. Allow only the +# glibc runtime family and the ELF loader (matches the mosh build policy). +ldd "$OUT_BIN" > "$WORK/ldd.txt" || true +awk ' + /=>/ { print $1; next } + /^[[:space:]]*\/.*ld-linux/ { print $1; next } +' "$WORK/ldd.txt" > "$WORK/deps.txt" +if grep -Ev '^(linux-vdso\.so\.1|lib(c|m|pthread|rt|dl|resolv|util|z|stdc\+\+|gcc_s|atomic|anl)\.so\.[0-9]+|/lib.*/ld-linux.*\.so\.[0-9]+|ld-linux.*\.so\.[0-9]+)$' "$WORK/deps.txt"; then + echo "ERROR: et links a non-system shared library; static linking failed." >&2 + exit 1 +fi + +BUNDLE_TGZ="$OUT_DIR/et-linux-$ARCH.tar.gz" +( cd "$BUNDLE_DIR" && tar -czf "$BUNDLE_TGZ" "et" ) + +( cd "$OUT_DIR" && sha256sum "et-linux-$ARCH.tar.gz" > "et-linux-$ARCH.tar.gz.sha256" ) +cat "$OUT_DIR/et-linux-$ARCH.tar.gz.sha256" diff --git a/scripts/build-et/build-macos.sh b/scripts/build-et/build-macos.sh new file mode 100644 index 00000000..43a26054 --- /dev/null +++ b/scripts/build-et/build-macos.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# Build a universal EternalTerminal `et` client on macOS (arm64 + x86_64). +# +# Inputs (env): +# ET_REF — git ref of MisterTea/EternalTerminal to build (e.g. et-v6.2.10) +# OUT_DIR — directory to write et-darwin-universal.tar.gz + sha256 +# MACOSX_DEPLOYMENT_TARGET — min macOS (default 11.0) +# +# Output: +# $OUT_DIR/et-darwin-universal.tar.gz (single universal `et`) +# $OUT_DIR/et-darwin-universal.tar.gz.sha256 +# +# Builds each arch separately (vcpkg arm64-osx / x64-osx static triplets) and +# lipo-combines the two `et` binaries. Links only macOS system dylibs. +set -euo pipefail + +: "${ET_REF:?missing ET_REF}" +: "${OUT_DIR:?missing OUT_DIR}" +export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}" + +validate_et_ref() { + if [[ ! "$ET_REF" =~ ^[A-Za-z0-9][A-Za-z0-9._/-]*$ ]] \ + || [[ "$ET_REF" == *..* ]] \ + || [[ "$ET_REF" == *@\{* ]] \ + || [[ "$ET_REF" == */ ]] \ + || [[ "$ET_REF" == *.lock ]]; then + echo "ERROR: invalid ET_REF: $ET_REF" >&2 + exit 1 + fi +} +validate_et_ref + +command -v ninja >/dev/null 2>&1 || brew install ninja +command -v cmake >/dev/null 2>&1 || brew install cmake +command -v autoconf >/dev/null 2>&1 || brew install automake autoconf libtool + +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT +mkdir -p "$OUT_DIR" + +cd "$WORK" +git init et +git -C et remote add origin https://github.com/MisterTea/EternalTerminal.git +git -C et fetch --depth 1 origin "$ET_REF" +git -C et checkout --detach FETCH_HEAD +git -C et submodule update --init --recursive --depth 1 + +# Drop sentry-native from the vcpkg manifest — see build-linux.sh for the +# full rationale. -DDISABLE_TELEMETRY=ON means ET never references Sentry, +# yet vcpkg's manifest would otherwise force-build it (and crashpad) anyway. +if ! grep -q '"sentry-native"' "$WORK/et/vcpkg.json"; then + echo "ERROR: sentry-native not in vcpkg.json (ET manifest changed?)" >&2; exit 1 +fi +grep -v '"sentry-native"' "$WORK/et/vcpkg.json" > "$WORK/et/vcpkg.json.tmp" +mv "$WORK/et/vcpkg.json.tmp" "$WORK/et/vcpkg.json" + +# Release-only vcpkg deps (skip the Debug pass) to halve build time, via +# overlay triplets that mirror the osx triplets but force release-only. +OVERLAY="$WORK/vcpkg-overlay-triplets" +mkdir -p "$OVERLAY" +for t in arm64-osx x64-osx; do + src=$(find "$WORK/et/external/vcpkg/triplets" -name "$t.cmake" | head -1) + [ -n "$src" ] || { echo "ERROR: vcpkg triplet $t.cmake not found" >&2; exit 1; } + cp "$src" "$OVERLAY/$t.cmake" + echo 'set(VCPKG_BUILD_TYPE release)' >> "$OVERLAY/$t.cmake" +done +export VCPKG_OVERLAY_TRIPLETS="$OVERLAY" + +( cd et && ./external/vcpkg/bootstrap-vcpkg.sh -disableMetrics ) + +build_arch() { + local arch="$1" # arm64 | x86_64 + local triplet="$2" # arm64-osx | x64-osx + local build_dir="$WORK/build-$arch" + echo "=== building et for $arch ($triplet) ===" + cmake -S "$WORK/et" -B "$build_dir" \ + -GNinja \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DDISABLE_TELEMETRY=ON \ + -DCMAKE_OSX_ARCHITECTURES="$arch" \ + -DVCPKG_TARGET_TRIPLET="$triplet" + cmake --build "$build_dir" --target et + echo "$build_dir/et" +} + +ARM_BIN=$(build_arch arm64 arm64-osx | tail -1) +X64_BIN=$(build_arch x86_64 x64-osx | tail -1) + +BUNDLE_DIR="$WORK/darwin-universal-bundle" +mkdir -p "$BUNDLE_DIR" +OUT_BIN="$BUNDLE_DIR/et" +lipo -create -output "$OUT_BIN" "$ARM_BIN" "$X64_BIN" +strip "$OUT_BIN" || true + +echo "--- lipo info ---" +lipo -info "$OUT_BIN" +echo "--- otool -L ---" +otool -L "$OUT_BIN" || true + +# Sanity check: only macOS system dylibs (/usr/lib, /System/Library) allowed. +# A universal binary makes `otool -L` print a " (architecture X):" +# header per slice; key off the "(compatibility version ...)" suffix that only +# real dependency lines carry, so those per-arch headers aren't misread as a +# non-system dylib (tail -n +2 only drops the first one). +if otool -L "$OUT_BIN" | awk '/\(compatibility version/ {print $1}' \ + | grep -Ev '^(/usr/lib/|/System/Library/)' | grep -q .; then + echo "ERROR: et links a non-system dylib; static linking failed." >&2 + otool -L "$OUT_BIN" >&2 + exit 1 +fi + +BUNDLE_TGZ="$OUT_DIR/et-darwin-universal.tar.gz" +( cd "$BUNDLE_DIR" && tar -czf "$BUNDLE_TGZ" "et" ) +( cd "$OUT_DIR" && shasum -a 256 "et-darwin-universal.tar.gz" > "et-darwin-universal.tar.gz.sha256" ) +cat "$OUT_DIR/et-darwin-universal.tar.gz.sha256" diff --git a/scripts/build-et/build-windows.ps1 b/scripts/build-et/build-windows.ps1 new file mode 100644 index 00000000..d3f83e95 --- /dev/null +++ b/scripts/build-et/build-windows.ps1 @@ -0,0 +1,105 @@ +# Build a static EternalTerminal `et` client on Windows (x64, MSVC). +# +# Inputs (env): +# ET_REF — git ref of MisterTea/EternalTerminal to build (e.g. et-v6.2.10) +# OUT_DIR — directory to write et-win32-x64.tar.gz + sha256 +# +# Output: +# $OUT_DIR/et-win32-x64.tar.gz (single static et.exe, no DLLs) +# $OUT_DIR/et-win32-x64.tar.gz.sha256 +# +# Uses the vendored vcpkg x64-windows-static triplet so the produced et.exe +# statically links the MSVC runtime and all third-party deps — no DLL bundle +# is needed. Run from a Developer Command Prompt (ilammy/msvc-dev-cmd) so +# cl.exe / ninja are on PATH. +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +if (-not $env:ET_REF) { throw "missing ET_REF" } +if (-not $env:OUT_DIR) { throw "missing OUT_DIR" } + +$etRef = $env:ET_REF +if ($etRef -notmatch '^[A-Za-z0-9][A-Za-z0-9._/-]*$' -or $etRef -match '\.\.' -or $etRef -match '@\{' -or $etRef.EndsWith('/') -or $etRef.EndsWith('.lock')) { + throw "invalid ET_REF: $etRef" +} + +# Root the build just under the drive root. vcpkg unpacks dependencies into +# \et\external_imported\vcpkg\buildtrees\... and libsodium's bundled +# MSBuild project pulls sources via long "..\..\..\..\src\..." relative paths. +# Rooted in %TEMP% (~60 chars) the unnormalized path exceeds Windows MAX_PATH +# (260) and fails with "C1083: Cannot open source file". A short drive-root +# (e.g. C:\et-XXXXXXXX) keeps every path comfortably under the limit. +$work = "$env:SystemDrive\et-" + [System.Guid]::NewGuid().ToString("N").Substring(0, 8) +if (Test-Path $work) { Remove-Item -Recurse -Force $work -ErrorAction SilentlyContinue } +New-Item -ItemType Directory -Force -Path $work | Out-Null +New-Item -ItemType Directory -Force -Path $env:OUT_DIR | Out-Null + +try { + $etDir = Join-Path $work "et" + git init $etDir + git -C $etDir remote add origin https://github.com/MisterTea/EternalTerminal.git + git -C $etDir fetch --depth 1 origin $etRef + git -C $etDir checkout --detach FETCH_HEAD + git -C $etDir submodule update --init --recursive --depth 1 + + # Drop sentry-native from the vcpkg manifest. We configure with + # -DDISABLE_TELEMETRY=ON so ET never references Sentry, but vcpkg's manifest + # mode would still force-build it (and crashpad). Removing it avoids an + # unused heavy dependency and speeds up the build. + $manifest = Join-Path $etDir "vcpkg.json" + if (-not (Select-String -Path $manifest -Pattern '"sentry-native"' -Quiet)) { + throw "sentry-native not in vcpkg.json (ET manifest changed?)" + } + (Get-Content $manifest) | Where-Object { $_ -notmatch '"sentry-native"' } | Set-Content $manifest + + # Build only the Release halves of the vcpkg deps (skip Debug) to roughly + # halve build time, via an overlay triplet mirroring x64-windows-static but + # forcing release-only. + $overlay = Join-Path $work "vcpkg-overlay-triplets" + New-Item -ItemType Directory -Force -Path $overlay | Out-Null + $srcTriplet = Join-Path $etDir "external\vcpkg\triplets\x64-windows-static.cmake" + if (-not (Test-Path $srcTriplet)) { + $srcTriplet = Join-Path $etDir "external\vcpkg\triplets\community\x64-windows-static.cmake" + } + if (-not (Test-Path $srcTriplet)) { throw "vcpkg triplet x64-windows-static.cmake not found" } + Copy-Item $srcTriplet (Join-Path $overlay "x64-windows-static.cmake") + Add-Content -Path (Join-Path $overlay "x64-windows-static.cmake") -Value 'set(VCPKG_BUILD_TYPE release)' + $env:VCPKG_OVERLAY_TRIPLETS = $overlay + + & (Join-Path $etDir "external\vcpkg\bootstrap-vcpkg.bat") -disableMetrics + + $buildDir = Join-Path $etDir "build" + cmake -S $etDir -B $buildDir ` + -GNinja ` + -DCMAKE_BUILD_TYPE=RelWithDebInfo ` + -DDISABLE_TELEMETRY=ON ` + -DVCPKG_TARGET_TRIPLET=x64-windows-static + if ($LASTEXITCODE -ne 0) { throw "cmake configure failed" } + + cmake --build $buildDir --target et + if ($LASTEXITCODE -ne 0) { throw "cmake build failed" } + + $bundleDir = Join-Path $work "win32-x64-bundle" + New-Item -ItemType Directory -Force -Path $bundleDir | Out-Null + $srcExe = Join-Path $buildDir "et.exe" + if (-not (Test-Path $srcExe)) { $srcExe = Join-Path $buildDir "RelWithDebInfo\et.exe" } + Copy-Item $srcExe (Join-Path $bundleDir "et.exe") + + # Report any non-system DLL imports (informational; a static build should + # only import the in-box Windows DLLs). + Write-Host "--- et.exe built ---" + Get-Item (Join-Path $bundleDir "et.exe") | Format-List Name, Length + + $tgz = Join-Path $env:OUT_DIR "et-win32-x64.tar.gz" + # Windows ships bsdtar as tar.exe. + tar -czf $tgz -C $bundleDir "et.exe" + if ($LASTEXITCODE -ne 0) { throw "tar failed" } + + $hash = (Get-FileHash -Algorithm SHA256 $tgz).Hash.ToLower() + $sumLine = "$hash et-win32-x64.tar.gz" + Set-Content -Path (Join-Path $env:OUT_DIR "et-win32-x64.tar.gz.sha256") -Value $sumLine -NoNewline + Write-Host $sumLine +} +finally { + Remove-Item -Recurse -Force $work -ErrorAction SilentlyContinue +}