Skip to content

Commit c2392de

Browse files
authored
fix: attempting to wrap all hdiutil's in a common retry mechanism for CI stability (#8135)
1 parent f55980f commit c2392de

File tree

5 files changed

+39
-18
lines changed

5 files changed

+39
-18
lines changed

.changeset/fast-maps-dress.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"app-builder-lib": patch
3+
"builder-util": patch
4+
"dmg-builder": patch
5+
"electron-builder": patch
6+
"electron-builder-squirrel-windows": patch
7+
"electron-publish": patch
8+
"electron-updater": patch
9+
---
10+
11+
fix: unstable hdiutil retry mechanism

packages/builder-util/src/util.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -404,14 +404,14 @@ export async function executeAppBuilder(
404404
}
405405
}
406406

407-
export async function retry<T>(task: () => Promise<T>, retriesLeft: number, interval: number, backoff = 0, attempt = 0): Promise<T> {
407+
export async function retry<T>(task: () => Promise<T>, retryCount: number, interval: number, backoff = 0, attempt = 0, shouldRetry?: (e: any) => boolean): Promise<T> {
408408
try {
409409
return await task()
410410
} catch (error: any) {
411-
log.info(`Above command failed, retrying ${retriesLeft} more times`)
412-
if (retriesLeft > 0) {
411+
log.info(`Above command failed, retrying ${retryCount} more times`)
412+
if ((shouldRetry?.(error) ?? true) && retryCount > 0) {
413413
await new Promise(resolve => setTimeout(resolve, interval + backoff * attempt))
414-
return await retry(task, retriesLeft - 1, interval, backoff, attempt + 1)
414+
return await retry(task, retryCount - 1, interval, backoff, attempt + 1, shouldRetry)
415415
} else {
416416
throw error
417417
}

packages/dmg-builder/src/dmg.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import MacPackager from "app-builder-lib/out/macPackager"
44
import { createBlockmap } from "app-builder-lib/out/targets/differentialUpdateInfoBuilder"
55
import { executeAppBuilderAsJson } from "app-builder-lib/out/util/appBuilder"
66
import { sanitizeFileName } from "app-builder-lib/out/util/filename"
7-
import { Arch, AsyncTaskManager, exec, getArchSuffix, InvalidConfigurationError, isEmptyOrSpaces, log, spawn, retry } from "builder-util"
7+
import { Arch, AsyncTaskManager, exec, getArchSuffix, InvalidConfigurationError, isEmptyOrSpaces, log } from "builder-util"
88
import { CancellationToken } from "builder-util-runtime"
99
import { copyDir, copyFile, exists, statOrNull } from "builder-util/out/fs"
1010
import { stat } from "fs-extra"
@@ -13,6 +13,7 @@ import { TmpDir } from "temp-file"
1313
import { addLicenseToDmg } from "./dmgLicense"
1414
import { attachAndExecute, computeBackground, detach, getDmgVendorPath } from "./dmgUtil"
1515
import { release as getOsRelease } from "os"
16+
import { hdiUtil } from "./hdiuil"
1617

1718
export class DmgTarget extends Target {
1819
readonly options: DmgOptions = this.packager.config.dmg || Object.create(null)
@@ -51,7 +52,7 @@ export class DmgTarget extends Target {
5152
const backgroundFile = specification.background == null ? null : await transformBackgroundFileIfNeed(specification.background, packager.info.tempDirManager)
5253
const finalSize = await computeAssetSize(packager.info.cancellationToken, tempDmg, specification, backgroundFile)
5354
const expandingFinalSize = finalSize * 0.1 + finalSize
54-
await exec("hdiutil", ["resize", "-size", expandingFinalSize.toString(), tempDmg])
55+
await hdiUtil(["resize", "-size", expandingFinalSize.toString(), tempDmg])
5556

5657
const volumePath = path.join("/Volumes", volumeName)
5758
if (await exists(volumePath)) {
@@ -68,9 +69,9 @@ export class DmgTarget extends Target {
6869
if (specification.format === "UDZO") {
6970
args.push("-imagekey", `zlib-level=${process.env.ELECTRON_BUILDER_COMPRESSION_LEVEL || "9"}`)
7071
}
71-
await spawn("hdiutil", addLogLevel(args))
72+
await hdiUtil(addLogLevel(args))
7273
if (this.options.internetEnabled && parseInt(getOsRelease().split(".")[0], 10) < 19) {
73-
await exec("hdiutil", addLogLevel(["internet-enable"]).concat(artifactPath))
74+
await hdiUtil(addLogLevel(["internet-enable"]).concat(artifactPath))
7475
}
7576

7677
const licenseData = await addLicenseToDmg(packager, artifactPath)
@@ -210,9 +211,7 @@ async function createStageDmg(tempDmg: string, appPath: string, volumeName: stri
210211
}
211212
imageArgs.push("-fs", ...filesystem)
212213
imageArgs.push(tempDmg)
213-
// The reason for retrying up to ten times is that hdiutil create in some cases fail to unmount due to "resource busy".
214-
// https://github.com/electron-userland/electron-builder/issues/5431
215-
await retry(() => spawn("hdiutil", imageArgs), 5, 1000)
214+
await hdiUtil(imageArgs)
216215
return tempDmg
217216
}
218217

packages/dmg-builder/src/dmgUtil.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { exec, retry } from "builder-util"
21
import { PlatformPackager } from "app-builder-lib"
32
import { executeFinally } from "builder-util/out/promise"
43
import * as path from "path"
4+
import { hdiUtil } from "./hdiuil"
55

66
export { DmgTarget } from "./dmg"
77

@@ -23,7 +23,7 @@ export async function attachAndExecute(dmgPath: string, readWrite: boolean, task
2323
}
2424

2525
args.push(dmgPath)
26-
const attachResult = await exec("hdiutil", args)
26+
const attachResult = await hdiUtil(args)
2727
const deviceResult = attachResult == null ? null : /^(\/dev\/\w+)/.exec(attachResult)
2828
const device = deviceResult == null || deviceResult.length !== 2 ? null : deviceResult[1]
2929
if (device == null) {
@@ -34,11 +34,7 @@ export async function attachAndExecute(dmgPath: string, readWrite: boolean, task
3434
}
3535

3636
export async function detach(name: string) {
37-
try {
38-
await exec("hdiutil", ["detach", "-quiet", name])
39-
} catch (e: any) {
40-
await retry(() => exec("hdiutil", ["detach", "-force", "-debug", name]), 5, 1000, 500)
41-
}
37+
return hdiUtil(["detach", "-quiet", name])
4238
}
4339

4440
export async function computeBackground(packager: PlatformPackager<any>): Promise<string> {

packages/dmg-builder/src/hdiuil.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { exec, log, retry } from "builder-util"
2+
3+
export async function hdiUtil(args: string[]) {
4+
return retry(
5+
() => exec("hdiutil", args),
6+
5,
7+
1000,
8+
2000,
9+
0,
10+
(error: any) => {
11+
log.error({ args, code: error.code, error: (error.message || error).toString() }, "unable to execute hdiutil")
12+
return true
13+
}
14+
)
15+
}

0 commit comments

Comments
 (0)