diff --git a/vscode-dotnet-runtime-extension/package-lock.json b/vscode-dotnet-runtime-extension/package-lock.json index a31f939f02..144deffed5 100644 --- a/vscode-dotnet-runtime-extension/package-lock.json +++ b/vscode-dotnet-runtime-extension/package-lock.json @@ -61,8 +61,8 @@ "@types/shelljs": "0.8.9", "@types/vscode": "1.74.0", "@vscode/sudo-prompt": "^9.3.1", - "axios": "^1.3.4", - "axios-cache-interceptor": "^1.0.1", + "axios": "^1.7.2", + "axios-cache-interceptor": "^1.5.3", "axios-retry": "^3.4.0", "chai": "4.3.4", "chai-as-promised": "^7.1.1", @@ -7372,8 +7372,8 @@ "@types/shelljs": "0.8.9", "@types/vscode": "1.74.0", "@vscode/sudo-prompt": "^9.3.1", - "axios": "^1.3.4", - "axios-cache-interceptor": "^1.0.1", + "axios": "^1.7.2", + "axios-cache-interceptor": "^1.5.3", "axios-retry": "^3.4.0", "chai": "4.3.4", "chai-as-promised": "^7.1.1", diff --git a/vscode-dotnet-runtime-extension/src/extension.ts b/vscode-dotnet-runtime-extension/src/extension.ts index c17e0904bf..c03e701289 100644 --- a/vscode-dotnet-runtime-extension/src/extension.ts +++ b/vscode-dotnet-runtime-extension/src/extension.ts @@ -20,7 +20,9 @@ import { DotnetExistingPathResolutionCompleted, DotnetRuntimeAcquisitionStarted, DotnetRuntimeAcquisitionTotalSuccessEvent, + DotnetGlobalSDKAcquisitionTotalSuccessEvent, enableExtensionTelemetry, + EventBasedError, ErrorConfiguration, ExistingPathResolver, ExtensionConfigurationWorker, @@ -166,7 +168,8 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE } if (!commandContext.version || commandContext.version === 'latest') { - throw new Error(`Cannot acquire .NET version "${commandContext.version}". Please provide a valid version.`); + throw new EventBasedError('BadContextualVersion', + `Cannot acquire .NET version "${commandContext.version}". Please provide a valid version.`); } const existingPath = await resolveExistingPathIfExists(existingPathConfigWorker, commandContext); @@ -201,8 +204,22 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE return Promise.reject('No requesting extension id was provided.'); } - const pathResult = callWithErrorHandling(async () => + let fullyResolvedVersion = ''; + + const pathResult = await callWithErrorHandling(async () => { + // Warning: Between now and later in this call-stack, the context 'version' is incomplete as it has not been resolved. + // Errors between here and the place where it is resolved cannot be routed to one another. + + sdkAcquisitionWorker.setAcquisitionContext(commandContext); + telemetryObserver?.setAcquisitionContext(sdkContext, commandContext); + + if(commandContext.version === '' || !commandContext.version) + { + throw new EventCancellationError('BadContextualRuntimeVersionError', + `No version was defined to install.`); + } + globalEventStream.post(new DotnetSDKAcquisitionStarted(commandContext.requestingExtensionId)); globalEventStream.post(new DotnetAcquisitionRequested(commandContext.version, commandContext.requestingExtensionId)); @@ -212,15 +229,14 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE return Promise.resolve(existingPath); } + const globalInstallerResolver = new GlobalInstallerResolver(sdkContext, commandContext.version); + fullyResolvedVersion = await globalInstallerResolver.getFullySpecifiedVersion(); + + // Reset context to point to the fully specified version so it is not possible for someone to access incorrect data during the install process. + commandContext.version = fullyResolvedVersion; sdkAcquisitionWorker.setAcquisitionContext(commandContext); telemetryObserver?.setAcquisitionContext(sdkContext, commandContext); - if(commandContext.version === '' || !commandContext.version) - { - throw Error(`No version was defined to install.`); - } - - const globalInstallerResolver = new GlobalInstallerResolver(sdkContext, commandContext.version); outputChannel.show(true); const dotnetPath = await sdkAcquisitionWorker.acquireGlobalSDK(globalInstallerResolver); @@ -228,6 +244,11 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE return dotnetPath; }, sdkIssueContextFunctor(commandContext.errorConfiguration, commandKeys.acquireGlobalSDK), commandContext.requestingExtensionId, sdkContext); + const iKey = sdkAcquisitionWorker.getInstallKey(fullyResolvedVersion); + const install = {installKey : iKey, version : fullyResolvedVersion, installMode: 'sdk', isGlobal: true, + architecture: commandContext.architecture ?? DotnetCoreAcquisitionWorker.defaultArchitecture()} as DotnetInstall; + + globalEventStream.post(new DotnetGlobalSDKAcquisitionTotalSuccessEvent(commandContext.version, install, commandContext.requestingExtensionId ?? '', pathResult?.dotnetPath ?? '')); return pathResult; }); @@ -245,8 +266,8 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE if (!activeSupportVersions || activeSupportVersions.length < 1) { - const err = new Error(`An active-support version of dotnet couldn't be found. Discovered versions: ${JSON.stringify(availableVersions)}`); - globalEventStream.post(new DotnetVersionResolutionError(err as EventCancellationError, null)); + const err = new EventCancellationError('DotnetVersionResolutionError', `An active-support version of dotnet couldn't be found. Discovered versions: ${JSON.stringify(availableVersions)}`); + globalEventStream.post(new DotnetVersionResolutionError(err, null)); if(!availableVersions || availableVersions.length < 1) { return []; diff --git a/vscode-dotnet-runtime-extension/yarn.lock b/vscode-dotnet-runtime-extension/yarn.lock index 1d8517a7d5..8ade32defd 100644 --- a/vscode-dotnet-runtime-extension/yarn.lock +++ b/vscode-dotnet-runtime-extension/yarn.lock @@ -2481,8 +2481,8 @@ "@types/shelljs" "0.8.9" "@types/vscode" "1.74.0" "@vscode/sudo-prompt" "^9.3.1" - "axios" "^1.3.4" - "axios-cache-interceptor" "^1.0.1" + "axios" "^1.7.2" + "axios-cache-interceptor" "^1.5.3" "axios-retry" "^3.4.0" "chai" "4.3.4" "chai-as-promised" "^7.1.1" diff --git a/vscode-dotnet-runtime-library/src/Acquisition/AcquisitionInvoker.ts b/vscode-dotnet-runtime-library/src/Acquisition/AcquisitionInvoker.ts index 4189adf603..02af7a0355 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/AcquisitionInvoker.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/AcquisitionInvoker.ts @@ -15,6 +15,7 @@ import { DotnetAcquisitionTimeoutError, DotnetAcquisitionUnexpectedError, DotnetOfflineFailure, + EventBasedError, } from '../EventStream/EventStreamEvents'; import { timeoutConstants } from '../Utils/ErrorHandler'; @@ -100,19 +101,22 @@ You will need to restart VS Code after these changes. If PowerShell is still not { if (!(await this.isOnline(installContext))) { - const offlineError = new Error('No internet connection detected: Cannot install .NET'); + const offlineError = new EventBasedError('DotnetOfflineFailure', 'No internet connection detected: Cannot install .NET'); this.eventStream.post(new DotnetOfflineFailure(offlineError, installKey)); reject(offlineError); } else if (error.signal === 'SIGKILL') { - error.message = timeoutConstants.timeoutMessage; + const newError = new EventBasedError('DotnetAcquisitionTimeoutError', + `${timeoutConstants.timeoutMessage}, MESSAGE: ${error.message}, CODE: ${error.code}, KILLED: ${error.killed}`, error.stack); this.eventStream.post(new DotnetAcquisitionTimeoutError(error, installKey, installContext.timeoutSeconds)); - reject(error); + reject(newError); } else { - this.eventStream.post(new DotnetAcquisitionInstallError(error, installKey)); - reject(error); + const newError = new EventBasedError('DotnetAcquisitionInstallError', + `${timeoutConstants.timeoutMessage}, MESSAGE: ${error.message}, CODE: ${error.code}, SIGNAL: ${error.signal}`, error.stack); + this.eventStream.post(new DotnetAcquisitionInstallError(newError, installKey)); + reject(newError); } } else if (stderr && stderr.length > 0) @@ -127,10 +131,11 @@ You will need to restart VS Code after these changes. If PowerShell is still not } }); } - catch (error) + catch (error : any) { - this.eventStream.post(new DotnetAcquisitionUnexpectedError(error as Error, installKey)); - reject(error); + const newError = new EventBasedError('DotnetAcquisitionUnexpectedError', error?.message, error?.stack) + this.eventStream.post(new DotnetAcquisitionUnexpectedError(newError, installKey)); + reject(newError); } }); } @@ -211,7 +216,7 @@ If you cannot safely and confidently change the execution policy, try setting a error = err; } } - catch(err) + catch(err : any) { if(!knownError) { @@ -222,7 +227,7 @@ If you cannot safely and confidently change the execution policy, try setting a if(error != null) { this.eventStream.post(new DotnetAcquisitionScriptError(error as Error, installKey)); - throw error; + throw new EventBasedError('DotnetAcquisitionScriptError', error?.message, error?.stack); } return command!.commandRoot; diff --git a/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts b/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts index f7c992bead..e6576c3fd9 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts @@ -29,6 +29,8 @@ import { DotnetCompletedGlobalInstallerExecution, DotnetFakeSDKEnvironmentVariableTriggered, SuppressedAcquisitionError, + EventBasedError, + EventCancellationError, } from '../EventStream/EventStreamEvents'; import { GlobalInstallerResolver } from './GlobalInstallerResolver'; @@ -36,7 +38,7 @@ import { WinMacGlobalInstaller } from './WinMacGlobalInstaller'; import { LinuxGlobalInstaller } from './LinuxGlobalInstaller'; import { TelemetryUtilities } from '../EventStream/TelemetryUtilities'; import { Debugging } from '../Utils/Debugging'; -import { IDotnetAcquireContext} from '../IDotnetAcquireContext'; +import { DotnetInstallType, IDotnetAcquireContext} from '../IDotnetAcquireContext'; import { IGlobalInstaller } from './IGlobalInstaller'; import { IVSCodeExtensionContext } from '../IVSCodeExtensionContext'; import { IUtilityContext } from '../Utils/IUtilityContext'; @@ -128,7 +130,7 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker */ public async acquireStatus(version: string, installMode: DotnetInstallMode, architecture? : string): Promise { - const install = GetDotnetInstallInfo(version, installMode, false, architecture ? architecture : this.installingArchitecture ?? DotnetCoreAcquisitionWorker.defaultArchitecture()) + const install = GetDotnetInstallInfo(version, installMode, 'local', architecture ? architecture : this.installingArchitecture ?? DotnetCoreAcquisitionWorker.defaultArchitecture()) const existingAcquisitionPromise = this.installTracker.getPromise(install); if (existingAcquisitionPromise) @@ -176,14 +178,14 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker private async acquire(version: string, mode: DotnetInstallMode, globalInstallerResolver : GlobalInstallerResolver | null = null, localInvoker? : IAcquisitionInvoker): Promise { - let install = GetDotnetInstallInfo(version, mode, globalInstallerResolver !== null, this.installingArchitecture ?? DotnetCoreAcquisitionWorker.defaultArchitecture()); + let install = GetDotnetInstallInfo(version, mode, globalInstallerResolver !== null ? 'global' : 'local', this.installingArchitecture ?? DotnetCoreAcquisitionWorker.defaultArchitecture()); // Allow for the architecture to be null, which is a legacy behavior. if(this.context.acquisitionContext?.architecture === null && this.context.acquisitionContext?.architecture !== undefined) { install = { - installKey: DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(version, null, globalInstallerResolver !== null), + installKey: DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(version, null, globalInstallerResolver !== null ? 'global' : 'local'), version: install.version, isGlobal: install.isGlobal, installMode: mode, @@ -208,20 +210,20 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker { Debugging.log(`The Acquisition Worker has Determined a Global Install was requested.`, this.context.eventStream); - acquisitionPromise = this.acquireGlobalCore(globalInstallerResolver, install).catch(async (error: Error) => { + acquisitionPromise = this.acquireGlobalCore(globalInstallerResolver, install).catch(async (error: any) => + { await this.installTracker.untrackInstallingVersion(install); - error.message = `.NET Acquisition Failed: ${error.message}`; - throw error; + const err = this.getErrorOrStringAsEventError(error); + throw err; }); } else { - Debugging.log(`The Acquisition Worker has Determined a Local Install was requested.`, this.context.eventStream); - - acquisitionPromise = this.acquireLocalCore(version, mode, install, localInvoker!).catch(async (error: Error) => { + acquisitionPromise = this.acquireLocalCore(version, mode, install, localInvoker!).catch(async (error: any) => + { await this.installTracker.untrackInstallingVersion(install); - error.message = `.NET Acquisition Failed: ${error.message}`; - throw error; + const err = this.getErrorOrStringAsEventError(error); + throw err; }); } @@ -232,22 +234,23 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker } } - public static getInstallKeyCustomArchitecture(version : string, architecture: string | null | undefined, isGlobal = false) : string + public static getInstallKeyCustomArchitecture(version : string, architecture: string | null | undefined, + installType : DotnetInstallType = 'local') : string { if(!architecture) { // Use the legacy method (no architecture) of installs - return isGlobal ? `${version}-global` : version; + return installType === 'global' ? `${version}-global` : version; } else { - return isGlobal ? `${version}-global~${architecture}` : `${version}~${architecture}`; + return installType === 'global' ? `${version}-global~${architecture}` : `${version}~${architecture}`; } } public getInstallKey(version : string) : string { - return DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(version, this.installingArchitecture, this.globalResolver !== null); + return DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(version, this.installingArchitecture, this.globalResolver !== null ? 'global' : 'local'); } /** @@ -296,11 +299,13 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker timeoutSeconds: this.context.timeoutSeconds, installRuntime : mode === 'runtime', installMode : mode, + installType : this.context.acquisitionContext?.installType ?? 'local', // Before this API param existed, all calls were for local types. architecture: this.installingArchitecture } as IDotnetInstallationContext; this.context.eventStream.post(new DotnetAcquisitionStarted(install, version, this.context.acquisitionContext?.requestingExtensionId)); - await acquisitionInvoker.installDotnet(installContext, install).catch((reason) => { - throw Error(`Installation failed: ${reason}`); + await acquisitionInvoker.installDotnet(installContext, install).catch((reason) => + { + throw reason; // This will get handled and cast into an event based error by its caller. }); this.context.installationValidator.validateDotnetInstall(install, dotnetPath); @@ -345,6 +350,20 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker return os.arch(); } + private getErrorOrStringAsEventError(error : any) + { + if(error instanceof EventBasedError || error instanceof EventCancellationError) + { + error.message = `.NET Acquisition Failed: ${error.message}`; + return error; + } + else + { + const newError = new EventBasedError('DotnetAcquisitionError', `.NET Acquisition Failed: ${error?.message ?? error}`); + return newError; + } + } + private async acquireGlobalCore(globalInstallerResolver : GlobalInstallerResolver, install : DotnetInstall): Promise { const installingVersion = await globalInstallerResolver.getFullySpecifiedVersion(); @@ -371,7 +390,8 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker if(installerResult !== '0') { - const err = new DotnetNonZeroInstallerExitCodeError(new Error(`An error was raised by the .NET SDK installer. The exit code it gave us: ${installerResult}`), install); + const err = new DotnetNonZeroInstallerExitCodeError(new EventBasedError('DotnetNonZeroInstallerExitCodeError', + `An error was raised by the .NET SDK installer. The exit code it gave us: ${installerResult}`), install); this.context.eventStream.post(err); throw err; } diff --git a/vscode-dotnet-runtime-library/src/Acquisition/DotnetInstall.ts b/vscode-dotnet-runtime-library/src/Acquisition/DotnetInstall.ts index 28f84c067c..1040911e21 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/DotnetInstall.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/DotnetInstall.ts @@ -3,6 +3,7 @@ * The .NET Foundation licenses this file to you under the MIT license. *--------------------------------------------------------------------------------------------*/ +import { DotnetInstallType } from '..'; import { DotnetCoreAcquisitionWorker } from './DotnetCoreAcquisitionWorker'; import { DotnetInstallMode } from './DotnetInstallMode'; @@ -67,12 +68,12 @@ export function looksLikeRuntimeVersion(version: string): boolean { return !band || band.length <= 2; // assumption : there exists no runtime version at this point over 99 sub versions } -export function GetDotnetInstallInfo(installVersion: string, installationMode: DotnetInstallMode, isGlobalInstall: boolean, installArchitecture: string): DotnetInstall { +export function GetDotnetInstallInfo(installVersion: string, installationMode: DotnetInstallMode, installType: DotnetInstallType, installArchitecture: string): DotnetInstall { return { - installKey: DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(installVersion, installArchitecture), + installKey: DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(installVersion, installArchitecture, installType), version: installVersion, architecture: installArchitecture, - isGlobal: isGlobalInstall, + isGlobal: installType === 'global', installMode: installationMode, } as DotnetInstall; } \ No newline at end of file diff --git a/vscode-dotnet-runtime-library/src/Acquisition/GenericDistroSDKProvider.ts b/vscode-dotnet-runtime-library/src/Acquisition/GenericDistroSDKProvider.ts index c0d3441ad7..541d28f5ef 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/GenericDistroSDKProvider.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/GenericDistroSDKProvider.ts @@ -9,7 +9,7 @@ import { CommandExecutorCommand } from '../Utils/CommandExecutorCommand'; import { DotnetDistroSupportStatus } from './LinuxVersionResolver'; import { DotnetInstallMode } from './DotnetInstallMode'; import { IDistroDotnetSDKProvider } from './IDistroDotnetSDKProvider'; -import { DotnetVersionResolutionError } from '../EventStream/EventStreamEvents'; +import { DotnetVersionResolutionError, EventBasedError } from '../EventStream/EventStreamEvents'; /* tslint:disable:no-any */ export class GenericDistroSDKProvider extends IDistroDotnetSDKProvider @@ -212,7 +212,7 @@ export class GenericDistroSDKProvider extends IDistroDotnetSDKProvider if(maxVersion === '0') { - const err = new DotnetVersionResolutionError(new Error(`No packages for .NET are available. + const err = new DotnetVersionResolutionError(new EventBasedError('DotnetVersionResolutionError', `No packages for .NET are available. Please refer to https://learn.microsoft.com/en-us/dotnet/core/install/linux if you'd link to install .NET.`), null); this.context.eventStream.post(err); throw(err); diff --git a/vscode-dotnet-runtime-library/src/Acquisition/GlobalInstallerResolver.ts b/vscode-dotnet-runtime-library/src/Acquisition/GlobalInstallerResolver.ts index 79e646aa09..f1751d39e2 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/GlobalInstallerResolver.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/GlobalInstallerResolver.ts @@ -17,6 +17,7 @@ import { DotnetFeatureBandDoesNotExistError, DotnetUnexpectedInstallerOSError, DotnetVersionCategorizedEvent, DotnetVersionResolutionError, + EventBasedError, EventCancellationError } from '../EventStream/EventStreamEvents'; import { FileUtilities } from '../Utils/FileUtilities'; @@ -160,7 +161,8 @@ export class GlobalInstallerResolver { return [installerUrlAndHash[0], fullySpecifiedVersionRequested, installerUrlAndHash[1]]; } - const err = new DotnetVersionResolutionError(new EventCancellationError(`${this.badResolvedVersionErrorString} ${version}`), getInstallKeyFromContext(this.context)); + const err = new DotnetVersionResolutionError(new EventCancellationError('DotnetVersionResolutionError', + `${this.badResolvedVersionErrorString} ${version}`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(err); throw err.error; } @@ -176,7 +178,8 @@ export class GlobalInstallerResolver { { if(specificVersion === null || specificVersion === undefined || specificVersion === '') { - const versionErr = new DotnetVersionResolutionError(new EventCancellationError(`${this.badResolvedVersionErrorString} ${specificVersion}.`), + const versionErr = new DotnetVersionResolutionError(new EventCancellationError('DotnetVersionResolutionError', + `${this.badResolvedVersionErrorString} ${specificVersion}.`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(versionErr); throw versionErr.error; @@ -185,7 +188,8 @@ export class GlobalInstallerResolver { const convertedOs = this.fileUtilities.nodeOSToDotnetOS(os.platform(), this.context.eventStream); if(convertedOs === 'auto') { - const osErr = new DotnetUnexpectedInstallerOSError(new Error(`The OS ${os.platform()} is currently unsupported or unknown.`), getInstallKeyFromContext(this.context)); + const osErr = new DotnetUnexpectedInstallerOSError(new EventBasedError('DotnetUnexpectedInstallerOSError', + `The OS ${os.platform()} is currently unsupported or unknown.`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(osErr); throw osErr.error; } @@ -193,7 +197,8 @@ export class GlobalInstallerResolver { const convertedArch = this.fileUtilities.nodeArchToDotnetArch(os.arch(), this.context.eventStream); if(convertedArch === 'auto') { - const archErr = new DotnetUnexpectedInstallerArchitectureError(new Error(`The architecture ${os.arch()} is currently unsupported or unknown. + const archErr = new DotnetUnexpectedInstallerArchitectureError(new EventBasedError('DotnetUnexpectedInstallerArchitectureError', + `The architecture ${os.arch()} is currently unsupported or unknown. Your architecture: ${os.arch()}. Your OS: ${os.platform()}.`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(archErr); throw archErr.error; @@ -205,7 +210,8 @@ export class GlobalInstallerResolver { const releases = indexJson![this.releasesJsonKey]; if(releases.length === 0) { - const jsonErr = new DotnetInvalidReleasesJSONError(new Error(`${this.releasesJsonErrorString}${indexUrl}`), getInstallKeyFromContext(this.context)); + const jsonErr = new DotnetInvalidReleasesJSONError(new EventBasedError('DotnetInvalidReleasesJSONError', + `${this.releasesJsonErrorString}${indexUrl}`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(jsonErr); throw jsonErr.error; } @@ -230,7 +236,8 @@ export class GlobalInstallerResolver { const installerUrl = installer[this.releasesUrlKey]; if(installerUrl === undefined) { - const releaseJsonErr = new DotnetInvalidReleasesJSONError(new Error(`URL for ${desiredRidPackage} on ${specificVersion} is unavailable: + const releaseJsonErr = new DotnetInvalidReleasesJSONError(new EventBasedError('DotnetInvalidReleasesJSONError', + `URL for ${desiredRidPackage} on ${specificVersion} is unavailable: The version may be Out of Support, or the releases json format used by ${indexUrl} may be invalid and the extension needs to be updated.`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(releaseJsonErr); @@ -238,7 +245,8 @@ The version may be Out of Support, or the releases json format used by ${indexUr } if(!(installerUrl as string).startsWith('https://download.visualstudio.microsoft.com/')) { - const releaseJsonErr = new DotnetInvalidReleasesJSONError(new Error(`The url: ${installerUrl} is hosted on an unexpected domain. + const releaseJsonErr = new DotnetInvalidReleasesJSONError(new EventBasedError('DotnetInvalidReleasesJSONError', + `The url: ${installerUrl} is hosted on an unexpected domain. We cannot verify that .NET downloads are hosted in a secure location, so we have rejected .NET. The url should be download.visualstudio.microsoft.com. Please report this issue so it can be remedied or investigated.`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(releaseJsonErr); @@ -258,14 +266,16 @@ Please report this issue so it can be remedied or investigated.`), getInstallKey } } - const installerErr = new DotnetNoInstallerFileExistsError(new Error(`An installer for the runtime ${desiredRidPackage} could not be found for version ${specificVersion}.`), + const installerErr = new DotnetNoInstallerFileExistsError(new EventBasedError('DotnetNoInstallerFileExistsError', + `An installer for the runtime ${desiredRidPackage} could not be found for version ${specificVersion}.`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(installerErr); throw installerErr.error; } } - const fileErr = new DotnetNoInstallerFileExistsError(new Error(`The SDK installation files for version ${specificVersion} running on ${desiredRidPackage} couldn't be found. + const fileErr = new DotnetNoInstallerFileExistsError(new EventBasedError('DotnetNoInstallerFileExistsError', + `The SDK installation files for version ${specificVersion} running on ${desiredRidPackage} couldn't be found. Is the version in support? Note that -preview versions or versions with build numbers aren't yet supported. Visit https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core for support information.`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(fileErr); @@ -294,7 +304,8 @@ Visit https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core for const installerFileName = installerJson[this.releasesSdkNameKey]; if(installerFileName === undefined) { - const err = new DotnetInvalidReleasesJSONError(new Error(`${this.releasesJsonErrorString} + const err = new DotnetInvalidReleasesJSONError(new EventBasedError('DotnetInvalidReleasesJSONError', + `${this.releasesJsonErrorString} ${this.getIndexUrl(this.versionResolver.getMajorMinor(version))}. The json does not have the parameter ${this.releasesSdkNameKey} which means the API publisher has published invalid dotnet release data. Please file an issue at https://github.com/dotnet/vscode-dotnet-runtime.`), getInstallKeyFromContext(this.context)); @@ -320,7 +331,8 @@ Please file an issue at https://github.com/dotnet/vscode-dotnet-runtime.`), getI } default: { - const err = new DotnetUnexpectedInstallerOSError(new Error(`The SDK Extension failed to map the OS ${operatingSystemInDotnetFormat} to a proper package type. + const err = new DotnetUnexpectedInstallerOSError(new EventBasedError('DotnetUnexpectedInstallerOSError', + `The SDK Extension failed to map the OS ${operatingSystemInDotnetFormat} to a proper package type. Your architecture: ${os.arch()}. Your OS: ${os.platform()}.`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(err); throw err.error; @@ -346,7 +358,8 @@ Your architecture: ${os.arch()}. Your OS: ${os.platform()}.`), getInstallKeyFrom if(releases.length === 0) { - const badJsonErr = new DotnetInvalidReleasesJSONError(new Error(`${this.releasesJsonErrorString}${indexUrl}`), getInstallKeyFromContext(this.context)); + const badJsonErr = new DotnetInvalidReleasesJSONError(new EventBasedError('DotnetInvalidReleasesJSONError', + `${this.releasesJsonErrorString}${indexUrl}`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(badJsonErr); throw badJsonErr.error; } @@ -365,7 +378,8 @@ Your architecture: ${os.arch()}. Your OS: ${os.platform()}.`), getInstallKeyFrom } const availableBands = Array.from(new Set(sdks.map((x : any) => this.versionResolver.getFeatureBandFromVersion(x[this.releasesSdkVersionKey])))); - const err = new DotnetFeatureBandDoesNotExistError(new Error(`The feature band '${band}' doesn't exist for the SDK major version '${version}'. + const err = new DotnetFeatureBandDoesNotExistError(new EventBasedError('DotnetFeatureBandDoesNotExistError', + `The feature band '${band}' doesn't exist for the SDK major version '${version}'. Available feature bands for this SDK version are ${availableBands}.`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(err); throw err.error; diff --git a/vscode-dotnet-runtime-library/src/Acquisition/IAcquisitionInvoker.ts b/vscode-dotnet-runtime-library/src/Acquisition/IAcquisitionInvoker.ts index 4a1319b2a3..d67c981611 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/IAcquisitionInvoker.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/IAcquisitionInvoker.ts @@ -14,5 +14,5 @@ export abstract class IAcquisitionInvoker { this.installationValidator = new InstallationValidator(eventStream); } - public abstract installDotnet(installContext: IDotnetInstallationContext, installKey : DotnetInstall): Promise; + public abstract installDotnet(installContext: IDotnetInstallationContext, install : DotnetInstall): Promise; } diff --git a/vscode-dotnet-runtime-library/src/Acquisition/IDistroDotnetSDKProvider.ts b/vscode-dotnet-runtime-library/src/Acquisition/IDistroDotnetSDKProvider.ts index 2315bfe77b..4830be9d36 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/IDistroDotnetSDKProvider.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/IDistroDotnetSDKProvider.ts @@ -8,7 +8,7 @@ import * as fs from 'fs'; import path = require('path'); import { DistroVersionPair, DotnetDistroSupportStatus } from './LinuxVersionResolver'; -import { DotnetAcquisitionDistroUnknownError, DotnetVersionResolutionError, SuppressedAcquisitionError } from '../EventStream/EventStreamEvents'; +import { DotnetAcquisitionDistroUnknownError, DotnetVersionResolutionError, EventBasedError, EventCancellationError, SuppressedAcquisitionError } from '../EventStream/EventStreamEvents'; import { VersionResolver } from './VersionResolver'; import { CommandExecutorCommand } from '../Utils/CommandExecutorCommand'; import { CommandExecutor } from '../Utils/CommandExecutor'; @@ -81,7 +81,8 @@ export abstract class IDistroDotnetSDKProvider { this.distroJson = JSON.parse(fs.readFileSync(distroDataFile, 'utf8')); if(!distroVersion || !this.distroJson || !((this.distroJson as any)[this.distroVersion.distro])) { - const error = new DotnetAcquisitionDistroUnknownError(new Error(`Automated installation for the distro ${this.distroVersion.distro} is not yet supported. + const error = new DotnetAcquisitionDistroUnknownError(new EventBasedError('DotnetAcquisitionDistroUnknownError', + `Automated installation for the distro ${this.distroVersion.distro} is not yet supported. Please install the .NET SDK manually: https://dotnet.microsoft.com/download`), getInstallKeyFromContext(this.context)); throw error.error; @@ -361,7 +362,7 @@ Please install the .NET SDK manually: https://dotnet.microsoft.com/download`), return dotnetPackage.packages[0]; } } - const err = new Error(`Could not find a .NET package for version ${fullySpecifiedDotnetVersion}. Found only: ${JSON.stringify(myDotnetVersions)}`); + const err = new EventCancellationError('DotnetVersionResolutionError', `Could not find a .NET package for version ${fullySpecifiedDotnetVersion}. Found only: ${JSON.stringify(myDotnetVersions)}`); this.context.eventStream.post(new DotnetVersionResolutionError(err, getInstallKeyFromContext(this.context))); throw err; } diff --git a/vscode-dotnet-runtime-library/src/Acquisition/IDotnetInstallationContext.ts b/vscode-dotnet-runtime-library/src/Acquisition/IDotnetInstallationContext.ts index e36e472c9c..983625566d 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/IDotnetInstallationContext.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/IDotnetInstallationContext.ts @@ -3,6 +3,7 @@ * The .NET Foundation licenses this file to you under the MIT license. *--------------------------------------------------------------------------------------------*/ +import { DotnetInstallType } from '..'; import { DotnetInstallMode } from './DotnetInstallMode'; export interface IDotnetInstallationContext { @@ -12,5 +13,6 @@ export interface IDotnetInstallationContext { timeoutSeconds: number; installRuntime: boolean; // kept to remove breaking change installMode : DotnetInstallMode; + installType : DotnetInstallType; architecture: string; } diff --git a/vscode-dotnet-runtime-library/src/Acquisition/InstallScriptAcquisitionWorker.ts b/vscode-dotnet-runtime-library/src/Acquisition/InstallScriptAcquisitionWorker.ts index 6e28a9a4b6..2d01f50290 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/InstallScriptAcquisitionWorker.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/InstallScriptAcquisitionWorker.ts @@ -9,6 +9,7 @@ import { DotnetFallbackInstallScriptUsed, DotnetInstallScriptAcquisitionCompleted, DotnetInstallScriptAcquisitionError, + EventBasedError, } from '../EventStream/EventStreamEvents'; import { WebRequestWorker } from '../Utils/WebRequestWorker'; import { Debugging } from '../Utils/Debugging'; @@ -40,7 +41,7 @@ export class InstallScriptAcquisitionWorker implements IInstallScriptAcquisition const script = await this.webWorker.getCachedData(); if (!script) { Debugging.log('The request to acquire the script failed.'); - throw new Error('Unable to get script path.'); + throw new EventBasedError('NoInstallScriptPathExists', 'Unable to get script path.'); } await this.fileUtilities.writeFileOntoDisk(script, this.scriptFilePath, false, this.context.eventStream); @@ -60,7 +61,7 @@ export class InstallScriptAcquisitionWorker implements IInstallScriptAcquisition return fallbackPath; } - throw new Error(`Failed to Acquire Dotnet Install Script: ${error}`); + throw new EventBasedError('UnableToAcquireDotnetInstallScript', `Failed to Acquire Dotnet Install Script: ${error}`); } } diff --git a/vscode-dotnet-runtime-library/src/Acquisition/InstallTracker.ts b/vscode-dotnet-runtime-library/src/Acquisition/InstallTracker.ts index 2054c3acd2..2954426b7a 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/InstallTracker.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/InstallTracker.ts @@ -13,6 +13,7 @@ import { DotnetPreinstallDetected, DotnetPreinstallDetectionError, DuplicateInstallDetected, + EventBasedError, NoMatchingInstallToStopTracking } from '../EventStream/EventStreamEvents'; import { @@ -76,7 +77,7 @@ export class InstallTracker { // Either the lock could not be acquired or releasing it failed this.context.eventStream.post(new DotnetLockErrorEvent(e, e?.message ?? 'Unable to acquire lock to update installation state', new Date().toISOString(), lockPath, lockPath)); - throw error(); + throw new EventBasedError('DotnetLockErrorEvent', e?.message, e?.stack); } } @@ -285,7 +286,7 @@ Installs: ${[...this.inProgressInstalls].map(x => x.dotnetInstall.installKey).jo for (const installKey of installKeys) { localSDKDirectoryKeyIter = installKey; - const installRecord = GetDotnetInstallInfo(getVersionFromLegacyInstallKey(installKey), 'sdk', false, DotnetCoreAcquisitionWorker.defaultArchitecture()); + const installRecord = GetDotnetInstallInfo(getVersionFromLegacyInstallKey(installKey), 'sdk', 'local', DotnetCoreAcquisitionWorker.defaultArchitecture()); this.context.eventStream.post(new DotnetPreinstallDetected(installRecord)); await this.addVersionToExtensionState(this.installedVersionsKey, installRecord, true); installedInstallKeys.push({ dotnetInstall: installRecord, installingExtensions: [ null ] } as InstallRecord); @@ -293,7 +294,7 @@ Installs: ${[...this.inProgressInstalls].map(x => x.dotnetInstall.installKey).jo } catch (error) { - this.context.eventStream.post(new DotnetPreinstallDetectionError(error as Error, GetDotnetInstallInfo(localSDKDirectoryKeyIter, 'sdk', false, + this.context.eventStream.post(new DotnetPreinstallDetectionError(error as Error, GetDotnetInstallInfo(localSDKDirectoryKeyIter, 'sdk', 'local', DotnetCoreAcquisitionWorker.defaultArchitecture()))); } return installedInstallKeys; diff --git a/vscode-dotnet-runtime-library/src/Acquisition/InstallationValidator.ts b/vscode-dotnet-runtime-library/src/Acquisition/InstallationValidator.ts index 2b24a22882..540edbf193 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/InstallationValidator.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/InstallationValidator.ts @@ -7,6 +7,7 @@ import * as path from 'path'; import { DotnetInstallationValidated, DotnetInstallationValidationError, + EventBasedError, } from '../EventStream/EventStreamEvents'; import { IInstallationValidator } from './IInstallationValidator'; import { DotnetInstall } from './DotnetInstall'; @@ -42,7 +43,7 @@ export class InstallationValidator extends IInstallationValidator { private assertOrThrowError(check: boolean, message: string, install: DotnetInstall, dotnetPath: string) { if (!check) { this.eventStream.post(new DotnetInstallationValidationError(new Error(message), install, dotnetPath)); - throw new Error(message); + throw new EventBasedError('DotnetInstallationValidationError', message); } } } diff --git a/vscode-dotnet-runtime-library/src/Acquisition/LinuxVersionResolver.ts b/vscode-dotnet-runtime-library/src/Acquisition/LinuxVersionResolver.ts index fddcffd751..3ebe6a763f 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/LinuxVersionResolver.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/LinuxVersionResolver.ts @@ -12,6 +12,7 @@ import DotnetCustomLinuxInstallExistsError, DotnetInstallLinuxChecks, DotnetUpgradedEvent, + EventBasedError, EventCancellationError } from '../EventStream/EventStreamEvents'; import { GenericDistroSDKProvider } from './GenericDistroSDKProvider' @@ -130,7 +131,8 @@ If you would like to contribute to the list of supported distros, please visit: if(distroName === '' || distroVersion === '') { - const error = new DotnetAcquisitionDistroUnknownError(new EventCancellationError(this.baseUnsupportedDistroErrorMessage), getInstallKeyFromContext(this.acquisitionContext)); + const error = new DotnetAcquisitionDistroUnknownError(new EventCancellationError('DotnetAcquisitionDistroUnknownError', + this.baseUnsupportedDistroErrorMessage), getInstallKeyFromContext(this.acquisitionContext)); this.acquisitionContext.eventStream.post(error); throw error.error; } @@ -140,7 +142,8 @@ If you would like to contribute to the list of supported distros, please visit: } catch(error) { - const err = new DotnetAcquisitionDistroUnknownError(new EventCancellationError(`${this.baseUnsupportedDistroErrorMessage} ... does /etc/os-release exist?`), + const err = new DotnetAcquisitionDistroUnknownError(new EventCancellationError('DotnetAcquisitionDistroUnknownError', + `${this.baseUnsupportedDistroErrorMessage} ... does /etc/os-release exist?`), getInstallKeyFromContext(this.acquisitionContext)); this.acquisitionContext.eventStream.post(err); throw err.error; @@ -167,7 +170,9 @@ If you would like to contribute to the list of supported distros, please visit: if(!this.distro || !this.distroSDKProvider) { - const error = new DotnetAcquisitionDistroUnknownError(new EventCancellationError(`${this.baseUnsupportedDistroErrorMessage} ... we cannot initialize.`), + const error = new DotnetAcquisitionDistroUnknownError(new EventCancellationError( + 'DotnetAcquisitionDistroUnknownError', + `${this.baseUnsupportedDistroErrorMessage} ... we cannot initialize.`), getInstallKeyFromContext(this.acquisitionContext)); this.acquisitionContext.eventStream.post(error); throw error.error; @@ -188,13 +193,17 @@ If you would like to contribute to the list of supported distros, please visit: { // Implement any custom logic for a Distro Class in a new DistroSDKProvider and add it to the factory here. case null: - const unknownDistroErr = new DotnetAcquisitionDistroUnknownError(new EventCancellationError(this.unsupportedDistroErrorMessage), getInstallKeyFromContext(this.acquisitionContext)); + const unknownDistroErr = new DotnetAcquisitionDistroUnknownError(new EventCancellationError( + 'DotnetAcquisitionDistroUnknownError', + this.unsupportedDistroErrorMessage), getInstallKeyFromContext(this.acquisitionContext)); this.acquisitionContext.eventStream.post(unknownDistroErr); throw unknownDistroErr.error; case 'Red Hat Enterprise Linux': if(this.isRedHatVersion7(distroAndVersion.version)) { - const unsupportedRhelErr = new DotnetAcquisitionDistroUnknownError(new EventCancellationError(this.redhatUnsupportedDistroErrorMessage), + const unsupportedRhelErr = new DotnetAcquisitionDistroUnknownError(new EventCancellationError( + 'DotnetAcquisitionDistroUnknownError', + this.redhatUnsupportedDistroErrorMessage), getInstallKeyFromContext(this.acquisitionContext)); this.acquisitionContext.eventStream.post(unsupportedRhelErr); throw unsupportedRhelErr.error; @@ -222,7 +231,8 @@ If you would like to contribute to the list of supported distros, please visit: const microsoftFeedDir = await this.distroSDKProvider!.getExpectedDotnetMicrosoftFeedInstallationDirectory(); if(fs.existsSync(microsoftFeedDir)) { - const err = new DotnetConflictingLinuxInstallTypesError(new EventCancellationError(this.conflictingInstallErrorMessage + microsoftFeedDir), + const err = new DotnetConflictingLinuxInstallTypesError(new EventCancellationError('DotnetConflictingLinuxInstallTypesError', + this.conflictingInstallErrorMessage + microsoftFeedDir), getInstallKeyFromContext(this.acquisitionContext)); this.acquisitionContext.eventStream.post(err); throw err.error; @@ -233,7 +243,8 @@ If you would like to contribute to the list of supported distros, please visit: const distroFeedDir = await this.distroSDKProvider!.getExpectedDotnetDistroFeedInstallationDirectory(); if(fs.existsSync(distroFeedDir)) { - const err = new DotnetConflictingLinuxInstallTypesError(new EventCancellationError(this.conflictingInstallErrorMessage + distroFeedDir), + const err = new DotnetConflictingLinuxInstallTypesError(new EventCancellationError('DotnetConflictingLinuxInstallTypesError', + this.conflictingInstallErrorMessage + distroFeedDir), getInstallKeyFromContext(this.acquisitionContext)); this.acquisitionContext.eventStream.post(err); throw err.error; @@ -253,7 +264,8 @@ If you would like to contribute to the list of supported distros, please visit: supportStatus === DotnetDistroSupportStatus.Distro ? await this.distroSDKProvider!.getExpectedDotnetDistroFeedInstallationDirectory() : await this.distroSDKProvider!.getExpectedDotnetMicrosoftFeedInstallationDirectory() )) { - const err = new DotnetCustomLinuxInstallExistsError(new EventCancellationError(this.conflictingCustomInstallErrorMessage + existingInstall), + const err = new DotnetCustomLinuxInstallExistsError(new EventCancellationError('DotnetCustomLinuxInstallExistsError', + this.conflictingCustomInstallErrorMessage + existingInstall), getInstallKeyFromContext(this.acquisitionContext)); this.acquisitionContext.eventStream.post(err); throw err.error; @@ -285,7 +297,8 @@ If you would like to contribute to the list of supported distros, please visit: || Number(this.versionResolver.getFeatureBandFromVersion(existingGlobalInstallSDKVersion)) > Number(this.versionResolver.getFeatureBandFromVersion(fullySpecifiedDotnetVersion))) { // We shouldn't downgrade to a lower patch - const err = new DotnetCustomLinuxInstallExistsError(new EventCancellationError(`An installation of ${fullySpecifiedDotnetVersion} was requested but ${existingGlobalInstallSDKVersion} is already available.`), + const err = new DotnetCustomLinuxInstallExistsError(new EventCancellationError('DotnetCustomLinuxInstallExistsError', + `An installation of ${fullySpecifiedDotnetVersion} was requested but ${existingGlobalInstallSDKVersion} is already available.`), getInstallKeyFromContext(this.acquisitionContext)); this.acquisitionContext.eventStream.post(err); throw err.error; @@ -318,7 +331,7 @@ If you would like to contribute to the list of supported distros, please visit: // Verify the version of dotnet is supported if (!( await this.distroSDKProvider!.isDotnetVersionSupported(fullySpecifiedDotnetVersion, 'sdk') )) { - throw new Error(`The distro ${this.distro?.distro} ${this.distro?.version} does not officially support dotnet version ${fullySpecifiedDotnetVersion}.`); + throw new EventBasedError('UnsupportedDistro', `The distro ${this.distro?.distro} ${this.distro?.version} does not officially support dotnet version ${fullySpecifiedDotnetVersion}.`); } // Verify there are no conflicting installs diff --git a/vscode-dotnet-runtime-library/src/Acquisition/VersionResolver.ts b/vscode-dotnet-runtime-library/src/Acquisition/VersionResolver.ts index a04327f59e..d776f0e110 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/VersionResolver.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/VersionResolver.ts @@ -11,7 +11,8 @@ import { DotnetVersionResolutionCompleted, DotnetVersionResolutionError, DotnetVersionParseEvent, - EventCancellationError + EventCancellationError, + EventBasedError } from '../EventStream/EventStreamEvents'; import { WebRequestWorker } from '../Utils/WebRequestWorker'; import { getInstallKeyFromContext } from '../Utils/InstallKeyUtilities'; @@ -113,9 +114,9 @@ export class VersionResolver implements IVersionResolver { { releasesVersions = await this.getReleasesInfo(mode); } - catch(error) + catch(error : any) { - throw error; + throw new EventBasedError(error, error?.message, error?.stack); } return new Promise((resolve, reject) => @@ -126,9 +127,10 @@ export class VersionResolver implements IVersionResolver { this.context.eventStream.post(new DotnetVersionResolutionCompleted(version, versionResult)); resolve(versionResult); } - catch (error) + catch (error : any) { - this.context.eventStream.post(new DotnetVersionResolutionError(error as EventCancellationError, getAssumedInstallInfo(this.context, version, mode))); + this.context.eventStream.post(new DotnetVersionResolutionError(new EventCancellationError('DotnetVersionResolutionError', + error?.message ?? ''), getAssumedInstallInfo(this.context, version, mode))); reject(error); } }); @@ -147,7 +149,8 @@ export class VersionResolver implements IVersionResolver { } if (!matchingVersion || matchingVersion.length < 1) { - const err = new DotnetVersionResolutionError(new EventCancellationError(`The requested and or resolved version is invalid.`), + const err = new DotnetVersionResolutionError(new EventCancellationError('DotnetVersionResolutionError', + `The requested and or resolved version is invalid.`), getAssumedInstallInfo(this.context, version, this.context.installMode)); this.context.eventStream.post(err); throw err.error; @@ -172,7 +175,8 @@ export class VersionResolver implements IVersionResolver { if (!parsedVer || (version.split('.').length !== 2 && version.split('.').length !== 3)) { Debugging.log(`Resolving the version: ${version} ... it is invalid!`, this.context.eventStream); - const err = new DotnetVersionResolutionError(new EventCancellationError(`An invalid version was requested. Version: ${version}`), + const err = new DotnetVersionResolutionError(new EventCancellationError('DotnetVersionResolutionError', + `An invalid version was requested. Version: ${version}`), getAssumedInstallInfo(this.context, version, this.context.installMode)); this.context.eventStream.post(err); throw err.error; @@ -187,7 +191,7 @@ export class VersionResolver implements IVersionResolver { const response = await this.GetAvailableDotnetVersions(apiContext); if (!response) { - const err = new DotnetInvalidReleasesJSONError(new Error(`We could not reach the releases API ${this.releasesUrl} to download dotnet, is your machine offline or is this website down?`), + const err = new DotnetInvalidReleasesJSONError(new EventBasedError('DotnetInvalidReleasesJSONError', `We could not reach the releases API ${this.releasesUrl} to download dotnet, is your machine offline or is this website down?`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(err); throw err.error; @@ -216,7 +220,8 @@ export class VersionResolver implements IVersionResolver { { if(fullySpecifiedVersion.split('.').length < 2) { - const event = new DotnetVersionResolutionError(new EventCancellationError(`The requested version ${fullySpecifiedVersion} is invalid.`), + const event = new DotnetVersionResolutionError(new EventCancellationError('DotnetVersionResolutionError', + `The requested version ${fullySpecifiedVersion} is invalid.`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(event); throw event.error; @@ -236,7 +241,7 @@ export class VersionResolver implements IVersionResolver { const band : string | undefined = fullySpecifiedVersion.split('.')?.at(2)?.charAt(0); if(band === undefined) { - const event = new DotnetFeatureBandDoesNotExistError(new Error(`${VersionResolver.invalidFeatureBandErrorString}${fullySpecifiedVersion}.`), + const event = new DotnetFeatureBandDoesNotExistError(new EventCancellationError('DotnetFeatureBandDoesNotExistError', `${VersionResolver.invalidFeatureBandErrorString}${fullySpecifiedVersion}.`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(event); throw event.error; @@ -264,7 +269,8 @@ export class VersionResolver implements IVersionResolver { const patch : string | undefined = fullySpecifiedVersion.split('.')?.at(2)?.substring(1); if(patch === undefined || !this.isNumber(patch)) { - const event = new DotnetFeatureBandDoesNotExistError(new Error(`${VersionResolver.invalidFeatureBandErrorString}${fullySpecifiedVersion}.`), + const event = new DotnetFeatureBandDoesNotExistError(new EventCancellationError('DotnetFeatureBandDoesNotExistError', + `${VersionResolver.invalidFeatureBandErrorString}${fullySpecifiedVersion}.`), getInstallKeyFromContext(this.context)); this.context.eventStream.post(event); throw event.error; diff --git a/vscode-dotnet-runtime-library/src/Acquisition/WinMacGlobalInstaller.ts b/vscode-dotnet-runtime-library/src/Acquisition/WinMacGlobalInstaller.ts index 1f8c66e80f..aed202c11f 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/WinMacGlobalInstaller.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/WinMacGlobalInstaller.ts @@ -17,6 +17,7 @@ import { DotnetFileIntegrityCheckEvent, DotnetInstallCancelledByUserError, DotnetUnexpectedInstallerOSError, + EventBasedError, EventCancellationError, NetInstallerBeginExecutionEvent, NetInstallerEndExecutionEvent, @@ -87,7 +88,9 @@ export class WinMacGlobalInstaller extends IGlobalInstaller { ? this.acquisitionContext.acquisitionContext.requestingExtensionId : null)); return '0'; } - const err = new DotnetConflictingGlobalWindowsInstallError(new EventCancellationError(`An global install is already on the machine: version ${conflictingVersion}, that conflicts with the requested version. + const err = new DotnetConflictingGlobalWindowsInstallError(new EventCancellationError( + 'DotnetConflictingGlobalWindowsInstallError', + `An global install is already on the machine: version ${conflictingVersion}, that conflicts with the requested version. Please uninstall this version first if you would like to continue. If Visual Studio is installed, you may need to use the VS Setup Window to uninstall the SDK component.`), getInstallKeyFromContext(this.acquisitionContext)); this.acquisitionContext.eventStream.post(err); @@ -99,7 +102,8 @@ export class WinMacGlobalInstaller extends IGlobalInstaller { const canContinue = await this.installerFileHasValidIntegrity(installerFile); if(!canContinue) { - const err = new DotnetConflictingGlobalWindowsInstallError(new EventCancellationError(`The integrity of the .NET install file is invalid, or there was no integrity to check and you denied the request to continue with those risks. + const err = new DotnetConflictingGlobalWindowsInstallError(new EventCancellationError('DotnetConflictingGlobalWindowsInstallError', + `The integrity of the .NET install file is invalid, or there was no integrity to check and you denied the request to continue with those risks. We cannot verify .NET is safe to download at this time. Please try again later.`), getInstallKeyFromContext(this.acquisitionContext)); this.acquisitionContext.eventStream.post(err); throw err.error; @@ -119,7 +123,7 @@ We cannot verify .NET is safe to download at this time. Please try again later.` else if(installerResult === '1602') { // Special code for when user cancels the install - const err = new DotnetInstallCancelledByUserError(new EventCancellationError( + const err = new DotnetInstallCancelledByUserError(new EventCancellationError('DotnetInstallCancelledByUserError', `The install of .NET was cancelled by the user. Aborting.`), getInstallKeyFromContext(this.acquisitionContext)); this.acquisitionContext.eventStream.post(err); throw err.error; @@ -205,7 +209,8 @@ We cannot verify .NET is safe to download at this time. Please try again later.` return path.resolve(`/usr/local/share/dotnet/dotnet`); } - const err = new DotnetUnexpectedInstallerOSError(new Error(`The operating system ${os.platform()} is unsupported.`), getInstallKeyFromContext(this.acquisitionContext)); + const err = new DotnetUnexpectedInstallerOSError(new EventBasedError('DotnetUnexpectedInstallerOSError', + `The operating system ${os.platform()} is unsupported.`), getInstallKeyFromContext(this.acquisitionContext)); this.acquisitionContext.eventStream.post(err); throw err.error; } @@ -232,7 +237,8 @@ We cannot verify .NET is safe to download at this time. Please try again later.` let workingCommand = await this.commandRunner.tryFindWorkingCommand(possibleCommands); if(!workingCommand) { - const error = new Error(`The 'open' command on OSX was not detected. This is likely due to the PATH environment variable on your system being clobbered by another program. + const error = new EventBasedError('OSXOpenNotAvailableError', + `The 'open' command on OSX was not detected. This is likely due to the PATH environment variable on your system being clobbered by another program. Please correct your PATH variable or make sure the 'open' utility is installed so .NET can properly execute.`); this.acquisitionContext.eventStream.post(new OSXOpenNotAvailableError(error, getInstallKeyFromContext(this.acquisitionContext))); throw error; diff --git a/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts b/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts index bbb934e469..1a8c582956 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts @@ -15,22 +15,33 @@ import { DotnetInstall } from '../Acquisition/DotnetInstall'; export class EventCancellationError extends Error { + constructor(public readonly eventType : string, msg : string, stack ? : string) + { + super(msg); + } +} +export class EventBasedError extends Error +{ + constructor(public readonly eventType : string, msg : string, stack? : string) + { + super(msg); + } } export class DotnetAcquisitionStarted extends IEvent { public readonly eventName = 'DotnetAcquisitionStarted'; public readonly type = EventType.DotnetAcquisitionStart; - constructor(public readonly installKey: DotnetInstall, public readonly startingVersion: string, public readonly requestingExtensionId = '') { + constructor(public readonly install: DotnetInstall, public readonly startingVersion: string, public readonly requestingExtensionId = '') { super(); } public getProperties() { return { - ...InstallToStrings(this.installKey), + ...InstallToStrings(this.install), AcquisitionStartVersion : this.startingVersion, - AcquisitionInstallKey : this.installKey.installKey, + AcquisitionInstallKey : this.install.installKey, extensionId : this.requestingExtensionId }; } @@ -66,19 +77,19 @@ export class DotnetAcquisitionCompleted extends IEvent { public readonly eventName = 'DotnetAcquisitionCompleted'; public readonly type = EventType.DotnetAcquisitionCompleted; - constructor(public readonly installKey: DotnetInstall, public readonly dotnetPath: string, public readonly version: string) { + constructor(public readonly install: DotnetInstall, public readonly dotnetPath: string, public readonly version: string) { super(); } public getProperties(telemetry = false): { [key: string]: string } | undefined { if (telemetry) { - return {...InstallToStrings(this.installKey), - AcquisitionCompletedInstallKey : this.installKey.installKey, + return {...InstallToStrings(this.install), + AcquisitionCompletedInstallKey : this.install.installKey, AcquisitionCompletedVersion: this.version}; } else { - return {...InstallToStrings(this.installKey), + return {...InstallToStrings(this.install), AcquisitionCompletedVersion: this.version, - AcquisitionCompletedInstallKey : this.installKey.installKey, + AcquisitionCompletedInstallKey : this.install.installKey, AcquisitionCompletedDotnetPath : this.dotnetPath}; } @@ -106,6 +117,25 @@ export class DotnetRuntimeAcquisitionTotalSuccessEvent extends IEvent } } +export class DotnetGlobalSDKAcquisitionTotalSuccessEvent extends IEvent +{ + public readonly eventName = 'DotnetGlobalSDKAcquisitionTotalSuccessEvent'; + public readonly type = EventType.DotnetTotalSuccessEvent; + + constructor(public readonly startingVersion: string, public readonly installKey: DotnetInstall, public readonly requestingExtensionId = '', public readonly finalPath: string) { + super(); + } + + public getProperties() { + return { + AcquisitionStartVersion : this.startingVersion, + ...InstallToStrings(this.installKey), + ExtensionId : TelemetryUtilities.HashData(this.requestingExtensionId), + FinalPath : this.finalPath, + }; + } +} + export abstract class DotnetAcquisitionError extends IEvent { public readonly type = EventType.DotnetAcquisitionError; public isError = true; @@ -130,6 +160,32 @@ export abstract class DotnetAcquisitionError extends IEvent { } } +/** + * @remarks A wrapper around events to detect them as a failure to install the Global SDK. + * This allows us to count all errors and analyze them into categories. + * The event name for the failure cause is stored in the originalEventName property. + */ +export class DotnetGlobalSDKAcquisitionError extends DotnetAcquisitionError +{ + public eventName = 'DotnetGlobalSDKAcquisitionError'; + + constructor(public readonly error: Error, public readonly originalEventName : string, public readonly install: DotnetInstall | null) + { + super(error, install); + } + + public getProperties(telemetry = false): { [key: string]: string } | undefined { + return { + FailureMode: this.originalEventName, + ErrorName : this.error.name, + ErrorMessage : this.error.message, + StackTrace : this.error.stack ? TelemetryUtilities.HashAllPaths(this.error.stack) : '', + InstallKey : this.install?.installKey ?? 'null', + ...InstallToStrings(this.install!) + }; + } +} + export abstract class DotnetNonAcquisitionError extends IEvent { public readonly type = EventType.DotnetAcquisitionError; public isError = true; @@ -152,10 +208,10 @@ export abstract class DotnetInstallExpectedAbort extends IEvent { /** * * @param error The error that triggered, so the call stack, etc. can be analyzed. - * @param installKey For acquisition errors, you MUST include this install key. For commands unrelated to acquiring or managing a specific dotnet version, you + * @param install For acquisition errors, you MUST include this install key. For commands unrelated to acquiring or managing a specific dotnet version, you * have the option to leave this parameter null. If it is NULL during acquisition the extension CANNOT properly manage what it has finished installing or not. */ - constructor(public readonly error: Error, public readonly installKey: DotnetInstall | null) + constructor(public readonly error: Error, public readonly install: DotnetInstall | null) { super(); } @@ -164,8 +220,8 @@ export abstract class DotnetInstallExpectedAbort extends IEvent { return {ErrorName : this.error.name, ErrorMessage : this.error.message, StackTrace : this.error.stack ? TelemetryUtilities.HashAllPaths(this.error.stack) : '', - InstallKey : this.installKey?.installKey ?? 'null', - ...InstallToStrings(this.installKey)}; + InstallKey : this.install?.installKey ?? 'null', + ...InstallToStrings(this.install)}; } } @@ -406,8 +462,8 @@ export class DotnetAcquisitionDistroUnknownError extends DotnetInstallExpectedAb return {ErrorMessage : this.error.message, ErrorName : this.error.name, StackTrace : this.error.stack ? this.error.stack : '', - InstallKey : this.installKey?.installKey ?? 'null', - ...InstallToStrings(this.installKey!)}; + InstallKey : this.install?.installKey ?? 'null', + ...InstallToStrings(this.install!)}; } } diff --git a/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts b/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts index f176ea7a14..7bc57c9cfd 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts @@ -43,11 +43,11 @@ export class OutputChannelObserver implements IEventStreamObserver { case EventType.DotnetAcquisitionStart: const acquisitionStarted = event as DotnetAcquisitionStarted; - this.inProgressDownloads.push(acquisitionStarted.installKey.installKey); + this.inProgressDownloads.push(acquisitionStarted.install.installKey); if (this.inProgressDownloads.length > 1) { // Already a download in progress - this.outputChannel.appendLine(` -- Concurrent download of '${acquisitionStarted.installKey}' started!`); + this.outputChannel.appendLine(` -- Concurrent download of '${acquisitionStarted.install.installKey}' started!`); this.outputChannel.appendLine(''); } else { this.startDownloadIndicator(); @@ -59,10 +59,10 @@ export class OutputChannelObserver implements IEventStreamObserver { case EventType.DotnetAcquisitionCompleted: const acquisitionCompleted = event as DotnetAcquisitionCompleted; this.outputChannel.appendLine(' Done!'); - this.outputChannel.appendLine(`.NET ${acquisitionCompleted.installKey} executable path: ${acquisitionCompleted.dotnetPath}`); + this.outputChannel.appendLine(`.NET ${acquisitionCompleted.install.installKey} executable path: ${acquisitionCompleted.dotnetPath}`); this.outputChannel.appendLine(''); - this.inProgressVersionDone(acquisitionCompleted.installKey.installKey); + this.inProgressVersionDone(acquisitionCompleted.install.installKey); if (this.inProgressDownloads.length > 0) { const completedVersionString = `'${this.inProgressDownloads.join('\', \'')}'`; @@ -82,7 +82,7 @@ export class OutputChannelObserver implements IEventStreamObserver { this.outputChannel.append(`${ (event as DotnetAcquisitionAlreadyInstalled).requestingExtensionId }: Trying to install .NET ${ - (event as DotnetAcquisitionAlreadyInstalled).install + (event as DotnetAcquisitionAlreadyInstalled).install.installKey } but it already exists. No downloads or changes were made.\n`); } break; @@ -99,7 +99,7 @@ export class OutputChannelObserver implements IEventStreamObserver { case EventType.DotnetAcquisitionError: const error = event as DotnetAcquisitionError; this.outputChannel.appendLine('Error'); - this.outputChannel.appendLine(`Failed to download .NET ${error.install}:`); + this.outputChannel.appendLine(`Failed to download .NET ${error.install?.installKey}:`); this.outputChannel.appendLine(error.error.message); this.outputChannel.appendLine(''); @@ -107,10 +107,10 @@ export class OutputChannelObserver implements IEventStreamObserver { break; case EventType.DotnetInstallExpectedAbort: const abortEvent = event as DotnetInstallExpectedAbort; - this.outputChannel.appendLine(`Cancelled Installation of .NET ${abortEvent.installKey}.`); + this.outputChannel.appendLine(`Cancelled Installation of .NET ${abortEvent.install?.installKey}.`); this.outputChannel.appendLine(abortEvent.error.message); - this.updateDownloadIndicators(abortEvent.installKey?.installKey); + this.updateDownloadIndicators(abortEvent.install?.installKey); break; case EventType.DotnetUpgradedEvent: const upgradeMessage = event as DotnetUpgradedEvent; diff --git a/vscode-dotnet-runtime-library/src/Utils/CommandExecutor.ts b/vscode-dotnet-runtime-library/src/Utils/CommandExecutor.ts index 93c7b01336..10460ff9a8 100644 --- a/vscode-dotnet-runtime-library/src/Utils/CommandExecutor.ts +++ b/vscode-dotnet-runtime-library/src/Utils/CommandExecutor.ts @@ -39,7 +39,8 @@ import { SudoProcCommandExchangeEnd, SudoProcCommandExchangePing, TimeoutSudoCommandExecutionError, - TimeoutSudoProcessSpawnerError + TimeoutSudoProcessSpawnerError, + EventBasedError } from '../EventStream/EventStreamEvents'; import {exec} from '@vscode/sudo-prompt'; import * as lockfile from 'proper-lockfile'; @@ -123,7 +124,8 @@ status: ${commandResult.status?.toString()}` // GUI in WSL is not supported, so it will fail. // We had a working implementation that opens a vscode box and gets the user password, but that will require more security analysis. - const err = new DotnetWSLSecurityError(new EventCancellationError(`Automatic .NET SDK Installation is not yet supported in WSL due to VS Code & WSL limitations. + const err = new DotnetWSLSecurityError(new EventCancellationError('DotnetWSLSecurityError', + `Automatic .NET SDK Installation is not yet supported in WSL due to VS Code & WSL limitations. Please install the .NET SDK manually by following https://learn.microsoft.com/en-us/dotnet/core/install/linux-ubuntu. Then, add it to the path by following https://github.com/dotnet/vscode-dotnet-runtime/blob/main/Documentation/troubleshooting-runtime.md#manually-installing-net`, ), getInstallKeyFromContext(this.context)); this.context?.eventStream.post(err); @@ -190,7 +192,8 @@ ${stderr}`)); { if(error.code === 126) { - const cancelledErr = new CommandExecutionUserRejectedPasswordRequest(new EventCancellationError(`Cancelling .NET Install, as command ${fullCommandString} failed. + const cancelledErr = new CommandExecutionUserRejectedPasswordRequest(new EventCancellationError('CommandExecutionUserRejectedPasswordRequest', + `Cancelling .NET Install, as command ${fullCommandString} failed. The user refused the password prompt.`), getInstallKeyFromContext(this.context)); this.context?.eventStream.post(cancelledErr); @@ -198,7 +201,8 @@ The user refused the password prompt.`), } else if(error.code === 111777) { - const securityErr = new CommandExecutionUnknownCommandExecutionAttempt(new EventCancellationError(`Cancelling .NET Install, as command ${fullCommandString} is UNKNOWN. + const securityErr = new CommandExecutionUnknownCommandExecutionAttempt(new EventCancellationError('CommandExecutionUnknownCommandExecutionAttempt', + `Cancelling .NET Install, as command ${fullCommandString} is UNKNOWN. Please report this at https://github.com/dotnet/vscode-dotnet-runtime/issues.`), getInstallKeyFromContext(this.context)); this.context?.eventStream.post(securityErr); @@ -270,7 +274,7 @@ Please report this at https://github.com/dotnet/vscode-dotnet-runtime/issues.`), if(!isLive && errorIfDead) { - const err = new TimeoutSudoProcessSpawnerError(new Error(`We are unable to spawn the process to run commands under sudo for installing .NET. + const err = new TimeoutSudoProcessSpawnerError(new EventCancellationError('TimeoutSudoProcessSpawnerError', `We are unable to spawn the process to run commands under sudo for installing .NET. Process Directory: ${this.sudoProcessCommunicationDir} failed with error mode: ${errorIfDead}. It had previously spawned: ${this.hasEverLaunchedSudoFork}.`), getInstallKeyFromContext(this.context)); this.context?.eventStream.post(err); @@ -366,7 +370,8 @@ It had previously spawned: ${this.hasEverLaunchedSudoFork}.`), getInstallKeyFrom if(!commandOutputJson && terminalFailure) { - const err = new TimeoutSudoCommandExecutionError(new Error(`Timeout: The master process with command ${commandToExecuteString} never finished executing. + const err = new TimeoutSudoCommandExecutionError(new EventCancellationError('TimeoutSudoCommandExecutionError', + `Timeout: The master process with command ${commandToExecuteString} never finished executing. Process Directory: ${this.sudoProcessCommunicationDir} failed with error mode: ${terminalFailure}. It had previously spawned: ${this.hasEverLaunchedSudoFork}.`), getInstallKeyFromContext(this.context)); this.context?.eventStream.post(err); @@ -397,7 +402,8 @@ ${stderr}`)); if(statusCode !== '0' && failOnNonZeroExit) { - const err = new CommandExecutionNonZeroExitFailure(new Error(`Cancelling .NET Install, as command ${commandToExecuteString} returned with status ${statusCode}.`), + const err = new CommandExecutionNonZeroExitFailure(new EventBasedError('CommandExecutionNonZeroExitFailure', + `Cancelling .NET Install, as command ${commandToExecuteString} returned with status ${statusCode}.`), getInstallKeyFromContext(this.context)); this.context?.eventStream.post(err); throw err.error; diff --git a/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts b/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts index f9d8037d84..eda92e795f 100644 --- a/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts +++ b/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts @@ -7,13 +7,18 @@ import * as open from 'open'; import { DotnetCommandFailed, DotnetCommandSucceeded, + DotnetGlobalSDKAcquisitionError, DotnetInstallExpectedAbort, - DotnetNotInstallRelatedCommandFailed + DotnetNotInstallRelatedCommandFailed, + EventCancellationError } from '../EventStream/EventStreamEvents'; import { getInstallKeyFromContext } from './InstallKeyUtilities'; import { IIssueContext } from './IIssueContext'; import { formatIssueUrl } from './IssueReporter'; import { IAcquisitionWorkerContext } from '../Acquisition/IAcquisitionWorkerContext'; +import { GetDotnetInstallInfo } from '../Acquisition/DotnetInstall'; +import { DotnetCoreAcquisitionWorker } from '../Acquisition/DotnetCoreAcquisitionWorker'; +/* tslint:disable:no-any */ export enum AcquireErrorConfiguration { DisplayAllErrorPopups = 0, @@ -57,7 +62,7 @@ export async function callWithErrorHandling(callback: () => T, context: IIssu context.eventStream.post(new DotnetCommandSucceeded(context.commandName)); return result; } - catch (caughtError) + catch (caughtError : any) { const error = caughtError as Error; if(!isCancellationStyleError(error)) @@ -69,6 +74,15 @@ export async function callWithErrorHandling(callback: () => T, context: IIssu ); } + if(acquireContext?.installMode === 'sdk' && acquireContext.acquisitionContext?.installType === 'global') + { + context.eventStream.post(new DotnetGlobalSDKAcquisitionError(error, (caughtError?.eventType) ?? 'Unknown', + GetDotnetInstallInfo(acquireContext.acquisitionContext.version, acquireContext.installMode, 'global', acquireContext.installingArchitecture ?? + + DotnetCoreAcquisitionWorker.defaultArchitecture() + ))); + } + if (context.errorConfiguration === AcquireErrorConfiguration.DisplayAllErrorPopups) { if ((error.message as string).includes(timeoutConstants.timeoutMessage)) @@ -145,5 +159,6 @@ async function configureManualInstall(context: IIssueContext, requestingExtensio function isCancellationStyleError(error : Error) { // Handle both when the event.error or event itself is posted. - return error && error.constructor && (error.constructor.name === 'UserCancelledError' || error.constructor.name === 'EventCancellationError') || error instanceof DotnetInstallExpectedAbort; + return error && error.constructor && (error.constructor.name === 'UserCancelledError' || error.constructor.name === 'EventCancellationError') || + error instanceof DotnetInstallExpectedAbort || error instanceof EventCancellationError; } \ No newline at end of file diff --git a/vscode-dotnet-runtime-library/src/Utils/ExtensionConfigurationWorker.ts b/vscode-dotnet-runtime-library/src/Utils/ExtensionConfigurationWorker.ts index ac9aea0982..765ce05a0b 100644 --- a/vscode-dotnet-runtime-library/src/Utils/ExtensionConfigurationWorker.ts +++ b/vscode-dotnet-runtime-library/src/Utils/ExtensionConfigurationWorker.ts @@ -2,6 +2,7 @@ * Licensed to the .NET Foundation under one or more agreements. * The .NET Foundation licenses this file to you under the MIT license. *--------------------------------------------------------------------------------------------*/ +import { EventBasedError } from '../EventStream/EventStreamEvents'; import { IExistingPaths, IExtensionConfiguration, ILocalExistingPath } from '../IExtensionContext'; import { IExtensionConfigurationWorker } from './IExtensionConfigurationWorker'; @@ -26,14 +27,14 @@ export class ExtensionConfigurationWorker implements IExtensionConfigurationWork public getSharedPathConfigurationValue(): string | undefined { if (!this.sharedExistingDotnetPath) { - throw Error(this.unsupportedMessage); + throw new EventBasedError('unsupportedSharedPathConfiguration', this.unsupportedMessage); } return this.pathConfigValueName ? this.extensionConfiguration.get(this.sharedExistingDotnetPath) : undefined; } public async setSharedPathConfigurationValue(configValue: string): Promise { if (!this.sharedExistingDotnetPath) { - throw Error(this.unsupportedMessage); + throw new EventBasedError('unsupportedSharedExistingPathConfiguration', this.unsupportedMessage); } await this.extensionConfiguration.update(this.sharedExistingDotnetPath, configValue, true); } diff --git a/vscode-dotnet-runtime-library/src/Utils/InstallKeyUtilities.ts b/vscode-dotnet-runtime-library/src/Utils/InstallKeyUtilities.ts index c7da47fbc1..836fc87c16 100644 --- a/vscode-dotnet-runtime-library/src/Utils/InstallKeyUtilities.ts +++ b/vscode-dotnet-runtime-library/src/Utils/InstallKeyUtilities.ts @@ -21,7 +21,7 @@ export function getInstallKeyFromContext(ctx : IAcquisitionWorkerContext | undef return { installKey : DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(acquireContext.version, acquireContext.architecture, - acquireContext.installType ? acquireContext.installType === 'global' : false), + acquireContext.installType), version: acquireContext.version, architecture: acquireContext.architecture, isGlobal: acquireContext.installType ? acquireContext.installType === 'global' : false, diff --git a/vscode-dotnet-runtime-library/src/Utils/WebRequestWorker.ts b/vscode-dotnet-runtime-library/src/Utils/WebRequestWorker.ts index b2817d9a71..a88088fe2f 100644 --- a/vscode-dotnet-runtime-library/src/Utils/WebRequestWorker.ts +++ b/vscode-dotnet-runtime-library/src/Utils/WebRequestWorker.ts @@ -7,7 +7,7 @@ import axiosRetry from 'axios-retry'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { getProxySettings } from 'get-proxy-settings'; import { AxiosCacheInstance, buildMemoryStorage, setupCache } from 'axios-cache-interceptor'; -import {SuppressedAcquisitionError, WebRequestError, WebRequestSent } from '../EventStream/EventStreamEvents'; +import {EventBasedError, SuppressedAcquisitionError, WebRequestError, WebRequestSent } from '../EventStream/EventStreamEvents'; import { getInstallKeyFromContext } from './InstallKeyUtilities'; import * as fs from 'fs'; @@ -69,7 +69,7 @@ export class WebRequestWorker { if(url === '' || !url) { - throw new Error(`Request to the url ${this.url} failed, as the URL is invalid.`); + throw new EventBasedError('AxiosGetFailedWithInvalidURL', `Request to the url ${this.url} failed, as the URL is invalid.`); } const response = await this.client.get(url, { ...options }); @@ -207,7 +207,7 @@ export class WebRequestWorker if(isAxiosError(error)) { const axiosBasedError = error as AxiosError; - const summarizedError = new Error( + const summarizedError = new EventBasedError('WebRequestFailedFromAxios', `Request to ${this.url} Failed: ${axiosBasedError.message}. Aborting. ${axiosBasedError.cause? `Error Cause: ${axiosBasedError.cause!.message}` : ``} Please ensure that you are online. @@ -218,7 +218,8 @@ If you're on a proxy and disable registry access, you must set the proxy in our } else { - const genericError = new Error(`Web Request to ${this.url} Failed: ${error.message}. Aborting. Stack: ${'stack' in error ? error?.stack : 'unavailable.'}`); + const genericError = new EventBasedError('WebRequestFailedGenerically', + `Web Request to ${this.url} Failed: ${error.message}. Aborting. Stack: ${'stack' in error ? error?.stack : 'unavailable.'}`); this.context.eventStream.post(new WebRequestError(genericError, getInstallKeyFromContext(this.context))); throw genericError; } diff --git a/vscode-dotnet-runtime-library/src/test/mocks/MockObjects.ts b/vscode-dotnet-runtime-library/src/test/mocks/MockObjects.ts index 84e136eb14..1f97930350 100644 --- a/vscode-dotnet-runtime-library/src/test/mocks/MockObjects.ts +++ b/vscode-dotnet-runtime-library/src/test/mocks/MockObjects.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import { InstallScriptAcquisitionWorker } from '../../Acquisition/InstallScriptAcquisitionWorker'; import { VersionResolver } from '../../Acquisition/VersionResolver'; import { DotnetCoreAcquisitionWorker } from '../../Acquisition/DotnetCoreAcquisitionWorker'; -import { DotnetAcquisitionCompleted, TestAcquireCalled } from '../../EventStream/EventStreamEvents'; +import { DotnetAcquisitionCompleted, EventBasedError, TestAcquireCalled } from '../../EventStream/EventStreamEvents'; import { IExistingPaths, IExtensionConfiguration, ILocalExistingPath } from '../../IExtensionContext'; import { FileUtilities } from '../../Utils/FileUtilities'; import { WebRequestWorker } from '../../Utils/WebRequestWorker'; @@ -36,7 +36,6 @@ import { ITelemetryReporter } from '../../EventStream/TelemetryObserver'; import { IUtilityContext } from '../../Utils/IUtilityContext'; import { IVSCodeEnvironment } from '../../Utils/IVSCodeEnvironment'; import { IDotnetAcquireResult } from '../../IDotnetAcquireResult'; -import { IDotnetCoreAcquisitionWorker } from '../../Acquisition/IDotnetCoreAcquisitionWorker'; import { GetDotnetInstallInfo } from '../../Acquisition/DotnetInstall'; import { DotnetInstall } from '../../Acquisition/DotnetInstall'; import { InstallTracker } from '../../Acquisition/InstallTracker'; @@ -78,7 +77,7 @@ export class NoInstallAcquisitionInvoker extends IAcquisitionInvoker { public installDotnet(installContext: IDotnetInstallationContext): Promise { return new Promise((resolve, reject) => { this.eventStream.post(new TestAcquireCalled(installContext)); - const install = GetDotnetInstallInfo(installContext.version, installContext.installMode, false, installContext.architecture) + const install = GetDotnetInstallInfo(installContext.version, installContext.installMode, 'local', installContext.architecture) this.eventStream.post(new DotnetAcquisitionCompleted( install, installContext.dotnetPath, installContext.version)); resolve(); @@ -149,7 +148,7 @@ export class RejectingAcquisitionInvoker extends IAcquisitionInvoker { export class ErrorAcquisitionInvoker extends IAcquisitionInvoker { public installDotnet(installContext: IDotnetInstallationContext): Promise { - throw new Error('Command Failed'); + throw new EventBasedError('MockErrorAcquisitionInvokerFailure', 'Command Failed'); } } diff --git a/vscode-dotnet-runtime-library/src/test/unit/DotnetCoreAcquisitionWorker.test.ts b/vscode-dotnet-runtime-library/src/test/unit/DotnetCoreAcquisitionWorker.test.ts index cc474609f0..c8fab68aba 100644 --- a/vscode-dotnet-runtime-library/src/test/unit/DotnetCoreAcquisitionWorker.test.ts +++ b/vscode-dotnet-runtime-library/src/test/unit/DotnetCoreAcquisitionWorker.test.ts @@ -31,8 +31,6 @@ import { getMockAcquisitionContext, getMockAcquisitionWorker } from './TestUtili import { IAcquisitionInvoker } from '../../Acquisition/IAcquisitionInvoker'; import { InstallOwner, InstallRecord } from '../../Acquisition/InstallRecord'; import { GetDotnetInstallInfo } from '../../Acquisition/DotnetInstall'; -import { DotnetInstallOrStr } from '../../Acquisition/DotnetInstall'; -import { DotnetInstall } from '../../Acquisition/DotnetInstall'; import { DotnetInstallMode } from '../../Acquisition/DotnetInstallMode'; const assert = chai.assert; @@ -85,17 +83,18 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { //  No errors in event stream assert.notExists(eventStream.events.find(event => event.type === EventType.DotnetAcquisitionError)); const startEvent = eventStream.events - .find(event => event instanceof DotnetAcquisitionStarted && (event as DotnetAcquisitionStarted).installKey.installKey === installKey); + .find(event => event instanceof DotnetAcquisitionStarted && (event as DotnetAcquisitionStarted).install.installKey === installKey); assert.exists(startEvent, 'The acquisition started event appears'); const completedEvent = eventStream.events - .find(event => event instanceof DotnetAcquisitionCompleted && (event as DotnetAcquisitionCompleted).installKey.installKey === installKey + .find(event => event instanceof DotnetAcquisitionCompleted && (event as DotnetAcquisitionCompleted).install.installKey === installKey && (event as DotnetAcquisitionCompleted).dotnetPath === expectedPath); assert.exists(completedEvent, 'The acquisition completed event appears'); //  Acquire got called with the correct args const acquireEvent = eventStream.events.find(event => event instanceof TestAcquireCalled && - ((DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture((event as TestAcquireCalled).context.version, (event as TestAcquireCalled).context.architecture))) + ((DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture((event as TestAcquireCalled).context.version, + (event as TestAcquireCalled).context.architecture, (event as TestAcquireCalled).context.installType))) === installKey) as TestAcquireCalled; assert.exists(acquireEvent, 'The acquisition acquire event appears'); assert.equal(acquireEvent!.context.dotnetPath, expectedPath, 'The acquisition went to the expected dotnetPath'); @@ -218,7 +217,7 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { const version = '1.0'; const [acquisitionWorker, eventStream, context, invoker] = setupWorker('runtime', version); const installKey = acquisitionWorker.getInstallKey(version); - const install = GetDotnetInstallInfo(version, 'runtime', false, os.arch()); + const install = GetDotnetInstallInfo(version, 'runtime', 'local', os.arch()); const res = await acquisitionWorker.acquireRuntime(version, invoker); await assertAcquisitionSucceeded(installKey, res.dotnetPath, eventStream, context); @@ -314,7 +313,7 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { const [acquisitionWorker, eventStream, _, __] = setupWorker('runtime', version); const acquisitionInvoker = new RejectingAcquisitionInvoker(eventStream); - return assert.isRejected(acquisitionWorker.acquireRuntime(version, acquisitionInvoker), '.NET Acquisition Failed: Installation failed: Rejecting message'); + return assert.isRejected(acquisitionWorker.acquireRuntime(version, acquisitionInvoker), '.NET Acquisition Failed: Rejecting message'); }).timeout(expectedTimeoutTime); test('Repeated SDK Acquisition', async () => { @@ -338,7 +337,7 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { const acquisitionWorker = getMockAcquisitionWorker('runtime', version); const acquisitionInvoker = new MockAcquisitionInvoker(acquisitionContext, installApostropheFolder); - const installKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(version, os.arch()); + const installKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(version, os.arch(), 'local'); const result = await acquisitionWorker.acquireRuntime(version, acquisitionInvoker); const expectedPath = getExpectedPath(installKey, true); assert.equal(result.dotnetPath, expectedPath); diff --git a/vscode-dotnet-runtime-library/src/test/unit/InstallTracker.test.ts b/vscode-dotnet-runtime-library/src/test/unit/InstallTracker.test.ts index 928bd1c1d6..065bfce4a3 100644 --- a/vscode-dotnet-runtime-library/src/test/unit/InstallTracker.test.ts +++ b/vscode-dotnet-runtime-library/src/test/unit/InstallTracker.test.ts @@ -3,12 +3,10 @@ * The .NET Foundation licenses this file to you under the MIT license. *--------------------------------------------------------------------------------------------*/ import * as chai from 'chai'; -import { InstallationValidator } from '../../Acquisition/InstallationValidator'; import { MockEventStream, MockExtensionContext, MockInstallTracker } from '../mocks/MockObjects'; import * as os from 'os'; -import { DotnetInstall, GetDotnetInstallInfo } from '../../Acquisition/DotnetInstall'; +import { DotnetInstall } from '../../Acquisition/DotnetInstall'; import { getMockAcquisitionContext } from './TestUtility'; -import { getInstallKeyFromContext } from '../../Utils/InstallKeyUtilities'; import { InstallRecord } from '../../Acquisition/InstallRecord'; const assert = chai.assert; diff --git a/vscode-dotnet-runtime-library/src/test/unit/InstallationValidator.test.ts b/vscode-dotnet-runtime-library/src/test/unit/InstallationValidator.test.ts index 761b4d71bd..56e1ea3a36 100644 --- a/vscode-dotnet-runtime-library/src/test/unit/InstallationValidator.test.ts +++ b/vscode-dotnet-runtime-library/src/test/unit/InstallationValidator.test.ts @@ -15,7 +15,7 @@ suite('InstallationValidator Unit Tests', () => { const validator = new InstallationValidator(eventStream); test('Error With Invalid File Structure', async () => { - const install = GetDotnetInstallInfo('7.0', 'runtime', false, os.arch()); + const install = GetDotnetInstallInfo('7.0', 'runtime', 'local', os.arch()); assert.throws(() => validator.validateDotnetInstall(install, ''), `Validation of .dotnet installation for version`); assert.throws(() => validator.validateDotnetInstall(install, ''), `fail`); }); diff --git a/vscode-dotnet-runtime-library/src/test/unit/WinMacGlobalInstaller.test.ts b/vscode-dotnet-runtime-library/src/test/unit/WinMacGlobalInstaller.test.ts index f0f131e84d..bbfa0c229c 100644 --- a/vscode-dotnet-runtime-library/src/test/unit/WinMacGlobalInstaller.test.ts +++ b/vscode-dotnet-runtime-library/src/test/unit/WinMacGlobalInstaller.test.ts @@ -109,7 +109,7 @@ suite('Windows & Mac Global Installer Tests', () => ${mockVersion} REG_DWORD 0x1 `; - const install = GetDotnetInstallInfo(mockVersion, 'sdk', true, os.arch()); + const install = GetDotnetInstallInfo(mockVersion, 'sdk', 'global', os.arch()); const result = await installer.installSDK(install); assert.exists(result); assert.equal(result, '0'); @@ -123,7 +123,7 @@ suite('Windows & Mac Global Installer Tests', () => { mockExecutor.fakeReturnValue = `0`; installer.cleanupInstallFiles = false; - const install = GetDotnetInstallInfo(mockVersion, 'sdk', true, os.arch()); + const install = GetDotnetInstallInfo(mockVersion, 'sdk', 'global', os.arch()); const result = await installer.installSDK(install); assert.exists(result); assert.equal(result, '0'); @@ -152,7 +152,7 @@ suite('Windows & Mac Global Installer Tests', () => { mockExecutor.fakeReturnValue = `0`; installer.cleanupInstallFiles = false; - const install = GetDotnetInstallInfo(mockVersion, 'sdk', true, os.arch()); + const install = GetDotnetInstallInfo(mockVersion, 'sdk', 'global', os.arch()); const result = await installer.installSDK(install); assert.exists(result, 'The installation on test was successful'); assert.equal(result, '0', 'No errors were reported by the fake install'); diff --git a/vscode-dotnet-sdk-extension/src/extension.ts b/vscode-dotnet-sdk-extension/src/extension.ts index 1ee42b0cdf..33bb21aa44 100644 --- a/vscode-dotnet-sdk-extension/src/extension.ts +++ b/vscode-dotnet-sdk-extension/src/extension.ts @@ -24,7 +24,7 @@ import { IDotnetAcquireContext, IDotnetListVersionsContext, IDotnetUninstallContext, - IDotnetListVersionsResult, + EventBasedError, IDotnetVersion, IEventStreamContext, IExtensionContext, @@ -164,7 +164,8 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE if(commandContext.version === '' || !commandContext.version) { - throw Error(`No version was defined to install.`); + throw new EventBasedError('BadContextualSDKExtensionVersionError', + `No version was defined to install.`); } const globalInstallerResolver = new GlobalInstallerResolver(acquisitionContext, commandContext.version);