Skip to content

Commit

Permalink
Merge pull request microsoft#41353 from Microsoft/alex/asar
Browse files Browse the repository at this point in the history
Use ASAR
  • Loading branch information
alexdima authored Jan 29, 2018
2 parents 4a01f5a + f8d1683 commit f27e4b0
Show file tree
Hide file tree
Showing 13 changed files with 468 additions and 24 deletions.
4 changes: 3 additions & 1 deletion build/gulpfile.vscode.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const i18n = require('./lib/i18n');
const glob = require('glob');
const deps = require('./dependencies');
const getElectronVersion = require('./lib/electron').getElectronVersion;
const createAsar = require('./lib/asar').createAsar;

const productionDependencies = deps.getProductionDependencies(path.dirname(__dirname));
const baseModules = Object.keys(process.binding('natives')).filter(n => !/^_|\//.test(n));
Expand Down Expand Up @@ -303,7 +304,8 @@ function packageTask(platform, arch, opts) {
.pipe(util.cleanNodeModule('keytar', ['binding.gyp', 'build/**', 'src/**', 'script/**', 'node_modules/**'], ['**/*.node']))
.pipe(util.cleanNodeModule('node-pty', ['binding.gyp', 'build/**', 'src/**', 'tools/**'], ['build/Release/**']))
.pipe(util.cleanNodeModule('nsfw', ['binding.gyp', 'build/**', 'src/**', 'openpa/**', 'includes/**'], ['**/*.node', '**/*.a']))
.pipe(util.cleanNodeModule('vsda', ['binding.gyp', 'README.md', 'build/**', '*.bat', '*.sh', '*.cpp', '*.h'], ['build/Release/vsda.node']));
.pipe(util.cleanNodeModule('vsda', ['binding.gyp', 'README.md', 'build/**', '*.bat', '*.sh', '*.cpp', '*.h'], ['build/Release/vsda.node']))
.pipe(createAsar(path.join(process.cwd(), 'node_modules'), ['**/*.node', '**/vscode-ripgrep/bin/*'], 'app/node_modules.asar'));

let all = es.merge(
packageJsonStream,
Expand Down
118 changes: 118 additions & 0 deletions build/lib/asar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
var path = require("path");
var es = require("event-stream");
var pickle = require("chromium-pickle-js");
var Filesystem = require("asar/lib/filesystem");
var VinylFile = require("vinyl");
var minimatch = require("minimatch");
function createAsar(folderPath, unpackGlobs, destFilename) {
var shouldUnpackFile = function (file) {
for (var i = 0; i < unpackGlobs.length; i++) {
if (minimatch(file.relative, unpackGlobs[i])) {
return true;
}
}
return false;
};
var filesystem = new Filesystem(folderPath);
var out = [];
// Keep track of pending inserts
var pendingInserts = 0;
var onFileInserted = function () { pendingInserts--; };
// Do not insert twice the same directory
var seenDir = {};
var insertDirectoryRecursive = function (dir) {
if (seenDir[dir]) {
return;
}
var lastSlash = dir.lastIndexOf('/');
if (lastSlash === -1) {
lastSlash = dir.lastIndexOf('\\');
}
if (lastSlash !== -1) {
insertDirectoryRecursive(dir.substring(0, lastSlash));
}
seenDir[dir] = true;
filesystem.insertDirectory(dir);
};
var insertDirectoryForFile = function (file) {
var lastSlash = file.lastIndexOf('/');
if (lastSlash === -1) {
lastSlash = file.lastIndexOf('\\');
}
if (lastSlash !== -1) {
insertDirectoryRecursive(file.substring(0, lastSlash));
}
};
var insertFile = function (relativePath, stat, shouldUnpack) {
insertDirectoryForFile(relativePath);
pendingInserts++;
filesystem.insertFile(relativePath, shouldUnpack, { stat: stat }, {}, onFileInserted);
};
return es.through(function (file) {
if (file.stat.isDirectory()) {
return;
}
if (!file.stat.isFile()) {
throw new Error("unknown item in stream!");
}
var shouldUnpack = shouldUnpackFile(file);
insertFile(file.relative, { size: file.contents.length, mode: file.stat.mode }, shouldUnpack);
if (shouldUnpack) {
// The file goes outside of xx.asar, in a folder xx.asar.unpacked
var relative = path.relative(folderPath, file.path);
this.queue(new VinylFile({
cwd: folderPath,
base: folderPath,
path: path.join(destFilename + '.unpacked', relative),
stat: file.stat,
contents: file.contents
}));
}
else {
// The file goes inside of xx.asar
out.push(file.contents);
}
}, function () {
var _this = this;
var finish = function () {
{
var headerPickle = pickle.createEmpty();
headerPickle.writeString(JSON.stringify(filesystem.header));
var headerBuf = headerPickle.toBuffer();
var sizePickle = pickle.createEmpty();
sizePickle.writeUInt32(headerBuf.length);
var sizeBuf = sizePickle.toBuffer();
out.unshift(headerBuf);
out.unshift(sizeBuf);
}
var contents = Buffer.concat(out);
out.length = 0;
_this.queue(new VinylFile({
cwd: folderPath,
base: folderPath,
path: destFilename,
contents: contents
}));
_this.queue(null);
};
// Call finish() only when all file inserts have finished...
if (pendingInserts === 0) {
finish();
}
else {
onFileInserted = function () {
pendingInserts--;
if (pendingInserts === 0) {
finish();
}
};
}
});
}
exports.createAsar = createAsar;
131 changes: 131 additions & 0 deletions build/lib/asar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

'use strict';

import * as path from 'path';
import * as es from 'event-stream';
import * as pickle from 'chromium-pickle-js';
import * as Filesystem from 'asar/lib/filesystem';
import * as VinylFile from 'vinyl';
import * as minimatch from 'minimatch';

export function createAsar(folderPath: string, unpackGlobs: string[], destFilename: string): NodeJS.ReadWriteStream {

const shouldUnpackFile = (file: VinylFile): boolean => {
for (let i = 0; i < unpackGlobs.length; i++) {
if (minimatch(file.relative, unpackGlobs[i])) {
return true;
}
}
return false;
};

const filesystem = new Filesystem(folderPath);
const out: Buffer[] = [];

// Keep track of pending inserts
let pendingInserts = 0;
let onFileInserted = () => { pendingInserts--; };

// Do not insert twice the same directory
const seenDir: { [key: string]: boolean; } = {};
const insertDirectoryRecursive = (dir: string) => {
if (seenDir[dir]) {
return;
}

let lastSlash = dir.lastIndexOf('/');
if (lastSlash === -1) {
lastSlash = dir.lastIndexOf('\\');
}
if (lastSlash !== -1) {
insertDirectoryRecursive(dir.substring(0, lastSlash));
}
seenDir[dir] = true;
filesystem.insertDirectory(dir);
};

const insertDirectoryForFile = (file: string) => {
let lastSlash = file.lastIndexOf('/');
if (lastSlash === -1) {
lastSlash = file.lastIndexOf('\\');
}
if (lastSlash !== -1) {
insertDirectoryRecursive(file.substring(0, lastSlash));
}
};

const insertFile = (relativePath: string, stat: { size: number; mode: number; }, shouldUnpack: boolean) => {
insertDirectoryForFile(relativePath);
pendingInserts++;
filesystem.insertFile(relativePath, shouldUnpack, { stat: stat }, {}, onFileInserted);
};

return es.through(function (file) {
if (file.stat.isDirectory()) {
return;
}
if (!file.stat.isFile()) {
throw new Error(`unknown item in stream!`);
}
const shouldUnpack = shouldUnpackFile(file);
insertFile(file.relative, { size: file.contents.length, mode: file.stat.mode }, shouldUnpack);

if (shouldUnpack) {
// The file goes outside of xx.asar, in a folder xx.asar.unpacked
const relative = path.relative(folderPath, file.path);
this.queue(new VinylFile({
cwd: folderPath,
base: folderPath,
path: path.join(destFilename + '.unpacked', relative),
stat: file.stat,
contents: file.contents
}));
} else {
// The file goes inside of xx.asar
out.push(file.contents);
}
}, function () {

let finish = () => {
{
const headerPickle = pickle.createEmpty();
headerPickle.writeString(JSON.stringify(filesystem.header));
const headerBuf = headerPickle.toBuffer();

const sizePickle = pickle.createEmpty();
sizePickle.writeUInt32(headerBuf.length);
const sizeBuf = sizePickle.toBuffer();

out.unshift(headerBuf);
out.unshift(sizeBuf);
}

const contents = Buffer.concat(out);
out.length = 0;

this.queue(new VinylFile({
cwd: folderPath,
base: folderPath,
path: destFilename,
contents: contents
}));
this.queue(null);
};

// Call finish() only when all file inserts have finished...
if (pendingInserts === 0) {
finish();
} else {
onFileInserted = () => {
pendingInserts--;
if (pendingInserts === 0) {
finish();
}
};
}
});
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@
"@types/minimist": "1.2.0",
"@types/mocha": "2.2.39",
"@types/sinon": "1.16.34",
"asar": "^0.14.0",
"azure-storage": "^0.3.1",
"chromium-pickle-js": "^0.2.0",
"clean-css": "3.4.6",
"coveralls": "^2.11.11",
"cson-parser": "^1.3.3",
Expand Down
27 changes: 23 additions & 4 deletions src/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,29 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

// disable electron's asar support early on because bootstrap.js is used in forked processes
// where the environment is purely node based. this will instruct electron to not treat files
// with *.asar ending any special from normal files.
process.noAsar = true;
//#region Add support for using node_modules.asar
(function () {
const path = require('path');
const Module = require('module');
const NODE_MODULES_PATH = path.join(__dirname, '../node_modules');
const NODE_MODULES_ASAR_PATH = NODE_MODULES_PATH + '.asar';

const originalResolveLookupPaths = Module._resolveLookupPaths;
Module._resolveLookupPaths = function (request, parent) {
const result = originalResolveLookupPaths(request, parent);

const paths = result[1];
for (let i = 0, len = paths.length; i < len; i++) {
if (paths[i] === NODE_MODULES_PATH) {
paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
break;
}
}

return result;
};
})();
//#endregion

// Will be defined if we got forked from another node process
// In that case we override console.log/warn/error to be able
Expand Down
24 changes: 24 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,30 @@ perf.mark('main:started');
// Perf measurements
global.perfStartTime = Date.now();

//#region Add support for using node_modules.asar
(function () {
const path = require('path');
const Module = require('module');
const NODE_MODULES_PATH = path.join(__dirname, '../node_modules');
const NODE_MODULES_ASAR_PATH = NODE_MODULES_PATH + '.asar';

const originalResolveLookupPaths = Module._resolveLookupPaths;
Module._resolveLookupPaths = function (request, parent) {
const result = originalResolveLookupPaths(request, parent);

const paths = result[1];
for (let i = 0, len = paths.length; i < len; i++) {
if (paths[i] === NODE_MODULES_PATH) {
paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
break;
}
}

return result;
};
})();
//#endregion

let app = require('electron').app;
let fs = require('fs');
let path = require('path');
Expand Down
3 changes: 1 addition & 2 deletions src/vs/base/node/stdFork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ function generatePatchedEnv(env: any, stdInPipeName: string, stdOutPipeName: str
newEnv['STDOUT_PIPE_NAME'] = stdOutPipeName;
newEnv['STDERR_PIPE_NAME'] = stdErrPipeName;
newEnv['ELECTRON_RUN_AS_NODE'] = '1';
newEnv['ELECTRON_NO_ASAR'] = '1';

return newEnv;
}
Expand Down Expand Up @@ -138,4 +137,4 @@ export function fork(modulePath: string, args: string[], options: IForkOpts, cal

// On vscode exit still close server #7758
process.once('exit', closeServer);
}
}
Loading

0 comments on commit f27e4b0

Please sign in to comment.