Skip to content

Commit

Permalink
Bundle etcher-util with main app
Browse files Browse the repository at this point in the history
  • Loading branch information
dfunckt committed Nov 29, 2023
1 parent fce17c7 commit 09c9c37
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 19 deletions.
3 changes: 2 additions & 1 deletion forge.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-nati
import { WebpackPlugin } from '@electron-forge/plugin-webpack';

import { mainConfig, rendererConfig } from './webpack.config';
import * as sidecar from './forge.sidecar';

const { hostDependencies, productDescription } = require('./package.json');

Expand Down Expand Up @@ -128,6 +129,7 @@ const config: ForgeConfig = {
],
},
}),
new sidecar.SidecarPlugin(),
],
hooks: {
readPackageJson: async (_config, packageJson) => {
Expand All @@ -147,4 +149,3 @@ const config: ForgeConfig = {
};

export default config;

86 changes: 86 additions & 0 deletions forge.sidecar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { WebpackPlugin } from '@electron-forge/plugin-webpack';

import { PluginBase } from '@electron-forge/plugin-base';
import { ForgeHookMap, ResolvedForgeConfig } from '@electron-forge/shared-types';
import { DefinePlugin } from 'webpack';

import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';

import * as d from 'debug';

const debug = d('sidecar');

function addWebpackDefine(config: ResolvedForgeConfig, name: string, outPath: string): ResolvedForgeConfig {
config.plugins.forEach((plugin) => {
if (plugin.name == 'webpack' && plugin instanceof WebpackPlugin) {
const { mainConfig } = plugin.config as any;
if (mainConfig.plugins == null) {
mainConfig.plugins = [];
}
mainConfig.plugins.push(new DefinePlugin({
// expose path to helper via this webpack define
[name]: JSON.stringify(
process.argv[1].includes('electron-forge-start')
// on `npm run start`, point directly to the build dir
? outPath
// otherwise point relative to the resources folder of the bundled app
: path.basename(outPath)
),
}));
}
});
return config;
}

function build(baseDir: string, arch: string, outPath: string) {
const commands = [
//'npm rebuild mountutils',
'tsc --project tsconfig.sidecar.json',
`pkg ${baseDir}/util/api.js -c pkg-sidecar.json` +
// so that we can cross-compile for arm64 on x64
' --no-bytecode --public --public-packages "*"' +
` --target ${arch} --output ${outPath}`,
];
commands.forEach((cmd) => execSync(cmd, { shell: 'bash', stdio: 'inherit' }));
}

function copyArtifact(filePath: string, outPath: string) {
const dest = path.resolve(outPath, path.basename(filePath));
debug(`copying '${filePath}' to '${dest}'`);
fs.copyFileSync(filePath, dest);
}

// loosely based on https://github.com/cwellsx/electron-forge-resource-plugin
export class SidecarPlugin extends PluginBase<void> {
name = 'sidecar';

constructor() {
super();
this.getHooks = this.getHooks.bind(this);
}

getHooks(): ForgeHookMap {
const DEFINE_NAME = 'ETCHER_UTIL_BIN_PATH';
const BASE_DIR = 'out/sidecar';
const BIN_DIR = `${BASE_DIR}/bin`;
const OUT_PATH = `${BIN_DIR}/etcher-util${process.platform === 'win32' ? '.exe' : ''}`;

return {
resolveForgeConfig: async (currentConfig) => {
debug('resolveForgeConfig');
return addWebpackDefine(currentConfig, DEFINE_NAME, OUT_PATH);
},
generateAssets: async (_config, platform, arch) => {
debug('generateAssets', { platform, arch });
build(BASE_DIR, arch, OUT_PATH);
},
packageAfterCopy: async (_config, buildPath, electronVersion, platform, arch) => {
debug('packageAfterCopy', { buildPath, electronVersion, platform, arch });
// buildPath points to appPath, which is inside resources dir, which is what we want
copyArtifact(OUT_PATH, path.dirname(buildPath));
},
};
}
}
7 changes: 3 additions & 4 deletions lib/gui/app/modules/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import * as os from 'os';
import * as path from 'path';
import * as packageJSON from '../../../../package.json';
import * as permissions from '../../../shared/permissions';
import { getAppPath } from '../../../shared/get-app-path';
import * as errors from '../../../shared/errors';

const THREADS_PER_CPU = 16;
Expand All @@ -27,8 +26,8 @@ const THREADS_PER_CPU = 16;
// the stdout maxBuffer size to be exceeded when flashing
ipc.config.silent = true;

function writerArgv(): string[] {
let entryPoint = path.join(getAppPath(), 'generated', 'etcher-util');
async function writerArgv(): Promise<string[]> {
let entryPoint = await window.etcher.getEtcherUtilPath();
// AppImages run over FUSE, so the files inside the mount point
// can only be accessed by the user that mounted the AppImage.
// This means we can't re-spawn Etcher as root from the same
Expand Down Expand Up @@ -75,7 +74,7 @@ async function spawnChild({
IPC_SERVER_ID: string;
IPC_SOCKET_ROOT: string;
}) {
const argv = writerArgv();
const argv = await writerArgv();
const env = writerEnv(IPC_CLIENT_ID, IPC_SERVER_ID, IPC_SOCKET_ROOT);
if (withPrivileges) {
return await permissions.elevateCommand(argv, {
Expand Down
10 changes: 10 additions & 0 deletions lib/gui/app/preload.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts

import * as webapi from '../webapi';

declare global {
interface Window {
etcher: typeof webapi;
}
}

window['etcher'] = webapi;
14 changes: 14 additions & 0 deletions lib/gui/etcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,20 @@ electron.app.on('before-quit', () => {
process.exit(EXIT_CODES.SUCCESS);
});

// this is replaced at build-time with the path to helper binary,
// relative to the app resources directory.
declare const ETCHER_UTIL_BIN_PATH: string;

electron.ipcMain.handle('get-util-path', () => {
if (process.env.NODE_ENV === 'development') {
// In development there is no "app bundle" and we're working directly with
// artifacts from the "out" directory, where this value point to.
return ETCHER_UTIL_BIN_PATH;
}
// In any other case, resolve the helper relative to resources path.
return path.resolve(process.resourcesPath, ETCHER_UTIL_BIN_PATH);
});

async function main(): Promise<void> {
if (!electron.app.requestSingleInstanceLock()) {
electron.app.quit();
Expand Down
13 changes: 13 additions & 0 deletions lib/gui/webapi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Anything exported from this module will become available to the
// renderer process via preload. They're accessible as `window.etcher.foo()`.
//

import { ipcRenderer } from 'electron';

// FIXME: this is a workaround for the renderer to be able to find the etcher-util
// binary. We should instead export a function that asks the main process to launch
// the binary itself.
export async function getEtcherUtilPath(): Promise<string> {
return await ipcRenderer.invoke('get-util-path');
}
10 changes: 0 additions & 10 deletions lib/pkg-sidekick.json

This file was deleted.

36 changes: 35 additions & 1 deletion npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
"url": "[email protected]:balena-io/etcher.git"
},
"scripts": {
"build:rebuild-mountutils": "cd node_modules/mountutils && npm rebuild",
"build:sidecar": "npm run build:rebuild-mountutils && tsc --project tsconfig.sidecar.json && pkg build/util/api.js -c pkg-sidecar.json --target node18 --output generated/etcher-util",
"lint-css": "prettier --write lib/**/*.css",
"lint-ts": "balena-lint --fix --typescript typings lib tests webpack.config.ts",
"lint": "npm run lint-ts && npm run lint-css",
Expand Down Expand Up @@ -88,6 +86,7 @@
"@svgr/webpack": "5.5.0",
"@types/chai": "4.3.4",
"@types/copy-webpack-plugin": "6.4.3",
"@types/debug": "^4.1.12",
"@types/mime-types": "2.1.1",
"@types/mini-css-extract-plugin": "1.4.3",
"@types/mocha": "^9.1.1",
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.sidecar.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"outDir": "build"
"outDir": "out/sidecar"
},
"include": ["lib/util"]
}

0 comments on commit 09c9c37

Please sign in to comment.