Skip to content

Commit

Permalink
feat:(mac): Apple Silicon builds (arm64) (#5426)
Browse files Browse the repository at this point in the history
* Apple Silicon support: Allowing arm64 builds for Mac. Mostly removing hardcoded values and making sure user-options are utilized for platform/dmg packagers. Updated tests to target electron 11. Updated mac snapshots to include multi-arch images

* Hardcoding to skip x64 arch in template strings for Mac packager

* Adding filter to mac updater for properly identifying arm64 builds (x64 does not have a bitness prefix to filter by)

* Consolidating nodeGypRebuild logic to a single function. Updating tests

Co-authored-by: Mike Maietta <michaelmaietta@upwork.com>
  • Loading branch information
mmaietta and Mike Maietta authored Dec 14, 2020
1 parent 0dec1b8 commit a98c895
Show file tree
Hide file tree
Showing 28 changed files with 180 additions and 158 deletions.
6 changes: 3 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
- install-dep-cache-{{ checksum "yarn.lock" }}
- restore_cache:
keys:
- v-8.2.5-electron
- v-11.0.0-electron
- run:
command: node .yarn/releases/yarn-2.2.2.cjs install
- run:
Expand All @@ -25,7 +25,7 @@ jobs:
- run:
command: node .yarn/releases/yarn-2.2.2.cjs node ./test/out/helpers/downloadElectron.js
- save_cache:
key: v-8.2.5-electron
key: v-11.0.0-electron
paths:
- ~/.cache/electron

Expand All @@ -43,7 +43,7 @@ jobs:
- install-dep-cache-{{ checksum "yarn.lock" }}
- restore_cache:
keys:
- v-8.2.5-electron
- v-11.0.0-electron
# because in the build job we use circleci docker image and circleci restores cache to original user home
- run:
command: |
Expand Down
3 changes: 0 additions & 3 deletions packages/app-builder-lib/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ export class Platform {
}

const archToType = new Map()
if (this === Platform.MAC) {
archs = [Arch.x64]
}

for (const arch of (archs == null || archs.length === 0 ? [archFromString(process.arch)] : archs)) {
archToType.set(arch, type == null ? [] : (Array.isArray(type) ? type : [type]))
Expand Down
2 changes: 1 addition & 1 deletion packages/app-builder-lib/src/macPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export default class MacPackager extends PlatformPackager<MacConfiguration> {
if (!hasMas || targets.length > 1) {
const appPath = prepackaged == null ? path.join(this.computeAppOutDir(outDir, arch), `${this.appInfo.productFilename}.app`) : prepackaged
nonMasPromise = (prepackaged ? Promise.resolve() : this.doPack(outDir, path.dirname(appPath), this.platform.nodeName as ElectronPlatformName, arch, this.platformSpecificBuildOptions, targets))
.then(() => this.packageInDistributableFormat(appPath, Arch.x64, targets, taskManager))
.then(() => this.packageInDistributableFormat(appPath, arch, targets, taskManager))
}

for (const target of targets) {
Expand Down
13 changes: 3 additions & 10 deletions packages/app-builder-lib/src/packager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addValue, Arch, archFromString, AsyncTaskManager, DebugLogger, deepAssign, exec, InvalidConfigurationError, log, safeStringifyJson, serializeToYaml, TmpDir } from "builder-util"
import { addValue, Arch, archFromString, AsyncTaskManager, DebugLogger, deepAssign, InvalidConfigurationError, log, safeStringifyJson, serializeToYaml, TmpDir } from "builder-util"
import { CancellationToken } from "builder-util-runtime"
import { executeFinally, orNullIfFileNotExist } from "builder-util/out/promise"
import { EventEmitter } from "events"
Expand All @@ -23,7 +23,7 @@ import { expandMacro } from "./util/macroExpander"
import { createLazyProductionDeps, NodeModuleDirInfo } from "./util/packageDependencies"
import { checkMetadata, readPackageJson } from "./util/packageMetadata"
import { getRepositoryInfo } from "./util/repositoryInfo"
import { getGypEnv, installOrRebuild } from "./util/yarn"
import { installOrRebuild, nodeGypRebuild } from "./util/yarn"
import { WinPackager } from "./winPackager"

function addHandler(emitter: EventEmitter, event: string, handler: (...args: Array<any>) => void) {
Expand Down Expand Up @@ -185,10 +185,6 @@ export class Packager {

function processTargets(platform: Platform, types: Array<string>) {
function commonArch(currentIfNotSpecified: boolean): Array<Arch> {
if (platform === Platform.MAC) {
return currentIfNotSpecified ? [Arch.x64] : []
}

const result = Array<Arch>()
return result.length === 0 && currentIfNotSpecified ? [archFromString(process.arch)] : result
}
Expand Down Expand Up @@ -489,10 +485,7 @@ export class Packager {
const frameworkInfo = {version: this.framework.version, useCustomDist: true}
const config = this.config
if (config.nodeGypRebuild === true) {
log.info({arch: Arch[arch]}, "executing node-gyp rebuild")
await exec(process.platform === "win32" ? "node-gyp.cmd" : "node-gyp", ["rebuild"], {
env: getGypEnv(frameworkInfo, platform.nodeName, Arch[arch], true),
})
await nodeGypRebuild(platform.nodeName, Arch[arch], frameworkInfo)
}

if (config.npmRebuild === false) {
Expand Down
7 changes: 3 additions & 4 deletions packages/app-builder-lib/src/platformPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,11 +471,10 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
if (pattern == null) {
// tslint:disable-next-line:no-invalid-template-strings
pattern = defaultPattern || "${productName}-${version}-${arch}.${ext}"
}
else {
} else {
// https://github.com/electron-userland/electron-builder/issues/3510
// always respect arch in user custom artifact pattern
skipArchIfX64 = false
skipArchIfX64 = this.platform === Platform.MAC
}
return this.computeArtifactName(pattern, ext, skipArchIfX64 && arch === Arch.x64 ? null : arch)
}
Expand All @@ -487,7 +486,7 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>

private computeArtifactName(pattern: any, ext: string, arch: Arch | null | undefined): string {
const archName = arch == null ? null : getArtifactArchName(arch, ext)
return this.expandMacro(pattern, this.platform === Platform.MAC ? null : archName, {
return this.expandMacro(pattern, archName, {
ext
})
}
Expand Down
2 changes: 1 addition & 1 deletion packages/app-builder-lib/src/targets/targetFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function computeArchToTargetNamesMap(raw: Map<Arch, Array<string>>, platf
}
}

const defaultArchs: Array<ArchType> = raw.size === 0 ? [platform === Platform.MAC ? "x64" : process.arch as ArchType] : Array.from(raw.keys()).map(it => Arch[it] as ArchType)
const defaultArchs: Array<ArchType> = raw.size === 0 ? [process.arch as ArchType] : Array.from(raw.keys()).map(it => Arch[it] as ArchType)
const result = new Map(raw)
for (const target of asArray(platformPackager.platformSpecificBuildOptions.target).map<TargetConfiguration>(it => typeof it === "string" ? {target: it} : it)) {
let name = target.target
Expand Down
7 changes: 7 additions & 0 deletions packages/app-builder-lib/src/util/yarn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ function installDependencies(appDir: string, options: RebuildOptions): Promise<a
})
}

export async function nodeGypRebuild(platform: NodeJS.Platform, arch: string, frameworkInfo: DesktopFrameworkInfo) {
log.info({platform, arch}, "executing node-gyp rebuild")
// this script must be used only for electron
const nodeGyp = `node-gyp${process.platform === "win32" ? ".cmd" : ""}`
await spawn(nodeGyp, ["rebuild"], { env: getGypEnv(frameworkInfo, platform, arch, true) })
}

function getPackageToolPath() {
if (process.env.FORCE_YARN === "true") {
return process.platform === "win32" ? "yarn.cmd" : "yarn"
Expand Down
12 changes: 7 additions & 5 deletions packages/dmg-builder/src/dmg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { findIdentity, isSignAllowed } from "app-builder-lib/out/codeSign/macCod
import MacPackager from "app-builder-lib/out/macPackager"
import { createBlockmap } from "app-builder-lib/out/targets/differentialUpdateInfoBuilder"
import { executeAppBuilderAsJson } from "app-builder-lib/out/util/appBuilder"
import { Arch, AsyncTaskManager, exec, InvalidConfigurationError, isEmptyOrSpaces, log, spawn } from "builder-util"
import { Arch, AsyncTaskManager, exec, getArchSuffix, InvalidConfigurationError, isEmptyOrSpaces, log, spawn } from "builder-util"
import { CancellationToken } from "builder-util-runtime"
import { copyDir, copyFile, exists, statOrNull } from "builder-util/out/fs"
import { stat } from "fs-extra"
Expand All @@ -23,15 +23,15 @@ export class DmgTarget extends Target {
async build(appPath: string, arch: Arch) {
const packager = this.packager
// tslint:disable-next-line:no-invalid-template-strings
const artifactName = packager.expandArtifactNamePattern(packager.config.dmg, "dmg", null, "${productName}-" + (packager.platformSpecificBuildOptions.bundleShortVersion || "${version}") + ".${ext}")
const artifactName = packager.expandArtifactNamePattern(this.options, "dmg", arch, "${productName}-" + (packager.platformSpecificBuildOptions.bundleShortVersion || "${version}") + "-${arch}.${ext}", true)
const artifactPath = path.join(this.outDir, artifactName)
await packager.info.callArtifactBuildStarted({
targetPresentableName: "DMG",
file: artifactPath,
arch,
})

const volumeName = sanitizeFileName(this.computeVolumeName(this.options.title))
const volumeName = sanitizeFileName(this.computeVolumeName(arch, this.options.title))

const tempDmg = await createStageDmg(await packager.getTempFile(".dmg"), appPath, volumeName)

Expand Down Expand Up @@ -115,15 +115,17 @@ export class DmgTarget extends Target {
await exec("codesign", args)
}

computeVolumeName(custom?: string | null): string {
computeVolumeName(arch: Arch, custom?: string | null): string {
const appInfo = this.packager.appInfo
const shortVersion = this.packager.platformSpecificBuildOptions.bundleShortVersion || appInfo.version
const archString = getArchSuffix(arch)

if (custom == null) {
return `${appInfo.productFilename} ${shortVersion}`
return `${appInfo.productFilename} ${shortVersion}${archString}`
}

return custom
.replace(/\${arch}/g, archString)
.replace(/\${shortVersion}/g, shortVersion)
.replace(/\${version}/g, appInfo.version)
.replace(/\${name}/g, appInfo.name)
Expand Down
6 changes: 1 addition & 5 deletions packages/electron-builder/src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ export function normalizeOptions(args: CliOptions): BuildOptions {

function processTargets(platform: Platform, types: Array<string>) {
function commonArch(currentIfNotSpecified: boolean): Array<Arch> {
if (platform === Platform.MAC) {
return args.x64 || currentIfNotSpecified ? [Arch.x64] : []
}

const result = Array<Arch>()
if (args.x64) {
result.push(Arch.x64)
Expand Down Expand Up @@ -202,7 +198,7 @@ export function coerceTypes(host: any): any {
export function createTargets(platforms: Array<Platform>, type?: string | null, arch?: string | null): Map<Platform, Map<Arch, Array<string>>> {
const targets = new Map<Platform, Map<Arch, Array<string>>>()
for (const platform of platforms) {
const archs = platform === Platform.MAC ? [Arch.x64] : (arch === "all" ? [Arch.x64, Arch.ia32] : [archFromString(arch == null ? process.arch : arch)])
const archs = (arch === "all" ? (platform === Platform.MAC ? [Arch.x64, Arch.arm64] : [Arch.x64, Arch.ia32]) : [archFromString(arch == null ? process.arch : arch)])
const archToType = new Map<Arch, Array<string>>()
targets.set(platform, archToType)

Expand Down
9 changes: 3 additions & 6 deletions packages/electron-builder/src/cli/cli.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
#! /usr/bin/env node

import { exec, InvalidConfigurationError, log } from "builder-util"
import { InvalidConfigurationError, log } from "builder-util"
import chalk from "chalk"
import { getElectronVersion } from "app-builder-lib/out/electron/electronVersion"
import { getGypEnv } from "app-builder-lib/out/util/yarn"
import { pathExists, readJson } from "fs-extra"
import isCi from "is-ci"
import * as path from "path"
Expand All @@ -14,6 +13,7 @@ import { build, configureBuildCommand, createYargs } from "../builder"
import { createSelfSignedCert } from "./create-self-signed-cert"
import { configureInstallAppDepsCommand, installAppDeps } from "./install-app-deps"
import { start } from "./start"
import { nodeGypRebuild } from "app-builder-lib/out/util/yarn"

// tslint:disable:no-unused-expression
createYargs()
Expand Down Expand Up @@ -83,9 +83,6 @@ function checkIsOutdated() {

async function rebuildAppNativeCode(args: any) {
const projectDir = process.cwd()
log.info({platform: args.platform, arch: args.arch}, "executing node-gyp rebuild")
// this script must be used only for electron
await exec(process.platform === "win32" ? "node-gyp.cmd" : "node-gyp", ["rebuild"], {
env: getGypEnv({version: await getElectronVersion(projectDir), useCustomDist: true}, args.platform, args.arch, true),
})
return nodeGypRebuild(args.platform, args.arch, {version: await getElectronVersion(projectDir), useCustomDist: true})
}
10 changes: 5 additions & 5 deletions packages/electron-builder/src/cli/install-app-deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,18 @@ export async function installAppDeps(args: any) {
const projectDir = process.cwd()
const packageMetadata = new Lazy(() => orNullIfFileNotExist(readJson(path.join(projectDir, "package.json"))))
const config = await getConfig(projectDir, null, null, packageMetadata)
const results = await Promise.all<string>([
const [appDir, version] = await Promise.all<string>([
computeDefaultAppDirectory(projectDir, use(config.directories, it => it!.app)),
getElectronVersion(projectDir, config, packageMetadata),
])

// if two package.json — force full install (user wants to install/update app deps in addition to dev)
await installOrRebuild(config, results[0], {
frameworkInfo: {version: results[1], useCustomDist: true},
await installOrRebuild(config, appDir, {
frameworkInfo: {version, useCustomDist: true},
platform: args.platform,
arch: args.arch,
productionDeps: createLazyProductionDeps(results[0], null),
}, results[0] !== projectDir)
productionDeps: createLazyProductionDeps(appDir, null),
}, appDir !== projectDir)
}

function main() {
Expand Down
1 change: 1 addition & 0 deletions packages/electron-updater/src/MacUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class MacUpdater extends AppUpdater {
this.updateInfoForPendingUpdateDownloadedEvent = null

const files = downloadUpdateOptions.updateInfoAndProvider.provider.resolveFiles(downloadUpdateOptions.updateInfoAndProvider.info)
.filter(file => (process.arch == 'arm64') === (file.url.pathname.includes('arm64')));
const zipFileInfo = findFile(files, "zip", ["pkg", "dmg"])
if (zipFileInfo == null) {
throw newError(`ZIP file not provided: ${safeStringifyJson(files)}`, "ERR_UPDATER_ZIP_FILE_NOT_FOUND")
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/test-app-build-sub/electron-builder.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
electronVersion: 8.2.5
electronVersion: 11.0.0
appId: org.electron-builder.testApp
compression: store
npmRebuild: false
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/test-app-one/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"author": "Foo Bar <foo@example.com>",
"license": "MIT",
"build": {
"electronVersion": "8.2.5",
"electronVersion": "11.0.0",
"appId": "org.electron-builder.testApp",
"compression": "store",
"npmRebuild": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"author": "Foo Bar <foo@example.com>",
"license": "MIT",
"build": {
"electronVersion": "8.2.5",
"electronVersion": "11.0.0",
"appId": "org.electron-builder.testApp",
"compression": "store",
"npmRebuild": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"author": "Foo Bar <foo@example.com>",
"license": "MIT",
"build": {
"electronVersion": "8.2.5",
"electronVersion": "11.0.0",
"appId": "org.electron-builder.testApp",
"compression": "store",
"npmRebuild": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"author": "Foo Bar <foo@example.com>",
"license": "MIT",
"build": {
"electronVersion": "8.2.5",
"electronVersion": "11.0.0",
"appId": "org.electron-builder.testApp",
"compression": "store",
"npmRebuild": false,
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/test-app/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"build": {
"electronVersion": "8.2.5",
"electronVersion": "11.0.0",
"appId": "org.electron-builder.testApp",
"compression": "store",
"npmRebuild": false,
Expand Down
10 changes: 5 additions & 5 deletions test/snapshots/BuildTest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -837,10 +837,10 @@ Object {
"size": 765,
},
"ini.js": Object {
"size": 4767,
"size": 5061,
},
"package.json": Object {
"size": 458,
"size": 541,
},
},
},
Expand Down Expand Up @@ -944,13 +944,13 @@ Object {
"ms": Object {
"files": Object {
"index.js": Object {
"size": 3023,
"size": 3024,
},
"license.md": Object {
"size": 1077,
"size": 1079,
},
"package.json": Object {
"size": 470,
"size": 497,
},
},
},
Expand Down
3 changes: 3 additions & 0 deletions test/snapshots/PublishManagerTest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ Object {
"CFBundlePackageType": "APPL",
"CFBundleShortVersionString": "1.1.0",
"LSApplicationCategoryType": "your.app.category.type",
"LSRequiresNativeExecution": true,
"NSAppTransportSecurity": Object {
"NSAllowsLocalNetworking": true,
"NSExceptionDomains": Object {
Expand Down Expand Up @@ -273,6 +274,7 @@ Object {
"CFBundlePackageType": "APPL",
"CFBundleShortVersionString": "1.1.0",
"LSApplicationCategoryType": "your.app.category.type",
"LSRequiresNativeExecution": true,
"NSAppTransportSecurity": Object {
"NSAllowsLocalNetworking": true,
"NSExceptionDomains": Object {
Expand Down Expand Up @@ -349,6 +351,7 @@ Object {
"CFBundlePackageType": "APPL",
"CFBundleShortVersionString": "1.1.0",
"LSApplicationCategoryType": "your.app.category.type",
"LSRequiresNativeExecution": true,
"NSAppTransportSecurity": Object {
"NSAllowsLocalNetworking": true,
"NSExceptionDomains": Object {
Expand Down
1 change: 1 addition & 0 deletions test/snapshots/filesTest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Array [
"lib/net45/v8_context_snapshot.bin",
"lib/net45/vk_swiftshader.dll",
"lib/net45/vk_swiftshader_icd.json",
"lib/net45/vulkan-1.dll",
"lib/net45/locales/en-US.pak",
"lib/net45/resources/app.asar",
"lib/net45/resources/platformSpecificR",
Expand Down
Loading

0 comments on commit a98c895

Please sign in to comment.