From b338f57e763ec926869a026eb170f8ecd6155015 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 29 Jan 2018 12:23:43 +0100 Subject: [PATCH] built in extensions control file --- build/gulpfile.vscode.js | 20 +--- build/lib/builtInExtensions.js | 106 ++++++++++++++++-- scripts/code.bat | 3 +- scripts/code.sh | 4 +- .../electron-browser/extensionPoints.ts | 52 ++++++--- .../electron-browser/extensionService.ts | 52 ++++++++- 6 files changed, 190 insertions(+), 47 deletions(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 11f409c6ebf..1da37657bb9 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -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({ @@ -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 () { @@ -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 () { @@ -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 diff --git a/build/lib/builtInExtensions.js b/build/lib/builtInExtensions.js index 0a78f4ccffd..c744349c7ee 100644 --- a/build/lib/builtInExtensions.js +++ b/build/lib/builtInExtensions.js @@ -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(); diff --git a/scripts/code.bat b/scripts/code.bat index b23ee223546..018577357d7 100644 --- a/scripts/code.bat +++ b/scripts/code.bat @@ -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 diff --git a/scripts/code.sh b/scripts/code.sh index 7f52ded6fe5..0d9402a6eec 100755 --- a/scripts/code.sh +++ b/scripts/code.sh @@ -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 diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionPoints.ts b/src/vs/workbench/services/extensions/electron-browser/extensionPoints.ts index 1ce6fdca2c2..d79f768e150 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionPoints.ts @@ -289,6 +289,25 @@ export class ExtensionScannerInput { } } +export interface IExtensionReference { + name: string; + path: string; +} + +export interface IExtensionResolver { + resolveExtensions(): TPromise; +} + +class DefaultExtensionResolver implements IExtensionResolver { + + constructor(private root: string) { } + + resolveExtensions(): TPromise { + return pfs.readDirsInDir(this.root) + .then(folders => folders.map(name => ({ name, path: join(this.root, name) }))); + } +} + export class ExtensionScanner { /** @@ -318,10 +337,14 @@ export class ExtensionScanner { /** * Scan a list of extensions defined in `absoluteFolderPath` */ - public static async scanExtensions(input: ExtensionScannerInput, log: ILog): TPromise { + public static async scanExtensions(input: ExtensionScannerInput, log: ILog, resolver?: IExtensionResolver): TPromise { const absoluteFolderPath = input.absoluteFolderPath; const isBuiltin = input.isBuiltin; + if (!resolver) { + resolver = new DefaultExtensionResolver(absoluteFolderPath); + } + try { let obsolete: { [folderName: string]: boolean; } = {}; if (!isBuiltin) { @@ -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) { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 1f32a1e5386..020e3c73d30 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -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'; @@ -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 { + 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; @@ -632,7 +668,19 @@ export class ExtensionService extends Disposable implements IExtensionService { let finalBuiltinExtensions: TPromise = 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(raw => JSON.parse(raw)); + + const controlFilePath = path.join(process.env['HOME'], '.vscode-oss-dev', 'extensions', 'control.json'); + const controlFile = pfs.readFile(controlFilePath, 'utf8') + .then(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++) { -- GitLab