diff --git a/docs/Multi Platform Build.md b/docs/Multi Platform Build.md index 0d513513788..6df8098a663 100755 --- a/docs/Multi Platform Build.md +++ b/docs/Multi Platform Build.md @@ -49,6 +49,11 @@ To build app in distributable format for Windows on Linux: sudo apt-get install mono-devel -y ``` +* Install zip. + ``` + apt-get install zip + ``` + To build app in 32 bit from a machine with 64 bit: ``` diff --git a/package.json b/package.json index 56e34d69ad7..0ca4376f814 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "deep-assign": "^2.0.0", "electron-osx-sign-tf": "0.4.0-beta.0", "electron-packager-tf": "^7.0.2-beta.0", - "electron-winstaller-fixed": "~2.6.2", + "electron-winstaller-fixed": "~2.7.0", "fs-extra-p": "^1.0.1", "globby": "^4.0.0", "hosted-git-info": "^2.1.4", @@ -106,7 +106,7 @@ "should": "^8.3.1", "ts-babel": "^0.8.6", "tsconfig-glob": "^0.4.3", - "tslint": "^3.9.0-dev.0", + "tslint": "^3.10.0-dev.1", "typescript": "1.9.0-dev.20160511", "whitespace": "^2.0.0" }, diff --git a/src/cleanup.ts b/src/cleanup.ts index e2140153441..452aeefe407 100644 --- a/src/cleanup.ts +++ b/src/cleanup.ts @@ -46,7 +46,7 @@ async function main() { async function isRecentlyUsed(dir: string) { try { const lastUsed = parseInt(await readFile(path.join(dir, ".lastUsed"), "utf8"), 10) - if (!isNaN(lastUsed) && (Date.now() - lastUsed) < 3600000) { + if (!isNaN(lastUsed) && (Date.now() - lastUsed) < (3600000 * 2)) { return true } } diff --git a/src/linuxPackager.ts b/src/linuxPackager.ts index b1b06390e48..18fde509251 100755 --- a/src/linuxPackager.ts +++ b/src/linuxPackager.ts @@ -63,6 +63,15 @@ export class LinuxPackager extends PlatformPackager { return [].concat(...await BluebirdPromise.all(promises)) } + async pack(outDir: string, arch: string, postAsyncTasks: Array>): Promise { + const appOutDir = this.computeAppOutDir(outDir, arch) + await this.doPack(this.computePackOptions(outDir, arch), outDir, appOutDir, arch, this.customBuildOptions) + + if (this.options.dist) { + postAsyncTasks.push(this.packageInDistributableFormat(outDir, appOutDir, arch)) + } + } + private async computeDesktop(tempDir: string): Promise> { const tempFile = path.join(tempDir, this.appName + ".desktop") await outputFile(tempFile, this.debOptions.desktop || `[Desktop Entry] diff --git a/src/platformPackager.ts b/src/platformPackager.ts index 17da810a952..da10545e000 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -105,17 +105,11 @@ export abstract class PlatformPackager }) } - pack(outDir: string, arch: string, postAsyncTasks: Array>): Promise { - const appOutDir = this.computeAppOutDir(outDir, arch) - return this.doPack(this.computePackOptions(outDir, arch), outDir, appOutDir, arch, this.customBuildOptions, postAsyncTasks) - } + abstract pack(outDir: string, arch: string, postAsyncTasks: Array>): Promise - protected async doPack(options: ElectronPackagerOptions, outDir: string, appOutDir: string, arch: string, customBuildOptions: DC, postAsyncTasks: Array> | null = null) { + protected async doPack(options: ElectronPackagerOptions, outDir: string, appOutDir: string, arch: string, customBuildOptions: DC) { await this.packApp(options, appOutDir) await this.copyExtraResources(appOutDir, arch, customBuildOptions) - if (postAsyncTasks != null && this.options.dist) { - postAsyncTasks.push(this.packageInDistributableFormat(outDir, appOutDir, arch)) - } } protected computePackOptions(outDir: string, arch: string): ElectronPackagerOptions { @@ -137,6 +131,7 @@ export abstract class PlatformPackager asar: true, overwrite: true, "app-version": version, + "app-copyright": `Copyright © ${new Date().getFullYear()} ${this.metadata.author.name || this.appName}`, "build-version": buildVersion, tmpdir: false, "version-string": { @@ -187,8 +182,6 @@ export abstract class PlatformPackager return await BluebirdPromise.map(await this.getExtraResources(arch, customBuildOptions), it => copy(path.join(this.projectDir, it), path.join(resourcesDir, it))) } - protected abstract packageInDistributableFormat(outDir: string, appOutDir: string, arch: string): Promise - protected async computePackageUrl(): Promise { const url = this.metadata.homepage || this.devMetadata.homepage if (url != null) { diff --git a/src/winPackager.ts b/src/winPackager.ts index 3ac26bb2f1e..a6be57f4784 100644 --- a/src/winPackager.ts +++ b/src/winPackager.ts @@ -4,8 +4,9 @@ import { PlatformPackager, BuildInfo } from "./platformPackager" import { Platform, WinBuildOptions } from "./metadata" import * as path from "path" import { log, statOrNull } from "./util" -import { readFile, deleteFile, stat, rename, copy, emptyDir, writeFile, open, close, read } from "fs-extra-p" +import { readFile, deleteFile, rename, copy, emptyDir, writeFile, open, close, read, ensureDir } from "fs-extra-p" import { sign } from "signcode-tf" +import ElectronPackagerOptions = ElectronPackager.ElectronPackagerOptions //noinspection JSUnusedLocalSymbols const __awaiter = require("./awaiter") @@ -13,8 +14,6 @@ const __awaiter = require("./awaiter") export class WinPackager extends PlatformPackager { certFilePromise: Promise - extraNuGetFileSources: Promise> | null - loadingGifStat: Promise | null readonly iconPath: Promise @@ -56,32 +55,32 @@ export class WinPackager extends PlatformPackager { // we must check icon before pack because electron-packager uses icon and it leads to cryptic error message "spawn wine ENOENT" await this.iconPath + let appOutDir = this.computeAppOutDir(outDir, arch) + const packOptions = this.computePackOptions(outDir, arch) + if (!this.options.dist) { - return await super.pack(outDir, arch, postAsyncTasks) + await this.doPack(packOptions, outDir, appOutDir, arch, this.customBuildOptions) + return } - const appOutDir = this.computeAppOutDir(outDir, arch) + const unpackedDir = path.join(outDir, `win${arch === "x64" ? "" : `-${arch}`}-unpacked`) + const finalAppOut = path.join(outDir, `win${arch === "x64" ? "" : `-${arch}`}-unpacked`, "lib", "net45") const installerOut = computeDistOut(outDir, arch) log("Removing %s", installerOut) await BluebirdPromise.all([ - this.packApp(this.computePackOptions(outDir, arch), appOutDir), - emptyDir(installerOut) + this.packApp(packOptions, appOutDir), + emptyDir(installerOut), + emptyDir(unpackedDir) + // must be otherwise rename doesn't work + .then(() => ensureDir(finalAppOut)) ]) - const extraResources = await this.copyExtraResources(appOutDir, arch, this.customBuildOptions) - if (extraResources.length > 0) { - this.extraNuGetFileSources = BluebirdPromise.map(extraResources, file => { - return stat(file) - .then(it => { - const relativePath = path.relative(appOutDir, file) - const src = it.isDirectory() ? `${relativePath}${path.sep}**` : relativePath - return `` - }) - }) - } + await rename(appOutDir, finalAppOut) + appOutDir = finalAppOut + await this.copyExtraResources(appOutDir, arch, this.customBuildOptions) if (this.options.dist) { - postAsyncTasks.push(this.packageInDistributableFormat(outDir, appOutDir, arch)) + postAsyncTasks.push(this.packageInDistributableFormat(outDir, appOutDir, arch, packOptions)) } } @@ -102,7 +101,7 @@ export class WinPackager extends PlatformPackager { } } - protected async computeEffectiveDistOptions(appOutDir: string, installerOutDir: string): Promise { + protected async computeEffectiveDistOptions(appOutDir: string, installerOutDir: string, packOptions: ElectronPackagerOptions): Promise { let iconUrl = this.customBuildOptions.iconUrl || this.devMetadata.build.iconUrl if (iconUrl == null) { if (this.info.repositoryInfo != null) { @@ -120,6 +119,13 @@ export class WinPackager extends PlatformPackager { checkConflictingOptions(this.customBuildOptions) const projectUrl = await this.computePackageUrl() + const rceditOptions = { + "version-string": packOptions["version-string"], + "file-version": packOptions["build-version"], + "product-version": packOptions["app-version"], + } + rceditOptions["version-string"]!.LegalCopyright = packOptions["app-copyright"] + const options: any = Object.assign({ name: this.metadata.name, productName: this.appName, @@ -138,13 +144,14 @@ export class WinPackager extends PlatformPackager { skipUpdateIcon: true, usePackageJson: false, noMsi: true, - extraFileSpecs: this.extraNuGetFileSources == null ? null : ("\n" + (await this.extraNuGetFileSources).join("\n")), extraMetadataSpecs: projectUrl == null ? null : `\n ${projectUrl}`, + copyright: packOptions["app-copyright"], sign: { name: this.appName, site: projectUrl, overwrite: true, - } + }, + rcedit: rceditOptions, }, this.customBuildOptions) if (this.loadingGifStat != null) { @@ -154,9 +161,9 @@ export class WinPackager extends PlatformPackager { return options } - async packageInDistributableFormat(outDir: string, appOutDir: string, arch: string): Promise { + async packageInDistributableFormat(outDir: string, appOutDir: string, arch: string, packOptions: ElectronPackagerOptions): Promise { const installerOutDir = computeDistOut(outDir, arch) - await require("electron-winstaller-fixed").createWindowsInstaller(await this.computeEffectiveDistOptions(appOutDir, installerOutDir)) + await require("electron-winstaller-fixed").createWindowsInstaller(await this.computeEffectiveDistOptions(appOutDir, installerOutDir, packOptions)) const version = this.metadata.version const archSuffix = arch === "x64" ? "" : ("-" + arch) @@ -170,8 +177,8 @@ export class WinPackager extends PlatformPackager { } const promises: Array> = [ - rename(path.join(installerOutDir, "Setup.exe"), path.join(installerOutDir, `${this.appName}Setup-${version}${archSuffix}.exe`)) - .then(it => this.dispatchArtifactCreated(it, `${this.metadata.name}Setup-${version}${archSuffix}.exe`)), + rename(path.join(installerOutDir, "Setup.exe"), path.join(installerOutDir, `${this.appName} Setup ${version}${archSuffix}.exe`)) + .then(it => this.dispatchArtifactCreated(it, `${this.metadata.name}-Setup-${version}${archSuffix}.exe`)), ] if (archSuffix === "") { @@ -244,7 +251,7 @@ function isIco(buffer: Buffer): boolean { } export function computeDistOut(outDir: string, arch: string): string { - return path.join(outDir, "win" + (arch === "x64" ? "-x64" : "")) + return path.join(outDir, "win" + (arch === "x64" ? "" : "-arch")) } function checkConflictingOptions(options: any) { diff --git a/test/fixtures/test-app-one/package.json b/test/fixtures/test-app-one/package.json index 1bd432e4ac9..becb89c8939 100755 --- a/test/fixtures/test-app-one/package.json +++ b/test/fixtures/test-app-one/package.json @@ -3,7 +3,7 @@ "name": "TestApp", "version": "1.1.0", "homepage": "http://foo.example.com", - "description": "Test Application", + "description": "Test Application (test quite \" #378)", "scripts": { "start": "electron ." }, diff --git a/test/src/BuildTest.ts b/test/src/BuildTest.ts index 3fa70194cf5..adf8ecdaae5 100755 --- a/test/src/BuildTest.ts +++ b/test/src/BuildTest.ts @@ -1,10 +1,12 @@ import test from "./helpers/avaEx" import { assertPack, modifyPackageJson, outDirName } from "./helpers/packTester" +import { expectedWinContents } from "./helpers/expectedContents" import { move, outputFile, outputJson } from "fs-extra-p" import { Promise as BluebirdPromise } from "bluebird" import * as path from "path" import { assertThat } from "./helpers/fileAssert" import { Platform, PackagerOptions } from "out" +import pathSorter = require("path-sort") //noinspection JSUnusedLocalSymbols const __awaiter = require("out/awaiter") @@ -105,14 +107,16 @@ test("www as default dir", () => assertPack("test-app", { })) test("copy extra resource", async () => { - for (let platform of getPossiblePlatforms()) { + for (let platform of [Platform.WINDOWS]) { const osName = platform.buildConfigurationKey //noinspection SpellCheckingInspection await assertPack("test-app", { platform: [platform], // to check NuGet package - dist: platform === Platform.WINDOWS + dist: platform === Platform.WINDOWS, + cscLink: null, + cscInstallerLink: null, }, { tempDirCreated: (projectDir) => { return BluebirdPromise.all([ @@ -146,6 +150,9 @@ test("copy extra resource", async () => { if (platform === Platform.OSX) { resourcesDir = path.join(resourcesDir, "TestApp.app", "Contents", "Resources") } + else if (platform === Platform.WINDOWS) { + resourcesDir = path.join(projectDir, outDirName, "win-unpacked", "lib", "net45") + } await assertThat(path.join(resourcesDir, "foo")).isDirectory() await assertThat(path.join(resourcesDir, "foo", "nameWithoutDot")).isFile() await assertThat(path.join(resourcesDir, "bar", "hello.txt")).isFile() @@ -154,39 +161,13 @@ test("copy extra resource", async () => { await assertThat(path.join(resourcesDir, "platformSpecific")).isFile() await assertThat(path.join(resourcesDir, "ignoreMe.txt")).doesNotExist() }, - expectedContents: platform === Platform.WINDOWS ? [ - "lib/net45/content_resources_200_percent.pak", - "lib/net45/content_shell.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", - "lib/net45/LICENSES.chromium.html", - "lib/net45/msvcp120.dll", - "lib/net45/msvcr120.dll", - "lib/net45/natives_blob.bin", - "lib/net45/node.dll", - "lib/net45/platformSpecific", - "lib/net45/snapshot_blob.bin", - "lib/net45/TestApp.exe", - "lib/net45/ui_resources_200_percent.pak", - "lib/net45/Update.exe", - "lib/net45/vccorlib120.dll", - "lib/net45/version", - "lib/net45/xinput1_3.dll", + expectedContents: platform === Platform.WINDOWS ? pathSorter(expectedWinContents.concat( "lib/net45/bar/hello.txt", "lib/net45/bar/x64.txt", "lib/net45/foo/nameWithoutDot", - "lib/net45/locales/en-US.pak", - "lib/net45/resources/app.asar", - "lib/net45/resources/electron.asar", - "lib/net45/win/x64.txt", - "TestApp.nuspec", - "[Content_Types].xml", - "_rels/.rels" - ] : null, + "lib/net45/platformSpecific", + "lib/net45/win/x64.txt" + )) : null, }) } }) diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index 9e1f620ceab..d33e147689b 100755 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -144,7 +144,7 @@ async function checkLinuxResult(projectDir: string, packager: Packager, packager Maintainer: "Foo Bar ", Vendor: "Foo Bar ", Package: "testapp", - Description: " \n Test Application", + Description: " \n Test Application (test quite \" #378)", }) } @@ -206,7 +206,7 @@ async function checkWindowsResult(packager: Packager, packagerOptions: PackagerO function getWinExpected(archSuffix: string) { return [ `RELEASES${archSuffix}`, - `${productName}Setup-1.1.0${archSuffix}.exe`, + `${productName} Setup 1.1.0${archSuffix}.exe`, `TestApp-1.1.0${archSuffix}-full.nupkg`, ] } @@ -225,7 +225,7 @@ async function checkWindowsResult(packager: Packager, packagerOptions: PackagerO if (archSuffix == "") { const expectedArtifactNames = expected.slice() expectedArtifactNames[1] = `TestAppSetup-1.1.0${archSuffix}.exe` - assertThat(artifacts.map(it => it.artifactName).filter(it => it != null)).deepEqual([`TestAppSetup-1.1.0${archSuffix}.exe`]) + assertThat(artifacts.map(it => it.artifactName).filter(it => it != null)).deepEqual([`TestApp-Setup-1.1.0${archSuffix}.exe`]) } const packageFile = path.join(path.dirname(artifacts[0].file), `TestApp-1.1.0${archSuffix}-full.nupkg`) @@ -238,7 +238,7 @@ async function checkWindowsResult(packager: Packager, packagerOptions: PackagerO const expectedContents = checkOptions == null || checkOptions.expectedContents == null ? expectedWinContents : checkOptions.expectedContents assertThat(files).deepEqual(expectedContents.map(it => { if (it === "lib/net45/TestApp.exe") { - return `lib/net45/${productName.replace(/ /g, "%20")}.exe` + return `lib/net45/${productName}.exe` } else { return it @@ -260,7 +260,7 @@ async function checkWindowsResult(packager: Packager, packagerOptions: PackagerO Foo Bar https://raw.githubusercontent.com/szwacz/electron-boilerplate/master/resources/windows/icon.ico false - Test Application + Test Application (test quite \" #378) Copyright © ${new Date().getFullYear()} Foo Bar http://foo.example.com diff --git a/test/src/winPackagerTest.ts b/test/src/winPackagerTest.ts index 0cbf29446f4..270f682d85e 100755 --- a/test/src/winPackagerTest.ts +++ b/test/src/winPackagerTest.ts @@ -7,6 +7,7 @@ import { WinPackager, computeDistOut } from "out/winPackager" import { BuildInfo } from "out/platformPackager" import { Promise as BluebirdPromise } from "bluebird" import * as assertThat from "should/as-function" +import ElectronPackagerOptions = ElectronPackager.ElectronPackagerOptions //noinspection JSUnusedLocalSymbols const __awaiter = require("out/awaiter") @@ -85,10 +86,11 @@ class CheckingWinPackager extends WinPackager { async pack(outDir: string, arch: string): Promise { // skip pack const installerOutDir = computeDistOut(outDir, arch) - this.effectiveDistOptions = await this.computeEffectiveDistOptions(this.computeAppOutDir(outDir, arch), installerOutDir) + const packOptions = this.computePackOptions(outDir, arch) + this.effectiveDistOptions = await this.computeEffectiveDistOptions(this.computeAppOutDir(outDir, arch), installerOutDir, packOptions) } - async packageInDistributableFormat(outDir: string, appOutDir: string, arch: string): Promise { + async packageInDistributableFormat(outDir: string, appOutDir: string, arch: string, packOptions: ElectronPackagerOptions): Promise { // skip } } \ No newline at end of file diff --git a/typings/electron-packager.d.ts b/typings/electron-packager.d.ts index 47bb2ee1a33..81ffd891b17 100644 --- a/typings/electron-packager.d.ts +++ b/typings/electron-packager.d.ts @@ -57,6 +57,8 @@ declare namespace ElectronPackager { "asar-unpack"?: string; /** Should contain the identity to be used when running `codesign` (OS X only). */ sign?: string; + + "app-copyright"?: string } /** Object hash of application metadata to embed into the executable (Windows only). */