зеркало из https://github.com/electron/universal.git
feat: add support for v8 snapshots and cross-arch app code
This commit is contained in:
Родитель
c6136396fd
Коммит
46ff9c4b38
|
@ -0,0 +1,7 @@
|
|||
if (process.arch === 'arm64') {
|
||||
process._asarPath = require.resolve('../arm64.app.asar');
|
||||
} else {
|
||||
process._asarPath = require.resolve('../x64.app.asar');
|
||||
}
|
||||
|
||||
require(process._asarPath);
|
122
src/index.ts
122
src/index.ts
|
@ -1,5 +1,6 @@
|
|||
import { spawn } from '@malept/cross-spawn-promise';
|
||||
import * as asar from 'asar';
|
||||
import * as crypto from 'crypto';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
@ -42,8 +43,7 @@ type MakeUniversalOpts = {
|
|||
|
||||
enum AsarMode {
|
||||
NO_ASAR,
|
||||
PURE_ASAR_EMBEDDED_NATIVE_MODULES,
|
||||
PURE_ASAR_UNPACKED_NATIVE_MODULES,
|
||||
HAS_ASAR,
|
||||
}
|
||||
|
||||
const detectAsarMode = async (appPath: string) => {
|
||||
|
@ -51,16 +51,24 @@ const detectAsarMode = async (appPath: string) => {
|
|||
const asarUnpackedPath = path.resolve(appPath, 'Contents', 'Resources', 'app.asar.unpacked');
|
||||
|
||||
if (!(await fs.pathExists(asarPath))) return AsarMode.NO_ASAR;
|
||||
const nativeContents = asar.listPackage(asarPath).filter((p) => p.endsWith('.node'));
|
||||
for (const nativeModule of nativeContents) {
|
||||
if (!(await fs.pathExists(path.resolve(asarUnpackedPath, nativeModule.substr(1)))))
|
||||
return AsarMode.PURE_ASAR_EMBEDDED_NATIVE_MODULES;
|
||||
}
|
||||
return AsarMode.PURE_ASAR_UNPACKED_NATIVE_MODULES;
|
||||
|
||||
return AsarMode.HAS_ASAR;
|
||||
};
|
||||
|
||||
const getAllMachOFiles = async (appPath: string) => {
|
||||
const machoOFiles: string[] = [];
|
||||
enum AppFileType {
|
||||
MACHO,
|
||||
PLAIN,
|
||||
SNAPSHOT,
|
||||
APP_CODE,
|
||||
}
|
||||
|
||||
type AppFile = {
|
||||
relativePath: string;
|
||||
type: AppFileType;
|
||||
}
|
||||
|
||||
const getAllFiles = async (appPath: string): Promise<AppFile[]> => {
|
||||
const files: AppFile[] = [];
|
||||
|
||||
const visited = new Set<string>();
|
||||
const traverse = async (p: string) => {
|
||||
|
@ -71,10 +79,21 @@ const getAllMachOFiles = async (appPath: string) => {
|
|||
const info = await fs.stat(p);
|
||||
if (info.isSymbolicLink()) return;
|
||||
if (info.isFile()) {
|
||||
let fileType = AppFileType.PLAIN;
|
||||
|
||||
const fileOutput = await spawn('file', ['--brief', '--no-pad', p]);
|
||||
if (fileOutput.startsWith(MACHO_PREFIX)) {
|
||||
machoOFiles.push(path.relative(appPath, p));
|
||||
if (p.includes('app.asar')) {
|
||||
fileType = AppFileType.APP_CODE;
|
||||
} else if (fileOutput.startsWith(MACHO_PREFIX)) {
|
||||
fileType = AppFileType.MACHO;
|
||||
} else if (p.endsWith('.bin')) {
|
||||
fileType = AppFileType.SNAPSHOT;
|
||||
}
|
||||
|
||||
files.push({
|
||||
relativePath: path.relative(appPath, p),
|
||||
type: fileType,
|
||||
});
|
||||
}
|
||||
|
||||
if (info.isDirectory()) {
|
||||
|
@ -85,9 +104,15 @@ const getAllMachOFiles = async (appPath: string) => {
|
|||
};
|
||||
await traverse(appPath);
|
||||
|
||||
return machoOFiles;
|
||||
return files;
|
||||
};
|
||||
|
||||
const dupedFiles = (files: AppFile[]) => files.filter(f => f.type !== AppFileType.SNAPSHOT && f.type !== AppFileType.APP_CODE);
|
||||
|
||||
const sha = async (filePath: string) => {
|
||||
return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
||||
}
|
||||
|
||||
export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> => {
|
||||
if (process.platform !== 'darwin')
|
||||
throw new Error('@electron/universal is only supported on darwin platforms');
|
||||
|
@ -115,14 +140,6 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||
throw new Error(
|
||||
'Both the x64 and arm64 versions of your application need to have been built with the same asar settings (enabled vs disabled)',
|
||||
);
|
||||
if (x64AsarMode === AsarMode.PURE_ASAR_EMBEDDED_NATIVE_MODULES)
|
||||
throw new Error(
|
||||
'@electron/universal does not currently support apps that contain native modules in ASAR files. Please use asar.unpacked',
|
||||
);
|
||||
if (arm64AsarMode === AsarMode.PURE_ASAR_EMBEDDED_NATIVE_MODULES)
|
||||
throw new Error(
|
||||
'@electron/universal does not currently support apps that contain native modules in ASAR files. Please use asar.unpacked',
|
||||
);
|
||||
|
||||
const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-universal-'));
|
||||
|
||||
|
@ -132,14 +149,14 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||
|
||||
const uniqueToX64: string[] = [];
|
||||
const uniqueToArm64: string[] = [];
|
||||
const x64MachOFiles = await getAllMachOFiles(await fs.realpath(tmpApp));
|
||||
const arm64MachoOFiles = await getAllMachOFiles(opts.arm64AppPath);
|
||||
const x64Files = await getAllFiles(await fs.realpath(tmpApp));
|
||||
const arm64Files = await getAllFiles(opts.arm64AppPath);
|
||||
|
||||
for (const file of x64MachOFiles) {
|
||||
if (!arm64MachoOFiles.includes(file)) uniqueToX64.push(file);
|
||||
for (const file of dupedFiles(x64Files)) {
|
||||
if (!arm64Files.some(f => f.relativePath === file.relativePath)) uniqueToX64.push(file.relativePath);
|
||||
}
|
||||
for (const file of arm64MachoOFiles) {
|
||||
if (!x64MachOFiles.includes(file)) uniqueToArm64.push(file);
|
||||
for (const file of dupedFiles(arm64Files)) {
|
||||
if (!x64Files.some(f => f.relativePath === file.relativePath)) uniqueToArm64.push(file.relativePath);
|
||||
}
|
||||
if (uniqueToX64.length !== 0 || uniqueToArm64.length !== 0) {
|
||||
console.error({
|
||||
|
@ -151,16 +168,59 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||
);
|
||||
}
|
||||
|
||||
for (const machOFile of x64MachOFiles) {
|
||||
for (const file of x64Files.filter(f => f.type === AppFileType.PLAIN)) {
|
||||
const x64Sha = await sha(path.resolve(opts.x64AppPath, file.relativePath));
|
||||
const arm64Sha = await sha(path.resolve(opts.arm64AppPath, file.relativePath));
|
||||
if (x64Sha !== arm64Sha) {
|
||||
console.error(`${x64Sha} !== ${arm64Sha}`);
|
||||
throw new Error(`Expected all non-binary files to have identical SHAs when creating a universal build but "${file.relativePath}" did not`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const machOFile of x64Files.filter(f => f.type === AppFileType.MACHO)) {
|
||||
await spawn('lipo', [
|
||||
await fs.realpath(path.resolve(tmpApp, machOFile)),
|
||||
await fs.realpath(path.resolve(opts.arm64AppPath, machOFile)),
|
||||
await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)),
|
||||
await fs.realpath(path.resolve(opts.arm64AppPath, machOFile.relativePath)),
|
||||
'-create',
|
||||
'-output',
|
||||
await fs.realpath(path.resolve(tmpApp, machOFile)),
|
||||
await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)),
|
||||
]);
|
||||
}
|
||||
|
||||
if (x64AsarMode === AsarMode.NO_ASAR) {
|
||||
await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'x64.app'));
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'arm64.app'));
|
||||
} else {
|
||||
await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), path.resolve(tmpApp, 'Contents', 'Resources', 'x64.app.asar'));
|
||||
const x64Unpacked = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar.unpacked');
|
||||
if (await fs.pathExists(x64Unpacked)) {
|
||||
await fs.move(x64Unpacked, path.resolve(tmpApp, 'Contents', 'Resources', 'x64.app.asar.unpacked'));
|
||||
}
|
||||
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'), path.resolve(tmpApp, 'Contents', 'Resources', 'arm64.app.asar'));
|
||||
const arm64Unpacked = path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar.unpacked');
|
||||
if (await fs.pathExists(arm64Unpacked)) {
|
||||
await fs.copy(arm64Unpacked, path.resolve(tmpApp, 'Contents', 'Resources', 'arm64.app.asar.unpacked'));
|
||||
}
|
||||
}
|
||||
|
||||
const entryAsar = path.resolve(tmpDir, 'entry-asar');
|
||||
await fs.mkdir(entryAsar);
|
||||
await fs.copy(path.resolve(__dirname, '..', '..', 'entry-asar', 'index.js'), path.resolve(entryAsar, 'index.js'));
|
||||
let pj: any;
|
||||
if (x64AsarMode === AsarMode.NO_ASAR) {
|
||||
pj = await fs.readJson(path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app', 'package.json'));
|
||||
} else {
|
||||
pj = JSON.parse((await asar.extractFile(path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app.asar'), 'package.json')).toString('utf8'));
|
||||
}
|
||||
pj.main = 'index.js';
|
||||
await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj);
|
||||
await asar.createPackage(entryAsar, path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'));
|
||||
|
||||
for (const snapshotsFile of arm64Files.filter(f => f.type === AppFileType.SNAPSHOT)) {
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, snapshotsFile.relativePath), path.resolve(tmpApp, snapshotsFile.relativePath));
|
||||
}
|
||||
|
||||
await spawn('mv', [tmpApp, opts.outAppPath]);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
|
|
Загрузка…
Ссылка в новой задаче