From 3659364c3f8a2d8c7129acc97094899c6271b883 Mon Sep 17 00:00:00 2001 From: Noah Gilson Date: Tue, 25 Jun 2024 10:22:20 -0700 Subject: [PATCH] Add Support for ASP.NET Installation (#1843) * wip - separate out worker paths so contexts are needed for every call, but there is less potential for things to go wrong and less code duplication * Further isolate code, need to isolate the install tracker now * migrate install tracker to use event stream instead of being coupled to the context * move installtracker to a singleton and fix tests the install tracker really only needs to exist once since the event stream and extension state are global, this will prevent errors from different states of promises and simplify our code with this design pattern to decouple the installation tracking mechanism from the context object, which will reduce code duplication for aspnet install feature additions * Fix some issues with the test during code migration * Fix issue with accessing instance I forgot to ctrl s! * Clean up logic with install key generation * Remove duplicate source of truth for architecture * Improve logging and error handling of install tracker This class is super annoying to debug without better logging * Update some broken components of the tests * Prevent saying object object in the log output by expanding the object * Tests working We used to need to concat these tests results with different contexts but not any longer * FIx uninstall all commands * Fix the file rename * Improve logging messages * Add mode handling for aspnet * Add ASP NET Support * Add Generic -> Mode Specific Event Stream Class Hierarchy * [wip] create events for all 'modal' events * Dont hold a generic arg obj because it becomes too unwieldy * Fix some issues with the test and sdk extension * Add 'mode' to the acquire context This will allow API callers to set the mode to call aspnet without having to write a new endpoint call. We can default to runtime for when it wasnt provided that would be the old behavior. * Move 'installMode' into the acquire context This allows API callers to provide the mode, We remove it from the worker context to dedupe this and prevent 2x sources of truth. Now this is a bit weird because some API endpoints will take the mode even though the API endpoint is only for one mode. We will just ignore the mode for that. Eventually we can try to transition all of the related calls to a single endpoint based on 'mode' which would simplify API usage. * Code cleanup * De-Dupe Test Code * Add Requested Events to Republisher * Update InstallKey pattern to include ~aspnetcore This is a bit strange since - used to be for 'global' and ~ for architecture. There are only so many path safe characters to use, more importantly though we dont rely on the install key as the information for the install anymore, it is stored in the DotnetInstall object. ~ has logic in place to not account for a 'legacy' install, so ~ with aspnet (which would not have existed before arch was added) is also a bet that will work with pre-existing logic. * fix build issues * Add ASP.NET Directory Provider * Only Report Total Success if Path exists The path can be undefined if an error throws so its not a success * Add ASP.NET API to sample extension * Resolve Asp Net Runtime versions as Runtime versions * Fix mistake on test * Fix bug with install script param * add source-map-support to allow build in CI this got removed and passed earlier somehow * respond to linter * add source map support * Respond to linter * Add test for specific telemetry messages * 2.0.7 branding * Prevent circular import * rewrite completion event so we can test the modaleventrepublisher * fix linter issue * Revert new test since the object is not mockable * Get rid of check for event that cannot be published via mocks * Fix bug where logging was not up to date to prevent i/o costs the file is dumped at the end of error handling, but this means other events past error handling dont get added to the log. This fixes this. * respond to pr feedback --- .vscode/settings.json | 3 +- sample/package-lock.json | 34 ++- sample/package.json | 11 + sample/src/extension.ts | 44 ++- sample/yarn.lock | 12 +- vscode-dotnet-runtime-extension/CHANGELOG.md | 8 + .../package-lock.json | 24 +- vscode-dotnet-runtime-extension/package.json | 3 +- .../src/extension.ts | 86 +++--- .../DotnetCoreAcquisitionExtension.test.ts | 61 +++-- vscode-dotnet-runtime-extension/yarn.lock | 10 +- ...NetRuntimeInstallationDirectoryProvider.ts | 15 + .../src/Acquisition/AcquisitionInvoker.ts | 34 ++- .../Acquisition/DirectoryProviderFactory.ts | 6 +- .../DotnetCoreAcquisitionWorker.ts | 35 +-- .../src/Acquisition/DotnetInstall.ts | 6 +- .../Acquisition/IAcquisitionWorkerContext.ts | 2 - .../IDotnetCoreAcquisitionWorker.ts | 6 +- .../Acquisition/IDotnetInstallationContext.ts | 1 - .../src/Acquisition/IVersionResolver.ts | 8 +- .../Acquisition/InstallTrackerSingleton.ts | 1 - .../src/Acquisition/VersionResolver.ts | 41 +-- .../src/EventStream/EventStreamEvents.ts | 239 +++++++++++----- .../EventStream/EventStreamRegistration.ts | 8 +- .../src/EventStream/EventType.ts | 6 +- .../IModalEventPublisher.ts} | 24 +- .../src/EventStream/LoggingObserver.ts | 2 +- .../src/EventStream/ModalEventPublisher.ts | 117 ++++++++ .../src/EventStream/OutputChannelObserver.ts | 18 +- .../src/IDotnetAcquireContext.ts | 9 +- .../src/Utils/ErrorHandler.ts | 11 +- .../src/Utils/IIssueContext.ts | 2 +- .../src/Utils/InstallKeyUtilities.ts | 26 +- vscode-dotnet-runtime-library/src/index.ts | 1 + .../unit/DotnetCoreAcquisitionWorker.test.ts | 259 ++++++++++-------- .../src/test/unit/InstallTracker.test.ts | 1 - .../src/test/unit/TestUtility.ts | 8 +- .../src/test/unit/VersionResolver.test.ts | 10 +- .../src/test/unit/WebRequestWorker.test.ts | 2 +- vscode-dotnet-runtime.code-workspace | 3 +- vscode-dotnet-sdk-extension/package-lock.json | 14 +- vscode-dotnet-sdk-extension/package.json | 2 +- vscode-dotnet-sdk-extension/src/extension.ts | 17 +- .../DotnetCoreAcquisitionExtension.test.ts | 11 +- vscode-dotnet-sdk-extension/yarn.lock | 9 +- 45 files changed, 845 insertions(+), 405 deletions(-) create mode 100644 vscode-dotnet-runtime-library/src/Acquisition/ASPNetRuntimeInstallationDirectoryProvider.ts rename vscode-dotnet-runtime-library/src/{test/ILoggingObserver.ts => EventStream/IModalEventPublisher.ts} (62%) create mode 100644 vscode-dotnet-runtime-library/src/EventStream/ModalEventPublisher.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index b7c259c482..6d4884baf4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,7 +12,8 @@ "proccom", "programfiles", "REGHEXVALUE", - "REGTYPE" + "REGTYPE", + "Republisher" ], "azure-pipelines.1ESPipelineTemplatesSchemaFile": true } \ No newline at end of file diff --git a/sample/package-lock.json b/sample/package-lock.json index 85f7198632..de919ae888 100644 --- a/sample/package-lock.json +++ b/sample/package-lock.json @@ -20,6 +20,7 @@ "@types/mocha": "9.0.0", "@types/node": "16.11.7", "@types/rimraf": "3.0.2", + "@types/source-map-support": "^0.5.10", "@types/vscode": "1.74.0", "mocha": "^9.2.2", "rimraf": "3.0.2", @@ -32,7 +33,7 @@ }, "../vscode-dotnet-runtime-extension": { "name": "vscode-dotnet-runtime", - "version": "2.0.6", + "version": "2.0.7", "license": "MIT", "dependencies": { "@types/chai-as-promised": "^7.1.8", @@ -47,6 +48,7 @@ "open": "^8.4.0", "rimraf": "3.0.2", "shelljs": "^0.8.5", + "ts-loader": "^9.5.1", "typescript": "4.4.4", "vscode-dotnet-runtime-library": "file:../vscode-dotnet-runtime-library", "webpack-permissions-plugin": "^1.0.9" @@ -56,6 +58,7 @@ "@types/mocha": "^9.0.0", "@types/node": "16.11.7", "@types/rimraf": "3.0.2", + "@types/source-map-support": "^0.5.10", "@types/vscode": "1.74.0", "copy-webpack-plugin": "9.0.1", "webpack": "5.88.2", @@ -90,6 +93,7 @@ "proper-lockfile": "^4.1.2", "rimraf": "3.0.2", "run-script-os": "^1.1.6", + "semver": "^7.6.2", "shelljs": "0.8.5", "typescript": "4.4.4", "vscode-extension-telemetry": "^0.4.3", @@ -132,11 +136,12 @@ "run-script-os": "^1.1.6", "shelljs": "^0.8.5", "source-map-support": "^0.5.21", + "ts-loader": "^9.5.1", "typescript": "4.4.4", "vscode-dotnet-runtime-library": "file:../vscode-dotnet-runtime-library" }, "devDependencies": { - "@types/source-map-support": "^0.5.6", + "@types/source-map-support": "^0.5.10", "copy-webpack-plugin": "^9.0.1", "webpack": "5.76.0", "webpack-cli": "4.9.1" @@ -316,6 +321,16 @@ "@types/node": "*" } }, + "node_modules/@types/source-map-support": { + "version": "0.5.10", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha1-gk3O+YlJa66Y6dBMjcGsHXDhvTk=", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "^0.6.0" + } + }, "node_modules/@types/vscode": { "version": "1.74.0", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/vscode/-/vscode-1.74.0.tgz", @@ -3668,6 +3683,15 @@ "@types/node": "*" } }, + "@types/source-map-support": { + "version": "0.5.10", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha1-gk3O+YlJa66Y6dBMjcGsHXDhvTk=", + "dev": true, + "requires": { + "source-map": "^0.6.0" + } + }, "@types/vscode": { "version": "1.74.0", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/vscode/-/vscode-1.74.0.tgz", @@ -5615,6 +5639,7 @@ "@types/mocha": "^9.0.0", "@types/node": "16.11.7", "@types/rimraf": "3.0.2", + "@types/source-map-support": "^0.5.10", "@types/vscode": "1.74.0", "@vscode/test-electron": "^2.3.9", "axios": "^1.3.4", @@ -5628,6 +5653,7 @@ "open": "^8.4.0", "rimraf": "3.0.2", "shelljs": "^0.8.5", + "ts-loader": "^9.5.1", "typescript": "4.4.4", "vscode-dotnet-runtime-library": "file:../vscode-dotnet-runtime-library", "webpack": "5.88.2", @@ -5663,6 +5689,7 @@ "proper-lockfile": "^4.1.2", "rimraf": "3.0.2", "run-script-os": "^1.1.6", + "semver": "^7.6.2", "shelljs": "0.8.5", "typescript": "4.4.4", "vscode-extension-telemetry": "^0.4.3", @@ -5677,7 +5704,7 @@ "@types/mocha": "^9.0.0", "@types/node": "16.11.7", "@types/rimraf": "3.0.2", - "@types/source-map-support": "^0.5.6", + "@types/source-map-support": "^0.5.10", "@types/vscode": "1.74.0", "@vscode/test-electron": "^2.3.9", "axios": "^1.3.4", @@ -5694,6 +5721,7 @@ "run-script-os": "^1.1.6", "shelljs": "^0.8.5", "source-map-support": "^0.5.21", + "ts-loader": "^9.5.1", "typescript": "4.4.4", "vscode-dotnet-runtime-library": "file:../vscode-dotnet-runtime-library", "webpack": "5.76.0", diff --git a/sample/package.json b/sample/package.json index 2d59fb15bb..9633a09476 100644 --- a/sample/package.json +++ b/sample/package.json @@ -31,6 +31,11 @@ "title": "Acquire .NET runtime", "category": "Sample" }, + { + "command": "sample.dotnet.acquireASPNET", + "title": "Acquire ASPNET runtime", + "category": "Sample" + }, { "command": "sample.dotnet.acquireStatus", "title": "Check the status of acquiring the .NET runtime", @@ -46,6 +51,11 @@ "title": "Concurrently acquire all 2.X .NET Core runtimes", "category": "Sample" }, + { + "command": "sample.dotnet.concurrentASPNETTest", + "title": "Concurrently acquire all 2.X ASPNET Core runtimes", + "category": "Sample" + }, { "command": "sample.dotnet.showAcquisitionLog", "title": "Show .NET runtime acquisition log", @@ -112,6 +122,7 @@ "@types/mocha": "9.0.0", "@types/node": "16.11.7", "@types/rimraf": "3.0.2", + "@types/source-map-support": "^0.5.10", "@types/vscode": "1.74.0", "mocha": "^9.2.2", "rimraf": "3.0.2", diff --git a/sample/src/extension.ts b/sample/src/extension.ts index bb10b94a43..3b612c9fa6 100644 --- a/sample/src/extension.ts +++ b/sample/src/extension.ts @@ -7,13 +7,14 @@ import * as cp from 'child_process'; import * as path from 'path'; import * as vscode from 'vscode'; import { + DotnetInstallMode, IDotnetAcquireContext, IDotnetAcquireResult, IDotnetListVersionsResult, - IDotnetVersion, } from 'vscode-dotnet-runtime-library'; import * as runtimeExtension from 'vscode-dotnet-runtime'; import * as sdkExtension from 'vscode-dotnet-sdk'; +import { install } from 'source-map-support'; export function activate(context: vscode.ExtensionContext) { @@ -76,7 +77,8 @@ ${stderr}`); } }); - const sampleAcquireRegistration = vscode.commands.registerCommand('sample.dotnet.acquire', async (version) => { + async function callAcquireAPI(version : string | undefined, installMode : DotnetInstallMode | undefined) + { if (!version) { version = await vscode.window.showInputBox({ placeHolder: '3.1', @@ -87,10 +89,18 @@ ${stderr}`); try { await vscode.commands.executeCommand('dotnet.showAcquisitionLog'); - await vscode.commands.executeCommand('dotnet.acquire', { version, requestingExtensionId }); + await vscode.commands.executeCommand('dotnet.acquire', { version, requestingExtensionId, mode: installMode }); } catch (error) { vscode.window.showErrorMessage((error as Error).toString()); } + } + + const sampleAcquireRegistration = vscode.commands.registerCommand('sample.dotnet.acquire', async (version) => { + await callAcquireAPI(version, undefined); + }); + + const sampleAcquireASPNETRegistration = vscode.commands.registerCommand('sample.dotnet.acquireASPNET', async (version) => { + await callAcquireAPI(version, 'aspnetcore' ); }); const sampleAcquireStatusRegistration = vscode.commands.registerCommand('sample.dotnet.acquireStatus', async (version) => { @@ -120,21 +130,33 @@ ${stderr}`); } }); - const sampleConcurrentTest = vscode.commands.registerCommand('sample.dotnet.concurrentTest', async () => { - try { + async function acquireConcurrent(versions : [string, string, string], installMode? : DotnetInstallMode) + { + try + { vscode.commands.executeCommand('dotnet.showAcquisitionLog'); const promises = [ - vscode.commands.executeCommand('dotnet.acquire', { version: '2.0', requestingExtensionId }), - vscode.commands.executeCommand('dotnet.acquire', { version: '2.1', requestingExtensionId }), - vscode.commands.executeCommand('dotnet.acquire', { version: '2.2', requestingExtensionId })]; + vscode.commands.executeCommand('dotnet.acquire', { version: versions[0], requestingExtensionId, mode: installMode }), + vscode.commands.executeCommand('dotnet.acquire', { version: versions[1], requestingExtensionId, mode: installMode }), + vscode.commands.executeCommand('dotnet.acquire', { version: versions[2], requestingExtensionId, mode: installMode })]; - for (const promise of promises) { + for (const promise of promises) + { // Await here so we can detect errors await promise; } - } catch (error) { + } catch (error) + { vscode.window.showErrorMessage((error as Error).toString()); } + } + + const sampleConcurrentTest = vscode.commands.registerCommand('sample.dotnet.concurrentTest', async () => { + await acquireConcurrent(['2.0', '2.1', '2.2'], 'runtime'); + }); + + const sampleConcurrentASPNETTest = vscode.commands.registerCommand('sample.dotnet.concurrentASPNETTest', async () => { + await acquireConcurrent(['2.0', '2.1', '2.2'], 'aspnetcore'); }); const sampleShowAcquisitionLogRegistration = vscode.commands.registerCommand('sample.dotnet.showAcquisitionLog', async () => { @@ -148,9 +170,11 @@ ${stderr}`); context.subscriptions.push( sampleHelloWorldRegistration, sampleAcquireRegistration, + sampleAcquireASPNETRegistration, sampleAcquireStatusRegistration, sampleDotnetUninstallAllRegistration, sampleConcurrentTest, + sampleConcurrentASPNETTest, sampleShowAcquisitionLogRegistration, ); diff --git a/sample/yarn.lock b/sample/yarn.lock index 434d3df0e9..333da169a5 100644 --- a/sample/yarn.lock +++ b/sample/yarn.lock @@ -121,6 +121,13 @@ "@types/glob" "*" "@types/node" "*" +"@types/source-map-support@^0.5.10": + "integrity" "sha1-gk3O+YlJa66Y6dBMjcGsHXDhvTk=" + "resolved" "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/source-map-support/-/source-map-support-0.5.10.tgz" + "version" "0.5.10" + dependencies: + "source-map" "^0.6.0" + "@types/vscode@1.74.0": "integrity" "sha1-StwhtOf1J7iT3jQYwhqR8eUDvc0=" "resolved" "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/vscode/-/vscode-1.74.0.tgz" @@ -1814,6 +1821,7 @@ "proper-lockfile" "^4.1.2" "rimraf" "3.0.2" "run-script-os" "^1.1.6" + "semver" "^7.6.2" "shelljs" "0.8.5" "typescript" "4.4.4" "vscode-extension-telemetry" "^0.4.3" @@ -1823,7 +1831,7 @@ "vscode-dotnet-runtime@file:../vscode-dotnet-runtime-extension": "resolved" "file:../vscode-dotnet-runtime-extension" - "version" "2.0.6" + "version" "2.0.7" dependencies: "@types/chai-as-promised" "^7.1.8" "@vscode/test-electron" "^2.3.9" @@ -1837,6 +1845,7 @@ "open" "^8.4.0" "rimraf" "3.0.2" "shelljs" "^0.8.5" + "ts-loader" "^9.5.1" "typescript" "4.4.4" "vscode-dotnet-runtime-library" "file:../vscode-dotnet-runtime-library" "webpack-permissions-plugin" "^1.0.9" @@ -1865,6 +1874,7 @@ "run-script-os" "^1.1.6" "shelljs" "^0.8.5" "source-map-support" "^0.5.21" + "ts-loader" "^9.5.1" "typescript" "4.4.4" "vscode-dotnet-runtime-library" "file:../vscode-dotnet-runtime-library" diff --git a/vscode-dotnet-runtime-extension/CHANGELOG.md b/vscode-dotnet-runtime-extension/CHANGELOG.md index 9aa9c7c660..5435e50dac 100644 --- a/vscode-dotnet-runtime-extension/CHANGELOG.md +++ b/vscode-dotnet-runtime-extension/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning]. ## [Unreleased] +## [2.0.7] - 2024-06-30 + +Adds support for ASP.NET Core Runtime installation via the `acquire` API. + + +Smaller bug fixes for error handling and reporting. +Updated dependencies and added additional notices to our main page. + ## [2.0.6] - 2024-06-10 Keeps track of which extensions manage which installs to allow for better cleanup of old runtimes and sdks. diff --git a/vscode-dotnet-runtime-extension/package-lock.json b/vscode-dotnet-runtime-extension/package-lock.json index 03a32e1190..496af3611b 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.6", + "version": "2.0.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-dotnet-runtime", - "version": "2.0.6", + "version": "2.0.7", "license": "MIT", "dependencies": { "@types/chai-as-promised": "^7.1.8", @@ -31,6 +31,7 @@ "@types/mocha": "^9.0.0", "@types/node": "16.11.7", "@types/rimraf": "3.0.2", + "@types/source-map-support": "^0.5.10", "@types/vscode": "1.74.0", "copy-webpack-plugin": "9.0.1", "webpack": "5.88.2", @@ -299,6 +300,16 @@ "@types/node": "*" } }, + "node_modules/@types/source-map-support": { + "version": "0.5.10", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha1-gk3O+YlJa66Y6dBMjcGsHXDhvTk=", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "^0.6.0" + } + }, "node_modules/@types/vscode": { "version": "1.74.0", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/vscode/-/vscode-1.74.0.tgz", @@ -3622,6 +3633,15 @@ "@types/node": "*" } }, + "@types/source-map-support": { + "version": "0.5.10", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha1-gk3O+YlJa66Y6dBMjcGsHXDhvTk=", + "dev": true, + "requires": { + "source-map": "^0.6.0" + } + }, "@types/vscode": { "version": "1.74.0", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/vscode/-/vscode-1.74.0.tgz", diff --git a/vscode-dotnet-runtime-extension/package.json b/vscode-dotnet-runtime-extension/package.json index b0bf3e3ed8..e8c4bc88ee 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.6", + "version": "2.0.7", "publisher": "ms-dotnettools", "engines": { "vscode": "^1.74.0" @@ -129,6 +129,7 @@ "@types/mocha": "^9.0.0", "@types/node": "16.11.7", "@types/rimraf": "3.0.2", + "@types/source-map-support": "^0.5.10", "@types/vscode": "1.74.0", "copy-webpack-plugin": "9.0.1", "webpack": "5.88.2", diff --git a/vscode-dotnet-runtime-extension/src/extension.ts b/vscode-dotnet-runtime-extension/src/extension.ts index 553389c7ac..3745244868 100644 --- a/vscode-dotnet-runtime-extension/src/extension.ts +++ b/vscode-dotnet-runtime-extension/src/extension.ts @@ -18,9 +18,6 @@ import { DotnetCoreAcquisitionWorker, DotnetCoreDependencyInstaller, DotnetExistingPathResolutionCompleted, - DotnetRuntimeAcquisitionStarted, - DotnetRuntimeAcquisitionTotalSuccessEvent, - DotnetGlobalSDKAcquisitionTotalSuccessEvent, enableExtensionTelemetry, EventBasedError, ErrorConfiguration, @@ -43,7 +40,6 @@ import { VSCodeExtensionContext, VSCodeEnvironment, WindowDisplayWorker, - DotnetSDKAcquisitionStarted, GlobalInstallerResolver, CommandExecutor, IDotnetListVersionsContext, @@ -60,7 +56,9 @@ import { UserManualInstallFailure, DotnetInstall, EventCancellationError, + getInstallKeyCustomArchitecture, DotnetInstallType, + DotnetAcquisitionTotalSuccessEvent, } from 'vscode-dotnet-runtime-library'; import { dotnetCoreAcquisitionExtensionId } from './DotnetCoreAcquisitionId'; @@ -95,10 +93,10 @@ const defaultTimeoutValue = 600; const moreInfoUrl = 'https://github.com/dotnet/vscode-dotnet-runtime/blob/main/Documentation/troubleshooting-runtime.md'; let disableActivationUnderTest = true; -export function activate(context: vscode.ExtensionContext, extensionContext?: IExtensionContext) +export function activate(vsCodeContext: vscode.ExtensionContext, extensionContext?: IExtensionContext) { - if((process.env.DOTNET_INSTALL_TOOL_UNDER_TEST === 'true' || (context?.extensionMode === vscode.ExtensionMode.Test)) && disableActivationUnderTest) + if((process.env.DOTNET_INSTALL_TOOL_UNDER_TEST === 'true' || (vsCodeContext?.extensionMode === vscode.ExtensionMode.Test)) && disableActivationUnderTest) { return; } @@ -110,8 +108,8 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE // Reading Extension Configuration const timeoutValue = extensionConfiguration.get(configKeys.installTimeoutValue); - if (!fs.existsSync(context.globalStoragePath)) { - fs.mkdirSync(context.globalStoragePath); + if (!fs.existsSync(vsCodeContext.globalStoragePath)) { + fs.mkdirSync(vsCodeContext.globalStoragePath); } const resolvedTimeoutSeconds = timeoutValue === undefined ? defaultTimeoutValue : timeoutValue; const proxyLink = extensionConfiguration.get(configKeys.proxyUrl); @@ -124,17 +122,18 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE vsCodeEnv: new VSCodeEnvironment() } - const vsCodeExtensionContext = new VSCodeExtensionContext(context); + const vsCodeExtensionContext = new VSCodeExtensionContext(vsCodeContext); const eventStreamContext = { displayChannelName, - logPath: context.logPath, + logPath: vsCodeContext.logPath, extensionId: dotnetCoreAcquisitionExtensionId, enableTelemetry: isExtensionTelemetryEnabled, telemetryReporter: extensionContext ? extensionContext.telemetryReporter : undefined, showLogCommand: `${commandPrefix}.${commandKeys.showAcquisitionLog}`, packageJson } as IEventStreamContext; - const [globalEventStream, 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 @@ -145,12 +144,14 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE const dotnetAcquireRegistration = vscode.commands.registerCommand(`${commandPrefix}.${commandKeys.acquire}`, async (commandContext: IDotnetAcquireContext) => { const worker = getAcquisitionWorker(); - const runtimeContext = getAcquisitionWorkerContext('runtime', commandContext); + commandContext.mode = commandContext.mode ?? 'runtime' as DotnetInstallMode; + const mode = commandContext.mode; + + const runtimeContext = getAcquisitionWorkerContext(mode, commandContext); const dotnetPath = await callWithErrorHandling>(async () => { - globalEventStream.post(new DotnetRuntimeAcquisitionStarted(commandContext.requestingExtensionId)); - globalEventStream.post(new DotnetAcquisitionRequested(commandContext.version, commandContext.requestingExtensionId)); + globalEventStream.post(new DotnetAcquisitionRequested(commandContext.version, commandContext.requestingExtensionId ?? 'notProvided', mode, commandContext.installType ?? 'local')); telemetryObserver?.setAcquisitionContext(runtimeContext, commandContext); @@ -174,28 +175,36 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE // Note: This will impact the context object given to the worker and error handler since objects own a copy of a reference in JS. const runtimeVersionResolver = new VersionResolver(runtimeContext); - commandContext.version = await runtimeVersionResolver.getFullRuntimeVersion(commandContext.version); + commandContext.version = await runtimeVersionResolver.getFullVersion(commandContext.version, mode); const acquisitionInvoker = new AcquisitionInvoker(runtimeContext, utilContext); - return worker.acquireRuntime(runtimeContext, acquisitionInvoker); + return mode === 'aspnetcore' ? worker.acquireLocalASPNET(runtimeContext, acquisitionInvoker) : worker.acquireLocalRuntime(runtimeContext, acquisitionInvoker); }, getIssueContext(existingPathConfigWorker)(commandContext.errorConfiguration, 'acquire', commandContext.version), commandContext.requestingExtensionId, runtimeContext); - const iKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(commandContext.version, commandContext.architecture, 'local'); - const install = {installKey : iKey, version : commandContext.version, installMode: 'runtime', isGlobal: false, + const iKey = getInstallKeyCustomArchitecture(commandContext.version, commandContext.architecture, mode, 'local'); + const install = {installKey : iKey, version : commandContext.version, installMode: mode, isGlobal: false, architecture: commandContext.architecture ?? DotnetCoreAcquisitionWorker.defaultArchitecture()} as DotnetInstall; - globalEventStream.post(new DotnetRuntimeAcquisitionTotalSuccessEvent(commandContext.version, install, commandContext.requestingExtensionId ?? '', dotnetPath?.dotnetPath ?? '')); + + if(dotnetPath !== undefined && dotnetPath?.dotnetPath !== null) + { + globalEventStream.post(new DotnetAcquisitionTotalSuccessEvent(commandContext.version, install, commandContext.requestingExtensionId ?? '', dotnetPath.dotnetPath)); + } + + loggingObserver.dispose(); return dotnetPath; }); const dotnetAcquireGlobalSDKRegistration = vscode.commands.registerCommand(`${commandPrefix}.${commandKeys.acquireGlobalSDK}`, async (commandContext: IDotnetAcquireContext) => { + commandContext.mode = commandContext.mode ?? 'sdk' as DotnetInstallMode; + if (commandContext.requestingExtensionId === undefined) { return Promise.reject('No requesting extension id was provided.'); } let fullyResolvedVersion = ''; - const sdkContext = getAcquisitionWorkerContext('runtime', commandContext); + const sdkContext = getAcquisitionWorkerContext(commandContext.mode, commandContext); const worker = getAcquisitionWorker(); const pathResult = await callWithErrorHandling(async () => @@ -211,8 +220,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE `No version was defined to install.`); } - globalEventStream.post(new DotnetSDKAcquisitionStarted(commandContext.requestingExtensionId)); - globalEventStream.post(new DotnetAcquisitionRequested(commandContext.version, commandContext.requestingExtensionId)); + globalEventStream.post(new DotnetAcquisitionRequested(commandContext.version, commandContext.requestingExtensionId ?? 'notProvided', commandContext.mode!, commandContext.installType ?? 'global')); const existingPath = await resolveExistingPathIfExists(existingPathConfigWorker, commandContext); if(existingPath) @@ -235,11 +243,16 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE return dotnetPath; }, getIssueContext(existingPathConfigWorker)(commandContext.errorConfiguration, commandKeys.acquireGlobalSDK), commandContext.requestingExtensionId, sdkContext); - const iKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(commandContext.version, commandContext.architecture, 'global'); - const install = {installKey : iKey, version : commandContext.version, installMode: 'sdk', isGlobal: true, - architecture: commandContext.architecture ?? DotnetCoreAcquisitionWorker.defaultArchitecture()} as DotnetInstall; + const iKey = getInstallKeyCustomArchitecture(commandContext.version, commandContext.architecture, commandContext.mode, 'global'); + const install = {installKey : iKey, version : commandContext.version, installMode: commandContext.mode, isGlobal: true, + architecture: commandContext.architecture ?? DotnetCoreAcquisitionWorker.defaultArchitecture()} as DotnetInstall; + + if(pathResult !== undefined && pathResult?.dotnetPath !== null) + { + globalEventStream.post(new DotnetAcquisitionTotalSuccessEvent(commandContext.version, install, commandContext.requestingExtensionId ?? '', pathResult.dotnetPath)); + } - globalEventStream.post(new DotnetGlobalSDKAcquisitionTotalSuccessEvent(commandContext.version, install, commandContext.requestingExtensionId ?? '', pathResult?.dotnetPath ?? '')); + loggingObserver.dispose(); return pathResult; }); @@ -312,7 +325,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE globalEventStream.post(new DotnetAcquisitionStatusRequested(commandContext.version, commandContext.requestingExtensionId)); const runtimeVersionResolver = new VersionResolver(workerContext); - const resolvedVersion = await runtimeVersionResolver.getFullRuntimeVersion(commandContext.version); + const resolvedVersion = await runtimeVersionResolver.getFullVersion(commandContext.version, mode); commandContext.version = resolvedVersion; const dotnetPath = await worker.acquireStatus(workerContext, 'runtime'); return dotnetPath; @@ -325,9 +338,9 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE { const mode = 'runtime' as DotnetInstallMode; const worker = getAcquisitionWorker(); - const installDirectoryProvider = directoryProviderFactory(mode, context.globalStoragePath); + const installDirectoryProvider = directoryProviderFactory(mode, vsCodeContext.globalStoragePath); - await worker.uninstallAll(globalEventStream, installDirectoryProvider.getStoragePath(), context.globalState); + await worker.uninstallAll(globalEventStream, installDirectoryProvider.getStoragePath(), vsCodeContext.globalState); }, getIssueContext(existingPathConfigWorker)(commandContext ? commandContext.errorConfiguration : undefined, 'uninstallAll') ); @@ -387,8 +400,8 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE customWebWorker: WebRequestWorker | undefined, onRecommendationMode : boolean) : Promise => { const mode = 'sdk' as DotnetInstallMode; - const context = getVersionResolverContext(mode, 'global', commandContext?.errorConfiguration); - const customVersionResolver = new VersionResolver(context, customWebWorker); + const workercontext = getVersionResolverContext(mode, 'global', commandContext?.errorConfiguration); + const customVersionResolver = new VersionResolver(workercontext, customWebWorker); if(os.platform() !== 'linux' || !onRecommendationMode) { @@ -401,7 +414,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE } else { - const linuxResolver = new LinuxVersionResolver(context, utilContext); + const linuxResolver = new LinuxVersionResolver(workercontext, utilContext); try { const suggestedVersion = await linuxResolver.getRecommendedDotnetVersion('sdk' as DotnetInstallMode); @@ -448,14 +461,13 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE function getAcquisitionWorkerContext(mode : DotnetInstallMode, acquiringContext : IDotnetAcquireContext) : IAcquisitionWorkerContext { return { - storagePath: context.globalStoragePath, - extensionState: context.globalState, + storagePath: vsCodeContext.globalStoragePath, + extensionState: vsCodeContext.globalState, eventStream: globalEventStream, installationValidator: new InstallationValidator(globalEventStream), timeoutSeconds: resolvedTimeoutSeconds, acquisitionContext: acquiringContext, - installMode: mode, - installDirectoryProvider: directoryProviderFactory(mode, context.globalStoragePath), + installDirectoryProvider: directoryProviderFactory(mode, vsCodeContext.globalStoragePath), proxyUrl: proxyLink, isExtensionTelemetryInitiallyEnabled: isExtensionTelemetryEnabled } @@ -484,7 +496,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE } // Exposing API Endpoints - context.subscriptions.push( + vsCodeContext.subscriptions.push( dotnetAcquireRegistration, dotnetAcquireStatusRegistration, dotnetAcquireGlobalSDKRegistration, diff --git a/vscode-dotnet-runtime-extension/src/test/functional/DotnetCoreAcquisitionExtension.test.ts b/vscode-dotnet-runtime-extension/src/test/functional/DotnetCoreAcquisitionExtension.test.ts index 27eaa55cf0..b74068253c 100644 --- a/vscode-dotnet-runtime-extension/src/test/functional/DotnetCoreAcquisitionExtension.test.ts +++ b/vscode-dotnet-runtime-extension/src/test/functional/DotnetCoreAcquisitionExtension.test.ts @@ -9,21 +9,21 @@ import * as path from 'path'; import * as rimraf from 'rimraf'; import * as vscode from 'vscode'; import { - DotnetCoreAcquisitionWorker, FileUtilities, IDotnetAcquireContext, IDotnetAcquireResult, IExistingPaths, IDotnetListVersionsContext, IDotnetListVersionsResult, - IDotnetVersion, + getInstallKeyCustomArchitecture, ITelemetryEvent, MockExtensionConfiguration, MockExtensionContext, MockTelemetryReporter, MockWebRequestWorker, MockWindowDisplayWorker, - getMockAcquisitionContext + getMockAcquisitionContext, + DotnetInstallMode } from 'vscode-dotnet-runtime-library'; import * as extension from '../../extension'; import { warn } from 'console'; @@ -33,7 +33,8 @@ import { warn } from 'console'; const assert : any = chai.assert; const standardTimeoutTime = 40000; -suite('DotnetCoreAcquisitionExtension End to End', function() { +suite('DotnetCoreAcquisitionExtension End to End', function() +{ this.retries(3); const storagePath = path.join(__dirname, 'tmp'); const mockState = new MockExtensionContext(); @@ -46,7 +47,7 @@ suite('DotnetCoreAcquisitionExtension End to End', function() { const mockExistingPathsWithGlobalConfig: IExistingPaths = { individualizedExtensionPaths: [{extensionId: 'alternative.extension', path: 'foo'}], sharedExistingPath: undefined -} + } const mockReleasesData = `{ "releases-index": [ @@ -102,18 +103,30 @@ suite('DotnetCoreAcquisitionExtension End to End', function() { assert.isAbove(extensionContext.subscriptions.length, 0); }).timeout(standardTimeoutTime); - test('Install Local Runtime Command', async () => { - const context: IDotnetAcquireContext = { version: '2.2', requestingExtensionId }; + async function installRuntime(dotnetVersion : string, installMode : DotnetInstallMode) + { + const context: IDotnetAcquireContext = { version: dotnetVersion, requestingExtensionId, mode: installMode }; const result = await vscode.commands.executeCommand('dotnet.acquire', context); assert.exists(result, 'Command results a result'); assert.exists(result!.dotnetPath, 'The return type of the local runtime install command has a .dotnetPath property'); assert.isTrue(fs.existsSync(result!.dotnetPath), 'The returned path of .net does exist'); assert.include(result!.dotnetPath, '.dotnet', '.dotnet is in the path of the local runtime install'); assert.include(result!.dotnetPath, context.version, 'the path of the local runtime install includes the version of the runtime requested'); + } + + test('Install Local Runtime Command', async () => + { + await installRuntime('2.2', 'runtime'); }).timeout(standardTimeoutTime); - test('Uninstall Local Runtime Command', async () => { - const context: IDotnetAcquireContext = { version: '2.1', requestingExtensionId }; + test('Install Local ASP.NET Runtime Command', async () => + { + await installRuntime('2.2', 'aspnetcore'); + }).timeout(standardTimeoutTime); + + async function installUninstallAll(dotnetVersion : string, installMode : DotnetInstallMode) + { + const context: IDotnetAcquireContext = { version: dotnetVersion, requestingExtensionId, mode: installMode }; const result = await vscode.commands.executeCommand('dotnet.acquire', context); assert.exists(result); assert.exists(result!.dotnetPath); @@ -121,14 +134,21 @@ suite('DotnetCoreAcquisitionExtension End to End', function() { assert.include(result!.dotnetPath, context.version); await vscode.commands.executeCommand('dotnet.uninstallAll', context.version); assert.isFalse(fs.existsSync(result!.dotnetPath), 'the dotnet path result does not exist after uninstall'); + } + + test('Uninstall Local Runtime Command', async () => { + await installUninstallAll('2.2', 'runtime') }).timeout(standardTimeoutTime); + test('Uninstall Local ASP.NET Runtime Command', async () => { + await installUninstallAll('2.2', 'aspnetcore') + }).timeout(standardTimeoutTime); - test('Install and Uninstall Multiple Local Runtime Versions', async () => { - const versions = ['2.2', '3.0', '3.1']; + async function installMultipleVersions(versions : string[], installMode : DotnetInstallMode) + { let dotnetPaths: string[] = []; for (const version of versions) { - const result = await vscode.commands.executeCommand('dotnet.acquire', { version, requestingExtensionId }); + const result = await vscode.commands.executeCommand('dotnet.acquire', { version, requestingExtensionId, mode: installMode }); assert.exists(result); assert.exists(result!.dotnetPath); assert.include(result!.dotnetPath, version); @@ -140,8 +160,17 @@ suite('DotnetCoreAcquisitionExtension End to End', function() { for (const dotnetPath of dotnetPaths) { assert.isTrue(fs.existsSync(dotnetPath)); } + } + + test('Install and Uninstall Multiple Local Runtime Versions', async () => { + await installMultipleVersions(['2.2', '3.0', '3.1'], 'runtime'); + }).timeout(standardTimeoutTime * 2); + + test('Install and Uninstall Multiple Local ASP.NET Runtime Versions', async () => { + await installMultipleVersions(['2.2', '3.0', '3.1'], 'aspnetcore'); }).timeout(standardTimeoutTime * 2); + test('Install SDK Globally E2E (Requires Admin)', async () => { // We only test if the process is running under ADMIN because non-admin requires user-intervention. if(new FileUtilities().isElevated()) @@ -202,7 +231,7 @@ suite('DotnetCoreAcquisitionExtension End to End', function() { const rntVersion = '2.2'; const fullyResolvedVersion = '2.2.8'; // 2.2 is very much out of support, so we don't expect this to change to a newer version - const installKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(fullyResolvedVersion, os.arch()); + const installKey = getInstallKeyCustomArchitecture(fullyResolvedVersion, os.arch(), 'runtime', 'local'); const context: IDotnetAcquireContext = { version: rntVersion, requestingExtensionId }; const result = await vscode.commands.executeCommand('dotnet.acquire', context); @@ -257,9 +286,9 @@ suite('DotnetCoreAcquisitionExtension End to End', function() { test('Install Local Runtime Command Passes With Warning With No RequestingExtensionId', async () => { const context: IDotnetAcquireContext = { version: '3.1' }; const result = await vscode.commands.executeCommand('dotnet.acquire', context); - assert.exists(result); - assert.exists(result!.dotnetPath); - assert.include(result!.dotnetPath, context.version); + assert.exists(result, 'A result from the API exists'); + assert.exists(result!.dotnetPath, 'The result has a dotnet path'); + assert.include(result!.dotnetPath, context.version, 'The version is included in the path'); assert.include(mockDisplayWorker.warningMessage, 'Ignoring existing .NET paths'); }).timeout(standardTimeoutTime); diff --git a/vscode-dotnet-runtime-extension/yarn.lock b/vscode-dotnet-runtime-extension/yarn.lock index 5baa82ead4..c7b7650fbd 100644 --- a/vscode-dotnet-runtime-extension/yarn.lock +++ b/vscode-dotnet-runtime-extension/yarn.lock @@ -149,6 +149,13 @@ "@types/glob" "*" "@types/node" "*" +"@types/source-map-support@^0.5.10": + "integrity" "sha1-gk3O+YlJa66Y6dBMjcGsHXDhvTk=" + "resolved" "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/source-map-support/-/source-map-support-0.5.10.tgz" + "version" "0.5.10" + dependencies: + "source-map" "^0.6.0" + "@types/vscode@1.74.0": "integrity" "sha1-StwhtOf1J7iT3jQYwhqR8eUDvc0=" "resolved" "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/vscode/-/vscode-1.74.0.tgz" @@ -1860,12 +1867,13 @@ "chai-as-promised" "^7.1.1" "eol" "^0.9.1" "get-proxy-settings" "^0.1.13" - "https-proxy-agent" "^7.0.2" + "https-proxy-agent" "^7.0.4" "mocha" "^9.1.3" "open" "^8.4.0" "proper-lockfile" "^4.1.2" "rimraf" "3.0.2" "run-script-os" "^1.1.6" + "semver" "^7.6.2" "shelljs" "0.8.5" "typescript" "4.4.4" "vscode-extension-telemetry" "^0.4.3" diff --git a/vscode-dotnet-runtime-library/src/Acquisition/ASPNetRuntimeInstallationDirectoryProvider.ts b/vscode-dotnet-runtime-library/src/Acquisition/ASPNetRuntimeInstallationDirectoryProvider.ts new file mode 100644 index 0000000000..ff59e16765 --- /dev/null +++ b/vscode-dotnet-runtime-library/src/Acquisition/ASPNetRuntimeInstallationDirectoryProvider.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- +* Licensed to the .NET Foundation under one or more agreements. +* The .NET Foundation licenses this file to you under the MIT license. +*--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import { IInstallationDirectoryProvider } from './IInstallationDirectoryProvider'; + +export class ASPNetRuntimeInstallationDirectoryProvider extends IInstallationDirectoryProvider { + public getInstallDir(installKey: string): string + { + const dotnetInstallDir = path.join(this.getStoragePath(), installKey); + return dotnetInstallDir; + } +} diff --git a/vscode-dotnet-runtime-library/src/Acquisition/AcquisitionInvoker.ts b/vscode-dotnet-runtime-library/src/Acquisition/AcquisitionInvoker.ts index 02af7a0355..a0bad8b780 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/AcquisitionInvoker.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/AcquisitionInvoker.ts @@ -18,10 +18,9 @@ import { EventBasedError, } from '../EventStream/EventStreamEvents'; -import { timeoutConstants } from '../Utils/ErrorHandler'; +import { timeoutConstants } from '../Utils/ErrorHandler' import { InstallScriptAcquisitionWorker } from './InstallScriptAcquisitionWorker'; import { TelemetryUtilities } from '../EventStream/TelemetryUtilities'; -import { DotnetCoreAcquisitionWorker } from './DotnetCoreAcquisitionWorker'; import { FileUtilities } from '../Utils/FileUtilities'; import { CommandExecutor } from '../Utils/CommandExecutor'; @@ -31,6 +30,7 @@ import { IAcquisitionInvoker } from './IAcquisitionInvoker'; import { IDotnetInstallationContext } from './IDotnetInstallationContext'; import { IInstallScriptAcquisitionWorker } from './IInstallScriptAcquisitionWorker'; import { DotnetInstall } from './DotnetInstall'; +import { DotnetInstallMode } from './DotnetInstallMode'; /* tslint:disable:no-any */ /* tslint:disable:only-arrow-functions */ @@ -69,10 +69,10 @@ You will need to restart VS Code after these changes. If PowerShell is still not return false; } - public async installDotnet(installContext: IDotnetInstallationContext, installKey : DotnetInstall): Promise + public async installDotnet(installContext: IDotnetInstallationContext, install : DotnetInstall): Promise { const winOS = os.platform() === 'win32'; - const installCommand = await this.getInstallCommand(installContext.version, installContext.installDir, installContext.installRuntime, installContext.architecture); + const installCommand = await this.getInstallCommand(installContext.version, installContext.installDir, installContext.installMode, installContext.architecture); return new Promise(async (resolve, reject) => { @@ -81,7 +81,7 @@ You will need to restart VS Code after these changes. If PowerShell is still not let windowsFullCommand = `powershell.exe -NoProfile -NonInteractive -NoLogo -ExecutionPolicy unrestricted -Command "& { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12; & ${installCommand} }"`; if(winOS) { - const powershellReference = await this.verifyPowershellCanRun(installContext, installKey); + const powershellReference = await this.verifyPowershellCanRun(installContext, install); windowsFullCommand = windowsFullCommand.replace('powershell.exe', powershellReference); } @@ -91,42 +91,42 @@ You will need to restart VS Code after these changes. If PowerShell is still not { if (stdout) { - this.eventStream.post(new DotnetAcquisitionScriptOutput(installKey, TelemetryUtilities.HashAllPaths(stdout))); + this.eventStream.post(new DotnetAcquisitionScriptOutput(install, TelemetryUtilities.HashAllPaths(stdout))); } if (stderr) { - this.eventStream.post(new DotnetAcquisitionScriptOutput(installKey, `STDERR: ${TelemetryUtilities.HashAllPaths(stderr)}`)); + this.eventStream.post(new DotnetAcquisitionScriptOutput(install, `STDERR: ${TelemetryUtilities.HashAllPaths(stderr)}`)); } if (error) { if (!(await this.isOnline(installContext))) { const offlineError = new EventBasedError('DotnetOfflineFailure', 'No internet connection detected: Cannot install .NET'); - this.eventStream.post(new DotnetOfflineFailure(offlineError, installKey)); + this.eventStream.post(new DotnetOfflineFailure(offlineError, install)); reject(offlineError); } else if (error.signal === 'SIGKILL') { 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)); + this.eventStream.post(new DotnetAcquisitionTimeoutError(error, install, installContext.timeoutSeconds)); reject(newError); } else { 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)); + this.eventStream.post(new DotnetAcquisitionInstallError(newError, install)); reject(newError); } } else if (stderr && stderr.length > 0) { - this.eventStream.post(new DotnetAcquisitionCompleted(installKey, installContext.dotnetPath, installContext.version)); + this.eventStream.post(new DotnetAcquisitionCompleted(install, installContext.dotnetPath, installContext.version)); resolve(); } else { - this.eventStream.post(new DotnetAcquisitionCompleted(installKey, installContext.dotnetPath, installContext.version)); + this.eventStream.post(new DotnetAcquisitionCompleted(install, installContext.dotnetPath, installContext.version)); resolve(); } }); @@ -134,23 +134,27 @@ You will need to restart VS Code after these changes. If PowerShell is still not catch (error : any) { const newError = new EventBasedError('DotnetAcquisitionUnexpectedError', error?.message, error?.stack) - this.eventStream.post(new DotnetAcquisitionUnexpectedError(newError, installKey)); + this.eventStream.post(new DotnetAcquisitionUnexpectedError(newError, install)); reject(newError); } }); } - private async getInstallCommand(version: string, dotnetInstallDir: string, installRuntime: boolean, architecture: string): Promise { + private async getInstallCommand(version: string, dotnetInstallDir: string, installMode: DotnetInstallMode, architecture: string): Promise { const arch = this.fileUtilities.nodeArchToDotnetArch(architecture, this.eventStream); let args = [ '-InstallDir', this.escapeFilePath(dotnetInstallDir), '-Version', version, '-Verbose' ]; - if (installRuntime) + if (installMode === 'runtime') { args = args.concat('-Runtime', 'dotnet'); } + else if(installMode === 'aspnetcore') + { + args = args.concat('-Runtime', 'aspnetcore'); + } if(arch !== 'auto') { args = args.concat('-Architecture', arch); diff --git a/vscode-dotnet-runtime-library/src/Acquisition/DirectoryProviderFactory.ts b/vscode-dotnet-runtime-library/src/Acquisition/DirectoryProviderFactory.ts index 30955de58f..5572d72e3c 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/DirectoryProviderFactory.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/DirectoryProviderFactory.ts @@ -4,6 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ +import { ASPNetRuntimeInstallationDirectoryProvider } from './ASPNetRuntimeInstallationDirectoryProvider'; import { DotnetInstallMode } from './DotnetInstallMode'; import { IInstallationDirectoryProvider } from './IInstallationDirectoryProvider'; import { RuntimeInstallationDirectoryProvider } from './RuntimeInstallationDirectoryProvider'; @@ -12,5 +13,8 @@ import { SdkInstallationDirectoryProvider } from './SdkInstallationDirectoryProv export function directoryProviderFactory(mode: DotnetInstallMode, storagePath: string) : IInstallationDirectoryProvider { - return mode === 'runtime' ? new RuntimeInstallationDirectoryProvider(storagePath) : new SdkInstallationDirectoryProvider(storagePath); + return mode === 'runtime' ? new RuntimeInstallationDirectoryProvider(storagePath) : + mode === 'sdk' ? new SdkInstallationDirectoryProvider(storagePath) : + mode === 'aspnetcore' ? new ASPNetRuntimeInstallationDirectoryProvider(storagePath) + : new RuntimeInstallationDirectoryProvider(storagePath); // default if no mode is provided - should never happen } diff --git a/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts b/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts index 7385d2858a..6efaf813f7 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/DotnetCoreAcquisitionWorker.ts @@ -38,7 +38,7 @@ import { WinMacGlobalInstaller } from './WinMacGlobalInstaller'; import { LinuxGlobalInstaller } from './LinuxGlobalInstaller'; import { TelemetryUtilities } from '../EventStream/TelemetryUtilities'; import { Debugging } from '../Utils/Debugging'; -import { DotnetInstallType, IDotnetAcquireContext} from '../IDotnetAcquireContext'; +import { DotnetInstallType } from '../IDotnetAcquireContext'; import { IGlobalInstaller } from './IGlobalInstaller'; import { IVSCodeExtensionContext } from '../IVSCodeExtensionContext'; import { IUtilityContext } from '../Utils/IUtilityContext'; @@ -62,6 +62,7 @@ import { DotnetInstallMode } from './DotnetInstallMode'; import { IEventStream } from '../EventStream/EventStream'; import { strict } from 'assert'; import { IExtensionState } from '../IExtensionState'; +import { getInstallKeyCustomArchitecture } from '../Utils/InstallKeyUtilities'; /* tslint:disable:no-any */ @@ -99,7 +100,7 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker * @remarks this is simply a wrapper around the acquire function. * @returns the requested dotnet path. */ - public async acquireSDK(context: IAcquisitionWorkerContext, invoker : IAcquisitionInvoker): Promise { + public async acquireLocalSDK(context: IAcquisitionWorkerContext, invoker : IAcquisitionInvoker): Promise { return this.acquire(context, 'sdk', undefined, invoker); } @@ -109,12 +110,18 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker return this.acquire(context, 'sdk', installerResolver); } + public async acquireLocalASPNET(context: IAcquisitionWorkerContext, invoker : IAcquisitionInvoker) + { + return this.acquire(context, 'aspnetcore', undefined, invoker); + } + /** * * @remarks this is simply a wrapper around the acquire function. * @returns the requested dotnet path. */ - public async acquireRuntime(context: IAcquisitionWorkerContext, invoker : IAcquisitionInvoker): Promise { + public async acquireLocalRuntime(context: IAcquisitionWorkerContext, invoker : IAcquisitionInvoker): Promise + { return this.acquire(context, 'runtime', undefined, invoker); } @@ -191,7 +198,8 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker { install = { - installKey: DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(version, context.acquisitionContext.architecture, globalInstallerResolver !== null ? 'global' : 'local'), + installKey: getInstallKeyCustomArchitecture(version, context.acquisitionContext.architecture, + context.acquisitionContext.mode!, globalInstallerResolver !== null ? 'global' : 'local'), version: install.version, isGlobal: install.isGlobal, installMode: mode, @@ -240,22 +248,6 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker } } - public static getInstallKeyCustomArchitecture(version : string, architecture: string | null | undefined, - installType : DotnetInstallType = 'local') : string - { - if(architecture === null || architecture === 'null') - { - // Use the legacy method (no architecture) of installs - return installType === 'global' ? `${version}-global` : version; - } - else if(architecture === undefined) - { - architecture = DotnetCoreAcquisitionWorker.defaultArchitecture(); - } - - return installType === 'global' ? `${version}-global~${architecture}` : `${version}~${architecture}`; - } - /** * * @param version The version of the object to acquire. @@ -302,7 +294,6 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker version, dotnetPath, timeoutSeconds: context.timeoutSeconds, - installRuntime : mode === 'runtime', installMode : mode, installType : context.acquisitionContext.installType ?? 'local', // Before this API param existed, all calls were for local types. architecture: context.acquisitionContext.architecture ?? this.getDefaultInternalArchitecture(context.acquisitionContext.architecture), @@ -378,7 +369,7 @@ export class DotnetCoreAcquisitionWorker implements IDotnetCoreAcquisitionWorker } else { - const newError = new EventBasedError('DotnetAcquisitionError', `.NET Acquisition Failed: ${error?.message ?? error}`); + const newError = new EventBasedError('DotnetAcquisitionError', `.NET Acquisition Failed: ${error?.message ?? JSON.stringify(error)}`); return newError; } } diff --git a/vscode-dotnet-runtime-library/src/Acquisition/DotnetInstall.ts b/vscode-dotnet-runtime-library/src/Acquisition/DotnetInstall.ts index 7887ef4c36..944e08a214 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/DotnetInstall.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/DotnetInstall.ts @@ -3,8 +3,8 @@ * The .NET Foundation licenses this file to you under the MIT license. *--------------------------------------------------------------------------------------------*/ -import { DotnetInstallType } from '..'; -import { DotnetCoreAcquisitionWorker } from './DotnetCoreAcquisitionWorker'; +import { DotnetInstallType } from '../IDotnetAcquireContext'; +import { getInstallKeyCustomArchitecture } from '../Utils/InstallKeyUtilities'; import { DotnetInstallMode } from './DotnetInstallMode'; export interface DotnetInstall { @@ -71,7 +71,7 @@ export function looksLikeRuntimeVersion(version: string): boolean { export function GetDotnetInstallInfo(installVersion: string, installationMode: DotnetInstallMode, installType: DotnetInstallType, installArchitecture: string): DotnetInstall { return { - installKey: DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(installVersion, installArchitecture, installType), + installKey: getInstallKeyCustomArchitecture(installVersion, installArchitecture, installationMode, installType), version: installVersion, architecture: installArchitecture, isGlobal: installType === 'global', diff --git a/vscode-dotnet-runtime-library/src/Acquisition/IAcquisitionWorkerContext.ts b/vscode-dotnet-runtime-library/src/Acquisition/IAcquisitionWorkerContext.ts index df76de65db..f5011dee0b 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/IAcquisitionWorkerContext.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/IAcquisitionWorkerContext.ts @@ -7,7 +7,6 @@ import { IEventStream } from '../EventStream/EventStream'; import { IExtensionState } from '../IExtensionState'; import { IInstallationDirectoryProvider } from './IInstallationDirectoryProvider'; import { IInstallationValidator } from './IInstallationValidator'; -import { DotnetInstallMode } from './DotnetInstallMode'; export interface IAcquisitionWorkerContext { @@ -18,7 +17,6 @@ export interface IAcquisitionWorkerContext timeoutSeconds: number; installDirectoryProvider: IInstallationDirectoryProvider; acquisitionContext : IDotnetAcquireContext; - installMode: DotnetInstallMode; proxyUrl? : string | undefined; isExtensionTelemetryInitiallyEnabled : boolean; } diff --git a/vscode-dotnet-runtime-library/src/Acquisition/IDotnetCoreAcquisitionWorker.ts b/vscode-dotnet-runtime-library/src/Acquisition/IDotnetCoreAcquisitionWorker.ts index 350d854a34..4fcc268d90 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/IDotnetCoreAcquisitionWorker.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/IDotnetCoreAcquisitionWorker.ts @@ -14,9 +14,11 @@ export interface IDotnetCoreAcquisitionWorker { uninstallAll(eventStream : IEventStream, storagePath : string, extensionState : IExtensionState): void; - acquireRuntime(context: IAcquisitionWorkerContext, invoker : IAcquisitionInvoker): Promise; + acquireLocalRuntime(context: IAcquisitionWorkerContext, invoker : IAcquisitionInvoker): Promise; - acquireSDK(context: IAcquisitionWorkerContext, invoker : IAcquisitionInvoker): Promise; + acquireLocalASPNET(context: IAcquisitionWorkerContext, invoker : IAcquisitionInvoker) : Promise; + + acquireLocalSDK(context: IAcquisitionWorkerContext, invoker : IAcquisitionInvoker): Promise; acquireGlobalSDK(context: IAcquisitionWorkerContext, installerResolver: GlobalInstallerResolver): Promise; } diff --git a/vscode-dotnet-runtime-library/src/Acquisition/IDotnetInstallationContext.ts b/vscode-dotnet-runtime-library/src/Acquisition/IDotnetInstallationContext.ts index 983625566d..d47c59dae3 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/IDotnetInstallationContext.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/IDotnetInstallationContext.ts @@ -11,7 +11,6 @@ export interface IDotnetInstallationContext { version: string; dotnetPath: string; timeoutSeconds: number; - installRuntime: boolean; // kept to remove breaking change installMode : DotnetInstallMode; installType : DotnetInstallType; architecture: string; diff --git a/vscode-dotnet-runtime-library/src/Acquisition/IVersionResolver.ts b/vscode-dotnet-runtime-library/src/Acquisition/IVersionResolver.ts index 348dd2c321..33c6f3c08a 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/IVersionResolver.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/IVersionResolver.ts @@ -3,7 +3,9 @@ * The .NET Foundation licenses this file to you under the MIT license. *--------------------------------------------------------------------------------------------*/ -export interface IVersionResolver { - getFullRuntimeVersion(version: string): Promise; - getFullSDKVersion(version: string): Promise; +import { DotnetInstallMode } from './DotnetInstallMode'; + +export interface IVersionResolver +{ + getFullVersion(version: string, mode : DotnetInstallMode): Promise; } diff --git a/vscode-dotnet-runtime-library/src/Acquisition/InstallTrackerSingleton.ts b/vscode-dotnet-runtime-library/src/Acquisition/InstallTrackerSingleton.ts index e234e6a145..716bca5cd1 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/InstallTrackerSingleton.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/InstallTrackerSingleton.ts @@ -33,7 +33,6 @@ import { import { getVersionFromLegacyInstallKey, getAssumedInstallInfo } from '../Utils/InstallKeyUtilities'; import { InstallRecord, InstallRecordOrStr } from './InstallRecord'; import { DotnetCoreAcquisitionWorker } from './DotnetCoreAcquisitionWorker'; -import { error } from 'console'; import { IEventStream } from '../EventStream/EventStream'; import { IExtensionState } from '../IExtensionState'; /* tslint:disable:no-any */ diff --git a/vscode-dotnet-runtime-library/src/Acquisition/VersionResolver.ts b/vscode-dotnet-runtime-library/src/Acquisition/VersionResolver.ts index 5b07cb85c1..ccac967a27 100644 --- a/vscode-dotnet-runtime-library/src/Acquisition/VersionResolver.ts +++ b/vscode-dotnet-runtime-library/src/Acquisition/VersionResolver.ts @@ -59,8 +59,7 @@ export class VersionResolver implements IVersionResolver { */ public async GetAvailableDotnetVersions(commandContext: IDotnetListVersionsContext | undefined) : Promise { - // If shouldObtainSdkVersions === false, get Runtimes. Else, get Sdks. - const shouldObtainSdkVersions : boolean = !commandContext?.listRuntimes; + const getSdkVersions : boolean = !commandContext?.listRuntimes; const availableVersions : IDotnetListVersionsResult = []; const response : any = await this.webWorker.getCachedData(); @@ -75,17 +74,17 @@ export class VersionResolver implements IVersionResolver { } else { - const sdkDetailsJson = response['releases-index']; + const releases = response['releases-index']; - for(const availableSdk of sdkDetailsJson) + for(const release of releases) { - if(availableSdk['release-type'] === 'lts' || availableSdk['release-type'] === 'sts') + if(release['release-type'] === 'lts' || release['release-type'] === 'sts') { availableVersions.push({ - supportStatus: (availableSdk['release-type'] as DotnetVersionSupportStatus), - supportPhase: (availableSdk['support-phase'] as DotnetVersionSupportPhase), - version: availableSdk[shouldObtainSdkVersions ? 'latest-sdk' : 'latest-runtime'], - channelVersion: availableSdk['channel-version'] + supportStatus: (release['release-type'] as DotnetVersionSupportStatus), + supportPhase: (release['support-phase'] as DotnetVersionSupportPhase), + version: release[getSdkVersions ? 'latest-sdk' : 'latest-runtime'], + channelVersion: release['channel-version'] } as IDotnetVersion ); } @@ -96,18 +95,7 @@ export class VersionResolver implements IVersionResolver { }); } - public async getFullRuntimeVersion(version: string): Promise { - return this.getFullVersion(version, 'runtime'); - } - - public async getFullSDKVersion(version: string): Promise { - return this.getFullVersion(version, 'sdk'); - } - - /** - * @param getRuntimeVersion - True for getting the full runtime version, false for the SDK version. - */ - private async getFullVersion(version: string, mode: DotnetInstallMode): Promise + public async getFullVersion(version: string, mode: DotnetInstallMode): Promise { let releasesVersions : IDotnetListVersionsResult; try @@ -136,12 +124,13 @@ export class VersionResolver implements IVersionResolver { }); } - private resolveVersion(version: string, releases: IDotnetListVersionsResult): string { - Debugging.log(`Resolving the version: ${version}`, this.context.eventStream); + private resolveVersion(version: string, releases: IDotnetListVersionsResult): string + { this.validateVersionInput(version); // Search for the specific version let matchingVersion = releases.filter((availableVersions : IDotnetVersion) => availableVersions.version === version); + // If a x.y version is given, just find that instead (which is how almost all requests are given atm) if(!matchingVersion || matchingVersion.length < 1) { @@ -151,7 +140,7 @@ export class VersionResolver implements IVersionResolver { { const err = new DotnetVersionResolutionError(new EventCancellationError('DotnetVersionResolutionError', `The requested and or resolved version is invalid.`), - getAssumedInstallInfo(version, this.context.installMode)); + getAssumedInstallInfo(version, this.context.acquisitionContext.mode!)); this.context.eventStream.post(err); throw err.error; } @@ -177,7 +166,7 @@ export class VersionResolver implements IVersionResolver { Debugging.log(`Resolving the version: ${version} ... it is invalid!`, this.context.eventStream); const err = new DotnetVersionResolutionError(new EventCancellationError('DotnetVersionResolutionError', `An invalid version was requested. Version: ${version}`), - getAssumedInstallInfo(version, this.context.installMode)); + getAssumedInstallInfo(version, this.context.acquisitionContext.mode!)); this.context.eventStream.post(err); throw err.error; } @@ -186,7 +175,7 @@ export class VersionResolver implements IVersionResolver { private async getReleasesInfo(mode : DotnetInstallMode): Promise { - const apiContext: IDotnetListVersionsContext = { listRuntimes: mode === 'runtime' }; + const apiContext: IDotnetListVersionsContext = { listRuntimes: mode === 'runtime' || mode === 'aspnetcore' }; const response = await this.GetAvailableDotnetVersions(apiContext); if (!response) diff --git a/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts b/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts index 7847e0273a..7ab5c62198 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/EventStreamEvents.ts @@ -10,6 +10,8 @@ import { IEvent } from './IEvent'; import { TelemetryUtilities } from './TelemetryUtilities'; import { InstallToStrings } from '../Acquisition/DotnetInstall'; import { DotnetInstall } from '../Acquisition/DotnetInstall'; +import { DotnetInstallMode } from '../Acquisition/DotnetInstallMode'; +import { DotnetInstallType } from '../IDotnetAcquireContext'; // tslint:disable max-classes-per-file @@ -29,12 +31,23 @@ export class EventBasedError extends Error } } -export class DotnetAcquisitionStarted extends IEvent { +export abstract class GenericModalEvent extends IEvent +{ + abstract readonly mode : DotnetInstallMode; + abstract readonly installType : DotnetInstallType; +} + +export class DotnetAcquisitionStarted extends GenericModalEvent +{ public readonly eventName = 'DotnetAcquisitionStarted'; public readonly type = EventType.DotnetAcquisitionStart; + public readonly mode : DotnetInstallMode; + public readonly installType: DotnetInstallType; constructor(public readonly install: DotnetInstall, public readonly startingVersion: string, public readonly requestingExtensionId = '') { super(); + this.mode = install.installMode; + this.installType = install.isGlobal ? 'global' : 'local'; } public getProperties() { @@ -47,10 +60,8 @@ export class DotnetAcquisitionStarted extends IEvent { } } -export class DotnetRuntimeAcquisitionStarted extends IEvent { - public readonly eventName = 'DotnetRuntimeAcquisitionStarted'; - public readonly type = EventType.DotnetRuntimeAcquisitionStart; - +abstract class DotnetAcquisitionStartedBase extends IEvent +{ constructor(public readonly requestingExtensionId = '') { super(); } @@ -60,79 +71,157 @@ export class DotnetRuntimeAcquisitionStarted extends IEvent { } } -export class DotnetSDKAcquisitionStarted extends IEvent { +export class DotnetRuntimeAcquisitionStarted extends DotnetAcquisitionStartedBase { + public readonly eventName = 'DotnetRuntimeAcquisitionStarted'; + public readonly type = EventType.DotnetModalChildEvent; +} + +export class DotnetSDKAcquisitionStarted extends DotnetAcquisitionStartedBase { public readonly eventName = 'DotnetSDKAcquisitionStarted'; - public readonly type = EventType.DotnetSDKAcquisitionStart; + public readonly type = EventType.DotnetModalChildEvent; +} - constructor(public readonly requestingExtensionId = '') { - super(); - } +export class DotnetGlobalSDKAcquisitionStarted extends DotnetAcquisitionStartedBase { + public readonly eventName = 'DotnetGlobalSDKAcquisitionStarted'; + public readonly type = EventType.DotnetModalChildEvent; +} - public getProperties() { - return {extensionId : TelemetryUtilities.HashData(this.requestingExtensionId)}; - } +export class DotnetASPNetRuntimeAcquisitionStarted extends DotnetAcquisitionStartedBase { + public readonly eventName = 'DotnetASPNetRuntimeAcquisitionStarted'; + public readonly type = EventType.DotnetModalChildEvent; } -export class DotnetAcquisitionCompleted extends IEvent { - public readonly eventName = 'DotnetAcquisitionCompleted'; - public readonly type = EventType.DotnetAcquisitionCompleted; +export class DotnetAcquisitionTotalSuccessEvent extends GenericModalEvent +{ + public readonly type = EventType.DotnetTotalSuccessEvent; + public readonly eventName = 'DotnetAcquisitionTotalSuccessEvent'; + public readonly mode; + public readonly installType: DotnetInstallType; - constructor(public readonly install: DotnetInstall, public readonly dotnetPath: string, public readonly version: string) { + constructor(public readonly startingVersion: string, public readonly install: DotnetInstall, public readonly requestingExtensionId = '', public readonly finalPath: string) { super(); + this.mode = install.installMode; + this.installType = install.isGlobal ? 'global' : 'local'; } - public getProperties(telemetry = false): { [key: string]: string } | undefined { - if (telemetry) { - return {...InstallToStrings(this.install), - AcquisitionCompletedInstallKey : this.install.installKey, - AcquisitionCompletedVersion: this.version}; - } else { - return {...InstallToStrings(this.install), - AcquisitionCompletedVersion: this.version, - AcquisitionCompletedInstallKey : this.install.installKey, - AcquisitionCompletedDotnetPath : this.dotnetPath}; - } - + public getProperties() { + return { + AcquisitionStartVersion : this.startingVersion, + AcquisitionInstallKey : this.install.installKey, + ...InstallToStrings(this.install), + ExtensionId : TelemetryUtilities.HashData(this.requestingExtensionId), + FinalPath : this.finalPath, + }; } } -export class DotnetRuntimeAcquisitionTotalSuccessEvent extends IEvent +abstract class DotnetAcquisitionTotalSuccessEventBase extends IEvent { - public readonly eventName = 'DotnetRuntimeAcquisitionTotalSuccessEvent'; - public readonly type = EventType.DotnetTotalSuccessEvent; - + public readonly type = EventType.DotnetModalChildEvent; - constructor(public readonly startingVersion: string, public readonly installKey: DotnetInstall, public readonly requestingExtensionId = '', public readonly finalPath: string) { + constructor(public readonly installKey: DotnetInstall) { super(); } public getProperties() { return { - AcquisitionStartVersion : this.startingVersion, - AcquisitionInstallKey : this.installKey.installKey, ...InstallToStrings(this.installKey), - ExtensionId : TelemetryUtilities.HashData(this.requestingExtensionId), - FinalPath : this.finalPath, }; } } -export class DotnetGlobalSDKAcquisitionTotalSuccessEvent extends IEvent +export class DotnetRuntimeAcquisitionTotalSuccessEvent extends DotnetAcquisitionTotalSuccessEventBase +{ + public readonly eventName = 'DotnetRuntimeAcquisitionTotalSuccessEvent'; +} + +export class DotnetGlobalSDKAcquisitionTotalSuccessEvent extends DotnetAcquisitionTotalSuccessEventBase { public readonly eventName = 'DotnetGlobalSDKAcquisitionTotalSuccessEvent'; +} + +export class DotnetASPNetRuntimeAcquisitionTotalSuccessEvent extends DotnetAcquisitionTotalSuccessEventBase +{ + public readonly eventName = 'DotnetASPNetRuntimeAcquisitionTotalSuccessEvent'; +} + + +export class DotnetAcquisitionRequested extends GenericModalEvent +{ + public readonly eventName = 'DotnetAcquisitionRequested'; public readonly type = EventType.DotnetTotalSuccessEvent; + public readonly mode; + public readonly installType: DotnetInstallType; - constructor(public readonly startingVersion: string, public readonly installKey: DotnetInstall, public readonly requestingExtensionId = '', public readonly finalPath: string) { + constructor(public readonly startingVersion: string, public readonly requestingId = '', mode : DotnetInstallMode, installType : DotnetInstallType) + { super(); + this.mode = mode; + this.installType = installType; } public getProperties() { + return {AcquisitionStartVersion : this.startingVersion, + RequestingExtensionId: TelemetryUtilities.HashData(this.requestingId)}; + } +} + + +abstract class DotnetAcquisitionRequestedEventBase extends IEvent +{ + public readonly type = EventType.DotnetModalChildEvent; + + constructor(public readonly startingVersion: string, public readonly requestingId = '', public readonly mode : DotnetInstallMode) { + super(); + } + + public getProperties() + { return { - AcquisitionStartVersion : this.startingVersion, - ...InstallToStrings(this.installKey), - ExtensionId : TelemetryUtilities.HashData(this.requestingExtensionId), - FinalPath : this.finalPath, - }; + AcquisitionStartVersion : this.startingVersion, + RequestingExtensionId: TelemetryUtilities.HashData(this.requestingId), + Mode: this.mode + }; + } +} + +export class DotnetRuntimeAcquisitionRequested extends DotnetAcquisitionRequestedEventBase +{ + public readonly eventName = 'DotnetRuntimeAcquisitionRequested'; +} + +export class DotnetGlobalSDKAcquisitionRequested extends DotnetAcquisitionRequestedEventBase +{ + public readonly eventName = 'DotnetGlobalSDKAcquisitionRequested'; +} + +export class DotnetASPNetRuntimeAcquisitionRequested extends DotnetAcquisitionRequestedEventBase +{ + public readonly eventName = 'DotnetASPNetRuntimeAcquisitionRequested'; +} + + +export class DotnetAcquisitionCompleted extends IEvent { + public readonly eventName = 'DotnetAcquisitionCompleted'; + public readonly type = EventType.DotnetAcquisitionCompleted; + + 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.install), + AcquisitionCompletedInstallKey : this.install.installKey, + AcquisitionCompletedVersion: this.version}; + } + else + { + return {...InstallToStrings(this.install), + AcquisitionCompletedVersion: this.version, + AcquisitionCompletedInstallKey : this.install.installKey, + AcquisitionCompletedDotnetPath : this.dotnetPath}; + } } } @@ -160,16 +249,37 @@ export abstract class DotnetAcquisitionError extends IEvent { } } +export class DotnetAcquisitionFinalError extends GenericModalEvent +{ + public readonly type = EventType.DotnetTotalSuccessEvent; + public readonly eventName = 'DotnetAcquisitionTotalSuccessEvent'; + public readonly mode; + public readonly installType: DotnetInstallType; + + constructor(public readonly error: Error, public readonly originalEventName : string, public readonly install: DotnetInstall) + { + super(); + this.mode = install.installMode; + this.installType = install.isGlobal ? 'global' : 'local'; + } + + 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.install?.installKey ?? 'null', + ...InstallToStrings(this.install!)}; + } +} + /** - * @remarks A wrapper around events to detect them as a failure to install the Global SDK. + * @remarks A wrapper around events to detect them as a failure to install. * 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 +abstract class DotnetAcquisitionFinalErrorBase extends DotnetAcquisitionError { - public eventName = 'DotnetGlobalSDKAcquisitionError'; - - constructor(public readonly error: Error, public readonly originalEventName : string, public readonly install: DotnetInstall | null) + constructor(public readonly error: Error, public readonly originalEventName : string, public readonly install: DotnetInstall) { super(error, install); } @@ -186,6 +296,21 @@ export class DotnetGlobalSDKAcquisitionError extends DotnetAcquisitionError } } +export class DotnetGlobalSDKAcquisitionError extends DotnetAcquisitionFinalErrorBase +{ + public eventName = 'DotnetGlobalSDKAcquisitionError'; +} + +export class DotnetRuntimeFinalAcquisitionError extends DotnetAcquisitionFinalErrorBase +{ + public eventName = 'DotnetRuntimeFinalAcquisitionError'; +} + +export class DotnetASPNetRuntimeFinalAcquisitionError extends DotnetAcquisitionFinalErrorBase +{ + public eventName = 'DotnetASPNetRuntimeFinalAcquisitionError'; +} + export abstract class DotnetNonAcquisitionError extends IEvent { public readonly type = EventType.DotnetAcquisitionError; public isError = true; @@ -894,20 +1019,6 @@ export class DotnetInstallationValidated extends DotnetAcquisitionMessage { } } -export class DotnetAcquisitionRequested extends DotnetAcquisitionMessage { - public readonly eventName = 'DotnetAcquisitionRequested'; - - constructor(public readonly startingVersion: string, - public readonly requestingId = '') { - super(); - } - - public getProperties() { - return {AcquisitionStartVersion : this.startingVersion, - RequestingExtensionId: TelemetryUtilities.HashData(this.requestingId)}; - } -} - export class DotnetAcquisitionStatusRequested extends DotnetAcquisitionMessage { public readonly eventName = 'DotnetAcquisitionStatusRequested'; diff --git a/vscode-dotnet-runtime-library/src/EventStream/EventStreamRegistration.ts b/vscode-dotnet-runtime-library/src/EventStream/EventStreamRegistration.ts index fc48d13d9e..3f4beee66d 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/EventStreamRegistration.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/EventStreamRegistration.ts @@ -15,6 +15,7 @@ import { StatusBarObserver } from './StatusBarObserver'; import { ITelemetryReporter, TelemetryObserver } from './TelemetryObserver'; import { IVSCodeExtensionContext } from '../IVSCodeExtensionContext'; import { IUtilityContext } from '../Utils/IUtilityContext'; +import { ModalEventRepublisher } from './ModalEventPublisher'; export interface IPackageJson { version: string; @@ -33,7 +34,7 @@ export interface IEventStreamContext { } export function registerEventStream(context: IEventStreamContext, extensionContext : IVSCodeExtensionContext, - utilityContext : IUtilityContext): [EventStream, vscode.OutputChannel, LoggingObserver, IEventStreamObserver[], TelemetryObserver | null] + utilityContext : IUtilityContext): [EventStream, vscode.OutputChannel, LoggingObserver, IEventStreamObserver[], TelemetryObserver | null, ModalEventRepublisher] { const outputChannel = vscode.window.createOutputChannel(context.displayChannelName); if (!fs.existsSync(context.logPath)) @@ -62,7 +63,10 @@ export function registerEventStream(context: IEventStreamContext, extensionConte eventStream.subscribe(event => telemetryObserver!.post(event)); } - return [eventStream, outputChannel, loggingObserver, eventStreamObservers, telemetryObserver]; + const modalEventObserver = new ModalEventRepublisher(eventStream); + eventStream.subscribe(event => modalEventObserver.post(event)); + + return [eventStream, outputChannel, loggingObserver, eventStreamObservers, telemetryObserver, modalEventObserver]; } export function enableExtensionTelemetry(extensionConfiguration: IExtensionConfiguration, enableTelemetryKey: string): boolean { diff --git a/vscode-dotnet-runtime-library/src/EventStream/EventType.ts b/vscode-dotnet-runtime-library/src/EventStream/EventType.ts index ac2b4d1907..900b0a870b 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/EventType.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/EventType.ts @@ -7,6 +7,7 @@ export enum EventType { DotnetAcquisitionStart, DotnetSDKAcquisitionStart, DotnetRuntimeAcquisitionStart, + DotnetASPNetRuntimeAcquisitionStarted, DotnetAcquisitionCompleted, DotnetAcquisitionError, DotnetAcquisitionSuccessEvent, @@ -18,5 +19,8 @@ export enum EventType { DotnetTotalSuccessEvent, DotnetUpgradedEvent, SuppressedAcquisitionError, - DotnetInstallExpectedAbort + DotnetInstallExpectedAbort, + + DotnetModalChildEvent, // For sub-events that are published as a more specific version of an existing published generic event. + // Example: DotnetAcquisitionStarted -> Children events are RuntimeStarted, SDKStarted, etc. } diff --git a/vscode-dotnet-runtime-library/src/test/ILoggingObserver.ts b/vscode-dotnet-runtime-library/src/EventStream/IModalEventPublisher.ts similarity index 62% rename from vscode-dotnet-runtime-library/src/test/ILoggingObserver.ts rename to vscode-dotnet-runtime-library/src/EventStream/IModalEventPublisher.ts index 7d171efee6..1341e86127 100644 --- a/vscode-dotnet-runtime-library/src/test/ILoggingObserver.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/IModalEventPublisher.ts @@ -1,12 +1,12 @@ -/*--------------------------------------------------------------------------------------------- -* Licensed to the .NET Foundation under one or more agreements. -* The .NET Foundation licenses this file to you under the MIT license. -*--------------------------------------------------------------------------------------------*/ -import { IEvent } from '../EventStream/IEvent'; -import { IEventStreamObserver } from '../EventStream/IEventStreamObserver'; - -export interface ILoggingObserver extends IEventStreamObserver { - post(event: IEvent): void; - dispose(): void; - getFileLocation(): string; -} +/*--------------------------------------------------------------------------------------------- +* Licensed to the .NET Foundation under one or more agreements. +* The .NET Foundation licenses this file to you under the MIT license. +*--------------------------------------------------------------------------------------------*/ +import { IEvent } from './IEvent'; +import { IEventStreamObserver } from './IEventStreamObserver'; + +export interface IModalEventRepublisher extends IEventStreamObserver +{ + post(event: IEvent): void; + dispose(): void; +} diff --git a/vscode-dotnet-runtime-library/src/EventStream/LoggingObserver.ts b/vscode-dotnet-runtime-library/src/EventStream/LoggingObserver.ts index fe018c1eb2..da2c7d30b5 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/LoggingObserver.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/LoggingObserver.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; import { IEvent } from './IEvent'; -import { ILoggingObserver } from '../test/ILoggingObserver'; +import { ILoggingObserver } from './ILoggingObserver'; export class LoggingObserver implements ILoggingObserver { private log: string[] = []; diff --git a/vscode-dotnet-runtime-library/src/EventStream/ModalEventPublisher.ts b/vscode-dotnet-runtime-library/src/EventStream/ModalEventPublisher.ts new file mode 100644 index 0000000000..232525990d --- /dev/null +++ b/vscode-dotnet-runtime-library/src/EventStream/ModalEventPublisher.ts @@ -0,0 +1,117 @@ +/*--------------------------------------------------------------------------------------------- +* Licensed to the .NET Foundation under one or more agreements. +* The .NET Foundation licenses this file to you under the MIT license. +*--------------------------------------------------------------------------------------------*/ +import +{ + DotnetAcquisitionFinalError, + DotnetAcquisitionRequested, + DotnetAcquisitionStarted, + DotnetAcquisitionTotalSuccessEvent, + DotnetASPNetRuntimeAcquisitionRequested, + DotnetASPNetRuntimeAcquisitionStarted, + DotnetASPNetRuntimeAcquisitionTotalSuccessEvent, + DotnetASPNetRuntimeFinalAcquisitionError, + DotnetGlobalSDKAcquisitionError, + DotnetGlobalSDKAcquisitionRequested, + DotnetGlobalSDKAcquisitionTotalSuccessEvent, + DotnetRuntimeAcquisitionRequested, + DotnetRuntimeAcquisitionStarted, + DotnetRuntimeAcquisitionTotalSuccessEvent, + DotnetRuntimeFinalAcquisitionError, + DotnetGlobalSDKAcquisitionStarted, + GenericModalEvent +} from '../EventStream/EventStreamEvents'; +import { IEventStream } from './EventStream'; +import { IEvent } from './IEvent'; +import { IModalEventRepublisher } from './IModalEventPublisher'; +/* tslint:disable:no-empty */ + +export class ModalEventRepublisher implements IModalEventRepublisher +{ + constructor(protected readonly eventStreamReference : IEventStream) {} + + private getSpecificEvent(event : GenericModalEvent) : IEvent | null + { + const mode = event.mode; + + if(event instanceof DotnetAcquisitionStarted) + { + switch(mode) + { + case 'sdk': + return event.installType === 'global' ? new DotnetGlobalSDKAcquisitionStarted(event.requestingExtensionId) : null; + case 'runtime': + return new DotnetRuntimeAcquisitionStarted(event.requestingExtensionId); + case 'aspnetcore': + return new DotnetASPNetRuntimeAcquisitionStarted(event.requestingExtensionId); + default: + break; + } + } + else if(event instanceof DotnetAcquisitionTotalSuccessEvent) + { + switch(mode) + { + case 'sdk': + return event.installType === 'global' ? new DotnetGlobalSDKAcquisitionTotalSuccessEvent(event.install) : null; + case 'runtime': + return new DotnetRuntimeAcquisitionTotalSuccessEvent(event.install); + case 'aspnetcore': + return new DotnetASPNetRuntimeAcquisitionTotalSuccessEvent(event.install); + default: + break; + } + } + else if(event instanceof DotnetAcquisitionFinalError) + { + switch(mode) + { + case 'sdk': + return event.installType === 'global' ? new DotnetGlobalSDKAcquisitionError(event.error, event.originalEventName, event.install) : null; + case 'runtime': + return new DotnetRuntimeFinalAcquisitionError(event.error, event.originalEventName, event.install); + case 'aspnetcore': + return new DotnetASPNetRuntimeFinalAcquisitionError(event.error, event.originalEventName, event.install); + default: + break; + } + } + else if(event instanceof DotnetAcquisitionRequested) + { + switch(mode) + { + case 'sdk': + return event.installType === 'global' ? new DotnetGlobalSDKAcquisitionRequested(event.startingVersion, event.requestingId, event.mode) : null; + case 'runtime': + return new DotnetRuntimeAcquisitionRequested(event.startingVersion, event.requestingId, event.mode); + case 'aspnetcore': + return new DotnetASPNetRuntimeAcquisitionRequested(event.startingVersion, event.requestingId, event.mode); + default: + break; + } + } + + return null; + } + + private republishEvent(event : GenericModalEvent) : void + { + const modeSpecificEvent = this.getSpecificEvent(event); + if(modeSpecificEvent !== null) + { + this.eventStreamReference.post(modeSpecificEvent); + } + } + + public post(event: IEvent): void + { + if(event instanceof GenericModalEvent) + { + this.republishEvent(event); + } + } + + public dispose(): void { + } +} diff --git a/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts b/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts index 7bc57c9cfd..42f6c6c45a 100644 --- a/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts +++ b/vscode-dotnet-runtime-library/src/EventStream/OutputChannelObserver.ts @@ -30,21 +30,19 @@ export class OutputChannelObserver implements IEventStreamObserver { { switch (event.type) { - case EventType.DotnetRuntimeAcquisitionStart: - const runtimeAcquisitionStarted = event as DotnetAcquisitionStarted; - this.outputChannel.append(`${runtimeAcquisitionStarted.requestingExtensionId} requested to download the .NET Runtime.`); - this.outputChannel.appendLine(''); - break; - case EventType.DotnetSDKAcquisitionStart: - const sdkAcquisitionStarted = event as DotnetAcquisitionStarted; - this.outputChannel.append(`${sdkAcquisitionStarted.requestingExtensionId} requested to download the .NET SDK.`); - this.outputChannel.appendLine(''); - break; case EventType.DotnetAcquisitionStart: const acquisitionStarted = event as DotnetAcquisitionStarted; this.inProgressDownloads.push(acquisitionStarted.install.installKey); + this.outputChannel.append(`${acquisitionStarted.requestingExtensionId} requested to download the ${ + acquisitionStarted.install.installMode === 'sdk' ? '.NET SDK' : + acquisitionStarted.install.installMode === 'runtime' ? '.NET Runtime' : + '.NET ASP.NET Runtime' + }.`); + + this.outputChannel.appendLine(''); + if (this.inProgressDownloads.length > 1) { // Already a download in progress this.outputChannel.appendLine(` -- Concurrent download of '${acquisitionStarted.install.installKey}' started!`); diff --git a/vscode-dotnet-runtime-library/src/IDotnetAcquireContext.ts b/vscode-dotnet-runtime-library/src/IDotnetAcquireContext.ts index ef78c18066..b4651386d1 100644 --- a/vscode-dotnet-runtime-library/src/IDotnetAcquireContext.ts +++ b/vscode-dotnet-runtime-library/src/IDotnetAcquireContext.ts @@ -10,7 +10,7 @@ export interface IDotnetAcquireContext { /** * @remarks - * The data required to acquire either the sdk or the runtime. + * The data required to acquire either the sdk or the runtimes. * * @property version - The major.minor version of the SDK or Runtime desired. * @@ -31,12 +31,19 @@ export interface IDotnetAcquireContext * @property architecture - null is for deliberate legacy install behavior that is not-architecture specific. * undefined is for the default of node.arch(). * Does NOT impact global installs yet, it will be ignored. Follows node architecture terminology. + * + * @property mode - Whether the install should be of the sdk, runtime, aspnet runtime, or windowsdesktop runtime. + * For example, set to 'aspnetcore' to install the aspnet runtime. + * Defaults to 'runtime' as this was the default choice before this existed. + * Note: If you try to call an install SDK endpoint or install runtime endpoint with a conflicting mode set, the mode will be ignored. + * Only certain install types / modes combinations are supported at this time, such as local runtimes, local aspnet, and global SDKs. */ version: string; requestingExtensionId?: string; errorConfiguration?: AcquireErrorConfiguration; installType?: DotnetInstallType; architecture?: string | null | undefined; + mode? : DotnetInstallMode; } /** diff --git a/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts b/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts index d0f69251f2..9d79bfceae 100644 --- a/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts +++ b/vscode-dotnet-runtime-library/src/Utils/ErrorHandler.ts @@ -5,9 +5,9 @@ import * as fs from 'fs'; import * as open from 'open'; import { + DotnetAcquisitionFinalError, DotnetCommandFailed, DotnetCommandSucceeded, - DotnetGlobalSDKAcquisitionError, DotnetInstallExpectedAbort, DotnetNotInstallRelatedCommandFailed, EventCancellationError @@ -74,13 +74,12 @@ export async function callWithErrorHandling(callback: () => T, context: IIssu ); } - if(acquireContext?.installMode === 'sdk' && acquireContext.acquisitionContext?.installType === 'global') + if(acquireContext) { - context.eventStream.post(new DotnetGlobalSDKAcquisitionError(error, (caughtError?.eventType) ?? 'Unknown', - GetDotnetInstallInfo(acquireContext.acquisitionContext.version, acquireContext.installMode, 'global', acquireContext.acquisitionContext.architecture ?? - + context.eventStream.post(new DotnetAcquisitionFinalError(error, (caughtError?.eventType) ?? 'Unknown', + GetDotnetInstallInfo(acquireContext.acquisitionContext.version, acquireContext.acquisitionContext.mode!, 'global', acquireContext.acquisitionContext.architecture ?? DotnetCoreAcquisitionWorker.defaultArchitecture() - ))); + ))); } if (context.errorConfiguration === AcquireErrorConfiguration.DisplayAllErrorPopups) diff --git a/vscode-dotnet-runtime-library/src/Utils/IIssueContext.ts b/vscode-dotnet-runtime-library/src/Utils/IIssueContext.ts index 1415d8ec30..427a7e06a4 100644 --- a/vscode-dotnet-runtime-library/src/Utils/IIssueContext.ts +++ b/vscode-dotnet-runtime-library/src/Utils/IIssueContext.ts @@ -3,7 +3,7 @@ * The .NET Foundation licenses this file to you under the MIT license. *--------------------------------------------------------------------------------------------*/ import { IEventStream } from '../EventStream/EventStream'; -import { ILoggingObserver } from '../test/ILoggingObserver'; +import { ILoggingObserver } from '../EventStream/ILoggingObserver'; import { IWindowDisplayWorker } from '../EventStream/IWindowDisplayWorker'; import { ErrorConfiguration } from './ErrorHandler'; import { IExtensionConfigurationWorker } from './IExtensionConfigurationWorker'; diff --git a/vscode-dotnet-runtime-library/src/Utils/InstallKeyUtilities.ts b/vscode-dotnet-runtime-library/src/Utils/InstallKeyUtilities.ts index d392ac9dda..82795228cc 100644 --- a/vscode-dotnet-runtime-library/src/Utils/InstallKeyUtilities.ts +++ b/vscode-dotnet-runtime-library/src/Utils/InstallKeyUtilities.ts @@ -8,7 +8,25 @@ import { looksLikeRuntimeVersion } from '../Acquisition/DotnetInstall'; import { DotnetInstall } from '../Acquisition/DotnetInstall'; import { DOTNET_INSTALL_MODE_LIST, DotnetInstallMode } from '../Acquisition/DotnetInstallMode'; import { IAcquisitionWorkerContext } from '../Acquisition/IAcquisitionWorkerContext'; -import * as os from 'os'; +import { DotnetInstallType } from '../IDotnetAcquireContext'; + + +export function getInstallKeyCustomArchitecture(version : string, architecture: string | null | undefined, mode: DotnetInstallMode, + installType : DotnetInstallType = 'local') : string +{ + if(architecture === null || architecture === 'null') + { + // Use the legacy method (no architecture) of installs + return installType === 'global' ? `${version}-global` : version; + } + else if(architecture === undefined) + { + architecture = DotnetCoreAcquisitionWorker.defaultArchitecture(); + } + + return installType === 'global' ? `${version}-global~${architecture}${mode === 'aspnetcore' ? '~aspnetcore' : ''}` : + `${version}~${architecture}${mode === 'aspnetcore' ? '~aspnetcore' : ''}`; +} export function getInstallKeyFromContext(ctx : IAcquisitionWorkerContext | undefined | null) : DotnetInstall | null { @@ -20,12 +38,12 @@ export function getInstallKeyFromContext(ctx : IAcquisitionWorkerContext | undef const acquireContext = ctx.acquisitionContext!; return { - installKey : DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(acquireContext.version, acquireContext.architecture, + installKey : getInstallKeyCustomArchitecture(acquireContext.version, acquireContext.architecture, ctx.acquisitionContext.mode!, acquireContext.installType), version: acquireContext.version, architecture: acquireContext.architecture, isGlobal: acquireContext.installType ? acquireContext.installType === 'global' : false, - installMode: ctx.installMode + installMode: ctx.acquisitionContext.mode! } as DotnetInstall; @@ -42,7 +60,7 @@ export function isGlobalLegacyInstallKey(installKey: string): boolean { export function getArchFromLegacyInstallKey(installKey: string): string | undefined { const splitKey = installKey.split('~'); - if (splitKey.length === 2) { + if (splitKey.length >= 2) { return splitKey[1]; } return undefined; diff --git a/vscode-dotnet-runtime-library/src/index.ts b/vscode-dotnet-runtime-library/src/index.ts index faee8d7082..160a863cb1 100644 --- a/vscode-dotnet-runtime-library/src/index.ts +++ b/vscode-dotnet-runtime-library/src/index.ts @@ -25,6 +25,7 @@ export * from './Utils/ExtensionConfigurationWorker'; export * from './Utils/FileUtilities'; export * from './Utils/ICommandExecutor'; export * from './Utils/IFileUtilities'; +export * from './Utils/InstallKeyUtilities'; export * from './Utils/IIssueContext'; export * from './Utils/IssueReporter'; export * from './Utils/IVSCodeEnvironment'; 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 9ddef0d07b..bad8385158 100644 --- a/vscode-dotnet-runtime-library/src/test/unit/DotnetCoreAcquisitionWorker.test.ts +++ b/vscode-dotnet-runtime-library/src/test/unit/DotnetCoreAcquisitionWorker.test.ts @@ -17,6 +17,9 @@ import { DotnetUninstallAllCompleted, DotnetUninstallAllStarted, TestAcquireCalled, + DotnetASPNetRuntimeAcquisitionTotalSuccessEvent, + DotnetGlobalSDKAcquisitionTotalSuccessEvent, + DotnetRuntimeAcquisitionTotalSuccessEvent } from '../../EventStream/EventStreamEvents'; import { EventType } from '../../EventStream/EventType'; import { @@ -34,9 +37,9 @@ import { InstallOwner, InstallRecord } from '../../Acquisition/InstallRecord'; import { GetDotnetInstallInfo } from '../../Acquisition/DotnetInstall'; import { DotnetInstallMode } from '../../Acquisition/DotnetInstallMode'; import { IAcquisitionWorkerContext } from '../../Acquisition/IAcquisitionWorkerContext'; -import { InstallTrackerSingleton } from '../../Acquisition/InstallTrackerSingleton'; -import { get } from 'http'; import { IEventStream } from '../../EventStream/EventStream'; +import { DotnetInstallType} from '../../IDotnetAcquireContext'; +import { getInstallKeyCustomArchitecture } from '../../Utils/InstallKeyUtilities'; const assert = chai.assert; chai.use(chaiAsPromised); @@ -63,25 +66,44 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { return [acquisitionWorker, invoker]; } + async function callAcquire(workerContext : IAcquisitionWorkerContext, acquisitionWorker : DotnetCoreAcquisitionWorker, invoker : IAcquisitionInvoker) + { + const result = workerContext.acquisitionContext.mode === undefined || workerContext.acquisitionContext.mode === 'runtime' ? + await acquisitionWorker.acquireLocalRuntime(workerContext, invoker) : + workerContext.acquisitionContext.mode === 'sdk' ? await acquisitionWorker.acquireLocalSDK(workerContext, invoker) : + workerContext.acquisitionContext.mode === 'aspnetcore' ? await acquisitionWorker.acquireLocalASPNET(workerContext, invoker) : + {} as { dotnetPath: string }; + + return result; + } + function migrateContextToNewInstall(worker : IAcquisitionWorkerContext, newVersion : string, newArch : string | null | undefined) { worker.acquisitionContext.version = newVersion; worker.acquisitionContext.architecture = newArch; } - function getExpectedPath(version: string, isRuntimeInstall: boolean): string { - return isRuntimeInstall ? - path.join(dotnetFolderName, version, os.platform() === 'win32' ? 'dotnet.exe' : 'dotnet') : - path.join(dotnetFolderName, os.platform() === 'win32' ? 'dotnet.exe' : 'dotnet'); + function getExpectedPath(installKey: string, mode: DotnetInstallMode): string + { + if(mode === 'runtime' || mode === 'aspnetcore') + { + return path.join(dotnetFolderName, installKey, os.platform() === 'win32' ? 'dotnet.exe' : 'dotnet') + } + else if(mode === 'sdk') + { + return path.join(dotnetFolderName, os.platform() === 'win32' ? 'dotnet.exe' : 'dotnet'); + } + + return 'There is a mode without a designated return path'; } async function assertAcquisitionSucceeded(installKey: string, exePath: string, eventStream: MockEventStream, context: MockExtensionContext, - isRuntimeInstall = true) + mode : DotnetInstallMode = 'runtime') { - const expectedPath = getExpectedPath(installKey, isRuntimeInstall); + const expectedPath = getExpectedPath(installKey, mode); // Path to exe should be correct assert.equal(exePath, expectedPath, 'The exe path is correct'); @@ -104,10 +126,13 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { //  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, (event as TestAcquireCalled).context.installType))) - === installKey) as TestAcquireCalled; - assert.exists(acquireEvent, 'The acquisition acquire event appears'); + getInstallKeyCustomArchitecture((event as TestAcquireCalled).context.version, + (event as TestAcquireCalled).context.architecture, mode, (event as TestAcquireCalled).context.installType) + === installKey + ) as TestAcquireCalled; + + assert.exists(acquireEvent, `The acquisition acquire event appears. Events: ${eventStream.events.filter(event => + event instanceof TestAcquireCalled).map((e) => e.eventName).join(', ')};`); assert.equal(acquireEvent!.context.dotnetPath, expectedPath, 'The acquisition went to the expected dotnetPath'); assert.equal(acquireEvent!.context.installDir, path.dirname(expectedPath), 'The acquisition went to the expected installation directory'); } @@ -116,72 +141,99 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { process.env._VSCODE_DOTNET_INSTALL_FOLDER = dotnetFolderName; }); - async function AssertInstallRuntime(acquisitionWorker : DotnetCoreAcquisitionWorker, context : MockExtensionContext, eventStream : MockEventStream, version : string, + async function AssertInstall(acquisitionWorker : DotnetCoreAcquisitionWorker, context : MockExtensionContext, eventStream : MockEventStream, version : string, invoker : IAcquisitionInvoker, workerContext : IAcquisitionWorkerContext) { - const installKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(workerContext.acquisitionContext.version, workerContext.acquisitionContext.architecture, 'local'); - const result = await acquisitionWorker.acquireRuntime(workerContext, invoker); - await assertAcquisitionSucceeded(installKey, result.dotnetPath, eventStream, context); - } + const installKey = getInstallKeyCustomArchitecture(workerContext.acquisitionContext.version, workerContext.acquisitionContext.architecture, + workerContext.acquisitionContext.mode ?? 'runtime', workerContext.acquisitionContext.installType ?? 'local'); - async function AssertInstallSDK(acquisitionWorker : DotnetCoreAcquisitionWorker, context : MockExtensionContext, eventStream : MockEventStream, version : string, - invoker : IAcquisitionInvoker, workerContext : IAcquisitionWorkerContext) - { - const installKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(workerContext.acquisitionContext.version, workerContext.acquisitionContext.architecture, 'local'); - const result = await acquisitionWorker.acquireSDK(workerContext, invoker); - await assertAcquisitionSucceeded(installKey, result.dotnetPath, eventStream, context, false); + const result = await callAcquire(workerContext, acquisitionWorker, invoker); + + await assertAcquisitionSucceeded(installKey, result.dotnetPath, eventStream, context, workerContext.acquisitionContext.mode!); } - test('Acquire Runtime Version', async () => { - const version = '1.0'; + async function acquireWithVersion(version : string, mode : DotnetInstallMode) + { const [eventStream, extContext] = setupStates(); - const ctx = getMockAcquisitionContext('runtime', version, expectedTimeoutTime, eventStream, extContext); + const ctx = getMockAcquisitionContext(mode, version, expectedTimeoutTime, eventStream, extContext); const [acquisitionWorker, invoker] = setupWorker(ctx, eventStream); - await AssertInstallRuntime(acquisitionWorker, extContext, eventStream, version, invoker, ctx); - }).timeout(expectedTimeoutTime); - test('Acquire SDK Version', async () => { - const version = '5.0'; - const [eventStream, extContext] = setupStates(); - const ctx = getMockAcquisitionContext('sdk', version, expectedTimeoutTime, eventStream, extContext ); - const [acquisitionWorker, invoker] = setupWorker(ctx, eventStream); - await AssertInstallSDK(acquisitionWorker, extContext, eventStream, version, invoker, ctx); - }).timeout(expectedTimeoutTime); + await AssertInstall(acquisitionWorker, extContext, eventStream, version, invoker, ctx); + } - test('Acquire SDK Status', async () => { - const version = '5.0'; + async function acquireStatus(version : string, mode : DotnetInstallMode, type : DotnetInstallType) + { const [eventStream, extContext] = setupStates(); - const ctx = getMockAcquisitionContext('sdk', version, expectedTimeoutTime, eventStream, extContext); + const ctx = getMockAcquisitionContext(mode, version, expectedTimeoutTime, eventStream, extContext); const [acquisitionWorker, invoker] = setupWorker(ctx, eventStream); - const installKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(ctx.acquisitionContext.version, ctx.acquisitionContext.architecture, 'local'); - let result = await acquisitionWorker.acquireStatus(ctx, 'sdk'); + const installKey = getInstallKeyCustomArchitecture(ctx.acquisitionContext.version, ctx.acquisitionContext.architecture, mode, type); + + let result = await acquisitionWorker.acquireStatus(ctx, mode); assert.isUndefined(result); const undefinedEvent = eventStream.events.find(event => event instanceof DotnetAcquisitionStatusUndefined); assert.exists(undefinedEvent, 'Undefined event exists'); - await acquisitionWorker.acquireSDK(ctx, invoker,); - result = await acquisitionWorker.acquireStatus(ctx, 'sdk', undefined); - await assertAcquisitionSucceeded(installKey, result!.dotnetPath, eventStream, extContext, false); + await callAcquire(ctx, acquisitionWorker, invoker); + + result = await acquisitionWorker.acquireStatus(ctx, mode, undefined); + await assertAcquisitionSucceeded(installKey, result!.dotnetPath, eventStream, extContext, mode); const resolvedEvent = eventStream.events.find(event => event instanceof DotnetAcquisitionStatusResolved); assert.exists(resolvedEvent, 'The sdk is resolved'); - }).timeout(expectedTimeoutTime); + } - test('Acquire Runtime Status', async () => { - const version = '5.0'; + async function repeatAcquisition(version : string, mode : DotnetInstallMode) + { const [eventStream, extContext] = setupStates(); - const ctx = getMockAcquisitionContext('runtime', version, expectedTimeoutTime, eventStream, extContext); + const ctx = getMockAcquisitionContext(mode, version, expectedTimeoutTime, eventStream, extContext); const [acquisitionWorker, invoker] = setupWorker(ctx, eventStream); - const installKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(ctx.acquisitionContext.version, ctx.acquisitionContext.architecture, 'local'); - let result = await acquisitionWorker.acquireStatus(ctx, 'sdk'); - assert.isUndefined(result); - const undefinedEvent = eventStream.events.find(event => event instanceof DotnetAcquisitionStatusUndefined); - assert.exists(undefinedEvent); - await acquisitionWorker.acquireRuntime(ctx, invoker,); - result = await acquisitionWorker.acquireStatus(ctx, 'runtime', undefined); - await assertAcquisitionSucceeded(installKey, result!.dotnetPath, eventStream, extContext, true); - const resolvedEvent = eventStream.events.find(event => event instanceof DotnetAcquisitionStatusResolved); - assert.exists(resolvedEvent); + for (let i = 0; i < 3; i++) + { + await callAcquire(ctx, acquisitionWorker, invoker); + } + + // We should only actually Acquire once + const events = eventStream.events.filter(event => event instanceof DotnetAcquisitionStarted); + assert.equal(events.length, 1); + } + + async function acquireAndUninstall(version : string, mode : DotnetInstallMode, type : DotnetInstallType) + { + const [eventStream, extContext] = setupStates(); + const ctx = getMockAcquisitionContext(mode, version, expectedTimeoutTime, eventStream, extContext); + const [acquisitionWorker, invoker] = setupWorker(ctx, eventStream); + + const installKey = getInstallKeyCustomArchitecture(ctx.acquisitionContext.version, ctx.acquisitionContext.architecture, mode, type); + const res = await callAcquire(ctx, acquisitionWorker, invoker); + await assertAcquisitionSucceeded(installKey, res.dotnetPath, eventStream, extContext, mode); + + await acquisitionWorker.uninstallAll(ctx.eventStream, ctx.installDirectoryProvider.getStoragePath(), ctx.extensionState); + assert.exists(eventStream.events.find(event => event instanceof DotnetUninstallAllStarted)); + assert.exists(eventStream.events.find(event => event instanceof DotnetUninstallAllCompleted)); + assert.isEmpty(extContext.get(installingVersionsKey, [])); + assert.isEmpty(extContext.get(installedVersionsKey, [])); + } + + test('Acquire Runtime Version', async () => + { + await acquireWithVersion('1.0', 'runtime'); + }).timeout(expectedTimeoutTime); + + test('Acquire SDK Version', async () => { + await acquireWithVersion('5.0', 'sdk'); + }).timeout(expectedTimeoutTime); + + test('Acquire ASP.NET Runtime Version', async () => + { + await acquireWithVersion('1.0', 'aspnetcore'); + }).timeout(expectedTimeoutTime); + + test('Acquire SDK Status', async () => { + await acquireStatus('5.0', 'sdk', 'local'); + }).timeout(expectedTimeoutTime); + + test('Acquire Runtime Status', async () => { + await acquireStatus('5.0', 'runtime', 'local'); }).timeout(expectedTimeoutTime); test('Acquire Runtime Version Multiple Times', async () => { @@ -192,8 +244,8 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { const [acquisitionWorker, invoker] = setupWorker(ctx, eventStream); for (let i = 0; i < numAcquisitions; i++) { - const pathResult = await acquisitionWorker.acquireRuntime(ctx, invoker); - const installKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(ctx.acquisitionContext.version, ctx.acquisitionContext.architecture, 'local'); + const pathResult = await acquisitionWorker.acquireLocalRuntime(ctx, invoker); + const installKey = getInstallKeyCustomArchitecture(ctx.acquisitionContext.version, ctx.acquisitionContext.architecture, 'runtime', 'local'); await assertAcquisitionSucceeded(installKey, pathResult.dotnetPath, eventStream, extContext); } @@ -211,8 +263,8 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { for (const version of versions) { migrateContextToNewInstall(ctx, version, os.arch()); - const res = await acquisitionWorker.acquireRuntime(ctx, invoker); - const installKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(ctx.acquisitionContext.version, ctx.acquisitionContext.architecture, 'local'); + const res = await acquisitionWorker.acquireLocalRuntime(ctx, invoker); + const installKey = getInstallKeyCustomArchitecture(ctx.acquisitionContext.version, ctx.acquisitionContext.architecture, 'runtime', 'local'); await assertAcquisitionSucceeded(installKey, res.dotnetPath, eventStream, extContext); } @@ -225,20 +277,17 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { test('Acquire Runtime and UninstallAll', async () => { - const version = '1.0'; - const [eventStream, extContext] = setupStates(); - const ctx = getMockAcquisitionContext('runtime', version, expectedTimeoutTime, eventStream, extContext); - const [acquisitionWorker, invoker] = setupWorker(ctx, eventStream); + await acquireAndUninstall('1.0', 'runtime', 'local'); + }).timeout(expectedTimeoutTime); - const installKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(ctx.acquisitionContext.version, ctx.acquisitionContext.architecture, 'local'); - const res = await acquisitionWorker.acquireRuntime(ctx, invoker); - await assertAcquisitionSucceeded(installKey, res.dotnetPath, eventStream, extContext); + test('Acquire ASP.NET and UninstallAll', async () => + { + await acquireAndUninstall('1.0', 'aspnetcore', 'local'); + }).timeout(expectedTimeoutTime); - await acquisitionWorker.uninstallAll(ctx.eventStream, ctx.installDirectoryProvider.getStoragePath(), ctx.extensionState); - assert.exists(eventStream.events.find(event => event instanceof DotnetUninstallAllStarted)); - assert.exists(eventStream.events.find(event => event instanceof DotnetUninstallAllCompleted)); - assert.isEmpty(extContext.get(installingVersionsKey, [])); - assert.isEmpty(extContext.get(installedVersionsKey, [])); + test('Acquire SDK and UninstallAll', async () => + { + await acquireAndUninstall('6.0', 'sdk', 'local'); }).timeout(expectedTimeoutTime); test('Graveyard Removes Failed Uninstalls', async () => { @@ -246,16 +295,16 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { const [eventStream, extContext] = setupStates(); const ctx = getMockAcquisitionContext('runtime', version, expectedTimeoutTime, eventStream, extContext); const [acquisitionWorker, invoker] = setupWorker(ctx, eventStream); - const installKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(ctx.acquisitionContext.version, ctx.acquisitionContext.architecture, 'local'); + const installKey = getInstallKeyCustomArchitecture(ctx.acquisitionContext.version, ctx.acquisitionContext.architecture, 'runtime', 'local'); const install = GetDotnetInstallInfo(version, 'runtime', 'local', os.arch()); - const res = await acquisitionWorker.acquireRuntime(ctx, invoker); + const res = await acquisitionWorker.acquireLocalRuntime(ctx, invoker); await assertAcquisitionSucceeded(installKey, res.dotnetPath, eventStream, extContext); acquisitionWorker.AddToGraveyard(ctx, install, 'Not applicable'); const versionToKeep = '5.0'; migrateContextToNewInstall(ctx, versionToKeep, os.arch()); - await acquisitionWorker.acquireRuntime(ctx, invoker); + await acquisitionWorker.acquireLocalRuntime(ctx, invoker); assert.exists(eventStream.events.find(event => event instanceof DotnetInstallGraveyardEvent), 'The graveyard tried to uninstall .NET'); assert.isEmpty(extContext.get(installingVersionsKey, []), 'We did not hang/ get interrupted during the install.'); @@ -289,19 +338,19 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { const [worker, invoker] = setupWorker(ctx, eventStream); // Install 5.0, 6.0 runtime without an architecture - await AssertInstallRuntime(worker, extensionContext, eventStream, runtimeV5, invoker, ctx); + await AssertInstall(worker, extensionContext, eventStream, runtimeV5, invoker, ctx); migrateContextToNewInstall(ctx, runtimeV6, null); - await AssertInstallRuntime(worker, extensionContext, eventStream, runtimeV6, invoker, ctx); + await AssertInstall(worker, extensionContext, eventStream, runtimeV6, invoker, ctx); // Install similar SDKs without an architecture. const sdkCtx = getMockAcquisitionContext('sdk', sdkV5, expectedTimeoutTime, eventStream, extensionContext, null); - await AssertInstallSDK(worker, extensionContext, eventStream, sdkV5, invoker, sdkCtx); + await AssertInstall(worker, extensionContext, eventStream, sdkV5, invoker, sdkCtx); migrateContextToNewInstall(sdkCtx, sdkV6, null); - await AssertInstallSDK(worker, extensionContext, eventStream, sdkV6, invoker, sdkCtx); + await AssertInstall(worker, extensionContext, eventStream, sdkV6, invoker, sdkCtx); // Install 5.0 runtime with an architecture. Share the same event stream and context. migrateContextToNewInstall(ctx, runtimeV5, os.arch()); - await AssertInstallRuntime(worker, extensionContext, eventStream, runtimeV5, invoker, ctx); + await AssertInstall(worker, extensionContext, eventStream, runtimeV5, invoker, ctx); // 5.0 legacy runtime should be replaced, but 6.0 runtime should remain, and all SDK items should remain. let detailedRemainingInstalls : InstallRecord[] = extensionContext.get(installedVersionsKey, []); @@ -311,11 +360,11 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { // Install a legacy runtime again to make sure its not removed when installing a new SDK with the same version migrateContextToNewInstall(ctx, runtimeV5, null); - await AssertInstallRuntime(worker, extensionContext, eventStream, runtimeV5, invoker, ctx); + await AssertInstall(worker, extensionContext, eventStream, runtimeV5, invoker, ctx); // Install non-legacy SDK migrateContextToNewInstall(sdkCtx, sdkV5, os.arch()); - await AssertInstallSDK(worker, extensionContext, eventStream, sdkV5, invoker, sdkCtx); + await AssertInstall(worker, extensionContext, eventStream, sdkV5, invoker, sdkCtx); // 6.0 sdk legacy should remain, as well as 5.0 and 6.0 runtime. 5.0 SDK should be removed. detailedRemainingInstalls = extensionContext.get(installedVersionsKey, []); @@ -324,19 +373,16 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { 'Only The Requested Legacy SDK is replaced when new SDK is installed'); }).timeout(expectedTimeoutTime * 6); - test('Repeated Acquisition', async () => { - const version = '1.0'; - const [eventStream, extContext] = setupStates(); - const ctx = getMockAcquisitionContext('runtime', version, expectedTimeoutTime, eventStream, extContext); - const [acquisitionWorker, invoker] = setupWorker(ctx, eventStream); + test('Repeated Runtime Acquisition', async () => { + await repeatAcquisition('1.0', 'runtime'); + }).timeout(expectedTimeoutTime); - for (let i = 0; i < 3; i++) - { - await acquisitionWorker.acquireRuntime(ctx, invoker); - } - // We should only actually Acquire once - const events = eventStream.events.filter(event => event instanceof DotnetAcquisitionStarted); - assert.equal(events.length, 1); + test('Repeated ASP.NET Acquisition', async () => { + await repeatAcquisition('1.0', 'aspnetcore'); + }).timeout(expectedTimeoutTime); + + test('Repeated SDK Acquisition', async () => { + await repeatAcquisition('5.0', 'sdk'); }).timeout(expectedTimeoutTime); test('Error is Redirected on Acquisition Failure', async () => { @@ -346,22 +392,7 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { const [acquisitionWorker, _] = setupWorker(ctx, eventStream); const acquisitionInvoker = new RejectingAcquisitionInvoker(eventStream); - return assert.isRejected(acquisitionWorker.acquireRuntime(ctx, acquisitionInvoker), '.NET Acquisition Failed: Rejecting message'); - }).timeout(expectedTimeoutTime); - - test('Repeated SDK Acquisition', async () => { - const version = '5.0'; - const [eventStream, extContext] = setupStates(); - const ctx = getMockAcquisitionContext('sdk', version, expectedTimeoutTime, eventStream, extContext); - const [acquisitionWorker, invoker] = setupWorker(ctx, eventStream); - - for (let i = 0; i < 3; i++) - { - await acquisitionWorker.acquireSDK(ctx, invoker); - } - // We should only actually Acquire once - const events = eventStream.events.filter(event => event instanceof DotnetAcquisitionStarted); - assert.equal(events.length, 1); + return assert.isRejected(acquisitionWorker.acquireLocalRuntime(ctx, acquisitionInvoker), '.NET Acquisition Failed: "Rejecting message"'); }).timeout(expectedTimeoutTime); test('Get Expected Path With Apostrophe In Install path', async () => { @@ -373,9 +404,9 @@ suite('DotnetCoreAcquisitionWorker Unit Tests', function () { const [acquisitionWorker, invoker] = setupWorker(acquisitionContext, eventStream); const acquisitionInvoker = new MockAcquisitionInvoker(acquisitionContext, installApostropheFolder); - const installKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(version, os.arch(), 'local'); - const result = await acquisitionWorker.acquireRuntime(acquisitionContext, acquisitionInvoker); - const expectedPath = getExpectedPath(installKey, true); + const installKey = getInstallKeyCustomArchitecture(version, os.arch(), 'runtime', 'local'); + const result = await acquisitionWorker.acquireLocalRuntime(acquisitionContext, acquisitionInvoker); + const expectedPath = getExpectedPath(installKey, acquisitionContext.acquisitionContext.mode!); assert.equal(result.dotnetPath, expectedPath); deleteFolderRecursive(path.join(process.cwd(), installApostropheFolder)); } 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 6e39f397dc..6b5752c860 100644 --- a/vscode-dotnet-runtime-library/src/test/unit/InstallTracker.test.ts +++ b/vscode-dotnet-runtime-library/src/test/unit/InstallTracker.test.ts @@ -87,7 +87,6 @@ suite('InstallTracker Unit Tests', () => { const validator = new MockInstallTracker(mockContext.eventStream, mockContext.extensionState); await validator.trackInstalledVersion(mockContext, defaultInstall); - // TODO: verify these tests still work const otherRequesterValidator = new MockInstallTracker(mockContextFromOtherExtension.eventStream, mockContext.extensionState); // Inject the extension state from the old class into the new one, because in vscode its a shared global state but here its mocked otherRequesterValidator.setExtensionState(validator.getExtensionState()); diff --git a/vscode-dotnet-runtime-library/src/test/unit/TestUtility.ts b/vscode-dotnet-runtime-library/src/test/unit/TestUtility.ts index f0edc250e1..75bab0387b 100644 --- a/vscode-dotnet-runtime-library/src/test/unit/TestUtility.ts +++ b/vscode-dotnet-runtime-library/src/test/unit/TestUtility.ts @@ -27,10 +27,9 @@ export function getMockAcquisitionContext(mode: DotnetInstallMode, version : str storagePath: '', extensionState: extensionContext, eventStream: myEventStream, - acquisitionContext: getMockAcquireContext(version, arch), + acquisitionContext: getMockAcquireContext(version, arch, mode), installationValidator: new MockInstallationValidator(myEventStream), timeoutSeconds: timeoutTime, - installMode: mode, proxyUrl: undefined, installDirectoryProvider: directory ? directory : directoryProviderFactory(mode, ''), isExtensionTelemetryInitiallyEnabled: true @@ -53,13 +52,14 @@ export function getMockUtilityContext() : IUtilityContext return utilityContext; } -export function getMockAcquireContext(nextAcquiringVersion : string, arch : string | null | undefined) : IDotnetAcquireContext +export function getMockAcquireContext(nextAcquiringVersion : string, arch : string | null | undefined, installMode : DotnetInstallMode) : IDotnetAcquireContext { const acquireContext : IDotnetAcquireContext = { version: nextAcquiringVersion, architecture: arch, - requestingExtensionId: 'test' + requestingExtensionId: 'test', + mode: installMode }; return acquireContext; } \ No newline at end of file diff --git a/vscode-dotnet-runtime-library/src/test/unit/VersionResolver.test.ts b/vscode-dotnet-runtime-library/src/test/unit/VersionResolver.test.ts index 6a038564c9..45134e5cc6 100644 --- a/vscode-dotnet-runtime-library/src/test/unit/VersionResolver.test.ts +++ b/vscode-dotnet-runtime-library/src/test/unit/VersionResolver.test.ts @@ -36,25 +36,25 @@ suite('VersionResolver Unit Tests', () => { }); test('Error With Invalid Version', async () => { - assert.isRejected(resolver.getFullRuntimeVersion('foo'), Error, 'Invalid version'); + assert.isRejected(resolver.getFullVersion('foo', 'runtime'), Error, 'Invalid version'); }); test('Error With Three Part Version', async () => { - assert.isRejected(resolver.getFullRuntimeVersion('1.0.16'), Error, 'Invalid version'); + assert.isRejected(resolver.getFullVersion('1.0.16', 'runtime'), Error, 'Invalid version'); }); test('Error With Invalid Major.Minor', async () => { - assert.isRejected(resolver.getFullRuntimeVersion('0.0'), Error, 'Unable to resolve version'); + assert.isRejected(resolver.getFullVersion('0.0', 'runtime'), Error, 'Unable to resolve version'); }); test('Resolve Valid Runtime Versions', async () => { for (const version of versionPairs) { - assert.equal(await resolver.getFullRuntimeVersion(version[0]), version[1]); + assert.equal(await resolver.getFullVersion(version[0], 'runtime'), version[1]); } }); test('Resolve Latest SDK Version', async () => { - assert.equal(await resolver.getFullSDKVersion('2.2'), '2.2.207'); + assert.equal(await resolver.getFullVersion('2.2', 'sdk'), '2.2.207'); }); test('Get Major from SDK Version', async () => { diff --git a/vscode-dotnet-runtime-library/src/test/unit/WebRequestWorker.test.ts b/vscode-dotnet-runtime-library/src/test/unit/WebRequestWorker.test.ts index ed7eaf32e4..b97d09e4e4 100644 --- a/vscode-dotnet-runtime-library/src/test/unit/WebRequestWorker.test.ts +++ b/vscode-dotnet-runtime-library/src/test/unit/WebRequestWorker.test.ts @@ -38,7 +38,7 @@ suite('WebRequestWorker Unit Tests', () => { const mockContext = getMockAcquisitionContext('runtime', '1.0', undefined, eventStream); const acquisitionWorker = new DotnetCoreAcquisitionWorker(getMockUtilityContext(), new MockVSCodeExtensionContext()); const invoker = new ErrorAcquisitionInvoker(eventStream); - return assert.isRejected(acquisitionWorker.acquireRuntime(mockContext, invoker), Error, '.NET Acquisition Failed'); + return assert.isRejected(acquisitionWorker.acquireLocalRuntime(mockContext, invoker), Error, '.NET Acquisition Failed'); }).timeout(maxTimeoutTime); test('Install Script Request Failure', async () => { diff --git a/vscode-dotnet-runtime.code-workspace b/vscode-dotnet-runtime.code-workspace index 56542c586a..2942fb4c8a 100644 --- a/vscode-dotnet-runtime.code-workspace +++ b/vscode-dotnet-runtime.code-workspace @@ -15,7 +15,8 @@ ], "settings": { "cSpell.words": [ - "DOTNETINSTALLMODELIST" + "DOTNETINSTALLMODELIST", + "Republisher" ] } } \ No newline at end of file diff --git a/vscode-dotnet-sdk-extension/package-lock.json b/vscode-dotnet-sdk-extension/package-lock.json index 70cb058cb0..74f66f5c93 100644 --- a/vscode-dotnet-sdk-extension/package-lock.json +++ b/vscode-dotnet-sdk-extension/package-lock.json @@ -34,7 +34,7 @@ "vscode-dotnet-runtime-library": "file:../vscode-dotnet-runtime-library" }, "devDependencies": { - "@types/source-map-support": "^0.5.6", + "@types/source-map-support": "^0.5.10", "copy-webpack-plugin": "^9.0.1", "webpack": "5.76.0", "webpack-cli": "4.9.1" @@ -371,9 +371,9 @@ } }, "node_modules/@types/source-map-support": { - "version": "0.5.6", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/source-map-support/-/source-map-support-0.5.6.tgz", - "integrity": "sha1-qkqMmOxzofHzCoE1c6myFUpus5o=", + "version": "0.5.10", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha1-gk3O+YlJa66Y6dBMjcGsHXDhvTk=", "dev": true, "license": "MIT", "dependencies": { @@ -4208,9 +4208,9 @@ } }, "@types/source-map-support": { - "version": "0.5.6", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/source-map-support/-/source-map-support-0.5.6.tgz", - "integrity": "sha1-qkqMmOxzofHzCoE1c6myFUpus5o=", + "version": "0.5.10", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha1-gk3O+YlJa66Y6dBMjcGsHXDhvTk=", "dev": true, "requires": { "source-map": "^0.6.0" diff --git a/vscode-dotnet-sdk-extension/package.json b/vscode-dotnet-sdk-extension/package.json index 0cdb882172..2e48e80915 100644 --- a/vscode-dotnet-sdk-extension/package.json +++ b/vscode-dotnet-sdk-extension/package.json @@ -111,7 +111,7 @@ "vscode-dotnet-runtime-library": "file:../vscode-dotnet-runtime-library" }, "devDependencies": { - "@types/source-map-support": "^0.5.6", + "@types/source-map-support": "^0.5.10", "copy-webpack-plugin": "^9.0.1", "webpack": "5.76.0", "webpack-cli": "4.9.1" diff --git a/vscode-dotnet-sdk-extension/src/extension.ts b/vscode-dotnet-sdk-extension/src/extension.ts index 2cdeb07bb8..d2109d38f0 100644 --- a/vscode-dotnet-sdk-extension/src/extension.ts +++ b/vscode-dotnet-sdk-extension/src/extension.ts @@ -16,16 +16,13 @@ import { DotnetAcquisitionStatusRequested, DotnetCoreAcquisitionWorker, DotnetSDKAcquisitionStarted, - DotnetVersionResolutionError, enableExtensionTelemetry, ErrorConfiguration, ExtensionConfigurationWorker, formatIssueUrl, IDotnetAcquireContext, - IDotnetListVersionsContext, IDotnetUninstallContext, EventBasedError, - IDotnetVersion, IEventStreamContext, IExtensionContext, IIssueContext, @@ -35,8 +32,6 @@ import { VersionResolver, VSCodeExtensionContext, VSCodeEnvironment, - WebRequestWorker, - IWindowDisplayWorker, WindowDisplayWorker, Debugging, CommandExecutor, @@ -94,7 +89,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 [eventStream, outputChannel, loggingObserver, eventStreamObservers, telemetryObserver, _] = registerEventStream(eventStreamContext, vsCodeExtensionContext, utilContext); const extensionConfigWorker = new ExtensionConfigurationWorker(extensionConfiguration, undefined, undefined); const issueContext = (errorConfiguration: ErrorConfiguration | undefined, commandName: string, version?: string) => { @@ -148,7 +143,7 @@ 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)); + eventStream.post(new DotnetAcquisitionRequested(commandContext.version, commandContext.requestingExtensionId ?? 'notProvided', 'sdk', 'local')); telemetryObserver?.setAcquisitionContext(acquisitionContext, commandContext); if(commandContext.installType === 'global') @@ -172,10 +167,10 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE { Debugging.log(`Acquisition Request was remarked as local.`, eventStream); - const resolvedVersion = await versionResolver.getFullSDKVersion(commandContext.version); + const resolvedVersion = await versionResolver.getFullVersion(commandContext.version, 'sdk'); acquisitionContext.acquisitionContext.version = resolvedVersion; const acquisitionInvoker = new LocalAcquisitionInvoker(acquisitionContext, utilContext); - const dotnetPath = await acquisitionWorker.acquireSDK(acquisitionContext, acquisitionInvoker); + const dotnetPath = await acquisitionWorker.acquireLocalSDK(acquisitionContext, acquisitionInvoker); const pathEnvVar = path.dirname(dotnetPath.dotnetPath); new CommandExecutor(acquisitionContext, utilContext).setPathEnvVar(pathEnvVar, troubleshootingUrl, displayWorker, vsCodeExtensionContext, false); @@ -194,7 +189,7 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE const fakeContext = getContext(null); const versionResolver = new VersionResolver(fakeContext); - commandContext.version = await versionResolver.getFullSDKVersion(commandContext.version); + commandContext.version = await versionResolver.getFullVersion(commandContext.version, 'sdk'); const dotnetPath = await acquisitionWorker.acquireStatus(fakeContext, 'sdk'); return dotnetPath; }, issueContext(commandContext.errorConfiguration, 'acquireSDKStatus')); @@ -222,13 +217,13 @@ export function activate(context: vscode.ExtensionContext, extensionContext?: IE extensionState: context.globalState, eventStream, installationValidator: new InstallationValidator(eventStream), - installMode: 'sdk', timeoutSeconds: resolvedTimeoutSeconds, installDirectoryProvider: new SdkInstallationDirectoryProvider(storagePath), acquisitionContext : commandContext ?? { // See runtime extension for more details on this fake context. version: 'unspecified', architecture: os.arch(), requestingExtensionId: 'notAnAcquisitionCall', + mode: 'sdk' }, isExtensionTelemetryInitiallyEnabled : isExtensionTelemetryEnabled, }; diff --git a/vscode-dotnet-sdk-extension/src/test/functional/DotnetCoreAcquisitionExtension.test.ts b/vscode-dotnet-sdk-extension/src/test/functional/DotnetCoreAcquisitionExtension.test.ts index 70351f8fd7..ac2d110d86 100644 --- a/vscode-dotnet-sdk-extension/src/test/functional/DotnetCoreAcquisitionExtension.test.ts +++ b/vscode-dotnet-sdk-extension/src/test/functional/DotnetCoreAcquisitionExtension.test.ts @@ -28,8 +28,7 @@ import { NoInstallAcquisitionInvoker, SdkInstallationDirectoryProvider, MockIndexWebRequestWorker, - MockVSCodeExtensionContext, - getMockUtilityContext, + getInstallKeyCustomArchitecture, IExistingPaths, getMockAcquisitionContext, getMockAcquisitionWorker, @@ -99,10 +98,10 @@ suite('DotnetCoreAcquisitionExtension End to End', function() const dotnetDir = installDirectoryProvider.getInstallDir(version); const dotnetExePath = path.join(dotnetDir, `dotnet${os.platform() === 'win32' ? '.exe' : ''}`); - const sdkCurrentInstallKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(version, os.arch()); + const sdkCurrentInstallKey = getInstallKeyCustomArchitecture(version, os.arch(), 'sdk', 'local'); const sdkDirCurrent = path.join(dotnetDir, 'sdk', sdkCurrentInstallKey); - const sdkEarlierInstallKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(earlierVersion, os.arch()); + const sdkEarlierInstallKey = getInstallKeyCustomArchitecture(earlierVersion, os.arch(), 'sdk', 'local'); const sdkDirEarlier = path.join(dotnetDir, 'sdk', sdkEarlierInstallKey); fs.mkdirSync(sdkDirCurrent, { recursive: true }); fs.mkdirSync(sdkDirEarlier, { recursive: true }); @@ -110,7 +109,7 @@ suite('DotnetCoreAcquisitionExtension End to End', function() // Assert preinstalled SDKs are detected const acquisitionInvoker = new NoInstallAcquisitionInvoker(eventStream, acquisitionWorker); - const result = await acquisitionWorker.acquireSDK(mockContext, acquisitionInvoker); + const result = await acquisitionWorker.acquireLocalSDK(mockContext, acquisitionInvoker); assert.equal(path.dirname(result.dotnetPath), dotnetDir, 'preinstalled sdk path is the same as installed sdk path on api call'); const preinstallEvents = eventStream.events .filter(event => event instanceof DotnetPreinstallDetected) @@ -135,7 +134,7 @@ suite('DotnetCoreAcquisitionExtension End to End', function() const mockContext = getMockAcquisitionContext('sdk', version, 10000, undefined, undefined, undefined, installDirectoryProvider); const acquisitionWorker = getMockAcquisitionWorker(mockContext); - const currentVersionInstallKey = DotnetCoreAcquisitionWorker.getInstallKeyCustomArchitecture(version, os.arch()); + const currentVersionInstallKey = getInstallKeyCustomArchitecture(version, os.arch(), 'sdk', 'local'); // Ensure nothing is returned when there is no preinstalled SDK const noPreinstallResult = await acquisitionWorker.acquireStatus(mockContext, 'sdk'); assert.isUndefined(noPreinstallResult); diff --git a/vscode-dotnet-sdk-extension/yarn.lock b/vscode-dotnet-sdk-extension/yarn.lock index 78b78561e3..bd4b29ded3 100644 --- a/vscode-dotnet-sdk-extension/yarn.lock +++ b/vscode-dotnet-sdk-extension/yarn.lock @@ -212,10 +212,10 @@ "@types/glob" "*" "@types/node" "*" -"@types/source-map-support@^0.5.6": - "integrity" "sha1-qkqMmOxzofHzCoE1c6myFUpus5o=" - "resolved" "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/source-map-support/-/source-map-support-0.5.6.tgz" - "version" "0.5.6" +"@types/source-map-support@^0.5.10": + "integrity" "sha1-gk3O+YlJa66Y6dBMjcGsHXDhvTk=" + "resolved" "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@types/source-map-support/-/source-map-support-0.5.10.tgz" + "version" "0.5.10" dependencies: "source-map" "^0.6.0" @@ -2193,6 +2193,7 @@ "proper-lockfile" "^4.1.2" "rimraf" "3.0.2" "run-script-os" "^1.1.6" + "semver" "^7.6.2" "shelljs" "0.8.5" "typescript" "4.4.4" "vscode-extension-telemetry" "^0.4.3"