Skip to content

Commit

Permalink
chore(android): implement remote snapshot generation
Browse files Browse the repository at this point in the history
  • Loading branch information
Gary Mathews committed Dec 7, 2019
1 parent 6a094f6 commit 03cb177
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 117 deletions.
2 changes: 1 addition & 1 deletion android/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"v8": {
"version": "7.8.279.23",
"mode": "release",
"checksum": "3d68aa597fe26d142b7f1373906d82bad835849d13ef2fb53aac0e55c9f3d0fff87d5c28719cf1ccedbd8e807c3805ae5df0cd3d5411261c9e33fb1f3cb06d7f"
"checksum": "b7e3768e95463ea77842c54f6d46b87e9f79761ff2e351d770af23943e11e26bb9066eaa9966f621085ff79cba4a972e339038afefbf83d462917d708a2b0c43"
},
"minSDKVersion": "19",
"compileSDKVersion": "29",
Expand Down
28 changes: 0 additions & 28 deletions build/lib/android/V8Snapshots.h.ejs

This file was deleted.

130 changes: 45 additions & 85 deletions build/lib/android/snapshot.js
Original file line number Diff line number Diff line change
@@ -1,114 +1,74 @@
'use strict';

const promisify = require('util').promisify;
const exec = require('child_process').execFile; // eslint-disable-line security/detect-child-process
const os = require('os');
const fs = require('fs-extra');
const path = require('path');
const ejs = require('ejs');
const request = require('request-promise-native');

const ROOT_DIR = path.join(__dirname, '..', '..', '..');
const DIST_DIR = path.join(ROOT_DIR, 'dist');
const TMP_DIR = path.join(DIST_DIR, 'tmp');
const ANDROID_DIR = path.join(ROOT_DIR, 'android');
const ANDROID_PROPS = require(path.join(ANDROID_DIR, 'package.json')); // eslint-disable-line security/detect-non-literal-require
const V8_PROPS = ANDROID_PROPS.v8;
const V8_LIB_DIR = path.join(ROOT_DIR, 'dist', 'android', 'libv8', V8_PROPS.version, V8_PROPS.mode, 'libs');

// Obtain target architectures
const TARGETS = [];
for (const target of ANDROID_PROPS.architectures) {
if (target === 'arm64-v8a') {
TARGETS.push('arm64');
} else if (target === 'armeabi-v7a') {
TARGETS.push('arm');
} else {
TARGETS.push(target);
}
}

/**
* Runs mksnapshot to generate snapshots for each architecture
* @param {string} target targets to generate blob
* Generates empty snapshot blobs for each supported architecture
* and creates a header to include the snapshots at build time.
* @returns {Promise<void>}
*/
async function generateBlob(target) {
const V8_LIB_TARGET_DIR = path.resolve(V8_LIB_DIR, target);
const MKSNAPSHOT_PATH = path.join(V8_LIB_TARGET_DIR, 'mksnapshot');
const BLOB_PATH = path.join(V8_LIB_TARGET_DIR, 'blob.bin');
const STARTUP_PATH = path.join(TMP_DIR, 'startup.js');
const TI_MAIN_PATH = path.join(ROOT_DIR, 'common', 'Resources', 'ti.main.js');
const TI_MAIN_PLATFORM_PATH = path.join(TMP_DIR, 'common', 'android', 'ti.main.js');
const args = [
'--startup_blob=' + BLOB_PATH,
STARTUP_PATH,
'--print-all-exceptions'
];

// Snapshot already exists, skip...
if (await fs.exists(BLOB_PATH)) {
const { blobStat, commonStat } = await Promise.all([ fs.stat(BLOB_PATH), fs.stat(TI_MAIN_PATH) ]);
if (commonStat && blobStat && commonStat.mtime < blobStat.mtime) {
return;
}
}
async function build() {
const v8 = V8_PROPS.version;
const script = (await fs.readFile(path.join(TMP_DIR, 'common', 'android', 'ti.main.js'))).toString();

// Load platform optimized 'common' bundle
const commonBundle = await fs.readFile(TI_MAIN_PLATFORM_PATH);
return new Promise(async resolve => { // eslint-disable-line no-async-promise-executor

// Generate 'startup.js'
const output = await promisify(ejs.renderFile)(path.join(__dirname, 'startup.ejs'), { script: commonBundle }, {});
await fs.writeFile(STARTUP_PATH, output);
console.log('Attempting to request snapshot...');

// Set correct permissions for 'mksnapshot'
await fs.chmod(MKSNAPSHOT_PATH, 0o755);
// Obtain snapshot `id` and start new snapshot generation.
const id = await request.post('http://52.10.192.87', { body: { v8, script }, json: true });

console.warn(`Generating snapshot blob for ${target}...`);
async function getSnapshot() {

// Generate snapshot
return promisify(exec)(MKSNAPSHOT_PATH, args).catch(e => console.warn(`Could not generate blob for ${target}: ${e.message}`));
}
// Request generated snapshot.
const header = await request.get(`http://52.10.192.87/snapshot/${id}`, {
simple: false,
resolveWithFullResponse: true
});

/**
* Generates V8Snapshots.h header from architecture blobs
* @returns {Promise<void>}
*/
async function generateHeader() {
const blobs = {};
if (header.statusCode === 200) {
console.log('Writing snapshot...');

// Load snapshots for each architecture
await Promise.all(TARGETS.map(async target => {
const V8_LIB_TARGET_DIR = path.resolve(V8_LIB_DIR, target);
const BLOB_PATH = path.join(V8_LIB_TARGET_DIR, 'blob.bin');
// Overwrite stub snapshot header.
await fs.writeFile(path.join(ANDROID_DIR, 'runtime', 'v8', 'src', 'native', 'V8Snapshots.h'), header.body);

if (await fs.exists(BLOB_PATH)) {
blobs[target] = Buffer.from(await fs.readFile(BLOB_PATH, 'binary'), 'binary');
}
}));
// Done. Resolve `build` promise.
resolve();

console.log(`Generating V8Snapshots.h for ${Object.keys(blobs).join(', ')}...`);
} else if (header.statusCode === 418) {
console.log('Waiting for snapshot generation...');

// Generate 'V8Snapshots.h' from template
const output = await promisify(ejs.renderFile)(path.join(__dirname, 'V8Snapshots.h.ejs'), { blobs }, {});
return fs.writeFile(path.join(ANDROID_DIR, 'runtime', 'v8', 'src', 'native', 'V8Snapshots.h'), output);
}
// Snapshot server is still building, wait for next interval.
return false;

/**
* Generates empty snapshot blobs for each supported architecture
* and creates a header to include the snapshots at build time.
* NOTE: SNAPSHOT GENERATION IS ONLY SUPPORTED ON macOS
* @returns {Promise<void>}
*/
async function build() {
// Only macOS is supports creating snapshots
if (os.platform() !== 'darwin') {
console.warn('Snapshot generation is only supported on macOS, skipping...');
return;
}

// Generate snapshots in parallel
await Promise.all(TARGETS.map(target => generateBlob(target)));
return generateHeader();
} else {
console.error('Could not generate snapshot, skipping...');
}

// Prevent retry interval.
return true;
}

// Attempt to grab generated snapshot.
if (!await getSnapshot()) {

// Retry until snapshot generation finishes.
const interval = setInterval(async () => {
if (await getSnapshot()) {
clearInterval(interval);
}
}, 5000);
}
});
}

module.exports = { build };
3 changes: 0 additions & 3 deletions build/lib/android/startup.ejs

This file was deleted.

23 changes: 23 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
"p-limit": "^2.2.0",
"pngjs": "^3.4.0",
"request": "^2.87.0",
"request-promise-native": "^1.0.8",
"semver": "^6.3.0",
"sprintf": "0.1.5",
"temp": "0.9.0",
Expand Down

0 comments on commit 03cb177

Please sign in to comment.