diff --git a/sample/package-lock.json b/sample/package-lock.json index 405a80ec4a..e5f05d7664 100644 --- a/sample/package-lock.json +++ b/sample/package-lock.json @@ -35,7 +35,7 @@ }, "../vscode-dotnet-runtime-extension": { "name": "vscode-dotnet-runtime", - "version": "2.0.0", + "version": "2.0.1", "license": "MIT", "dependencies": { "axios": "^1.3.4", @@ -128,7 +128,7 @@ } }, "../vscode-dotnet-sdk-extension": { - "version": "2.0.0", + "version": "2.0.1", "license": "MIT", "dependencies": { "@types/chai": "4.2.22", diff --git a/sample/yarn.lock b/sample/yarn.lock index 6943604fb8..036a582564 100644 --- a/sample/yarn.lock +++ b/sample/yarn.lock @@ -1832,7 +1832,7 @@ "vscode-dotnet-runtime@file:../vscode-dotnet-runtime-extension": "resolved" "file:../vscode-dotnet-runtime-extension" - "version" "2.0.0" + "version" "2.0.1" dependencies: "axios" "^1.3.4" "axios-cache-interceptor" "^1.0.1" @@ -1860,7 +1860,7 @@ "vscode-dotnet-sdk@file:../vscode-dotnet-sdk-extension": "resolved" "file:../vscode-dotnet-sdk-extension" - "version" "2.0.0" + "version" "2.0.1" dependencies: "@types/chai" "4.2.22" "@types/chai-as-promised" "^7.1.4" diff --git a/vscode-dotnet-runtime-extension/CHANGELOG.md b/vscode-dotnet-runtime-extension/CHANGELOG.md index 0fd064292d..6fbb079b89 100644 --- a/vscode-dotnet-runtime-extension/CHANGELOG.md +++ b/vscode-dotnet-runtime-extension/CHANGELOG.md @@ -6,7 +6,27 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic Versioning]. ## [Unreleased] -## [2.0.0] - 2023-09-18 +## [2.0.1] - 2024-01-03 + +Fixes several key bugs with installing .NET: + +- Ubuntu Global SDK Installs would fail for the first time on 18.04. +- The extension would print ... forever after installation failure for certain errors. +- The extension would fail to read Ubuntu directories properly for the first time if PMC was installed in certain scenarios. +- GitHub Forms is now added. +- Corrects behavior on our unknown Ubuntu Versions by estimating the correct behavior for known versions. +- Improve timeout error handling +- Catch bug in the caching library we use to prevent it from failing to cache +- Remove bug where status bar would stay red when cancelling install +- Fix bug where Linux would not update .NET SDKs properly when it could update instead of install +- Detect when a user cancels the installation in the password prompt or windows installer so we can remove the error failure message +- Adds more logging to the extension to improve diagnostics and speed to resolve github issues +- Improve installation management, so that the extension is aware that installs it manages can be deleted by external sources, to prevent it from thinking something is installed when it is no longer installed. +- Fix an issue where the uninstall command would think it could uninstall a global SDK. This is not the case. +- Improve detection logic for existing Ubuntu and RHEL installations of linux to prevent installing when it is not needed +- Several other key issues. + +## [2.0.0] - 2023-11-23 The '.NET Runtime Install Tool' has been renamed to the '.NET Install Tool.' diff --git a/vscode-dotnet-runtime-extension/package-lock.json b/vscode-dotnet-runtime-extension/package-lock.json index 19c9170317..5086f95b43 100644 --- a/vscode-dotnet-runtime-extension/package-lock.json +++ b/vscode-dotnet-runtime-extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-dotnet-runtime", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-dotnet-runtime", - "version": "2.0.0", + "version": "2.0.1", "license": "MIT", "dependencies": { "axios": "^1.3.4", diff --git a/vscode-dotnet-runtime-extension/package.json b/vscode-dotnet-runtime-extension/package.json index 0c4d18346f..c2b426b46d 100644 --- a/vscode-dotnet-runtime-extension/package.json +++ b/vscode-dotnet-runtime-extension/package.json @@ -13,7 +13,7 @@ "description": "This extension installs and manages different versions of the .NET SDK and Runtime.", "appInsightsKey": "02dc18e0-7494-43b2-b2a3-18ada5fcb522", "icon": "images/dotnetIcon.png", - "version": "2.0.0", + "version": "2.0.1", "publisher": "ms-dotnettools", "engines": { "vscode": "^1.74.0" diff --git a/vscode-dotnet-runtime-extension/src/extension.ts b/vscode-dotnet-runtime-extension/src/extension.ts index 43f4753028..7d475d0e05 100644 --- a/vscode-dotnet-runtime-extension/src/extension.ts +++ b/vscode-dotnet-runtime-extension/src/extension.ts @@ -106,7 +106,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE showLogCommand: `${commandPrefix}.${commandKeys.showAcquisitionLog}`, packageJson } as IEventStreamContext; - const [eventStream, outputChannel, loggingObserver, eventStreamObservers, telemetryObserver] = registerEventStream(eventStreamContext, vsCodeExtensionContext, utilContext); + const [globalEventStream, outputChannel, loggingObserver, eventStreamObservers, telemetryObserver] = registerEventStream(eventStreamContext, vsCodeExtensionContext, utilContext); // Setting up command-shared classes for Runtime & SDK Acquisition @@ -125,8 +125,8 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE const dotnetAcquireRegistration = vscode.commands.registerCommand(`${commandPrefix}.${commandKeys.acquire}`, async (commandContext: IDotnetAcquireContext) => { let fullyResolvedVersion = ''; const dotnetPath = await callWithErrorHandling>(async () => { - eventStream.post(new DotnetRuntimeAcquisitionStarted(commandContext.requestingExtensionId)); - eventStream.post(new DotnetAcquisitionRequested(commandContext.version, commandContext.requestingExtensionId)); + globalEventStream.post(new DotnetRuntimeAcquisitionStarted(commandContext.requestingExtensionId)); + globalEventStream.post(new DotnetAcquisitionRequested(commandContext.version, commandContext.requestingExtensionId)); runtimeAcquisitionWorker.setAcquisitionContext(commandContext); telemetryObserver?.setAcquisitionContext(runtimeContext, commandContext); @@ -154,7 +154,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE }, runtimeIssueContextFunctor(commandContext.errorConfiguration, 'acquire', commandContext.version), commandContext.requestingExtensionId, runtimeContext); const installKey = runtimeAcquisitionWorker.getInstallKey(fullyResolvedVersion); - eventStream.post(new DotnetRuntimeAcquisitionTotalSuccessEvent(commandContext.version, installKey, commandContext.requestingExtensionId ?? '', dotnetPath?.dotnetPath ?? '')); + globalEventStream.post(new DotnetRuntimeAcquisitionTotalSuccessEvent(commandContext.version, installKey, commandContext.requestingExtensionId ?? '', dotnetPath?.dotnetPath ?? '')); return dotnetPath; }); @@ -167,8 +167,8 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE const pathResult = callWithErrorHandling(async () => { - eventStream.post(new DotnetSDKAcquisitionStarted(commandContext.requestingExtensionId)); - eventStream.post(new DotnetAcquisitionRequested(commandContext.version, commandContext.requestingExtensionId)); + globalEventStream.post(new DotnetSDKAcquisitionStarted(commandContext.requestingExtensionId)); + globalEventStream.post(new DotnetAcquisitionRequested(commandContext.version, commandContext.requestingExtensionId)); const existingPath = await resolveExistingPathIfExists(existingPathConfigWorker, commandContext); if(existingPath) @@ -196,7 +196,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE const dotnetAcquireStatusRegistration = vscode.commands.registerCommand(`${commandPrefix}.${commandKeys.acquireStatus}`, async (commandContext: IDotnetAcquireContext) => { const pathResult = callWithErrorHandling(async () => { - eventStream.post(new DotnetAcquisitionStatusRequested(commandContext.version, commandContext.requestingExtensionId)); + globalEventStream.post(new DotnetAcquisitionStatusRequested(commandContext.version, commandContext.requestingExtensionId)); const resolvedVersion = await runtimeVersionResolver.getFullRuntimeVersion(commandContext.version); const dotnetPath = await runtimeAcquisitionWorker.acquireStatus(resolvedVersion, true); return dotnetPath; @@ -220,7 +220,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE const result = cp.spawnSync(commandContext.command, commandContext.arguments); const installer = new DotnetCoreDependencyInstaller(); if (installer.signalIndicatesMissingLinuxDependencies(result.signal!)) { - eventStream.post(new DotnetAcquisitionMissingLinuxDependencies()); + globalEventStream.post(new DotnetAcquisitionMissingLinuxDependencies()); await installer.promptLinuxDependencyInstall('Failed to run .NET runtime.'); } }, runtimeIssueContextFunctor(commandContext.errorConfiguration, 'ensureDependencies')); @@ -239,7 +239,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE const existingPath = existingPathResolver.resolveExistingPath(configResolver.getPathConfigurationValue(), commandContext.requestingExtensionId, displayWorker); if (existingPath) { - eventStream.post(new DotnetExistingPathResolutionCompleted(existingPath.dotnetPath)); + globalEventStream.post(new DotnetExistingPathResolutionCompleted(existingPath.dotnetPath)); return new Promise((resolve) => { resolve(existingPath); }); @@ -254,8 +254,8 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE return { storagePath: context.globalStoragePath, extensionState: context.globalState, - eventStream, - installationValidator: new InstallationValidator(eventStream), + eventStream: globalEventStream, + installationValidator: new InstallationValidator(globalEventStream), timeoutSeconds: resolvedTimeoutSeconds, installDirectoryProvider: isRuntimeInstall ? new RuntimeInstallationDirectoryProvider(context.globalStoragePath): new SdkInstallationDirectoryProvider(context.globalStoragePath), proxyUrl: proxyLink, @@ -276,7 +276,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE errorConfiguration: errorConfiguration || AcquireErrorConfiguration.DisplayAllErrorPopups, displayWorker, extensionConfigWorker: configResolver, - eventStream, + eventStream: globalEventStream, commandName, version, moreInfoUrl, diff --git a/vscode-dotnet-runtime-library/distro-data/distro-support.json b/vscode-dotnet-runtime-library/distro-data/distro-support.json index c4df64a12c..747c1b182d 100644 --- a/vscode-dotnet-runtime-library/distro-data/distro-support.json +++ b/vscode-dotnet-runtime-library/distro-data/distro-support.json @@ -271,7 +271,7 @@ "commandParts": ["-f", "{path}"] } ], - "expectedDistroFeedInstallDirectory": "/usr/lib64/dotnet/dotnet", + "expectedDistroFeedInstallDirectory": "/usr/lib64/dotnet", "expectedMicrosoftFeedInstallDirectory": "", "installedSDKVersionsCommand": [ { diff --git a/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts b/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts index e18eac8b35..992e9d303a 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts @@ -335,7 +335,7 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker this.context.eventStream.post(new DotnetBeginGlobalInstallerExecution(`Beginning to run installer for ${installKey} in ${os.platform()}.`)) const installerResult = await installer.installSDK(); - this.context.eventStream.post(new DotnetCompletedGlobalInstallerExecution(`Beginning to run installer for ${installKey} in ${os.platform()}.`)) + this.context.eventStream.post(new DotnetCompletedGlobalInstallerExecution(`Completed installer for ${installKey} in ${os.platform()}.`)) if(installerResult !== '0') { diff --git a/vscode-dotnet-runtime-library/src/Acquisition/GenericDistroSDKProvider.ts b/vscode-dotnet-runtime-library/src/Acquisition/GenericDistroSDKProvider.ts index 4d26a9ad78..b620423872 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/GenericDistroSDKProvider.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/GenericDistroSDKProvider.ts @@ -35,12 +35,23 @@ export class GenericDistroSDKProvider extends IDistroDotnetSDKProvider public async getInstalledGlobalDotnetPathIfExists(installType : LinuxInstallType) : Promise { const commandResult = await this.commandRunner.executeMultipleCommands(this.myDistroCommands(this.currentInstallPathCommandKey)); + + const oldReturnStatusSetting = this.commandRunner.returnStatus; + this.commandRunner.returnStatus = true; + const commandSignal = await this.commandRunner.executeMultipleCommands(this.myDistroCommands(this.currentInstallPathCommandKey)); + this.commandRunner.returnStatus = oldReturnStatusSetting; + + if(commandSignal[0] !== '0') // no dotnet error can be returned, dont want to try to parse this as a path + { + return null; + } + if(commandResult[0]) { commandResult[0] = commandResult[0].trim(); } - if(commandResult && this.resolvePathAsSymlink) + if(commandResult[0] && this.resolvePathAsSymlink) { let symLinkReadCommand = this.myDistroCommands(this.readSymbolicLinkCommandKey); symLinkReadCommand = CommandExecutor.replaceSubstringsInCommands(symLinkReadCommand, this.missingPathKey, commandResult[0]); @@ -51,7 +62,7 @@ export class GenericDistroSDKProvider extends IDistroDotnetSDKProvider } } - return commandResult[0]; + return commandResult[0] ?? null; } public async dotnetPackageExistsOnSystem(fullySpecifiedDotnetVersion : string, installType : LinuxInstallType) : Promise @@ -77,11 +88,15 @@ export class GenericDistroSDKProvider extends IDistroDotnetSDKProvider public async upgradeDotnet(versionToUpgrade : string, installType : LinuxInstallType): Promise { + const oldReturnStatusSetting = this.commandRunner.returnStatus; + this.commandRunner.returnStatus = true; + let command = this.myDistroCommands(this.updateCommandKey); const sdkPackage = await this.myDotnetVersionPackageName(versionToUpgrade, installType); command = CommandExecutor.replaceSubstringsInCommands(command, this.missingPackageNameKey, sdkPackage); const commandResult = (await this.commandRunner.executeMultipleCommands(command))[0]; + this.commandRunner.returnStatus = oldReturnStatusSetting; return commandResult[0]; } diff --git a/vscode-dotnet-runtime-library/src/Acquisition/LinuxVersionResolver.ts b/vscode-dotnet-runtime-library/src/Acquisition/LinuxVersionResolver.ts index 2c75bd9878..73453f79f0 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/LinuxVersionResolver.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/LinuxVersionResolver.ts @@ -9,7 +9,9 @@ import { DotnetAcquisitionDistroUnknownError, DotnetConflictingLinuxInstallTypesError, - DotnetCustomLinuxInstallExistsError + DotnetCustomLinuxInstallExistsError, + DotnetInstallLinuxChecks, + DotnetUpgradedEvent } from '../EventStream/EventStreamEvents'; import { GenericDistroSDKProvider } from './GenericDistroSDKProvider' import { VersionResolver } from './VersionResolver'; @@ -67,6 +69,8 @@ export class LinuxVersionResolver protected distroSDKProvider : IDistroDotnetSDKProvider | null = null; protected commandRunner : ICommandExecutor; protected versionResolver : VersionResolver; + public okUpdateExitCode = 11188; // Arbitrary number that is not shared or used by other things we rely on as an exit code + public okAlreadyExistsExitCode = 11166; public conflictingInstallErrorMessage = `A dotnet installation was found which indicates dotnet that was installed via Microsoft package feeds. But for this distro and version, we only acquire .NET via the distro feeds. You should not mix distro feed and microsoft feed installations. To continue, please completely remove this version of dotnet to continue by following https://learn.microsoft.com/dotnet/core/install/remove-runtime-sdk-versions?pivots=os-linux. @@ -181,9 +185,9 @@ export class LinuxVersionResolver case 'Red Hat Enterprise Linux': if(this.isRedHatVersion7(distroAndVersion.version)) { - const error = new DotnetAcquisitionDistroUnknownError(new Error(this.redhatUnsupportedDistroErrorMessage), getInstallKeyFromContext(this.acquireContext)); - this.acquisitionContext.eventStream.post(error); - throw error.error; + const unsupportedRhelErr = new DotnetAcquisitionDistroUnknownError(new Error(this.redhatUnsupportedDistroErrorMessage), getInstallKeyFromContext(this.acquireContext)); + this.acquisitionContext.eventStream.post(unsupportedRhelErr); + throw unsupportedRhelErr.error; } return new RedHatDistroSDKProvider(distroAndVersion, this.acquisitionContext, this.utilityContext); default: @@ -250,17 +254,22 @@ export class LinuxVersionResolver * @returns 0 if we can proceed. Will throw if a conflicting install exists. If we can update, it will do the update and return 1. * A string is returned in case we want to make this return more info about the update. */ - private async UpdateOrRejectIfVersionRequestDoesNotRequireInstall(fullySpecifiedDotnetVersion : string, existingInstall : string | null) + private async UpdateOrRejectIfVersionRequestDoesNotRequireInstall(fullySpecifiedDotnetVersion : string, existingInstall : string | null) : Promise { await this.Initialize(); + this.acquisitionContext.eventStream.post(new DotnetInstallLinuxChecks(`Checking to see if we should install, update, or cancel...`)); if(existingInstall) { const existingGlobalInstallSDKVersion = await this.distroSDKProvider!.getInstalledGlobalDotnetVersionIfExists(); if(existingGlobalInstallSDKVersion && Number(this.versionResolver.getMajorMinor(existingGlobalInstallSDKVersion)) === Number(this.versionResolver.getMajorMinor(fullySpecifiedDotnetVersion))) { - if(Number(this.versionResolver.getMajorMinor(existingGlobalInstallSDKVersion)) > Number(this.versionResolver.getMajorMinor(fullySpecifiedDotnetVersion))) + const isPatchUpgrade = Number(this.versionResolver.getFeatureBandPatchVersion(existingGlobalInstallSDKVersion)) < + Number(this.versionResolver.getFeatureBandPatchVersion(fullySpecifiedDotnetVersion)); + + if(Number(this.versionResolver.getMajorMinor(existingGlobalInstallSDKVersion)) > Number(this.versionResolver.getMajorMinor(fullySpecifiedDotnetVersion)) + || Number(this.versionResolver.getFeatureBandFromVersion(existingGlobalInstallSDKVersion)) > Number(this.versionResolver.getFeatureBandFromVersion(fullySpecifiedDotnetVersion))) { // We shouldn't downgrade to a lower patch const err = new DotnetCustomLinuxInstallExistsError(new Error(`An installation of ${fullySpecifiedDotnetVersion} was requested but ${existingGlobalInstallSDKVersion} is already available.`), @@ -268,16 +277,20 @@ export class LinuxVersionResolver this.acquisitionContext.eventStream.post(err); throw err.error; } - else if(await this.distroSDKProvider!.dotnetPackageExistsOnSystem(fullySpecifiedDotnetVersion, 'sdk') || - Number(this.versionResolver.getFeatureBandPatchVersion(existingGlobalInstallSDKVersion)) < Number(this.versionResolver.getFeatureBandPatchVersion(fullySpecifiedDotnetVersion))) + else if(await this.distroSDKProvider!.dotnetPackageExistsOnSystem(fullySpecifiedDotnetVersion, 'sdk') || isPatchUpgrade) { // We can update instead of doing an install - return (await this.distroSDKProvider!.upgradeDotnet(existingGlobalInstallSDKVersion, 'sdk')) ? '1' : '1'; + this.acquisitionContext.eventStream.post(new DotnetUpgradedEvent( + isPatchUpgrade ? + `Updating .NET: Current Version: ${existingGlobalInstallSDKVersion} to ${fullySpecifiedDotnetVersion}.` + : + `Repairing .NET Packages for ${existingGlobalInstallSDKVersion}.`)); + return (await this.distroSDKProvider!.upgradeDotnet(existingGlobalInstallSDKVersion, 'sdk')) === '0' ? String(this.okUpdateExitCode) : '1'; } else { // An existing install exists. - return '1'; + return String(this.okAlreadyExistsExitCode); } } // Additional logic to check the major.minor could be added here if we wanted to prevent installing lower major.minors if an existing install existed. @@ -310,7 +323,11 @@ export class LinuxVersionResolver { return await this.distroSDKProvider!.installDotnet(fullySpecifiedDotnetVersion, 'sdk') ? '0' : '1'; } - return updateOrRejectState; + else if(updateOrRejectState === String(this.okUpdateExitCode) || updateOrRejectState === String(this.okAlreadyExistsExitCode)) + { + return '0'; + } + return String(updateOrRejectState); } /** diff --git a/vscode-dotnet-runtime-library/src/Acquisition/RedHatDistroSDKProvider.ts b/vscode-dotnet-runtime-library/src/Acquisition/RedHatDistroSDKProvider.ts index 4580ce6c0d..fe7bc3e344 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/RedHatDistroSDKProvider.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/RedHatDistroSDKProvider.ts @@ -16,7 +16,6 @@ export class RedHatDistroSDKProvider extends GenericDistroSDKProvider constructor(distroVersion : DistroVersionPair, context : IAcquisitionWorkerContext, utilContext : IUtilityContext, executor : ICommandExecutor | null = null) { super(distroVersion, context, utilContext, executor); - this.resolvePathAsSymlink = false; } protected myVersionDetails() : any @@ -26,17 +25,4 @@ export class RedHatDistroSDKProvider extends GenericDistroSDKProvider const versionData = distroVersions.filter((x: { [x: string]: string; }) => x[this.versionKey] === String(targetVersion)); return versionData; } - - public async getInstalledGlobalDotnetPathIfExists(installType : LinuxInstallType) : Promise - { - this.commandRunner.returnStatus = true; - const commandResult = await this.commandRunner.executeMultipleCommands(this.myDistroCommands(this.currentInstallPathCommandKey)); - this.commandRunner.returnStatus = false; - - if (commandResult[0] !== '0'){ - return ''; - } - const verboseCommandResult = await this.commandRunner.executeMultipleCommands(this.myDistroCommands(this.currentInstallPathCommandKey)); - return verboseCommandResult[0]; - } } \ No newline at end of file diff --git a/vscode-dotnet-runtime-library/src/Acquisition/WinMacGlobalInstaller.ts b/vscode-dotnet-runtime-library/src/Acquisition/WinMacGlobalInstaller.ts index 9d6a8e4302..f87d0c9595 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/WinMacGlobalInstaller.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/WinMacGlobalInstaller.ts @@ -14,7 +14,10 @@ import { CommandExecutor } from '../Utils/CommandExecutor'; import { DotnetConflictingGlobalWindowsInstallError, DotnetFileIntegrityCheckEvent, + DotnetInstallCancelledByUserError as DotnetInstallCancelledByUser, DotnetUnexpectedInstallerOSError, + NetInstallerBeginExecutionEvent, + NetInstallerEndExecutionEvent, OSXOpenNotAvailableError, SuppressedAcquisitionError } from '../EventStream/EventStreamEvents'; @@ -107,6 +110,14 @@ We cannot verify .NET is safe to download at this time. Please try again later.` { return '0'; // These statuses are a success, we don't want to throw. } + else if(installerResult === '1602') + { + // Special code for when user cancels the install + const err = new DotnetInstallCancelledByUser(new Error( + `The install of .NET was cancelled by the user. Aborting.`), getInstallKeyFromContext(this.acquisitionContext.acquisitionContext)); + this.acquisitionContext.eventStream.post(err); + throw err.error; + } else { return installerResult; @@ -225,12 +236,14 @@ Please correct your PATH variable or make sure the 'open' utility is installed s workingCommand = CommandExecutor.makeCommand(`open`, [`-W`, `${path.resolve(installerPath)}`]); } + this.acquisitionContext.eventStream.post(new NetInstallerBeginExecutionEvent(`The OS X .NET Installer has been launched.`)); const commandResult = await this.commandRunner.execute( workingCommand ); + this.acquisitionContext.eventStream.post(new NetInstallerEndExecutionEvent(`The OS X .NET Installer has closed.`)); this.commandRunner.returnStatus = false; - return commandResult[0]; + return commandResult; } else { @@ -240,11 +253,15 @@ Please correct your PATH variable or make sure the 'open' utility is installed s { commandOptions = [`/quiet`, `/install`, `/norestart`]; } + + this.acquisitionContext.eventStream.post(new NetInstallerBeginExecutionEvent(`The Windows .NET Installer has been launched.`)); const commandResult = await this.commandRunner.execute( CommandExecutor.makeCommand(command, commandOptions) ); + this.acquisitionContext.eventStream.post(new NetInstallerEndExecutionEvent(`The Windows .NET Installer has closed.`)); + this.commandRunner.returnStatus = false; - return commandResult[0]; + return commandResult; } } diff --git a/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts b/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts index ca02bff77b..bf43b7e107 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts @@ -131,6 +131,28 @@ export abstract class DotnetNonAcquisitionError extends IEvent { } } +export abstract class DotnetInstallExpectedAbort extends IEvent { + public readonly type = EventType.DotnetInstallExpectedAbort; + public isError = true; + + /** + * + * @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 + * 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: string | null) + { + super(); + } + + public getProperties(telemetry = false): { [key: string]: string } | undefined { + return {ErrorName : this.error.name, + ErrorMessage : this.error.message, + StackTrace : this.error.stack ? TelemetryUtilities.HashAllPaths(this.error.stack) : '', + InstallKey : this.installKey ?? 'null'}; + } +} export class SuppressedAcquisitionError extends IEvent { public readonly eventName = 'SuppressedAcquisitionError'; @@ -217,7 +239,7 @@ export class DotnetFeatureBandDoesNotExistError extends DotnetAcquisitionError { public readonly eventName = 'DotnetFeatureBandDoesNotExistError'; } -export class DotnetWSLSecurityError extends DotnetAcquisitionError { +export class DotnetWSLSecurityError extends DotnetInstallExpectedAbort { public readonly eventName = 'DotnetWSLSecurityError'; } @@ -247,10 +269,14 @@ export class DotnetAcquisitionScriptError extends DotnetAcquisitionVersionError public readonly eventName = 'DotnetAcquisitionScriptError'; } -export class DotnetConflictingGlobalWindowsInstallError extends DotnetAcquisitionError { +export class DotnetConflictingGlobalWindowsInstallError extends DotnetInstallExpectedAbort { public readonly eventName = 'DotnetConflictingGlobalWindowsInstallError'; } +export class DotnetInstallCancelledByUserError extends DotnetInstallExpectedAbort { + public readonly eventName = 'DotnetInstallCancelledByUserError'; +} + export class DotnetDebuggingMessage extends IEvent { public readonly eventName = 'DotnetDebuggingMessage'; public readonly type = EventType.DotnetDebuggingMessage; @@ -293,11 +319,11 @@ export class DotnetVersionResolutionError extends DotnetAcquisitionVersionError public readonly eventName = 'DotnetVersionResolutionError'; } -export class DotnetConflictingLinuxInstallTypesError extends DotnetAcquisitionVersionError { +export class DotnetConflictingLinuxInstallTypesError extends DotnetInstallExpectedAbort { public readonly eventName = 'DotnetConflictingLinuxInstallTypesError'; } -export class DotnetCustomLinuxInstallExistsError extends DotnetAcquisitionVersionError { +export class DotnetCustomLinuxInstallExistsError extends DotnetInstallExpectedAbort { public readonly eventName = 'DotnetCustomLinuxInstallExistsError'; } @@ -338,7 +364,7 @@ export class DotnetInstallationValidationError extends DotnetAcquisitionVersionE } } -export class DotnetAcquisitionDistroUnknownError extends DotnetAcquisitionError { +export class DotnetAcquisitionDistroUnknownError extends DotnetInstallExpectedAbort { public readonly eventName = 'DotnetAcquisitionDistroUnknownError'; public getProperties(telemetry = false): { [key: string]: string } | undefined { @@ -402,7 +428,7 @@ export class DotnetExistingPathResolutionCompleted extends DotnetAcquisitionSucc } export abstract class DotnetAcquisitionMessage extends IEvent { - public readonly type = EventType.DotnetAcquisitionMessage; + public type = EventType.DotnetAcquisitionMessage; public getProperties(): { [key: string]: string } | undefined { return undefined; @@ -521,6 +547,10 @@ export class CommandExecutionEvent extends DotnetCustomMessageEvent { public readonly eventName = 'CommandExecutionEvent'; } +export class CommandExecutionUserAskDialogueEvent extends DotnetCustomMessageEvent { + public readonly eventName = 'CommandExecutionUserAskDialogueEvent'; +} + export class CommandExecutionUserCompletedDialogueEvent extends DotnetCustomMessageEvent { public readonly eventName = 'CommandExecutionUserCompletedDialogueEvent'; } @@ -529,10 +559,36 @@ export class CommandExecutionUnderSudoEvent extends DotnetCustomMessageEvent { public readonly eventName = 'CommandExecutionUnderSudoEvent'; } +export class CommandExecutionUserRejectedPasswordRequest extends DotnetInstallExpectedAbort { + public readonly eventName = 'CommandExecutionUserRejectedPasswordRequest'; +} + export class DotnetVersionParseEvent extends DotnetCustomMessageEvent { public readonly eventName = 'DotnetVersionParseEvent'; } +export class DotnetUpgradedEvent extends DotnetCustomMessageEvent { + public readonly eventName = 'DotnetUpgradedEvent'; + constructor(eventMsg : string) + { + super(eventMsg); + this.type = EventType.DotnetUpgradedEvent; + } +} + +export class NetInstallerBeginExecutionEvent extends DotnetCustomMessageEvent { + public readonly eventName = 'NetInstallerBeginExecutionEvent'; +} + +export class NetInstallerEndExecutionEvent extends DotnetCustomMessageEvent { + public readonly eventName = 'NetInstallerEndExecutionEvent'; +} + + +export class DotnetInstallLinuxChecks extends DotnetCustomMessageEvent { + public readonly eventName = 'DotnetInstallLinuxChecks'; +} + export abstract class DotnetFileEvent extends DotnetAcquisitionMessage { constructor(public readonly eventMessage: string, public readonly time: string, public readonly file: string) { super(); } diff --git a/vscode-dotnet-runtime-library/src/EventStream/EventType.ts b/vscode-dotnet-runtime-library/src/EventStream/EventType.ts index 69b1908c32..ac2b4d1907 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/EventType.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/EventType.ts @@ -16,5 +16,7 @@ export enum EventType { DotnetAcquisitionInProgress, DotnetDebuggingMessage, DotnetTotalSuccessEvent, + DotnetUpgradedEvent, SuppressedAcquisitionError, + DotnetInstallExpectedAbort } diff --git a/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts b/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts index cd8e0113dc..811c7d1f99 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts @@ -9,9 +9,10 @@ import { DotnetAcquisitionError, DotnetAcquisitionInProgress, DotnetAcquisitionStarted, - DotnetAcquisitionVersionError, DotnetDebuggingMessage, DotnetExistingPathResolutionCompleted, + DotnetInstallExpectedAbort, + DotnetUpgradedEvent, } from './EventStreamEvents'; import { EventType } from './EventType'; import { IEvent } from './IEvent'; @@ -98,23 +99,22 @@ export class OutputChannelObserver implements IEventStreamObserver { case EventType.DotnetAcquisitionError: const error = event as DotnetAcquisitionError; this.outputChannel.appendLine(' Error!'); - if (error instanceof DotnetAcquisitionVersionError) { - this.outputChannel.appendLine(`Failed to download .NET ${error.installKey}:`); - } + this.outputChannel.appendLine(`Failed to download .NET ${error.installKey}:`); this.outputChannel.appendLine(error.error.message); this.outputChannel.appendLine(''); - if(error.installKey && error.installKey !== 'null') - { - this.inProgressVersionDone(error.installKey); - } + this.updateDownloadIndicators(error.installKey); + break; + case EventType.DotnetInstallExpectedAbort: + const abortEvent = event as DotnetInstallExpectedAbort; + this.outputChannel.appendLine(`Cancelled Installation of .NET ${abortEvent.installKey}.`); + this.outputChannel.appendLine(abortEvent.error.message); - if (this.inProgressDownloads.length > 0) { - const errorVersionString = this.inProgressDownloads.join(', '); - this.outputChannel.append(`Still downloading .NET version(s) ${errorVersionString} ...`); - } else { - this.stopDownloadIndicator(); - } + this.updateDownloadIndicators(abortEvent.installKey); + break; + case EventType.DotnetUpgradedEvent: + const upgradeMessage = event as DotnetUpgradedEvent; + this.outputChannel.appendLine(`${upgradeMessage.eventMessage}:`); break; case EventType.DotnetDebuggingMessage: const loggedMessage = event as DotnetDebuggingMessage; @@ -127,6 +127,23 @@ export class OutputChannelObserver implements IEventStreamObserver { // Nothing to dispose } + private updateDownloadIndicators(installKey : string | null | undefined) + { + if(installKey && installKey !== 'null') + { + this.inProgressVersionDone(installKey); + } + + if (this.inProgressDownloads.length > 0) + { + const errorVersionString = this.inProgressDownloads.join(', '); + this.outputChannel.append(`Still downloading .NET version(s) ${errorVersionString} ...`); + } + else { + this.stopDownloadIndicator(); + } + } + private startDownloadIndicator() { this.downloadProgressInterval = setInterval(() => this.outputChannel.append('.'), 1000); } diff --git a/vscode-dotnet-runtime-library/src/EventStream/StatusBarObserver.ts b/vscode-dotnet-runtime-library/src/EventStream/StatusBarObserver.ts index a4b1b9e647..bcb2a82af5 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/StatusBarObserver.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/StatusBarObserver.ts @@ -23,6 +23,7 @@ export class StatusBarObserver implements IEventStreamObserver { this.setAndShowStatusBar('$(cloud-download) Downloading .NET...', this.showLogCommand, '', 'Downloading .NET...'); break; case EventType.DotnetAcquisitionCompleted: + case EventType.DotnetInstallExpectedAbort: this.resetAndHideStatusBar(); break; case EventType.DotnetAcquisitionError: diff --git a/vscode-dotnet-runtime-library/src/Utils/CommandExecutor.ts b/vscode-dotnet-runtime-library/src/Utils/CommandExecutor.ts index 344c463210..bd3ae5e0fc 100644 --- a/vscode-dotnet-runtime-library/src/Utils/CommandExecutor.ts +++ b/vscode-dotnet-runtime-library/src/Utils/CommandExecutor.ts @@ -17,7 +17,9 @@ import { CommandExecutionStdError, CommandExecutionStdOut, CommandExecutionUnderSudoEvent, + CommandExecutionUserAskDialogueEvent, CommandExecutionUserCompletedDialogueEvent, + CommandExecutionUserRejectedPasswordRequest, DotnetAlternativeCommandFoundEvent, DotnetCommandNotFoundEvent, DotnetWSLSecurityError @@ -89,6 +91,8 @@ Please install the .NET SDK manually by following https://learn.microsoft.com/en let sanitizedCallerName = this.context?.acquisitionContext?.requestingExtensionId?.replace(/[^0-9a-z]/gi, ''); // Remove non-alphanumerics per OS requirements sanitizedCallerName = sanitizedCallerName?.substring(0, 69); // 70 Characters is the maximum limit we can use for the prompt. const options = { name: `${sanitizedCallerName ?? '.NET Install Tool'}` }; + + this.context?.eventStream.post(new CommandExecutionUserAskDialogueEvent(`Prompting user for command ${fullCommandString} under sudo.`)); exec((fullCommandString), options, (error?: any, stdout?: any, stderr?: any) => { let commandResultString = ''; @@ -111,6 +115,14 @@ ${stderr}`)); this.context?.eventStream.post(new CommandExecutionUserCompletedDialogueEvent(`The command ${fullCommandString} failed to run under sudo.`)); if(terminalFailure) { + if(error.code === 126) + { + const err = new CommandExecutionUserRejectedPasswordRequest(new Error(`Cancelling .NET Install, as command ${fullCommandString} failed. +The user refused the password prompt.`), + getInstallKeyFromContext(this.context?.acquisitionContext!)); + this.context?.eventStream.post(err); + reject(err.error); + } reject(error); } else diff --git a/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts b/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts index 13e768f7a2..5673bc7331 100644 --- a/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts +++ b/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts @@ -7,6 +7,8 @@ import * as open from 'open'; import { DotnetCommandFailed, DotnetCommandSucceeded, + DotnetInstallCancelledByUserError, + DotnetInstallExpectedAbort, DotnetNotInstallRelatedCommandFailed } from '../EventStream/EventStreamEvents'; import { getInstallKeyFromContext } from '../Utils/InstallKeyGenerator'; @@ -62,11 +64,14 @@ export async function callWithErrorHandling(callback: () => T, context: IIssu catch (caughtError) { const error = caughtError as Error; - context.eventStream.post(isAcquisitionError ? - new DotnetCommandFailed(error, context.commandName, getInstallKeyFromContext(acquireContext?.acquisitionContext)) : - // The output observer will keep track of installs and we don't want a non-install failure to make it think it should -=1 from the no. of installs - new DotnetNotInstallRelatedCommandFailed(error, context.commandName) - ); + if(!(error instanceof DotnetInstallExpectedAbort) && error && error.constructor && error.constructor.name !== 'UserCancelledError') + { + context.eventStream.post(isAcquisitionError ? + new DotnetCommandFailed(error, context.commandName, getInstallKeyFromContext(acquireContext?.acquisitionContext)) : + // The output observer will keep track of installs and we don't want a non-install failure to make it think it should -=1 from the no. of installs + new DotnetNotInstallRelatedCommandFailed(error, context.commandName) + ); + } if (context.errorConfiguration === AcquireErrorConfiguration.DisplayAllErrorPopups) { diff --git a/vscode-dotnet-runtime-library/src/test/unit/LinuxDistroTests.test.ts b/vscode-dotnet-runtime-library/src/test/unit/LinuxDistroTests.test.ts index 43bebe2452..61ae4af852 100644 --- a/vscode-dotnet-runtime-library/src/test/unit/LinuxDistroTests.test.ts +++ b/vscode-dotnet-runtime-library/src/test/unit/LinuxDistroTests.test.ts @@ -104,7 +104,7 @@ Microsoft.NETCore.App 7.0.5 [/usr/lib/dotnet/shared/Microsoft.NETCore.App]`; if(shouldRun) { await provider.getInstalledGlobalDotnetPathIfExists(installType); - assert.equal(mockExecutor.attemptedCommand, 'readlink -f /usr/bin/dotnet'); + assert.equal(mockExecutor.attemptedCommand, 'which dotnet'); } }).timeout(standardTimeoutTime); diff --git a/vscode-dotnet-runtime-library/src/test/unit/RedHatDistroTests.test.ts b/vscode-dotnet-runtime-library/src/test/unit/RedHatDistroTests.test.ts index 3155c6c88a..e01a954563 100644 --- a/vscode-dotnet-runtime-library/src/test/unit/RedHatDistroTests.test.ts +++ b/vscode-dotnet-runtime-library/src/test/unit/RedHatDistroTests.test.ts @@ -53,7 +53,7 @@ suite('Red Hat For Linux Distro Logic Unit Tests', () => if(shouldRun) { const distroFeedDir = await provider.getExpectedDotnetDistroFeedInstallationDirectory(); - assert.equal(distroFeedDir, '/usr/lib64/dotnet/dotnet'); + assert.equal(distroFeedDir, '/usr/lib64/dotnet'); } }).timeout(standardTimeoutTime); diff --git a/vscode-dotnet-sdk-extension/package-lock.json b/vscode-dotnet-sdk-extension/package-lock.json index 8711c294e3..32b2bd0789 100644 --- a/vscode-dotnet-sdk-extension/package-lock.json +++ b/vscode-dotnet-sdk-extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-dotnet-sdk", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-dotnet-sdk", - "version": "2.0.0", + "version": "2.0.1", "license": "MIT", "dependencies": { "@types/chai": "4.2.22", diff --git a/vscode-dotnet-sdk-extension/package.json b/vscode-dotnet-sdk-extension/package.json index 7dc9ac279f..5075ba90d9 100644 --- a/vscode-dotnet-sdk-extension/package.json +++ b/vscode-dotnet-sdk-extension/package.json @@ -13,7 +13,7 @@ "description": "This extension installs and manages different versions of the .NET SDK.", "appInsightsKey": "02dc18e0-7494-43b2-b2a3-18ada5fcb522", "icon": "images/dotnetIcon.png", - "version": "2.0.0", + "version": "2.0.1", "publisher": "ms-dotnettools", "engines": { "vscode": "^1.74.0"