diff --git a/.idea/dictionaries/develar.xml b/.idea/dictionaries/develar.xml index fe554bc3971..1ea56ad7ed3 100644 --- a/.idea/dictionaries/develar.xml +++ b/.idea/dictionaries/develar.xml @@ -33,6 +33,7 @@ gnubin graphicsmagick grün + gsettings gtar guid headerimage diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6a11370e375..c073c5324b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,7 @@ Must be one of the following: * **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation. ### Scope -The scope is optional and could be anything specifying place of the commit change. For example `nsis`, `osx`, `linux`, etc... +The scope is optional and could be anything specifying place of the commit change. For example `nsis`, `mac`, `linux`, etc... ### Subject The subject contains succinct description of the change: diff --git a/docker/4/Dockerfile b/docker/4/Dockerfile index f1f92365f06..aa21e2eb77a 100644 --- a/docker/4/Dockerfile +++ b/docker/4/Dockerfile @@ -1,6 +1,6 @@ FROM electronuserland/electron-builder:base -ENV NODE_VERSION 4.4.6 +ENV NODE_VERSION 4.4.7 # https://github.com/npm/npm/issues/4531 RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \ diff --git a/docker/appImage.sh b/docker/appImage.sh new file mode 100755 index 00000000000..e058ee141c7 --- /dev/null +++ b/docker/appImage.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -e + +dir=${PWD##*/} +rm -rf ../${dir}.7z +7za a -m0=lzma2 -mx=9 -mfb=64 -md=64m -ms=on ../${dir}.7z . \ No newline at end of file diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index cccae8527cf..6d803b2929a 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -21,7 +21,7 @@ ENV FORCE_COLOR true # libcurl4-openssl-dev, libtool and automake are required to build osslsigncode RUN apt-get update -y && \ - apt-get install --no-install-recommends -y bsdtar build-essential autoconf automake libcurl4-openssl-dev libtool libssl-dev icnsutils graphicsmagick gcc-multilib g++-multilib libgnome-keyring-dev lzip rpm yasm && \ + apt-get install --no-install-recommends -y xorriso bsdtar build-essential autoconf automake libcurl4-openssl-dev libtool libssl-dev icnsutils graphicsmagick gcc-multilib g++-multilib libgnome-keyring-dev lzip rpm yasm && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ curl -L http://tukaani.org/xz/xz-$XZ_VERSION.tar.xz | tar -xJ && cd xz-$XZ_VERSION && ./configure && make && make install && cd .. && rm -rf xz-$XZ_VERSION && ldconfig && \ diff --git a/docs/Code Signing.md b/docs/Code Signing.md index 2d07c4b21b6..9092243d68b 100644 --- a/docs/Code Signing.md +++ b/docs/Code Signing.md @@ -13,7 +13,7 @@ To sign app on build server you need to set `CSC_LINK`, `CSC_KEY_PASSWORD` (and 1. [Export](https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/MaintainingCertificates/MaintainingCertificates.html#//apple_ref/doc/uid/TP40012582-CH31-SW7) certificate. Consider to not use special characters (for bash) in the password because “*values are not escaped when your builds are executed*”. -2. Encode file to base64 (osx/linux: `base64 -i yourFile.p12 -o envValue.txt`). +2. Encode file to base64 (macOS/linux: `base64 -i yourFile.p12 -o envValue.txt`). Or upload `*.p12` file (e.g. on Google Drive, use [direct link generator](http://www.syncwithtech.org/p/direct-download-link-generator.html) to get correct download link). diff --git a/docs/Multi Platform Build.md b/docs/Multi Platform Build.md index 549259f8415..626b5a71f3d 100755 --- a/docs/Multi Platform Build.md +++ b/docs/Multi Platform Build.md @@ -10,43 +10,44 @@ Don't think that mentioned issues are major, you should use build servers — e. See [sample appveyor.yml](https://github.com/develar/onshape-desktop-shell/blob/master/appveyor.yml) to build Electron app for Windows. And [sample .travis.yml](https://github.com/develar/onshape-desktop-shell/blob/master/.travis.yml) to build Electron app for MacOS. -By default build for current platform and current arch. Use CLI flags `--osx`, `--win`, `--linux` to specify platforms. And `--ia32`, `--x64` to specify arch. +By default build for current platform and current arch. Use CLI flags `--mac`, `--win`, `--linux` to specify platforms. And `--ia32`, `--x64` to specify arch. For example, to build app for MacOS, Windows and Linux: ``` build -mwl ``` -Build performed in parallel, so, it is highly recommended to not use npm task per platform (e.g. `npm run dist:osx && npm run dist:win32`), but specify multiple platforms/targets in one build command. +Build performed in parallel, so, it is highly recommended to not use npm task per platform (e.g. `npm run dist:mac && npm run dist:win32`), but specify multiple platforms/targets in one build command. You don't need to clean dist output before build — output directory is cleaned automatically. ## MacOS Use [brew](http://brew.sh) to install required packages. -To build app in distributable format for Windows on MacOS: +### To build app for Windows on MacOS: ``` brew install Caskroom/cask/xquartz wine mono ``` -To build app in distributable format for Linux on MacOS: +### To build app for Linux on MacOS: ``` -brew install gnu-tar libicns graphicsmagick +brew install gnu-tar libicns graphicsmagick xz ``` To build rpm: `brew install rpm`. ## Linux + To build app in distributable format for Linux: ``` -sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils +sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils xorriso ``` To build rpm: `sudo apt-get install --no-install-recommends -y rpm`. To build pacman: `sudo apt-get install --no-install-recommends -y bsdtar`. -To build app in distributable format for Windows on Linux: +### To build app for Windows on Linux: * Install Wine (1.8+ is required): ``` @@ -69,7 +70,7 @@ To build app in distributable format for Windows on Linux: apt-get install --no-install-recommends -y osslsigncode ``` -To build app in 32 bit from a machine with 64 bit: +### To build app in 32 bit from a machine with 64 bit: ``` sudo apt-get install --no-install-recommends -y gcc-multilib g++-multilib @@ -84,4 +85,4 @@ dist: trusty ## Windows -Not documented yet. +Use [Docker](https://github.com/electron-userland/electron-builder/wiki/Docker). diff --git a/docs/Options.md b/docs/Options.md index 52beb1322a8..199d979e1c0 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -3,7 +3,7 @@ In the development `package.json` custom `build` field can be specified to customize format: ```json "build": { - "osx": { + "dmg": { "contents": [ { "x": 410, @@ -35,7 +35,7 @@ Here documented only `electron-builder` specific options: | --- | --- | **name** | The application name. | productName |

As [name](#AppMetadata-name), but allows you to specify a product name for your executable which contains spaces and other special characters not allowed in the [name property](https://docs.npmjs.com/files/package.json#name}).

-| **description** | The application description. +| description | The application description. | homepage |

The url to the project [homepage](https://docs.npmjs.com/files/package.json#homepage) (NuGet Package projectUrl (optional) or Linux Package URL (required)).

If not specified and your project repository is public on GitHub, it will be https://github.com/${user}/${project} by default.

| license | *linux-only.* The [license](https://docs.npmjs.com/files/package.json#license) name. diff --git a/src/appInfo.ts b/src/appInfo.ts index 141e1da67e0..ab1c71533a7 100644 --- a/src/appInfo.ts +++ b/src/appInfo.ts @@ -10,7 +10,7 @@ import sanitizeFileName = require("sanitize-filename") const __awaiter = require("./util/awaiter") export class AppInfo { - readonly description = smarten(this.metadata.description) + readonly description = smarten(this.metadata.description!) // windows-only versionString = { @@ -27,7 +27,7 @@ export class AppInfo { readonly productFilename: string constructor(public metadata: AppMetadata, private devMetadata: DevMetadata) { - let buildVersion = metadata.version + let buildVersion = metadata.version! this.version = buildVersion const buildNumber = this.buildNumber @@ -40,7 +40,7 @@ export class AppInfo { } get companyName() { - return this.metadata.author.name + return this.metadata.author!.name } get buildNumber(): string | null { @@ -76,7 +76,7 @@ export class AppInfo { if (copyright != null) { return copyright } - return `Copyright © ${new Date().getFullYear()} ${this.metadata.author.name || this.productName}` + return `Copyright © ${new Date().getFullYear()} ${this.metadata.author!.name || this.productName}` } async computePackageUrl(): Promise { diff --git a/src/builder.ts b/src/builder.ts index a9f671883dc..a239f445ab6 100644 --- a/src/builder.ts +++ b/src/builder.ts @@ -23,8 +23,9 @@ export async function createPublisher(packager: Packager, options: PublishOption throw new Error(`Please specify 'repository' in the dev package.json ('${packager.devPackageFile}')`) } else { - log(`Creating Github Publisher — user: ${info.user}, project: ${info.project}, version: ${packager.metadata.version}`) - return new GitHubPublisher(info.user, info.project, packager.metadata.version, options, isPublishOptionGuessed) + const version = packager.metadata.version! + log(`Creating Github Publisher — user: ${info.user}, project: ${info.project}, version: ${version}`) + return new GitHubPublisher(info.user, info.project, version, options, isPublishOptionGuessed) } } diff --git a/src/cliOptions.ts b/src/cliOptions.ts index c3cf477827e..706171cb9e8 100644 --- a/src/cliOptions.ts +++ b/src/cliOptions.ts @@ -12,7 +12,7 @@ export function createYargs(): any { .example("build --win --ia32", "build for Windows ia32") .option("mac", { group: buildGroup, - alias: ["m", "o", "osx"], + alias: ["m", "o", "osx", "macos"], describe: `Build for MacOS, accepts target list (see ${underline("https://goo.gl/HAnnq8")}).`, type: "array", }) diff --git a/src/metadata.ts b/src/metadata.ts index cc903ebdb65..95a6741303f 100755 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -9,7 +9,7 @@ export interface Metadata { # Application `package.json` */ export interface AppMetadata extends Metadata { - readonly version: string + readonly version?: string /* The application name. @@ -25,11 +25,11 @@ export interface AppMetadata extends Metadata { /* The application description. */ - readonly description: string + readonly description?: string readonly main?: string | null - readonly author: AuthorMetadata + readonly author?: AuthorMetadata /* The url to the project [homepage](https://docs.npmjs.com/files/package.json#homepage) (NuGet Package `projectUrl` (optional) or Linux Package URL (required)). diff --git a/src/packager.ts b/src/packager.ts index 5440bb24d73..07ec80452a7 100644 --- a/src/packager.ts +++ b/src/packager.ts @@ -63,7 +63,15 @@ export class Packager implements BuildInfo { this.isTwoPackageJsonProjectLayoutUsed = this.appDir !== this.projectDir const appPackageFile = this.projectDir === this.appDir ? devPackageFile : path.join(this.appDir, "package.json") - this.metadata = appPackageFile === devPackageFile ? (this.options.appMetadata || this.devMetadata) : deepAssign(await readPackageJson(appPackageFile), this.options.appMetadata) + if (appPackageFile === devPackageFile) { + if (this.options.appMetadata != null) { + this.devMetadata = deepAssign(this.devMetadata, this.options.appMetadata) + } + this.metadata = this.devMetadata + } + else { + this.metadata = deepAssign(await readPackageJson(appPackageFile), this.options.appMetadata) + } this.checkMetadata(appPackageFile, devPackageFile) checkConflictingOptions(this.devMetadata.build) @@ -147,7 +155,7 @@ export class Packager implements BuildInfo { throw new Error(`Please specify '${missedFieldName}' in the application package.json ('${appPackageFile}')`) } - const checkNotEmpty = (name: string, value: string) => { + const checkNotEmpty = (name: string, value: string | n) => { if (isEmptyOrSpaces(value)) { reportError(name) } @@ -178,10 +186,10 @@ export class Packager implements BuildInfo { } else { const author = appMetadata.author - if (author == null) { + if (author == null) { throw new Error(`Please specify "author" in the application package.json ('${appPackageFile}') — it is used as company name.`) } - else if (author.email == null && this.options.targets!.has(Platform.LINUX)) { + else if (author.email == null && this.options.targets!.has(Platform.LINUX)) { throw new Error(util.format(errorMessages.authorEmailIsMissed, appPackageFile)) } diff --git a/src/targets/LinuxTargetHelper.ts b/src/targets/LinuxTargetHelper.ts index ef14e2e3dc0..8b62f3b8e7f 100644 --- a/src/targets/LinuxTargetHelper.ts +++ b/src/targets/LinuxTargetHelper.ts @@ -64,7 +64,7 @@ export class LinuxTargetHelper { } } - async computeDesktopEntry(relativeExec: boolean): Promise { + async computeDesktopEntry(exec?: string): Promise { const appInfo = this.packager.appInfo const custom = this.packager.platformSpecificBuildOptions.desktop @@ -73,13 +73,11 @@ export class LinuxTargetHelper { } const productFilename = appInfo.productFilename - const appExec = relativeExec ? `"${productFilename}"` : `"${installPrefix}/${productFilename}/${productFilename}"` - const tempFile = path.join(await this.tempDirPromise, `${productFilename}.desktop`) await outputFile(tempFile, this.packager.platformSpecificBuildOptions.desktop || `[Desktop Entry] Name=${appInfo.productName} Comment=${this.packager.platformSpecificBuildOptions.description || appInfo.description} -Exec=${appExec} +Exec=${(exec == null ? `"${installPrefix}/${productFilename}/${productFilename}"` : exec)} Terminal=false Type=Application Icon=${appInfo.name} diff --git a/src/targets/appImage.ts b/src/targets/appImage.ts index 1ff9e0effd8..94fe267b02a 100644 --- a/src/targets/appImage.ts +++ b/src/targets/appImage.ts @@ -1,7 +1,7 @@ import { PlatformPackager, TargetEx } from "../platformPackager" import { LinuxBuildOptions, Arch } from "../metadata" import * as path from "path" -import { exec, unlinkIfExists } from "../util/util" +import { exec, unlinkIfExists, spawn, debug } from "../util/util" import { open, write, createReadStream, createWriteStream, close, chmod } from "fs-extra-p" import { LinuxTargetHelper } from "./LinuxTargetHelper" import { getBin } from "../util/binDownload" @@ -11,58 +11,51 @@ import { Promise as BluebirdPromise } from "bluebird" const __awaiter = require("../util/awaiter") const appImageVersion = "AppImage-5" -const appImagePathPromise = getBin("AppImage", appImageVersion, `https://dl.bintray.com/electron-userland/bin/${appImageVersion}.7z`, "") +const appImagePathPromise = getBin("AppImage", appImageVersion, `https://dl.bintray.com/electron-userland/bin/${appImageVersion}.7z`, "19833e5db3cbc546432de8ddc8a54181489e6faad4944bd1f3138adf4b771259") export default class AppImageTarget extends TargetEx { - private readonly desktopEntry: Promise - constructor(private packager: PlatformPackager, private helper: LinuxTargetHelper, private outDir: string) { super("appImage") - - this.desktopEntry = helper.computeDesktopEntry(true) } async build(appOutDir: string, arch: Arch): Promise { const packager = this.packager - const destination = path.join(this.outDir, packager.generateName(null, arch, true)) + const image = path.join(this.outDir, packager.generateName(null, arch, true)) const appInfo = packager.appInfo - - await unlinkIfExists(destination) + await unlinkIfExists(image) const appImagePath = await appImagePathPromise const args = [ "-joliet", "on", "-volid", "AppImage", - "-dev", destination, + "-dev", image, "-padding", "0", - "-map", appOutDir, "/usr/bin", "--", - "-map", await this.desktopEntry, `/${appInfo.name}.desktop`, "--", - "-map", path.join(appImagePath, arch === Arch.ia32 ? "32": "64", "AppRun"), "/AppRun", "--", + "-map", appOutDir, "/usr/bin", + "-map", path.join(__dirname, "..", "..", "templates", "linux", "AppRun.sh"), `/AppRun`, + "-move", `/usr/bin/${appInfo.productFilename}`, "/usr/bin/app", ] - for (let [from, to] of (await this.helper.icons)) { - args.push("-map", from, `/usr/share/icons/default/${to}`, "--",) + args.push("-map", from, `/usr/share/icons/default/${to}`) } // must be after this.helper.icons call if (this.helper.maxIconPath == null) { throw new Error("Icon is not provided") } - args.push("-map", this.helper.maxIconPath, "/.DirIcon", "--",) - args.push("-map", this.helper.maxIconPath, `/${appInfo.name}${path.extname(this.helper.maxIconPath)}`, "--",) + args.push("-map", this.helper.maxIconPath, "/.DirIcon") - args.push("-zisofs", `level=${packager.devMetadata.build.compression === "store" ? "0" : "9"}:block_size=128k:by_magic=off`, "-chown_r", "0") - args.push("/", "--", "set_filter_r", "--zisofs", "/") + // args.push("-zisofs", `level=0:block_size=128k:by_magic=off`, "-chown_r", "0") + // args.push("/", "--", "set_filter_r", "--zisofs", "/") - await exec("xorriso", args) + await exec(process.platform === "darwin" ? path.join(appImagePath, "xorriso") : "xorriso", args) - const fd = await open(destination, "r+") + const fd = await open(image, "r+") try { await new BluebirdPromise((resolve, reject) => { - const rd = createReadStream(path.join(appImagePath, arch === Arch.ia32 ? "32" : "64", "AppRun")) + const rd = createReadStream(path.join(appImagePath, arch === Arch.ia32 ? "32" : "64", "runtime")) rd.on("error", reject) - const wr = createWriteStream(destination, {fd: fd, autoClose: false}) + const wr = createWriteStream(image, {fd: fd, autoClose: false}) wr.on("error", reject) wr.on("finish", resolve) rd.pipe(wr) @@ -75,8 +68,15 @@ export default class AppImageTarget extends TargetEx { await close(fd) } - await chmod(destination, "0755") + await chmod(image, "0755") + // we archive because you cannot distribute exe as is - e.g. Ubuntu clear exec flag and user cannot just click on AppImage to run + // also, LZMA compression - 29MB vs zip 42MB + // we use slow xz instead of 7za because 7za doesn't preserve exec file permissions for xz + await spawn("xz", ["--compress", "--force", image], { + cwd: path.dirname(image), + stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"], + }) - packager.dispatchArtifactCreated(destination) + packager.dispatchArtifactCreated(image) } } \ No newline at end of file diff --git a/src/targets/fpm.ts b/src/targets/fpm.ts index 7b026ba71f4..c332641a0d7 100644 --- a/src/targets/fpm.ts +++ b/src/targets/fpm.ts @@ -3,7 +3,6 @@ import { smarten, PlatformPackager, TargetEx } from "../platformPackager" import { use, exec } from "../util/util" import * as path from "path" import { downloadFpm } from "../util/binDownload" -import { tmpdir } from "os" import { readFile, outputFile } from "fs-extra-p" import { Promise as BluebirdPromise } from "bluebird" import { LinuxTargetHelper, installPrefix } from "./LinuxTargetHelper" @@ -27,7 +26,7 @@ export default class FpmTarget extends TargetEx { super(name) this.scriptFiles = this.createScripts(helper.tempDirPromise) - this.desktopEntry = helper.computeDesktopEntry(false) + this.desktopEntry = helper.computeDesktopEntry() } private async createScripts(tempDirPromise: Promise): Promise> { @@ -62,7 +61,7 @@ export default class FpmTarget extends TargetEx { } const options = this.options - const author = options.maintainer || `${packager.appInfo.metadata.author.name} <${packager.appInfo.metadata.author.email}>` + const author = options.maintainer || `${appInfo.metadata.author!.name} <${appInfo.metadata.author!.email}>` const synopsis = options.synopsis const args = [ "-s", "dir", diff --git a/src/util/util.ts b/src/util/util.ts index c8ff8a184d0..a410cba6d75 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -7,7 +7,7 @@ import { readJson, stat, Stats, unlink } from "fs-extra-p" import { yellow, red } from "chalk" import debugFactory = require("debug") import IDebugger = debug.IDebugger -import { warn, task } from "./log" +import { warn, task, log } from "./log" import { createHash } from "crypto" //noinspection JSUnusedLocalSymbols @@ -79,6 +79,12 @@ export function exec(file: string, args?: Array | null, options?: ExecOp return new BluebirdPromise((resolve, reject) => { execFile(file, args, options, function (error, stdout, stderr) { if (error == null) { + if (debug.enabled) { + if (stderr.length !== 0) { + log(stderr) + } + // log(stdout) + } resolve(stdout) } else { diff --git a/templates/linux/AppRun.sh b/templates/linux/AppRun.sh new file mode 100755 index 00000000000..0961e95e890 --- /dev/null +++ b/templates/linux/AppRun.sh @@ -0,0 +1,239 @@ +#!/bin/bash + +# The purpose of this script is to provide lightweight desktop integration +# into the host system without special help from the host system. +# If you want to use it, then place this in usr/bin/$APPNAME.wrapper +# and set it as the Exec= line of the .desktop file in the AppImage. +# +# For example, to install the appropriate icons for Scribus, +# put them into the AppDir at the following locations: +# +# ./usr/share/icons/default/128x128/apps/scribus.png +# ./usr/share/icons/default/128x128/mimetypes/application-vnd.scribus.png +# +# Note that the filename application-vnd.scribus.png is derived from +# and must be match MimeType=application/vnd.scribus; in scribus.desktop +# (with "/" characters replaced by "-"). +# +# Then, change Exec=scribus to Exec=scribus.wrapper and place the script +# below in usr/bin/scribus.wrapper and make it executable. +# When you run AppRun, then AppRun runs the wrapper script below +# which in turn will run the main application. +# +# TODO: +# Handle multiple versions of the same AppImage? +# Handle removed AppImages? Currently we are just setting TryExec= +# See http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#DELETE +# Possibly move this to the C runtime that is part of every AppImage? + +# Exit on errors +set -e + +THIS="$0" + +HERE="$(dirname "$(readlink -f "${THIS}")")" +export PATH="${HERE}/usr/bin:${HERE}/usr/sbin:${PATH}" +export LD_LIBRARY_PATH="${HERE}/usr/lib:${LD_LIBRARY_PATH}" +export XDG_DATA_DIRS="${HERE}/usr/share:${XDG_DATA_DIRS}" +export GSETTINGS_SCHEMA_DIR="${HERE}/usr/share/glib-2.0/schemas:${GSETTINGS_SCHEMA_DIR}" + +# Be verbose if $DEBUG=1 is set +if [ ! -z "$DEBUG" ] ; then + env + set -x +fi + +args=("$@") # http://stackoverflow.com/questions/3190818/ +NUMBER_OF_ARGS="$#" + +# Please do not change $VENDORPREFIX as it will allow for desktop files +# belonging to AppImages to be recognized by future AppImageKit components +# such as desktop integration daemons +VENDORPREFIX=appimagekit + +echo "$APPDIR" + +BIN="${HERE}/usr/bin/app" + +trap atexit EXIT + +# Note that the following handles 0, 1 or more arguments (file paths) +# which can include blanks but uses a bashism; can the same be achieved +# in POSIX-shell? (FIXME) +# http://stackoverflow.com/questions/3190818 +atexit() +{ +if [ $NUMBER_OF_ARGS -eq 0 ] ; then + exec "${BIN}" +else + exec "${BIN}" "${args[@]}" +fi +} + +error() +{ + if [ -x /usr/bin/zenity ] ; then + LD_LIBRARY_PATH="" zenity --error --text "${1}" 2>/dev/null + elif [ -x /usr/bin/kdialog ] ; then + LD_LIBRARY_PATH="" kdialog --msgbox "${1}" 2>/dev/null + elif [ -x /usr/bin/Xdialog ] ; then + LD_LIBRARY_PATH="" Xdialog --msgbox "${1}" 2>/dev/null + else + echo "${1}" + fi + exit 1 +} + +yesno() +{ + TITLE=$1 + TEXT=$2 + if [ -x /usr/bin/zenity ] ; then + LD_LIBRARY_PATH="" zenity --question --title="$TITLE" --text="$TEXT" || exit 0 + elif [ -x /usr/bin/kdialog ] ; then + LD_LIBRARY_PATH="" kdialog --caption "Disk auswerfen?" --title "$TITLE" -yesno "$TEXT" || exit 0 + elif [ -x /usr/bin/Xdialog ] ; then + LD_LIBRARY_PATH="" Xdialog --title "$TITLE" --clear --yesno "$TEXT" 10 80 || exit 0 + else + echo "zenity, kdialog, Xdialog missing. Skipping ${THIS}." + exit 0 + fi +} + +check_prevent() +{ + FILE=$1 + if [ -e "$FILE" ] ; then + exit 0 + fi +} + +# Exit immediately of one of these files is present +# (e.g., because the desktop environment wants to handle desktop integration itself) +check_prevent "$HOME/.local/share/$VENDORPREFIX/no_desktopintegration" +check_prevent "/usr/share/$VENDORPREFIX/no_desktopintegration" +check_prevent "/etc/$VENDORPREFIX/no_desktopintegration" + +# Exit immediately if appimaged is running +pidof appimaged 2>/dev/null && exit 0 + +# Exit immediately if $DESKTOPINTEGRATION is not empty +if [ ! -z "$DESKTOPINTEGRATION" ] ; then + exit 0 +fi + +check_dep() +{ + DEP=$1 + if [ -z $(which $DEP) ] ; then + echo "$DEP is missing. Skipping ${THIS}." + exit 0 + fi +} + +# Check whether dependencies are present in base system (we do not bundle these) +# http://cgit.freedesktop.org/xdg/desktop-file-utils/ +check_dep desktop-file-validate +check_dep update-desktop-database +check_dep desktop-file-install +check_dep xdg-icon-resource +check_dep xdg-mime +check_dep xdg-desktop-menu + +DESKTOPFILE=$(find "$APPDIR" -maxdepth 1 -name "*.desktop" | head -n 1) +echo "$DESKTOPFILE" +DESKTOPFILE_NAME=$(basename $DESKTOPFILE) + +if [ ! -f "$DESKTOPFILE" ] ; then + echo "Desktop file is missing. Please run ${THIS} from within an AppImage." + exit 0 +fi + +if [ -z "$APPIMAGE" ] ; then + APPIMAGE="$APPDIR/AppRun" + # Not running from within an AppImage; hence using the AppRun for Exec= +fi + +# Construct path to the icon according to +# http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html +ABS_APPIMAGE=$(readlink -e "$APPIMAGE") +ICONURL="file://$ABS_APPIMAGE" +MD5=$(echo -n $ICONURL | md5sum | cut -c -32) +ICONFILE="$HOME/.cache/thumbnails/normal/$MD5.png" +if [ ! -f "$ICONFILE" ] ; then + echo "$ICONFILE is missing. Probably not running ${THIS} from within an AppImage." + echo "Hence falling back to using .DirIcon" + ICONFILE="$APPDIR/.DirIcon" +fi + +# $XDG_DATA_DIRS contains the default paths /usr/local/share:/usr/share +# desktop file has to be installed in an applications subdirectory +# of one of the $XDG_DATA_DIRS components +if [ -z "$XDG_DATA_DIRS" ] ; then + echo "\$XDG_DATA_DIRS is missing. Please run ${THIS} from within an AppImage." + exit 0 +fi + +# Determine where the desktop file should be installed +if [[ $EUID -ne 0 ]]; then + DESTINATION_DIR_DESKTOP="$HOME/.local/share/applications" + SYSTEM_WIDE="" +else + # TODO: Check $XDG_DATA_DIRS + DESTINATION_DIR_DESKTOP="/usr/local/share/applications" + SYSTEM_WIDE="--mode system" # for xdg-mime and xdg-icon-resource +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-$DESKTOPFILE_NAME" ] ; then + # echo "$DESTINATION_DIR_DESKTOP/$VENDORPREFIX-$DESKTOPFILE_NAME already there" + EXEC=$(grep "^Exec=" "$DESTINATION_DIR_DESKTOP/$VENDORPREFIX-$DESKTOPFILE_NAME" | head -n 1 | cut -d " " -f 1) + # echo $EXEC + if [ "Exec=$APPIMAGE" == "$EXEC" ] ; then + exit 0 + fi +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?" +fi + +APP=$(echo "$DESKTOPFILE_NAME" | sed -e 's/.desktop//g') + +# If the user has agreed, rewrite and install the desktop file, and the MIME information +if [ -z "$SKIP" ] ; then + # desktop-file-install is supposed to install .desktop files to the user's + # applications directory when run as a non-root user, + # and to /usr/share/applications if run as root + # but that does not really work for me... + desktop-file-install --rebuild-mime-info-cache \ + --vendor=$VENDORPREFIX --set-key=Exec --set-value="${APPIMAGE} %U" \ + --set-key=X-AppImage-Comment --set-value="Generated by ${THIS}" \ + --set-icon=$ICONFILE --set-key=TryExec --set-value=$APPIMAGE $DESKTOPFILE \ + --dir "$DESTINATION_DIR_DESKTOP" + chmod a+x "$DESTINATION_DIR_DESKTOP/"* + RESOURCE_NAME=$(echo "$VENDORPREFIX-$DESKTOPFILE_NAME" | sed -e 's/.desktop//g') + echo $RESOURCE_NAME + + # Install the icon files for the application; TODO: scalable + ICONS=$(find "${APPDIR}/usr/share/icons/" -wholename "*/apps/${APP}.png" || true) + for ICON in $ICONS ; do + ICON_SIZE=$(echo "${ICON}" | rev | cut -d "/" -f 3 | rev | cut -d "x" -f 1) + xdg-icon-resource install --context apps --size ${ICON_SIZE} "${ICON}" "${RESOURCE_NAME}" + done + + # Install mime type + find "${APPDIR}/usr/share/mime/" -type f -name *xml -exec xdg-mime install $SYSTEM_WIDE --novendor {} \; || true + + # Install the icon files for the mime type; TODO: scalable + ICONS=$(find "${APPDIR}/usr/share/icons/" -wholename "*/mimetypes/*.png" || true) + for ICON in $ICONS ; do + ICON_SIZE=$(echo "${ICON}" | rev | cut -d "/" -f 3 | rev | cut -d "x" -f 1) + xdg-icon-resource install --context mimetypes --size ${ICON_SIZE} "${ICON}" $(basename $ICON | sed -e 's/.png//g') + done + + xdg-desktop-menu forceupdate + gtk-update-icon-cache # for MIME +fi \ No newline at end of file diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index 1aa913f3ca0..1f554ab6ff6 100755 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -137,6 +137,10 @@ async function checkLinuxResult(projectDir: string, packager: Packager, checkOpt return result } + if (nameToTarget.has("appimage")) { + return + } + assertThat(getFileNames(artifacts)).containsAll(getExpected()) if (!nameToTarget.has("deb")) { diff --git a/test/src/linuxPackagerTest.ts b/test/src/linuxPackagerTest.ts index 1729174b367..0d0b8753a4a 100755 --- a/test/src/linuxPackagerTest.ts +++ b/test/src/linuxPackagerTest.ts @@ -9,10 +9,14 @@ const __awaiter = require("out/util/awaiter") test.ifNotWindows("deb", () => assertPack("test-app-one", platform(Platform.LINUX))) -// test.ifDevOrLinuxCi("AppImage", () => assertPack("test-app-one", { -// targets: Platform.LINUX.createTarget("appimage") -// } -// )) +test.ifDevOrLinuxCi("AppImage", () => assertPack("test-app-one", { + targets: Platform.LINUX.createTarget("appimage"), + appMetadata: { + name: "Bar", + productName: "Bar", + }, + } +)) test.ifDevOrLinuxCi("targets", () => assertPack("test-app-one", { targets: Platform.LINUX.createTarget(),