Skip to content

Commit

Permalink
Apple Silicon macOS test/builds (#3077)
Browse files Browse the repository at this point in the history
  • Loading branch information
philrz authored May 25, 2024
1 parent 54f5b29 commit cde7ad8
Show file tree
Hide file tree
Showing 13 changed files with 974 additions and 321 deletions.
4 changes: 2 additions & 2 deletions .github/actions/build-zui/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ runs:
env:
GH_TOKEN: ${{ inputs.gh_token }}
APPLE_ID: ${{ inputs.apple_id }}
APPLE_ID_PASSWORD: ${{ inputs.apple_id_password }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ inputs.apple_id_password }}
APPLE_TEAM_ID: ${{ inputs.apple_team_id }}
CODE_SIGN_SCRIPT_PATH: ${{ github.workspace }}/esigner-codesign/dist/index.js
INPUT_FILE_PATH: ${{ steps.paths.outputs.artifact }}
Expand All @@ -87,5 +87,5 @@ runs:
- name: Check notorization with gatekeeper
if: runner.os == 'macOS'
run: |
spctl --assess --type execute --verbose --ignore-cache --no-cache dist/apps/zui/mac/*.app
spctl --assess --type execute --verbose --ignore-cache --no-cache dist/apps/zui/mac*/*.app
shell: bash
3 changes: 2 additions & 1 deletion .github/actions/setup-zui/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ runs:
- name: Install Node
uses: actions/setup-node@v3
with:
cache: yarn
# Caching is disabled because it resulted in getting amd64 Zed binaries
# on arm64 builds. See https://github.com/actions/setup-node/issues/1008.
node-version-file: .node-version

- name: Cache NextJS Artifacts
Expand Down
13 changes: 12 additions & 1 deletion .github/actions/upload-build-artifacts/action.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
name: Upload Build Artifacts
description: Upload artifacts for each platform
inputs:
gh_token:
required: true

runs:
using: 'composite'
steps:
- uses: actions/upload-artifact@v3
with:
name: Mac Artifact
name: Mac Artifact (${{ runner.arch }})
path: dist/apps/zui/*.dmg

- name: Merge latest-mac.yml Mac release files for x64/arm64
if: runner.os == 'macOS'
run: |
node apps/zui/scripts/merge-mac-release-files.mjs
env:
GH_TOKEN: ${{ inputs.gh_token }}
shell: bash

- uses: actions/upload-artifact@v3
with:
name: Windows Artifact
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/build-insiders.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ jobs:
needs: check_latest
strategy:
matrix:
platform: [windows-2019, macos-12, ubuntu-20.04]
# macos-12 is is Intel-based (x64), macos-14 is Apple Silicon (arm64)
platform: [windows-2019, macos-12, macos-14, ubuntu-20.04]

runs-on: ${{ matrix.platform }}
steps:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ jobs:
release:
strategy:
matrix:
platform: [macos-12, ubuntu-20.04, windows-2019]
# macos-12 is is Intel-based (x64), macos-14 is Apple Silicon (arm64)
platform: [macos-12, macos-14, ubuntu-20.04, windows-2019]
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout Zui
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-12, ubuntu-20.04, windows-2019]
# macos-12 is is Intel-based (x64), macos-14 is Apple Silicon (arm64)
os: [macos-12, macos-14, ubuntu-20.04, windows-2019]
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v3
Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/release-insiders.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ jobs:
if: ${{ needs.check_latest.outputs.latest_sha != github.sha }}
strategy:
matrix:
platform: [windows-2019, macos-12, ubuntu-20.04]
# macos-12 is is Intel-based (x64), macos-14 is Apple Silicon (arm64)
platform: [windows-2019, macos-12, macos-14, ubuntu-20.04]

runs-on: ${{ matrix.platform }}
steps:
Expand Down Expand Up @@ -67,6 +68,14 @@ jobs:
cert_p12: ${{ secrets.APPLE_DEVELOPER_ID_CERT_P12_BASE64 }}
cert_passphrase: ${{ secrets.APPLE_DEVELOPER_ID_CERT_PASSPHRASE }}

- name: Merge latest-mac.yml Mac release files for x64/arm64
if: runner.os == 'macOS'
run: |
node apps/zui/scripts/merge-mac-release-files.mjs
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
shell: bash

- name: Inform Slack users of failure
uses: tiloio/[email protected]
if: ${{ failure() }}
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ jobs:
release:
strategy:
matrix:
platform: [macos-12, ubuntu-20.04, windows-2019]
# macos-12 is is Intel-based (x64), macos-14 is Apple Silicon (arm64)
platform: [macos-12, macos-14, ubuntu-20.04, windows-2019]
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout Zui
Expand Down Expand Up @@ -38,3 +39,5 @@ jobs:

- name: Upload Artifacts
uses: ./.github/actions/upload-build-artifacts
with:
gh_token: ${{ secrets.GITHUB_TOKEN }}
8 changes: 8 additions & 0 deletions apps/zui/darwin.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>
6 changes: 5 additions & 1 deletion apps/zui/electron-builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@
"sign": "./scripts/sign.js"
},
"linux": {"target": ["deb", "rpm"]},
"mac": {
"entitlements": "darwin.plist",
"notarize": {"teamId": "2DBXHXV7KJ"},
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"rpm": {"depends": ["openssl"]},
"deb": {"depends": ["openssl"]},
"nsis": {"oneClick": false, "perMachine": false},
"forceCodeSigning": true,
"afterSign": "electron-builder-notarize",
"publish": {
"provider": "github"
},
Expand Down
5 changes: 3 additions & 2 deletions apps/zui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@
"debut-css": "^0.7.0",
"decompress": "^4.2.1",
"electron": "30.0.1",
"electron-builder": "^23.6.0",
"electron-builder-notarize": "^1.2.0",
"electron-builder": "^24.13.3",
"electron-devtools-assembler": "^1.2.0",
"electron-dl": "^3.0.1",
"electron-localshortcut": "^3.2.1",
Expand All @@ -110,6 +109,7 @@
"jest": "^28.0.0",
"jest-css-modules-transform": "^4.4.2",
"jest-environment-jsdom": "^28.0.0",
"js-yaml": "^4.1.0",
"jwt-decode": "^3.1.2",
"lint-staged": "^12.1.5",
"livereload": "^0.9.1",
Expand All @@ -123,6 +123,7 @@
"node-fetch": "^2.6.1",
"nodemon": "^2.0.22",
"npm-run-all": "^4.1.5",
"octokit": "^4.0.2",
"ohm-js": "^17.0.4",
"on-idle": "^3.1.4",
"polished": "^3.6.5",
Expand Down
196 changes: 196 additions & 0 deletions apps/zui/scripts/merge-mac-release-files.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// Based on https://github.com/electron-userland/electron-builder/issues/5592#issuecomment-2004803764
import { Octokit } from "octokit"
import pkg from '../package.json' assert {type: 'json'}
import { TextDecoder } from 'node:util'
import fs from 'node:fs'
import { Readable } from 'node:stream'
import { Buffer } from 'node:buffer'
import yaml from 'js-yaml'

const token = process.env.GH_TOKEN

const client = new Octokit({
auth: token
})

// These are derived from settings in package.json so the script will work on
// both regular Zui and Zui Insiders.
const OWNER = pkg.repository.split('/')[3]
const REPO = pkg.repository.split('/')[4]
const PRODUCT_NAME= pkg.productName.replaceAll(' ', '-')
const URL = `/repos/${OWNER}/${REPO}/releases`
const VERSION = pkg.version
const RELEASE_NAME = (PRODUCT_NAME == 'Zui') ? 'v' + VERSION : VERSION
const FILE_NAME = 'latest-mac.yml'
const LOCAL_FILE_PATH = `dist/apps/zui/${FILE_NAME}`

const mergeFiles = (intel, arm) => {
const intelObject = yaml.load(intel)
const armObject = yaml.load(arm)

const mergedObject = {
...intelObject,
files: [...intelObject.files, ...armObject.files]
}

// avoids moving the sha512 checksum into its own line
const dumpOptions = { lineWidth: -1 }

return yaml.dump(mergedObject, dumpOptions)
}

const getPlatformFromLatestMacYml = (content) => {
const intelRe = `${PRODUCT_NAME}-${VERSION}-x64.dmg`
const armRe = `${PRODUCT_NAME}-${VERSION}-arm64.dmg`
const isIntel = content.includes(intelRe)
const isArm = content.includes(armRe)

if (isIntel && isArm) return 'both'
if (isIntel && !isArm) return 'intel'
if (!isIntel && isArm) return 'arm'

return 'none'
}

(async () => {
const allReleases = await client.request(`GET ${URL}`)
const currentRelease = allReleases.data.find(release => {
return release.name === RELEASE_NAME
})

if (!currentRelease) {
console.log('No release found. Skipping merge')
return
}
console.log('Release found')

if (!fs.existsSync(LOCAL_FILE_PATH)) {
console.log(`[local] could not find ${FILE_NAME}. Skipping merge`)
return
}
console.log(`[local] ${FILE_NAME} found`)

const localLatestMacYmlContent = fs.readFileSync(LOCAL_FILE_PATH, { encoding: 'utf8' })

const localPlatform = getPlatformFromLatestMacYml(localLatestMacYmlContent)

if (localPlatform === 'none' || localPlatform === 'both') {
console.log(`[local] ${FILE_NAME} invalid. Platform: ${localPlatform}. Skipping merge`)
return
}
console.log(`[local] ${FILE_NAME} valid: Platform: ${localPlatform}`)

const localPlatformPresentRemotely = currentRelease.assets.find(asset => asset.name === `latest-mac-${localPlatform}.yml`)

if (localPlatformPresentRemotely) {
try {
await client.request(`DELETE ${URL}/assets/${localPlatformPresentRemotely.id}`)
console.log(`[remote] deleted latest-mac-${localPlatform}.yml`)
} catch(e) {
console.log(`[remote] error deleting latest-mac-${localPlatform}.yml. Skipping merge`)
console.log(e)
return
}
}

const uploadUrl = currentRelease.upload_url
try {
await client.rest.repos.uploadReleaseAsset({
url: uploadUrl,
headers: {
'content-type': 'application/octet-stream',
'content-length': Buffer.byteLength(localLatestMacYmlContent),
},
name: `latest-mac-${localPlatform}.yml`,
data: Readable.from(localLatestMacYmlContent),
})
console.log(`[remote] latest-mac-${localPlatform}.yml uploaded`)
} catch(e) {
console.log(`[remote] error uploading latest-mac-${localPlatform}.yml. Skipping merge`)
console.log(e)
return
}

const remotePlatform = localPlatform === 'intel' ? 'arm' : 'intel'

const remotePlatformFileExists = currentRelease.assets.find(asset => asset.name === `latest-mac-${remotePlatform}.yml`)

if (!remotePlatformFileExists) {
console.log(`[remote] latest-mac-${remotePlatform}.yml does not exist. Skipping merge`)
return
}
console.log(`[remote] latest-mac-${remotePlatform}.yml found`)

let remotePlatformFile

try {
remotePlatformFile = await client.request(`GET ${URL}/assets/${remotePlatformFileExists.id}`, {
headers: {
accept: 'application/octet-stream'
}
})
console.log(`[remote] latest-mac-${remotePlatform}.yml downloaded`)
} catch(e) {
console.log(`[remote] error downloading latest-mac-${remotePlatform}.yml. Skipping merge`)
console.log(e)
return
}

const remoteLatestMacYmlContent = new TextDecoder().decode(remotePlatformFile.data)

const originalAsset = currentRelease.assets.find(asset => asset.name === FILE_NAME)

if (!originalAsset) {
console.log(`[remote] ${FILE_NAME} not found. Skipping merge`)
return
}
console.log(`[remote] ${FILE_NAME} found`)

try {
await client.request(`DELETE ${URL}/assets/${originalAsset.id}`)
console.log(`[remote] deleted ${FILE_NAME}`)
} catch(e) {
console.log(`[remote] error deleting ${FILE_NAME}. Skipping merge`)
console.log(e)
return
}

const mergedContent = remotePlatform === 'intel' ? mergeFiles(remoteLatestMacYmlContent, localLatestMacYmlContent) : mergeFiles(localLatestMacYmlContent, remoteLatestMacYmlContent)

try {
await client.rest.repos.uploadReleaseAsset({
url: uploadUrl,
headers: {
'content-type': 'application/octet-stream',
'content-length': Buffer.byteLength(mergedContent),
},
name: FILE_NAME,
data: Readable.from(mergedContent),
})
console.log(`[remote] uploaded merged ${FILE_NAME}`)
} catch(e) {
console.log(`[remote] error uploading merged ${FILE_NAME}. Skipping merge`)
console.log(e)
return
}

// cleanup
const updatedRelease = await client.request(`GET ${URL}`)
const updatedCurrentRelease = updatedRelease.data.find(release => release.name === RELEASE_NAME)

const assetsToClean = updatedCurrentRelease.assets.filter(asset => {
return asset.name === `latest-mac-arm.yml` || asset.name === `latest-mac-intel.yml`
})

for (const assetToClean of assetsToClean) {
try {
await client.request(`DELETE ${URL}/assets/${assetToClean.id}`)
console.log(`[remote:cleanup] deleted ${assetToClean.name}`)
} catch(e) {
console.log(`[remote:cleanup] error deleting ${assetToClean.name}`)
console.log(e)
}
}

console.log('Merge complete')
})()
Loading

0 comments on commit cde7ad8

Please sign in to comment.