Skip to content
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

feat: integrate @electron/universal #1346

Merged
merged 10 commits into from
Apr 19, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
],
"dependencies": {
"@electron/get": "^1.6.0",
"@electron/universal": "^1.2.1",
"asar": "^3.1.0",
"cross-spawn-windows-exe": "^1.2.0",
"debug": "^4.0.1",
Expand Down
15 changes: 15 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import {
TransporterOptions
} from 'electron-notarize/lib/types';
import { SignOptions } from 'electron-osx-sign';
import type { makeUniversalApp } from '@electron/universal';

type MakeUniversalOpts = Parameters<typeof makeUniversalApp>[0]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: this leverages Parameters in TS to get the args for makeUniversalApp since @electron/universal doesn't expose them.


type NotarizeLegacyOptions = LegacyNotarizeCredentials & TransporterOptions;

Expand Down Expand Up @@ -128,6 +131,12 @@ declare namespace electronPackager {
| ({ tool?: 'legacy' } & NotarizeLegacyOptions)
| ({ tool: 'notarytool' } & NotaryToolCredentials);

/**
* See the documentation for [`@electron/universal`](https://github.com/electron/universal)
* for details.
*/
type OsxUniversalOptions = Omit<MakeUniversalOpts, 'x64AppPath' | 'arm64AppPath' | 'outAppPath' | 'force'>

/**
* Defines URL protocol schemes to be used on macOS.
*/
Expand Down Expand Up @@ -444,6 +453,12 @@ declare namespace electronPackager {
* @category macOS
*/
osxSign?: true | OsxSignOptions;
/**
* Used to provide custom options to the internal call to `@electron/universal` when building a macOS
* app with the target architecture of "universal". Unused otherwise, providing a value does not imply
* a universal app is built.
*/
osxUniversal?: OsxUniversalOptions;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: not exposed via CLI

/**
* The base directory where the finished package(s) are created.
*
Expand Down
34 changes: 24 additions & 10 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const hooks = require('./hooks')
const path = require('path')
const targets = require('./targets')
const unzip = require('./unzip')
const { packageUniversalMac } = require('./universal')

function debugHostInfo () {
debug(common.hostInfo())
Expand Down Expand Up @@ -73,14 +74,18 @@ class Packager {
await hooks.promisifyHooks(this.opts.afterExtract, [buildDir, comboOpts.electronVersion, comboOpts.platform, comboOpts.arch])
}

async createApp (comboOpts, zipPath) {
buildDir (platform, arch) {
let buildParentDir
if (this.useTempDir) {
buildParentDir = this.tempBase
} else {
buildParentDir = this.opts.out || process.cwd()
}
const buildDir = path.resolve(buildParentDir, `${comboOpts.platform}-${comboOpts.arch}-template`)
return path.resolve(buildParentDir, `${platform}-${arch}-template`)
}

async createApp (comboOpts, zipPath) {
const buildDir = this.buildDir(comboOpts.platform, comboOpts.arch)
common.info(`Packaging app for platform ${comboOpts.platform} ${comboOpts.arch} using electron v${comboOpts.electronVersion}`, this.opts.quiet)

debug(`Creating ${buildDir}`)
Expand Down Expand Up @@ -125,15 +130,8 @@ class Packager {
}
}

async packageForPlatformAndArch (downloadOpts) {
async packageForPlatformAndArchWithOpts (comboOpts, downloadOpts) {
const zipPath = await this.getElectronZipPath(downloadOpts)
// Create delegated options object with specific platform and arch, for output directory naming
const comboOpts = {
...this.opts,
arch: downloadOpts.arch,
platform: downloadOpts.platform,
electronVersion: downloadOpts.version
}

if (!this.useTempDir) {
return this.createApp(comboOpts, zipPath)
Expand All @@ -150,6 +148,22 @@ class Packager {

return this.checkOverwrite(comboOpts, zipPath)
}

async packageForPlatformAndArch (downloadOpts) {
// Create delegated options object with specific platform and arch, for output directory naming
const comboOpts = {
...this.opts,
arch: downloadOpts.arch,
platform: downloadOpts.platform,
electronVersion: downloadOpts.version
}

if (common.isPlatformMac(comboOpts.platform) && comboOpts.arch === 'universal') {
return packageUniversalMac(this.packageForPlatformAndArchWithOpts.bind(this), this.buildDir(comboOpts.platform, comboOpts.arch), comboOpts, downloadOpts, this.tempBase)
}

return this.packageForPlatformAndArchWithOpts(comboOpts, downloadOpts)
}
}

async function packageAllSpecifiedCombos (opts, archs, platforms) {
Expand Down
12 changes: 7 additions & 5 deletions src/targets.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,27 @@ const common = require('./common')
const { getHostArch } = require('@electron/get')
const semver = require('semver')

const officialArchs = ['ia32', 'x64', 'armv7l', 'arm64', 'mips64el']
const officialArchs = ['ia32', 'x64', 'armv7l', 'arm64', 'mips64el', 'universal']
const officialPlatforms = ['darwin', 'linux', 'mas', 'win32']
const officialPlatformArchCombos = {
darwin: ['x64', 'arm64'],
darwin: ['x64', 'arm64', 'universal'],
linux: ['ia32', 'x64', 'armv7l', 'arm64', 'mips64el'],
mas: ['x64', 'arm64'],
mas: ['x64', 'arm64', 'universal'],
win32: ['ia32', 'x64', 'arm64']
}

const buildVersions = {
darwin: {
arm64: '>= 11.0.0-beta.1'
arm64: '>= 11.0.0-beta.1',
universal: '>= 11.0.0-beta.1'
},
linux: {
arm64: '>= 1.8.0',
mips64el: '^1.8.2-beta.5'
},
mas: {
arm64: '>= 11.0.0-beta.1'
arm64: '>= 11.0.0-beta.1',
universal: '>= 11.0.0-beta.1'
},
win32: {
arm64: '>= 6.0.8'
Expand Down
75 changes: 75 additions & 0 deletions src/universal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
'use strict'

const universal = require('@electron/universal')
const common = require('./common')
const fs = require('fs-extra')
const path = require('path')

async function packageUniversalMac (packageForPlatformAndArchWithOpts, buildDir, comboOpts, downloadOpts, tempBase) {
// In order to generate a universal macOS build we actually need to build the x64 and the arm64 app
// and then glue them together
common.info(`Packaging app for platform ${comboOpts.platform} universal using electron v${comboOpts.electronVersion} - Building x64 and arm64 slices now`, comboOpts.quiet)
await fs.mkdirp(tempBase)
const tempDir = await fs.mkdtemp(path.resolve(tempBase, 'electron-packager-universal-'))

const { App } = require('./mac')
const app = new App(comboOpts, buildDir)
const universalStagingPath = app.stagingPath
const finalUniversalPath = common.generateFinalPath(app.opts)

if (await fs.pathExists(finalUniversalPath)) {
if (comboOpts.overwrite) {
await fs.remove(finalUniversalPath)
} else {
common.info(`Skipping ${comboOpts.platform} ${comboOpts.arch} (output dir already exists, use --overwrite to force)`, comboOpts.quiet)
return true
}
}

const [x64AppPath, arm64AppPath] = await Promise.all(['x64', 'arm64'].map((tempArch) => {
const tempOpts = {
...comboOpts,
arch: tempArch,
out: tempDir
}
const tempDownloadOpts = {
...downloadOpts,
arch: tempArch
}
// Do not sign or notarize the individual slices, we sign and notarize the merged app later
delete tempOpts.osxSign
delete tempOpts.osxNotarize

return packageForPlatformAndArchWithOpts(tempOpts, tempDownloadOpts)
}))

common.info(`Stitching universal app for platform ${comboOpts.platform}`, comboOpts.quiet)

const generatedFiles = await fs.readdir(x64AppPath)
const appName = generatedFiles.filter(file => path.extname(file) === '.app')[0]

await universal.makeUniversalApp({
...comboOpts.osxUniversal,
x64AppPath: path.resolve(x64AppPath, appName),
arm64AppPath: path.resolve(arm64AppPath, appName),
outAppPath: path.resolve(universalStagingPath, appName)
})

await app.signAppIfSpecified()
await app.notarizeAppIfSpecified()
await app.move()

for (const generatedFile of generatedFiles) {
if (path.extname(generatedFile) === '.app') continue

await fs.copy(path.resolve(x64AppPath, generatedFile), path.resolve(finalUniversalPath, generatedFile))
}

await fs.remove(tempDir)

return finalUniversalPath
}

module.exports = {
packageUniversalMac
}
2 changes: 1 addition & 1 deletion test/_setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async function downloadAll (version) {
await downloadElectronChecksum(version)
return Promise.all(
[
...combinations.map(combination => downloadElectronZip(version, combination)),
...combinations.map(combination => combination.arch === 'universal' ? null : downloadElectronZip(version, combination)),
downloadElectronZip('6.0.0', {
platform: 'darwin'
})
Expand Down
2 changes: 1 addition & 1 deletion test/_util.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function packagerTestOptions (t) {
}

module.exports = {
allPlatformArchCombosCount: 12,
allPlatformArchCombosCount: 14,
assertDirectory: async function assertDirectory (t, pathToCheck, message) {
const stats = await fs.stat(pathToCheck)
t.true(stats.isDirectory(), message)
Expand Down
2 changes: 1 addition & 1 deletion test/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ test.serial('overwrite', util.testSinglePlatform(async (t, opts) => {
}))

test.serial('overwrite sans platform/arch set', util.testSinglePlatform(async (t, opts) => {
delete opts.platfrom
delete opts.platform
delete opts.arch
opts.dir = util.fixtureSubdir('basic')
opts.overwrite = true
Expand Down
10 changes: 8 additions & 2 deletions test/targets.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ test('validateListFromOptions works for armv7l host and target arch', t => {
})

test('build for all available official targets',
testMultiTarget({ all: true, electronVersion: '1.8.2' }, util.allPlatformArchCombosCount - 3,
testMultiTarget({ all: true, electronVersion: '1.8.2' }, util.allPlatformArchCombosCount - 5,
'Packages should be generated for all possible platforms (except win32/arm64)'))
test('build for all available official targets for a version without arm64 or mips64el support',
testMultiTarget({ all: true }, util.allPlatformArchCombosCount - 5,
testMultiTarget({ all: true }, util.allPlatformArchCombosCount - 7,
'Packages should be generated for all possible platforms (except linux/arm64, linux/mips64el, or win32/arm64)'))
test('platform=all (one arch)',
testMultiTarget({ arch: 'ia32', platform: 'all' }, 2, 'Packages should be generated for both 32-bit platforms'))
Expand Down Expand Up @@ -119,6 +119,12 @@ test('platform=darwin and arch=arm64 with an unsupported official Electron versi
test('platform=mas and arch=arm64 with a supported official Electron version', testMultiTarget({ arch: 'arm64', platform: 'mas', electronVersion: '11.0.0-beta.5' }, 1, 'Package should be generated for mas/arm64'))
test('platform=mas and arch=arm64 with an unsupported official Electron version', testMultiTarget({ arch: 'arm64', platform: 'mas' }, 0, 'Package should not be generated for mas/arm64'))

test('platform=darwin and arch=universal with a supported official Electron version', testMultiTarget({ arch: 'universal', platform: 'darwin', electronVersion: '11.0.0-beta.5' }, 1, 'Package should be generated for darwin/universal'))
test('platform=darwin and arch=universal with an unsupported official Electron version', testMultiTarget({ arch: 'universal', platform: 'darwin' }, 0, 'Package should not be generated for darwin/universal'))

test('platform=mas and arch=universal with a supported official Electron version', testMultiTarget({ arch: 'universal', platform: 'mas', electronVersion: '11.0.0-beta.5' }, 1, 'Package should be generated for mas/universal'))
test('platform=mas and arch=universal with an unsupported official Electron version', testMultiTarget({ arch: 'universal', platform: 'mas' }, 0, 'Package should not be generated for mas/universal'))

test('unofficial arch', testMultiTarget({ arch: 'z80', platform: 'linux', download: { mirrorOptions: { mirror: 'mirror' } } }, 1,
'Package should be generated for non-standard arch from non-official mirror'))
test('unofficial platform', testMultiTarget({ arch: 'ia32', platform: 'minix', download: { mirrorOptions: { mirror: 'mirror' } } }, 1,
Expand Down
4 changes: 2 additions & 2 deletions usage.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ version prints the version of Electron Packager and Node, plus the ta
all equivalent to --platform=all --arch=all
app-copyright human-readable copyright line for the app
app-version release version to set for the app
arch all, or one or more of: ia32, x64, armv7l, arm64, mips64el (comma-delimited if
multiple). Defaults to the host arch
arch all, or one or more of: ia32, x64, armv7l, arm64, mips64el, universal (comma-delimited if
multiple). Defaults to the host arch. Universal only supported on macOS.
asar whether to package the source code within your app into an archive. You can either
pass --asar by itself to use the default configuration, OR use dot notation to
configure a list of sub-properties, e.g. --asar.unpackDir=sub_dir - do not use
Expand Down