Adding ability to install SDKs

This commit is contained in:
Sarah Oslund 2021-02-09 13:20:08 -08:00
Родитель 23b4f9f190
Коммит a8c62d6401
24 изменённых файлов: 389 добавлений и 172 удалений

Просмотреть файл

@ -13,7 +13,7 @@
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
"${workspaceFolder}/dist/**/*.js"
],
"preLaunchTask": "compile-all"
},
@ -24,10 +24,10 @@
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test/functional"
"--extensionTestsPath=${workspaceFolder}/dist/test/functional"
],
"outFiles": [
"${workspaceFolder}/out/test/**/*.js"
"${workspaceFolder}/dist/test/**/*.js"
],
"internalConsoleOptions": "openOnSessionStart",
"preLaunchTask": "compile-all"

Просмотреть файл

@ -32,7 +32,7 @@ export class AcquisitionInvoker extends IAcquisitionInvoker {
public async installDotnet(installContext: IDotnetInstallationContext): Promise<void> {
const winOS = os.platform() === 'win32';
const installCommand = await this.getInstallCommand(installContext.version, installContext.installDir);
const installCommand = await this.getInstallCommand(installContext.version, installContext.installDir, installContext.installRuntime);
return new Promise<void>((resolve, reject) => {
try {
const windowsFullCommand = `powershell.exe -NoProfile -ExecutionPolicy unrestricted -Command "& { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 ; & ${installCommand} }`;
@ -75,12 +75,14 @@ export class AcquisitionInvoker extends IAcquisitionInvoker {
});
}
private async getInstallCommand(version: string, dotnetInstallDir: string): Promise<string> {
const args = [
private async getInstallCommand(version: string, dotnetInstallDir: string, installRuntime: boolean): Promise<string> {
let args = [
'-InstallDir', this.escapeFilePath(dotnetInstallDir),
'-Runtime', 'dotnet',
'-Version', version,
];
if (installRuntime) {
args = args.concat('-Runtime', 'dotnet');
}
const scriptPath = await this.scriptWorker.getDotnetInstallScriptPath();
return `${ this.escapeFilePath(scriptPath) } ${ args.join(' ') }`;

Просмотреть файл

@ -67,9 +67,15 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker
}
}
public async acquire(version: string): Promise<IDotnetAcquireResult> {
version = await this.context.versionResolver.getFullVersion(version);
public async acquireSDK(version: string): Promise<IDotnetAcquireResult> {
return this.acquire(version, false);
}
public async acquireRuntime(version: string): Promise<IDotnetAcquireResult> {
return this.acquire(version, true);
}
public async acquire(version: string, installRuntime: boolean): Promise<IDotnetAcquireResult> {
const existingAcquisitionPromise = this.acquisitionPromises[version];
if (existingAcquisitionPromise) {
// This version of dotnet is already being acquired. Memoize the promise.
@ -77,7 +83,7 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker
return existingAcquisitionPromise.then((res) => ({ dotnetPath: res }));
} else {
// We're the only one acquiring this version of dotnet, start the acquisition process.
const acquisitionPromise = this.acquireCore(version).catch((error: Error) => {
const acquisitionPromise = this.acquireCore(version, installRuntime).catch((error: Error) => {
delete this.acquisitionPromises[version];
throw new Error(`.NET Acquisition Failed: ${error.message}`);
});
@ -87,7 +93,7 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker
}
}
private async acquireCore(version: string): Promise<string> {
private async acquireCore(version: string, installRuntime: boolean): Promise<string> {
const installingVersions = this.context.extensionState.get<string[]>(this.installingVersionsKey, []);
const partialInstall = installingVersions.indexOf(version) >= 0;
if (partialInstall) {
@ -117,6 +123,7 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker
version,
dotnetPath,
timeoutValue: this.timeoutValue,
installRuntime,
} as IDotnetInstallationContext;
this.context.eventStream.post(new DotnetAcquisitionStarted(version));
await this.context.acquisitionInvoker.installDotnet(installContext).catch((reason) => {

Просмотреть файл

@ -6,14 +6,12 @@ import { Memento } from 'vscode';
import { IEventStream } from '../EventStream/EventStream';
import { IAcquisitionInvoker } from './IAcquisitionInvoker';
import { IInstallationValidator } from './IInstallationValidator';
import { IVersionResolver } from './IVersionResolver';
export interface IAcquisitionWorkerContext {
storagePath: string;
extensionState: Memento;
eventStream: IEventStream;
acquisitionInvoker: IAcquisitionInvoker;
versionResolver: IVersionResolver;
installationValidator: IInstallationValidator;
timeoutValue: number;
}

Просмотреть файл

@ -12,5 +12,7 @@ export interface IDotnetCoreAcquisitionWorker {
resolveExistingPath(existingPaths: IExistingPath[] | undefined, extensionId: string | undefined, windowDisplayWorker: IWindowDisplayWorker): IDotnetAcquireResult | undefined;
acquire(version: string): Promise<IDotnetAcquireResult>;
acquireRuntime(version: string): Promise<IDotnetAcquireResult>;
acquireSDK(version: string): Promise<IDotnetAcquireResult>;
}

Просмотреть файл

@ -8,4 +8,5 @@ export interface IDotnetInstallationContext {
version: string;
dotnetPath: string;
timeoutValue: number;
installRuntime: boolean;
}

Просмотреть файл

@ -4,5 +4,6 @@
* ------------------------------------------------------------------------------------------ */
export interface IVersionResolver {
getFullVersion(version: string): Promise<string>;
getFullRuntimeVersion(version: string): Promise<string>;
getFullSDKVersion(version: string): Promise<string>;
}

Просмотреть файл

@ -11,6 +11,7 @@ import { isNullOrUndefined } from 'util';
// {
// "channel-version": "X.X", --> Major.Minor version this channel represents
// "latest-runtime": "X.X.X", --> Most recently released full version of the runtime
// "latest-sdk": "X.X.X", --> Most recently released full version of the SDK
// ...
// },
// ...
@ -24,23 +25,25 @@ export class ReleasesResult {
throw new Error('Unable to resolve version: invalid releases data');
}
this.releasesIndex = releasesJson.map((channel: IReleasesChannel) => {
const [ channelVersion, latestRuntime ] = [ channel['channel-version'], channel['latest-runtime'] ];
const [ channelVersion, latestRuntime, latestSDK ] = [ channel['channel-version'], channel['latest-runtime'], channel['latest-sdk'] ];
if (isNullOrUndefined(channelVersion) || isNullOrUndefined(latestRuntime)) {
throw new Error('Unable to resolve version: invalid releases data');
}
return new ReleasesChannel(channelVersion, latestRuntime);
return new ReleasesChannel(channelVersion, latestRuntime, latestSDK);
});
}
}
export class ReleasesChannel {
constructor(public channelVersion: string,
public latestRuntime: string) { }
public latestRuntime: string,
public latestSDK: string) { }
}
interface IReleasesChannel {
['channel-version']: string;
['latest-runtime']: string;
['latest-sdk']: string;
}
type ReleaseChannels = IReleasesChannel[];

Просмотреть файл

@ -6,7 +6,10 @@ import * as semver from 'semver';
import { isNullOrUndefined } from 'util';
import { Memento } from 'vscode';
import { IEventStream } from '../EventStream/EventStream';
import { DotnetVersionResolutionCompleted, DotnetVersionResolutionError } from '../EventStream/EventStreamEvents';
import {
DotnetVersionResolutionCompleted,
DotnetVersionResolutionError,
} from '../EventStream/EventStreamEvents';
import { WebRequestWorker } from '../Utils/WebRequestWorker';
import { IVersionResolver } from './IVersionResolver';
import { ReleasesResult } from './ReleasesResult';
@ -21,15 +24,18 @@ export class VersionResolver implements IVersionResolver {
this.webWorker = new WebRequestWorker(extensionState, eventStream, this.releasesUrl, this.releasesKey);
}
public async getFullVersion(version: string): Promise<string> {
try {
const response = await this.webWorker.getCachedData();
if (!response) {
throw new Error('Unable to get the full version.');
}
public async getFullRuntimeVersion(version: string): Promise<string> {
return this.getFullVersion(version, true);
}
const releasesVersions = new ReleasesResult(response);
const versionResult = this.resolveVersion(version, releasesVersions);
public async getFullSDKVersion(version: string): Promise<string> {
return this.getFullVersion(version, false);
}
private async getFullVersion(version: string, runtimeVersion: boolean): Promise<string> {
try {
const releasesVersions = await this.getReleasesInfo();
const versionResult = this.resolveVersion(version, releasesVersions, runtimeVersion);
this.eventStream.post(new DotnetVersionResolutionCompleted(version, versionResult));
return versionResult;
} catch (error) {
@ -38,15 +44,15 @@ export class VersionResolver implements IVersionResolver {
}
}
private resolveVersion(version: string, releases: ReleasesResult): string {
private resolveVersion(version: string, releases: ReleasesResult, runtimeVersion: boolean): string {
this.validateVersionInput(version);
const channel = releases.releasesIndex.filter((channelVal) => channelVal.channelVersion === version);
if (isNullOrUndefined(channel) || channel.length !== 1) {
throw new Error(`Unable to resolve version: ${version}`);
}
const runtimeVersion = channel[0].latestRuntime;
return runtimeVersion;
const versionRes = runtimeVersion ? channel[0].latestRuntime : channel[0].latestSDK;
return versionRes;
}
private validateVersionInput(version: string) {
@ -55,4 +61,14 @@ export class VersionResolver implements IVersionResolver {
throw new Error(`Invalid version: ${version}`);
}
}
private async getReleasesInfo(): Promise<ReleasesResult> {
const response = await this.webWorker.getCachedData();
if (!response) {
throw new Error('Unable to get the full version.');
}
const releasesVersions = new ReleasesResult(response);
return releasesVersions;
}
}

Просмотреть файл

@ -2,60 +2,16 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as cp from 'child_process';
import open = require('open');
import * as os from 'os';
import * as vscode from 'vscode';
import { IDotnetEnsureDependenciesContext } from '..';
import { DotnetCoreDependencyInstaller } from '../Acquisition/DotnetCoreDependencyInstaller';
import { IDotnetCoreAcquisitionWorker } from '../Acquisition/IDotnetCoreAcquisitionWorker';
import { EventStream } from '../EventStream/EventStream';
import { DotnetAcquisitionMissingLinuxDependencies } from '../EventStream/EventStreamEvents';
import { IWindowDisplayWorker } from '../EventStream/IWindowDisplayWorker';
import { IDotnetUninstallContext } from '../IDotnetUninstallContext';
import { AcquireErrorConfiguration, callWithErrorHandling } from '../Utils/ErrorHandler';
import { IExtensionConfigurationWorker } from '../Utils/IExtensionConfigurationWorker';
import { AcquireErrorConfiguration } from '../Utils/ErrorHandler';
import { formatIssueUrl } from '../Utils/IssueReporter';
import { commandKeys, ICommand, ICommandProvider, IssueContextCallback } from './ICommandProvider';
import { commandKeys, ICommand, ICommandProvider, IExtensionCommandContext, IssueContextCallback } from './ICommandProvider';
export abstract class BaseCommandProvider implements ICommandProvider {
public abstract GetExtensionCommands(acquisitionWorker: IDotnetCoreAcquisitionWorker,
extensionConfigWorker: IExtensionConfigurationWorker,
displayWorker: IWindowDisplayWorker,
eventStream: EventStream,
issueContext: IssueContextCallback): ICommand[];
public abstract GetExtensionCommands(context: IExtensionCommandContext): ICommand[];
// Shared commands
protected getUninstallAllCommand(acquisitionWorker: IDotnetCoreAcquisitionWorker, issueContext: IssueContextCallback): ICommand {
return {
name: commandKeys.uninstallAll,
callback: async (commandContext: IDotnetUninstallContext | undefined) => {
await callWithErrorHandling(() => acquisitionWorker.uninstallAll(), issueContext(commandContext ? commandContext.errorConfiguration : undefined, 'uninstallAll'));
},
};
}
protected getEnsureDependenciesCommand(eventStream: EventStream, issueContext: IssueContextCallback): ICommand {
return {
name: commandKeys.ensureDotnetDependencies,
callback: async (commandContext: IDotnetEnsureDependenciesContext) => {
await callWithErrorHandling(async () => {
if (os.platform() !== 'linux') {
// We can't handle installing dependencies for anything other than Linux
return;
}
const result = cp.spawnSync(commandContext.command, commandContext.arguments);
const installer = new DotnetCoreDependencyInstaller();
if (installer.signalIndicatesMissingLinuxDependencies(result.signal)) {
eventStream.post(new DotnetAcquisitionMissingLinuxDependencies());
await installer.promptLinuxDependencyInstall('Failed to run .NET runtime.');
}
}, issueContext(commandContext.errorConfiguration, 'ensureDependencies'));
},
};
}
protected getReportIssueCommand(issueContext: IssueContextCallback): ICommand {
return {
name: commandKeys.reportIssue,

Просмотреть файл

@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { IDotnetCoreAcquisitionWorker } from '../Acquisition/IDotnetCoreAcquisitionWorker';
import { IVersionResolver } from '../Acquisition/IVersionResolver';
import { EventStream } from '../EventStream/EventStream';
import { IWindowDisplayWorker } from '../EventStream/IWindowDisplayWorker';
import { ErrorConfiguration } from '../Utils/ErrorHandler';
@ -25,10 +26,15 @@ export namespace commandKeys {
export const reportIssue = 'reportIssue';
}
export interface ICommandProvider {
GetExtensionCommands(acquisitionWorker: IDotnetCoreAcquisitionWorker,
extensionConfigWorker: IExtensionConfigurationWorker,
displayWorker: IWindowDisplayWorker,
eventStream: EventStream,
issueContext: IssueContextCallback): ICommand[];
export interface IExtensionCommandContext {
acquisitionWorker: IDotnetCoreAcquisitionWorker;
extensionConfigWorker: IExtensionConfigurationWorker;
displayWorker: IWindowDisplayWorker;
versionResolver: IVersionResolver;
eventStream: EventStream;
issueContext: IssueContextCallback;
}
export interface ICommandProvider {
GetExtensionCommands(context: IExtensionCommandContext): ICommand[];
}

Просмотреть файл

@ -2,34 +2,37 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as cp from 'child_process';
import * as os from 'os';
import { DotnetCoreDependencyInstaller } from '../Acquisition/DotnetCoreDependencyInstaller';
import { IDotnetCoreAcquisitionWorker } from '../Acquisition/IDotnetCoreAcquisitionWorker';
import { IVersionResolver } from '../Acquisition/IVersionResolver';
import { EventStream } from '../EventStream/EventStream';
import { DotnetAcquisitionRequested, DotnetExistingPathResolutionCompleted } from '../EventStream/EventStreamEvents';
import { DotnetAcquisitionMissingLinuxDependencies, DotnetAcquisitionRequested, DotnetExistingPathResolutionCompleted } from '../EventStream/EventStreamEvents';
import { IWindowDisplayWorker } from '../EventStream/IWindowDisplayWorker';
import { IDotnetAcquireContext } from '../IDotnetAcquireContext';
import { IDotnetAcquireResult } from '../IDotnetAcquireResult';
import { IDotnetEnsureDependenciesContext } from '../IDotnetEnsureDependenciesContext';
import { IDotnetUninstallContext } from '../IDotnetUninstallContext';
import { callWithErrorHandling } from '../Utils/ErrorHandler';
import { IExtensionConfigurationWorker } from '../Utils/IExtensionConfigurationWorker';
import { BaseCommandProvider } from './BaseCommandProvider';
import { commandKeys, ICommand, IssueContextCallback } from './ICommandProvider';
import { commandKeys, ICommand, IExtensionCommandContext, IssueContextCallback } from './ICommandProvider';
export class RuntimeCommandProvider extends BaseCommandProvider {
public GetExtensionCommands(acquisitionWorker: IDotnetCoreAcquisitionWorker,
extensionConfigWorker: IExtensionConfigurationWorker,
displayWorker: IWindowDisplayWorker,
eventStream: EventStream,
issueContext: IssueContextCallback): ICommand[] {
public GetExtensionCommands(context: IExtensionCommandContext): ICommand[] {
return [
this.getUninstallAllCommand(acquisitionWorker, issueContext),
this.getEnsureDependenciesCommand(eventStream, issueContext),
this.getReportIssueCommand(issueContext),
this.getAcquireCommand(acquisitionWorker, extensionConfigWorker, displayWorker, eventStream, issueContext),
this.getUninstallAllCommand(context.acquisitionWorker, context.issueContext),
this.getEnsureDependenciesCommand(context.eventStream, context.issueContext),
this.getReportIssueCommand(context.issueContext),
this.getAcquireCommand(context.acquisitionWorker, context.extensionConfigWorker, context.displayWorker, context.versionResolver, context.eventStream, context.issueContext),
];
}
private getAcquireCommand(acquisitionWorker: IDotnetCoreAcquisitionWorker,
extensionConfigWorker: IExtensionConfigurationWorker,
displayWorker: IWindowDisplayWorker,
versionResolver: IVersionResolver,
eventStream: EventStream,
issueContext: IssueContextCallback): ICommand {
return {
@ -50,10 +53,41 @@ export class RuntimeCommandProvider extends BaseCommandProvider {
});
}
return acquisitionWorker.acquire(commandContext.version);
const version = await versionResolver.getFullRuntimeVersion(commandContext.version);
return acquisitionWorker.acquireRuntime(version);
}, issueContext(commandContext.errorConfiguration, 'acquire', commandContext.version), commandContext.requestingExtensionId);
return dotnetPath;
},
};
}
private getUninstallAllCommand(acquisitionWorker: IDotnetCoreAcquisitionWorker, issueContext: IssueContextCallback): ICommand {
return {
name: commandKeys.uninstallAll,
callback: async (commandContext: IDotnetUninstallContext | undefined) => {
await callWithErrorHandling(async () => acquisitionWorker.uninstallAll(), issueContext(commandContext ? commandContext.errorConfiguration : undefined, 'uninstallAll'));
},
};
}
private getEnsureDependenciesCommand(eventStream: EventStream, issueContext: IssueContextCallback): ICommand {
return {
name: commandKeys.ensureDotnetDependencies,
callback: async (commandContext: IDotnetEnsureDependenciesContext) => {
await callWithErrorHandling(async () => {
if (os.platform() !== 'linux') {
// We can't handle installing dependencies for anything other than Linux
return;
}
const result = cp.spawnSync(commandContext.command, commandContext.arguments);
const installer = new DotnetCoreDependencyInstaller();
if (installer.signalIndicatesMissingLinuxDependencies(result.signal)) {
eventStream.post(new DotnetAcquisitionMissingLinuxDependencies());
await installer.promptLinuxDependencyInstall('Failed to run .NET runtime.');
}
}, issueContext(commandContext.errorConfiguration, 'ensureDependencies'));
},
};
}
}

Просмотреть файл

@ -2,51 +2,70 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { DotnetCoreAcquisitionWorker } from '../Acquisition/DotnetCoreAcquisitionWorker';
import * as vscode from 'vscode';
import { IDotnetCoreAcquisitionWorker } from '../Acquisition/IDotnetCoreAcquisitionWorker';
import { IVersionResolver } from '../Acquisition/IVersionResolver';
import { EventStream } from '../EventStream/EventStream';
import { DotnetAcquisitionRequested } from '../EventStream/EventStreamEvents';
import { IWindowDisplayWorker } from '../EventStream/IWindowDisplayWorker';
import { IDotnetAcquireContext } from '../IDotnetAcquireContext';
import { IDotnetAcquireResult } from '../IDotnetAcquireResult';
import { IDotnetUninstallContext } from '../IDotnetUninstallContext';
import { callWithErrorHandling } from '../Utils/ErrorHandler';
import { IExtensionConfigurationWorker } from '../Utils/IExtensionConfigurationWorker';
import { BaseCommandProvider } from './BaseCommandProvider';
import { commandKeys, ICommand, IssueContextCallback } from './ICommandProvider';
import { commandKeys, ICommand, IExtensionCommandContext, IssueContextCallback } from './ICommandProvider';
export class SDKCommandProvider extends BaseCommandProvider {
public GetExtensionCommands(acquisitionWorker: DotnetCoreAcquisitionWorker,
extensionConfigWorker: IExtensionConfigurationWorker,
displayWorker: IWindowDisplayWorker,
eventStream: EventStream,
issueContext: IssueContextCallback): ICommand[] {
public GetExtensionCommands(context: IExtensionCommandContext): ICommand[] {
return [
this.getUninstallAllCommand(acquisitionWorker, issueContext),
this.getEnsureDependenciesCommand(eventStream, issueContext),
this.getReportIssueCommand(issueContext),
this.getAcquireCommand(acquisitionWorker, displayWorker, eventStream, issueContext),
this.getUninstallAllCommand(context.acquisitionWorker, context.displayWorker, context.issueContext),
this.getReportIssueCommand(context.issueContext),
this.getAcquireCommand(context.acquisitionWorker, context.displayWorker, context.versionResolver, context.eventStream, context.issueContext),
];
}
private getAcquireCommand(acquisitionWorker: DotnetCoreAcquisitionWorker,
protected getUninstallAllCommand(acquisitionWorker: IDotnetCoreAcquisitionWorker,
displayWorker: IWindowDisplayWorker,
issueContext: IssueContextCallback): ICommand {
return {
name: commandKeys.uninstallAll,
callback: async (commandContext: IDotnetUninstallContext | undefined) => {
await callWithErrorHandling(async () => {
await acquisitionWorker.uninstallAll();
displayWorker.showInformationMessage('All VS Code copies of the .NET SDK uninstalled.', () => { /* No callback needed */ });
}, issueContext(commandContext ? commandContext.errorConfiguration : undefined, 'uninstallAll'));
},
};
}
private getAcquireCommand(acquisitionWorker: IDotnetCoreAcquisitionWorker,
displayWorker: IWindowDisplayWorker,
versionResolver: IVersionResolver,
eventStream: EventStream,
issueContext: IssueContextCallback): ICommand {
return {
name: commandKeys.acquire,
callback: async (commandContext: IDotnetAcquireContext) => {
const dotnetPath = await callWithErrorHandling<Promise<IDotnetAcquireResult>>(async () => {
eventStream.post(new DotnetAcquisitionRequested(commandContext.version, commandContext.requestingExtensionId));
callback: async (commandContext?: IDotnetAcquireContext) => {
return callWithErrorHandling(async () => {
let version: string | undefined = commandContext ? commandContext.version : undefined;
if (!version) {
version = await vscode.window.showInputBox({
placeHolder: '5.0',
value: '5.0',
prompt: '.NET version, i.e. 5.0',
});
}
if (!version) {
displayWorker.showErrorMessage('No .NET SDK version provided', () => { /* No callback needed */ });
return undefined;
}
if (!commandContext.version || commandContext.version === 'latest') {
throw new Error(`Cannot acquire .NET SDK version "${commandContext.version}". Please provide a valid version.`);
}
return acquisitionWorker.acquire(commandContext.version);
}, issueContext(commandContext.errorConfiguration, 'acquire', commandContext.version), commandContext.requestingExtensionId);
// TODO add to PATH instead
displayWorker.showWarningMessage(`SDK installed: ${dotnetPath}`, () => { /* No callback */ },
);
eventStream.post(new DotnetAcquisitionRequested(version!));
const resolvedVersion = await versionResolver.getFullSDKVersion(version!);
const dotnetPath = await acquisitionWorker.acquireSDK(resolvedVersion);
displayWorker.showInformationMessage(`.NET SDK ${version} installed to ${dotnetPath.dotnetPath}`, () => { /* No callback needed */ });
// TODO add to PATH?
return dotnetPath;
}, issueContext(undefined, 'acquireSDK'));
},
};
}

Просмотреть файл

@ -38,7 +38,7 @@ export class OutputChannelObserver implements IEventStreamObserver {
}
const startVersionString = this.inProgressDownloads.join(', ');
this.outputChannel.append(`Downloading .NET runtime version(s) ${startVersionString} ...`);
this.outputChannel.append(`Downloading .NET version(s) ${startVersionString} ...`);
break;
case EventType.DotnetAcquisitionCompleted:
const acquisitionCompleted = event as DotnetAcquisitionCompleted;
@ -50,7 +50,7 @@ export class OutputChannelObserver implements IEventStreamObserver {
if (this.inProgressDownloads.length > 0) {
const completedVersionString = `'${this.inProgressDownloads.join('\', \'')}'`;
this.outputChannel.append(`Still downloading .NET runtime version(s) ${completedVersionString} ...`);
this.outputChannel.append(`Still downloading .NET version(s) ${completedVersionString} ...`);
} else {
this.stopDownloadIndicator();
}
@ -64,7 +64,7 @@ export class OutputChannelObserver implements IEventStreamObserver {
const error = event as DotnetAcquisitionError;
this.outputChannel.appendLine(' Error!');
if (error instanceof DotnetAcquisitionVersionError) {
this.outputChannel.appendLine(`Failed to download .NET runtime ${error.version}:`);
this.outputChannel.appendLine(`Failed to download .NET ${error.version}:`);
}
this.outputChannel.appendLine(error.error.message);
this.outputChannel.appendLine('');
@ -75,7 +75,7 @@ export class OutputChannelObserver implements IEventStreamObserver {
if (this.inProgressDownloads.length > 0) {
const errorVersionString = this.inProgressDownloads.join(', ');
this.outputChannel.append(`Still downloading .NET runtime version(s) ${errorVersionString} ...`);
this.outputChannel.append(`Still downloading .NET version(s) ${errorVersionString} ...`);
} else {
this.stopDownloadIndicator();
}

Просмотреть файл

@ -20,13 +20,13 @@ export class StatusBarObserver implements IEventStreamObserver {
public post(event: IEvent): void {
switch (event.type) {
case EventType.DotnetAcquisitionStart:
this.setAndShowStatusBar('$(cloud-download) Downloading .NET runtime...', 'dotnet.showAcquisitionLog', '', 'Downloading .NET runtime...');
this.setAndShowStatusBar('$(cloud-download) Downloading .NET...', 'dotnet.showAcquisitionLog', '', 'Downloading .NET...');
break;
case EventType.DotnetAcquisitionCompleted:
this.resetAndHideStatusBar();
break;
case EventType.DotnetAcquisitionError:
this.setAndShowStatusBar('$(alert) Error acquiring .NET runtime!', 'dotnet.showAcquisitionLog', StatusBarColors.Red, 'Error acquiring .NET runtime');
this.setAndShowStatusBar('$(alert) Error acquiring .NET!', 'dotnet.showAcquisitionLog', StatusBarColors.Red, 'Error acquiring .NET');
break;
}
}

Просмотреть файл

@ -12,6 +12,7 @@ export interface IExtensionContext {
displayChannelName: string;
defaultTimeoutValue: number;
commandProvider: ICommandProvider;
storagePath?: string;
telemetryReporter?: ITelemetryReporter;
extensionConfiguration?: IExtensionConfiguration;
displayWorker?: IWindowDisplayWorker;

Просмотреть файл

@ -9,7 +9,7 @@ import { AcquisitionInvoker } from './Acquisition/AcquisitionInvoker';
import { DotnetCoreAcquisitionWorker } from './Acquisition/DotnetCoreAcquisitionWorker';
import { InstallationValidator } from './Acquisition/InstallationValidator';
import { VersionResolver } from './Acquisition/VersionResolver';
import { commandKeys } from './Commands/ICommandProvider';
import { commandKeys, IExtensionCommandContext } from './Commands/ICommandProvider';
import { EventStream } from './EventStream/EventStream';
import { IEventStreamObserver } from './EventStream/IEventStreamObserver';
import { LoggingObserver } from './EventStream/LoggingObserver';
@ -76,15 +76,15 @@ export function activate(context: vscode.ExtensionContext, extensionId: string,
} as IIssueContext;
};
const timeoutValue = extensionConfiguration.get<number>(configKeys.installTimeoutValue);
if (!fs.existsSync(context.globalStoragePath)) {
fs.mkdirSync(context.globalStoragePath);
const storagePath = extensionContext.storagePath ? extensionContext.storagePath : context.globalStoragePath;
if (!fs.existsSync(storagePath)) {
fs.mkdirSync(storagePath);
}
const acquisitionWorker = new DotnetCoreAcquisitionWorker({
storagePath: context.globalStoragePath,
storagePath,
extensionState: context.globalState,
eventStream,
acquisitionInvoker: new AcquisitionInvoker(context.globalState, eventStream),
versionResolver: new VersionResolver(context.globalState, eventStream), // TODO or sdk version resolver
installationValidator: new InstallationValidator(eventStream),
timeoutValue: timeoutValue === undefined ? extensionContext.defaultTimeoutValue : timeoutValue,
});
@ -92,7 +92,15 @@ export function activate(context: vscode.ExtensionContext, extensionId: string,
const showOutputChannelRegistration = vscode.commands.registerCommand(`${extensionContext.commandPrefix}.${commandKeys.showAcquisitionLog}`, () => outputChannel.show(/* preserveFocus */ false));
context.subscriptions.push(showOutputChannelRegistration);
const commands = extensionContext.commandProvider.GetExtensionCommands(acquisitionWorker, extensionConfigWorker, displayWorker, eventStream, issueContext);
const commandContext = {
acquisitionWorker,
extensionConfigWorker,
displayWorker,
versionResolver: new VersionResolver(context.globalState, eventStream),
eventStream,
issueContext,
} as IExtensionCommandContext;
const commands = extensionContext.commandProvider.GetExtensionCommands(commandContext);
for (const command of commands) {
const registration = vscode.commands.registerCommand(`${extensionContext.commandPrefix}.${command.name}`, command.callback);
context.subscriptions.push(registration);

Просмотреть файл

@ -19,10 +19,8 @@ import {
MockEventStream,
MockExtensionContext,
MockInstallationValidator,
MockVersionResolver,
NoInstallAcquisitionInvoker,
RejectingAcquisitionInvoker,
versionPairs,
} from '../mocks/MockObjects';
const assert = chai.assert;
chai.use(chaiAsPromised);
@ -39,7 +37,6 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function() {
extensionState: context,
eventStream,
acquisitionInvoker: new NoInstallAcquisitionInvoker(eventStream),
versionResolver: new MockVersionResolver(context, eventStream),
installationValidator: new MockInstallationValidator(eventStream),
timeoutValue: 10,
});
@ -84,20 +81,27 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function() {
process.env._VSCODE_DOTNET_INSTALL_FOLDER = dotnetFolderName;
});
test('Acquire Version', async () => {
test('Acquire Runtime Version', async () => {
const [acquisitionWorker, eventStream, context] = getTestAcquisitionWorker();
const result = await acquisitionWorker.acquire(versionPairs[0][0]);
await assertAcquisitionSucceeded(versionPairs[0][1], result.dotnetPath, eventStream, context);
const result = await acquisitionWorker.acquireRuntime('1.0');
await assertAcquisitionSucceeded('1.0', result.dotnetPath, eventStream, context);
});
test('Acquire Version Multiple Times', async () => {
test('Acquire SDK Version', async () => {
const [acquisitionWorker, eventStream, context] = getTestAcquisitionWorker();
const result = await acquisitionWorker.acquireSDK('5.0');
await assertAcquisitionSucceeded('5.0', result.dotnetPath, eventStream, context);
});
test('Acquire Runtime Version Multiple Times', async () => {
const numAcquisitions = 3;
const [acquisitionWorker, eventStream, context] = getTestAcquisitionWorker();
for (let i = 0; i < numAcquisitions; i++) {
const pathResult = await acquisitionWorker.acquire(versionPairs[0][0]);
await assertAcquisitionSucceeded(versionPairs[0][1], pathResult.dotnetPath, eventStream, context);
const pathResult = await acquisitionWorker.acquireRuntime('1.0');
await assertAcquisitionSucceeded('1.0', pathResult.dotnetPath, eventStream, context);
}
// AcquisitionInvoker was only called once
@ -107,20 +111,21 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function() {
test('Acquire Multiple Versions and UninstallAll', async () => {
        const [acquisitionWorker, eventStream, context] = getTestAcquisitionWorker();
        for (const version of versionPairs) {
            const res = await acquisitionWorker.acquire(version[0]);
            await assertAcquisitionSucceeded(version[1], res.dotnetPath, eventStream, context);
const versions = [ '1.0', '1.1', '2.0', '2.1', '2.2' ];
        for (const version of versions) {
            const res = await acquisitionWorker.acquireRuntime(version);
            await assertAcquisitionSucceeded(version, res.dotnetPath, eventStream, context);
        }
        await acquisitionWorker.uninstallAll();
        assert.exists(eventStream.events.find(event => event instanceof DotnetUninstallAllStarted));
        assert.exists(eventStream.events.find(event => event instanceof DotnetUninstallAllCompleted));
    });
test('Acquire and UninstallAll', async () => {
test('Acquire Runtime and UninstallAll', async () => {
const [acquisitionWorker, eventStream, context] = getTestAcquisitionWorker();
const res = await acquisitionWorker.acquire(versionPairs[0][0]);
await assertAcquisitionSucceeded(versionPairs[0][1], res.dotnetPath, eventStream, context);
const res = await acquisitionWorker.acquireRuntime('1.0');
await assertAcquisitionSucceeded('1.0', res.dotnetPath, eventStream, context);
await acquisitionWorker.uninstallAll();
assert.exists(eventStream.events.find(event => event instanceof DotnetUninstallAllStarted));
@ -130,9 +135,9 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function() {
test('Repeated Acquisition', async () => {
const [acquisitionWorker, eventStream, context] = getTestAcquisitionWorker();
for (let i = 0; i < 3; i ++) {
await acquisitionWorker.acquire(versionPairs[0][0]);
await acquisitionWorker.acquireRuntime('1.0');
}
// We should only actually acquire once
// We should only actually Acquire once
const events = eventStream.events.filter(event => event instanceof DotnetAcquisitionStarted);
assert.equal(events.length, 1);
});
@ -145,11 +150,10 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function() {
extensionState: context,
eventStream,
acquisitionInvoker: new RejectingAcquisitionInvoker(eventStream),
versionResolver: new MockVersionResolver(context, eventStream),
installationValidator: new MockInstallationValidator(eventStream),
timeoutValue: 10,
});
return assert.isRejected(acquisitionWorker.acquire(versionPairs[0][0]), '.NET Acquisition Failed: Installation failed: Rejecting message');
return assert.isRejected(acquisitionWorker.acquireRuntime('1.0'), '.NET Acquisition Failed: Installation failed: Rejecting message');
});
});

Просмотреть файл

@ -14,20 +14,24 @@ suite('VersionResolver Unit Tests', () => {
const resolver: MockVersionResolver = new MockVersionResolver(context, eventStream);
test('Error With Invalid Version', async () => {
return assert.isRejected(resolver.getFullVersion('foo'), Error, 'Invalid version');
return assert.isRejected(resolver.getFullRuntimeVersion('foo'), Error, 'Invalid version');
});
test('Error With Three Part Version', async () => {
return assert.isRejected(resolver.getFullVersion('1.0.16'), Error, 'Invalid version');
return assert.isRejected(resolver.getFullRuntimeVersion('1.0.16'), Error, 'Invalid version');
});
test('Error With Invalid Major.Minor', async () => {
return assert.isRejected(resolver.getFullVersion('0.0'), Error, 'Unable to resolve version');
return assert.isRejected(resolver.getFullRuntimeVersion('0.0'), Error, 'Unable to resolve version');
});
test('Resolve Valid Versions', async () => {
test('Resolve Valid Runtime Versions', async () => {
for (const version of versionPairs) {
assert.equal(await resolver.getFullVersion(version[0]), version[1]);
assert.equal(await resolver.getFullRuntimeVersion(version[0]), version[1]);
}
});
test('Resolve Latest SDK Version', async () => {
assert.equal(await resolver.getFullSDKVersion('2.2'), '2.2.207');
});
});

Просмотреть файл

@ -4,8 +4,6 @@
* ------------------------------------------------------------------------------------------ */
import * as chai from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { DotnetCoreAcquisitionWorker } from '../../Acquisition/DotnetCoreAcquisitionWorker';
import { IInstallScriptAcquisitionWorker } from '../../Acquisition/IInstallScriptAcquisitionWorker';
@ -19,9 +17,7 @@ import {
MockExtensionContext,
MockInstallationValidator,
MockInstallScriptWorker,
MockVersionResolver,
MockWebRequestWorker,
versionPairs,
} from '../mocks/MockObjects';
const assert = chai.assert;
chai.use(chaiAsPromised);
@ -41,11 +37,10 @@ suite('WebRequestWorker Unit Tests', () => {
extensionState: context,
eventStream,
acquisitionInvoker: new ErrorAcquisitionInvoker(eventStream),
versionResolver: new MockVersionResolver(context, eventStream),
installationValidator: new MockInstallationValidator(eventStream),
timeoutValue: 10,
});
return assert.isRejected(acquisitionWorker.acquire(versionPairs[0][0]), Error, '.NET Acquisition Failed');
return assert.isRejected(acquisitionWorker.acquireRuntime('1.0'), Error, '.NET Acquisition Failed');
});
test('Install Script Request Failure', async () => {

Просмотреть файл

@ -9,7 +9,7 @@
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
"${workspaceFolder}/dist/**/*.js"
],
"preLaunchTask": "compile-all"
},
@ -20,10 +20,10 @@
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test/functional"
"--extensionTestsPath=${workspaceFolder}/dist/test/functional"
],
"outFiles": [
"${workspaceFolder}/out/test/**/*.js"
"${workspaceFolder}/dist/test/**/*.js"
],
"internalConsoleOptions": "openOnSessionStart",
"preLaunchTask": "compile-all"

Просмотреть файл

@ -26,15 +26,55 @@
"dotnet",
".NET SDK"
],
"activationEvents": [
"onCommand:dotnet-sdk.acquire",
"onCommand:dotnet-sdk.uninstallAll",
"onCommand:dotnet-sdk.showAcquisitionLog",
"onCommand:dotnet-sdk.reportIssue"
],
"main": "./dist/extension.js",
"contributes": {
"commands": [
{
"command": "dotnet-sdk.reportIssue",
"title": "Report an issue with the .NET SDK Install Tool.",
"category": ".NET SDK Install Tool"
},
{
"command": "dotnet-sdk.acquire",
"title": "Install the .NET SDK.",
"category": ".NET SDK Install Tool"
},
{
"command": "dotnet-sdk.uninstallAll",
"title": "Uninstall all VS Code copies of .NET SDK.",
"category": ".NET SDK Install Tool"
}
],
"configuration": {
"title": ".NET SDK Install Tool",
"properties": {
"dotnetSDKAcquisitionExtension.enableTelemetry": {
"type": "boolean",
"default": true,
"description": "Enable Telemetry for the .NET SDK install tool."
},
"dotnetSDKAcquisitionExtension.installTimeoutValue": {
"type": "number",
"default": 120,
"description": "Timeout for installing .NET SDK in seconds."
}
}
}
},
"scripts": {
"vscode:prepublish": "npm run compile-all && npm install && webpack --mode production",
"compile": "npm run clean && tsc -p ./",
"watch": "npm run compile && tsc -watch -p ./",
"test": "npm run compile --silent && node ./dist/test/functional/runTest.js",
"clean": "rimraf dist",
"compile-all": "cd ../vscode-dotnet-runtime-library && npm install && npm run compile && cd ../vscode-dotnet-runtime-extension && npm install && npm run compile",
"lint": "tslint -c ../tslint.json '../vscode-dotnet-runtime-library/src/**/*.ts' '../vscode-dotnet-runtime-extension/src/**/*.ts'",
"compile-all": "cd ../vscode-dotnet-runtime-library && npm install && npm run compile && cd ../vscode-dotnet-sdk-extension && npm install && npm run compile",
"lint": "tslint -c ../tslint.json '../vscode-dotnet-runtime-library/src/**/*.ts' '../vscode-dotnet-sdk-extension/src/**/*.ts'",
"webpack": "webpack --mode development"
},
"dependencies": {

Просмотреть файл

@ -3,10 +3,78 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as chai from 'chai';
import * as fs from 'fs';
import * as path from 'path';
import * as rimraf from 'rimraf';
import * as vscode from 'vscode';
import {
defaultSDKContext,
IDotnetAcquireContext,
IDotnetAcquireResult,
MockExtensionConfiguration,
MockExtensionContext,
MockTelemetryReporter,
MockWindowDisplayWorker,
} from 'vscode-dotnet-runtime-library';
import * as extension from '../../extension';
const assert = chai.assert;
/* tslint:disable:no-any */
suite('DotnetCoreAcquisitionExtension End to End', () => {
test('Test', async () => {
assert.isTrue(true);
suite('DotnetCoreAcquisitionExtension End to End', function() {
this.retries(3);
const storagePath = path.join(__dirname, 'tmp');
const mockState = new MockExtensionContext();
const extensionPath = path.join(__dirname, '/../../..');
const logPath = path.join(__dirname, 'logs');
const mockDisplayWorker = new MockWindowDisplayWorker();
let extensionContext: vscode.ExtensionContext;
this.beforeAll(async () => {
extensionContext = {
subscriptions: [],
globalStoragePath: storagePath,
globalState: mockState,
extensionPath,
logPath,
} as any;
const context = defaultSDKContext;
context.displayWorker = mockDisplayWorker;
context.telemetryReporter = new MockTelemetryReporter();
context.extensionConfiguration = new MockExtensionConfiguration([], true);
extension.activate(extensionContext, context);
});
this.afterEach(async () => {
// Tear down tmp storage for fresh run
await vscode.commands.executeCommand<string>(`${defaultSDKContext.commandPrefix}.uninstallAll`);
mockState.clear();
MockTelemetryReporter.telemetryEvents = [];
rimraf.sync(storagePath);
});
test('Activate', async () => {
// Commands should now be registered
assert.exists(extensionContext);
assert.isAbove(extensionContext.subscriptions.length, 0);
});
test('Install Command', async () => {
const context: IDotnetAcquireContext = { version: '5.0' };
const result = await vscode.commands.executeCommand<IDotnetAcquireResult>(`${defaultSDKContext.commandPrefix}.acquire`, context);
assert.exists(result);
assert.exists(result!.dotnetPath);
assert.include(result!.dotnetPath, context.version);
assert.isTrue(fs.existsSync(result!.dotnetPath));
}).timeout(100000);
test('Uninstall Command', async () => {
const context: IDotnetAcquireContext = { version: '3.1' };
const result = await vscode.commands.executeCommand<IDotnetAcquireResult>(`${defaultSDKContext.commandPrefix}.acquire`, context);
assert.exists(result);
assert.exists(result!.dotnetPath);
assert.include(result!.dotnetPath, context.version);
assert.isTrue(fs.existsSync(result!.dotnetPath!));
await vscode.commands.executeCommand(`${defaultSDKContext.commandPrefix}.uninstallAll`);
assert.isFalse(fs.existsSync(result!.dotnetPath));
}).timeout(100000);
});

Просмотреть файл

@ -0,0 +1,52 @@
//@ts-check
'use strict';
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
/**@type {import('webpack').Configuration}*/
const config = {
target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
entry: './/src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
output: {
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
path: path.resolve(__dirname, 'dist'),
filename: 'extension.js',
libraryTarget: 'commonjs2',
devtoolModuleFilenameTemplate: '../[resource-path]'
},
node: {
__dirname: false,
__filename: false,
},
devtool: 'source-map',
externals: {
vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
},
resolve: {
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader'
}
]
}
]
},
plugins: [
new CopyPlugin({ patterns: [
{ from: path.resolve(__dirname, '../vscode-dotnet-runtime-library/install scripts'), to: path.resolve(__dirname, 'dist', 'install scripts') },
{ from: path.resolve(__dirname, '../images'), to: path.resolve(__dirname, 'images') }
]}),
]
};
module.exports = config;