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: {