Skip to content

Commit

Permalink
feat(snap): hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
develar committed Feb 1, 2018
1 parent fbfb8c6 commit 6faf828
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 38 deletions.
4 changes: 3 additions & 1 deletion docker/base/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ RUN curl -L https://yarnpkg.com/latest.tar.gz | tar xvz && mv yarn-* /yarn && ln
# python for node-gyp
# rpm is required for FPM to build rpm package
# libpng16-16 is required for libicns1_0.8.1-3.1 (on xenial)
# TODO remove graphicsmagick (3 months after electron-builder 19.53.0)
# libsecret-1-0 is required even for prebuild keytar
# libgtk2.0-dev for snap desktop-gtk2 (see https://github.com/ubuntu/snapcraft-desktop-helpers/blob/master/snapcraft.yaml#L248)
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 && \
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 && \
# libicns
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 && \
git lfs install && \
Expand Down
9 changes: 3 additions & 6 deletions packages/builder-util/src/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,12 @@ export function copyDir(src: string, destination: string, options: CopyDirOption

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

// pax requires created dir
const promise = ensureDir(destination)
return promise
.then(() => exec("pax", ["-rwl", "-p", "amp" /* Do not preserve file access times, Do not preserve file modification times, Preserve the file mode bits */, ".", destination], {
cwd: source,
Expand Down
6 changes: 6 additions & 0 deletions packages/electron-builder-lib/src/options/SnapOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export interface SnapOptions extends CommonLinuxOptions, TargetSpecificOptions {
*/
readonly stagePackages?: Array<string> | null

/**
* The [hooks](https://docs.snapcraft.io/build-snaps/hooks) directory, relative to `build` (build resources directory).
* @default build/snap-hooks
*/
readonly hooks?: string | null

/**
* The list of [plugs](https://snapcraft.io/docs/reference/interfaces).
* Defaults to `["desktop", "desktop-legacy", "home", "x11", "unity7", "browser-support", "network", "gsettings", "pulseaudio", "opengl"]`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ export class LinuxTargetHelper {

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

async computeDesktopEntry(targetSpecificOptions: LinuxTargetSpecificOptions, exec?: string, extra?: { [key: string]: string; }): Promise<string> {
Expand Down
72 changes: 45 additions & 27 deletions packages/electron-builder-lib/src/targets/snap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,28 @@ const defaultPlugs = ["desktop", "desktop-legacy", "home", "x11", "unity7", "bro
export default class SnapTarget extends Target {
readonly options: SnapOptions = {...this.packager.platformSpecificBuildOptions, ...(this.packager.config as any)[this.name]}

private isUsePrepackedSnap = true

constructor(name: string, private readonly packager: LinuxPackager, private readonly helper: LinuxTargetHelper, readonly outDir: string) {
super(name)
}

private replaceDefault(inList: Array<string> | null | undefined, defaultList: Array<string>) {
const result = _replaceDefault(inList, defaultList)
if (result !== defaultList) {
this.isUsePrepackedSnap = false
}
return result
}

async build(appOutDir: string, arch: Arch): Promise<any> {
const packager = this.packager
const appInfo = packager.appInfo
const options = this.options
const snapName = packager.executableName.toLowerCase()
const buildPackages = asArray(options.buildPackages)
const isUseDocker = process.platform !== "linux" || isEnvTrue(process.env.SNAP_USE_DOCKER)
let isUsePrepackedSnap = arch === Arch.x64 && buildPackages.length === 0

function replaceDefault(inList: Array<string> | null | undefined, defaultList: Array<string>) {
const result = _replaceDefault(inList, defaultList)
if (result !== defaultList) {
isUsePrepackedSnap = false
}
return result
}
this.isUsePrepackedSnap = arch === Arch.x64 && buildPackages.length === 0

const snap: any = {
name: snapName,
Expand All @@ -67,38 +69,55 @@ export default class SnapTarget extends Target {
TMPDIR: "$XDG_RUNTIME_DIR",
...options.environment,
},
plugs: replaceDefault(options.plugs, defaultPlugs),
plugs: this.replaceDefault(options.plugs, defaultPlugs),
}
},
parts: {
app: {
plugin: "dump",
"stage-packages": replaceDefault(options.stagePackages, defaultStagePackages),
"stage-packages": this.replaceDefault(options.stagePackages, defaultStagePackages),
source: isUseDocker ? "/appOutDir" : appOutDir,
after: replaceDefault(options.after, ["desktop-gtk2"]),
after: this.replaceDefault(options.after, ["desktop-gtk2"]),
}
},
}

if (options.assumes != null) {
snap.assumes = asArray(options.assumes)
const wrapperFileName = `command-${packager.executableName}.wrapper`
if (this.isUsePrepackedSnap) {
delete snap.parts
snap.apps[snapName].command = wrapperFileName
}

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

if (options.assumes != null) {
snap.assumes = asArray(options.assumes)
}

const stageDir = await createStageDir(this, packager, arch)
// snapcraft.yaml inside a snap directory
const snapDir = path.join(stageDir.dir, "snap")
const snapMetaDir = this.isUsePrepackedSnap ? path.join(stageDir.dir, "meta") : snapDir

await this.helper.icons
if (this.helper.maxIconPath != null) {
snap.icon = "snap/gui/icon.png"
await copyFile(this.helper.maxIconPath, path.join(snapDir, "gui", "icon.png"))
if (!this.isUsePrepackedSnap) {
snap.icon = "snap/gui/icon.png"
}
await copyFile(this.helper.maxIconPath, path.join(snapMetaDir, "gui", "icon.png"))
}

const desktopFile = await this.helper.writeDesktopEntry(this.options, packager.executableName, path.join(snapDir, "gui", `${snap.name}.desktop`), {
const hooksDir = await packager.getResource(options.hooks, "snap-hooks")
if (hooksDir != null) {
await copyDir(hooksDir, path.join(snapMetaDir, "hooks"), {
isUseHardLink: USE_HARD_LINKS,
})
}

const desktopFile = path.join(snapMetaDir, "gui", `${snap.name}.desktop`)
await this.helper.writeDesktopEntry(this.options, packager.executableName, desktopFile, {
// tslint:disable:no-invalid-template-strings
Icon: "${SNAP}/meta/gui/icon.png"
})
Expand All @@ -107,43 +126,42 @@ export default class SnapTarget extends Target {
return
}

const snapcraftFile = isUsePrepackedSnap ? path.join(stageDir.dir, "meta", "snap.yaml") : path.join(snapDir, "snapcraft.yaml")
const snapcraftFile = path.join(snapMetaDir, this.isUsePrepackedSnap ? "snap.yaml" : "snapcraft.yaml")
await outputFile(snapcraftFile, serializeToYaml(snap))
const snapTemplateDir = isUsePrepackedSnap ? await getSnapTemplate() : null
if (isUsePrepackedSnap) {
if (this.isUsePrepackedSnap) {
// noinspection SpellCheckingInspection
await writeFile(path.join(stageDir.dir, `command-${packager.executableName}`), `#!/bin/sh
await writeFile(path.join(stageDir.dir, wrapperFileName), `#!/bin/sh
export PATH="$SNAP/usr/sbin:$SNAP/usr/bin:$SNAP/sbin:$SNAP/bin:$PATH"
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"
export LD_LIBRARY_PATH="$SNAP/usr/lib/x86_64-linux-gnu/mesa-egl:$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH="$SNAP/usr/lib/x86_64-linux-gnu:$SNAP/usr/lib/x86_64-linux-gnu/pulseaudio:$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH=$SNAP_LIBRARY_PATH:$LD_LIBRARY_PATH
exec "desktop-launch" "$SNAP/${packager.executableName}" "$@"
`, {mode: "0755"})
await copyDir(path.join(snapDir, "gui"), path.join(stageDir.dir, "meta", "gui"))
await copyDir(snapTemplateDir!!, stageDir.dir, {

await copyDir(await getSnapTemplate(), stageDir.dir, {
isUseHardLink: USE_HARD_LINKS,
})
await copyDirUsingHardLinks(appOutDir, stageDir.dir)
}

if (isUseDocker) {
if (isUsePrepackedSnap) {
if (this.isUsePrepackedSnap) {
await this.buildUsingDockerAndPrepackedSnap(snapFileName, stageDir)
}
else {
await this.buildUsingDocker(options, arch, snapFileName, stageDir, appOutDir)
}
}
else {
await this.buildWithoutDocker(buildPackages, stageDir.dir, isUsePrepackedSnap, arch, artifactPath)
await this.buildWithoutDocker(buildPackages, stageDir.dir, arch, artifactPath)
}

await stageDir.cleanup()
packager.dispatchArtifactCreated(artifactPath, this, arch)
}

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

let primeDir: string
if (isUsePrepackedSnap) {
if (this.isUsePrepackedSnap) {
primeDir = stageDir
}
else {
Expand Down
2 changes: 1 addition & 1 deletion packages/electron-builder-lib/src/targets/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function getAria() {

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

export interface ToolDescriptor {
Expand Down

0 comments on commit 6faf828

Please sign in to comment.