Skip to content

Commit 247f52a

Browse files
fix(packager): "packaging application" log never stops when building for multiple architectures (#3006)
* Fix "packaging application" log never stopping when building for multiple architectures There's some kinda complex race condition ish behaviour in the current way the packagerSpinner is handled. At the moment there's one variable for all architectures, that gets created in one of the later afterCopyHooks, and dismissed both in the first hook, and at the end of all the hooks. This would work if architectures where built for sequentially, but, at least on my system, these builds happen in parallel. Meaning one of the architectures packageSpinners overwrites the other, before the first is dismissed. At the end of the build the second packageSpinner has success fired on it, while the first continues to spin, preventing the process from exiting. The solution in this commit is far less than ideal. Constraints: - Without access to the list of architectures (the function to generate that list is not exported from electron-packer) we don't even know how many architectures we're building for. - This one is a little self-imposed, and maybe not a constraint for the project, but the code aims not to change any public facing apis. So we need to have one spinner we can pass to the postPackage hook. https://www.electronforge.io/configuration#postpackage Given these constraints the solution in this commit has one initial prepare / package spinner. AS well as prepare / package spinners per architecture. One downside is if the afterCopyHooks are executed one architecture at a time, instead of parallelized by architecture, the overarching prepareSpinner could be ended early, (though architecture specific prepare spinners would still be accurate). * Simplify Package spinners, removing prepackage & making package smarter. * build: update electron-packager Co-authored-by: Samuel Attard <sattard@salesforce.com>
1 parent 300387f commit 247f52a

File tree

6 files changed

+83
-47
lines changed

6 files changed

+83
-47
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"cross-spawn": "^7.0.3",
5353
"cross-zip": "^4.0.0",
5454
"debug": "^4.3.1",
55-
"electron-packager": "^17.0.0",
55+
"electron-packager": "^17.1.0",
5656
"electron-rebuild": "^3.2.6",
5757
"express": "^4.17.1",
5858
"express-ws": "^5.0.2",

packages/api/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"@malept/cross-spawn-promise": "^2.0.0",
5353
"chalk": "^4.0.0",
5454
"debug": "^4.3.1",
55-
"electron-packager": "^17.0.0",
55+
"electron-packager": "^17.1.0",
5656
"electron-rebuild": "^3.2.6",
5757
"fast-glob": "^3.2.7",
5858
"filenamify": "^4.1.0",

packages/api/core/src/api/package.ts

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import path from 'path';
22
import { promisify } from 'util';
33

4-
import { fakeOra, OraImpl, ora as realOra } from '@electron-forge/async-ora';
4+
import { fakeOra, ora as realOra } from '@electron-forge/async-ora';
55
import { getElectronVersion } from '@electron-forge/core-utils';
66
import { ForgeArch, ForgePlatform } from '@electron-forge/shared-types';
77
import { getHostArch } from '@electron/get';
88
import chalk from 'chalk';
99
import debug from 'debug';
10-
import packager, { HookFunction } from 'electron-packager';
10+
import packager, { FinalizePackageTargetsHookFunction, HookFunction, TargetDefinition } from 'electron-packager';
1111
import glob from 'fast-glob';
1212
import fs from 'fs-extra';
1313

@@ -25,16 +25,17 @@ const d = debug('electron-forge:packager');
2525
/**
2626
* Resolves hooks if they are a path to a file (instead of a `Function`).
2727
*/
28-
function resolveHooks(hooks: (string | HookFunction)[] | undefined, dir: string) {
28+
function resolveHooks<F = HookFunction>(hooks: (string | F)[] | undefined, dir: string) {
2929
if (hooks) {
30-
return hooks.map((hook) => (typeof hook === 'string' ? (requireSearch<HookFunction>(dir, [hook]) as HookFunction) : hook));
30+
return hooks.map((hook) => (typeof hook === 'string' ? (requireSearch<F>(dir, [hook]) as F) : hook));
3131
}
3232

3333
return [];
3434
}
3535

36-
type DoneFunction = () => void;
36+
type DoneFunction = (err?: Error) => void;
3737
type PromisifiedHookFunction = (buildPath: string, electronVersion: string, platform: string, arch: string) => Promise<void>;
38+
type PromisifiedFinalizePackageTargetsHookFunction = (targets: TargetDefinition[]) => Promise<void>;
3839

3940
/**
4041
* Runs given hooks sequentially by mapping them to promises and iterating
@@ -44,12 +45,30 @@ function sequentialHooks(hooks: HookFunction[]): PromisifiedHookFunction[] {
4445
return [
4546
async (buildPath: string, electronVersion: string, platform: string, arch: string, done: DoneFunction) => {
4647
for (const hook of hooks) {
47-
await promisify(hook)(buildPath, electronVersion, platform, arch);
48+
try {
49+
await promisify(hook)(buildPath, electronVersion, platform, arch);
50+
} catch (err) {
51+
return done(err as Error);
52+
}
4853
}
4954
done();
5055
},
5156
] as PromisifiedHookFunction[];
5257
}
58+
function sequentialFinalizePackageTargetsHooks(hooks: FinalizePackageTargetsHookFunction[]): PromisifiedFinalizePackageTargetsHookFunction[] {
59+
return [
60+
async (targets: TargetDefinition[], done: DoneFunction) => {
61+
for (const hook of hooks) {
62+
try {
63+
await promisify(hook)(targets);
64+
} catch (err) {
65+
return done(err as Error);
66+
}
67+
}
68+
done();
69+
},
70+
] as PromisifiedFinalizePackageTargetsHookFunction[];
71+
}
5372

5473
export interface PackageOptions {
5574
/**
@@ -83,8 +102,7 @@ export default async ({
83102
}: PackageOptions): Promise<void> => {
84103
const ora = interactive ? realOra : fakeOra;
85104

86-
let prepareSpinner = ora(`Preparing to Package Application for arch: ${chalk.cyan(arch === 'all' ? 'ia32' : arch)}`).start();
87-
let prepareCounter = 0;
105+
let spinner = ora(`Preparing to Package Application`).start();
88106

89107
const resolvedDir = await resolveDir(dir);
90108
if (!resolvedDir) {
@@ -100,45 +118,63 @@ export default async ({
100118
}
101119

102120
const calculatedOutDir = outDir || getCurrentOutDir(dir, forgeConfig);
103-
let packagerSpinner: OraImpl | undefined;
121+
122+
let pending: TargetDefinition[] = [];
123+
124+
function readableTargets(targets: TargetDefinition[]) {
125+
return targets.map(({ platform, arch }) => `${platform}:${arch}`).join(', ');
126+
}
127+
128+
const afterFinalizePackageTargetsHooks: FinalizePackageTargetsHookFunction[] = [
129+
(matrix, done) => {
130+
spinner.succeed();
131+
spinner = ora(`Packaging for ${chalk.cyan(readableTargets(matrix))}`).start();
132+
pending.push(...matrix);
133+
done();
134+
},
135+
...resolveHooks(forgeConfig.packagerConfig.afterFinalizePackageTargets, dir),
136+
];
104137

105138
const pruneEnabled = !('prune' in forgeConfig.packagerConfig) || forgeConfig.packagerConfig.prune;
106139

107140
const afterCopyHooks: HookFunction[] = [
108141
async (buildPath, electronVersion, pPlatform, pArch, done) => {
109-
if (packagerSpinner) {
110-
packagerSpinner.succeed();
111-
prepareCounter += 1;
112-
prepareSpinner = ora(`Preparing to Package Application for arch: ${chalk.cyan(prepareCounter === 2 ? 'armv7l' : 'x64')}`).start();
113-
}
114142
const bins = await glob(path.join(buildPath, '**/.bin/**/*'));
115143
for (const bin of bins) {
116144
await fs.remove(bin);
117145
}
118146
done();
119147
},
120148
async (buildPath, electronVersion, pPlatform, pArch, done) => {
121-
prepareSpinner.succeed();
122149
await runHook(forgeConfig, 'packageAfterCopy', buildPath, electronVersion, pPlatform, pArch);
123150
done();
124151
},
125152
async (buildPath, electronVersion, pPlatform, pArch, done) => {
126153
await rebuildHook(buildPath, electronVersion, pPlatform, pArch, forgeConfig.rebuildConfig);
127-
packagerSpinner = ora('Packaging Application').start();
128154
done();
129155
},
130-
];
131-
132-
afterCopyHooks.push(async (buildPath, electronVersion, pPlatform, pArch, done) => {
133-
const copiedPackageJSON = await readMutatedPackageJson(buildPath, forgeConfig);
134-
if (copiedPackageJSON.config && copiedPackageJSON.config.forge) {
135-
delete copiedPackageJSON.config.forge;
136-
}
137-
await fs.writeJson(path.resolve(buildPath, 'package.json'), copiedPackageJSON, { spaces: 2 });
138-
done();
139-
});
156+
async (buildPath, electronVersion, pPlatform, pArch, done) => {
157+
const copiedPackageJSON = await readMutatedPackageJson(buildPath, forgeConfig);
158+
if (copiedPackageJSON.config && copiedPackageJSON.config.forge) {
159+
delete copiedPackageJSON.config.forge;
160+
}
161+
await fs.writeJson(path.resolve(buildPath, 'package.json'), copiedPackageJSON, { spaces: 2 });
162+
done();
163+
},
164+
...resolveHooks(forgeConfig.packagerConfig.afterCopy, dir),
165+
async (buildPath, electronVersion, pPlatform, pArch, done) => {
166+
spinner.text = `Packaging for ${chalk.cyan(pArch)} complete`;
167+
spinner.succeed();
168+
pending = pending.filter(({ arch, platform }) => !(arch === pArch && platform === pPlatform));
169+
if (pending.length > 0) {
170+
spinner = ora(`Packaging for ${chalk.cyan(readableTargets(pending))}`).start();
171+
} else {
172+
spinner = ora(`Packaging complete`).start();
173+
}
140174

141-
afterCopyHooks.push(...resolveHooks(forgeConfig.packagerConfig.afterCopy, dir));
175+
done();
176+
},
177+
];
142178

143179
const afterPruneHooks = [];
144180

@@ -168,6 +204,7 @@ export default async ({
168204
dir,
169205
arch: arch as PackagerArch,
170206
platform,
207+
afterFinalizePackageTargets: sequentialFinalizePackageTargetsHooks(afterFinalizePackageTargetsHooks),
171208
afterCopy: sequentialHooks(afterCopyHooks),
172209
afterExtract: sequentialHooks(afterExtractHooks),
173210
afterPrune: sequentialHooks(afterPruneHooks),
@@ -202,9 +239,8 @@ export default async ({
202239
arch,
203240
outputPaths,
204241
platform,
205-
spinner: packagerSpinner,
242+
spinner,
206243
});
207244

208-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
209-
if (packagerSpinner) packagerSpinner!.succeed();
245+
if (spinner) spinner.succeed();
210246
};

packages/plugin/webpack/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"@malept/cross-spawn-promise": "^2.0.0",
1515
"@types/node": "^18.0.3",
1616
"chai": "^4.3.3",
17-
"electron-packager": "^17.0.0",
17+
"electron-packager": "^17.1.0",
1818
"mocha": "^9.0.1",
1919
"sinon": "^13.0.1",
2020
"which": "^2.0.2",

packages/utils/types/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"typings": "dist/index.d.ts",
1010
"dependencies": {
1111
"@electron-forge/async-ora": "6.0.0-beta.70",
12-
"electron-packager": "^17.0.0",
12+
"electron-packager": "^17.1.0",
1313
"electron-rebuild": "^3.2.6",
1414
"ora": "^5.0.0"
1515
},

yarn.lock

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,6 +1141,14 @@
11411141
global-agent "^3.0.0"
11421142
global-tunnel-ng "^2.7.1"
11431143

1144+
"@electron/notarize@^1.2.3":
1145+
version "1.2.3"
1146+
resolved "https://registry.yarnpkg.com/@electron/notarize/-/notarize-1.2.3.tgz#38056a629e5a0b5fd56c975c4828c0f74285b644"
1147+
integrity sha512-9oRzT56rKh5bspk3KpAVF8lPKHYQrBnRwcgiOeR0hdilVEQmszDaAu0IPCPrwwzJN0ugNs0rRboTreHMt/6mBQ==
1148+
dependencies:
1149+
debug "^4.1.1"
1150+
fs-extra "^9.0.1"
1151+
11441152
"@electron/osx-sign@^1.0.1":
11451153
version "1.0.1"
11461154
resolved "https://registry.yarnpkg.com/@electron/osx-sign/-/osx-sign-1.0.1.tgz#ab4fceded7fed9f2f18c25650f46c1e3a6f17054"
@@ -3904,26 +3912,18 @@ electron-installer-snap@^5.1.0:
39043912
which "^2.0.1"
39053913
yargs "^15.0.1"
39063914

3907-
electron-notarize@^1.1.1:
3908-
version "1.1.1"
3909-
resolved "https://registry.yarnpkg.com/electron-notarize/-/electron-notarize-1.1.1.tgz#3ed274b36158c1beb1dbef14e7faf5927e028629"
3910-
integrity sha512-kufsnqh86CTX89AYNG3NCPoboqnku/+32RxeJ2+7A4Rbm4bbOx0Nc7XTy3/gAlBfpj9xPAxHfhZLOHgfi6cJVw==
3911-
dependencies:
3912-
debug "^4.1.1"
3913-
fs-extra "^9.0.1"
3914-
3915-
electron-packager@^17.0.0:
3916-
version "17.0.0"
3917-
resolved "https://registry.yarnpkg.com/electron-packager/-/electron-packager-17.0.0.tgz#09e86734da51049de08b14cee8fc6b2d424efeec"
3918-
integrity sha512-dDy/gMR7Zl9t5AOFNIDjX+7T0d6GEifh1o/ciDUUf/fnHpVVNjDG92HsWgToGN/H9CZi5NEdlHWUQ49Wj7TN5g==
3915+
electron-packager@^17.1.0:
3916+
version "17.1.0"
3917+
resolved "https://registry.yarnpkg.com/electron-packager/-/electron-packager-17.1.0.tgz#e9653aa57dc523cdb3e0de7ec1a8e0112a1befec"
3918+
integrity sha512-60TBoutfZuAcfJJL1IVWXDZnZ+Yn88HX3UhOLhHbdB/DKA8RAfwXUhSmsWWozh118gbqYRG/WEk9aDL1xVSMKQ==
39193919
dependencies:
39203920
"@electron/asar" "^3.2.1"
39213921
"@electron/get" "^2.0.0"
3922+
"@electron/notarize" "^1.2.3"
39223923
"@electron/osx-sign" "^1.0.1"
39233924
"@electron/universal" "^1.3.2"
39243925
cross-spawn-windows-exe "^1.2.0"
39253926
debug "^4.0.1"
3926-
electron-notarize "^1.1.1"
39273927
extract-zip "^2.0.0"
39283928
filenamify "^4.1.0"
39293929
fs-extra "^10.1.0"

0 commit comments

Comments
 (0)