Files
kid aa92d0e676 初始提交:terminalX 可运行态(M0/M1/M1.5 已真机验证)
- M0: libghostty SSH 终端(渲染/输入/连接)+ 白屏修复(OutputGate) + 会话状态机/自动重连
- M1: tsnet 用户态组网 + SSH-over-tsnet(fd 桥),shell 级真机验证;R5(Go+gvisor+C+++Swift 同进程) retire
- M1.5: tmux -CC 原生 tab(MVP)
- 结构: packages/(TXCore·TXTransport), apps/TerminalX, vendor/(libghostty-spm/libssh2/mbedtls/tsnet-bridge), artifacts/
- 文档: CLAUDE.md + docs/HANDOFF.md(新会话入口)
- 环境: 认证代理→依赖 vendor 本地化;Go 在 ~/.local/go;仅模拟器/未签名
- 待续: M2 mosh, tmux 多 pane, M4 安全(host key/SE)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 10:20:46 +08:00

307 lines
11 KiB
YAML

name: Build GhosttyKit
on:
workflow_dispatch:
inputs:
package_version:
description: "GhosttyKit package version to publish, for example 1.3.1."
required: true
type: string
schedule:
- cron: "23 6 * * 1"
permissions:
contents: write
concurrency:
group: build-${{ github.ref_name || 'scheduled' }}
cancel-in-progress: true
jobs:
prepare:
runs-on: macos-15
outputs:
build_needed: ${{ steps.release.outputs.build_needed }}
ghostty_ref: ${{ steps.release.outputs.ghostty_ref }}
resolved_sha: ${{ steps.release.outputs.resolved_sha }}
package_version: ${{ steps.release.outputs.package_version }}
storage_tag: ${{ steps.release.outputs.storage_tag }}
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Resolve Release
id: release
run: |
EVENT="${{ github.event_name }}"
INPUT_PACKAGE_VERSION="${{ github.event.inputs.package_version }}"
GHOSTTY_REF=$(tr -d '[:space:]' < Ghostty.ref)
if ! echo "$GHOSTTY_REF" | grep -Eq '^[0-9a-f]{40}$'; then
echo "[!] Ghostty.ref must contain one full lowercase commit sha"
exit 1
fi
git fetch --tags origin
LATEST_PACKAGE_VERSION=$(
git tag --list |
python3 -c 'import re, sys; versions = [tuple(int(part) for part in tag.split(".")) for tag in (line.strip() for line in sys.stdin) if re.fullmatch(r"\d+\.\d+\.\d+", tag)]; print(".".join(map(str, max(versions))), end="") if versions else None'
)
case "$EVENT" in
workflow_dispatch)
PACKAGE_VERSION="$INPUT_PACKAGE_VERSION"
;;
schedule)
PACKAGE_VERSION=$(
python3 -c 'import sys; version = sys.argv[1]; major, minor, patch = map(int, version.split(".")) if version else (1, 0, -1); print(f"{major}.{minor}.{patch + 1}", end="")' "$LATEST_PACKAGE_VERSION"
)
;;
*)
echo "[!] unsupported event: $EVENT"
exit 1
;;
esac
if ! echo "$PACKAGE_VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "[!] package_version must be semantic, for example 1.3.1: $PACKAGE_VERSION"
exit 1
fi
if [ -n "$LATEST_PACKAGE_VERSION" ] && ! python3 -c 'import sys; latest = tuple(map(int, sys.argv[1].split("."))); requested = tuple(map(int, sys.argv[2].split("."))); raise SystemExit(0 if requested > latest else 1)' "$LATEST_PACKAGE_VERSION" "$PACKAGE_VERSION"; then
echo "[!] package_version must be newer than $LATEST_PACKAGE_VERSION: $PACKAGE_VERSION"
exit 1
fi
STORAGE_TAG="storage.$PACKAGE_VERSION"
if git rev-parse "refs/tags/$PACKAGE_VERSION" >/dev/null 2>&1; then
echo "[!] package tag already exists: $PACKAGE_VERSION"
exit 1
fi
if git rev-parse "refs/tags/$STORAGE_TAG" >/dev/null 2>&1; then
echo "[!] storage tag already exists: $STORAGE_TAG"
exit 1
fi
if [ "$EVENT" = "schedule" ] && [ -n "$LATEST_PACKAGE_VERSION" ] && [ "$(git rev-list -n 1 "$LATEST_PACKAGE_VERSION")" = "$(git rev-parse HEAD)" ]; then
echo "[*] main matches package tag $LATEST_PACKAGE_VERSION, skipping scheduled release"
echo "build_needed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
RESOLVED_SHA=$(gh api "repos/ghostty-org/ghostty/commits/$GHOSTTY_REF" --jq '.sha')
if [ "$RESOLVED_SHA" != "$GHOSTTY_REF" ]; then
echo "[!] pinned Ghostty commit did not resolve exactly: $GHOSTTY_REF"
exit 1
fi
echo "[*] package_version=$PACKAGE_VERSION ghostty_ref=$GHOSTTY_REF"
echo "build_needed=true" >> "$GITHUB_OUTPUT"
echo "ghostty_ref=$GHOSTTY_REF" >> "$GITHUB_OUTPUT"
echo "resolved_sha=$RESOLVED_SHA" >> "$GITHUB_OUTPUT"
echo "package_version=$PACKAGE_VERSION" >> "$GITHUB_OUTPUT"
echo "storage_tag=$STORAGE_TAG" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-target:
needs: prepare
if: needs.prepare.outputs.build_needed == 'true'
runs-on: macos-15
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-macos
variant: macosx
- target: arm64e-macos
variant: macosx
cpu: apple_a12
- target: x86_64-macos
variant: macosx
- target: aarch64-ios
variant: iphoneos
- target: arm64e-ios
variant: iphoneos
cpu: apple_a12
- target: aarch64-ios-simulator
variant: iphonesimulator
cpu: apple_a17
- target: x86_64-ios-simulator
variant: iphonesimulator
- target: aarch64-ios-macabi
variant: maccatalyst
cpu: apple_a17
- target: arm64e-ios-macabi
variant: maccatalyst
cpu: apple_a17
- target: x86_64-ios-macabi
variant: maccatalyst
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Zig
uses: mlugg/setup-zig@v2
with:
version: 0.15.2
- name: Clone Ghostty
run: |
git clone https://github.com/ghostty-org/ghostty ./References/ghostty-upstream
git -C ./References/ghostty-upstream checkout "${{ needs.prepare.outputs.ghostty_ref }}"
- name: Build Target
run: |
export ZIG_CPU="${{ matrix.cpu }}"
./Script/build-ghostty.sh \
"$PWD/References/ghostty-upstream" \
"${{ matrix.target }}" \
"$PWD/build/out"
- name: Pack Artifact
run: tar -czf "target--${{ matrix.variant }}--${{ matrix.target }}.tar.gz" -C ./build/out .
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: target--${{ matrix.variant }}--${{ matrix.target }}
path: target--${{ matrix.variant }}--${{ matrix.target }}.tar.gz
compression-level: 0
retention-days: 1
merge-test-release:
needs: [prepare, build-target]
if: needs.prepare.outputs.build_needed == 'true'
runs-on: macos-26
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Xcode
run: sudo xcode-select -s /Applications/Xcode.app
- name: Download All Artifacts
uses: actions/download-artifact@v4
with:
pattern: target-*
path: ./build/tarballs
merge-multiple: true
- name: Assemble Variants
run: |
mkdir -p ./build/targets ./build/dest
for tarball in ./build/tarballs/target--*.tar.gz; do
BASENAME=$(basename "$tarball" .tar.gz)
VARIANT=$(echo "$BASENAME" | awk -F'--' '{print $2}')
TARGET=$(echo "$BASENAME" | awk -F'--' '{print $3}')
echo "[*] unpacking $BASENAME → variant=$VARIANT target=$TARGET"
mkdir -p "./build/targets/$VARIANT/$TARGET"
tar -xzf "$tarball" -C "./build/targets/$VARIANT/$TARGET"
done
for variant_dir in ./build/targets/*/; do
VARIANT=$(basename "$variant_dir")
mkdir -p "./build/dest/$VARIANT/lib" "./build/dest/$VARIANT/include"
LIBS=()
FIRST_HEADERS=
for target_dir in "$variant_dir"*/; do
LIBS+=("$target_dir/lib/libghostty.a")
if [ -z "$FIRST_HEADERS" ]; then
FIRST_HEADERS="$target_dir/include"
fi
done
cp -R "$FIRST_HEADERS/." "./build/dest/$VARIANT/include/"
if [ "${#LIBS[@]}" -eq 1 ]; then
cp "${LIBS[0]}" "./build/dest/$VARIANT/lib/libghostty.a"
echo "[*] $VARIANT: single arch"
else
lipo -create "${LIBS[@]}" -output "./build/dest/$VARIANT/lib/libghostty.a"
echo "[*] $VARIANT: lipo ${#LIBS[@]} archs"
fi
done
- name: Merge XCFramework
run: |
./Script/merge-xcframework.sh \
"$PWD/build/dest" \
"$PWD/BinaryTarget/GhosttyKit.xcframework" \
"$PWD/build/GhosttyKit.xcframework.zip"
- name: Test XCFramework Consumer
run: ./Script/test-xcframework-consumer.sh "$PWD/BinaryTarget/GhosttyKit.xcframework"
- name: Use Local Binary Target
run: cp Package.local.swift Package.swift
- name: Build Package Matrix
run: ./Script/test.sh
- name: Run Swift Tests
run: swift test
- name: Prepare Release
run: |
STORAGE_TAG="${{ needs.prepare.outputs.storage_tag }}"
DOWNLOAD_URL="https://github.com/${{ github.repository }}/releases/download/$STORAGE_TAG/GhosttyKit.xcframework.zip"
echo "[*] predicted download url: $DOWNLOAD_URL"
mv ./build/GhosttyKit.xcframework.zip /tmp/GhosttyKit.xcframework.zip
git clean -fdx -f
git reset --hard
mv /tmp/GhosttyKit.xcframework.zip ./GhosttyKit.xcframework.zip
./Script/build-manifest.sh GhosttyKit.xcframework.zip "$DOWNLOAD_URL"
- name: Commit & Push
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "chore: autopublish $(date -u +%Y-%m-%dT%H:%M:%SZ)" || echo "No changes to commit"
git push
- name: Create Storage Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.prepare.outputs.storage_tag }}
target_commitish: ${{ github.ref_name }}
body: |
GhosttyKit XCFramework
Package version: ${{ needs.prepare.outputs.package_version }}
Ghostty commit: ${{ needs.prepare.outputs.resolved_sha }}
draft: false
prerelease: false
files: |
GhosttyKit.xcframework.zip
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.prepare.outputs.package_version }}
target_commitish: ${{ github.ref_name }}
body: |
GhosttyKit ${{ needs.prepare.outputs.package_version }}
Package version: ${{ needs.prepare.outputs.package_version }}
Ghostty commit: ${{ needs.prepare.outputs.resolved_sha }}
draft: false
prerelease: false
- name: Verify Release Consistency
run: ./Script/verify-release.sh "${{ needs.prepare.outputs.package_version }}" "${{ needs.prepare.outputs.storage_tag }}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}