From 87616c0604ed1937a943301aee2567a63e041847 Mon Sep 17 00:00:00 2001 From: develar Date: Wed, 7 Sep 2016 19:52:54 +0200 Subject: [PATCH] feat(linux): Categories desktop entry Closes #727, #641 --- docs/Options.md | 8 +++- src/appInfo.ts | 9 ----- src/linuxPackager.ts | 2 +- src/macPackager.ts | 5 +-- src/metadata.ts | 49 ++++++++++++++++++------- src/packager/dirPackager.ts | 18 ++------- src/packager/mac.ts | 33 +++++++++++------ src/platformPackager.ts | 23 +++--------- src/targets/LinuxTargetHelper.ts | 40 ++++++++++++-------- src/targets/appImage.ts | 9 ++++- src/targets/fpm.ts | 7 +++- src/winPackager.ts | 2 +- templates/linux/AppRun.sh | 6 +-- test/fixtures/test-app-one/package.json | 10 ++++- test/src/helpers/packTester.ts | 1 + test/src/linuxPackagerTest.ts | 22 ++++------- test/src/macPackagerTest.ts | 6 +-- test/src/winPackagerTest.ts | 5 --- typings/uuid-1345.d.ts | 6 +++ 19 files changed, 141 insertions(+), 120 deletions(-) diff --git a/docs/Options.md b/docs/Options.md index 625ae43c233..af9be8f4e04 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -49,7 +49,6 @@ Don't customize paths to background and icon, — just follow conventions. | Name | Description | --- | --- | appId |

The application id. Used as [CFBundleIdentifier](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102070) for MacOS and as [Application User Model ID](https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx) for Windows (NSIS target only, Squirrel.Windows not supported).

Defaults to com.electron.${name}. It is strongly recommended that an explicit ID be set.

-| category |

*macOS-only.* The application category type, as shown in the Finder via *View -> Arrange by Application Category* when viewing the Applications directory.

For example, "category": "public.app-category.developer-tools" will set the application category to *Developer Tools*.

Valid values are listed in [Apple’s documentation](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8).

| copyright | The human-readable copyright line for the app. Defaults to `Copyright © year author`. | asar |

Whether to package the application’s source code into an archive, using [Electron’s archive format](https://github.com/electron/asar). Defaults to true. Reasons why you may want to disable this feature are described in [an application packaging tutorial in Electron’s documentation](http://electron.atom.io/docs/latest/tutorial/application-packaging/#limitations-on-node-api/).

Or you can pass object of any asar options.

Node modules, that must be unpacked, will be detected automatically, you don’t need to explicitly set asar.unpackDir - please file issue if this doesn’t work.

| productName | See [AppMetadata.productName](#AppMetadata-productName). @@ -76,12 +75,14 @@ MacOS specific build options. | Name | Description | --- | --- +| category |

The application category type, as shown in the Finder via *View -> Arrange by Application Category* when viewing the Applications directory.

For example, "category": "public.app-category.developer-tools" will set the application category to *Developer Tools*.

Valid values are listed in [Apple’s documentation](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8).

| target | Target package type: list of `default`, `dmg`, `mas`, `7z`, `zip`, `tar.xz`, `tar.lz`, `tar.gz`, `tar.bz2`. Defaults to `default` (dmg and zip for Squirrel.Mac). | identity |

The name of certificate to use when signing. Consider using environment variables [CSC_LINK or CSC_NAME](https://github.com/electron-userland/electron-builder/wiki/Code-Signing). MAS installer identity is specified in the [.build.mas](#MasBuildOptions-identity).

| icon | The path to application icon. Defaults to `build/icon.icns` (consider using this convention instead of complicating your configuration). | entitlements |

The path to entitlements file for signing the app. build/entitlements.mac.plist will be used if exists (it is a recommended way to set). MAS entitlements is specified in the [.build.mas](#MasBuildOptions-entitlements).

| entitlementsInherit |

The path to child entitlements which inherit the security settings for signing frameworks and bundles of a distribution. build/entitlements.mac.inherit.plist will be used if exists (it is a recommended way to set). Otherwise [default](https://github.com/electron-userland/electron-osx-sign/blob/master/default.entitlements.darwin.inherit.plist).

This option only applies when signing with entitlements provided.

| bundleVersion | The `CFBundleVersion`. Do not use it unless [you need to](see (https://github.com/electron-userland/electron-builder/issues/565#issuecomment-230678643)). +| helperBundleId | The bundle identifier to use in the application helper's plist. Defaults to `${appBundleIdentifier}.helper`. ### `.build.dmg` @@ -142,7 +143,7 @@ See [NSIS target notes](https://github.com/electron-userland/electron-builder/wi | installerHeaderIcon | *one-click installer only.* The path to header icon (above the progress bar), relative to the project directory. Defaults to `build/installerHeaderIcon.ico` or application icon. | include | The path to NSIS include script to customize installer. Defaults to `build/installer.nsh`. See [Custom NSIS script](https://github.com/electron-userland/electron-builder/wiki/NSIS#custom-nsis-script). | script | The path to NSIS script to customize installer. Defaults to `build/installer.nsi`. See [Custom NSIS script](https://github.com/electron-userland/electron-builder/wiki/NSIS#custom-nsis-script). -| language | * LCID Dec, defaults to `1033`(`English - United States`, see https://msdn.microsoft.com/en-au/goglobal/bb964664.aspx?f=255&MSPPError=-2147217396). +| language | * [LCID Dec](https://msdn.microsoft.com/en-au/goglobal/bb964664.aspx), defaults to `1033`(`English - United States`). ### `.build.linux` @@ -151,11 +152,14 @@ Linux specific build options. | Name | Description | --- | --- +| category | The [application category](https://specifications.freedesktop.org/menu-spec/latest/apa.html#main-category-registry). +| packageCategory | The [package category](https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Section). Not applicable for AppImage. | description | As [description](#AppMetadata-description) from application package.json, but allows you to specify different for Linux. | target |

Target package type: list of AppImage, deb, rpm, freebsd, pacman, p5p, apk, 7z, zip, tar.xz, tar.lz, tar.gz, tar.bz2. Defaults to AppImage.

The most effective [xz](https://en.wikipedia.org/wiki/Xz) compression format used by default.

Only deb and AppImage is tested. Feel free to file issues for rpm and other package formats.

| synopsis | *deb-only.* The [short description](https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Description). | maintainer | The maintainer. Defaults to [author](#AppMetadata-author). | vendor | The vendor. Defaults to [author](#AppMetadata-author). +| desktop | The [Desktop file](https://developer.gnome.org/integration-guide/stable/desktop-files.html.en) entries. | compression | *deb-only.* The compression type, one of `gz`, `bzip2`, `xz`. Defaults to `xz`. | depends | Package dependencies. Defaults to `["libappindicator1", "libnotify-bin"]`. diff --git a/src/appInfo.ts b/src/appInfo.ts index 92ca7239b51..289224d1b4c 100644 --- a/src/appInfo.ts +++ b/src/appInfo.ts @@ -61,15 +61,6 @@ export class AppInfo { return this.metadata.name } - get category() { - const metadata = this.devMetadata.build - const old = (metadata)["app-category-type"] - if (old != null) { - warn('"app-category-type" is deprecated — please use "category" instead') - } - return metadata.category || old - } - get copyright(): string { const metadata = this.devMetadata.build const old = (metadata)["app-copyright"] diff --git a/src/linuxPackager.ts b/src/linuxPackager.ts index 0a437937e34..68a55105b2e 100755 --- a/src/linuxPackager.ts +++ b/src/linuxPackager.ts @@ -61,7 +61,7 @@ export class LinuxPackager extends PlatformPackager { async pack(outDir: string, arch: Arch, targets: Array, postAsyncTasks: Array>): Promise { const appOutDir = this.computeAppOutDir(outDir, arch) - await this.doPack(await this.computePackOptions(), outDir, appOutDir, this.platform.nodeName, arch, this.platformSpecificBuildOptions) + await this.doPack(outDir, appOutDir, this.platform.nodeName, arch, this.platformSpecificBuildOptions) postAsyncTasks.push(this.packageInDistributableFormat(outDir, appOutDir, arch, targets)) } diff --git a/src/macPackager.ts b/src/macPackager.ts index 8a3c311a839..8412ed72e01 100644 --- a/src/macPackager.ts +++ b/src/macPackager.ts @@ -67,14 +67,13 @@ export default class MacPackager extends PlatformPackager { } async pack(outDir: string, arch: Arch, targets: Array, postAsyncTasks: Array>): Promise { - const packOptions = await this.computePackOptions() let nonMasPromise: Promise | null = null const hasMas = targets.length !== 0 && targets.some(it => it.name === "mas") if (!hasMas || targets.length > 1) { const appOutDir = this.computeAppOutDir(outDir, arch) - nonMasPromise = this.doPack(packOptions, outDir, appOutDir, this.platform.nodeName, arch, this.platformSpecificBuildOptions) + nonMasPromise = this.doPack(outDir, appOutDir, this.platform.nodeName, arch, this.platformSpecificBuildOptions) .then(() => this.sign(appOutDir, null)) .then(() => { this.packageInDistributableFormat(appOutDir, targets, postAsyncTasks) @@ -86,7 +85,7 @@ export default class MacPackager extends PlatformPackager { const appOutDir = path.join(outDir, "mas") const masBuildOptions = deepAssign({}, this.platformSpecificBuildOptions, (this.devMetadata.build).mas) //noinspection JSUnusedGlobalSymbols - await this.doPack(packOptions, outDir, appOutDir, "mas", arch, masBuildOptions) + await this.doPack(outDir, appOutDir, "mas", arch, masBuildOptions) await this.sign(appOutDir, masBuildOptions) } diff --git a/src/metadata.ts b/src/metadata.ts index 1c9c7c72bde..88d1ae2a413 100755 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -1,5 +1,5 @@ import { AsarOptions } from "asar-electron-builder" -import { ElectronPackagerOptions } from "./packager/dirPackager" +import { PlatformPackager } from "./platformPackager" export interface Metadata { readonly repository?: string | RepositoryInfo | null @@ -91,15 +91,6 @@ export interface BuildMetadata { */ readonly appId?: string | null - /* - *macOS-only.* The application category type, as shown in the Finder via *View -> Arrange by Application Category* when viewing the Applications directory. - - For example, `"category": "public.app-category.developer-tools"` will set the application category to *Developer Tools*. - - Valid values are listed in [Apple's documentation](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8). - */ - readonly category?: string | null - /* The human-readable copyright line for the app. Defaults to `Copyright © year author`. */ @@ -219,7 +210,11 @@ export interface BuildMetadata { export interface AfterPackContext { readonly appOutDir: string - readonly options: ElectronPackagerOptions + + // deprecated + readonly options: any + + readonly packager: PlatformPackager } /* @@ -228,6 +223,15 @@ export interface AfterPackContext { MacOS specific build options. */ export interface MacOptions extends PlatformSpecificBuildOptions { + /* + The application category type, as shown in the Finder via *View -> Arrange by Application Category* when viewing the Applications directory. + + For example, `"category": "public.app-category.developer-tools"` will set the application category to *Developer Tools*. + + Valid values are listed in [Apple's documentation](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8). + */ + readonly category?: string | null + /* Target package type: list of `default`, `dmg`, `mas`, `7z`, `zip`, `tar.xz`, `tar.lz`, `tar.gz`, `tar.bz2`. Defaults to `default` (dmg and zip for Squirrel.Mac). */ @@ -262,6 +266,11 @@ export interface MacOptions extends PlatformSpecificBuildOptions { The `CFBundleVersion`. Do not use it unless [you need to](see (https://github.com/electron-userland/electron-builder/issues/565#issuecomment-230678643)). */ readonly bundleVersion?: string | null + + /* + The bundle identifier to use in the application helper's plist. Defaults to `${appBundleIdentifier}.helper`. + */ + readonly helperBundleId?: string | null } /* @@ -432,7 +441,7 @@ export interface NsisOptions { readonly script?: string | null /* - * LCID Dec, defaults to `1033`(`English - United States`, see https://msdn.microsoft.com/en-au/goglobal/bb964664.aspx?f=255&MSPPError=-2147217396). + * [LCID Dec](https://msdn.microsoft.com/en-au/goglobal/bb964664.aspx), defaults to `1033`(`English - United States`). */ readonly language?: string | null } @@ -443,6 +452,16 @@ export interface NsisOptions { Linux specific build options. */ export interface LinuxBuildOptions extends PlatformSpecificBuildOptions { + /* + The [application category](https://specifications.freedesktop.org/menu-spec/latest/apa.html#main-category-registry). + */ + readonly category?: string | null + + /* + The [package category](https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Section). Not applicable for AppImage. + */ + readonly packageCategory?: string | null + /* As [description](#AppMetadata-description) from application package.json, but allows you to specify different for Linux. */ @@ -475,8 +494,10 @@ export interface LinuxBuildOptions extends PlatformSpecificBuildOptions { // should be not documented, only to experiment readonly fpm?: Array | null - //.desktop file template - readonly desktop?: string | null + /** + The [Desktop file](https://developer.gnome.org/integration-guide/stable/desktop-files.html.en) entries. + */ + readonly desktop?: { [key: string]: string; } | null readonly afterInstall?: string | null readonly afterRemove?: string | null diff --git a/src/packager/dirPackager.ts b/src/packager/dirPackager.ts index b80bc220b12..6493e52907f 100644 --- a/src/packager/dirPackager.ts +++ b/src/packager/dirPackager.ts @@ -1,7 +1,6 @@ import { Promise as BluebirdPromise } from "bluebird" import { emptyDir } from "fs-extra-p" import { warn } from "../util/log" -import { AppInfo } from "../appInfo" import { PlatformPackager } from "../platformPackager" const downloadElectron: (options: any) => Promise = BluebirdPromise.promisify(require("electron-download")) @@ -10,17 +9,6 @@ const extract: any = BluebirdPromise.promisify(require("extract-zip")) //noinspection JSUnusedLocalSymbols const __awaiter = require("../util/awaiter") -export interface ElectronPackagerOptions { - "extend-info"?: string - - appInfo: AppInfo - platformPackager: PlatformPackager - - "helper-bundle-id"?: string | null - - ignore?: any -} - function createDownloadOpts(opts: any, platform: string, arch: string, electronVersion: string) { const downloadOpts = Object.assign({ cache: opts.cache, @@ -40,15 +28,15 @@ function subOptionWarning (properties: any, optionName: any, parameter: any, val properties[parameter] = value } -export async function pack(opts: ElectronPackagerOptions, out: string, platform: string, arch: string, electronVersion: string, initializeApp: () => Promise) { +export async function pack(packager: PlatformPackager, out: string, platform: string, arch: string, electronVersion: string, initializeApp: () => Promise) { const zipPath = (await BluebirdPromise.all([ - downloadElectron(createDownloadOpts(opts, platform, arch, electronVersion)), + downloadElectron(createDownloadOpts(packager.devMetadata.build, platform, arch, electronVersion)), emptyDir(out) ]))[0] await extract(zipPath, {dir: out}) if (platform === "darwin" || platform === "mas") { - await(require("./mac")).createApp(opts, out, initializeApp) + await(require("./mac")).createApp(packager, out, initializeApp) } else { await initializeApp() diff --git a/src/packager/mac.ts b/src/packager/mac.ts index 118756e4b1a..3da9f61bcf0 100644 --- a/src/packager/mac.ts +++ b/src/packager/mac.ts @@ -1,11 +1,10 @@ -import { ElectronPackagerOptions } from "./dirPackager" import { rename, readFile, writeFile, copy, unlink } from "fs-extra-p" import * as path from "path" import { parse as parsePlist, build as buildPlist } from "plist" import { Promise as BluebirdPromise } from "bluebird" import { use, asArray } from "../util/util" -import { normalizeExt } from "../platformPackager" -import { FileAssociation } from "../metadata" +import { normalizeExt, PlatformPackager } from "../platformPackager" +import { warn } from "../util/log" //noinspection JSUnusedLocalSymbols const __awaiter = require("../util/awaiter") @@ -28,8 +27,8 @@ function filterCFBundleIdentifier(identifier: string) { return identifier.replace(/ /g, "-").replace(/[^a-zA-Z0-9.-]/g, "") } -export async function createApp(opts: ElectronPackagerOptions, appOutDir: string, initializeApp: () => Promise) { - const appInfo = opts.appInfo +export async function createApp(packager: PlatformPackager, appOutDir: string, initializeApp: () => Promise) { + const appInfo = packager.appInfo const appFilename = appInfo.productFilename const contentsPath = path.join(appOutDir, "Electron.app", "Contents") @@ -40,9 +39,11 @@ export async function createApp(opts: ElectronPackagerOptions, appOutDir: string const helperEHPlistFilename = path.join(frameworksPath, "Electron Helper EH.app", "Contents", "Info.plist") const helperNPPlistFilename = path.join(frameworksPath, "Electron Helper NP.app", "Contents", "Info.plist") + const buildMetadata = packager.devMetadata.build! + const result = await BluebirdPromise.all([ initializeApp(), - BluebirdPromise.map([appPlistFilename, helperPlistFilename, helperEHPlistFilename, helperNPPlistFilename, opts["extend-info"]], it => it == null ? it : readFile(it, "utf8")) + BluebirdPromise.map([appPlistFilename, helperPlistFilename, helperEHPlistFilename, helperNPPlistFilename, (buildMetadata)["extend-info"]], it => it == null ? it : readFile(it, "utf8")) ]) const fileContents: Array = result[1]! const appPlist = parsePlist(fileContents[0]) @@ -56,9 +57,13 @@ export async function createApp(opts: ElectronPackagerOptions, appOutDir: string } const appBundleIdentifier = filterCFBundleIdentifier(appInfo.id) - const helperBundleIdentifier = filterCFBundleIdentifier(opts["helper-bundle-id"] || `${appBundleIdentifier}.helper`) - const packager = opts.platformPackager + const oldHelperBundleId = (buildMetadata)["helper-bundle-id"] + if (oldHelperBundleId != null) { + warn("build.helper-bundle-id is deprecated, please set as build.mac.helperBundleId") + } + const helperBundleIdentifier = filterCFBundleIdentifier(packager.platformSpecificBuildOptions.helperBundleId || oldHelperBundleId || `${appBundleIdentifier}.helper`) + const icon = await packager.getIconPath() const oldIcon = appPlist.CFBundleIconFile if (icon != null) { @@ -88,7 +93,7 @@ export async function createApp(opts: ElectronPackagerOptions, appOutDir: string }) use(appInfo.buildVersion, it => appPlist.CFBundleVersion = it) - const protocols = asArray(packager.devMetadata.build.protocols).concat(asArray(packager.platformSpecificBuildOptions.protocols)) + const protocols = asArray(buildMetadata.protocols).concat(asArray(packager.platformSpecificBuildOptions.protocols)) if (protocols.length > 0) { appPlist.CFBundleURLTypes = protocols.map(protocol => { const schemes = asArray(protocol.schemes) @@ -104,7 +109,7 @@ export async function createApp(opts: ElectronPackagerOptions, appOutDir: string const fileAssociations = packager.getFileAssociations() if (fileAssociations.length > 0) { - appPlist.CFBundleDocumentTypes = await BluebirdPromise.map(fileAssociations, async fileAssociation => { + appPlist.CFBundleDocumentTypes = await BluebirdPromise.map(fileAssociations, async fileAssociation => { const extensions = asArray(fileAssociation.ext).map(normalizeExt) const customIcon = await packager.getResource(fileAssociation.icon, `${extensions[0]}.icns`) // todo rename electron.icns @@ -117,7 +122,13 @@ export async function createApp(opts: ElectronPackagerOptions, appOutDir: string }) } - use(appInfo.category, it => appPlist.LSApplicationCategoryType = it) + const oldCategory = (buildMetadata)["app-category-type"] + if (oldCategory != null) { + warn("app-category-type is deprecated, please set as build.mac.category") + } + + let category = packager.platformSpecificBuildOptions.category || (buildMetadata).category || oldCategory + use(category || oldCategory, it => appPlist.LSApplicationCategoryType = it) use(appInfo.copyright, it => appPlist.NSHumanReadableCopyright = it) const promises: Array> = [ diff --git a/src/platformPackager.ts b/src/platformPackager.ts index fcbce357b1c..abc54ceba52 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -12,7 +12,7 @@ import { checkFileInArchive, createAsarArchive } from "./asarUtil" import { warn, log, task } from "./util/log" import { AppInfo } from "./appInfo" import { copyFiltered, devDependencies } from "./util/filter" -import { ElectronPackagerOptions, pack } from "./packager/dirPackager" +import { pack } from "./packager/dirPackager" import { TmpDir } from "./util/tmp" import { FileMatchOptions, FileMatcher, FilePattern, deprecatedUserIgnoreFilter } from "./fileMatcher" import { BuildOptions } from "./builder" @@ -164,7 +164,7 @@ export abstract class PlatformPackager return this.getFileMatchers(isResources ? "extraResources" : "extraFiles", this.projectDir, base, true, fileMatchOptions, customBuildOptions) } - protected async doPack(options: ElectronPackagerOptions, outDir: string, appOutDir: string, platformName: string, arch: Arch, platformSpecificBuildOptions: DC) { + protected async doPack(outDir: string, appOutDir: string, platformName: string, arch: Arch, platformSpecificBuildOptions: DC) { const asarOptions = this.computeAsarOptions(platformSpecificBuildOptions) const fileMatchOptions: FileMatchOptions = { arch: Arch[arch], @@ -176,7 +176,7 @@ export abstract class PlatformPackager const resourcesPath = this.platform === Platform.MAC ? path.join(appOutDir, "Electron.app", "Contents", "Resources") : path.join(appOutDir, "resources") - const p = pack(options, appOutDir, platformName, Arch[arch], this.info.electronVersion, async() => { + const p = pack(this, appOutDir, platformName, Arch[arch], this.info.electronVersion, async() => { const ignoreFiles = new Set([path.resolve(this.info.appDir, outDir), path.resolve(this.info.appDir, this.buildResourcesDir)]) if (!this.info.isTwoPackageJsonProjectLayoutUsed) { const result = await devDependencies(this.info.appDir) @@ -242,7 +242,8 @@ export abstract class PlatformPackager if (afterPack != null) { await afterPack({ appOutDir: appOutDir, - options: options, + options: this.devMetadata.build, + packager: this, }) } @@ -253,20 +254,6 @@ export abstract class PlatformPackager return BluebirdPromise.resolve(null) } - protected async computePackOptions(): Promise { - //noinspection JSUnusedGlobalSymbols - const appInfo = this.appInfo - const options: any = Object.assign({ - appInfo: appInfo, - platformPackager: this, - }, this.devMetadata.build) - - delete options.osx - delete options.win - delete options.linux - return options - } - async getIconPath(): Promise { return null } diff --git a/src/targets/LinuxTargetHelper.ts b/src/targets/LinuxTargetHelper.ts index b452f936aa6..fa95a46dce5 100644 --- a/src/targets/LinuxTargetHelper.ts +++ b/src/targets/LinuxTargetHelper.ts @@ -1,6 +1,6 @@ import { readdir, outputFile, ensureDir } from "fs-extra-p" import * as path from "path" -import { exec, debug } from "../util/util" +import { exec, debug, isEmptyOrSpaces } from "../util/util" import { PlatformPackager } from "../platformPackager" import { Promise as BluebirdPromise } from "bluebird" import { LinuxBuildOptions } from "../metadata" @@ -66,24 +66,34 @@ export class LinuxTargetHelper { return iconPath == null ? await this.packager.getDefaultIcon("icns") : path.resolve(this.packager.projectDir, iconPath) } - async computeDesktopEntry(exec?: string, extra?: string): Promise { + async computeDesktopEntry(platformSpecificBuildOptions: LinuxBuildOptions, exec?: string, extra?: { [key: string]: string; }): Promise { const appInfo = this.packager.appInfo - const custom = this.packager.platformSpecificBuildOptions.desktop - if (custom != null) { - return custom - } - const productFilename = appInfo.productFilename const tempFile = await this.packager.getTempFile(`${productFilename}.desktop`) - await outputFile(tempFile, this.packager.platformSpecificBuildOptions.desktop || `[Desktop Entry] -Name=${appInfo.productName} -Comment=${this.packager.platformSpecificBuildOptions.description || appInfo.description} -Exec=${(exec == null ? `"${installPrefix}/${productFilename}/${productFilename}"` : exec)} -Terminal=false -Type=Application -Icon=${appInfo.name} -${extra == null ? "" : `${extra}\n`}`) + + const desktopMeta: any = Object.assign({ + Name: appInfo.productName, + Comment: platformSpecificBuildOptions.description || appInfo.description, + Exec: exec == null ? `"${installPrefix}/${productFilename}/${productFilename}"` : exec, + Terminal: "false", + Type: "Application", + Icon: appInfo.name, + }, extra, platformSpecificBuildOptions.desktop) + + const category = platformSpecificBuildOptions.category + if (!isEmptyOrSpaces(category)) { + desktopMeta.Categories = category + } + + let data = `[Desktop Entry]` + for (let name of Object.keys(desktopMeta)) { + const value = desktopMeta[name] + data += `\n${name}=${value}` + } + data += "\n" + + await outputFile(tempFile, data) return tempFile } diff --git a/src/targets/appImage.ts b/src/targets/appImage.ts index 1a2b9a487a6..be81b00f2a7 100644 --- a/src/targets/appImage.ts +++ b/src/targets/appImage.ts @@ -6,6 +6,7 @@ import { open, write, createReadStream, createWriteStream, close, chmod } from " import { LinuxTargetHelper } from "./LinuxTargetHelper" import { getBin } from "../util/binDownload" import { Promise as BluebirdPromise } from "bluebird" +import { v1 as uuid1 } from "uuid-1345" //noinspection JSUnusedLocalSymbols const __awaiter = require("../util/awaiter") @@ -17,12 +18,18 @@ const appImageSha256 = process.platform === "darwin" ? "5d4a954876654403698a01ef const appImagePathPromise = getBin("AppImage", appImageVersion, `https://dl.bintray.com/electron-userland/bin/${appImageVersion}.7z`, appImageSha256) export default class AppImageTarget extends TargetEx { + private readonly options = Object.assign({}, this.packager.platformSpecificBuildOptions, (this.packager.devMetadata.build)[this.name]) private readonly desktopEntry: Promise constructor(private packager: PlatformPackager, private helper: LinuxTargetHelper, private outDir: string) { super("appImage") - this.desktopEntry = helper.computeDesktopEntry("AppRun", `X-AppImage-Version=${packager.appInfo.buildVersion}`) + // we add X-AppImage-BuildId to ensure that new desktop file will be installed + this.desktopEntry = BluebirdPromise.promisify(uuid1)({mac: false}) + .then(uuid => helper.computeDesktopEntry(this.options, "AppRun", { + "X-AppImage-Version": `${packager.appInfo.buildVersion}`, + "X-AppImage-BuildId": uuid, + })) } async build(appOutDir: string, arch: Arch): Promise { diff --git a/src/targets/fpm.ts b/src/targets/fpm.ts index 7b39ee21931..f464957cef8 100644 --- a/src/targets/fpm.ts +++ b/src/targets/fpm.ts @@ -28,7 +28,7 @@ export default class FpmTarget extends TargetEx { super(name) this.scriptFiles = this.createScripts() - this.desktopEntry = helper.computeDesktopEntry() + this.desktopEntry = helper.computeDesktopEntry(this.options) } private async createScripts(): Promise> { @@ -93,6 +93,11 @@ export default class FpmTarget extends TargetEx { "--url", projectUrl, ] + const packageCategory = options.packageCategory + if (packageCategory != null && packageCategory !== null) { + args.push("--category", packageCategory) + } + if (target === "deb") { args.push("--deb-compression", options.compression || (packager.devMetadata.build.compression === "store" ? "gz" : "xz")) } diff --git a/src/winPackager.ts b/src/winPackager.ts index f56a8f37402..e83c86577ad 100644 --- a/src/winPackager.ts +++ b/src/winPackager.ts @@ -112,7 +112,7 @@ export class WinPackager extends PlatformPackager { async pack(outDir: string, arch: Arch, targets: Array, postAsyncTasks: Array>): Promise { const appOutDir = this.computeAppOutDir(outDir, arch) - await this.doPack(await this.computePackOptions(), outDir, appOutDir, this.platform.nodeName, arch, this.platformSpecificBuildOptions) + await this.doPack(outDir, appOutDir, this.platform.nodeName, arch, this.platformSpecificBuildOptions) this.packageInDistributableFormat(outDir, appOutDir, arch, targets, postAsyncTasks) } diff --git a/templates/linux/AppRun.sh b/templates/linux/AppRun.sh index de2f610ad26..9437a259316 100755 --- a/templates/linux/AppRun.sh +++ b/templates/linux/AppRun.sh @@ -147,8 +147,8 @@ fi # Check if the desktop file is already there # and if so, whether it points to the same AppImage if [ -e "$DESTINATION_DIR_DESKTOP/$VENDORPREFIX-$DESKTOP_FILE_NAME" ] ; then - INSTALLED_APP_VERSION=$(grep "^X-AppImage-Version=" "$DESTINATION_DIR_DESKTOP/$VENDORPREFIX-$DESKTOP_FILE_NAME" | head -n 1 | cut -d " " -f 1) - APP_VERSION=$(grep "^X-AppImage-Version=" "$DESKTOP_FILE" | head -n 1 | cut -d " " -f 1) + INSTALLED_APP_VERSION=$(grep "^X-AppImage-BuildId=" "$DESTINATION_DIR_DESKTOP/$VENDORPREFIX-$DESKTOP_FILE_NAME" | head -n 1 | cut -d " " -f 1) + APP_VERSION=$(grep "^X-AppImage-BuildId=" "$DESKTOP_FILE" | head -n 1 | cut -d " " -f 1) echo "installed: $INSTALLED_APP_VERSION image: $APP_VERSION" if [ "$INSTALLED_APP_VERSION" == "$APP_VERSION" ] ; then exit 0 @@ -157,7 +157,7 @@ fi # We ask the user only if we have found no reason to skip until here if [ -z "$SKIP" ] ; then - yesno "Install" "Should a desktop file for $APPIMAGE be installed?" + yesno "Install" "Would you like to integrate $APPIMAGE with your system?\n\nThis will add it to your applications menu and install icons.\nIf you don't do this you can still launch the application by double-clicking on the AppImage." fi # If the user has agreed, rewrite and install the desktop file, and the MIME information diff --git a/test/fixtures/test-app-one/package.json b/test/fixtures/test-app-one/package.json index d97d234fdb5..a836f156630 100755 --- a/test/fixtures/test-app-one/package.json +++ b/test/fixtures/test-app-one/package.json @@ -10,8 +10,14 @@ "build": { "electronVersion": "1.3.5", "appId": "org.electron-builder.testApp", - "category": "your.app.category.type", "iconUrl": "https://raw.githubusercontent.com/szwacz/electron-boilerplate/master/resources/windows/icon.ico", - "compression": "store" + "compression": "store", + "mac": { + "category": "your.app.category.type" + }, + "linux": { + "category": "Development", + "packageCategory": "devel" + } } } \ No newline at end of file diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index 684abbffa65..6c541542588 100755 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -231,6 +231,7 @@ async function checkLinuxResult(outDir: string, packager: Packager, checkOptions Package: "testapp", Description: " \n Test Application (test quite “ #378)", Depends: checkOptions == null || checkOptions.expectedDepends == null ? "libappindicator1, libnotify-bin" : checkOptions.expectedDepends, + Section: "devel", }) } diff --git a/test/src/linuxPackagerTest.ts b/test/src/linuxPackagerTest.ts index 95d5e3f6a27..49a64bac37b 100755 --- a/test/src/linuxPackagerTest.ts +++ b/test/src/linuxPackagerTest.ts @@ -1,5 +1,5 @@ import test from "./helpers/avaEx" -import { assertPack, platform, modifyPackageJson, app, appThrows } from "./helpers/packTester" +import { modifyPackageJson, app, appThrows } from "./helpers/packTester" import { remove } from "fs-extra-p" import * as path from "path" import { Platform } from "out" @@ -7,24 +7,16 @@ import { Platform } from "out" //noinspection JSUnusedLocalSymbols const __awaiter = require("out/util/awaiter") -test.ifNotWindows("deb", () => assertPack("test-app-one", platform(Platform.LINUX))) +test.ifNotWindows("deb", app({targets: Platform.LINUX.createTarget("deb")})) -test.ifDevOrLinuxCi("AppImage", () => assertPack("test-app-one", { - targets: Platform.LINUX.createTarget("appimage"), - } -)) +test.ifDevOrLinuxCi("AppImage", app({targets: Platform.LINUX.createTarget()})) -test.ifDevOrLinuxCi("AppImage - default icon", () => assertPack("test-app-one", { - targets: Platform.LINUX.createTarget("appimage"), - }, { +test.ifDevOrLinuxCi("AppImage - default icon", app({targets: Platform.LINUX.createTarget("appimage")}, { projectDirCreated: projectDir => remove(path.join(projectDir, "build")) - }, -)) +})) // "apk" is very slow, don't test for now -test.ifDevOrLinuxCi("targets", () => assertPack("test-app-one", { - targets: Platform.LINUX.createTarget(["sh", "freebsd", "pacman", "zip", "7z"]), -})) +test.ifDevOrLinuxCi("targets", app({targets: Platform.LINUX.createTarget(["sh", "freebsd", "pacman", "zip", "7z"])})) test.ifDevOrLinuxCi("tar", app({targets: Platform.LINUX.createTarget(["tar.xz", "tar.lz", "tar.bz2"])})) @@ -36,7 +28,7 @@ test.ifNotWindows("icons from ICNS", app({targets: Platform.LINUX.createTarget() projectDirCreated: it => remove(path.join(it, "build", "icons")) })) -test.ifNotWindows("custom depends", () => assertPack("test-app-one", { +test.ifNotWindows("custom depends", app({ targets: Platform.LINUX.createTarget("deb"), devMetadata: { build: { diff --git a/test/src/macPackagerTest.ts b/test/src/macPackagerTest.ts index 4753f5b22a5..cb015707e05 100644 --- a/test/src/macPackagerTest.ts +++ b/test/src/macPackagerTest.ts @@ -6,7 +6,6 @@ import * as path from "path" import { BuildInfo } from "out/platformPackager" import { Promise as BluebirdPromise } from "bluebird" import { assertThat } from "./helpers/fileAssert" -import { ElectronPackagerOptions } from "out/packager/dirPackager" import { Platform, MacOptions, createTargets } from "out" import { SignOptions, FlatOptions } from "electron-osx-sign" import { Arch } from "out" @@ -207,7 +206,6 @@ test.ifOsx("disable dmg icon, bundleVersion", () => { class CheckingMacPackager extends OsXPackager { effectiveDistOptions: any - effectivePackOptions: ElectronPackagerOptions effectiveSignOptions: SignOptions effectiveFlatOptions: FlatOptions @@ -226,8 +224,8 @@ class CheckingMacPackager extends OsXPackager { return await super.pack(outDir, arch, targets, postAsyncTasks) } - async doPack(options: ElectronPackagerOptions, outDir: string, appOutDir: string, platformName: string, arch: Arch, customBuildOptions: MacOptions, postAsyncTasks: Array> = null) { - this.effectivePackOptions = options + async doPack(outDir: string, appOutDir: string, platformName: string, arch: Arch, customBuildOptions: MacOptions, postAsyncTasks: Array> = null) { + // skip } async doSign(opts: SignOptions): Promise { diff --git a/test/src/winPackagerTest.ts b/test/src/winPackagerTest.ts index 3c21a768f10..5a2c14c1b01 100755 --- a/test/src/winPackagerTest.ts +++ b/test/src/winPackagerTest.ts @@ -9,7 +9,6 @@ import { assertThat } from "./helpers/fileAssert" import { SignOptions } from "out/windowsCodeSign" import SquirrelWindowsTarget from "out/targets/squirrelWindows" import { Target } from "out/platformPackager" -import { ElectronPackagerOptions } from "out/packager/dirPackager" //noinspection JSUnusedLocalSymbols const __awaiter = require("out/util/awaiter") @@ -125,16 +124,12 @@ class CheckingWinPackager extends WinPackager { effectiveDistOptions: any signOptions: SignOptions | null - effectivePackOptions: ElectronPackagerOptions - constructor(info: BuildInfo) { super(info) } async pack(outDir: string, arch: Arch, targets: Array, postAsyncTasks: Array>): Promise { // skip pack - this.effectivePackOptions = await this.computePackOptions() - const helperClass: typeof SquirrelWindowsTarget = require("out/targets/squirrelWindows").default this.effectiveDistOptions = await (new helperClass(this).computeEffectiveDistOptions()) diff --git a/typings/uuid-1345.d.ts b/typings/uuid-1345.d.ts index fa9c206d4a2..2ddb4519d79 100644 --- a/typings/uuid-1345.d.ts +++ b/typings/uuid-1345.d.ts @@ -4,5 +4,11 @@ declare module "uuid-1345" { name: string } + interface TimeBasedUuidOptions { + mac: boolean + } + export function v5(options: NameUuidOptions, callback: (error: Error, result: string) => void): void + export function v4(callback: (error: Error, result: string) => void): void + export function v1(options: TimeBasedUuidOptions, callback: (error: Error, result: string) => void): void } \ No newline at end of file