Skip to content

Commit

Permalink
built in extensions control file
Browse files Browse the repository at this point in the history
  • Loading branch information
joaomoreno committed Jan 29, 2018
1 parent f13654c commit b338f57
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 47 deletions.
20 changes: 3 additions & 17 deletions build/gulpfile.vscode.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const BUNDLED_FILE_HEADER = [
' *--------------------------------------------------------*/'
].join('\n');

const languages = i18n.defaultLanguages.concat(process.env.VSCODE_QUALITY !== 'stable' ? i18n.extraLanguages: []);
const languages = i18n.defaultLanguages.concat(process.env.VSCODE_QUALITY !== 'stable' ? i18n.extraLanguages : []);

gulp.task('clean-optimized-vscode', util.rimraf('out-vscode'));
gulp.task('optimize-vscode', ['clean-optimized-vscode', 'compile-build', 'compile-extensions-build'], common.optimizeTask({
Expand Down Expand Up @@ -409,7 +409,7 @@ gulp.task('vscode-translations-push', function () {
gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()),
gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions())
).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken)
).pipe(i18n.pushXlfFiles(apiHostname, apiName, apiToken));
).pipe(i18n.pushXlfFiles(apiHostname, apiName, apiToken));
});

gulp.task('vscode-translations-push-test', function () {
Expand All @@ -422,7 +422,7 @@ gulp.task('vscode-translations-push-test', function () {
gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()),
gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions())
).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken)
).pipe(vfs.dest('../vscode-transifex-input'));
).pipe(vfs.dest('../vscode-transifex-input'));
});

gulp.task('vscode-translations-pull', function () {
Expand Down Expand Up @@ -597,17 +597,3 @@ gulp.task('generate-vscode-configuration', () => {
console.error(e.toString());
});
});

//#region Built-In Extensions
gulp.task('clean-builtin-extensions', util.rimraf('.build/builtInExtensions'));
gulp.task('download-builtin-extensions', ['clean-builtin-extensions'], function () {
const marketplaceExtensions = es.merge(...builtInExtensions.map(extension => {
return ext.fromMarketplace(extension.name, extension.version)
.pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`));
}));

return marketplaceExtensions
.pipe(util.setExecutableBit(['**/*.sh']))
.pipe(vfs.dest('.build/builtInExtensions'));
});
//#endregion
106 changes: 98 additions & 8 deletions build/lib/builtInExtensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,116 @@

const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');
const rimraf = require('rimraf');
const es = require('event-stream');
const rename = require('gulp-rename');
const vfs = require('vinyl-fs');
const ext = require('./extensions');
const util = require('gulp-util');

const root = path.dirname(path.dirname(__dirname));
const builtInExtensions = require('../builtInExtensions');
const controlFilePath = path.join(process.env['HOME'], '.vscode-oss-dev', 'extensions', 'control.json');

function getExtensionPath(extension) {
return path.join(root, '.build', 'builtInExtensions', extension.name);
}

function isUpToDate(extension) {
const packagePath = path.join(root, '.build', 'builtInExtensions', extension.name, 'package.json');
const packagePath = path.join(getExtensionPath(extension), 'package.json');

if (!fs.existsSync(packagePath)) {
return false;
}

const packageContents = fs.readFileSync(packagePath);

try {
const diskVersion = JSON.parse(packageContents).version;
return (diskVersion === extension.version);
} catch(err) {
} catch (err) {
return false;
}
}

const builtInExtensions = require('../builtInExtensions');
builtInExtensions.forEach((extension) => {
if (!isUpToDate(extension)) {
process.exit(1);
function syncMarketplaceExtension(extension) {
if (isUpToDate(extension)) {
util.log(util.colors.blue('[marketplace]'), `${extension.name}@${extension.version}`, util.colors.green('✔︎'));
return es.readArray([]);
}

rimraf.sync(getExtensionPath(extension));

return ext.fromMarketplace(extension.name, extension.version)
.pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`))
.pipe(vfs.dest('.build/builtInExtensions'))
.on('end', () => util.log(util.colors.blue('[marketplace]'), extension.name, util.colors.green('✔︎')));
}

function syncExtension(extension, controlState) {
switch (controlState) {
case 'disabled':
util.log(util.colors.blue('[disabled]'), util.colors.gray(extension.name));
rimraf.sync(getExtensionPath(extension));
return es.readArray([]);

case 'marketplace':
return syncMarketplaceExtension(extension);

default:
if (!fs.existsSync(controlState)) {
util.log(util.colors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but that path does not exist.`));
return es.readArray([]);

} else if (!fs.existsSync(path.join(controlState, 'package.json'))) {
util.log(util.colors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but there is no 'package.json' file in that directory.`));
return es.readArray([]);
}

util.log(util.colors.blue('[local]'), `${extension.name}: ${controlState}`, util.colors.green('✔︎'));
return es.readArray([]);
}
});
process.exit(0);
}

function readControlFile() {
try {
return JSON.parse(fs.readFileSync(controlFilePath, 'utf8'));
} catch (err) {
return {};
}
}

function writeControlFile(control) {
mkdirp.sync(path.dirname(controlFilePath));
fs.writeFileSync(controlFilePath, JSON.stringify(control, null, 2));
}

function main() {
util.log('Syncronizing built-in extensions...');
util.log('Control file:', controlFilePath);

const control = readControlFile();
const streams = [];

for (const extension of builtInExtensions) {
let controlState = control[extension.name] || 'marketplace';
control[extension.name] = controlState;

streams.push(syncExtension(extension, controlState));
}

writeControlFile(control);

es.merge(streams)
.on('error', err => {
console.error(err);
process.exit(1);
})
.on('end', () => {
util.log(`${streams.length} built-in extensions processed.`);
process.exit(0);
});
}

main();
3 changes: 1 addition & 2 deletions scripts/code.bat
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ set CODE=".build\electron\%NAMESHORT%"
node build\lib\electron.js
if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js electron

:: Get built-in extensions
:: Sync built-in extensions
node build\lib\builtInExtensions.js
if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js download-builtin-extensions

:: Build
if not exist out node .\node_modules\gulp\bin\gulp.js compile
Expand Down
4 changes: 2 additions & 2 deletions scripts/code.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ function code() {
# Get electron
node build/lib/electron.js || ./node_modules/.bin/gulp electron

# Get built-in extensions
node build/lib/builtInExtensions.js || ./node_modules/.bin/gulp download-builtin-extensions
# Sync built-in extensions
node build/lib/builtInExtensions.js

# Build
test -d out || ./node_modules/.bin/gulp compile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,25 @@ export class ExtensionScannerInput {
}
}

export interface IExtensionReference {
name: string;
path: string;
}

export interface IExtensionResolver {
resolveExtensions(): TPromise<IExtensionReference[]>;
}

class DefaultExtensionResolver implements IExtensionResolver {

constructor(private root: string) { }

resolveExtensions(): TPromise<IExtensionReference[]> {
return pfs.readDirsInDir(this.root)
.then(folders => folders.map(name => ({ name, path: join(this.root, name) })));
}
}

export class ExtensionScanner {

/**
Expand Down Expand Up @@ -318,10 +337,14 @@ export class ExtensionScanner {
/**
* Scan a list of extensions defined in `absoluteFolderPath`
*/
public static async scanExtensions(input: ExtensionScannerInput, log: ILog): TPromise<IExtensionDescription[]> {
public static async scanExtensions(input: ExtensionScannerInput, log: ILog, resolver?: IExtensionResolver): TPromise<IExtensionDescription[]> {
const absoluteFolderPath = input.absoluteFolderPath;
const isBuiltin = input.isBuiltin;

if (!resolver) {
resolver = new DefaultExtensionResolver(absoluteFolderPath);
}

try {
let obsolete: { [folderName: string]: boolean; } = {};
if (!isBuiltin) {
Expand All @@ -333,38 +356,35 @@ export class ExtensionScanner {
}
}

const rawFolders = await pfs.readDirsInDir(absoluteFolderPath);
let refs = await resolver.resolveExtensions();

// Ensure the same extension order
rawFolders.sort();
refs.sort((a, b) => a.name < b.name ? -1 : 1);

let folders: string[] = null;
if (isBuiltin) {
folders = rawFolders;
} else {
if (!isBuiltin) {
// TODO: align with extensionsService
const nonGallery: string[] = [];
const gallery: string[] = [];
const nonGallery: IExtensionReference[] = [];
const gallery: IExtensionReference[] = [];

rawFolders.forEach(folder => {
if (obsolete[folder]) {
refs.forEach(ref => {
if (obsolete[ref.name]) {
return;
}

const { id, version } = getIdAndVersionFromLocalExtensionId(folder);
const { id, version } = getIdAndVersionFromLocalExtensionId(ref.name);

if (!id || !version) {
nonGallery.push(folder);
nonGallery.push(ref);
} else {
gallery.push(folder);
gallery.push(ref);
}
});

folders = [...nonGallery, ...gallery];
refs = [...nonGallery, ...gallery];
}

const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
let extensionDescriptions = await TPromise.join(folders.map(f => this.scanExtension(input.ourVersion, log, join(absoluteFolderPath, f), isBuiltin, nlsConfig)));
let extensionDescriptions = await TPromise.join(refs.map(r => this.scanExtension(input.ourVersion, log, r.path, isBuiltin, nlsConfig)));
extensionDescriptions = extensionDescriptions.filter(item => item !== null);

if (!isBuiltin) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { IMessage, IExtensionDescription, IExtensionsStatus, IExtensionService,
import { IExtensionEnablementService, IExtensionIdentifier, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, BetterMergeId, BetterMergeDisabledNowKey } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionsRegistry, ExtensionPoint, IExtensionPointUser, ExtensionMessageCollector, IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistry';
import { ExtensionScanner, ILog, ExtensionScannerInput } from 'vs/workbench/services/extensions/electron-browser/extensionPoints';
import { ExtensionScanner, ILog, ExtensionScannerInput, IExtensionResolver, IExtensionReference } from 'vs/workbench/services/extensions/electron-browser/extensionPoints';
import { IMessageService, CloseAction } from 'vs/platform/message/common/message';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier';
import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/node/extHost.protocol';
Expand All @@ -46,6 +46,42 @@ import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol';
const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions'));
const ExtraDevSystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', '.build', 'builtInExtensions'));

interface IBuiltInExtension {
name: string;
version: string;
repo: string;
}

interface IBuiltInExtensionControl {
[name: string]: 'marketplace' | 'disabled' | string;
}

class ExtraBuiltInExtensionResolver implements IExtensionResolver {

constructor(private builtInExtensions: IBuiltInExtension[], private control: IBuiltInExtensionControl) { }

resolveExtensions(): TPromise<IExtensionReference[]> {
const result: IExtensionReference[] = [];

for (const ext of this.builtInExtensions) {
const controlState = this.control[ext.name] || 'marketplace';

switch (controlState) {
case 'disabled':
break;
case 'marketplace':
result.push({ name: ext.name, path: path.join(ExtraDevSystemExtensionsRoot, ext.name) });
break;
default:
result.push({ name: ext.name, path: controlState });
break;
}
}

return TPromise.as(result);
}
}

// Enable to see detailed message communication between window and extension host
const logExtensionHostCommunication = false;

Expand Down Expand Up @@ -632,7 +668,19 @@ export class ExtensionService extends Disposable implements IExtensionService {
let finalBuiltinExtensions: TPromise<IExtensionDescription[]> = builtinExtensions;

if (devMode) {
const extraBuiltinExtensions = ExtensionScanner.scanExtensions(new ExtensionScannerInput(version, commit, locale, devMode, ExtraDevSystemExtensionsRoot, true), log);
const builtInExtensionsFilePath = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'build', 'builtInExtensions.json'));
const builtInExtensions = pfs.readFile(builtInExtensionsFilePath, 'utf8')
.then<IBuiltInExtension[]>(raw => JSON.parse(raw));

const controlFilePath = path.join(process.env['HOME'], '.vscode-oss-dev', 'extensions', 'control.json');
const controlFile = pfs.readFile(controlFilePath, 'utf8')
.then<IBuiltInExtensionControl>(raw => JSON.parse(raw), () => ({} as any));

const input = new ExtensionScannerInput(version, commit, locale, devMode, ExtraDevSystemExtensionsRoot, true);
const extraBuiltinExtensions = TPromise.join([builtInExtensions, controlFile])
.then(([builtInExtensions, control]) => new ExtraBuiltInExtensionResolver(builtInExtensions, control))
.then(resolver => ExtensionScanner.scanExtensions(input, log, resolver));

finalBuiltinExtensions = TPromise.join([builtinExtensions, extraBuiltinExtensions]).then(([builtinExtensions, extraBuiltinExtensions]) => {
let resultMap: { [id: string]: IExtensionDescription; } = Object.create(null);
for (let i = 0, len = builtinExtensions.length; i < len; i++) {
Expand Down

0 comments on commit b338f57

Please sign in to comment.