diff --git a/.travis.yml b/.travis.yml
index b514f5a3ad8..0b8c13901c2 100755
--- a/.travis.yml
+++ b/.travis.yml
@@ -30,6 +30,8 @@ addons:
- icnsutils
- graphicsmagick
- mono-devel
+ - bsdtar
+ - rpm
before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 3aed0a6fa1d..6a18e9c24e5 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -3,7 +3,7 @@ FROM buildpack-deps:xenial-curl
# rpm is required for FPM to build rpm package
RUN apt-get update -y && \
-apt-get install --no-install-recommends -y build-essential icnsutils graphicsmagick gcc-multilib g++-multilib libgnome-keyring-dev zip rpm && \
+apt-get install --no-install-recommends -y bsdtar build-essential autoconf libssl-dev icnsutils graphicsmagick gcc-multilib g++-multilib libgnome-keyring-dev zip rpm && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
@@ -42,4 +42,74 @@ COPY test.sh /test.sh
WORKDIR /project
ENV DEBUG_COLORS true
-ENV FORCE_COLOR true
\ No newline at end of file
+ENV FORCE_COLOR true
+
+# copied from https://github.com/docker-library/ruby/blob/0b94677b368947b64dcdcb312cd81ba946df3676/2.3/Dockerfile
+
+# skip installing gem documentation
+RUN mkdir -p /usr/local/etc \
+ && { \
+ echo 'install: --no-document'; \
+ echo 'update: --no-document'; \
+ } >> /usr/local/etc/gemrc
+
+ENV RUBY_MAJOR 2.3
+ENV RUBY_VERSION 2.3.1
+ENV RUBY_DOWNLOAD_SHA256 b87c738cb2032bf4920fef8e3864dc5cf8eae9d89d8d523ce0236945c5797dcd
+ENV RUBYGEMS_VERSION 2.6.4
+
+# some of ruby's build scripts are written in ruby
+# we purge this later to make sure our final image uses what we just built
+RUN set -ex \
+ && buildDeps=' \
+ bison \
+ libgdbm-dev \
+ ruby \
+ ' \
+ && apt-get update \
+ && apt-get install -y --no-install-recommends $buildDeps \
+ && rm -rf /var/lib/apt/lists/* \
+ && curl -fSL -o ruby.tar.gz "http://cache.ruby-lang.org/pub/ruby/$RUBY_MAJOR/ruby-$RUBY_VERSION.tar.gz" \
+ && echo "$RUBY_DOWNLOAD_SHA256 *ruby.tar.gz" | sha256sum -c - \
+ && mkdir -p /usr/src/ruby \
+ && tar -xzf ruby.tar.gz -C /usr/src/ruby --strip-components=1 \
+ && rm ruby.tar.gz \
+ && cd /usr/src/ruby \
+ && { echo '#define ENABLE_PATH_CHECK 0'; echo; cat file.c; } > file.c.new && mv file.c.new file.c \
+ && autoconf \
+ && ./configure --disable-install-doc \
+ && make -j"$(nproc)" \
+ && make install \
+ && apt-get purge -y --auto-remove $buildDeps \
+ && gem update --system $RUBYGEMS_VERSION \
+ && rm -r /usr/src/ruby
+
+ENV BUNDLER_VERSION 1.12.4
+
+RUN gem install bundler --version "$BUNDLER_VERSION"
+
+# install things globally, for great justice
+# and don't create ".bundle" in all our apps
+ENV GEM_HOME /usr/local/bundle
+ENV BUNDLE_PATH="$GEM_HOME" \
+ BUNDLE_BIN="$GEM_HOME/bin" \
+ BUNDLE_SILENCE_ROOT_WARNING=1 \
+ BUNDLE_APP_CONFIG="$GEM_HOME"
+ENV PATH $BUNDLE_BIN:$PATH
+RUN mkdir -p "$GEM_HOME" "$BUNDLE_BIN" \
+ && chmod 777 "$GEM_HOME" "$BUNDLE_BIN" \
+ && mkdir /fpm && curl -L https://github.com/jordansissel/fpm/archive/6e2514df27664912826b4fcd89affa19df0e713b.tar.gz | tar -xz -C /fpm --strip-components 1 && cd /fpm && bundle install && make install && cd ..
+
+# use fpm commit https://github.com/jordansissel/fpm/commit/6e2514df27664912826b4fcd89affa19df0e713b because of some important unreleased fixes:
+# https://github.com/jordansissel/fpm/commit/94be82c0a23c8cd641ab9e60f3eb4a8db445fff0
+# https://github.com/jordansissel/fpm/commit/77b95747b9cc01ca420ee24084a449b3ac19e6d5
+
+ENV USE_SYSTEM_FPM true
+
+# fix error /usr/local/bundle/gems/fpm-1.5.0/lib/fpm/package/freebsd.rb:72:in `encode': "\xE2" from ASCII-8BIT to UTF-8 (Encoding::UndefinedConversionError)
+# http://jaredmarkell.com/docker-and-locales/
+# http://askubuntu.com/a/601498
+RUN locale-gen en_US.UTF-8
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
+ENV LC_ALL en_US.UTF-8
\ No newline at end of file
diff --git a/docker/readme.md b/docker/readme.md
index 423ce2b2da8..662b6f92597 100644
--- a/docker/readme.md
+++ b/docker/readme.md
@@ -1,14 +1,17 @@
# Development machine
+To build Linux:
```sh
docker run --rm -ti -v `pwd`:/project -v `pwd`/node_modules/.linux:/project/node_modules -v ~/.electron:/root/.electron electronuserland/electron-builder
```
-Wine:
+To build windows:
```sh
docker run --rm -ti -v ${PWD}:/project -v ${PWD##*/}-node-modules:/project/node_modules -v ~/.electron:/root/.electron electronuserland/electron-builder:wine
```
+Consider using `/test.sh` to install npm dependencies and run tests.
+
# CI Server
```sh
@@ -22,6 +25,10 @@ docker build -t electronuserland/electron-builder docker
docker build -t electronuserland/electron-builder:wine docker/wine
```
+Or just `npm run docker-images`
+
+# Notes
+
* We use [named data volume](https://madcoda.com/2016/03/docker-named-volume-explained/) instead of mounted host directory to store `node_modules` because NPM is unreliable and NPM team [doesn't want to fix it](https://github.com/npm/npm/issues/3565).
`${PWD##*/}-node-modules` is used as name of data volume — it is your current directory name (e. g. `foo`) and suffix `-node-modules`.
diff --git a/docs/Multi Platform Build.md b/docs/Multi Platform Build.md
index fbc2991dd7f..44de072c4b0 100755
--- a/docs/Multi Platform Build.md
+++ b/docs/Multi Platform Build.md
@@ -25,19 +25,25 @@ To build app in distributable format for Linux on OS X:
brew install gnu-tar libicns graphicsmagick
```
+To build rpm: `brew install rpm`.
+
## Linux
To build app in distributable format for Linux:
```
-sudo apt-get install icnsutils graphicsmagick xz-utils
+sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils
```
+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:
* Install Wine (1.8+ is required):
```
sudo add-apt-repository ppa:ubuntu-wine/ppa -y
sudo apt-get update
- sudo apt-get install wine1.8 -y
+ sudo apt-get install --no-install-recommends -y wine1.8
```
* Install [Mono](http://www.mono-project.com/docs/getting-started/install/linux/#usage) (4.2+ is required):
@@ -46,18 +52,18 @@ To build app in distributable format for Windows on Linux:
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list
sudo apt-get update
- sudo apt-get install mono-devel ca-certificates-mono -y
+ sudo apt-get install --no-install-recommends -y mono-devel ca-certificates-mono
```
* Install zip.
```
- apt-get install zip
+ apt-get install --no-install-recommends -y zip
```
To build app in 32 bit from a machine with 64 bit:
```
-sudo apt-get install -y gcc-multilib g++-multilib
+sudo apt-get install --no-install-recommends -y gcc-multilib g++-multilib
```
### Travis Linux
diff --git a/docs/Options.md b/docs/Options.md
index ece85f6f0b9..68359cf329d 100644
--- a/docs/Options.md
+++ b/docs/Options.md
@@ -71,7 +71,7 @@ See all [appdmg options](https://www.npmjs.com/package/appdmg#json-specification
| --- | ---
| icon | The path to icon, which will be shown when mounted (default: `build/icon.icns`).
| background |
The path to background (default: build/background.png
if exists). The resolution of this file determines the resolution of the installer window. If background is not specified, use window.size
, see [specification](https://github.com/LinusU/node-appdmg#json-specification).
-| target | Target package type: list of `default`, `dmg`, `zip`, `mas`, `7z`. Defaults to `default` (dmg and zip for Squirrel.Mac).
+| target | Target package type: list of `default`, `dmg`, `zip`, `mas`, `7z`, `tar.xz`, `tar.gz`, `tar.bz2`, `tar.7z`. 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).
| entitlements | The path to entitlements file for signing the app. build/osx.entitlements
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/osx.inherit.entitlements
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.darwin.inherit.entitlements).
This option only applies when signing with entitlements
provided.
@@ -107,6 +107,7 @@ MAS (Mac Application Store) specific options (in addition to `build.osx`).
| vendor | The vendor. Defaults to [author](#AppMetadata-author).
| compression | *deb-only.* The compression type, one of `gz`, `bzip2`, `xz` (default: `xz`).
| depends | Package dependencies. Defaults to `["libappindicator1", "libnotify-bin"]`.
+| target | Target package type: list of default
, deb
, rpm
, freebsd
, pacman
, p5p
, apk
, 7z
, zip
, tar.xz
, tar.gz
, tar.bz2
, tar.7z
. Defaults to default
(deb
).
Only deb
is tested. Feel free to file issues for rpm
and other package formats.
## `.directories`
diff --git a/package.json b/package.json
index 9e98ff3ab9d..917fcdb541c 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,6 @@
"lint": "tslint src/*.ts test/src/*.ts",
"pretest": "npm run compile && npm run lint",
"test": "node ./test/out/helpers/runTests.js",
- "ci-test": "git-lfs pull && npm install && npm prune && npm run test",
"semantic-release": "semantic-release pre && npm publish && semantic-release post",
"//": "Update wiki if docs changed. Update only if functionalily are generally available (latest release, not next)",
"update-wiki": "git subtree split -b wiki --prefix docs/ && git push wiki wiki:master",
@@ -70,11 +69,12 @@
"electron-packager-tf": "~7.1.0",
"electron-winstaller-fixed": "~2.8.3",
"fs-extra-p": "^1.0.1",
- "globby": "^4.0.0",
+ "globby": "^4.1.0",
"hosted-git-info": "^2.1.5",
"image-size": "^0.5.0",
"lodash.template": "^4.2.5",
"mime": "^1.3.4",
+ "pipe-io": "^1.2.1",
"progress": "^1.1.8",
"progress-stream": "^1.2.0",
"read-package-json": "^2.0.4",
@@ -105,11 +105,11 @@
"plist": "^1.2.0",
"pre-git": "^3.8.4",
"semantic-release": "^6.2.2",
- "should": "^8.3.1",
+ "should": "^8.3.2",
"ts-babel": "^0.8.6",
"tsconfig-glob": "^0.4.3",
- "tslint": "3.10.2",
- "typescript": "1.9.0-dev.20160515",
+ "tslint": "3.10.0-dev.2",
+ "typescript": "1.9.0-dev.20160520-1.0",
"whitespace": "^2.0.0"
},
"babel": {
diff --git a/src/fpmDownload.ts b/src/fpmDownload.ts
index ece9d01d704..3d8ff107498 100644
--- a/src/fpmDownload.ts
+++ b/src/fpmDownload.ts
@@ -1,4 +1,4 @@
-import { statOrNull, spawn, debug, debug7z } from "./util"
+import { statOrNull, spawn, debug, debug7zArgs } from "./util"
import { writeFile, rename, remove, unlink, emptyDir } from "fs-extra-p"
import { download } from "./httpRequest"
import { path7za } from "7zip-bin"
@@ -55,15 +55,7 @@ async function doDownloadFpm(version: string, osAndArch: string): Promise {
- private readonly debOptions: LinuxBuildOptions
+ private readonly buildOptions: LinuxBuildOptions
private readonly packageFiles: Promise>
private readonly scriptFiles: Promise>
@@ -25,7 +25,7 @@ export class LinuxPackager extends PlatformPackager {
constructor(info: BuildInfo) {
super(info)
- this.debOptions = Object.assign({
+ this.buildOptions = Object.assign({
name: this.metadata.name,
description: this.metadata.description,
}, this.customBuildOptions)
@@ -47,6 +47,10 @@ export class LinuxPackager extends PlatformPackager {
}
}
+ protected get supportedTargets(): Array {
+ return ["deb", "rpm", "sh", "freebsd", "pacman", "apk", "p5p"]
+ }
+
get platform() {
return Platform.LINUX
}
@@ -75,9 +79,9 @@ export class LinuxPackager extends PlatformPackager {
private async computeDesktop(tempDir: string): Promise> {
const tempFile = path.join(tempDir, this.appName + ".desktop")
- await outputFile(tempFile, this.debOptions.desktop || `[Desktop Entry]
+ await outputFile(tempFile, this.buildOptions.desktop || `[Desktop Entry]
Name=${this.appName}
-Comment=${this.debOptions.description}
+Comment=${this.buildOptions.description}
Exec="${installPrefix}/${this.appName}/${this.appName}"
Terminal=false
Type=Application
@@ -168,26 +172,27 @@ Icon=${this.metadata.name}
const templateOptions = Object.assign({
// old API compatibility
executable: this.appName,
- }, this.debOptions)
+ }, this.buildOptions)
- const afterInstallTemplate = this.debOptions.afterInstall || path.join(defaultTemplatesDir, "after-install.tpl")
+ const afterInstallTemplate = this.buildOptions.afterInstall || path.join(defaultTemplatesDir, "after-install.tpl")
const afterInstallFilePath = writeConfigFile(tempDir, afterInstallTemplate, templateOptions)
- const afterRemoveTemplate = this.debOptions.afterRemove || path.join(defaultTemplatesDir, "after-remove.tpl")
+ const afterRemoveTemplate = this.buildOptions.afterRemove || path.join(defaultTemplatesDir, "after-remove.tpl")
const afterRemoveFilePath = writeConfigFile(tempDir, afterRemoveTemplate, templateOptions)
return await BluebirdPromise.all([afterInstallFilePath, afterRemoveFilePath])
}
- async packageInDistributableFormat(outDir: string, appOutDir: string, arch: string): Promise {
- return await this.buildDeb(this.debOptions, outDir, appOutDir, arch)
- .then(it => this.dispatchArtifactCreated(it))
+ packageInDistributableFormat(outDir: string, appOutDir: string, arch: string): Promise {
+ return BluebirdPromise.map(this.targets, (target): any => {
+ target = target === "default" ? "deb" : target
+ const destination = path.join(outDir, `${this.metadata.name}-${this.metadata.version}${archSuffix(arch)}.${target}`)
+ return (target === "zip" || target === "7z" || target.startsWith("tar.") ? this.archiveApp(target, appOutDir, destination) : this.buildPackage(destination, target, this.buildOptions, appOutDir, arch))
+ .then(() => this.dispatchArtifactCreated(destination))
+ })
}
- private async buildDeb(options: LinuxBuildOptions, outDir: string, appOutDir: string, arch: string): Promise {
- const archName = arch === "ia32" ? "i386" : "amd64"
- const target = "deb"
- const destination = path.join(outDir, `${this.metadata.name}-${this.metadata.version}-${archName}.${target}`)
+ private async buildPackage(destination: string, target: string, options: LinuxBuildOptions, appOutDir: string, arch: string): Promise {
const scripts = await this.scriptFiles
const projectUrl = await this.computePackageUrl()
@@ -196,24 +201,35 @@ Icon=${this.metadata.name}
}
const author = options.maintainer || `${this.metadata.author.name} <${this.metadata.author.email}>`
+ const synopsis = options.synopsis
const args = [
"-s", "dir",
"-t", target,
- "--architecture", archName,
- "--rpm-os", "linux",
+ "--architecture", arch === "ia32" ? "i386" : "amd64",
"--name", this.metadata.name,
"--force",
"--after-install", scripts[0],
"--after-remove", scripts[1],
- "--description", `${options.synopsis || ""}\n ${this.debOptions.description}`,
+ "--description", smarten(target === "rpm" ? this.buildOptions.description! : `${synopsis || ""}\n ${this.buildOptions.description}`),
"--maintainer", author,
"--vendor", options.vendor || author,
"--version", this.metadata.version,
"--package", destination,
- "--deb-compression", options.compression || (this.devMetadata.build.compression === "store" ? "gz" : "xz"),
"--url", projectUrl,
]
+ if (target === "deb") {
+ args.push("--deb-compression", options.compression || (this.devMetadata.build.compression === "store" ? "gz" : "xz"))
+ }
+ else if (target === "rpm") {
+ // args.push("--rpm-compression", options.compression || (this.devMetadata.build.compression === "store" ? "none" : "xz"))
+ args.push("--rpm-os", "linux")
+
+ if (synopsis != null) {
+ args.push("--rpm-summary", smarten(synopsis))
+ }
+ }
+
let depends = options.depends
if (depends == null) {
depends = ["libappindicator1", "libnotify-bin"]
@@ -239,7 +255,6 @@ Icon=${this.metadata.name}
args.push(`${appOutDir}/=${installPrefix}/${this.appName}`)
args.push(...(await this.packageFiles)!)
await exec(await this.fpmPath, args)
- return destination
}
}
diff --git a/src/metadata.ts b/src/metadata.ts
index 81d1ef2cece..19b77f91099 100755
--- a/src/metadata.ts
+++ b/src/metadata.ts
@@ -170,7 +170,7 @@ export interface OsXBuildOptions extends PlatformSpecificBuildOptions {
readonly background?: string | null
/*
- Target package type: list of `default`, `dmg`, `zip`, `mas`, `7z`. Defaults to `default` (dmg and zip for Squirrel.Mac).
+ Target package type: list of `default`, `dmg`, `zip`, `mas`, `7z`, `tar.xz`, `tar.gz`, `tar.bz2`, `tar.7z`. Defaults to `default` (dmg and zip for Squirrel.Mac).
*/
readonly target?: Array | null
@@ -261,7 +261,7 @@ export interface WinBuildOptions extends PlatformSpecificBuildOptions {
/*
### `.build.linux`
*/
-export interface LinuxBuildOptions {
+export interface LinuxBuildOptions extends PlatformSpecificBuildOptions {
/*
As [description](#AppMetadata-description) from application package.json, but allows you to specify different for Linux.
*/
@@ -300,6 +300,13 @@ export interface LinuxBuildOptions {
Package dependencies. Defaults to `["libappindicator1", "libnotify-bin"]`.
*/
readonly depends?: string[] | null
+
+ /*
+ Target package type: list of `default`, `deb`, `rpm`, `freebsd`, `pacman`, `p5p`, `apk`, `7z`, `zip`, `tar.xz`, `tar.gz`, `tar.bz2`, `tar.7z`. Defaults to `default` (`deb`).
+
+ Only `deb` is tested. Feel free to file issues for `rpm` and other package formats.
+ */
+ readonly target?: Array | null
}
/*
@@ -324,6 +331,8 @@ export interface MetadataDirectories {
export interface PlatformSpecificBuildOptions {
readonly extraResources?: Array | null
+
+ readonly target?: Array | null
}
export class Platform {
diff --git a/src/osxPackager.ts b/src/osxPackager.ts
index 43c17c7894a..b5aa756933c 100644
--- a/src/osxPackager.ts
+++ b/src/osxPackager.ts
@@ -1,10 +1,9 @@
-import { PlatformPackager, BuildInfo, normalizeTargets } from "./platformPackager"
+import { PlatformPackager, BuildInfo } from "./platformPackager"
import { Platform, OsXBuildOptions, MasBuildOptions } from "./metadata"
import * as path from "path"
import { Promise as BluebirdPromise } from "bluebird"
-import { log, debug, debug7z, spawn, statOrNull, warn } from "./util"
+import { log, debug, statOrNull, warn } from "./util"
import { createKeychain, deleteKeychain, CodeSigningInfo, generateKeychainName } from "./codeSign"
-import { path7za } from "7zip-bin"
import deepAssign = require("deep-assign")
import { sign, flat, BaseSignOptions, SignOptions, FlatOptions } from "electron-osx-sign-tf"
import { readdir } from "fs-extra-p"
@@ -15,8 +14,6 @@ const __awaiter = require("./awaiter")
export default class OsXPackager extends PlatformPackager {
codeSigningInfo: Promise
- readonly targets: Array
-
readonly resourceList: Promise>
constructor(info: BuildInfo, cleanupTasks: Array<() => Promise>) {
@@ -31,16 +28,6 @@ export default class OsXPackager extends PlatformPackager {
this.codeSigningInfo = BluebirdPromise.resolve(null)
}
- const targets = normalizeTargets(this.customBuildOptions.target)
- if (targets != null) {
- for (let target of targets) {
- if (target !== "default" && target !== "dmg" && target !== "zip" && target !== "mas" && target !== "7z") {
- throw new Error("Unknown target: " + target)
- }
- }
- }
- this.targets = targets == null ? ["default"] : targets
-
this.resourceList = readdir(this.buildResourcesDir)
}
@@ -48,6 +35,10 @@ export default class OsXPackager extends PlatformPackager {
return Platform.OSX
}
+ protected get supportedTargets(): Array {
+ return ["dmg", "mas"]
+ }
+
async pack(outDir: string, arch: string, postAsyncTasks: Array>): Promise {
const packOptions = this.computePackOptions(outDir, arch)
let nonMasPromise: Promise | null = null
@@ -217,46 +208,12 @@ export default class OsXPackager extends PlatformPackager {
log("Creating OS X " + format)
// for default we use mac to be compatible with Squirrel.Mac
const classifier = target === "default" ? "mac" : "osx"
- promises.push(this.archiveApp(appOutDir, format, classifier)
- .then(it => this.dispatchArtifactCreated(it, `${this.metadata.name}-${this.metadata.version}-${classifier}.${format}`)))
+ // we use app name here - see https://github.com/electron-userland/electron-builder/pull/204
+ const outFile = path.join(appOutDir, `${this.appName}-${this.metadata.version}-${classifier}.${format}`)
+ promises.push(this.archiveApp(format, appOutDir, outFile)
+ .then(() => this.dispatchArtifactCreated(outFile, `${this.metadata.name}-${this.metadata.version}-${classifier}.${format}`)))
}
}
return BluebirdPromise.all(promises)
}
-
- private archiveApp(outDir: string, format: string, classifier: string): Promise {
- const args = ["a", "-bd"]
- if (debug7z.enabled) {
- args.push("-bb3")
- }
- else if (!debug.enabled) {
- args.push("-bb0")
- }
-
- const compression = this.devMetadata.build.compression
- const storeOnly = compression === "store"
- if (format === "zip" || storeOnly) {
- args.push("-mm=" + (storeOnly ? "Copy" : "Deflate"))
- }
- if (compression === "maximum") {
- // http://superuser.com/a/742034
- //noinspection SpellCheckingInspection
- if (format === "zip") {
- args.push("-mfb=258", "-mpass=15")
- }
- else if (format === "7z") {
- args.push("-m0=lzma2", "-mx=9", "-mfb=64", "-md=32m", "-ms=on")
- }
- }
-
- // we use app name here - see https://github.com/electron-userland/electron-builder/pull/204
- const resultPath = `${this.appName}-${this.metadata.version}-${classifier}.${format}`
- args.push(resultPath, this.appName + ".app")
-
- return spawn(path7za, args, {
- cwd: outDir,
- stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"],
- })
- .thenReturn(path.join(outDir, resultPath))
- }
}
\ No newline at end of file
diff --git a/src/platformPackager.ts b/src/platformPackager.ts
index 079adccc19e..6b4a0905b85 100644
--- a/src/platformPackager.ts
+++ b/src/platformPackager.ts
@@ -5,17 +5,20 @@ import { Promise as BluebirdPromise } from "bluebird"
import * as path from "path"
import packager = require("electron-packager-tf")
import globby = require("globby")
-import { copy } from "fs-extra-p"
-import { statOrNull, use } from "./util"
+import { copy, unlink } from "fs-extra-p"
+import { statOrNull, use, spawn, debug7zArgs, debug, doSpawn, handleProcess } from "./util"
import { Packager } from "./packager"
import deepAssign = require("deep-assign")
+import pipeCallback = require("pipe-io")
import { listPackage, statFile } from "asar"
import ElectronPackagerOptions = ElectronPackager.ElectronPackagerOptions
+import { path7za } from "7zip-bin"
//noinspection JSUnusedLocalSymbols
const __awaiter = require("./awaiter")
const pack = BluebirdPromise.promisify(packager)
+const pipe = BluebirdPromise.promisify(pipeCallback)
export interface PackagerOptions {
arch?: string | null
@@ -76,6 +79,8 @@ export abstract class PlatformPackager
readonly appName: string
+ readonly targets: Array
+
public abstract get platform(): Platform
constructor(protected info: BuildInfo) {
@@ -87,12 +92,25 @@ export abstract class PlatformPackager
this.buildResourcesDir = path.resolve(this.projectDir, this.relativeBuildResourcesDirname)
this.customBuildOptions = (info.devMetadata.build)[this.platform.buildConfigurationKey] || Object.create(null)
this.appName = getProductName(this.metadata, this.devMetadata)
+
+ const targets = normalizeTargets(this.customBuildOptions.target)
+ if (targets != null) {
+ const supportedTargets = this.supportedTargets.concat("default", "zip", "7z", "tar.xz", "tar.7z", "tar.gz", "tar.bz2")
+ for (let target of targets) {
+ if (!supportedTargets.includes(target)) {
+ throw new Error("Unknown target: " + target)
+ }
+ }
+ }
+ this.targets = targets == null ? ["default"] : targets
}
protected get relativeBuildResourcesDirname() {
return use(this.devMetadata.directories, it => it!.buildResources) || "build"
}
+ protected abstract get supportedTargets(): Array
+
protected computeAppOutDir(outDir: string, arch: string): string {
return path.join(outDir, `${this.appName}-${this.platform.nodeName}-${arch}`)
}
@@ -136,7 +154,7 @@ export abstract class PlatformPackager
tmpdir: false,
"version-string": {
CompanyName: this.metadata.author.name,
- FileDescription: this.metadata.description,
+ FileDescription: smarten(this.metadata.description),
ProductName: this.appName,
InternalName: this.appName,
}
@@ -271,6 +289,73 @@ export abstract class PlatformPackager
throw new Error(`Application entry file ${mainFile} could not be found in package. Seems like a wrong configuration.`)
}
}
+
+ protected async archiveApp(format: string, appOutDir: string, outFile: string): Promise {
+ const compression = this.devMetadata.build.compression
+ const storeOnly = compression === "store"
+ // remove file before - 7z doesn't overwrite file, but update
+ try {
+ await unlink(outFile)
+ }
+ catch (e) {
+ // ignore
+ }
+
+ const args = debug7zArgs("a")
+ if (compression === "maximum") {
+ if (format === "7z" || format.endsWith(".7z")) {
+ args.push("-mx=9", "-mfb=64", "-md=32m", "-ms=on")
+ }
+ else if (format === "zip") {
+ // http://superuser.com/a/742034
+ //noinspection SpellCheckingInspection
+ args.push("-mfb=258", "-mpass=15")
+ }
+ else {
+ args.push("-mx=9")
+ }
+ }
+ else if (storeOnly) {
+ if (format !== "zip") {
+ args.push("-mx=1")
+ }
+ }
+
+ const fileToArchive = this.platform === Platform.OSX ? path.join(appOutDir, `${this.appName}.app`) : appOutDir
+ const baseDir = path.dirname(fileToArchive)
+ if (format.startsWith("tar.")) {
+ const compressProcess = doSpawn(path7za, args.concat("-si", outFile), {cwd: baseDir, stdio: ["pipe", "ignore", "inherit"]})
+ const tarProcess = doSpawn(path7za, debug7zArgs("a").concat("-so", "anyName.tar", fileToArchive), {cwd: baseDir, stdio: ["ignore", "pipe", "inherit"]})
+
+ // we must not use "inherit" - read/write pipe don't work in this case
+ // nodejs pipe is very tricky, so, we use hgh-level library
+ await BluebirdPromise.all([
+ pipe([tarProcess.stdout, compressProcess.stdin]),
+ new BluebirdPromise((resolve, reject) => {
+ handleProcess("exit", tarProcess, "7za", resolve, reject)
+ }),
+ new BluebirdPromise((resolve, reject) => {
+ handleProcess("exit", compressProcess, "7za", resolve, reject)
+ }),
+ ])
+ return
+ }
+
+ if (format === "zip" || storeOnly) {
+ args.push("-mm=" + (storeOnly ? "Copy" : "Deflate"))
+ }
+
+ args.push(outFile, fileToArchive)
+
+ await spawn(path7za, args, {
+ cwd: baseDir,
+ stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"],
+ })
+ }
+}
+
+export function archSuffix(arch: string) {
+ return arch === "x64" ? "" : `-${arch}`
}
export interface ArtifactCreated {
@@ -288,3 +373,17 @@ export function normalizeTargets(targets: Array | string | null | undefi
return (Array.isArray(targets) ? targets : [targets]).map(it => it.toLowerCase().trim())
}
}
+
+// fpm bug - rpm build --description is not escaped, well... decided to replace quite to smart quote
+// http://leancrew.com/all-this/2010/11/smart-quotes-in-javascript/
+export function smarten(s: string): string {
+ // opening singles
+ s = s.replace(/(^|[-\u2014\s(\["])'/g, "$1\u2018")
+ // closing singles & apostrophes
+ s = s.replace(/'/g, "\u2019")
+ // opening doubles
+ s = s.replace(/(^|[-\u2014/\[(\u2018\s])"/g, "$1\u201c")
+ // closing doubles
+ s = s.replace(/"/g, "\u201d")
+ return s
+}
\ No newline at end of file
diff --git a/src/util.ts b/src/util.ts
index 9e869470d9b..6c788e5517a 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -92,18 +92,37 @@ export function exec(file: string, args?: Array | null, options?: ExecOp
})
}
-export function spawn(command: string, args?: Array | null, options?: SpawnOptions, processConsumer?: (it: ChildProcess, reject: (error: Error) => void) => void): BluebirdPromise {
- const notNullArgs = args || []
+export function doSpawn(command: string, args: Array, options?: SpawnOptions): ChildProcess {
if (debug.enabled) {
- debug(`Spawning ${command} ${notNullArgs.join(" ")}`)
+ debug(`Spawning ${command} ${args.join(" ")}`)
}
+ const childProcess = _spawn(command, args, options)
+ if (debug.enabled) {
+ debug(`Spawned (${childProcess.pid}) ${command} ${args.join(" ")}`)
+ }
+ return childProcess
+}
+export function spawn(command: string, args?: Array | null, options?: SpawnOptions): BluebirdPromise {
return new BluebirdPromise((resolve, reject) => {
- const p = _spawn(command, notNullArgs, options)
- p.on("error", reject)
- p.on("close", (code: number) => code === 0 ? resolve() : reject(new Error(command + " exited with code " + code)))
- if (processConsumer != null) {
- processConsumer(p, reject)
+ const notNullArgs = args || []
+ const childProcess = doSpawn(command, notNullArgs, options)
+ handleProcess("close", childProcess, command, resolve, reject)
+ })
+}
+
+export function handleProcess(event: string, childProcess: ChildProcess, command: string, resolve: ((value?: any) => void) | null, reject: (reason?: any) => void) {
+ childProcess.on("error", reject)
+ childProcess.on(event, (code: number) => {
+ if (debug.enabled) {
+ debug(`${command} (${childProcess.pid}) exited with code ${code}`)
+ }
+
+ if (code !== 0) {
+ reject(new Error(`${command} exited with code ${code}`))
+ }
+ else if (resolve != null) {
+ resolve()
}
})
}
@@ -172,4 +191,15 @@ export async function computeDefaultAppDirectory(projectDir: string, userAppDir:
export function use(value: T | null, task: (it: T) => R): R | null {
return value == null ? null : task(value)
+}
+
+export function debug7zArgs(command: "a" | "x"): Array {
+ const args = [command, "-bd"]
+ if (debug7z.enabled) {
+ args.push("-bb3")
+ }
+ else if (!debug.enabled) {
+ args.push("-bb0")
+ }
+ return args
}
\ No newline at end of file
diff --git a/src/winPackager.ts b/src/winPackager.ts
index 5e0070b950e..dbbcc200d19 100644
--- a/src/winPackager.ts
+++ b/src/winPackager.ts
@@ -1,6 +1,6 @@
import { downloadCertificate } from "./codeSign"
import { Promise as BluebirdPromise } from "bluebird"
-import { PlatformPackager, BuildInfo } from "./platformPackager"
+import { PlatformPackager, BuildInfo, smarten, archSuffix } from "./platformPackager"
import { Platform, WinBuildOptions } from "./metadata"
import * as path from "path"
import { log, statOrNull, warn } from "./util"
@@ -45,6 +45,10 @@ export class WinPackager extends PlatformPackager {
return Platform.WINDOWS
}
+ protected get supportedTargets(): Array {
+ return []
+ }
+
private async getValidIconPath(): Promise {
const iconPath = path.join(this.buildResourcesDir, "icon.ico")
await checkIcon(iconPath)
@@ -137,7 +141,7 @@ export class WinPackager extends PlatformPackager {
appDirectory: appOutDir,
outputDirectory: installerOutDir,
version: this.metadata.version,
- description: this.metadata.description,
+ description: smarten(this.metadata.description),
authors: this.metadata.author.name,
iconUrl: iconUrl,
setupIcon: await this.iconPath,
@@ -235,7 +239,7 @@ function isIco(buffer: Buffer): boolean {
}
export function computeDistOut(outDir: string, arch: string): string {
- return path.join(outDir, `win${arch === "x64" ? "" : `-${arch}` }`)
+ return path.join(outDir, `win${archSuffix(arch)}`)
}
function checkConflictingOptions(options: any) {
diff --git a/test/fixtures/test-app-one/index.js b/test/fixtures/test-app-one/index.js
index 11a4e82d04c..dd2fbe001c6 100644
--- a/test/fixtures/test-app-one/index.js
+++ b/test/fixtures/test-app-one/index.js
@@ -1,6 +1,6 @@
'use strict'
-const app = require('app')
+const app = require('electron').app
// this should be placed at top of main.js to handle setup events quickly
if (handleSquirrelEvent()) {
diff --git a/test/install-linux-dependencies.sh b/test/install-linux-dependencies.sh
index 2544d643b20..98c0f1cd2e4 100755
--- a/test/install-linux-dependencies.sh
+++ b/test/install-linux-dependencies.sh
@@ -3,4 +3,4 @@ wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key ad
sudo dpkg --add-architecture i386
sudo add-apt-repository ppa:ubuntu-wine/ppa -y
sudo apt-get update
-sudo apt-get install wine1.8 ca-certificates-mono -y
\ No newline at end of file
+sudo apt-get install --no-install-recommends wine1.8 ca-certificates-mono -y
\ No newline at end of file
diff --git a/test/src/helpers/avaEx.ts b/test/src/helpers/avaEx.ts
index b3e88200e48..e5caa3b3166 100644
--- a/test/src/helpers/avaEx.ts
+++ b/test/src/helpers/avaEx.ts
@@ -7,6 +7,8 @@ declare module "ava-tf" {
export const ifNotCi: typeof test;
export const ifNotCiOsx: typeof test;
export const ifDevOrWinCi: typeof test;
+ export const ifWinCi: typeof test;
+ export const ifDevOrLinuxCi: typeof test;
export const ifNotTravis: typeof test;
}
@@ -45,6 +47,16 @@ Object.defineProperties(test, {
get: function () {
return !process.env.CI || process.platform === "win32" ? this : this.skip
}
+ },
+ "ifDevOrLinuxCi": {
+ get: function () {
+ return !process.env.CI || process.platform === "linux" ? this : this.skip
+ }
+ },
+ "ifWinCi": {
+ get: function () {
+ return process.env.CI && process.platform === "win32" ? this : this.skip
+ }
}
})
diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts
index 1b35ca82341..30830490734 100755
--- a/test/src/helpers/packTester.ts
+++ b/test/src/helpers/packTester.ts
@@ -106,7 +106,7 @@ async function packAndCheck(projectDir: string, packagerOptions: PackagerOptions
await checkOsXResult(packager, packagerOptions, checkOptions, artifacts.get(Platform.OSX))
}
else if (platform === Platform.LINUX) {
- await checkLinuxResult(projectDir, packager, packagerOptions, checkOptions)
+ await checkLinuxResult(projectDir, packager, packagerOptions, checkOptions, artifacts.get(Platform.LINUX))
}
else if (platform === Platform.WINDOWS) {
await checkWindowsResult(packager, packagerOptions, checkOptions, artifacts.get(Platform.WINDOWS))
@@ -114,7 +114,24 @@ async function packAndCheck(projectDir: string, packagerOptions: PackagerOptions
}
}
-async function checkLinuxResult(projectDir: string, packager: Packager, packagerOptions: PackagerOptions, checkOptions: AssertPackOptions) {
+async function checkLinuxResult(projectDir: string, packager: Packager, packagerOptions: PackagerOptions, checkOptions: AssertPackOptions, artifacts: Array) {
+ const customBuildOptions = packager.devMetadata.build.linux
+ const targets = customBuildOptions == null || customBuildOptions.target == null ? ["default"] : customBuildOptions.target
+
+ function getExpected(): Array {
+ const result: Array = []
+ for (let target of targets) {
+ result.push(`TestApp-1.1.0.${target === "default" ? "deb" : target}`)
+ }
+ return result
+ }
+
+ assertThat(getFileNames(artifacts)).deepEqual((checkOptions == null || checkOptions.expectedArtifacts == null ? getExpected() : checkOptions.expectedArtifacts.slice()).sort())
+
+ if (!targets.includes("deb") || !targets.includes("default")) {
+ return
+ }
+
const productName = getProductName(packager.metadata, packager.devMetadata)
const expectedContents = expectedLinuxContents.map(it => {
if (it === "/opt/TestApp/TestApp") {
@@ -143,7 +160,7 @@ async function checkLinuxResult(projectDir: string, packager: Packager, packager
Maintainer: "Foo Bar ",
Vendor: "Foo Bar ",
Package: "testapp",
- Description: " \n Test Application (test quite \" #378)",
+ Description: " \n Test Application (test quite “ #378)",
Depends: checkOptions == null || checkOptions.expectedDepends == null ? "libappindicator1, libnotify-bin" : checkOptions.expectedDepends,
})
}
@@ -200,31 +217,36 @@ async function checkOsXResult(packager: Packager, packagerOptions: PackagerOptio
}
}
+function getFileNames(list: Array): Array {
+ return list.map(it => path.basename(it.file)).sort()
+}
+
async function checkWindowsResult(packager: Packager, packagerOptions: PackagerOptions, checkOptions: AssertPackOptions, artifacts: Array) {
const productName = getProductName(packager.metadata, packager.devMetadata)
- function getWinExpected(archSuffix: string) {
- return [
+ function getExpectedFileNames(archSuffix: string) {
+ const result = [
`RELEASES`,
`${productName} Setup 1.1.0${archSuffix}.exe`,
- `TestApp-1.1.0${archSuffix}-full.nupkg`,
+ `TestApp-1.1.0-full.nupkg`,
]
+ const buildOptions = packager.devMetadata.build.win
+ if (buildOptions != null && buildOptions.remoteReleases != null) {
+ result.push(`${productName}-1.1.0-delta.nupkg`)
+ }
+ return result
}
- const archSuffix = (packagerOptions.arch || process.arch) === "x64" ? "" : "-ia32"
- const expected = checkOptions == null || checkOptions.expectedArtifacts == null ? (archSuffix == "" ? getWinExpected(archSuffix) : getWinExpected(archSuffix).concat(getWinExpected(""))) : checkOptions.expectedArtifacts
- const filenames = artifacts.map(it => path.basename(it.file))
- assertThat(filenames.slice().sort()).deepEqual(expected.slice().sort())
+ const archSuffix = (packagerOptions.arch || process.arch) === "x64" ? "" : "-ia32"
+ assertThat(getFileNames(artifacts)).deepEqual((checkOptions == null || checkOptions.expectedArtifacts == null ? getExpectedFileNames(archSuffix) : checkOptions.expectedArtifacts.slice()).sort())
if (checkOptions != null && checkOptions.expectedArtifacts != null) {
return
}
- const expectedArtifactNames = expected.slice()
- expectedArtifactNames[1] = `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`)
+ const packageFile = path.join(path.dirname(artifacts[0].file), `TestApp-1.1.0-full.nupkg`)
const unZipper = new DecompressZip(packageFile)
const fileDescriptors = await unZipper.getFiles()
@@ -257,7 +279,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 quite \" #378)
+ Test Application (test quite “ #378)
Copyright © ${new Date().getFullYear()} Foo Bar
http://foo.example.com
diff --git a/test/src/linuxPackagerTest.ts b/test/src/linuxPackagerTest.ts
index 1d853c86880..5cae9a796f6 100755
--- a/test/src/linuxPackagerTest.ts
+++ b/test/src/linuxPackagerTest.ts
@@ -7,7 +7,42 @@ import { Platform } from "out"
//noinspection JSUnusedLocalSymbols
const __awaiter = require("out/awaiter")
-test.ifNotWindows("linux", () => assertPack("test-app-one", platform(Platform.LINUX)))
+test.ifNotWindows("deb", () => assertPack("test-app-one", platform(Platform.LINUX)))
+
+test.ifDevOrLinuxCi("rpm", () => assertPack("test-app-one", {
+ platform: [Platform.LINUX],
+ devMetadata: {
+ build: {
+ linux: {
+ target: ["rpm"]
+ }
+ }
+ }
+}))
+
+test.ifDevOrLinuxCi("targets", () => assertPack("test-app-one", {
+ platform: [Platform.LINUX],
+ devMetadata: {
+ build: {
+ linux: {
+ // "apk" is very slow, don't test for now
+ target: ["sh", "freebsd", "pacman", "zip", "7z"],
+ }
+ }
+ }
+}))
+
+test.ifDevOrLinuxCi("tar", () => assertPack("test-app-one", {
+ platform: [Platform.LINUX],
+ devMetadata: {
+ build: {
+ linux: {
+ // "apk" is very slow, don't test for now
+ target: ["tar.gz", "tar.xz", "tar.7z", "tar.bz2"],
+ }
+ }
+ }
+}))
test.ifNotWindows("icons from ICNS", () => assertPack("test-app-one", {
platform: [Platform.LINUX],
diff --git a/test/src/winPackagerTest.ts b/test/src/winPackagerTest.ts
index 307a97122ec..a6e21b7da13 100755
--- a/test/src/winPackagerTest.ts
+++ b/test/src/winPackagerTest.ts
@@ -15,17 +15,11 @@ const __awaiter = require("out/awaiter")
test.ifNotCiOsx("win", () => assertPack("test-app-one", signed({
platform: [Platform.WINDOWS],
arch: "x64",
- }),
- {
- expectedArtifacts: [
- "RELEASES",
- "TestApp Setup 1.1.0.exe",
- "TestApp-1.1.0-full.nupkg"
- ],
- }
+ })
))
-test.ifDevOrWinCi("delta", () => assertPack("test-app-one", {
+// very slow
+test.ifWinCi("delta", () => assertPack("test-app-one", {
platform: [Platform.WINDOWS],
arch: "ia32",
devMetadata: {
@@ -35,14 +29,6 @@ test.ifDevOrWinCi("delta", () => assertPack("test-app-one", {
}
}
},
- },
- {
- expectedArtifacts: [
- "RELEASES",
- "TestApp Setup 1.1.0-ia32.exe",
- "TestApp-1.1.0-delta.nupkg",
- "TestApp-1.1.0-full.nupkg"
- ],
}
))
diff --git a/test/tsconfig.json b/test/tsconfig.json
index 5ce8a55cbd4..6a1a24342fc 100755
--- a/test/tsconfig.json
+++ b/test/tsconfig.json
@@ -44,6 +44,7 @@
"../typings/main/definitions/debug/index.d.ts",
"../typings/main/definitions/source-map-support/source-map-support.d.ts",
"../typings/node.d.ts",
+ "../typings/pipe-io.d.ts",
"../typings/progress-stream.d.ts",
"../typings/read-package-json.d.ts",
"../typings/signcode.d.ts",
diff --git a/tsconfig.json b/tsconfig.json
index 92150df1651..c7216ec1b21 100755
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -49,6 +49,7 @@
"typings/main/definitions/debug/index.d.ts",
"typings/main/definitions/source-map-support/source-map-support.d.ts",
"typings/node.d.ts",
+ "typings/pipe-io.d.ts",
"typings/progress-stream.d.ts",
"typings/read-package-json.d.ts",
"typings/signcode.d.ts",
diff --git a/typings/node.d.ts b/typings/node.d.ts
index 1b24c768aa5..e0e87196746 100644
--- a/typings/node.d.ts
+++ b/typings/node.d.ts
@@ -5,17 +5,6 @@
// Definitions by: Microsoft TypeScript , DefinitelyTyped
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
-/************************************************
-* *
-* Node.js v4.x API *
-* *
-************************************************/
-
-interface Error {
- stack?: string;
-}
-
-
// compat for TypeScript 1.8
// if you use with --target es3 or --target es5 and use below definitions,
// use the lib.es6.d.ts that is bundled with TypeScript 1.8.
diff --git a/typings/pipe-io.d.ts b/typings/pipe-io.d.ts
new file mode 100644
index 00000000000..e2fc301df13
--- /dev/null
+++ b/typings/pipe-io.d.ts
@@ -0,0 +1,8 @@
+declare module "pipe-io" {
+ import ReadableStream = NodeJS.ReadableStream
+ import WritableStream = NodeJS.WritableStream
+
+ function pipe(streams: Array, callback: (error?: Error) => void): void
+
+ export = pipe
+}
\ No newline at end of file