Skip to content

Commit 6faf828

Browse files
committed
feat(snap): hooks
1 parent fbfb8c6 commit 6faf828

File tree

6 files changed

+61
-38
lines changed

6 files changed

+61
-38
lines changed

docker/base/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ 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
1315
# libgtk2.0-dev for snap desktop-gtk2 (see https://github.com/ubuntu/snapcraft-desktop-helpers/blob/master/snapcraft.yaml#L248)
14-
apt-get install --no-install-recommends -y git snapcraft qtbase5-dev bsdtar build-essential autoconf libssl-dev icnsutils libopenjp2-7 gcc-multilib g++-multilib lzip rpm python libcurl3 git git-lfs ssh libpng16-16 unzip libgtk2.0-dev && \
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 && \
1517
# libicns
1618
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 && \
1719
git lfs install && \

packages/builder-util/src/fs.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -293,15 +293,12 @@ export function copyDir(src: string, destination: string, options: CopyDirOption
293293

294294
// https://unix.stackexchange.com/questions/202430/how-to-copy-a-directory-recursively-using-hardlinks-for-each-file
295295
export function copyDirUsingHardLinks(source: string, destination: string) {
296+
const promise = ensureDir(destination)
296297
if (process.platform !== "darwin") {
297-
const args = ["-d", "--recursive", "--preserve=mode"]
298-
args.push("--link")
299-
args.push(source + "/", destination + "/")
300-
return ensureDir(path.dirname(destination)).then(() => exec("cp", args))
298+
return promise
299+
.then(() => exec("cp", ["-d", "--recursive", "--preserve=mode", "--link", "-T" /* to merge */, source + "/", destination + "/"]))
301300
}
302301

303-
// pax requires created dir
304-
const promise = ensureDir(destination)
305302
return promise
306303
.then(() => exec("pax", ["-rwl", "-p", "amp" /* Do not preserve file access times, Do not preserve file modification times, Preserve the file mode bits */, ".", destination], {
307304
cwd: source,

packages/electron-builder-lib/src/options/SnapOptions.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ export interface SnapOptions extends CommonLinuxOptions, TargetSpecificOptions {
4343
*/
4444
readonly stagePackages?: Array<string> | null
4545

46+
/**
47+
* The [hooks](https://docs.snapcraft.io/build-snaps/hooks) directory, relative to `build` (build resources directory).
48+
* @default build/snap-hooks
49+
*/
50+
readonly hooks?: string | null
51+
4652
/**
4753
* The list of [plugs](https://snapcraft.io/docs/reference/interfaces).
4854
* Defaults to `["desktop", "desktop-legacy", "home", "x11", "unity7", "browser-support", "network", "gsettings", "pulseaudio", "opengl"]`.

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ export class LinuxTargetHelper {
5252

5353
async writeDesktopEntry(targetSpecificOptions: LinuxTargetSpecificOptions, exec?: string, destination?: string | null, extra?: { [key: string]: string; }): Promise<string> {
5454
const data = await this.computeDesktopEntry(targetSpecificOptions, exec, extra)
55-
const tempFile = destination || await this.packager.getTempFile(`${this.packager.appInfo.productFilename}.desktop`)
56-
await outputFile(tempFile, data)
57-
return tempFile
55+
const file = destination || await this.packager.getTempFile(`${this.packager.appInfo.productFilename}.desktop`)
56+
await outputFile(file, data)
57+
return file
5858
}
5959

6060
async computeDesktopEntry(targetSpecificOptions: LinuxTargetSpecificOptions, exec?: string, extra?: { [key: string]: string; }): Promise<string> {

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

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,26 +32,28 @@ const defaultPlugs = ["desktop", "desktop-legacy", "home", "x11", "unity7", "bro
3232
export default class SnapTarget extends Target {
3333
readonly options: SnapOptions = {...this.packager.platformSpecificBuildOptions, ...(this.packager.config as any)[this.name]}
3434

35+
private isUsePrepackedSnap = true
36+
3537
constructor(name: string, private readonly packager: LinuxPackager, private readonly helper: LinuxTargetHelper, readonly outDir: string) {
3638
super(name)
3739
}
3840

41+
private replaceDefault(inList: Array<string> | null | undefined, defaultList: Array<string>) {
42+
const result = _replaceDefault(inList, defaultList)
43+
if (result !== defaultList) {
44+
this.isUsePrepackedSnap = false
45+
}
46+
return result
47+
}
48+
3949
async build(appOutDir: string, arch: Arch): Promise<any> {
4050
const packager = this.packager
4151
const appInfo = packager.appInfo
4252
const options = this.options
4353
const snapName = packager.executableName.toLowerCase()
4454
const buildPackages = asArray(options.buildPackages)
4555
const isUseDocker = process.platform !== "linux" || isEnvTrue(process.env.SNAP_USE_DOCKER)
46-
let isUsePrepackedSnap = arch === Arch.x64 && buildPackages.length === 0
47-
48-
function replaceDefault(inList: Array<string> | null | undefined, defaultList: Array<string>) {
49-
const result = _replaceDefault(inList, defaultList)
50-
if (result !== defaultList) {
51-
isUsePrepackedSnap = false
52-
}
53-
return result
54-
}
56+
this.isUsePrepackedSnap = arch === Arch.x64 && buildPackages.length === 0
5557

5658
const snap: any = {
5759
name: snapName,
@@ -67,38 +69,55 @@ export default class SnapTarget extends Target {
6769
TMPDIR: "$XDG_RUNTIME_DIR",
6870
...options.environment,
6971
},
70-
plugs: replaceDefault(options.plugs, defaultPlugs),
72+
plugs: this.replaceDefault(options.plugs, defaultPlugs),
7173
}
7274
},
7375
parts: {
7476
app: {
7577
plugin: "dump",
76-
"stage-packages": replaceDefault(options.stagePackages, defaultStagePackages),
78+
"stage-packages": this.replaceDefault(options.stagePackages, defaultStagePackages),
7779
source: isUseDocker ? "/appOutDir" : appOutDir,
78-
after: replaceDefault(options.after, ["desktop-gtk2"]),
80+
after: this.replaceDefault(options.after, ["desktop-gtk2"]),
7981
}
8082
},
8183
}
8284

83-
if (options.assumes != null) {
84-
snap.assumes = asArray(options.assumes)
85+
const wrapperFileName = `command-${packager.executableName}.wrapper`
86+
if (this.isUsePrepackedSnap) {
87+
delete snap.parts
88+
snap.apps[snapName].command = wrapperFileName
8589
}
8690

8791
const snapFileName = `${snap.name}_${snap.version}_${toLinuxArchString(arch)}.snap`
8892
const artifactPath = path.join(this.outDir, snapFileName)
8993
this.logBuilding("snap", artifactPath, arch)
9094

95+
if (options.assumes != null) {
96+
snap.assumes = asArray(options.assumes)
97+
}
98+
9199
const stageDir = await createStageDir(this, packager, arch)
92100
// snapcraft.yaml inside a snap directory
93101
const snapDir = path.join(stageDir.dir, "snap")
102+
const snapMetaDir = this.isUsePrepackedSnap ? path.join(stageDir.dir, "meta") : snapDir
94103

95104
await this.helper.icons
96105
if (this.helper.maxIconPath != null) {
97-
snap.icon = "snap/gui/icon.png"
98-
await copyFile(this.helper.maxIconPath, path.join(snapDir, "gui", "icon.png"))
106+
if (!this.isUsePrepackedSnap) {
107+
snap.icon = "snap/gui/icon.png"
108+
}
109+
await copyFile(this.helper.maxIconPath, path.join(snapMetaDir, "gui", "icon.png"))
99110
}
100111

101-
const desktopFile = await this.helper.writeDesktopEntry(this.options, packager.executableName, path.join(snapDir, "gui", `${snap.name}.desktop`), {
112+
const hooksDir = await packager.getResource(options.hooks, "snap-hooks")
113+
if (hooksDir != null) {
114+
await copyDir(hooksDir, path.join(snapMetaDir, "hooks"), {
115+
isUseHardLink: USE_HARD_LINKS,
116+
})
117+
}
118+
119+
const desktopFile = path.join(snapMetaDir, "gui", `${snap.name}.desktop`)
120+
await this.helper.writeDesktopEntry(this.options, packager.executableName, desktopFile, {
102121
// tslint:disable:no-invalid-template-strings
103122
Icon: "${SNAP}/meta/gui/icon.png"
104123
})
@@ -107,43 +126,42 @@ export default class SnapTarget extends Target {
107126
return
108127
}
109128

110-
const snapcraftFile = isUsePrepackedSnap ? path.join(stageDir.dir, "meta", "snap.yaml") : path.join(snapDir, "snapcraft.yaml")
129+
const snapcraftFile = path.join(snapMetaDir, this.isUsePrepackedSnap ? "snap.yaml" : "snapcraft.yaml")
111130
await outputFile(snapcraftFile, serializeToYaml(snap))
112-
const snapTemplateDir = isUsePrepackedSnap ? await getSnapTemplate() : null
113-
if (isUsePrepackedSnap) {
131+
if (this.isUsePrepackedSnap) {
114132
// noinspection SpellCheckingInspection
115-
await writeFile(path.join(stageDir.dir, `command-${packager.executableName}`), `#!/bin/sh
133+
await writeFile(path.join(stageDir.dir, wrapperFileName), `#!/bin/sh
116134
export PATH="$SNAP/usr/sbin:$SNAP/usr/bin:$SNAP/sbin:$SNAP/bin:$PATH"
117135
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$SNAP/lib:$SNAP/usr/lib:$SNAP/lib/x86_64-linux-gnu:$SNAP/usr/lib/x86_64-linux-gnu"
118136
export LD_LIBRARY_PATH="$SNAP/usr/lib/x86_64-linux-gnu/mesa-egl:$LD_LIBRARY_PATH"
119137
export LD_LIBRARY_PATH="$SNAP/usr/lib/x86_64-linux-gnu:$SNAP/usr/lib/x86_64-linux-gnu/pulseaudio:$LD_LIBRARY_PATH"
120138
export LD_LIBRARY_PATH=$SNAP_LIBRARY_PATH:$LD_LIBRARY_PATH
121139
exec "desktop-launch" "$SNAP/${packager.executableName}" "$@"
122140
`, {mode: "0755"})
123-
await copyDir(path.join(snapDir, "gui"), path.join(stageDir.dir, "meta", "gui"))
124-
await copyDir(snapTemplateDir!!, stageDir.dir, {
141+
142+
await copyDir(await getSnapTemplate(), stageDir.dir, {
125143
isUseHardLink: USE_HARD_LINKS,
126144
})
127145
await copyDirUsingHardLinks(appOutDir, stageDir.dir)
128146
}
129147

130148
if (isUseDocker) {
131-
if (isUsePrepackedSnap) {
149+
if (this.isUsePrepackedSnap) {
132150
await this.buildUsingDockerAndPrepackedSnap(snapFileName, stageDir)
133151
}
134152
else {
135153
await this.buildUsingDocker(options, arch, snapFileName, stageDir, appOutDir)
136154
}
137155
}
138156
else {
139-
await this.buildWithoutDocker(buildPackages, stageDir.dir, isUsePrepackedSnap, arch, artifactPath)
157+
await this.buildWithoutDocker(buildPackages, stageDir.dir, arch, artifactPath)
140158
}
141159

142160
await stageDir.cleanup()
143161
packager.dispatchArtifactCreated(artifactPath, this, arch)
144162
}
145163

146-
private async buildWithoutDocker(buildPackages: Array<string>, stageDir: string, isUsePrepackedSnap: boolean, arch: Arch, artifactPath: string) {
164+
private async buildWithoutDocker(buildPackages: Array<string>, stageDir: string, arch: Arch, artifactPath: string) {
147165
if (buildPackages.length > 0) {
148166
const notInstalledPackages = await BluebirdPromise.filter(buildPackages, (it): Promise<boolean> => {
149167
return exec("dpkg", ["-s", it])
@@ -160,7 +178,7 @@ exec "desktop-launch" "$SNAP/${packager.executableName}" "$@"
160178
}
161179

162180
let primeDir: string
163-
if (isUsePrepackedSnap) {
181+
if (this.isUsePrepackedSnap) {
164182
primeDir = stageDir
165183
}
166184
else {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export function getAria() {
7474

7575
export function getSnapTemplate() {
7676
// noinspection SpellCheckingInspection
77-
return getBinFromGithub("snap-template", "0.1.0", "EMgRw05KOmXqD2O5ifpfdaEa2BgTLlO/BKTJAAyDwxla556OAKib0Fd81fP9MBen+Xi14cufunxayBecEozRyw==")
77+
return getBinFromGithub("snap-template", "0.1.1", "W8JXQMwsrqH7T8kFD3KuULNVJRqygmcQPDPGhr9BXeRQS9U+A6jSsUEopQIwfQxlhuA6f7Jerc9XA0/ZLlK60w==")
7878
}
7979

8080
export interface ToolDescriptor {

0 commit comments

Comments
 (0)