Skip to content

Commit 9259f3b

Browse files
authored
findPath API Finds Extension Managed Runtimes (#2293)
* findPath API Finds Extension Managed Runtimes * Fix logic for rollFoward options It assumed that latestPatch and latestFeature could not be fulfilled if there was no patch requested by the user. But we should instead accept it. This would make '7.0.20' get rejected over '7.0' with 'latestPatch' which doesnt make sense because it should accept any later patch over the specified patch. In this case it was not specified so it should be accepted. Logic extracted out into another function was not changed. * Fix bad comment
1 parent 448ff2a commit 9259f3b

File tree

8 files changed

+137
-34
lines changed

8 files changed

+137
-34
lines changed

vscode-dotnet-runtime-extension/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vscode-dotnet-runtime-extension/src/extension.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,21 @@ export function activate(vsCodeContext: vscode.ExtensionContext, extensionContex
560560
}
561561
}
562562

563+
if (commandContext.acquireContext.mode === 'runtime' || commandContext.acquireContext.mode === 'aspnetcore')
564+
{
565+
const extensionManagedRuntimeRecordPaths = await finder.findExtensionManagedRuntimes();
566+
const filteredExtensionManagedRuntimeRecordPaths = validator.filterValidPaths(extensionManagedRuntimeRecordPaths, commandContext);
567+
for (const dotnetPath of filteredExtensionManagedRuntimeRecordPaths ?? [])
568+
{
569+
const validatedExistingManagedPath = await getPathIfValid(dotnetPath.path, validator, commandContext);
570+
if (validatedExistingManagedPath)
571+
{
572+
loggingObserver.dispose();
573+
return { dotnetPath: dotnetPath.path };
574+
}
575+
}
576+
}
577+
563578
const dotnetOnROOT = await finder.findDotnetRootPath(commandContext.acquireContext.architecture);
564579
const validatedRoot = await getPathIfValid(dotnetOnROOT, validator, commandContext);
565580
if (validatedRoot)

vscode-dotnet-runtime-extension/src/test/functional/DotnetCoreAcquisitionExtension.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,42 @@ suite('DotnetCoreAcquisitionExtension End to End', function ()
447447
}
448448
}).timeout(standardTimeoutTime);
449449

450+
test('Find dotnet PATH Command works with extension-managed runtime installations', async () =>
451+
{
452+
// First install a runtime that we'll try to find
453+
const version = '7.0';
454+
const runtimePath = await installRuntime(version, 'runtime', os.arch());
455+
assert.exists(runtimePath, 'Runtime should be installed successfully');
456+
457+
const originalPath = process.env.PATH;
458+
try
459+
{
460+
// Filter PATH to remove any existing dotnet installations
461+
process.env.PATH = process.env.PATH?.split(getPathSeparator())
462+
.filter((x: string) => !(includesPathWithLikelyDotnet(x)))
463+
.join(getPathSeparator());
464+
465+
const findPathContext: IDotnetFindPathContext = {
466+
acquireContext: {
467+
version,
468+
requestingExtensionId,
469+
mode: 'runtime',
470+
architecture: os.arch()
471+
},
472+
versionSpecRequirement: 'latestPatch'
473+
};
474+
475+
// Then verify we can find the extension-managed runtime
476+
const result = await vscode.commands.executeCommand<IDotnetAcquireResult>('dotnet.findPath', findPathContext);
477+
assert.exists(result, 'Should find a runtime');
478+
assert.exists(result!.dotnetPath, 'Should find a runtime path');
479+
assert.equal(result!.dotnetPath.toLowerCase(), runtimePath.toLowerCase(), 'Should find the correct runtime path');
480+
} finally
481+
{
482+
process.env.PATH = originalPath;
483+
}
484+
}).timeout(standardTimeoutTime);
485+
450486
test('Install SDK Globally E2E (Requires Admin)', async () =>
451487
{
452488
// We only test if the process is running under ADMIN because non-admin requires user-intervention.

vscode-dotnet-runtime-library/src/Acquisition/DotnetConditionValidator.ts

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { DOTNET_INFORMATION_CACHE_DURATION_MS } from './CacheTimeConstants';
1212
import { IAcquisitionWorkerContext } from './IAcquisitionWorkerContext';
1313
import { IDotnetConditionValidator } from './IDotnetConditionValidator';
1414
import { IDotnetListInfo } from './IDotnetListInfo';
15+
import { InstallRecordWithPath } from './InstallRecordWithPath';
1516
import * as versionUtils from './VersionUtilities';
1617

1718
type simplifiedVersionSpec = 'equal' | 'greater_than_or_equal' | 'less_than_or_equal' |
@@ -172,35 +173,11 @@ Please set the PATH to a dotnet host that matches the architecture ${requirement
172173

173174
if (availableMinor === requestedMinor && requestedPatch)
174175
{
175-
const availablePatchStr: string | null = requirement.acquireContext.mode !== 'sdk' ?
176-
versionUtils.getRuntimePatchVersionString(availableVersion, this.workerContext.eventStream, this.workerContext)
177-
:
178-
(() =>
179-
{
180-
const band = versionUtils.getSDKCompleteBandAndPatchVersionString(availableVersion, this.workerContext.eventStream, this.workerContext);
181-
if (band)
182-
{
183-
return band;
184-
}
185-
return null;
186-
})();
187-
const availablePatch = availablePatchStr ? Number(availablePatchStr) : null;
188-
189-
const availableBandStr: string | null = requirement.acquireContext.mode === 'sdk' ?
190-
(() =>
191-
{
192-
const featureBand = versionUtils.getFeatureBandFromVersion(availableVersion, this.workerContext.eventStream, this.workerContext, false);
193-
if (featureBand)
194-
{
195-
return featureBand;
196-
}
197-
return null;
198-
})() : null;
199-
const availableBand = availableBandStr ? Number(availableBandStr) : null;
176+
const availablePatch = this.getPatchOrFeatureBandWithPatch(availableVersion, requirement);
200177

201178
switch (adjustedVersionSpec)
202179
{
203-
// the 'availablePatch' must exist, since the version is from --list-runtimes or --list-sdks.
180+
// the 'availablePatch' must exist, since the version is from --list-runtimes or --list-sdks, or our internal tracking of installs.
204181
case 'equal':
205182
return availablePatch === requestedPatch;
206183
case 'greater_than_or_equal':
@@ -209,6 +186,7 @@ Please set the PATH to a dotnet host that matches the architecture ${requirement
209186
case 'less_than_or_equal':
210187
return availablePatch! <= requestedPatch;
211188
case 'latestPatch':
189+
const availableBand = this.getFeatureBand(availableVersion, requirement);
212190
const requestedBandStr = requirement.acquireContext.mode === 'sdk' ? versionUtils.getFeatureBandFromVersion(requestedVersion, this.workerContext.eventStream, this.workerContext, false) ?? null : null;
213191
const requestedBand = requestedBandStr ? Number(requestedBandStr) : null;
214192
return availablePatch! >= requestedPatch && (availableBand ? availableBand === requestedBand : true);
@@ -226,7 +204,10 @@ Please set the PATH to a dotnet host that matches the architecture ${requirement
226204
return availableMinor <= requestedMinor;
227205
case 'latestPatch':
228206
case 'latestFeature':
229-
return false
207+
const availableBand = this.getFeatureBand(availableVersion, requirement);
208+
const requestedBandStr = requirement.acquireContext.mode === 'sdk' ? versionUtils.getFeatureBandFromVersion(requestedVersion, this.workerContext.eventStream, this.workerContext, false) ?? null : null;
209+
const requestedBand = requestedBandStr ? Number(requestedBandStr) : null;
210+
return availableMinor === requestedMinor && (availableBand ? availableBand === requestedBand : true);
230211
}
231212
}
232213
}
@@ -247,6 +228,45 @@ Please set the PATH to a dotnet host that matches the architecture ${requirement
247228
}
248229
}
249230

231+
private getFeatureBand(availableVersion: string, requirement: IDotnetFindPathContext): number | null
232+
{
233+
const availableBandStr: string | null = requirement.acquireContext.mode === 'sdk' ?
234+
(() =>
235+
{
236+
const featureBand = versionUtils.getFeatureBandFromVersion(availableVersion, this.workerContext.eventStream, this.workerContext, false);
237+
if (featureBand)
238+
{
239+
return featureBand;
240+
}
241+
return null;
242+
})() : null;
243+
return availableBandStr ? Number(availableBandStr) : null;
244+
}
245+
246+
private getPatchOrFeatureBandWithPatch(availableVersion: string, requirement: IDotnetFindPathContext): number | null
247+
{
248+
const availablePatchStr: string | null = requirement.acquireContext.mode !== 'sdk' ?
249+
versionUtils.getRuntimePatchVersionString(availableVersion, this.workerContext.eventStream, this.workerContext)
250+
:
251+
(() =>
252+
{
253+
const band = versionUtils.getSDKCompleteBandAndPatchVersionString(availableVersion, this.workerContext.eventStream, this.workerContext);
254+
if (band)
255+
{
256+
return band;
257+
}
258+
return null;
259+
})();
260+
261+
const availablePatch = availablePatchStr ? Number(availablePatchStr) : null;
262+
return availablePatch;
263+
}
264+
265+
public filterValidPaths(recordPaths: InstallRecordWithPath[], requirement: IDotnetFindPathContext): InstallRecordWithPath[]
266+
{
267+
return recordPaths.filter(installInfo => this.stringVersionMeetsRequirement(installInfo.installRecord.dotnetInstall.version, requirement.acquireContext.version, requirement));
268+
}
269+
250270
private stringArchitectureMeetsRequirement(outputArchitecture: string, requiredArchitecture: string | null | undefined): boolean
251271
{
252272
return !requiredArchitecture || !outputArchitecture || FileUtilities.dotnetInfoArchToNodeArch(outputArchitecture, this.workerContext.eventStream) === requiredArchitecture;

vscode-dotnet-runtime-library/src/Acquisition/DotnetPathFinder.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import { IFileUtilities } from '../Utils/IFileUtilities';
3535
import { EnvironmentVariableIsDefined, getDotnetExecutable, getOSArch, getPathSeparator } from '../Utils/TypescriptUtilities';
3636
import { DOTNET_INFORMATION_CACHE_DURATION_MS, SYS_CMD_SEARCH_CACHE_DURATION_MS } from './CacheTimeConstants';
3737
import { DotnetConditionValidator } from './DotnetConditionValidator';
38+
import { InstallRecordWithPath } from './InstallRecordWithPath';
39+
import { InstallTrackerSingleton } from './InstallTrackerSingleton';
3840
import { RegistryReader } from './RegistryReader';
3941

4042
export class DotnetPathFinder implements IDotnetPathFinder
@@ -92,6 +94,13 @@ export class DotnetPathFinder implements IDotnetPathFinder
9294
return undefined;
9395
}
9496

97+
public async findExtensionManagedRuntimes(): Promise<InstallRecordWithPath[]>
98+
{
99+
const installedVersions = await InstallTrackerSingleton.getInstance(this.workerContext.eventStream, this.workerContext.extensionState).getExistingInstalls(this.workerContext.installDirectoryProvider);
100+
const installedVersionRuntimeHostPaths = installedVersions.map(install => ({ installRecord: install, path: path.join(this.workerContext.installDirectoryProvider.getInstallDir(install.dotnetInstall.installId), getDotnetExecutable()) }));
101+
return installedVersionRuntimeHostPaths ?? [];
102+
}
103+
95104
/**
96105
* @remarks This only checks dotnet --list-runtimes or --list-sdks, so it can be more performant for the base case.
97106
* This allows skipping which, where, and also does not rely on --info which is slower because it shells to the SDK instead of just being the host.
@@ -100,11 +109,14 @@ export class DotnetPathFinder implements IDotnetPathFinder
100109
public async findDotnetFastFromListOnly(): Promise<string[] | undefined>
101110
{
102111
const oldLookup = process.env.DOTNET_MULTILEVEL_LOOKUP;
103-
try {
112+
try
113+
{
104114
process.env.DOTNET_MULTILEVEL_LOOKUP = '0'; // make it so --list-runtimes only finds the runtimes on that path: https://learn.microsoft.com/en-us/dotnet/core/compatibility/deployment/7.0/multilevel-lookup#reason-for-change
105115
const finalPath = await this.getTruePath(['dotnet']);
106116
return this.returnWithRestoringEnvironment(finalPath, 'DOTNET_MULTILEVEL_LOOKUP', oldLookup);
107-
} finally {
117+
}
118+
finally
119+
{
108120
process.env.DOTNET_MULTILEVEL_LOOKUP = oldLookup; // Ensure the environment variable is always restored
109121
}
110122
}

vscode-dotnet-runtime-library/src/Acquisition/IDotnetPathFinder.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
* The .NET Foundation licenses this file to you under the MIT license.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { InstallRecordWithPath } from './InstallRecordWithPath';
7+
68
export interface IDotnetPathFinder
79
{
8-
findDotnetRootPath(requestedArchitecture : string): Promise<string | undefined>;
9-
findRawPathEnvironmentSetting(tryUseTrueShell : boolean): Promise<string[] | undefined>;
10-
findRealPathEnvironmentSetting(tryUseTrueShell : boolean): Promise<string[] | undefined>;
11-
findHostInstallPaths(requestedArchitecture : string): Promise<string[] | undefined>;
10+
findDotnetRootPath(requestedArchitecture: string): Promise<string | undefined>;
11+
findRawPathEnvironmentSetting(tryUseTrueShell: boolean): Promise<string[] | undefined>;
12+
findRealPathEnvironmentSetting(tryUseTrueShell: boolean): Promise<string[] | undefined>;
13+
findHostInstallPaths(requestedArchitecture: string): Promise<string[] | undefined>;
14+
findExtensionManagedRuntimes(): Promise<InstallRecordWithPath[]>;
1215
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Licensed to the .NET Foundation under one or more agreements.
3+
* The .NET Foundation licenses this file to you under the MIT license.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { InstallRecord } from './InstallRecord';
7+
8+
/**
9+
* Represents a .NET installation record along with its filesystem path
10+
*/
11+
export interface InstallRecordWithPath
12+
{
13+
installRecord: InstallRecord;
14+
path: string;
15+
}

vscode-dotnet-runtime-library/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export * from './Acquisition/IInstallationDirectoryProvider';
2222
export * from './Acquisition/IJsonInstaller';
2323
export * from './Acquisition/InstallationValidator';
2424
export * from './Acquisition/InstallRecord';
25+
export * from './Acquisition/InstallRecordWithPath';
2526
export * from './Acquisition/IVersionResolver';
2627
export * from './Acquisition/JsonInstaller';
2728
export * from './Acquisition/LinuxGlobalInstaller';
@@ -67,3 +68,4 @@ export * from './Utils/TypescriptUtilities';
6768
export * from './Utils/VSCodeEnvironment';
6869
export * from './Utils/WebRequestWorkerSingleton';
6970
export * from './VSCodeExtensionContext';
71+

0 commit comments

Comments
 (0)