-
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 5 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,7 @@ jobs: | |
if: ${{ needs.check_latest.outputs.latest_sha != github.sha }} | ||
strategy: | ||
matrix: | ||
platform: [windows-2019, macos-12, ubuntu-20.04] | ||
platform: [windows-2019, macos-12, macos-14, ubuntu-20.04] | ||
|
||
runs-on: ${{ matrix.platform }} | ||
steps: | ||
|
@@ -67,6 +67,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 Intel/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,11 @@ | |
"sign": "./scripts/sign.js" | ||
}, | ||
"linux": {"target": ["deb", "rpm"]}, | ||
"mac": {"entitlements": "darwin.plist", "notarize": {"teamId": "2DBXHXV7KJ"}}, | ||
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 |
||
"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,226 @@ | ||
// Based on https://github.com/electron-userland/electron-builder/issues/5592#issuecomment-2004803764 | ||
import { Octokit } from "octokit"; | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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(' ', '-'); | ||
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. This |
||
const URL = `/repos/${OWNER}/${REPO}/releases`; | ||
const VERSION = pkg.version; | ||
const RELEASE_NAME = (PRODUCT_NAME == 'Zui') ? 'v' + VERSION : VERSION; | ||
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. Here's another coping mechanism. For all the time they've existed, Zui Insiders builds have names without the leading |
||
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, | ||
}; | ||
|
||
mergedObject.files = [ | ||
...intelObject.files, | ||
...armObject.files, | ||
]; | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const dumpOptions = { | ||
// avoids moving the sha512 checksum into its own line | ||
lineWidth: -1, | ||
}; | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const mergedString = yaml.dump(mergedObject, dumpOptions); | ||
return mergedString; | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
const getPlatformFromLatestMacYml = (content) => { | ||
const intelRe = `${PRODUCT_NAME}-${VERSION}.dmg` | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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; | ||
} else { | ||
console.log('Release found') | ||
} | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const localLatestMacYmlExists = fs.existsSync(LOCAL_FILE_PATH); | ||
|
||
if (!localLatestMacYmlExists) { | ||
console.log(`[local] could not find ${FILE_NAME}. Skipping merge`); | ||
return; | ||
} else { | ||
console.log(`[local] ${FILE_NAME} found`); | ||
} | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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; | ||
} else { | ||
console.log(`[local] ${FILE_NAME} valid: Platform: ${localPlatform}`) | ||
} | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const localPlatformPresentRemotely = currentRelease.assets.find(asset => { | ||
return 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; | ||
const localAssetStream = new Readable(); | ||
localAssetStream.push(localLatestMacYmlContent); | ||
localAssetStream.push(null); | ||
|
||
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: localAssetStream, | ||
}) | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 => { | ||
return asset.name === `latest-mac-${remotePlatform}.yml`; | ||
}) | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (!remotePlatformFileExists) { | ||
console.log(`[remote] latest-mac-${remotePlatform}.yml does not exist. Skipping merge`) | ||
return; | ||
} else { | ||
console.log(`[remote] latest-mac-${remotePlatform}.yml found`) | ||
} | ||
|
||
let remotePlatformFile = null; | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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); | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
try { | ||
const originalAsset = currentRelease.assets.find(asset => { | ||
return asset.name === FILE_NAME; | ||
}) | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (!originalAsset) { | ||
console.log(`[remote] ${FILE_NAME} not found. Skipping merge`); | ||
return; | ||
} else { | ||
console.log(`[remote] ${FILE_NAME} found`) | ||
} | ||
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. I would move this stuff out of the try block since that it was you are doing elsewhere in this file. Also you can drop the else and just console log. |
||
|
||
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); | ||
|
||
const assetStream = new Readable(); | ||
assetStream.push(mergedContent); | ||
assetStream.push(null); | ||
|
||
try { | ||
await client.rest.repos.uploadReleaseAsset({ | ||
url: uploadUrl, | ||
headers: { | ||
'content-type': 'application/octet-stream', | ||
'content-length': Buffer.byteLength(mergedContent), | ||
}, | ||
name: FILE_NAME, | ||
data: assetStream, | ||
}) | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 => { | ||
return release.name === RELEASE_NAME; | ||
}) | ||
philrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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.