Skip to content

Commit

Permalink
fix(dmg): Unable to build with custom path
Browse files Browse the repository at this point in the history
  • Loading branch information
develar committed Dec 31, 2016
1 parent 598f283 commit 89f7f6a
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 30 deletions.
4 changes: 4 additions & 0 deletions .idea/dictionaries/develar.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion .idea/rc-producer.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion packages/electron-builder/src/macPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ export default class MacPackager extends PlatformPackager<MacOptions> {
}

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)
}
Expand Down
4 changes: 4 additions & 0 deletions packages/electron-builder/src/options/macOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
55 changes: 32 additions & 23 deletions packages/electron-builder/src/targets/dmg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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,

Expand All @@ -119,8 +101,6 @@ export class DmgTarget extends Target {
env.volumeIcon = volumeIcon
}

await BluebirdPromise.all<any>(promises)

if (specification.backgroundColor != null || specification.background == null) {
env.backgroundColor = specification.backgroundColor || "#ffffff"
env.windowWidth = window.width || 540
Expand All @@ -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<any>(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`)
Expand Down
3 changes: 1 addition & 2 deletions packages/electron-builder/templates/dmg/dmgProperties.pl
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
16 changes: 16 additions & 0 deletions test/out/mac/__snapshots__/dmgTest.js.snap
Original file line number Diff line number Diff line change
@@ -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`;
9 changes: 8 additions & 1 deletion test/src/helpers/fileAssert.ts
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -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()) {
Expand Down
47 changes: 45 additions & 2 deletions test/src/mac/dmgTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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: {
Expand Down

0 comments on commit 89f7f6a

Please sign in to comment.