Merge vscode 1.67 (#20883)

* Fix initial build breaks from 1.67 merge (#2514)

* Update yarn lock files

* Update build scripts

* Fix tsconfig

* Build breaks

* WIP

* Update yarn lock files

* Misc breaks

* Updates to package.json

* Breaks

* Update yarn

* Fix breaks

* Breaks

* Build breaks

* Breaks

* Breaks

* Breaks

* Breaks

* Breaks

* Missing file

* Breaks

* Breaks

* Breaks

* Breaks

* Breaks

* Fix several runtime breaks (#2515)

* Missing files

* Runtime breaks

* Fix proxy ordering issue

* Remove commented code

* Fix breaks with opening query editor

* Fix post merge break

* Updates related to setup build and other breaks (#2516)

* Fix bundle build issues

* Update distro

* Fix distro merge and update build JS files

* Disable pipeline steps

* Remove stats call

* Update license name

* Make new RPM dependencies a warning

* Fix extension manager version checks

* Update JS file

* Fix a few runtime breaks

* Fixes

* Fix runtime issues

* Fix build breaks

* Update notebook tests (part 1)

* Fix broken tests

* Linting errors

* Fix hygiene

* Disable lint rules

* Bump distro

* Turn off smoke tests

* Disable integration tests

* Remove failing "activate" test

* Remove failed test assertion

* Disable other broken test

* Disable query history tests

* Disable extension unit tests

* Disable failing tasks
This commit is contained in:
Karl Burtram
2022-10-19 19:13:18 -07:00
committed by GitHub
parent 33c6daaea1
commit 8a3d08f0de
3738 changed files with 192313 additions and 107208 deletions

View File

@@ -12,7 +12,7 @@ export const IExtensionHostStarter = createDecorator<IExtensionHostStarter>('ext
export const ipcExtensionHostStarterChannelName = 'extensionHostStarter';
export interface IExtensionHostProcessOptions {
env: { [key: string]: string | undefined; };
env: { [key: string]: string | undefined };
detached: boolean;
execArgv: string[] | undefined;
silent: boolean;
@@ -24,11 +24,11 @@ export interface IExtensionHostStarter {
onDynamicStdout(id: string): Event<string>;
onDynamicStderr(id: string): Event<string>;
onDynamicMessage(id: string): Event<any>;
onDynamicError(id: string): Event<{ error: SerializedError; }>;
onDynamicError(id: string): Event<{ error: SerializedError }>;
onDynamicExit(id: string): Event<{ code: number; signal: string }>;
createExtensionHost(): Promise<{ id: string; }>;
start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number; }>;
createExtensionHost(): Promise<{ id: string }>;
start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number }>;
enableInspectPort(id: string): Promise<boolean>;
kill(id: string): Promise<void>;

View File

@@ -3,7 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isEqualOrParent, joinPath } from 'vs/base/common/resources';
import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import * as semver from 'vs/base/common/semver/semver';
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
export interface IParsedVersion {
hasCaret: boolean;
@@ -232,27 +237,109 @@ export function isValidVersion(_inputVersion: string | INormalizedVersion, _inpu
return true;
}
export interface IReducedExtensionDescription {
isBuiltin: boolean;
engines: {
vscode: string;
// {{SQL CARBON EDIT}}
azdata?: string;
};
main?: string;
}
type ProductDate = string | Date | undefined;
export function isValidExtensionVersion(version: string, date: ProductDate, extensionDesc: IReducedExtensionDescription, notices: string[]): boolean {
export function validateExtensionManifest(productVersion: string, productDate: ProductDate, extensionLocation: URI, extensionManifest: IExtensionManifest, extensionIsBuiltin: boolean): readonly [Severity, string][] {
const validations: [Severity, string][] = [];
if (typeof extensionManifest.publisher !== 'undefined' && typeof extensionManifest.publisher !== 'string') {
validations.push([Severity.Error, nls.localize('extensionDescription.publisher', "property publisher must be of type `string`.")]);
return validations;
}
if (typeof extensionManifest.name !== 'string') {
validations.push([Severity.Error, nls.localize('extensionDescription.name', "property `{0}` is mandatory and must be of type `string`", 'name')]);
return validations;
}
if (typeof extensionManifest.version !== 'string') {
validations.push([Severity.Error, nls.localize('extensionDescription.version', "property `{0}` is mandatory and must be of type `string`", 'version')]);
return validations;
}
if (!extensionManifest.engines) {
validations.push([Severity.Error, nls.localize('extensionDescription.engines', "property `{0}` is mandatory and must be of type `object`", 'engines')]);
return validations;
}
if (typeof extensionManifest.engines.vscode !== 'string') {
validations.push([Severity.Error, nls.localize('extensionDescription.engines.vscode', "property `{0}` is mandatory and must be of type `string`", 'engines.vscode')]);
return validations;
}
if (typeof extensionManifest.extensionDependencies !== 'undefined') {
if (!isStringArray(extensionManifest.extensionDependencies)) {
validations.push([Severity.Error, nls.localize('extensionDescription.extensionDependencies', "property `{0}` can be omitted or must be of type `string[]`", 'extensionDependencies')]);
return validations;
}
}
if (typeof extensionManifest.activationEvents !== 'undefined') {
if (!isStringArray(extensionManifest.activationEvents)) {
validations.push([Severity.Error, nls.localize('extensionDescription.activationEvents1', "property `{0}` can be omitted or must be of type `string[]`", 'activationEvents')]);
return validations;
}
if (typeof extensionManifest.main === 'undefined' && typeof extensionManifest.browser === 'undefined') {
validations.push([Severity.Error, nls.localize('extensionDescription.activationEvents2', "properties `{0}` and `{1}` must both be specified or must both be omitted", 'activationEvents', 'main')]);
return validations;
}
}
if (typeof extensionManifest.extensionKind !== 'undefined') {
if (typeof extensionManifest.main === 'undefined') {
validations.push([Severity.Warning, nls.localize('extensionDescription.extensionKind', "property `{0}` can be defined only if property `main` is also defined.", 'extensionKind')]);
// not a failure case
}
}
if (typeof extensionManifest.main !== 'undefined') {
if (typeof extensionManifest.main !== 'string') {
validations.push([Severity.Error, nls.localize('extensionDescription.main1', "property `{0}` can be omitted or must be of type `string`", 'main')]);
return validations;
} else {
const mainLocation = joinPath(extensionLocation, extensionManifest.main);
if (!isEqualOrParent(mainLocation, extensionLocation)) {
validations.push([Severity.Warning, nls.localize('extensionDescription.main2', "Expected `main` ({0}) to be included inside extension's folder ({1}). This might make the extension non-portable.", mainLocation.path, extensionLocation.path)]);
// not a failure case
}
}
if (typeof extensionManifest.activationEvents === 'undefined') {
validations.push([Severity.Error, nls.localize('extensionDescription.main3', "properties `{0}` and `{1}` must both be specified or must both be omitted", 'activationEvents', 'main')]);
return validations;
}
}
if (typeof extensionManifest.browser !== 'undefined') {
if (typeof extensionManifest.browser !== 'string') {
validations.push([Severity.Error, nls.localize('extensionDescription.browser1', "property `{0}` can be omitted or must be of type `string`", 'browser')]);
return validations;
} else {
const browserLocation = joinPath(extensionLocation, extensionManifest.browser);
if (!isEqualOrParent(browserLocation, extensionLocation)) {
validations.push([Severity.Warning, nls.localize('extensionDescription.browser2', "Expected `browser` ({0}) to be included inside extension's folder ({1}). This might make the extension non-portable.", browserLocation.path, extensionLocation.path)]);
// not a failure case
}
}
if (typeof extensionManifest.activationEvents === 'undefined') {
validations.push([Severity.Error, nls.localize('extensionDescription.browser3', "properties `{0}` and `{1}` must both be specified or must both be omitted", 'activationEvents', 'browser')]);
return validations;
}
}
if (extensionDesc.isBuiltin || typeof extensionDesc.main === 'undefined') {
if (!semver.valid(extensionManifest.version)) {
validations.push([Severity.Error, nls.localize('notSemver', "Extension version is not semver compatible.")]);
return validations;
}
const notices: string[] = [];
const isValid = isValidExtensionVersion(productVersion, productDate, extensionManifest, extensionIsBuiltin, notices);
if (!isValid) {
for (const notice of notices) {
validations.push([Severity.Error, notice]);
}
}
return validations;
}
export function isValidExtensionVersion(productVersion: string, productDate: ProductDate, extensionManifest: IExtensionManifest, extensionIsBuiltin: boolean, notices: string[]): boolean {
if (extensionIsBuiltin || (typeof extensionManifest.main === 'undefined' && typeof extensionManifest.browser === 'undefined')) {
// No version check for builtin or declarative extensions
return true;
}
// {{SQL CARBON EDIT}}
return extensionDesc.engines.azdata ? extensionDesc.engines.azdata === '*' || isVersionValid(version, date, extensionDesc.engines.azdata, notices) : true;
return extensionManifest.engines.azdata ? extensionManifest.engines.azdata === '*' || isVersionValid(productVersion, productDate, extensionManifest.engines.vscode, notices) : true;
}
// {{SQL CARBON EDIT}}
@@ -293,3 +380,15 @@ function isVersionValid(currentVersion: string, date: ProductDate, requestedVers
return true;
}
function isStringArray(arr: string[]): boolean {
if (!Array.isArray(arr)) {
return false;
}
for (let i = 0, len = arr.length; i < len; i++) {
if (typeof arr[i] !== 'string') {
return false;
}
}
return true;
}

View File

@@ -3,16 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Severity from 'vs/base/common/severity';
import * as strings from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { ExtensionKind } from 'vs/platform/environment/common/environment';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILocalization } from 'vs/platform/localizations/common/localizations';
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
export const MANIFEST_CACHE_FOLDER = 'CachedExtensions';
export const USER_MANIFEST_CACHE_FILE = 'user';
export const BUILTIN_MANIFEST_CACHE_FILE = 'builtin';
export const UNDEFINED_PUBLISHER = 'undefined_publisher';
export const ExtensionsPolicyKey = 'extensions.extensionsPolicy'; // {{SQL CARBON EDIT}} start
export enum ExtensionsPolicy {
allowAll = 'allowAll',
@@ -33,10 +34,10 @@ export interface IConfigurationProperty {
}
export interface IConfiguration {
id?: string,
order?: number,
title?: string,
properties: { [key: string]: IConfigurationProperty; };
id?: string;
order?: number;
title?: string;
properties: { [key: string]: IConfigurationProperty };
}
export interface IDebugger {
@@ -97,7 +98,7 @@ export interface IView {
export interface IColor {
id: string;
description: string;
defaults: { light: string, dark: string, highContrast: string };
defaults: { light: string; dark: string; highContrast: string };
}
export interface IWebviewEditor {
@@ -129,9 +130,9 @@ export interface IWalkthroughStep {
readonly title: string;
readonly description: string | undefined;
readonly media:
| { image: string | { dark: string, light: string, hc: string }, altText: string, markdown?: never, svg?: never }
| { markdown: string, image?: never, svg?: never }
| { svg: string, altText: string, markdown?: never, image?: never }
| { image: string | { dark: string; light: string; hc: string }; altText: string; markdown?: never; svg?: never }
| { markdown: string; image?: never; svg?: never }
| { svg: string; altText: string; markdown?: never; image?: never };
readonly completionEvents?: string[];
/** @deprecated use `completionEvents: 'onCommand:...'` */
readonly doneOn?: { command: string };
@@ -139,7 +140,7 @@ export interface IWalkthroughStep {
}
export interface IWalkthrough {
readonly id: string,
readonly id: string;
readonly title: string;
readonly description: string;
readonly steps: IWalkthroughStep[];
@@ -155,8 +156,28 @@ export interface IStartEntry {
readonly category: 'file' | 'folder' | 'notebook';
}
export interface INotebookEntry {
readonly type: string;
readonly displayName: string;
}
export interface INotebookRendererContribution {
readonly id: string;
readonly displayName: string;
readonly mimeTypes: string[];
}
export interface ITranslation {
id: string;
path: string;
}
export interface ILocalizationContribution {
languageId: string;
languageName?: string;
localizedLanguageName?: string;
translations: ITranslation[];
minimalTranslations?: { [key: string]: string };
}
export interface IExtensionContributions {
@@ -175,12 +196,13 @@ export interface IExtensionContributions {
viewsContainers?: { [location: string]: IViewContainer[] };
views?: { [location: string]: IView[] };
colors?: IColor[];
localizations?: ILocalization[];
localizations?: ILocalizationContribution[];
readonly customEditors?: readonly IWebviewEditor[];
readonly codeActions?: readonly ICodeActionContribution[];
authentication?: IAuthenticationContribution[];
walkthroughs?: IWalkthrough[];
startEntries?: IStartEntry[];
readonly notebooks?: INotebookEntry[];
readonly notebookRenderer?: INotebookRendererContribution[];
}
@@ -191,14 +213,13 @@ export interface IExtensionCapabilities {
export const ALL_EXTENSION_KINDS: readonly ExtensionKind[] = ['ui', 'workspace', 'web'];
export type ExtensionKind = 'ui' | 'workspace' | 'web';
export type LimitedWorkspaceSupportType = 'limited';
export type ExtensionUntrustedWorkspaceSupportType = boolean | LimitedWorkspaceSupportType;
export type ExtensionUntrustedWorkspaceSupport = { supported: true; } | { supported: false, description: string } | { supported: LimitedWorkspaceSupportType, description: string, restrictedConfigurations?: string[] };
export type ExtensionUntrustedWorkspaceSupport = { supported: true } | { supported: false; description: string } | { supported: LimitedWorkspaceSupportType; description: string; restrictedConfigurations?: string[] };
export type ExtensionVirtualWorkspaceSupportType = boolean | LimitedWorkspaceSupportType;
export type ExtensionVirtualWorkspaceSupport = boolean | { supported: true; } | { supported: false | LimitedWorkspaceSupportType, description: string };
export type ExtensionVirtualWorkspaceSupport = boolean | { supported: true } | { supported: false | LimitedWorkspaceSupportType; description: string };
export function getWorkspaceSupportTypeMessage(supportType: ExtensionUntrustedWorkspaceSupport | ExtensionVirtualWorkspaceSupport | undefined): string | undefined {
if (typeof supportType === 'object' && supportType !== null) {
@@ -244,45 +265,72 @@ export const EXTENSION_CATEGORIES = [
// 'Other',
];
export interface IExtensionManifest {
readonly name: string;
readonly displayName?: string;
readonly publisher: string;
readonly version: string;
readonly engines: { vscode: string; azdata?: string }; // {{SQL CARBON EDIT}} add field
readonly forceReload?: boolean; // {{SQL CARBON EDIT}} add field
readonly description?: string;
readonly main?: string;
readonly browser?: string;
readonly icon?: string;
readonly categories?: string[];
readonly keywords?: string[];
readonly activationEvents?: string[];
readonly extensionDependencies?: string[];
readonly extensionPack?: string[];
readonly extensionKind?: ExtensionKind | ExtensionKind[];
readonly contributes?: IExtensionContributions;
readonly repository?: { url: string; };
readonly bugs?: { url: string; };
readonly enableProposedApi?: boolean;
readonly api?: string;
readonly scripts?: { [key: string]: string; };
readonly capabilities?: IExtensionCapabilities;
export interface IRelaxedExtensionManifest {
name: string;
displayName?: string;
publisher: string;
version: string;
engines: { vscode: string; azdata?: string }; // {{SQL CARBON EDIT}} add field
forceReload?: boolean; // {{SQL CARBON EDIT}} add field
description?: string;
main?: string;
browser?: string;
icon?: string;
categories?: string[];
keywords?: string[];
activationEvents?: string[];
extensionDependencies?: string[];
extensionPack?: string[];
extensionKind?: ExtensionKind | ExtensionKind[];
contributes?: IExtensionContributions;
repository?: { url: string };
bugs?: { url: string };
enabledApiProposals?: readonly string[];
api?: string;
scripts?: { [key: string]: string };
capabilities?: IExtensionCapabilities;
}
export type IExtensionManifest = Readonly<IRelaxedExtensionManifest>;
export const enum ExtensionType {
System,
User
}
export const enum TargetPlatform {
WIN32_X64 = 'win32-x64',
WIN32_IA32 = 'win32-ia32',
WIN32_ARM64 = 'win32-arm64',
LINUX_X64 = 'linux-x64',
LINUX_ARM64 = 'linux-arm64',
LINUX_ARMHF = 'linux-armhf',
ALPINE_X64 = 'alpine-x64',
ALPINE_ARM64 = 'alpine-arm64',
DARWIN_X64 = 'darwin-x64',
DARWIN_ARM64 = 'darwin-arm64',
WEB = 'web',
UNIVERSAL = 'universal',
UNKNOWN = 'unknown',
UNDEFINED = 'undefined',
}
export interface IExtension {
readonly type: ExtensionType;
readonly isBuiltin: boolean;
readonly identifier: IExtensionIdentifier;
readonly manifest: IExtensionManifest;
readonly location: URI;
readonly targetPlatform: TargetPlatform;
readonly readmeUrl?: URI;
readonly changelogUrl?: URI;
readonly isValid: boolean;
readonly validations: readonly [Severity, string][];
}
/**
@@ -340,17 +388,20 @@ export class ExtensionIdentifier {
}
}
export interface IExtensionDescription extends IExtensionManifest {
readonly identifier: ExtensionIdentifier;
readonly uuid?: string;
readonly isBuiltin: boolean;
readonly isUserBuiltin: boolean;
readonly isUnderDevelopment: boolean;
readonly extensionLocation: URI;
enableProposedApi?: boolean;
export interface IRelaxedExtensionDescription extends IRelaxedExtensionManifest {
id?: string;
identifier: ExtensionIdentifier;
uuid?: string;
targetPlatform: TargetPlatform;
isBuiltin: boolean;
isUserBuiltin: boolean;
isUnderDevelopment: boolean;
extensionLocation: URI;
readonly forceReload?: boolean; // {{SQL CARBON EDIT}}
}
export type IExtensionDescription = Readonly<IRelaxedExtensionDescription>;
export function isLanguagePackExtension(manifest: IExtensionManifest): boolean {
return manifest.contributes && manifest.contributes.localizations ? manifest.contributes.localizations.length > 0 : false;
}
@@ -360,7 +411,7 @@ export function isAuthenticationProviderExtension(manifest: IExtensionManifest):
}
export function isResolverExtension(manifest: IExtensionManifest, remoteAuthority: string | undefined): boolean {
if (remoteAuthority && manifest.enableProposedApi) {
if (remoteAuthority) {
const activationEvent = `onResolveRemoteAuthority:${getRemoteName(remoteAuthority)}`;
return manifest.activationEvents?.indexOf(activationEvent) !== -1;
}

View File

@@ -0,0 +1,173 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { canceled, SerializedError } from 'vs/base/common/errors';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter';
import { Event } from 'vs/base/common/event';
import { FileAccess } from 'vs/base/common/network';
import { ILogService } from 'vs/platform/log/common/log';
import { Worker } from 'worker_threads';
import { IWorker, IWorkerCallback, IWorkerFactory, SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker';
import type { ExtensionHostStarter, IExtensionHostStarterWorkerHost } from 'vs/platform/extensions/node/extensionHostStarterWorker';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { StopWatch } from 'vs/base/common/stopwatch';
class NodeWorker implements IWorker {
private readonly _worker: Worker;
public readonly onError: Event<Error>;
public readonly onExit: Event<number>;
public readonly onMessageError: Event<Error>;
constructor(callback: IWorkerCallback, onErrorCallback: (err: any) => void) {
this._worker = new Worker(
FileAccess.asFileUri('vs/platform/extensions/node/extensionHostStarterWorkerMain.js', require).fsPath,
);
this._worker.on('message', callback);
this._worker.on('error', onErrorCallback);
this.onError = Event.fromNodeEventEmitter(this._worker, 'error');
this.onExit = Event.fromNodeEventEmitter(this._worker, 'exit');
this.onMessageError = Event.fromNodeEventEmitter(this._worker, 'messageerror');
}
getId(): number {
return 1;
}
postMessage(message: any, transfer: ArrayBuffer[]): void {
this._worker.postMessage(message, transfer);
}
dispose(): void {
this._worker.terminate();
}
}
class ExtensionHostStarterWorkerHost implements IExtensionHostStarterWorkerHost {
constructor(
@ILogService private readonly _logService: ILogService
) { }
public async logInfo(message: string): Promise<void> {
this._logService.info(message);
}
}
export class WorkerMainProcessExtensionHostStarter implements IDisposable, IExtensionHostStarter {
_serviceBrand: undefined;
private _proxy: ExtensionHostStarter | null;
private readonly _worker: SimpleWorkerClient<ExtensionHostStarter, IExtensionHostStarterWorkerHost>;
private _shutdown = false;
constructor(
@ILogService private readonly _logService: ILogService,
@ILifecycleMainService lifecycleMainService: ILifecycleMainService
) {
this._proxy = null;
const workerFactory: IWorkerFactory = {
create: (moduleId: string, callback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker => {
const worker = new NodeWorker(callback, onErrorCallback);
worker.onError((err) => {
this._logService.error(`ExtensionHostStarterWorker has encountered an error:`);
this._logService.error(err);
});
worker.onMessageError((err) => {
this._logService.error(`ExtensionHostStarterWorker has encountered a message error:`);
this._logService.error(err);
});
worker.onExit((exitCode) => this._logService.info(`ExtensionHostStarterWorker exited with code ${exitCode}.`));
worker.postMessage(moduleId, []);
return worker;
}
};
this._worker = new SimpleWorkerClient<ExtensionHostStarter, IExtensionHostStarterWorkerHost>(
workerFactory,
'vs/platform/extensions/node/extensionHostStarterWorker',
new ExtensionHostStarterWorkerHost(this._logService)
);
this._initialize();
// On shutdown: gracefully await extension host shutdowns
lifecycleMainService.onWillShutdown((e) => {
this._shutdown = true;
if (this._proxy) {
e.join(this._proxy.waitForAllExit(6000));
}
});
}
dispose(): void {
// Intentionally not killing the extension host processes
}
async _initialize(): Promise<void> {
this._proxy = await this._worker.getProxyObject();
this._logService.info(`ExtensionHostStarterWorker created`);
}
onDynamicStdout(id: string): Event<string> {
return this._proxy!.onDynamicStdout(id);
}
onDynamicStderr(id: string): Event<string> {
return this._proxy!.onDynamicStderr(id);
}
onDynamicMessage(id: string): Event<any> {
return this._proxy!.onDynamicMessage(id);
}
onDynamicError(id: string): Event<{ error: SerializedError }> {
return this._proxy!.onDynamicError(id);
}
onDynamicExit(id: string): Event<{ code: number; signal: string }> {
return this._proxy!.onDynamicExit(id);
}
async createExtensionHost(): Promise<{ id: string }> {
const proxy = await this._worker.getProxyObject();
if (this._shutdown) {
throw canceled();
}
return proxy.createExtensionHost();
}
async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number }> {
const sw = StopWatch.create(false);
const proxy = await this._worker.getProxyObject();
if (this._shutdown) {
throw canceled();
}
const timeout = setTimeout(() => {
this._logService.info(`ExtensionHostStarterWorker.start() did not return within 30s. This might be a problem.`);
}, 30000);
const result = await proxy.start(id, opts);
const duration = sw.elapsed();
this._logService.info(`ExtensionHostStarterWorker.start() took ${duration} ms.`);
clearTimeout(timeout);
return result;
}
async enableInspectPort(id: string): Promise<boolean> {
const proxy = await this._worker.getProxyObject();
if (this._shutdown) {
throw canceled();
}
return proxy.enableInspectPort(id);
}
async kill(id: string): Promise<void> {
const proxy = await this._worker.getProxyObject();
if (this._shutdown) {
throw canceled();
}
return proxy.kill(id);
}
}

View File

@@ -3,18 +3,22 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SerializedError, transformErrorForSerialization } from 'vs/base/common/errors';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter';
import { Emitter, Event } from 'vs/base/common/event';
import { ChildProcess, fork } from 'child_process';
import { FileAccess } from 'vs/base/common/network';
import { StringDecoder } from 'string_decoder';
import * as platform from 'vs/base/common/platform';
import { ILogService } from 'vs/platform/log/common/log';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { Promises, timeout } from 'vs/base/common/async';
import { SerializedError, transformErrorForSerialization } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { FileAccess } from 'vs/base/common/network';
import { mixin } from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
import { cwd } from 'vs/base/common/process';
import { StopWatch } from 'vs/base/common/stopwatch';
import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter';
export interface IExtensionHostStarterWorkerHost {
logInfo(message: string): Promise<void>;
}
class ExtensionHostProcess extends Disposable {
@@ -27,34 +31,36 @@ class ExtensionHostProcess extends Disposable {
readonly _onMessage = this._register(new Emitter<any>());
readonly onMessage = this._onMessage.event;
readonly _onError = this._register(new Emitter<{ error: SerializedError; }>());
readonly _onError = this._register(new Emitter<{ error: SerializedError }>());
readonly onError = this._onError.event;
readonly _onExit = this._register(new Emitter<{ pid: number; code: number; signal: string }>());
readonly onExit = this._onExit.event;
private _process: ChildProcess | null = null;
private _hasExited: boolean = false;
constructor(
public readonly id: string,
@ILogService private readonly _logService: ILogService
private readonly _host: IExtensionHostStarterWorkerHost
) {
super();
}
register(disposable: IDisposable) {
this._register(disposable);
}
start(opts: IExtensionHostProcessOptions): { pid: number; } {
start(opts: IExtensionHostProcessOptions): { pid: number } {
if (platform.isCI) {
this._host.logInfo(`Calling fork to start extension host...`);
}
const sw = StopWatch.create(false);
this._process = fork(
FileAccess.asFileUri('bootstrap-fork', require).fsPath,
['--type=extensionHost', '--skipWorkspaceStorageLock'],
mixin({ cwd: cwd() }, opts),
);
const pid = this._process.pid;
const forkTime = sw.elapsed();
const pid = this._process.pid!;
this._logService.info(`Starting extension host with pid ${pid}.`);
this._host.logInfo(`Starting extension host with pid ${pid} (fork() took ${forkTime} ms).`);
const stdoutDecoder = new StringDecoder('utf-8');
this._process.stdout?.on('data', (chunk) => {
@@ -77,6 +83,7 @@ class ExtensionHostProcess extends Disposable {
});
this._process.on('exit', (code: number, signal: string) => {
this._hasExited = true;
this._onExit.fire({ pid, code, signal });
});
@@ -88,7 +95,7 @@ class ExtensionHostProcess extends Disposable {
return false;
}
this._logService.info(`Enabling inspect port on extension host with pid ${this._process.pid}.`);
this._host.logInfo(`Enabling inspect port on extension host with pid ${this._process.pid}.`);
interface ProcessExt {
_debugProcess?(n: number): any;
@@ -96,7 +103,7 @@ class ExtensionHostProcess extends Disposable {
if (typeof (<ProcessExt>process)._debugProcess === 'function') {
// use (undocumented) _debugProcess feature of node
(<ProcessExt>process)._debugProcess!(this._process.pid);
(<ProcessExt>process)._debugProcess!(this._process.pid!);
return true;
} else if (!platform.isWindows) {
// use KILL USR1 on non-windows platforms (fallback)
@@ -112,9 +119,24 @@ class ExtensionHostProcess extends Disposable {
if (!this._process) {
return;
}
this._logService.info(`Killing extension host with pid ${this._process.pid}.`);
this._host.logInfo(`Killing extension host with pid ${this._process.pid}.`);
this._process.kill();
}
async waitForExit(maxWaitTimeMs: number): Promise<void> {
if (!this._process) {
return;
}
const pid = this._process.pid;
this._host.logInfo(`Waiting for extension host with pid ${pid} to exit.`);
await Promise.race([Event.toPromise(this.onExit), timeout(maxWaitTimeMs)]);
if (!this._hasExited) {
// looks like we timed out
this._host.logInfo(`Extension host with pid ${pid} did not exit within ${maxWaitTimeMs}ms.`);
this._process.kill();
}
}
}
export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter {
@@ -122,10 +144,10 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter
private static _lastId: number = 0;
private readonly _extHosts: Map<string, ExtensionHostProcess>;
protected readonly _extHosts: Map<string, ExtensionHostProcess>;
constructor(
@ILogService private readonly _logService: ILogService
private readonly _host: IExtensionHostStarterWorkerHost
) {
this._extHosts = new Map<string, ExtensionHostProcess>();
}
@@ -154,20 +176,20 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter
return this._getExtHost(id).onMessage;
}
onDynamicError(id: string): Event<{ error: SerializedError; }> {
onDynamicError(id: string): Event<{ error: SerializedError }> {
return this._getExtHost(id).onError;
}
onDynamicExit(id: string): Event<{ code: number; signal: string; }> {
onDynamicExit(id: string): Event<{ code: number; signal: string }> {
return this._getExtHost(id).onExit;
}
async createExtensionHost(): Promise<{ id: string; }> {
async createExtensionHost(): Promise<{ id: string }> {
const id = String(++ExtensionHostStarter._lastId);
const extHost = new ExtensionHostProcess(id, this._logService);
const extHost = new ExtensionHostProcess(id, this._host);
this._extHosts.set(id, extHost);
extHost.onExit(({ pid, code, signal }) => {
this._logService.info(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`);
this._host.logInfo(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`);
setTimeout(() => {
extHost.dispose();
this._extHosts.delete(id);
@@ -176,7 +198,7 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter
return { id };
}
async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number; }> {
async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number }> {
return this._getExtHost(id).start(opts);
}
@@ -196,6 +218,26 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter
}
extHostProcess.kill();
}
async killAllNow(): Promise<void> {
for (const [, extHost] of this._extHosts) {
extHost.kill();
}
}
async waitForAllExit(maxWaitTimeMs: number): Promise<void> {
const exitPromises: Promise<void>[] = [];
for (const [, extHost] of this._extHosts) {
exitPromises.push(extHost.waitForExit(maxWaitTimeMs));
}
return Promises.settled(exitPromises).then(() => { });
}
}
registerSingleton(IExtensionHostStarter, ExtensionHostStarter, true);
/**
* The `create` function needs to be there by convention because
* we are loaded via the `vs/base/common/worker/simpleWorker` utility.
*/
export function create(host: IExtensionHostStarterWorkerHost) {
return new ExtensionHostStarter(host);
}

View File

@@ -0,0 +1,66 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function () {
'use strict';
const loader = require('../../../loader');
const bootstrap = require('../../../../bootstrap');
const path = require('path');
const parentPort = require('worker_threads').parentPort;
// Bootstrap: NLS
const nlsConfig = bootstrap.setupNLS();
// Bootstrap: Loader
loader.config({
baseUrl: bootstrap.fileUriFromPath(path.join(__dirname, '../../../../'), { isWindows: process.platform === 'win32' }),
catchError: true,
nodeRequire: require,
nodeMain: __filename,
'vs/nls': nlsConfig,
amdModulesPattern: /^vs\//,
recordStats: true
});
let isFirstMessage = true;
let beforeReadyMessages: any[] = [];
const initialMessageHandler = (data: any) => {
if (!isFirstMessage) {
beforeReadyMessages.push(data);
return;
}
isFirstMessage = false;
loadCode(data);
};
parentPort.on('message', initialMessageHandler);
const loadCode = function (moduleId: string) {
loader([moduleId], function (ws: any) {
setTimeout(() => {
const messageHandler = ws.create((msg: any, transfer?: ArrayBuffer[]) => {
parentPort.postMessage(msg, transfer);
}, null);
parentPort.off('message', initialMessageHandler);
parentPort.on('message', (data: any) => {
messageHandler.onmessage(data);
});
while (beforeReadyMessages.length > 0) {
const msg = beforeReadyMessages.shift()!;
messageHandler.onmessage(msg);
}
});
}, (err: any) => console.error(err));
};
parentPort.on('messageerror', (err: Error) => {
console.error(err);
});
})();

View File

@@ -3,7 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { INormalizedVersion, IParsedVersion, IReducedExtensionDescription, isValidExtensionVersion, isValidVersion, isValidVersionStr, normalizeVersion, parseVersion } from 'vs/platform/extensions/common/extensionValidator';
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { INormalizedVersion, IParsedVersion, isValidExtensionVersion, isValidVersion, isValidVersionStr, normalizeVersion, parseVersion } from 'vs/platform/extensions/common/extensionValidator';
suite('Extension Version Validator', () => {
const productVersion = '2021-05-11T21:54:30.577Z';
@@ -208,17 +209,19 @@ suite('Extension Version Validator', () => {
test.skip('isValidExtensionVersion', () => { // {{SQL CARBON EDIT}} skip test
function testExtensionVersion(version: string, desiredVersion: string, isBuiltin: boolean, hasMain: boolean, expectedResult: boolean): void {
let desc: IReducedExtensionDescription = {
isBuiltin: isBuiltin,
const manifest: IExtensionManifest = {
name: 'test',
publisher: 'test',
version: '0.0.0',
engines: {
vscode: desiredVersion
},
main: hasMain ? 'something' : undefined
};
let reasons: string[] = [];
let actual = isValidExtensionVersion(version, productVersion, desc, reasons);
let actual = isValidExtensionVersion(version, productVersion, manifest, isBuiltin, reasons);
assert.strictEqual(actual, expectedResult, 'version: ' + version + ', desiredVersion: ' + desiredVersion + ', desc: ' + JSON.stringify(desc) + ', reasons: ' + JSON.stringify(reasons));
assert.strictEqual(actual, expectedResult, 'version: ' + version + ', desiredVersion: ' + desiredVersion + ', desc: ' + JSON.stringify(manifest) + ', reasons: ' + JSON.stringify(reasons));
}
function testIsInvalidExtensionVersion(version: string, desiredVersion: string, isBuiltin: boolean, hasMain: boolean): void {
@@ -403,4 +406,17 @@ suite('Extension Version Validator', () => {
testIsValidVersion('1.10.1', '^1.10.0-20200101', true); // before date, but ahead version
testIsValidVersion('1.11.0', '^1.10.0-20200101', true);
});
test.skip('isValidExtensionVersion checks browser only extensions', () => {
const manifest = {
name: 'test',
publisher: 'test',
version: '0.0.0',
engines: {
vscode: '^1.45.0'
},
browser: 'something'
};
assert.strictEqual(isValidExtensionVersion('1.44.0', undefined, manifest, false, []), false);
});
});