From 89f7f6ab1aaf482a2d6147f6cbd81be4f8356f37 Mon Sep 17 00:00:00 2001 From: develar Date: Sat, 31 Dec 2016 13:43:30 +0100 Subject: [PATCH] fix(dmg): Unable to build with custom path Close #847 #1054 --- .idea/dictionaries/develar.xml | 4 ++ .idea/rc-producer.yml | 3 +- packages/electron-builder/src/macPackager.ts | 4 +- .../src/options/macOptions.ts | 4 ++ packages/electron-builder/src/targets/dmg.ts | 55 +++++++++++-------- .../templates/dmg/dmgProperties.pl | 3 +- test/out/mac/__snapshots__/dmgTest.js.snap | 16 ++++++ test/src/helpers/fileAssert.ts | 9 ++- test/src/mac/dmgTest.ts | 47 +++++++++++++++- 9 files changed, 115 insertions(+), 30 deletions(-) create mode 100644 test/out/mac/__snapshots__/dmgTest.js.snap diff --git a/.idea/dictionaries/develar.xml b/.idea/dictionaries/develar.xml index 3caff506b5f..75401b3ddb4 100644 --- a/.idea/dictionaries/develar.xml +++ b/.idea/dictionaries/develar.xml @@ -81,10 +81,12 @@ icnv iconset iconutil + icvo idms ifdef ifmacrodef ifndef + iloc inno insertmacro installmode @@ -143,6 +145,7 @@ packagejson pacman passin + perllib pkcs postinstall powerrequired @@ -162,6 +165,7 @@ rimraf scripthost semver + setfinderinfo shorthash signcode signtool diff --git a/.idea/rc-producer.yml b/.idea/rc-producer.yml index d32be87c1a5..14651b3aea6 100644 --- a/.idea/rc-producer.yml +++ b/.idea/rc-producer.yml @@ -11,4 +11,5 @@ scriptArgs: ["-i", "--env", "jest-environment-node-debug", "-t", "${0regExp}", *filePattern] rcName: "${fileNameWithoutExt}.${0}" env: - TEST_APP_TMP_DIR: /tmp/electron-builder-test \ No newline at end of file + TEST_APP_TMP_DIR: /tmp/electron-builder-test + DEBUG: electron-builder \ No newline at end of file diff --git a/packages/electron-builder/src/macPackager.ts b/packages/electron-builder/src/macPackager.ts index 58e6bfdbfe2..5a7203dd334 100644 --- a/packages/electron-builder/src/macPackager.ts +++ b/packages/electron-builder/src/macPackager.ts @@ -112,7 +112,9 @@ export default class MacPackager extends PlatformPackager { } if (name == null) { - const message = `App is not signed: cannot find valid ${isMas ? '"3rd Party Mac Developer Application" identity' : `"Developer ID Application" identity or custom non-Apple code signing certificate`}, see https://github.com/electron-userland/electron-builder/wiki/Code-Signing` + const message = process.env.CSC_IDENTITY_AUTO_DISCOVERY === "false" ? + `App is not signed: env CSC_IDENTITY_AUTO_DISCOVERY is set to false` : + `App is not signed: cannot find valid ${isMas ? '"3rd Party Mac Developer Application" identity' : `"Developer ID Application" identity or custom non-Apple code signing certificate`}, see https://github.com/electron-userland/electron-builder/wiki/Code-Signing` if (isMas || this.platformSpecificBuildOptions.forceCodeSigning) { throw new Error(message) } diff --git a/packages/electron-builder/src/options/macOptions.ts b/packages/electron-builder/src/options/macOptions.ts index bd92350debb..13b39cdd900 100644 --- a/packages/electron-builder/src/options/macOptions.ts +++ b/packages/electron-builder/src/options/macOptions.ts @@ -143,6 +143,10 @@ export interface DmgContent { x: number y: number type?: "link" | "file" + /* + The name of the file within the DMG. Defaults to basename of `path`. + */ + name?: string path?: string } diff --git a/packages/electron-builder/src/targets/dmg.ts b/packages/electron-builder/src/targets/dmg.ts index 54d0074feea..2172fe6d35d 100644 --- a/packages/electron-builder/src/targets/dmg.ts +++ b/packages/electron-builder/src/targets/dmg.ts @@ -2,10 +2,10 @@ import { deepAssign } from "../util/deepAssign" import * as path from "path" import { log, warn } from "../util/log" import { PlatformPackager } from "../platformPackager" -import { MacOptions, DmgOptions, DmgContent } from "../options/macOptions" +import { MacOptions, DmgOptions } from "../options/macOptions" import BluebirdPromise from "bluebird-lst-c" import { debug, use, exec, isEmptyOrSpaces, spawn } from "../util/util" -import { copy, unlink, outputFile, remove } from "fs-extra-p" +import { copy, unlink, outputFile, remove, readFile } from "fs-extra-p" import { executeFinally } from "../util/promise" import sanitizeFileName from "sanitize-filename" import { Arch } from "../metadata" @@ -64,7 +64,6 @@ export class DmgTarget extends Target { await attachAndExecute(tempDmg, true, async () => { const promises = [ specification.background == null ? remove(`${volumePath}/.background`) : unlink(`${volumePath}/.background/DSStorePlaceHolder`), - exec("ln", ["-s", "/Applications", `${volumePath}/Applications`]), ] let contents = specification.contents @@ -79,27 +78,10 @@ export class DmgTarget extends Target { ] } - let location = contents.find(it => it.path == null && it.type !== "link") - if (location == null) { - location = contents.find(it => { - if (it.path != null && it.path.endsWith(".app") && it.type !== "link") { - warn(`Do not specify path for application: "${it.path}". Actual path to app will be used instead.`) - return true - } - return false - })! - } - - const applicationsLocation: DmgContent = contents.find(it => it.type === "link" && (it.path === "/Applications" || it.path === "Applications"))! - const window = specification.window! const env = Object.assign({}, process.env, { volumePath: volumePath, appFileName: `${packager.appInfo.productFilename}.app`, - appFileX: location.x, - appFileY: location.y, - APPLICATIONS_LINK_X: applicationsLocation.x, - APPLICATIONS_LINK_Y: applicationsLocation.y, iconSize: specification.iconSize || 80, iconTextSize: specification.iconTextSize || 12, @@ -119,8 +101,6 @@ export class DmgTarget extends Target { env.volumeIcon = volumeIcon } - await BluebirdPromise.all(promises) - if (specification.backgroundColor != null || specification.background == null) { env.backgroundColor = specification.backgroundColor || "#ffffff" env.windowWidth = window.width || 540 @@ -145,12 +125,41 @@ export class DmgTarget extends Target { env.backgroundFilename = backgroundFilename } - await exec("/usr/bin/perl", [path.join(this.helperDir, "dmgProperties.pl")], { + let entries = "" + for (const c of contents) { + if (c.path != null && c.path.endsWith(".app") && c.type !== "link") { + warn(`Do not specify path for application: "${c.path}". Actual path to app will be used instead.`) + } + + let entryPath = c.path || `${packager.appInfo.productFilename}.app` + if (entryPath.startsWith("/")) { + entryPath = entryPath.substring(1) + } + + const entryName = c.name || path.basename(entryPath) + entries += `&makeEntries("${entryName}", Iloc_xy => [ ${c.x}, ${c.y} ]),\n` + + if (c.type === "link") { + promises.push(exec("ln", ["-s", `/${entryPath}`, `${volumePath}/${entryName}`])) + } + } + debug(entries) + + const dmgPropertiesFile = await packager.getTempFile("dmgProperties.pl") + + promises.push(outputFile(dmgPropertiesFile, (await readFile(path.join(this.helperDir, "dmgProperties.pl"), "utf-8")).replace("$ENTRIES", entries))) + await BluebirdPromise.all(promises) + + await exec("/usr/bin/perl", [dmgPropertiesFile], { cwd: this.helperDir, env: env }) await exec("sync") + + if (packager.options.effectiveOptionComputed != null && await packager.options.effectiveOptionComputed([volumePath, specification])) { + return + } }) const artifactPath = path.join(appOutDir, `${appInfo.productFilename}-${appInfo.version}.dmg`) diff --git a/packages/electron-builder/templates/dmg/dmgProperties.pl b/packages/electron-builder/templates/dmg/dmgProperties.pl index b48b52a5c81..b63ac041dc5 100644 --- a/packages/electron-builder/templates/dmg/dmgProperties.pl +++ b/packages/electron-builder/templates/dmg/dmgProperties.pl @@ -45,8 +45,7 @@ icvo => pack('A4 n A4 A4 n*', "icv4", $ENV{'iconSize'}, "none", "botm", 0, 0, 0, 0, 0, 1, 0, 100, 1), icvt => $ENV{'iconTextSize'} ), - &makeEntries(Encode::decode("UTF-8", $ENV{'appFileName'}), Iloc_xy => [ $ENV{'appFileX'}, $ENV{'appFileY'} ]), - &makeEntries("Applications", Iloc_xy => [ $ENV{'APPLICATIONS_LINK_X'}, $ENV{'APPLICATIONS_LINK_Y'} ]), + $ENTRIES ); sub syscall_setfinderinfo { diff --git a/test/out/mac/__snapshots__/dmgTest.js.snap b/test/out/mac/__snapshots__/dmgTest.js.snap new file mode 100644 index 00000000000..2f6c7c0010c --- /dev/null +++ b/test/out/mac/__snapshots__/dmgTest.js.snap @@ -0,0 +1,16 @@ +exports[`test no Applications link 1`] = ` +Array [ + Object { + "x": 110, + "y": 150, + }, + Object { + "path": "/Applications/TextEdit.app", + "type": "link", + "x": 410, + "y": 440, + }, +] +`; + +exports[`test no build directory 1`] = `undefined`; diff --git a/test/src/helpers/fileAssert.ts b/test/src/helpers/fileAssert.ts index 1e41c5bf5df..ba94300bb46 100644 --- a/test/src/helpers/fileAssert.ts +++ b/test/src/helpers/fileAssert.ts @@ -1,4 +1,4 @@ -import { stat, Stats } from "fs-extra-p" +import { stat, lstat, Stats } from "fs-extra-p" import * as path from "path" import { exists } from "electron-builder/out/util/fs" @@ -28,6 +28,13 @@ class Assertions { } } + async isSymbolicLink() { + const info: Stats = await lstat(this.actual) + if (!info.isSymbolicLink()) { + throw new Error(`Path ${this.actual} is not a symlink`) + } + } + async isDirectory() { const info: Stats = await stat(this.actual) if (!info.isDirectory()) { diff --git a/test/src/mac/dmgTest.ts b/test/src/mac/dmgTest.ts index df9d522d1b3..ccc7df55972 100644 --- a/test/src/mac/dmgTest.ts +++ b/test/src/mac/dmgTest.ts @@ -11,10 +11,18 @@ test.ifMac("no build directory", app({ config: { // dmg can mount only one volume name, so, to test in parallel, we set different product name productName: "NoBuildDirectory", - } + }, + effectiveOptionComputed: async it => { + const volumePath = it[0] + const specification = it[1] + await assertThat(path.join(volumePath, ".background", "background.tiff")).isFile() + await assertThat(path.join(volumePath, "Applications")).isSymbolicLink() + expect(specification.contents).toMatchSnapshot() + return false + }, }, { expectedContents: ["NoBuildDirectory-1.1.0.dmg"], - projectDirCreated: projectDir => remove(path.join(projectDir, "build")) + projectDirCreated: projectDir => remove(path.join(projectDir, "build")), })) test.ifMac("custom background - new way", () => { @@ -45,6 +53,41 @@ test.ifMac("custom background - new way", () => { }) }) +test.ifMac("no Applications link", () => { + return assertPack("test-app-one", { + targets: Platform.MAC.createTarget(), + config: { + productName: "NoApplicationsLink", + dmg: { + "contents": [ + { + "x": 110, + "y": 150 + }, + { + "x": 410, + "y": 440, + "type": "link", + "path": "/Applications/TextEdit.app" + } + ], + }, + }, + effectiveOptionComputed: async it => { + const volumePath = it[0] + const specification = it[1] + await BluebirdPromise.all([ + assertThat(path.join(volumePath, ".background", "background.tiff")).isFile(), + assertThat(path.join(volumePath, "Applications")).doesNotExist(), + assertThat(path.join(volumePath, "TextEdit.app")).isSymbolicLink(), + assertThat(path.join(volumePath, "TextEdit.app")).isDirectory(), + ]) + expect(specification.contents).toMatchSnapshot() + return false + }, + }) +}) + test.ifMac("unset dmg icon", app({ targets: Platform.MAC.createTarget("dmg"), config: {