-
Notifications
You must be signed in to change notification settings - Fork 132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Apple Silicon macOS test/builds #3077
Changes from 28 commits
5fa00f3
17d65db
f6467ca
5f57378
ff6e8a2
5f50914
ecfbff4
d8fbdac
c5fefd1
f20c316
84fa204
021eb54
4f127e6
95c542e
85f4ace
019735b
eb9468f
ef013d9
4d7fa77
c60f7e3
78fb254
b343b9c
f5a34ed
2cfd313
03eb879
501c79b
465259c
c575d52
61538de
b3a0694
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 }} | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The wildcard is needed here because the Apple Silicon builds end up in a subdirectory |
||
shell: bash |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,10 +8,13 @@ runs: | |
with: | ||
go-version: '1.21' | ||
|
||
# Caching is disabled because it resulted in getting x86_64 Zed binaries | ||
# on arm64 builds. See: | ||
# - https://github.com/actions/setup-node/issues/1008 | ||
- name: Install Node | ||
uses: actions/setup-node@v3 | ||
with: | ||
cache: yarn | ||
# cache: yarn | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In an early test iteration, @mattnibs spotted that the
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
node-version-file: .node-version | ||
|
||
- name: Cache NextJS Artifacts | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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: | ||
|
@@ -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() }} | ||
|
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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,11 +10,15 @@ | |
"sign": "./scripts/sign.js" | ||
}, | ||
"linux": {"target": ["deb", "rpm"]}, | ||
"mac": { | ||
"entitlements": "darwin.plist", | ||
"notarize": {"teamId": "2DBXHXV7KJ"}, | ||
"artifactName": "${productName}-${version}-${arch}.${ext}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At first when using |
||
}, | ||
"rpm": {"depends": ["openssl"]}, | ||
"deb": {"depends": ["openssl"]}, | ||
"nsis": {"oneClick": false, "perMachine": false}, | ||
"forceCodeSigning": true, | ||
"afterSign": "electron-builder-notarize", | ||
"publish": { | ||
"provider": "github" | ||
}, | ||
|
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') | ||
})() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we transition from the separate
electron-builder-notarize
to leveraging the notarization support that's inelectron-builder
, the name of this environment variable changes but everything else stays the same.