Skip to content

Commit 8f106a0

Browse files
committed
feat(appimage): move logic to app-builder, improve linux icon resolving
Close #2570
1 parent 66fbebf commit 8f106a0

File tree

17 files changed

+220
-248
lines changed

17 files changed

+220
-248
lines changed

docker/base/Dockerfile

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,12 @@ RUN curl -L https://yarnpkg.com/latest.tar.gz | tar xvz && mv yarn-* /yarn && ln
99
# git ssh for using as docker image on CircleCI
1010
# python for node-gyp
1111
# rpm is required for FPM to build rpm package
12-
# libpng16-16 is required for libicns1_0.8.1-3.1 (on xenial)
1312
# libsecret-1-0 and libgnome-keyring-dev are required even for prebuild keytar
1413
# libgtk2.0-dev for snap desktop-gtk2 (see https://github.com/ubuntu/snapcraft-desktop-helpers/blob/master/snapcraft.yaml#L248)
1514
apt-get -qq install --no-install-recommends git qtbase5-dev bsdtar build-essential autoconf libssl-dev gcc-multilib g++-multilib lzip rpm python libcurl3 git git-lfs ssh unzip \
16-
libpng16-16 icnsutils libopenjp2-7 \
1715
libsecret-1-0 libgnome-keyring-dev \
16+
libopenjp2-tools \
1817
libgtk2.0-dev && \
19-
# libicns
20-
curl -O http://mirrors.kernel.org/ubuntu/pool/universe/libi/libicns/libicns1_0.8.1-3.1_amd64.deb && dpkg --install libicns1_0.8.1-3.1_amd64.deb && unlink libicns1_0.8.1-3.1_amd64.deb && \
2118
# git-lfs
2219
git lfs install && \
2320
# snap

docs/multi-platform-build.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ You can use [Docker](#docker) to avoid installing system dependencies.
4545

4646
To build app in distributable format for Linux:
4747
```
48-
sudo apt-get install --no-install-recommends -y icnsutils
48+
sudo apt-get install --no-install-recommends -y libopenjp2-tools
4949
```
5050

5151
To build rpm: `sudo apt-get install --no-install-recommends -y rpm`.
@@ -68,7 +68,7 @@ sudo apt-get install --no-install-recommends -y gcc-multilib g++-multilib
6868
```
6969

7070
## Travis Linux
71-
[Trusty](https://docs.travis-ci.com/user/trusty-ci-environment/) is required — default Travis Linux dist is outdated and `icnsutils` version is non-functional.
71+
[Trusty](https://docs.travis-ci.com/user/trusty-ci-environment/) is required.
7272
```yaml
7373
sudo: required
7474
dist: trusty

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@
2929
"////": "All typings are added into root `package.json` to avoid duplication errors in the IDE compiler (several `node.d.ts` files).",
3030
"dependencies": {
3131
"7zip-bin": "~3.1.0",
32-
"app-builder-bin": "1.2.1",
32+
"app-builder-bin": "1.3.1",
3333
"archiver": "^2.1.1",
3434
"async-exit-hook": "^2.0.1",
35-
"aws-sdk": "^2.191.0",
35+
"aws-sdk": "^2.192.0",
3636
"bluebird-lst": "^1.0.5",
3737
"chalk": "^2.3.0",
3838
"chromium-pickle-js": "^0.2.0",
@@ -95,8 +95,8 @@
9595
"gitbook-plugin-github": "^2.0.0",
9696
"gitbook-plugin-github-buttons": "^3.0.0",
9797
"globby": "^7.1.1",
98-
"jest-cli": "^22.2.1",
99-
"jest-junit": "^3.5.0",
98+
"jest-cli": "^22.2.2",
99+
"jest-junit": "^3.6.0",
100100
"jsdoc-to-markdown": "^4.0.1",
101101
"path-sort": "^0.1.0",
102102
"ts-babel": "^4.1.8",

packages/builder-util/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"out"
1212
],
1313
"dependencies": {
14-
"app-builder-bin": "1.2.1",
14+
"app-builder-bin": "1.3.1",
1515
"temp-file": "^3.1.1",
1616
"fs-extra-p": "^4.5.0",
1717
"is-ci": "^1.1.0",

packages/builder-util/src/fs.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import isCi from "is-ci"
44
import * as path from "path"
55
import Mode from "stat-mode"
66
import { log } from "./log"
7-
import { exec } from "./util"
87
import { orNullIfFileNotExist } from "./promise"
98

109
export const MAX_FILE_REQUESTS = 8
@@ -291,20 +290,6 @@ export function copyDir(src: string, destination: string, options: CopyDirOption
291290
.then(() => BluebirdPromise.map(links, it => symlink(it.link, it.file), CONCURRENCY))
292291
}
293292

294-
// https://unix.stackexchange.com/questions/202430/how-to-copy-a-directory-recursively-using-hardlinks-for-each-file
295-
export function copyDirUsingHardLinks(source: string, destination: string) {
296-
const promise = ensureDir(destination)
297-
if (process.platform !== "darwin") {
298-
return promise
299-
.then(() => exec("cp", ["-d", "--recursive", "--preserve=mode", "--link", "-T" /* to merge */, source + "/", destination + "/"]))
300-
}
301-
302-
return promise
303-
.then(() => exec("pax", ["-rwl", "-p", "amp" /* Do not preserve file access times, Do not preserve file modification times, Preserve the file mode bits */, ".", destination], {
304-
cwd: source,
305-
}))
306-
}
307-
308293
export const DO_NOT_USE_HARD_LINKS = (file: string) => false
309294
export const USE_HARD_LINKS = (file: string) => true
310295

packages/electron-builder-lib/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"homepage": "https://github.com/electron-userland/electron-builder",
4343
"dependencies": {
4444
"7zip-bin": "~3.1.0",
45-
"app-builder-bin": "1.2.1",
45+
"app-builder-bin": "1.3.1",
4646
"async-exit-hook": "^2.0.1",
4747
"bluebird-lst": "^1.0.5",
4848
"chromium-pickle-js": "^0.2.0",

packages/electron-builder-lib/src/platformPackager.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,8 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
560560
protected async getOrConvertIcon(format: IconFormat): Promise<string | null> {
561561
const iconPath = this.platformSpecificBuildOptions.icon || this.config.icon
562562
if (iconPath != null) {
563-
return (await this.resolveIcon([iconPath], format))[0].file
563+
const iconInfos = await this.resolveIcon([iconPath], format)
564+
return (iconInfos)[0].file
564565
}
565566

566567
const resourceList = await this.resourceList
@@ -581,7 +582,11 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
581582

582583
// convert if need, validate size (it is a reason why tool is called even if file has target extension (already specified as foo.icns for example))
583584
async resolveIcon(sources: Array<string>, outputFormat: IconFormat): Promise<Array<IconInfo>> {
584-
const arg = ["icon", "--format", outputFormat, "--root", this.buildResourcesDir, "--root", this.projectDir]
585+
const arg = [
586+
"icon",
587+
"--format", outputFormat,
588+
"--root", this.buildResourcesDir, "--root", this.projectDir,
589+
]
585590
for (const source of sources) {
586591
arg.push("--input", source)
587592
}
@@ -593,7 +598,6 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
593598
// command creates temp dir amd cannot delete it automatically since result files located in and it is our responsibility remove it after use,
594599
// so, we just set TMPDIR to tempDirManager.rootTempDir and tempDirManager in any case will delete rootTempDir on exit
595600
TMPDIR: await this.info.tempDirManager.rootTempDir,
596-
DEBUG: log.isDebugEnabled ? "true" : "false",
597601
},
598602
})
599603

packages/electron-builder-lib/src/targets/AppImageTarget.ts

Lines changed: 16 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1+
import { path7za } from "7zip-bin"
2+
import { appBuilderPath } from "app-builder-bin"
13
import BluebirdPromise from "bluebird-lst"
2-
import { Arch, debug, exec, serializeToYaml } from "builder-util"
4+
import { Arch, log, serializeToYaml, isEnvTrue, spawn } from "builder-util"
35
import { UUID } from "builder-util-runtime"
4-
import { copyDir, copyOrLinkFile, unlinkIfExists, copyDirUsingHardLinks, USE_HARD_LINKS } from "builder-util/out/fs"
6+
import { copyOrLinkFile, unlinkIfExists } from "builder-util/out/fs"
57
import * as ejs from "ejs"
6-
import { ensureDir, readFile, symlink, writeFile } from "fs-extra-p"
8+
import { ensureDir, outputFile, readFile, symlink, writeFile } from "fs-extra-p"
79
import { Lazy } from "lazy-val"
810
import * as path from "path"
911
import { AppImageOptions } from ".."
1012
import { Target } from "../core"
11-
import { LinuxPackager, toAppImageOrSnapArch } from "../linuxPackager"
13+
import { LinuxPackager } from "../linuxPackager"
1214
import { getAppUpdatePublishConfiguration } from "../publish/PublishManager"
1315
import { getTemplatePath } from "../util/pathManager"
1416
import { appendBlockmap } from "./differentialUpdateInfoBuilder"
1517
import { LinuxTargetHelper } from "./LinuxTargetHelper"
1618
import { createStageDir } from "./targetUtil"
17-
import { getAppImage } from "./tools"
1819

1920
const appRunTemplate = new Lazy<(data: any) => string>(async () => {
2021
return ejs.compile(await readFile(path.join(getTemplatePath("linux"), "AppRun.sh"), "utf-8"))
@@ -46,14 +47,11 @@ export default class AppImageTarget extends Target {
4647

4748
// pax doesn't like dir with leading dot (e.g. `.__appimage`)
4849
const stageDir = await createStageDir(this, packager, arch)
49-
const appInStageDir = stageDir.getTempFile("app")
50-
await copyDirUsingHardLinks(appOutDir, appInStageDir)
51-
5250
const resourceName = `appimagekit-${this.packager.executableName}`
5351
const installIcons = await this.copyIcons(stageDir.dir, resourceName)
5452

5553
const finalDesktopFilename = `${this.packager.executableName}.desktop`
56-
await BluebirdPromise.all([
54+
await Promise.all([
5755
unlinkIfExists(artifactPath),
5856
writeFile(stageDir.getTempFile("/AppRun"), (await appRunTemplate.value)({
5957
systemIntegration: this.options.systemIntegration || "ask",
@@ -72,49 +70,29 @@ export default class AppImageTarget extends Target {
7270
throw new Error("Icon is not provided")
7371
}
7472

75-
//noinspection SpellCheckingInspection
76-
const vendorDir = await getAppImage()
77-
7873
if (this.packager.packagerOptions.effectiveOptionComputed != null && await this.packager.packagerOptions.effectiveOptionComputed({desktop: await this.desktopEntry.value})) {
7974
return
8075
}
8176

82-
if (arch === Arch.x64 || arch === Arch.ia32) {
83-
await copyDir(path.join(vendorDir, "lib", arch === Arch.x64 ? "x86_64-linux-gnu" : "i386-linux-gnu"), stageDir.getTempFile("usr/lib"), {
84-
isUseHardLink: USE_HARD_LINKS,
85-
})
86-
}
87-
8877
const publishConfig = await getAppUpdatePublishConfiguration(packager, arch)
8978
if (publishConfig != null) {
90-
await writeFile(path.join(packager.getResourcesDir(appInStageDir), "app-update.yml"), serializeToYaml(publishConfig))
79+
await outputFile(path.join(packager.getResourcesDir(stageDir.getTempFile("app")), "app-update.yml"), serializeToYaml(publishConfig))
9180
}
9281

93-
const vendorToolDir = path.join(vendorDir, process.platform === "darwin" ? "darwin" : `linux-${process.arch}`)
94-
// default gzip compression - 51.9, xz - 50.4 difference is negligible, start time - well, it seems, a little bit longer (but on Parallels VM on external SSD disk)
95-
// so, to be decided later, is it worth to use xz by default
96-
const args = [
97-
"--runtime-file", path.join(vendorDir, `runtime-${(archToRuntimeName(arch))}`),
98-
"--no-appstream",
99-
]
100-
if (debug.enabled) {
101-
args.push("--verbose")
102-
}
82+
const args = ["appimage", "--stage", stageDir.dir, "--arch", Arch[arch], "--output", artifactPath, "--app", appOutDir]
10383
if (packager.compression === "maximum") {
104-
args.push("--comp", "xz")
84+
args.push("--compression", "xz")
85+
}
86+
if (log.isDebugEnabled && !isEnvTrue(process.env.ELECTRON_BUILDER_REMOVE_STAGE_EVEN_IF_DEBUG)) {
87+
args.push("--no-remove-stage")
10588
}
106-
args.push(stageDir.dir, artifactPath)
107-
await exec(path.join(vendorToolDir, "appimagetool"), args, {
89+
await spawn(appBuilderPath, args, {
10890
env: {
10991
...process.env,
110-
PATH: `${vendorToolDir}:${process.env.PATH}`,
111-
// to avoid detection by appimagetool (see extract_arch_from_text about expected arch names)
112-
ARCH: toAppImageOrSnapArch(arch),
113-
}
92+
SZA_PATH: path7za,
93+
},
11494
})
11595

116-
await stageDir.cleanup()
117-
11896
const updateInfo = await appendBlockmap(artifactPath)
11997
packager.info.dispatchArtifactCreated({
12098
file: artifactPath,
@@ -154,23 +132,4 @@ export default class AppImageTarget extends Target {
154132
}
155133
return installIcons
156134
}
157-
}
158-
159-
function archToRuntimeName(arch: Arch) {
160-
switch (arch) {
161-
case Arch.armv7l:
162-
return "armv7"
163-
164-
case Arch.arm64:
165-
return "arm64"
166-
167-
case Arch.ia32:
168-
return "i686"
169-
170-
case Arch.x64:
171-
return "x86_64"
172-
173-
default:
174-
throw new Error(`AppImage for arch ${Arch[arch]} not supported`)
175-
}
176135
}

packages/electron-builder-lib/src/targets/LinuxTargetHelper.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,23 @@ export class LinuxTargetHelper {
2626
const packager = this.packager
2727
const iconDir = packager.platformSpecificBuildOptions.icon
2828
const sources = [iconDir == null ? "icons" : iconDir]
29-
const icnsPath = this.getIcns()
29+
30+
const commonConfiguration = packager.config
31+
let icnsPath = (commonConfiguration.mac || {}).icon || commonConfiguration.icon
3032
if (icnsPath != null) {
33+
if (!icnsPath.endsWith(".icns")) {
34+
icnsPath += ".icns"
35+
}
3136
sources.push(icnsPath)
3237
}
38+
3339
sources.push(path.join(getTemplatePath("linux"), "electron-icons"))
3440

3541
const result = await packager.resolveIcon(sources, "set")
3642
this.maxIconPath = result[result.length - 1].file
3743
return result
3844
}
3945

40-
private getIcns(): string | null {
41-
const build = this.packager.info.config
42-
let iconPath = (build.mac || {}).icon || build.icon
43-
if (iconPath != null && !iconPath.endsWith(".icns")) {
44-
iconPath += ".icns"
45-
}
46-
return iconPath == null ? null : path.resolve(this.packager.projectDir, iconPath)
47-
}
48-
4946
getDescription(options: LinuxTargetSpecificOptions) {
5047
return options.description || this.packager.appInfo.description
5148
}

packages/electron-builder-lib/src/targets/tools.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@ export function getLinuxToolsPath() {
1212
return getBinFromGithub("linux-tools", "mac-10.12.3", "SQ8fqIRVXuQVWnVgaMTDWyf2TLAJjJYw3tRSqQJECmgF6qdM7Kogfa6KD49RbGzzMYIFca9Uw3MdsxzOPRWcYw==")
1313
}
1414

15-
export function getAppImage() {
16-
//noinspection SpellCheckingInspection
17-
return getBinFromGithub("appimage", "9.0.5", "AZbiBSeyow/pKCzeyIwVtogIUSWD4GxAxkqwL9GQcL1vq+EhcNPeMKOdlSI045SU4pknU4ceLwO5tzV7o0tNOw==")
18-
}
19-
2015
export const fpmPath = new Lazy(() => {
2116
if (process.platform === "win32" || process.env.USE_SYSTEM_FPM === "true") {
2217
return Promise.resolve("fpm")
@@ -40,7 +35,6 @@ export const fpmPath = new Lazy(() => {
4035
export function prefetchBuildTools(): Promise<any> {
4136
// yes, we starting to use native Promise
4237
return Promise.all([
43-
getAppImage(),
4438
fpmPath.value,
4539
getBinFromGithub("snap-template", SNAP_TEMPLATE_VERSION, SNAP_TEMPLATE_SHA512),
4640
])

0 commit comments

Comments
 (0)