Skip to content

Commit 138b229

Browse files
committed
feat(snap): build snap on macOS
1 parent 2a2d832 commit 138b229

File tree

13 files changed

+96
-156
lines changed

13 files changed

+96
-156
lines changed

docker/9/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM electronuserland/builder:base
22

3-
ENV NODE_VERSION 9.4.0
3+
ENV NODE_VERSION 9.5.0
44

55
# this package is used for snapcraft and we should not clear apt list - to avoid apt-get update during snap build
66
RUN apt-get -qq update && \

docker/base/Dockerfile

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,27 @@ RUN curl -L https://yarnpkg.com/latest.tar.gz | tar xvz && mv yarn-* /yarn && ln
1010
# python for node-gyp
1111
# rpm is required for FPM to build rpm package
1212
# libpng16-16 is required for libicns1_0.8.1-3.1 (on xenial)
13-
# TODO remove graphicsmagick (3 months after electron-builder 19.53.0)
14-
# libsecret-1-0 is required even for prebuild keytar
13+
# libsecret-1-0 and libgnome-keyring-dev are required even for prebuild keytar
1514
# libgtk2.0-dev for snap desktop-gtk2 (see https://github.com/ubuntu/snapcraft-desktop-helpers/blob/master/snapcraft.yaml#L248)
16-
apt-get install --no-install-recommends -y libsecret-1-0 git snapcraft qtbase5-dev bsdtar build-essential autoconf libssl-dev icnsutils libopenjp2-7 graphicsmagick gcc-multilib g++-multilib libgnome-keyring-dev lzip rpm python libcurl3 git git-lfs ssh libpng16-16 unzip libgtk2.0-dev && \
15+
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 \
17+
libsecret-1-0 libgnome-keyring-dev \
18+
libgtk2.0-dev && \
1719
# libicns
1820
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 && \
21+
# git-lfs
1922
git lfs install && \
20-
# clean
23+
# snap
24+
apt-get -qq install --no-install-recommends jq squashfs-tools && \
25+
curl -L $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/core' | jq '.download_url' -r) --output core.snap && \
26+
mkdir -p /snap/core && unsquashfs -d /snap/core/current core.snap && rm core.snap && \
27+
curl -L $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/snapcraft?channel=edge' | jq '.download_url' -r) --output snapcraft.snap && \
28+
mkdir -p /snap/snapcraft && unsquashfs -d /snap/snapcraft/current snapcraft.snap && rm snapcraft.snap && \
29+
mkdir -p /snap/bin && \
30+
echo "#!/bin/sh" > /snap/bin/snapcraft && \
31+
echo 'exec $SNAP/usr/bin/python3 $SNAP/bin/snapcraft "$@"' >> /snap/bin/snapcraft && \
32+
chmod a+x /snap/bin/snapcraft && \
33+
apt-get -qq purge jq squashfs-tools && \
2134
rm -rf /var/lib/apt/lists/*
2235

2336
COPY test.sh /test.sh
@@ -34,4 +47,10 @@ ENV LC_ALL C.UTF-8
3447
ENV USE_UNZIP true
3548

3649
ENV DEBUG_COLORS true
37-
ENV FORCE_COLOR true
50+
ENV FORCE_COLOR true
51+
52+
ENV SNAP=/snap/snapcraft/current
53+
ENV SNAP_ARCH=amd64
54+
ENV SNAP_NAME=snapcraft
55+
ENV SNAP_VERSION=edge
56+
ENV PATH=/snap/bin:$PATH

docker/build.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#!/usr/bin/env bash
22
set -e
33

4-
docker build -t electronuserland/builder:base docker/base
4+
docker build -t electronuserland/builder:base -t electronuserland/builder:base-03.18 docker/base
55

6-
docker build -t electronuserland/builder:9 -t electronuserland/builder:latest docker/9
6+
docker build -t electronuserland/builder:9 -t electronuserland/builder:latest -t electronuserland/builder:9-03.18 docker/9
77

88
docker build -t electronuserland/builder:wine docker/wine
99
docker build -t electronuserland/builder:wine-mono docker/wine-mono

docker/wine/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ FROM electronuserland/builder:latest
22

33
RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && dpkg --add-architecture i386 && curl -L https://dl.winehq.org/wine-builds/Release.key > Release.key && apt-key add Release.key && apt-add-repository https://dl.winehq.org/wine-builds/ubuntu && \
44
apt-get update && \
5-
apt-get -y remove software-properties-common libdbus-glib-1-2 python3-dbus python3-gi python3-pycurl python3-software-properties && \
5+
apt-get -y purge software-properties-common libdbus-glib-1-2 python3-dbus python3-gi python3-pycurl python3-software-properties && \
66
apt-get install -y --no-install-recommends winehq-stable && \
77
# clean
88
apt-get clean && rm -rf /var/lib/apt/lists/* && unlink Release.key

packages/builder-util/src/binDownload.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ function doGetBin(name: string, url: string, checksum: string): Promise<string>
4040
...process.env,
4141
SZA_PATH: path7za,
4242
},
43-
stdio: ["ignore", "pipe", "inherit"]
43+
stdio: ["ignore", "pipe", process.stdout]
4444
})
4545
}

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

Lines changed: 38 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { isEnvTrue, Arch, exec, replaceDefault as _replaceDefault, serializeToYaml, spawn, toLinuxArchString } from "builder-util"
2-
import { copyFile, copyDir, copyDirUsingHardLinks, USE_HARD_LINKS } from "builder-util/out/fs"
1+
import { path7za } from "7zip-bin"
2+
import { appBuilderPath } from "app-builder-bin"
3+
import { isEnvTrue, Arch, replaceDefault as _replaceDefault, serializeToYaml, spawn, toLinuxArchString, log } from "builder-util"
34
import { outputFile } from "fs-extra-p"
45
import * as path from "path"
56
import { SnapOptions } from ".."
@@ -8,23 +9,8 @@ import { Target } from "../core"
89
import { LinuxPackager, toAppImageOrSnapArch } from "../linuxPackager"
910
import { PlugDescriptor } from "../options/SnapOptions"
1011
import { LinuxTargetHelper } from "./LinuxTargetHelper"
11-
import { createStageDir, StageDir } from "./targetUtil"
12-
import BluebirdPromise from "bluebird-lst"
13-
import { getSnapTemplate } from "./tools"
14-
15-
// usr/share/fonts is required, cannot run otherwise
16-
const unnecessaryFiles = [
17-
"usr/share/doc",
18-
"usr/share/man",
19-
"usr/share/icons",
20-
"usr/share/bash-completion",
21-
"usr/share/lintian",
22-
"usr/share/dh-python",
23-
"usr/share/python3",
24-
25-
"usr/lib/python*",
26-
"usr/bin/python*",
27-
]
12+
import { createStageDir } from "./targetUtil"
13+
import { SNAP_TEMPLATE_SHA512, SNAP_TEMPLATE_VERSION } from "./tools"
2814

2915
// libxss1, libasound2, gconf2 - was "error while loading shared libraries: libXss.so.1" on Xubuntu 16.04
3016
const defaultStagePackages = ["libasound2", "libgconf2-4", "libnotify4", "libnspr4", "libnss3", "libpcre3", "libpulse0", "libxss1", "libxtst6"]
@@ -47,7 +33,7 @@ export default class SnapTarget extends Target {
4733
return result
4834
}
4935

50-
private createDescriptor(snapName: string, appOutDir: string, arch: Arch, isUseDocker: boolean): any {
36+
private createDescriptor(snapName: string, arch: Arch): any {
5137
const appInfo = this.packager.appInfo
5238
const options = this.options
5339
const linuxArchName = toAppImageOrSnapArch(arch)
@@ -64,7 +50,7 @@ export default class SnapTarget extends Target {
6450
grade: options.grade || "stable",
6551
apps: {
6652
[snapName]: {
67-
command: `bin/desktop-launch $SNAP/${this.packager.executableName}`,
53+
command: `bin/desktop-launch $SNAP/app/${this.packager.executableName}`,
6854
adapter: "none",
6955
environment: {
7056
TMPDIR: "$XDG_RUNTIME_DIR",
@@ -84,9 +70,8 @@ export default class SnapTarget extends Target {
8470
},
8571
parts: {
8672
app: {
87-
plugin: "dump",
73+
plugin: "nil",
8874
"stage-packages": this.replaceDefault(options.stagePackages, defaultStagePackages),
89-
source: isUseDocker ? "/appOutDir" : appOutDir,
9075
after: this.replaceDefault(options.after, ["desktop-gtk2"]),
9176
}
9277
},
@@ -117,36 +102,36 @@ export default class SnapTarget extends Target {
117102
const options = this.options
118103
const snapName = packager.executableName.toLowerCase()
119104
const buildPackages = asArray(options.buildPackages)
120-
const isUseDocker = process.platform !== "linux" || isEnvTrue(process.env.SNAP_USE_DOCKER)
121105
this.isUseTemplateApp = this.options.useTemplateApp !== false && arch === Arch.x64 && buildPackages.length === 0
122106

123107
const snapFileName = `${snapName}_${packager.appInfo.version}_${toLinuxArchString(arch)}.snap`
124108
const artifactPath = path.join(this.outDir, snapFileName)
125109
this.logBuilding("snap", artifactPath, arch)
126110

127-
const snap: any = this.createDescriptor(snapName, appOutDir, arch, isUseDocker)
111+
const snap: any = this.createDescriptor(snapName, arch)
128112
if (this.isUseTemplateApp) {
129113
delete snap.parts
130114
}
131115

132116
const stageDir = await createStageDir(this, packager, arch)
133117
// snapcraft.yaml inside a snap directory
134-
const snapDir = path.join(stageDir.dir, "snap")
135-
const snapMetaDir = this.isUseTemplateApp ? path.join(stageDir.dir, "meta") : snapDir
118+
const snapMetaDir = path.join(stageDir.dir, this.isUseTemplateApp ? "meta" : "snap")
119+
120+
const args = [
121+
"snap",
122+
"--app", appOutDir,
123+
"--stage", stageDir.dir,
124+
"--arch", toLinuxArchString(arch),
125+
"--output", artifactPath,
126+
"--docker-image", "electronuserland/builder:latest"
127+
]
136128

137129
await this.helper.icons
138130
if (this.helper.maxIconPath != null) {
139131
if (!this.isUseTemplateApp) {
140132
snap.icon = "snap/gui/icon.png"
141133
}
142-
await copyFile(this.helper.maxIconPath, path.join(snapMetaDir, "gui", "icon.png"))
143-
}
144-
145-
const hooksDir = await packager.getResource(options.hooks, "snap-hooks")
146-
if (hooksDir != null) {
147-
await copyDir(hooksDir, path.join(snapMetaDir, "hooks"), {
148-
isUseHardLink: USE_HARD_LINKS,
149-
})
134+
args.push("--icon", this.helper.maxIconPath)
150135
}
151136

152137
const desktopFile = path.join(snapMetaDir, "gui", `${snap.name}.desktop`)
@@ -159,99 +144,33 @@ export default class SnapTarget extends Target {
159144
return
160145
}
161146

162-
const snapcraftFile = path.join(snapMetaDir, this.isUseTemplateApp ? "snap.yaml" : "snapcraft.yaml")
163-
await outputFile(snapcraftFile, serializeToYaml(snap))
164-
if (this.isUseTemplateApp) {
165-
await copyDir(await getSnapTemplate(), stageDir.dir, {
166-
isUseHardLink: USE_HARD_LINKS,
167-
})
168-
await copyDirUsingHardLinks(appOutDir, stageDir.dir)
169-
}
147+
await outputFile(path.join(snapMetaDir, this.isUseTemplateApp ? "snap.yaml" : "snapcraft.yaml"), serializeToYaml(snap))
170148

171-
if (isUseDocker) {
172-
if (this.isUseTemplateApp) {
173-
await this.buildUsingDockerAndPrepackedSnap(snapFileName, stageDir)
174-
}
175-
else {
176-
await this.buildUsingDocker(options, arch, snapFileName, stageDir, appOutDir)
177-
}
178-
}
179-
else {
180-
await this.buildWithoutDocker(buildPackages, stageDir.dir, arch, artifactPath)
149+
if (log.isDebugEnabled && !isEnvTrue(process.env.ELECTRON_BUILDER_REMOVE_STAGE_EVEN_IF_DEBUG)) {
150+
args.push("--no-remove-stage")
181151
}
182152

183-
await stageDir.cleanup()
184-
packager.dispatchArtifactCreated(artifactPath, this, arch)
185-
}
186-
187-
private async buildWithoutDocker(buildPackages: Array<string>, stageDir: string, arch: Arch, artifactPath: string) {
188-
if (buildPackages.length > 0) {
189-
const notInstalledPackages = await BluebirdPromise.filter(buildPackages, (it): Promise<boolean> => {
190-
return exec("dpkg", ["-s", it])
191-
.then(result => result.includes("is not installed"))
192-
})
193-
if (notInstalledPackages.length > 0) {
194-
await spawn("apt-get", ["-qq", "update"])
195-
await spawn("apt-get", ["-qq", "install", "--no-install-recommends"].concat(notInstalledPackages))
196-
}
197-
}
198-
const spawnOptions = {
199-
cwd: stageDir,
200-
stdio: ["ignore", "inherit", "inherit"],
153+
const hooksDir = await packager.getResource(options.hooks, "snap-hooks")
154+
if (hooksDir != null) {
155+
args.push("--hooks", hooksDir)
201156
}
202157

203-
let primeDir: string
204158
if (this.isUseTemplateApp) {
205-
primeDir = stageDir
159+
const templateDirName = `snap-template-${SNAP_TEMPLATE_VERSION}`
160+
args.push(
161+
"--template-url", `https://github.com/electron-userland/electron-builder-binaries/releases/download/${templateDirName}/${templateDirName}.7z`,
162+
"--template-sha512", SNAP_TEMPLATE_SHA512,
163+
)
206164
}
207-
else {
208-
await spawn("snapcraft", ["prime", "--target-arch", toLinuxArchString(arch)], spawnOptions)
209-
primeDir = stageDir + path.sep + "prime"
210-
await exec("/bin/bash", ["-c", `rm -rf ${unnecessaryFiles.join(" ")}`], {
211-
cwd: primeDir,
212-
})
213-
}
214-
await spawn("snapcraft", ["pack", primeDir, "--output", artifactPath], spawnOptions)
215-
}
216165

217-
private async buildUsingDockerAndPrepackedSnap(snapFileName: string, stageDir: StageDir) {
218-
await spawn("docker", ["run", "--rm",
219-
// dist dir can be outside of project dir
220-
"-v", `${this.outDir}:/out`,
221-
"-v", `${stageDir.dir}:/stage:ro`,
222-
"electronuserland/builder:latest",
223-
"/bin/bash", "-c", `snapcraft pack /stage --output /out/${snapFileName}`,
224-
], {
225-
cwd: this.packager.info.projectDir,
226-
stdio: ["ignore", "inherit", "inherit"],
227-
})
228-
}
229-
230-
private async buildUsingDocker(options: SnapOptions, arch: Arch, snapFileName: string, stageDir: StageDir, appOutDir: string) {
231-
const commands: Array<string> = []
232-
if (options.buildPackages != null && options.buildPackages.length > 0) {
233-
commands.push(`apt-get install --no-install-recommends -y ${options.buildPackages.join(" ")}`)
234-
}
235-
236-
// copy stage to linux fs to avoid performance issues (https://docs.docker.com/docker-for-mac/osxfs-caching/)
237-
commands.push("cp -R /stage /s/")
238-
commands.push("cd /s")
239-
commands.push(`snapcraft prime --target-arch ${toLinuxArchString(arch)}`)
240-
commands.push(`rm -rf ${unnecessaryFiles.map(it => `prime/${it}`).join(" ")}`)
241-
commands.push(`snapcraft pack /s/prime --output /out/${snapFileName}`)
242-
243-
await spawn("docker", ["run", "--rm",
244-
"-v", `${this.packager.info.projectDir}:/project:delegated`,
245-
// dist dir can be outside of project dir
246-
"-v", `${this.outDir}:/out`,
247-
"-v", `${stageDir.dir}:/stage:ro`,
248-
"-v", `${appOutDir}:/appOutDir:ro`,
249-
"electronuserland/builder:latest",
250-
"/bin/bash", "-c", commands.join(" && "),
251-
], {
252-
cwd: this.packager.info.projectDir,
253-
stdio: ["ignore", "inherit", "inherit"],
166+
await spawn(appBuilderPath, args, {
167+
env: {
168+
...process.env,
169+
SZA_PATH: path7za,
170+
},
171+
stdio: ["ignore", "inherit", "inherit"]
254172
})
173+
packager.dispatchArtifactCreated(artifactPath, this, arch)
255174
}
256175
}
257176

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { Lazy } from "lazy-val"
33
import * as path from "path"
44
import { Platform } from "../core"
55

6+
export const SNAP_TEMPLATE_VERSION = "0.1.1"
7+
// noinspection SpellCheckingInspection
8+
export const SNAP_TEMPLATE_SHA512 = "W8JXQMwsrqH7T8kFD3KuULNVJRqygmcQPDPGhr9BXeRQS9U+A6jSsUEopQIwfQxlhuA6f7Jerc9XA0/ZLlK60w=="
9+
610
export function getLinuxToolsPath() {
711
//noinspection SpellCheckingInspection
812
return getBinFromGithub("linux-tools", "mac-10.12.3", "SQ8fqIRVXuQVWnVgaMTDWyf2TLAJjJYw3tRSqQJECmgF6qdM7Kogfa6KD49RbGzzMYIFca9Uw3MdsxzOPRWcYw==")
@@ -35,7 +39,11 @@ export const fpmPath = new Lazy(() => {
3539
// noinspection JSUnusedGlobalSymbols
3640
export function prefetchBuildTools(): Promise<any> {
3741
// yes, we starting to use native Promise
38-
return Promise.all([getAppImage(), fpmPath.value, getSnapTemplate()])
42+
return Promise.all([
43+
getAppImage(),
44+
fpmPath.value,
45+
getBinFromGithub("snap-template", SNAP_TEMPLATE_VERSION, SNAP_TEMPLATE_SHA512),
46+
])
3947
}
4048

4149
export function getZstd() {
@@ -71,11 +79,6 @@ export function getAria() {
7179
.then(it => path.join(it, `aria2c${platform === Platform.WINDOWS ? ".exe" : ""}`))
7280
}
7381

74-
export function getSnapTemplate() {
75-
// noinspection SpellCheckingInspection
76-
return getBinFromGithub("snap-template", "0.1.1", "W8JXQMwsrqH7T8kFD3KuULNVJRqygmcQPDPGhr9BXeRQS9U+A6jSsUEopQIwfQxlhuA6f7Jerc9XA0/ZLlK60w==")
77-
}
78-
7982
export interface ToolDescriptor {
8083
name: string
8184
version: string

packages/electron-updater/src/AppImageUpdater.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import BluebirdPromise from "bluebird-lst"
21
import { AllPublishOptions, CancellationToken, DownloadOptions, newError, UpdateInfo } from "builder-util-runtime"
32
import { execFileSync, spawn } from "child_process"
43
import isDev from "electron-is-dev"
@@ -17,12 +16,17 @@ export class AppImageUpdater extends BaseUpdater {
1716

1817
checkForUpdatesAndNotify(): Promise<UpdateCheckResult | null> {
1918
if (isDev) {
20-
return BluebirdPromise.resolve(null)
19+
return Promise.resolve(null)
2120
}
2221

2322
if (process.env.APPIMAGE == null) {
24-
this._logger.warn("APPIMAGE env is not defined, current application is not an AppImage")
25-
return BluebirdPromise.resolve(null)
23+
if (process.env.SNAP == null) {
24+
this._logger.warn("APPIMAGE env is not defined, current application is not an AppImage")
25+
}
26+
else {
27+
this._logger.info("SNAP env is defined, updater is disabled")
28+
}
29+
return Promise.resolve(null)
2630
}
2731

2832
return super.checkForUpdatesAndNotify()

0 commit comments

Comments
 (0)