From 3503327adde518d60253231edeaa1015dbdb2f02 Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Wed, 28 Oct 2026 10:07:50 +0800 Subject: [PATCH] Add native Apple Silicon build and artifact workflow --- .github/workflows/apple-silicon.yml | 48 ++++++++++++++++++ .gitignore | 4 ++ desktop/README.md | 27 ++++++++++ desktop/vue.config.js | 1 + scripts/build-apple-silicon.sh | 77 +++++++++++++++++++++++++++++ 5 files changed, 157 insertions(+) create mode 100644 .github/workflows/apple-silicon.yml create mode 100755 scripts/build-apple-silicon.sh diff --git a/.github/workflows/apple-silicon.yml b/.github/workflows/apple-silicon.yml new file mode 100644 index 0000000..3a610f0 --- /dev/null +++ b/.github/workflows/apple-silicon.yml @@ -0,0 +1,48 @@ +name: Apple Silicon + +on: + push: + paths: + - '.github/workflows/apple-silicon.yml' + - 'scripts/build-apple-silicon.sh' + - 'desktop/**' + - 'n-link-core/**' + - 'package.json' + - 'yarn.lock' + - 'postcss.config.js' + - 'LICENSE' + pull_request: + paths: + - '.github/workflows/apple-silicon.yml' + - 'scripts/build-apple-silicon.sh' + - 'desktop/**' + - 'n-link-core/**' + - 'package.json' + - 'yarn.lock' + - 'postcss.config.js' + - 'LICENSE' + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + runs-on: macos-15 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Install Rust toolchain + run: rustup toolchain install 1.77.2 --profile minimal + - name: Build native app and archive + run: ./scripts/build-apple-silicon.sh + - uses: actions/upload-artifact@v4 + with: + name: n-link-macos-arm64 + path: | + releases/N-Link_*_macOS_arm64.zip + releases/N-Link_*_macOS_arm64.zip.sha256 + if-no-files-found: error diff --git a/.gitignore b/.gitignore index 5c3aa38..68a3863 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,7 @@ pnpm-debug.log* *.sw? *.tns + +# Local macOS release artifacts +/releases/ +/desktop/dist/ diff --git a/desktop/README.md b/desktop/README.md index 353e14d..aa67a39 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -2,6 +2,33 @@ Free, cross-platform, CX-II compatible computer linking program for the TI-Nspire +## Native Apple Silicon build + +From the repository root, run: + +```sh +rustup toolchain install 1.77.2 --profile minimal +./scripts/build-apple-silicon.sh +``` + +Requires Apple's Command Line Tools, Node.js/npm, Python 3, and Rust installed +through rustup. Node.js 22 is used in CI. The script selects Rust 1.77.2 for +this build to support the project's 2021 dependencies, and uses the existing Yarn and Cargo +lockfiles, skips the old Tauri CLI's Intel-only installer, builds the frontend, +and compiles the backend for `aarch64-apple-darwin`. `libusb` is linked statically; +the resulting app needs only macOS system libraries. + +Outputs are `releases/N-Link.app`, `releases/N-Link_0.1.6_macOS_arm64.zip`, and +the ZIP's SHA-256 checksum. The app is ad-hoc signed for local use; it is not +Developer ID signed or notarized. Install it by copying `N-Link.app` to +`~/Applications` and opening it. + +The deployment target is macOS 11.0; older macOS versions still need runtime +testing. `.github/workflows/apple-silicon.yml` builds on a native ARM64 runner, +checks the architecture and code signature, and uploads the release ZIP and +checksum as an Actions artifact. USB behavior needs testing on a physical Mac +with a connected calculator. + ## Project setup You'll need to install [Node.js](https://nodejs.org/en/download/package-manager), [Yarn](https://classic.yarnpkg.com/en/docs/install/) (which may be included with your installation of Node.js), and diff --git a/desktop/vue.config.js b/desktop/vue.config.js index 68f9b6a..6c4c430 100644 --- a/desktop/vue.config.js +++ b/desktop/vue.config.js @@ -1,3 +1,4 @@ module.exports = { + publicPath: process.env.TAURI_BUILD ? './' : '/', transpileDependencies: ['n-link-core'] } diff --git a/scripts/build-apple-silicon.sh b/scripts/build-apple-silicon.sh new file mode 100755 index 0000000..1c88882 --- /dev/null +++ b/scripts/build-apple-silicon.sh @@ -0,0 +1,77 @@ +#!/bin/bash +set -euo pipefail + +cd "$(dirname "$0")/.." +if [[ "$(uname -s)" != Darwin || "$(uname -m)" != arm64 ]]; then + echo "Run this script natively on an Apple Silicon Mac." >&2 + exit 1 +fi +export PATH="$HOME/.cargo/bin:$PATH" +export MACOSX_DEPLOYMENT_TARGET=11.0 + +# Keep the legacy dependencies buildable without changing other build targets. +rust_toolchain=1.77.2 +if ! rustup run "$rust_toolchain" rustc --version >/dev/null 2>&1; then + echo "Install the build toolchain with: rustup toolchain install $rust_toolchain --profile minimal" >&2 + exit 1 +fi + +# Skip the legacy Tauri CLI's installer, which fetches an Intel executable. +npm exec --yes --package yarn@1.22.22 -- yarn install \ + --frozen-lockfile --ignore-engines --ignore-scripts --non-interactive +( + cd desktop + TAURI_BUILD=true NODE_OPTIONS="${NODE_OPTIONS:-} --openssl-legacy-provider" \ + node ../node_modules/@vue/cli-service/bin/vue-cli-service.js build +) +( + cd desktop/src-tauri + cargo +"$rust_toolchain" build --release --locked --target aarch64-apple-darwin +) + +app="$PWD/releases/N-Link.app" +mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources" +cp desktop/src-tauri/target/aarch64-apple-darwin/release/n-link "$app/Contents/MacOS/n-link" +cp desktop/src-tauri/icons/icon.icns "$app/Contents/Resources/icon.icns" +cp LICENSE "$app/Contents/Resources/LICENSE" +python3 - "$app/Contents/Info.plist" <<'PY' +import json +import os +import plistlib +import sys +with open('desktop/package.json') as f: + version = json.load(f)['version'] +with open('desktop/src-tauri/tauri.conf.json') as f: + bundle = json.load(f)['tauri']['bundle'] +with open(sys.argv[1], 'wb') as f: + plistlib.dump({ + 'CFBundleDevelopmentRegion': 'en', + 'CFBundleDisplayName': 'N-Link', + 'CFBundleExecutable': 'n-link', + 'CFBundleIconFile': 'icon.icns', + 'CFBundleIdentifier': bundle['identifier'], + 'CFBundleInfoDictionaryVersion': '6.0', + 'CFBundleName': 'N-Link', + 'CFBundlePackageType': 'APPL', + 'CFBundleShortVersionString': version, + 'CFBundleVersion': version, + 'LSApplicationCategoryType': 'public.app-category.utilities', + 'LSMinimumSystemVersion': os.environ['MACOSX_DEPLOYMENT_TARGET'], + 'NSHighResolutionCapable': True, + 'NSHumanReadableCopyright': bundle['copyright'], + 'NSPrincipalClass': 'NSApplication', + }, f) +PY +codesign --force --sign - --timestamp=none "$app" +codesign --verify --deep --strict "$app" +[[ "$(lipo -archs "$app/Contents/MacOS/n-link")" == arm64 ]] +"$app/Contents/MacOS/n-link" --version +version="$(node -p 'require("./desktop/package.json").version')" +archive="$PWD/releases/N-Link_${version}_macOS_arm64.zip" +ditto -c -k --sequesterRsrc --keepParent "$app" "$archive" +( + cd releases + shasum -a 256 "$(basename "$archive")" > "$(basename "$archive").sha256" +) +echo "Built: $app" +echo "Archive: $archive"