diff --git a/.changeset/mighty-windows-wink.md b/.changeset/mighty-windows-wink.md new file mode 100644 index 00000000000..420ddc26d6c --- /dev/null +++ b/.changeset/mighty-windows-wink.md @@ -0,0 +1,8 @@ +--- +"app-builder-lib": patch +"builder-util": patch +"dmg-builder": patch +"electron-builder-squirrel-windows": patch +--- + +chore(refactor): enable parallel packaging of archs and targets with `concurrency` config prop diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 634cf9a50bd..a4e18430b65 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -72,6 +72,7 @@ jobs: - snapTest,debTest,fpmTest,protonTest - winPackagerTest,winCodeSignTest,webInstallerTest - oneClickInstallerTest,assistedInstallerTest + - concurrentBuildsTest steps: - name: Checkout code repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 @@ -169,6 +170,7 @@ jobs: - winCodeSignTest,differentialUpdateTest,squirrelWindowsTest - appxTest,msiTest,portableTest,assistedInstallerTest,protonTest - BuildTest,oneClickInstallerTest,winPackagerTest,nsisUpdaterTest,webInstallerTest + - concurrentBuildsTest steps: - name: Checkout code repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 @@ -196,6 +198,7 @@ jobs: - oneClickInstallerTest,assistedInstallerTest - winPackagerTest,winCodeSignTest,webInstallerTest - masTest,dmgTest,filesTest,macPackagerTest,differentialUpdateTest,macArchiveTest + - concurrentBuildsTest steps: - name: Checkout code repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 @@ -206,10 +209,11 @@ jobs: cache-path: ~/Library/Caches/electron cache-key: v-23.3.10-macos-electron - - name: Install pwsh and wine via brew + - name: Install toolset via brew run: | brew install powershell/tap/powershell brew install --cask wine-stable + brew install rpm - name: Test run: pnpm ci:test diff --git a/.vscode/launch.json b/.vscode/launch.json index c1d8b3044a2..51064a6df78 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,8 @@ "console": "integratedTerminal", "internalConsoleOptions": "openOnFirstSessionStart", "env": { - "TEST_FILES": "BuildTest" + "TEST_FILES": "macPackagerTest", + "UPDATE_SNAPSHOT": "false" } } ] diff --git a/packages/app-builder-lib/scheme.json b/packages/app-builder-lib/scheme.json index 80be7e2ab02..2a05e9f5cea 100644 --- a/packages/app-builder-lib/scheme.json +++ b/packages/app-builder-lib/scheme.json @@ -428,6 +428,20 @@ ], "type": "object" }, + "Concurrency": { + "additionalProperties": false, + "properties": { + "jobs": { + "default": 1, + "description": "The maximum number of concurrent jobs to run.", + "type": "number" + } + }, + "required": [ + "jobs" + ], + "type": "object" + }, "CustomNsisBinary": { "additionalProperties": false, "properties": { @@ -6924,6 +6938,17 @@ "default": "normal", "description": "The compression level. If you want to rapidly test build, `store` can reduce build time significantly. `maximum` doesn't lead to noticeable size difference, but increase build time." }, + "concurrency": { + "anyOf": [ + { + "$ref": "#/definitions/Concurrency" + }, + { + "type": "null" + } + ], + "description": "[Experimental] Configuration for concurrent builds." + }, "copyright": { "default": "Copyright © year ${author}", "description": "The human-readable copyright line for the app.", diff --git a/packages/app-builder-lib/src/configuration.ts b/packages/app-builder-lib/src/configuration.ts index 215fa231a20..1d7283e3ff5 100644 --- a/packages/app-builder-lib/src/configuration.ts +++ b/packages/app-builder-lib/src/configuration.ts @@ -187,7 +187,13 @@ export interface CommonConfiguration { * Ref: https://github.com/electron/fuses */ readonly electronFuses?: FuseOptionsV1 | null + + /** + * [Experimental] Configuration for concurrent builds. + */ + readonly concurrency?: Concurrency | null } + export interface Configuration extends CommonConfiguration, PlatformSpecificBuildOptions, Hooks { /** * Whether to use [electron-compile](http://github.com/electron/electron-compile) to compile app. Defaults to `true` if `electron-compile` in the dependencies. And `false` if in the `devDependencies` or doesn't specified. @@ -439,3 +445,11 @@ export interface FuseOptionsV1 { */ resetAdHocDarwinSignature?: boolean } + +export interface Concurrency { + /** + * The maximum number of concurrent jobs to run. + * @default 1 + */ + jobs: number +} diff --git a/packages/app-builder-lib/src/core.ts b/packages/app-builder-lib/src/core.ts index ccada81dd26..ec87be77b23 100644 --- a/packages/app-builder-lib/src/core.ts +++ b/packages/app-builder-lib/src/core.ts @@ -1,5 +1,5 @@ -import { Arch, archFromString, ArchType } from "builder-util" -import { AllPublishOptions, Nullish } from "builder-util-runtime" +import { Arch, archFromString, ArchType, AsyncTaskManager } from "builder-util" +import { AllPublishOptions, CancellationToken, Nullish } from "builder-util-runtime" // https://github.com/YousefED/typescript-json-schema/issues/80 export type Publish = AllPublishOptions | Array | null @@ -75,6 +75,9 @@ export abstract class Target { abstract readonly outDir: string abstract readonly options: TargetSpecificOptions | Nullish + // use only for tasks that cannot be executed in parallel (such as signing on windows and hdiutil on macOS due to file locking) + readonly buildQueueManager = new AsyncTaskManager(new CancellationToken()) + protected constructor( readonly name: string, readonly isAsyncSupported: boolean = true @@ -86,8 +89,8 @@ export abstract class Target { abstract build(appOutDir: string, arch: Arch): Promise - finishBuild(): Promise { - return Promise.resolve() + async finishBuild(): Promise { + await this.buildQueueManager.awaitTasks() } } diff --git a/packages/app-builder-lib/src/macPackager.ts b/packages/app-builder-lib/src/macPackager.ts index 257068e898e..5535e01e13a 100644 --- a/packages/app-builder-lib/src/macPackager.ts +++ b/packages/app-builder-lib/src/macPackager.ts @@ -357,7 +357,13 @@ export class MacPackager extends PlatformPackager { const artifactName = this.expandArtifactNamePattern(masOptions, "pkg", arch) const artifactPath = path.join(outDir!, artifactName) await this.doFlat(appPath, artifactPath, masInstallerIdentity, keychainFile) - await this.dispatchArtifactCreated(artifactPath, null, Arch.x64, this.computeSafeArtifactName(artifactName, "pkg", arch, true, this.platformSpecificBuildOptions.defaultArch)) + await this.info.emitArtifactBuildCompleted({ + file: artifactPath, + target: null, + arch: Arch.x64, + safeArtifactName: this.computeSafeArtifactName(artifactName, "pkg", arch, true, this.platformSpecificBuildOptions.defaultArch), + packager: this, + }) } if (!isMas) { diff --git a/packages/app-builder-lib/src/packager.ts b/packages/app-builder-lib/src/packager.ts index c437a973d23..dd30bfe75b7 100644 --- a/packages/app-builder-lib/src/packager.ts +++ b/packages/app-builder-lib/src/packager.ts @@ -9,6 +9,7 @@ import { getArtifactArchName, InvalidConfigurationError, log, + MAX_FILE_REQUESTS, orNullIfFileNotExist, safeStringifyJson, serializeToYaml, @@ -41,6 +42,7 @@ import { resolveFunction } from "./util/resolve" import { installOrRebuild, nodeGypRebuild } from "./util/yarn" import { PACKAGE_VERSION } from "./version" import { AsyncEventEmitter, HandlerType } from "./util/asyncEventEmitter" +import asyncPool from "tiny-async-pool" async function createFrameworkInfo(configuration: Configuration, packager: Packager): Promise { let framework = configuration.framework @@ -479,6 +481,18 @@ export class Packager { const nameToTarget: Map = new Map() platformToTarget.set(platform, nameToTarget) + let poolCount = Math.floor(packager.config.concurrency?.jobs || 1) + if (poolCount < 1) { + log.warn({ concurrency: poolCount }, "concurrency is invalid, overriding with job count: 1") + poolCount = 1 + } else if (poolCount > MAX_FILE_REQUESTS) { + log.warn( + { concurrency: poolCount, MAX_FILE_REQUESTS }, + `job concurrency is greater than recommended MAX_FILE_REQUESTS, this may lead to File Descriptor errors (too many files open). Proceed with caution (e.g. this is an experimental feature)` + ) + } + const packPromises: Promise[] = [] + for (const [arch, targetNames] of computeArchToTargetNamesMap(archToType, packager, platform)) { if (this.cancellationToken.cancelled) { break @@ -488,9 +502,21 @@ export class Packager { const outDir = path.resolve(this.projectDir, packager.expandMacro(this.config.directories!.output!, Arch[arch])) const targetList = createTargets(nameToTarget, targetNames.length === 0 ? packager.defaultTarget : targetNames, outDir, packager) await createOutDirIfNeed(targetList, createdOutDirs) - await packager.pack(outDir, arch, targetList, taskManager) + const promise = packager.pack(outDir, arch, targetList, taskManager) + if (poolCount < 2) { + await promise + } else { + packPromises.push(promise) + } } + await asyncPool(poolCount, packPromises, async it => { + if (this.cancellationToken.cancelled) { + return + } + await it + }) + if (this.cancellationToken.cancelled) { break } @@ -507,6 +533,9 @@ export class Packager { await taskManager.awaitTasks() for (const target of syncTargetsIfAny) { + if (this.cancellationToken.cancelled) { + break + } await target.finishBuild() } return platformToTarget diff --git a/packages/app-builder-lib/src/platformPackager.ts b/packages/app-builder-lib/src/platformPackager.ts index 7ac0f3729a4..865699b8137 100644 --- a/packages/app-builder-lib/src/platformPackager.ts +++ b/packages/app-builder-lib/src/platformPackager.ts @@ -153,16 +153,6 @@ export abstract class PlatformPackager ) } - dispatchArtifactCreated(file: string, target: Target | null, arch: Arch | null, safeArtifactName?: string | null): Promise { - return this.info.emitArtifactBuildCompleted({ - file, - safeArtifactName, - target, - arch, - packager: this, - }) - } - async pack(outDir: string, arch: Arch, targets: Array, taskManager: AsyncTaskManager): Promise { const appOutDir = this.computeAppOutDir(outDir, arch) await this.doPack({ diff --git a/packages/app-builder-lib/src/targets/AppxTarget.ts b/packages/app-builder-lib/src/targets/AppxTarget.ts index 2d6511b08ca..c7c232cf536 100644 --- a/packages/app-builder-lib/src/targets/AppxTarget.ts +++ b/packages/app-builder-lib/src/targets/AppxTarget.ts @@ -49,6 +49,8 @@ const DEFAULT_RESOURCE_LANG = "en-US" export default class AppXTarget extends Target { readonly options: AppXOptions = deepAssign({}, this.packager.platformSpecificBuildOptions, this.packager.config.appx) + isAsyncSupported = false + constructor( private readonly packager: WinPackager, readonly outDir: string @@ -145,18 +147,20 @@ export default class AppXTarget extends Target { if (this.options.makeappxArgs != null) { makeAppXArgs.push(...this.options.makeappxArgs) } - await vm.exec(vm.toVmFile(path.join(vendorPath, "windows-10", signToolArch, "makeappx.exe")), makeAppXArgs) - await packager.sign(artifactPath) - - await stageDir.cleanup() - - await packager.info.emitArtifactBuildCompleted({ - file: artifactPath, - packager, - arch, - safeArtifactName: packager.computeSafeArtifactName(artifactName, "appx"), - target: this, - isWriteUpdateInfo: this.options.electronUpdaterAware, + this.buildQueueManager.add(async () => { + await vm.exec(vm.toVmFile(path.join(vendorPath, "windows-10", signToolArch, "makeappx.exe")), makeAppXArgs) + await packager.sign(artifactPath) + + await stageDir.cleanup() + + await packager.info.emitArtifactBuildCompleted({ + file: artifactPath, + packager, + arch, + safeArtifactName: packager.computeSafeArtifactName(artifactName, "appx"), + target: this, + isWriteUpdateInfo: this.options.electronUpdaterAware, + }) }) } diff --git a/packages/app-builder-lib/src/targets/ArchiveTarget.ts b/packages/app-builder-lib/src/targets/ArchiveTarget.ts index 32530b41df1..272c25fc76d 100644 --- a/packages/app-builder-lib/src/targets/ArchiveTarget.ts +++ b/packages/app-builder-lib/src/targets/ArchiveTarget.ts @@ -33,67 +33,70 @@ export class ArchiveTarget extends Target { defaultPattern = "${productName}-${version}" + (arch === defaultArch ? "" : "-${arch}") + "-${os}.${ext}" } - const artifactName = packager.expandArtifactNamePattern(this.options, format, arch, defaultPattern, false) - const artifactPath = path.join(this.outDir, artifactName) + this.buildQueueManager.add(async () => { + const artifactName = packager.expandArtifactNamePattern(this.options, format, arch, defaultPattern, false) + const artifactPath = path.join(this.outDir, artifactName) - await packager.info.emitArtifactBuildStarted({ - targetPresentableName: `${isMac ? "macOS " : ""}${format}`, - file: artifactPath, - arch, - }) - let updateInfo: any = null - if (format.startsWith("tar.")) { - await tar(packager.compression, format, artifactPath, appOutDir, isMac, packager.info.tempDirManager) - } else { - let withoutDir = !isMac - let dirToArchive = appOutDir - if (isMac) { - dirToArchive = path.dirname(appOutDir) - const fileMatchers = getFileMatchers( - packager.config, - "extraDistFiles", - dirToArchive, - packager.createGetFileMatchersOptions(this.outDir, arch, packager.platformSpecificBuildOptions) - ) - if (fileMatchers == null) { - dirToArchive = appOutDir - } else { - await copyFiles(fileMatchers, null, true) - withoutDir = true + await packager.info.emitArtifactBuildStarted({ + targetPresentableName: `${isMac ? "macOS " : ""}${format}`, + file: artifactPath, + arch, + }) + let updateInfo: any = null + if (format.startsWith("tar.")) { + await tar(packager.compression, format, artifactPath, appOutDir, isMac, packager.info.tempDirManager) + } else { + let withoutDir = !isMac + let dirToArchive = appOutDir + if (isMac) { + dirToArchive = path.dirname(appOutDir) + const fileMatchers = getFileMatchers( + packager.config, + "extraDistFiles", + dirToArchive, + packager.createGetFileMatchersOptions(this.outDir, arch, packager.platformSpecificBuildOptions) + ) + if (fileMatchers == null) { + dirToArchive = appOutDir + } else { + await copyFiles(fileMatchers, null, true) + withoutDir = true + } } - } - const archiveOptions = { - compression: packager.compression, - withoutDir, - } - await archive(format, artifactPath, dirToArchive, archiveOptions) + const archiveOptions = { + compression: packager.compression, + withoutDir, + } + await archive(format, artifactPath, dirToArchive, archiveOptions) - if (this.isWriteUpdateInfo && format === "zip") { - if (isMac) { - updateInfo = await createBlockmap(artifactPath, this, packager, artifactName) - } else { - updateInfo = await appendBlockmap(artifactPath) + if (this.isWriteUpdateInfo && format === "zip") { + if (isMac) { + updateInfo = await createBlockmap(artifactPath, this, packager, artifactName) + } else { + updateInfo = await appendBlockmap(artifactPath) + } } } - } - await packager.info.emitArtifactBuildCompleted({ - updateInfo, - file: artifactPath, - // tslint:disable-next-line:no-invalid-template-strings - safeArtifactName: packager.computeSafeArtifactName( - artifactName, - format, + await packager.info.emitArtifactBuildCompleted({ + updateInfo, + file: artifactPath, + // tslint:disable-next-line:no-invalid-template-strings + safeArtifactName: packager.computeSafeArtifactName( + artifactName, + format, + arch, + false, + packager.platformSpecificBuildOptions.defaultArch, + defaultPattern.replace("${productName}", "${name}") + ), + target: this, arch, - false, - packager.platformSpecificBuildOptions.defaultArch, - defaultPattern.replace("${productName}", "${name}") - ), - target: this, - arch, - packager, - isWriteUpdateInfo: this.isWriteUpdateInfo, + packager, + isWriteUpdateInfo: this.isWriteUpdateInfo, + }) }) + return Promise.resolve() } } diff --git a/packages/app-builder-lib/src/targets/MsiWrappedTarget.ts b/packages/app-builder-lib/src/targets/MsiWrappedTarget.ts index fb37d533d52..fa69fb343f7 100644 --- a/packages/app-builder-lib/src/targets/MsiWrappedTarget.ts +++ b/packages/app-builder-lib/src/targets/MsiWrappedTarget.ts @@ -58,7 +58,8 @@ export default class MsiWrappedTarget extends MsiTarget { return Promise.resolve() } - finishBuild(): Promise { + async finishBuild(): Promise { + await super.finishBuild() // this target invokes `build` in `finishBuild` to guarantee // that the dependent target has already been built // this also affords us re-usability diff --git a/packages/app-builder-lib/src/targets/nsis/Defines.ts b/packages/app-builder-lib/src/targets/nsis/Defines.ts index daec792cc53..0f01ae818f3 100644 --- a/packages/app-builder-lib/src/targets/nsis/Defines.ts +++ b/packages/app-builder-lib/src/targets/nsis/Defines.ts @@ -1,4 +1,3 @@ -import { PathLike } from "fs" import { PortableOptions } from "./nsisOptions" /** * Parameters declared as environment variables in NSIS scripts. @@ -59,7 +58,7 @@ export type Defines = { COMPRESS?: "auto" BUILD_UNINSTALLER?: null - UNINSTALLER_OUT_FILE?: PathLike + UNINSTALLER_OUT_FILE?: string ONE_CLICK?: null RUN_AFTER_FINISH?: null diff --git a/packages/app-builder-lib/src/targets/nsis/NsisTarget.ts b/packages/app-builder-lib/src/targets/nsis/NsisTarget.ts index b53650e82a7..4b585167a22 100644 --- a/packages/app-builder-lib/src/targets/nsis/NsisTarget.ts +++ b/packages/app-builder-lib/src/targets/nsis/NsisTarget.ts @@ -152,6 +152,7 @@ export class NsisTarget extends Target { async finishBuild(): Promise { if (!this.shouldBuildUniversalInstaller) { + await super.finishBuild() return this.packageHelper.finishBuild() } try { @@ -165,6 +166,7 @@ export class NsisTarget extends Target { await this.buildInstaller(archs) } } finally { + await super.finishBuild() await this.packageHelper.finishBuild() } } @@ -274,7 +276,12 @@ export class NsisTarget extends Target { defines[defineUnpackedSizeKey] = Math.ceil(unpackedSize / 1024).toString() if (this.isWebInstaller) { - await packager.dispatchArtifactCreated(file, this, arch) + await packager.info.emitArtifactBuildCompleted({ + file, + target: this, + arch, + packager, + }) packageFiles[Arch[arch]] = fileInfo } const path7za = await getPath7za() @@ -340,37 +347,39 @@ export class NsisTarget extends Target { commandsUninstaller.VIAddVersionKey = this.computeVersionKey(true) } - const sharedHeader = await this.computeCommonInstallerScriptHeader() - const script = isPortable - ? await readFile(path.join(nsisTemplatesDir, "portable.nsi"), "utf8") - : await this.computeScriptAndSignUninstaller(definesUninstaller, commandsUninstaller, installerPath, sharedHeader, archs) + this.buildQueueManager.add(async () => { + const sharedHeader = await this.computeCommonInstallerScriptHeader() + const script = isPortable + ? await readFile(path.join(nsisTemplatesDir, "portable.nsi"), "utf8") + : await this.computeScriptAndSignUninstaller(definesUninstaller, commandsUninstaller, installerPath, sharedHeader, archs) - // copy outfile name into main options, as the computeScriptAndSignUninstaller function was kind enough to add important data to temporary defines. - defines.UNINSTALLER_OUT_FILE = definesUninstaller.UNINSTALLER_OUT_FILE + // copy outfile name into main options, as the computeScriptAndSignUninstaller function was kind enough to add important data to temporary defines. + defines.UNINSTALLER_OUT_FILE = definesUninstaller.UNINSTALLER_OUT_FILE - await this.executeMakensis(defines, commands, sharedHeader + (await this.computeFinalScript(script, true, archs))) - await Promise.all([packager.sign(installerPath), defines.UNINSTALLER_OUT_FILE == null ? Promise.resolve() : unlink(defines.UNINSTALLER_OUT_FILE)]) + await this.executeMakensis(defines, commands, sharedHeader + (await this.computeFinalScript(script, true, archs))) + await Promise.all([packager.sign(installerPath), defines.UNINSTALLER_OUT_FILE == null ? Promise.resolve() : unlink(defines.UNINSTALLER_OUT_FILE)]) - const safeArtifactName = computeSafeArtifactNameIfNeeded(installerFilename, () => this.generateGitHubInstallerName(primaryArch, defaultArch)) - let updateInfo: any - if (this.isWebInstaller) { - updateInfo = createNsisWebDifferentialUpdateInfo(installerPath, packageFiles) - } else if (this.isBuildDifferentialAware) { - updateInfo = await createBlockmap(installerPath, this, packager, safeArtifactName) - } + const safeArtifactName = computeSafeArtifactNameIfNeeded(installerFilename, () => this.generateGitHubInstallerName(primaryArch, defaultArch)) + let updateInfo: any + if (this.isWebInstaller) { + updateInfo = createNsisWebDifferentialUpdateInfo(installerPath, packageFiles) + } else if (this.isBuildDifferentialAware) { + updateInfo = await createBlockmap(installerPath, this, packager, safeArtifactName) + } - if (updateInfo != null && isPerMachine && (oneClick || options.packElevateHelper)) { - updateInfo.isAdminRightsRequired = true - } + if (updateInfo != null && isPerMachine && (oneClick || options.packElevateHelper)) { + updateInfo.isAdminRightsRequired = true + } - await packager.info.emitArtifactBuildCompleted({ - file: installerPath, - updateInfo, - target: this, - packager, - arch: primaryArch, - safeArtifactName, - isWriteUpdateInfo: !this.isPortable, + await packager.info.emitArtifactBuildCompleted({ + file: installerPath, + updateInfo, + target: this, + packager, + arch: primaryArch, + safeArtifactName, + isWriteUpdateInfo: !this.isPortable, + }) }) } @@ -401,7 +410,8 @@ export class NsisTarget extends Target { // https://github.com/electron-userland/electron-builder/issues/2103 // it is more safe and reliable to write uninstaller to our out dir - const uninstallerPath = path.join(this.outDir, `__uninstaller-${this.name}-${this.packager.appInfo.sanitizedName}.exe`) + // to support parallel builds, the uninstaller path must be unique to each target and arch combination + const uninstallerPath = path.join(this.outDir, `${path.basename(installerPath, "exe")}__uninstaller.exe`) const isWin = process.platform === "win32" defines.BUILD_UNINSTALLER = null defines.UNINSTALLER_OUT_FILE = isWin ? uninstallerPath : path.win32.join("Z:", uninstallerPath) diff --git a/packages/app-builder-lib/src/targets/pkg.ts b/packages/app-builder-lib/src/targets/pkg.ts index 442668c31db..f86b0ccfe8b 100644 --- a/packages/app-builder-lib/src/targets/pkg.ts +++ b/packages/app-builder-lib/src/targets/pkg.ts @@ -86,7 +86,13 @@ export class PkgTarget extends Target { }) await Promise.all([unlink(innerPackageFile), unlink(distInfoFile)]) await packager.notarizeIfProvided(artifactPath) - await packager.dispatchArtifactCreated(artifactPath, this, arch, packager.computeSafeArtifactName(artifactName, "pkg", arch)) + await packager.info.emitArtifactBuildCompleted({ + file: artifactPath, + target: this, + arch, + safeArtifactName: packager.computeSafeArtifactName(artifactName, "pkg", arch), + packager, + }) } private getExtraPackages(): ExtraPackages | null { diff --git a/packages/dmg-builder/src/dmg.ts b/packages/dmg-builder/src/dmg.ts index 1892c669d77..8f9029effe6 100644 --- a/packages/dmg-builder/src/dmg.ts +++ b/packages/dmg-builder/src/dmg.ts @@ -17,6 +17,8 @@ import { hdiUtil } from "./hdiuil" export class DmgTarget extends Target { readonly options: DmgOptions = this.packager.config.dmg || Object.create(null) + isAsyncSupported = false + constructor( private readonly packager: MacPackager, readonly outDir: string diff --git a/packages/electron-builder-squirrel-windows/src/SquirrelWindowsTarget.ts b/packages/electron-builder-squirrel-windows/src/SquirrelWindowsTarget.ts index a5dc0ca5f19..b2fd1a801ec 100644 --- a/packages/electron-builder-squirrel-windows/src/SquirrelWindowsTarget.ts +++ b/packages/electron-builder-squirrel-windows/src/SquirrelWindowsTarget.ts @@ -10,6 +10,8 @@ export default class SquirrelWindowsTarget extends Target { //tslint:disable-next-line:no-object-literal-type-assertion readonly options: SquirrelWindowsOptions = { ...this.packager.platformSpecificBuildOptions, ...this.packager.config.squirrelWindows } as SquirrelWindowsOptions + isAsyncSupported = false + constructor( private readonly packager: WinPackager, readonly outDir: string @@ -53,62 +55,65 @@ export default class SquirrelWindowsTarget extends Target { const artifactPath = path.join(installerOutDir, setupFile) const msiArtifactPath = path.join(installerOutDir, packager.expandArtifactNamePattern(this.options, "msi", arch, "${productName} Setup ${version}.${ext}")) - await packager.info.emitArtifactBuildStarted({ - targetPresentableName: "Squirrel.Windows", - file: artifactPath, - arch, - }) + this.buildQueueManager.add(async () => { + await packager.info.emitArtifactBuildStarted({ + targetPresentableName: "Squirrel.Windows", + file: artifactPath, + arch, + }) + const distOptions = await this.computeEffectiveDistOptions(appOutDir, installerOutDir, setupFile) + await createWindowsInstaller(distOptions) - const distOptions = await this.computeEffectiveDistOptions(appOutDir, installerOutDir, setupFile) - await createWindowsInstaller(distOptions) + await packager.signAndEditResources(artifactPath, arch, installerOutDir) - await packager.signAndEditResources(artifactPath, arch, installerOutDir) - if (this.options.msi) { - await packager.sign(msiArtifactPath) - } - - const safeArtifactName = (ext: string) => `${sanitizedName}-Setup-${version}${getArchSuffix(arch)}.${ext}` + if (this.options.msi) { + await packager.sign(msiArtifactPath) + } - await packager.info.emitArtifactBuildCompleted({ - file: artifactPath, - target: this, - arch, - safeArtifactName: safeArtifactName("exe"), - packager: this.packager, - }) + const safeArtifactName = (ext: string) => `${sanitizedName}-Setup-${version}${getArchSuffix(arch)}.${ext}` - if (this.options.msi) { await packager.info.emitArtifactBuildCompleted({ - file: msiArtifactPath, + file: artifactPath, target: this, arch, - safeArtifactName: safeArtifactName("msi"), + safeArtifactName: safeArtifactName("exe"), packager: this.packager, }) - } - const packagePrefix = `${this.appName}-${convertVersion(version)}-` - await packager.info.emitArtifactCreated({ - file: path.join(installerOutDir, `${packagePrefix}full.nupkg`), - target: this, - arch, - packager, - }) - if (distOptions.remoteReleases != null) { + if (this.options.msi) { + await packager.info.emitArtifactCreated({ + file: msiArtifactPath, + target: this, + arch, + safeArtifactName: safeArtifactName("msi"), + packager: this.packager, + }) + } + + const packagePrefix = `${this.appName}-${convertVersion(version)}-` await packager.info.emitArtifactCreated({ - file: path.join(installerOutDir, `${packagePrefix}delta.nupkg`), + file: path.join(installerOutDir, `${packagePrefix}full.nupkg`), target: this, arch, packager, }) - } + if (distOptions.remoteReleases != null) { + await packager.info.emitArtifactCreated({ + file: path.join(installerOutDir, `${packagePrefix}delta.nupkg`), + target: this, + arch, + packager, + }) + } - await packager.info.emitArtifactCreated({ - file: path.join(installerOutDir, "RELEASES"), - target: this, - arch, - packager, + await packager.info.emitArtifactCreated({ + file: path.join(installerOutDir, "RELEASES"), + target: this, + arch, + packager, + }) }) + return Promise.resolve() } private get appName() { diff --git a/test/snapshots/concurrentBuildsTest.js.snap b/test/snapshots/concurrentBuildsTest.js.snap new file mode 100644 index 00000000000..f2bfc03d489 --- /dev/null +++ b/test/snapshots/concurrentBuildsTest.js.snap @@ -0,0 +1,891 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`linux concurrent 1`] = ` +{ + "linux": [ + { + "arch": "armv7l", + "file": "Test Concurrent-1.1.0-armv7l.AppImage", + "safeArtifactName": "Test-Concurrent-1.1.0-armv7l.AppImage", + "updateInfo": { + "blockMapSize": "@blockMapSize", + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x86_64.AppImage", + "safeArtifactName": "Test-Concurrent-1.1.0-x86_64.AppImage", + "updateInfo": { + "blockMapSize": "@blockMapSize", + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "armv7l", + "file": "Test Concurrent-1.1.0-armv7l.rpm", + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x86_64.rpm", + }, + ], +} +`; + +exports[`mac concurrent 1`] = ` +{ + "mac": [ + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.dmg", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.dmg", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-arm64.dmg.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.dmg.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.dmg", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.dmg", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-x64.dmg.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.dmg.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.zip", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.zip", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-arm64.zip.blockmap", + "safeArtifactName": "Test Concurrent-1.1.0-arm64.zip.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.zip", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.zip", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-x64.zip.blockmap", + "safeArtifactName": "Test Concurrent-1.1.0-x64.zip.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + ], +} +`; + +exports[`mac concurrent 2`] = ` +{ + "CFBundleDisplayName": "Test Concurrent", + "CFBundleExecutable": "Test Concurrent", + "CFBundleIconFile": "icon.icns", + "CFBundleIdentifier": "test-concurrent", + "CFBundleInfoDictionaryVersion": "6.0", + "CFBundleName": "Test Concurrent", + "CFBundlePackageType": "APPL", + "CFBundleShortVersionString": "1.1.0", + "ElectronAsarIntegrity": { + "Resources/app.asar": { + "algorithm": "SHA256", + "hash": "hash", + }, + }, + "LSApplicationCategoryType": "your.app.category.type", + "LSEnvironment": { + "MallocNanoZone": "0", + }, + "NSAppTransportSecurity": {}, + "NSBluetoothAlwaysUsageDescription": "This app needs access to Bluetooth", + "NSBluetoothPeripheralUsageDescription": "This app needs access to Bluetooth", + "NSHighResolutionCapable": true, + "NSPrincipalClass": "AtomApplication", + "NSSupportsAutomaticGraphicsSwitching": true, +} +`; + +exports[`mac concurrent 3`] = ` +{ + "CFBundleDisplayName": "Test Concurrent", + "CFBundleExecutable": "Test Concurrent", + "CFBundleIconFile": "icon.icns", + "CFBundleIdentifier": "test-concurrent", + "CFBundleInfoDictionaryVersion": "6.0", + "CFBundleName": "Test Concurrent", + "CFBundlePackageType": "APPL", + "CFBundleShortVersionString": "1.1.0", + "ElectronAsarIntegrity": { + "Resources/app.asar": { + "algorithm": "SHA256", + "hash": "hash", + }, + }, + "LSApplicationCategoryType": "your.app.category.type", + "LSEnvironment": { + "MallocNanoZone": "0", + }, + "NSAppTransportSecurity": {}, + "NSBluetoothAlwaysUsageDescription": "This app needs access to Bluetooth", + "NSBluetoothPeripheralUsageDescription": "This app needs access to Bluetooth", + "NSHighResolutionCapable": true, + "NSPrincipalClass": "AtomApplication", + "NSSupportsAutomaticGraphicsSwitching": true, +} +`; + +exports[`mac/win/linux concurrent 1`] = ` +{ + "linux": [ + { + "arch": "armv7l", + "file": "Test Concurrent-1.1.0-armv7l.AppImage", + "safeArtifactName": "Test-Concurrent-1.1.0-armv7l.AppImage", + "updateInfo": { + "blockMapSize": "@blockMapSize", + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x86_64.AppImage", + "safeArtifactName": "Test-Concurrent-1.1.0-x86_64.AppImage", + "updateInfo": { + "blockMapSize": "@blockMapSize", + "sha512": "@sha512", + "size": "@size", + }, + }, + ], + "mac": [ + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.dmg", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.dmg", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-arm64.dmg.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.dmg.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.dmg", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.dmg", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-x64.dmg.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.dmg.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.zip", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.zip", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-arm64.zip.blockmap", + "safeArtifactName": "Test Concurrent-1.1.0-arm64.zip.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.zip", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.zip", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-x64.zip.blockmap", + "safeArtifactName": "Test Concurrent-1.1.0-x64.zip.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + ], + "win": [ + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.exe", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.exe", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-arm64.exe.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.exe.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.exe", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.exe", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-x64.exe.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.exe.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0.exe", + "safeArtifactName": "Test-Concurrent-1.1.0.exe", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0.exe.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0.exe.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + ], +} +`; + +exports[`mac/win/linux concurrent 2`] = ` +{ + "CFBundleDisplayName": "Test Concurrent", + "CFBundleExecutable": "Test Concurrent", + "CFBundleIconFile": "icon.icns", + "CFBundleIdentifier": "test-concurrent", + "CFBundleInfoDictionaryVersion": "6.0", + "CFBundleName": "Test Concurrent", + "CFBundlePackageType": "APPL", + "CFBundleShortVersionString": "1.1.0", + "ElectronAsarIntegrity": { + "Resources/app.asar": { + "algorithm": "SHA256", + "hash": "hash", + }, + }, + "LSApplicationCategoryType": "your.app.category.type", + "LSEnvironment": { + "MallocNanoZone": "0", + }, + "NSAppTransportSecurity": {}, + "NSBluetoothAlwaysUsageDescription": "This app needs access to Bluetooth", + "NSBluetoothPeripheralUsageDescription": "This app needs access to Bluetooth", + "NSHighResolutionCapable": true, + "NSPrincipalClass": "AtomApplication", + "NSSupportsAutomaticGraphicsSwitching": true, +} +`; + +exports[`mac/win/linux concurrent 3`] = ` +{ + "CFBundleDisplayName": "Test Concurrent", + "CFBundleExecutable": "Test Concurrent", + "CFBundleIconFile": "icon.icns", + "CFBundleIdentifier": "test-concurrent", + "CFBundleInfoDictionaryVersion": "6.0", + "CFBundleName": "Test Concurrent", + "CFBundlePackageType": "APPL", + "CFBundleShortVersionString": "1.1.0", + "ElectronAsarIntegrity": { + "Resources/app.asar": { + "algorithm": "SHA256", + "hash": "hash", + }, + }, + "LSApplicationCategoryType": "your.app.category.type", + "LSEnvironment": { + "MallocNanoZone": "0", + }, + "NSAppTransportSecurity": {}, + "NSBluetoothAlwaysUsageDescription": "This app needs access to Bluetooth", + "NSBluetoothPeripheralUsageDescription": "This app needs access to Bluetooth", + "NSHighResolutionCapable": true, + "NSPrincipalClass": "AtomApplication", + "NSSupportsAutomaticGraphicsSwitching": true, +} +`; + +exports[`mac/win/linux concurrent 4`] = ` +[ + "/", + "/opt/", + "/usr/", + "/opt/TestApp/", + "/opt/TestApp/chrome-sandbox", + "/opt/TestApp/chrome_100_percent.pak", + "/opt/TestApp/chrome_200_percent.pak", + "/opt/TestApp/chrome_crashpad_handler", + "/opt/TestApp/icudtl.dat", + "/opt/TestApp/libEGL.so", + "/opt/TestApp/libffmpeg.so", + "/opt/TestApp/libGLESv2.so", + "/opt/TestApp/libvk_swiftshader.so", + "/opt/TestApp/libvulkan.so.1", + "/opt/TestApp/LICENSE.electron.txt", + "/opt/TestApp/LICENSES.chromium.html", + "/opt/TestApp/resources.pak", + "/opt/TestApp/snapshot_blob.bin", + "/opt/TestApp/testapp", + "/opt/TestApp/v8_context_snapshot.bin", + "/opt/TestApp/vk_swiftshader_icd.json", + "/usr/share/", + "/opt/TestApp/resources/", + "/opt/TestApp/resources/app.asar", + "/opt/TestApp/resources/apparmor-profile", + "/usr/share/applications/", + "/usr/share/applications/testapp.desktop", + "/usr/share/doc/", + "/usr/share/icons/", + "/usr/share/doc/testapp/", + "/usr/share/doc/testapp/changelog.gz", + "/usr/share/icons/hicolor/", + "/usr/share/icons/hicolor/128x128/", + "/usr/share/icons/hicolor/16x16/", + "/usr/share/icons/hicolor/256x256/", + "/usr/share/icons/hicolor/32x32/", + "/usr/share/icons/hicolor/48x48/", + "/usr/share/icons/hicolor/512x512/", + "/usr/share/icons/hicolor/64x64/", + "/usr/share/icons/hicolor/128x128/apps/", + "/usr/share/icons/hicolor/128x128/apps/testapp.png", + "/usr/share/icons/hicolor/16x16/apps/", + "/usr/share/icons/hicolor/16x16/apps/testapp.png", + "/usr/share/icons/hicolor/256x256/apps/", + "/usr/share/icons/hicolor/256x256/apps/testapp.png", + "/usr/share/icons/hicolor/32x32/apps/", + "/usr/share/icons/hicolor/32x32/apps/testapp.png", + "/usr/share/icons/hicolor/48x48/apps/", + "/usr/share/icons/hicolor/48x48/apps/testapp.png", + "/usr/share/icons/hicolor/512x512/apps/", + "/usr/share/icons/hicolor/512x512/apps/testapp.png", + "/usr/share/icons/hicolor/64x64/apps/", + "/usr/share/icons/hicolor/64x64/apps/testapp.png", +] +`; + +exports[`mac/win/linux concurrent 5`] = ` +{ + "Architecture": "amd64", + "Depends": "libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6, xdg-utils, libatspi2.0-0, libuuid1, libsecret-1-0", + "Homepage": "http://foo.example.com", + "License": "MIT", + "Maintainer": "Foo Bar ", + "Package": "testapp", + "Priority": "optional", + "Recommends": "libappindicator3-1", + "Section": "default", + "Vendor": "Foo Bar ", +} +`; + +exports[`mac/win/linux concurrent 6`] = `"Test Application (test quite “ #378)"`; + +exports[`mac/win/linux concurrent 7`] = ` +[ + "/", + "/opt/", + "/usr/", + "/opt/TestApp/", + "/opt/TestApp/chrome-sandbox", + "/opt/TestApp/chrome_100_percent.pak", + "/opt/TestApp/chrome_200_percent.pak", + "/opt/TestApp/chrome_crashpad_handler", + "/opt/TestApp/icudtl.dat", + "/opt/TestApp/libEGL.so", + "/opt/TestApp/libffmpeg.so", + "/opt/TestApp/libGLESv2.so", + "/opt/TestApp/libvk_swiftshader.so", + "/opt/TestApp/libvulkan.so.1", + "/opt/TestApp/LICENSE.electron.txt", + "/opt/TestApp/LICENSES.chromium.html", + "/opt/TestApp/resources.pak", + "/opt/TestApp/snapshot_blob.bin", + "/opt/TestApp/testapp", + "/opt/TestApp/v8_context_snapshot.bin", + "/opt/TestApp/vk_swiftshader_icd.json", + "/usr/share/", + "/opt/TestApp/resources/", + "/opt/TestApp/resources/app.asar", + "/opt/TestApp/resources/apparmor-profile", + "/usr/share/applications/", + "/usr/share/applications/testapp.desktop", + "/usr/share/doc/", + "/usr/share/icons/", + "/usr/share/doc/testapp/", + "/usr/share/doc/testapp/changelog.gz", + "/usr/share/icons/hicolor/", + "/usr/share/icons/hicolor/128x128/", + "/usr/share/icons/hicolor/16x16/", + "/usr/share/icons/hicolor/256x256/", + "/usr/share/icons/hicolor/32x32/", + "/usr/share/icons/hicolor/48x48/", + "/usr/share/icons/hicolor/512x512/", + "/usr/share/icons/hicolor/64x64/", + "/usr/share/icons/hicolor/128x128/apps/", + "/usr/share/icons/hicolor/128x128/apps/testapp.png", + "/usr/share/icons/hicolor/16x16/apps/", + "/usr/share/icons/hicolor/16x16/apps/testapp.png", + "/usr/share/icons/hicolor/256x256/apps/", + "/usr/share/icons/hicolor/256x256/apps/testapp.png", + "/usr/share/icons/hicolor/32x32/apps/", + "/usr/share/icons/hicolor/32x32/apps/testapp.png", + "/usr/share/icons/hicolor/48x48/apps/", + "/usr/share/icons/hicolor/48x48/apps/testapp.png", + "/usr/share/icons/hicolor/512x512/apps/", + "/usr/share/icons/hicolor/512x512/apps/testapp.png", + "/usr/share/icons/hicolor/64x64/apps/", + "/usr/share/icons/hicolor/64x64/apps/testapp.png", +] +`; + +exports[`mac/win/linux concurrent 8`] = ` +{ + "Architecture": "armhf", + "Depends": "libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6, xdg-utils, libatspi2.0-0, libuuid1, libsecret-1-0", + "Homepage": "http://foo.example.com", + "License": "MIT", + "Maintainer": "Foo Bar ", + "Package": "testapp", + "Priority": "optional", + "Recommends": "libappindicator3-1", + "Section": "default", + "Vendor": "Foo Bar ", +} +`; + +exports[`mac/win/linux concurrent 9`] = `"Test Application (test quite “ #378)"`; + +exports[`win concurrent - all targets 1`] = ` +{ + "win": [ + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.7z", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.7z", + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.7z", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.7z", + }, + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.appx", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.appx", + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.appx", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.appx", + }, + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.exe", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.exe", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-arm64.exe.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.exe.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.exe", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.exe", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-x64.exe.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.exe.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0.exe", + "safeArtifactName": "Test-Concurrent-1.1.0.exe", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0.exe.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0.exe.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.exe", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.exe", + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.exe", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.exe", + }, + { + "file": "Test Concurrent-1.1.0.exe", + "safeArtifactName": "Test-Concurrent-1.1.0.exe", + }, + { + "arch": "x64", + "file": "concurrent-1.1.0-full.nupkg", + }, + { + "arch": "arm64", + "file": "concurrent-1.1.0-full.nupkg", + }, + { + "arch": "x64", + "file": "RELEASES", + }, + { + "arch": "arm64", + "file": "RELEASES", + }, + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.exe", + "safeArtifactName": "concurrent-Setup-1.1.0-arm64.exe", + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.exe", + "safeArtifactName": "concurrent-Setup-1.1.0.exe", + }, + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.tar.bz2", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.tar.bz2", + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.tar.bz2", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.tar.bz2", + }, + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.tar.gz", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.tar.gz", + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.tar.gz", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.tar.gz", + }, + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.tar.xz", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.tar.xz", + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.tar.xz", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.tar.xz", + }, + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.zip", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.zip", + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.zip", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.zip", + }, + ], +} +`; + +exports[`win concurrent - all targets 2`] = ` +[ + "concurrent.nuspec", + "lib/", + "lib/net45/", + "lib/net45/chrome_100_percent.pak", + "lib/net45/chrome_200_percent.pak", + "lib/net45/d3dcompiler_47.dll", + "lib/net45/ffmpeg.dll", + "lib/net45/icudtl.dat", + "lib/net45/libEGL.dll", + "lib/net45/libGLESv2.dll", + "lib/net45/LICENSE.electron.txt", + "lib/net45/LICENSES.chromium.html", + "lib/net45/resources.pak", + "lib/net45/snapshot_blob.bin", + "lib/net45/squirrel.exe", + "lib/net45/Test Concurrent.exe", + "lib/net45/Test Concurrent_ExecutionStub.exe", + "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/", + "lib/net45/resources/app.asar", + "package/", + "package/services/", + "package/services/metadata/", + "package/services/metadata/core-properties/", + "[Content_Types].xml", + "_rels/", + "_rels/.rels", +] +`; + +exports[`win concurrent - all targets 3`] = ` +[ + "concurrent.nuspec", + "lib/", + "lib/net45/", + "lib/net45/chrome_100_percent.pak", + "lib/net45/chrome_200_percent.pak", + "lib/net45/d3dcompiler_47.dll", + "lib/net45/ffmpeg.dll", + "lib/net45/icudtl.dat", + "lib/net45/libEGL.dll", + "lib/net45/libGLESv2.dll", + "lib/net45/LICENSE.electron.txt", + "lib/net45/LICENSES.chromium.html", + "lib/net45/resources.pak", + "lib/net45/snapshot_blob.bin", + "lib/net45/squirrel.exe", + "lib/net45/Test Concurrent.exe", + "lib/net45/Test Concurrent_ExecutionStub.exe", + "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/", + "lib/net45/resources/app.asar", + "package/", + "package/services/", + "package/services/metadata/", + "package/services/metadata/core-properties/", + "[Content_Types].xml", + "_rels/", + "_rels/.rels", +] +`; + +exports[`win concurrent 1`] = ` +{ + "win": [ + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.exe", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.exe", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-arm64.exe.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.exe.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.exe", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.exe", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-x64.exe.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.exe.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0.exe", + "safeArtifactName": "Test-Concurrent-1.1.0.exe", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0.exe.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0.exe.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + ], +} +`; + +exports[`win/linux concurrent 1`] = ` +{ + "linux": [ + { + "arch": "armv7l", + "file": "Test Concurrent-1.1.0-armv7l.AppImage", + "safeArtifactName": "Test-Concurrent-1.1.0-armv7l.AppImage", + "updateInfo": { + "blockMapSize": "@blockMapSize", + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x86_64.AppImage", + "safeArtifactName": "Test-Concurrent-1.1.0-x86_64.AppImage", + "updateInfo": { + "blockMapSize": "@blockMapSize", + "sha512": "@sha512", + "size": "@size", + }, + }, + ], + "win": [ + { + "arch": "arm64", + "file": "Test Concurrent-1.1.0-arm64.exe", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.exe", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-arm64.exe.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0-arm64.exe.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "arch": "x64", + "file": "Test Concurrent-1.1.0-x64.exe", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.exe", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0-x64.exe.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0-x64.exe.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0.exe", + "safeArtifactName": "Test-Concurrent-1.1.0.exe", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + { + "file": "Test Concurrent-1.1.0.exe.blockmap", + "safeArtifactName": "Test-Concurrent-1.1.0.exe.blockmap", + "updateInfo": { + "sha512": "@sha512", + "size": "@size", + }, + }, + ], +} +`; diff --git a/test/snapshots/mac/dmgTest.js.snap b/test/snapshots/mac/dmgTest.js.snap index 73086613460..4ef17116549 100644 --- a/test/snapshots/mac/dmgTest.js.snap +++ b/test/snapshots/mac/dmgTest.js.snap @@ -158,6 +158,7 @@ exports[`dmg > background color 1`] = ` }, ], "format": "UDRO", + "title": "Bar", "writeUpdateInfo": false, } `; diff --git a/test/src/concurrentBuildsTest.ts b/test/src/concurrentBuildsTest.ts new file mode 100644 index 00000000000..3436494efd3 --- /dev/null +++ b/test/src/concurrentBuildsTest.ts @@ -0,0 +1,150 @@ +import { Arch, Configuration, DIR_TARGET, Platform } from "app-builder-lib" +import { deepAssign } from "builder-util" +import { TmpDir } from "temp-file" +import { assertPack, modifyPackageJson } from "./helpers/packTester" + +const options = { timeout: 15 * 60 * 1000 } + +const winTargets = Platform.WINDOWS.createTarget([DIR_TARGET, "nsis"], Arch.x64, Arch.arm64) +const macTargets = Platform.MAC.createTarget([DIR_TARGET, "zip", "dmg", "mas"], Arch.arm64, Arch.x64) +const linuxTargets = Platform.LINUX.createTarget([DIR_TARGET, "AppImage"], Arch.x64, Arch.armv7l) + +const config: Configuration = { + productName: "Test Concurrent", + appId: "test-concurrent", + artifactName: "${productName}-${version}-${arch}.${ext}", + compression: "store", +} +const projectDirCreated = async (projectDir: string, tmpDir: TmpDir) => { + const buildConfig = (data: any, isApp: boolean) => { + deepAssign(data, { + name: "concurrent", // needs to be lowercase for fpm targets (can't use default fixture TestApp) + version: "1.1.0", + ...(!isApp ? { build: config } : {}), // build config is only allowed in "dev" (root) package.json in two-package.json setups + }) + } + await modifyPackageJson(projectDir, (data: any) => buildConfig(data, true), true) + await modifyPackageJson(projectDir, (data: any) => buildConfig(data, false), false) +} + +test.ifLinux("win/linux concurrent", options, ({ expect }) => { + const targets = new Map([...winTargets, ...linuxTargets]) + return assertPack( + expect, + "test-app", + { + targets, + config: { + concurrency: { + jobs: Object.keys(targets).length, + }, + ...config, + }, + }, + { + projectDirCreated, + } + ) +}) + +test.ifMac("mac/win/linux concurrent", options, ({ expect }) => { + const targets = new Map([...winTargets, ...macTargets, ...linuxTargets]) + return assertPack( + expect, + "test-app", + { + targets, + config: { + concurrency: { + jobs: Object.keys(targets).length, + }, + ...config, + }, + }, + { + projectDirCreated, + } + ) +}) + +test.ifMac("mac concurrent", options, ({ expect }) => { + const targets = macTargets + return assertPack( + expect, + "test-app", + { + targets, + config: { + concurrency: { + jobs: Object.keys(targets).length, + }, + ...config, + }, + }, + { + projectDirCreated, + } + ) +}) + +test.ifNotMac("win concurrent", options, ({ expect }) => { + const targets = winTargets + return assertPack( + expect, + "test-app", + { + targets, + config: { + concurrency: { + jobs: Object.keys(targets).length, + }, + ...config, + }, + }, + { + projectDirCreated, + } + ) +}) + +test.ifNotWindows("linux concurrent", options, ({ expect }) => { + const targets = Platform.LINUX.createTarget([DIR_TARGET, "rpm", "AppImage"], Arch.x64, Arch.armv7l) + return assertPack( + expect, + "test-app", + { + targets, + config: { + concurrency: { + jobs: Object.keys(targets).length, + }, + ...config, + }, + }, + { + projectDirCreated, + } + ) +}) + +test.ifWindows("win concurrent - all targets", options, ({ expect }) => { + const targetList = [DIR_TARGET, `appx`, `nsis`, `portable`, `squirrel`, `7z`, `zip`, `tar.xz`, `tar.gz`, `tar.bz2`] + const targets = Platform.WINDOWS.createTarget(targetList, Arch.x64, Arch.arm64) + return assertPack( + expect, + "test-app", + { + targets, + config: { + concurrency: { + jobs: Object.keys(targets).length, + }, + win: { target: targetList }, + ...config, + }, + }, + { + projectDirCreated, + } + ) +}) diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index 9979a6c2441..ca6ba211a9b 100644 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -325,10 +325,10 @@ async function checkLinuxResult(expect: ExpectStatic, outDir: string, packager: } const appInfo = packager.appInfo - const packageFile = `${outDir}/TestApp_${appInfo.version}_${arch === Arch.ia32 ? "i386" : arch === Arch.x64 ? "amd64" : "armv7l"}.deb` + const packageFile = `${outDir}/${appInfo.name}_${appInfo.version}_${arch === Arch.ia32 ? "i386" : arch === Arch.x64 ? "amd64" : "armv7l"}.deb` expect(await getContents(packageFile)).toMatchSnapshot() if (arch === Arch.ia32) { - expect(await getContents(`${outDir}/TestApp_${appInfo.version}_i386.deb`)).toMatchSnapshot() + expect(await getContents(`${outDir}/${appInfo.name}_${appInfo.version}_i386.deb`)).toMatchSnapshot() } const control = parseDebControl( diff --git a/test/src/mac/dmgTest.ts b/test/src/mac/dmgTest.ts index fdd6cf67e63..93df6fc3996 100644 --- a/test/src/mac/dmgTest.ts +++ b/test/src/mac/dmgTest.ts @@ -30,6 +30,9 @@ describe("dmg", { sequential: true }, () => { // dmg can mount only one volume name, so, to test in parallel, we set different product name productName: "NoBuildDirectory", publish: null, + dmg: { + title: "Foo", + }, }, effectiveOptionComputed: async it => { if (!("volumePath" in it)) { @@ -60,6 +63,7 @@ describe("dmg", { sequential: true }, () => { backgroundColor: "orange", // speed-up test writeUpdateInfo: false, + title: "Bar", }, }, effectiveOptionComputed: async it => { @@ -90,6 +94,7 @@ describe("dmg", { sequential: true }, () => { icon: "foo.icns", // speed-up test writeUpdateInfo: false, + title: "Custom Background", }, }, effectiveOptionComputed: async it => { @@ -120,6 +125,9 @@ describe("dmg", { sequential: true }, () => { targets: defaultTarget, config: { publish: null, + dmg: { + title: "Retina Background", + }, }, effectiveOptionComputed: async it => { expect(it.specification.background).toMatch(/\.tiff$/) @@ -155,6 +163,7 @@ describe("dmg", { sequential: true }, () => { publish: null, productName: "NoApplicationsLink", dmg: { + title: "No Applications", contents: [ { x: 110, @@ -252,6 +261,9 @@ describe("dmg", { sequential: true }, () => { bundleShortVersion: "2017.1-alpha5", darkModeSupport: true, }, + dmg: { + title: "bundleShortVersion", + }, }, }) ) @@ -263,6 +275,7 @@ describe("dmg", { sequential: true }, () => { publish: null, dmg: { icon: null, + title: "Disable Icon", }, mac: { bundleVersion: "50", @@ -281,6 +294,9 @@ describe("dmg", { sequential: true }, () => { targets: dmgTarget, config: { publish: null, + dmg: { + title: "Foo" + Math.floor(Math.random() * 1000), + }, }, } diff --git a/test/src/mac/macPackagerTest.ts b/test/src/mac/macPackagerTest.ts index d8b347d4a21..0f08b4d6262 100644 --- a/test/src/mac/macPackagerTest.ts +++ b/test/src/mac/macPackagerTest.ts @@ -23,6 +23,9 @@ test.ifMac("two-package", ({ expect }) => timestamp: undefined, notarize: false, }, + dmg: { + title: "Foo1", + }, //tslint:disable-next-line:no-invalid-template-strings artifactName: "${name}-${version}-${os}-${arch}.${ext}", electronFuses: { @@ -60,6 +63,9 @@ test.ifMac("one-package", ({ expect }) => url: "https://develar.s3.amazonaws.com/test/${os}/${arch}", }, downloadAlternateFFmpeg: false, + dmg: { + title: "Bar2", + }, mac: { // test appId per platform appId: "foo",