- 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>
307 lines
11 KiB
YAML
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 }}
|