diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 5769b89..93686ac 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -98,6 +98,33 @@ jobs: - name: Install dependencies run: bash scripts/ci/github-install-deps.sh + - name: Install Rosetta (Apple Silicon) + if: matrix.label == 'macos' + run: /usr/sbin/softwareupdate --install-rosetta --agree-to-license || true + + - name: Set up Python x64 for cx_Freeze universal slice + id: python_x64 + if: matrix.label == 'macos' + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 + with: + python-version: ${{ env.PYTHON_VERSION }} + architecture: x64 + update-environment: false + + - name: Export PYTHON_CMD_X64 for mac universal build + if: matrix.label == 'macos' + run: echo "PYTHON_CMD_X64=${{ steps.python_x64.outputs.python-path }}" >> "$GITHUB_ENV" + + - name: Install project deps into x64 Python (mac universal cx_Freeze) + if: matrix.label == 'macos' + env: + PY_X64: ${{ steps.python_x64.outputs.python-path }} + run: | + set -euo pipefail + "$PY_X64" -m pip install -U pip setuptools wheel + "$PY_X64" -m pip install "cx-freeze>=7.0.0" + "$PY_X64" -m pip install -e . + - name: Build distributables run: bash "${{ matrix.build_script }}" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3e858c7..7d8854a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,5 +76,32 @@ jobs: - name: Install dependencies run: bash scripts/ci/github-install-deps.sh + - name: Install Rosetta (Apple Silicon) + if: matrix.label == 'macos' + run: /usr/sbin/softwareupdate --install-rosetta --agree-to-license || true + + - name: Set up Python x64 for cx_Freeze universal slice + id: python_x64 + if: matrix.label == 'macos' + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 + with: + python-version: ${{ env.PYTHON_VERSION }} + architecture: x64 + update-environment: false + + - name: Export PYTHON_CMD_X64 for mac universal build + if: matrix.label == 'macos' + run: echo "PYTHON_CMD_X64=${{ steps.python_x64.outputs.python-path }}" >> "$GITHUB_ENV" + + - name: Install project deps into x64 Python (mac universal cx_Freeze) + if: matrix.label == 'macos' + env: + PY_X64: ${{ steps.python_x64.outputs.python-path }} + run: | + set -euo pipefail + "$PY_X64" -m pip install -U pip setuptools wheel + "$PY_X64" -m pip install "cx-freeze>=7.0.0" + "$PY_X64" -m pip install -e . + - name: Build distributables run: bash "${{ matrix.build_script }}" diff --git a/forge.config.js b/forge.config.js index 2969b81..7d599cf 100644 --- a/forge.config.js +++ b/forge.config.js @@ -2,7 +2,13 @@ const { FusesPlugin } = require("@electron-forge/plugin-fuses"); const { FuseV1Options, FuseVersion } = require("@electron/fuses"); const platform = process.env.PLATFORM || process.platform; -const extraResourceDir = platform === "win32" || platform === "win" ? "build/exe/win32" : "build/exe/linux"; +const arch = process.env.ARCH || process.arch; +let extraResourceDir = `build/exe/linux-${arch}`; +if (platform === "win32" || platform === "win") { + extraResourceDir = `build/exe/win32-${arch}`; +} else if (platform === "darwin") { + extraResourceDir = `build/exe/darwin-${arch}`; +} module.exports = { packagerConfig: { diff --git a/package.json b/package.json index 3214c27..f88ab69 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "dist:zip": "pnpm run electron-postinstall && pnpm run build && electron-forge make --targets @electron-forge/maker-zip", "dist-prebuilt": "pnpm run electron-postinstall && pnpm run build-backend && electron-builder --publish=never", "dist:mac-arm64": "pnpm run electron-postinstall && pnpm run build && electron-builder --mac --arm64 --publish=never", - "dist:mac-universal": "pnpm run electron-postinstall && pnpm run build && electron-builder --mac --universal --publish=never", + "dist:mac-universal": "bash scripts/build-macos-universal.sh", "start": "pnpm run build && electron-forge start", "package": "pnpm run build && electron-forge package", "make": "pnpm run build && electron-forge make" @@ -138,7 +138,7 @@ }, "extraResources": [ { - "from": "build/exe/linux-${arch}", + "from": "build/exe/darwin-${arch}", "to": "backend", "filter": [ "**/*" diff --git a/scripts/build-backend.js b/scripts/build-backend.js index 22beb9d..3570e9e 100755 --- a/scripts/build-backend.js +++ b/scripts/build-backend.js @@ -46,14 +46,19 @@ try { const platform = process.env.PLATFORM || process.platform; const arch = process.env.ARCH || process.arch; const isWin = platform === "win32" || platform === "win"; + const isDarwin = platform === "darwin"; const targetName = isWin ? "ReticulumMeshChatX.exe" : "ReticulumMeshChatX"; - // Create architecture-specific build directory - const platformFolder = isWin ? "win32" : "linux"; + let platformFolder = "linux"; + if (isWin) { + platformFolder = "win32"; + } else if (isDarwin) { + platformFolder = "darwin"; + } const buildDirRelative = `build/exe/${platformFolder}-${arch}`; const buildDir = path.join(__dirname, "..", buildDirRelative); - // Allow overriding the python command (e.g., to use wine python for cross-builds) + // Allow overriding the python command const pythonCmd = process.env.PYTHON_CMD || "poetry run python"; console.log( @@ -66,12 +71,23 @@ try { CX_FREEZE_BUILD_EXE: buildDirRelative, }; - // Split pythonCmd to handle arguments like "wine python" - const cmdParts = pythonCmd.split(" "); + const cmdParts = pythonCmd.trim().split(/\s+/).filter(Boolean); const cmd = cmdParts[0]; const args = [...cmdParts.slice(1), "cx_setup.py", "build"]; - const result = spawnSync(cmd, args, { + let spawnCmd = cmd; + let spawnArgs = args; + const rosettaX64 = + isDarwin && + arch === "x64" && + process.arch === "arm64" && + !process.env.PYTHON_CMD; + if (rosettaX64) { + spawnCmd = "arch"; + spawnArgs = ["-x86_64", cmd, ...args]; + } + + const result = spawnSync(spawnCmd, spawnArgs, { stdio: "inherit", shell: false, env: env, diff --git a/scripts/build-macos-universal.sh b/scripts/build-macos-universal.sh new file mode 100644 index 0000000..ebf7f5c --- /dev/null +++ b/scripts/build-macos-universal.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Build darwin-arm64 and darwin-x64 cx_Freeze backends, then electron-builder --mac --universal. +# On Apple Silicon, the x64 backend must be built with an x86_64 Python (e.g. Homebrew in /usr/local). +# Set PYTHON_CMD_X64 to that interpreter if Poetry's default env is arm64-only. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +pnpm run electron-postinstall +pnpm run version:sync +pnpm run build-frontend +cross-env ARCH=arm64 pnpm run build-backend +if [[ -n "${PYTHON_CMD_X64:-}" ]]; then + cross-env ARCH=x64 PYTHON_CMD="$PYTHON_CMD_X64" pnpm run build-backend +else + cross-env ARCH=x64 pnpm run build-backend +fi +exec pnpm exec electron-builder --mac --universal --publish=never