Skip to content

Commit 6b94c8b

Browse files
Use the component adapter in the extension. (#13869)
This allows us to start using the new discovery code in the extension. The key thing is to be careful not to regress in the available functionality. So for now we have disabled use of the component. When we feel comfortable with it we can enable it by setting the default for `ComponentAdapter.enabled` to `true`. This PR involves small fixes to a large number of files due to API changes. The adapter is actually used (injected) in the following files: src/client/interpreter/interpreterService.ts src/client/interpreter/helpers.ts src/client/pythonEnvironments/discovery/locators/index.ts src/client/pythonEnvironments/discovery/locators/services/condaService.ts src/client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter.ts
1 parent 7a86cf0 commit 6b94c8b

22 files changed

+183
-77
lines changed

src/client/application/diagnostics/checks/macPythonInterpreter.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,26 +95,27 @@ export class InvalidMacPythonInterpreterService extends BaseDiagnosticsService {
9595
return [];
9696
}
9797

98-
if (!this.helper.isMacDefaultPythonPath(settings.pythonPath)) {
98+
if (!(await this.helper.isMacDefaultPythonPath(settings.pythonPath))) {
9999
return [];
100100
}
101101
if (!currentInterpreter || currentInterpreter.envType !== EnvironmentType.Unknown) {
102102
return [];
103103
}
104104

105105
const interpreters = await this.interpreterService.getInterpreters(resource);
106-
if (interpreters.filter((i) => !this.helper.isMacDefaultPythonPath(i.path)).length === 0) {
107-
return [
108-
new InvalidMacPythonInterpreterDiagnostic(
109-
DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic,
110-
resource
111-
)
112-
];
106+
for (const info of interpreters) {
107+
if (!(await this.helper.isMacDefaultPythonPath(info.path))) {
108+
return [
109+
new InvalidMacPythonInterpreterDiagnostic(
110+
DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic,
111+
resource
112+
)
113+
];
114+
}
113115
}
114-
115116
return [
116117
new InvalidMacPythonInterpreterDiagnostic(
117-
DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic,
118+
DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic,
118119
resource
119120
)
120121
];

src/client/common/process/pythonExecutionFactory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export class PythonExecutionFactory implements IPythonExecutionFactory {
7171
processService,
7272
this.fileSystem,
7373
undefined,
74-
this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)
74+
await this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)
7575
);
7676
}
7777

src/client/interpreter/contracts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export const IInterpreterHelper = Symbol('IInterpreterHelper');
100100
export interface IInterpreterHelper {
101101
getActiveWorkspaceUri(resource: Resource): WorkspacePythonPath | undefined;
102102
getInterpreterInformation(pythonPath: string): Promise<undefined | Partial<PythonEnvironment>>;
103-
isMacDefaultPythonPath(pythonPath: string): Boolean;
103+
isMacDefaultPythonPath(pythonPath: string): Promise<boolean>;
104104
getInterpreterTypeDisplayName(interpreterType: EnvironmentType): string | undefined;
105105
getBestInterpreter(interpreters?: PythonEnvironment[]): PythonEnvironment | undefined;
106106
}

src/client/interpreter/helpers.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
PythonEnvironment,
1616
sortInterpreters
1717
} from '../pythonEnvironments/info';
18-
import { IInterpreterHelper } from './contracts';
18+
import { IComponentAdapter, IInterpreterHelper } from './contracts';
1919
import { IInterpreterHashProviderFactory } from './locators/types';
2020

2121
const EXPITY_DURATION = 24 * 60 * 60 * 1000;
@@ -44,12 +44,19 @@ export function isInterpreterLocatedInWorkspace(interpreter: PythonEnvironment,
4444
return interpreterPath.startsWith(resourcePath);
4545
}
4646

47+
// The parts of IComponentAdapter used here.
48+
interface IComponent {
49+
getInterpreterInformation(pythonPath: string): Promise<undefined | Partial<PythonEnvironment>>;
50+
isMacDefaultPythonPath(pythonPath: string): Promise<boolean | undefined>;
51+
}
52+
4753
@injectable()
4854
export class InterpreterHelper implements IInterpreterHelper {
4955
private readonly persistentFactory: IPersistentStateFactory;
5056
constructor(
5157
@inject(IServiceContainer) private serviceContainer: IServiceContainer,
52-
@inject(InterpeterHashProviderFactory) private readonly hashProviderFactory: IInterpreterHashProviderFactory
58+
@inject(InterpeterHashProviderFactory) private readonly hashProviderFactory: IInterpreterHashProviderFactory,
59+
@inject(IComponentAdapter) private readonly pyenvs: IComponent
5360
) {
5461
this.persistentFactory = this.serviceContainer.get<IPersistentStateFactory>(IPersistentStateFactory);
5562
}
@@ -78,6 +85,11 @@ export class InterpreterHelper implements IInterpreterHelper {
7885
}
7986
}
8087
public async getInterpreterInformation(pythonPath: string): Promise<undefined | Partial<PythonEnvironment>> {
88+
const found = await this.pyenvs.getInterpreterInformation(pythonPath);
89+
if (found !== undefined) {
90+
return found;
91+
}
92+
8193
const fileHash = await this.hashProviderFactory
8294
.create({ pythonPath })
8395
.then((provider) => provider.getInterpreterHash(pythonPath))
@@ -115,7 +127,11 @@ export class InterpreterHelper implements IInterpreterHelper {
115127
return;
116128
}
117129
}
118-
public isMacDefaultPythonPath(pythonPath: string) {
130+
public async isMacDefaultPythonPath(pythonPath: string): Promise<boolean> {
131+
const result = await this.pyenvs.isMacDefaultPythonPath(pythonPath);
132+
if (result !== undefined) {
133+
return result;
134+
}
119135
return isMacDefaultPythonPath(pythonPath);
120136
}
121137
public getInterpreterTypeDisplayName(interpreterType: EnvironmentType) {

src/client/interpreter/interpreterService.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { EnvironmentType, PythonEnvironment } from '../pythonEnvironments/info';
2525
import { captureTelemetry } from '../telemetry';
2626
import { EventName } from '../telemetry/constants';
2727
import {
28+
IComponentAdapter,
2829
IInterpreterDisplay,
2930
IInterpreterHelper,
3031
IInterpreterLocatorService,
@@ -40,6 +41,11 @@ export type GetInterpreterOptions = {
4041
onSuggestion?: boolean;
4142
};
4243

44+
// The parts of IComponentAdapter used here.
45+
interface IComponent {
46+
getInterpreterDetails(pythonPath: string): Promise<undefined | PythonEnvironment>;
47+
}
48+
4349
@injectable()
4450
export class InterpreterService implements Disposable, IInterpreterService {
4551
public get hasInterpreters(): Promise<boolean> {
@@ -70,7 +76,8 @@ export class InterpreterService implements Disposable, IInterpreterService {
7076

7177
constructor(
7278
@inject(IServiceContainer) private serviceContainer: IServiceContainer,
73-
@inject(InterpeterHashProviderFactory) private readonly hashProviderFactory: IInterpreterHashProviderFactory
79+
@inject(InterpeterHashProviderFactory) private readonly hashProviderFactory: IInterpreterHashProviderFactory,
80+
@inject(IComponentAdapter) private readonly pyenvs: IComponent
7481
) {
7582
this.locator = serviceContainer.get<IInterpreterLocatorService>(
7683
IInterpreterLocatorService,
@@ -160,6 +167,11 @@ export class InterpreterService implements Disposable, IInterpreterService {
160167
return this.getInterpreterDetails(fullyQualifiedPath, resource);
161168
}
162169
public async getInterpreterDetails(pythonPath: string, resource?: Uri): Promise<PythonEnvironment | undefined> {
170+
const info = await this.pyenvs.getInterpreterDetails(pythonPath);
171+
if (info !== undefined) {
172+
return info;
173+
}
174+
163175
// If we don't have the fully qualified path, then get it.
164176
if (path.basename(pythonPath) === pythonPath) {
165177
const pythonExecutionFactory = this.serviceContainer.get<IPythonExecutionFactory>(IPythonExecutionFactory);

src/client/interpreter/locators/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export interface IWindowsStoreInterpreter {
5353
* @returns {boolean}
5454
* @memberof WindowsStoreInterpreter
5555
*/
56-
isWindowsStoreInterpreter(pythonPath: string): boolean;
56+
isWindowsStoreInterpreter(pythonPath: string): Promise<boolean>;
5757
/**
5858
* Whether this is a python executable in a windows app store folder that is internal and can be hidden from users.
5959
*

src/client/pythonEnvironments/discovery/locators/index.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
CONDA_ENV_SERVICE,
1616
CURRENT_PATH_SERVICE,
1717
GLOBAL_VIRTUAL_ENV_SERVICE,
18+
IComponentAdapter,
1819
IInterpreterLocatorHelper,
1920
IInterpreterLocatorService,
2021
KNOWN_PATH_SERVICE,
@@ -180,6 +181,12 @@ function matchURI(uri: Uri, ...candidates: Uri[]): boolean {
180181
return matchedUri !== undefined;
181182
}
182183

184+
// The parts of IComponentAdapter used here.
185+
interface IComponent {
186+
hasInterpreters: Promise<boolean | undefined>;
187+
getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise<PythonEnvironment[] | undefined>;
188+
}
189+
183190
/**
184191
* Facilitates locating Python interpreters.
185192
*/
@@ -198,7 +205,10 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
198205
private readonly onLocatingEmitter:EventEmitter<Promise<PythonEnvironment[]>> =
199206
new EventEmitter<Promise<PythonEnvironment[]>>();
200207

201-
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
208+
constructor(
209+
@inject(IServiceContainer) private serviceContainer: IServiceContainer,
210+
@inject(IComponentAdapter) private readonly pyenvs: IComponent,
211+
) {
202212
this._hasInterpreters = createDeferred<boolean>();
203213
serviceContainer.get<Disposable[]>(IDisposableRegistry).push(this);
204214
this.platform = serviceContainer.get<IPlatformService>(IPlatformService);
@@ -219,7 +229,12 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
219229
}
220230

221231
public get hasInterpreters(): Promise<boolean> {
222-
return this._hasInterpreters.completed ? this._hasInterpreters.promise : Promise.resolve(false);
232+
return this.pyenvs.hasInterpreters.then((res) => {
233+
if (res !== undefined) {
234+
return res;
235+
}
236+
return this._hasInterpreters.completed ? this._hasInterpreters.promise : Promise.resolve(false);
237+
});
223238
}
224239

225240
/**
@@ -239,6 +254,10 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
239254
*/
240255
@traceDecorators.verbose('Get Interpreters')
241256
public async getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise<PythonEnvironment[]> {
257+
const envs = await this.pyenvs.getInterpreters(resource, options);
258+
if (envs !== undefined) {
259+
return envs;
260+
}
242261
const locators = this.getLocators(options);
243262
const promises = locators.map(async (provider) => provider.getInterpreters(resource));
244263
locators.forEach((locator) => {

src/client/pythonEnvironments/discovery/locators/services/condaService.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { IFileSystem, IPlatformService } from '../../../../common/platform/types
1313
import { IProcessServiceFactory } from '../../../../common/process/types';
1414
import { IConfigurationService, IDisposableRegistry, IPersistentStateFactory } from '../../../../common/types';
1515
import { cache } from '../../../../common/utils/decorators';
16-
import { ICondaService, IInterpreterLocatorService, WINDOWS_REGISTRY_SERVICE } from '../../../../interpreter/contracts';
16+
import { IComponentAdapter, ICondaService, IInterpreterLocatorService, WINDOWS_REGISTRY_SERVICE } from '../../../../interpreter/contracts';
1717
import { EnvironmentType, PythonEnvironment } from '../../../info';
1818
import { CondaEnvironmentInfo, CondaInfo } from './conda';
1919
import { parseCondaEnvFileContents } from './condaHelper';
@@ -49,6 +49,12 @@ export const CondaLocationsGlobWin = `{${condaGlobPathsForWindows.join(',')}}`;
4949

5050
export const CondaGetEnvironmentPrefix = 'Outputting Environment Now...';
5151

52+
// The parts of IComponentAdapter used here.
53+
interface IComponent {
54+
isCondaEnvironment(interpreterPath: string): Promise<boolean | undefined>;
55+
getCondaEnvironment(interpreterPath: string): Promise<CondaEnvironmentInfo | undefined>;
56+
}
57+
5258
/**
5359
* A wrapper around a conda installation.
5460
*/
@@ -66,6 +72,7 @@ export class CondaService implements ICondaService {
6672
@inject(IConfigurationService) private configService: IConfigurationService,
6773
@inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry,
6874
@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService,
75+
@inject(IComponentAdapter) private readonly pyenvs: IComponent,
6976
@inject(IInterpreterLocatorService)
7077
@named(WINDOWS_REGISTRY_SERVICE)
7178
@optional()
@@ -183,6 +190,10 @@ export class CondaService implements ICondaService {
183190
* @memberof CondaService
184191
*/
185192
public async isCondaEnvironment(interpreterPath: string): Promise<boolean> {
193+
const result = await this.pyenvs.isCondaEnvironment(interpreterPath);
194+
if (result !== undefined) {
195+
return result;
196+
}
186197
const dir = path.dirname(interpreterPath);
187198
const { isWindows } = this.platform;
188199
const condaMetaDirectory = isWindows ? path.join(dir, 'conda-meta') : path.join(dir, '..', 'conda-meta');
@@ -193,6 +204,10 @@ export class CondaService implements ICondaService {
193204
* Return (env name, interpreter filename) for the interpreter.
194205
*/
195206
public async getCondaEnvironment(interpreterPath: string): Promise<{ name: string; path: string } | undefined> {
207+
const found = await this.pyenvs.getCondaEnvironment(interpreterPath);
208+
if (found !== undefined) {
209+
return found;
210+
}
196211
const isCondaEnv = await this.isCondaEnvironment(interpreterPath);
197212
if (!isCondaEnv) {
198213
return;

src/client/pythonEnvironments/discovery/locators/services/hashProviderFactory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class InterpeterHashProviderFactory implements IInterpreterHashProviderFa
2828
? options.pythonPath
2929
: this.configService.getSettings(options.resource).pythonPath;
3030

31-
return this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)
31+
return (await this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath))
3232
? this.windowsStoreHashProvider
3333
: this.hashProvider;
3434
}

src/client/pythonEnvironments/discovery/locators/services/windowsRegistryService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ export class WindowsRegistryService extends CacheableLocatorService {
186186
// Give preference to what we have retrieved from getInterpreterInformation.
187187
version: details.version || parsePythonVersion(version),
188188
companyDisplayName: interpreterInfo.companyDisplayName,
189-
envType: this.windowsStoreInterpreter.isWindowsStoreInterpreter(executablePath)
189+
envType: (await this.windowsStoreInterpreter.isWindowsStoreInterpreter(executablePath))
190190
? EnvironmentType.WindowsStore
191191
: EnvironmentType.Unknown,
192192
} as PythonEnvironment;

src/client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { traceDecorators } from '../../../../common/logger';
99
import { IFileSystem } from '../../../../common/platform/types';
1010
import { IPythonExecutionFactory } from '../../../../common/process/types';
1111
import { IPersistentStateFactory } from '../../../../common/types';
12+
import { IComponentAdapter } from '../../../../interpreter/contracts';
1213
import { IInterpreterHashProvider, IWindowsStoreInterpreter } from '../../../../interpreter/locators/types';
1314
import { IServiceContainer } from '../../../../ioc/types';
1415

@@ -31,6 +32,11 @@ export function isRestrictedWindowsStoreInterpreterPath(pythonPath: string): boo
3132
);
3233
}
3334

35+
// The parts of IComponentAdapter used here.
36+
interface IComponent {
37+
isWindowsStoreInterpreter(pythonPath: string): Promise<boolean | undefined>;
38+
}
39+
3440
/**
3541
* The default location of Windows apps are `%ProgramFiles%\WindowsApps`.
3642
* (https://www.samlogic.net/articles/windows-8-windowsapps-folder.htm)
@@ -60,6 +66,7 @@ export class WindowsStoreInterpreter implements IWindowsStoreInterpreter, IInter
6066
@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer,
6167
@inject(IPersistentStateFactory) private readonly persistentFactory: IPersistentStateFactory,
6268
@inject(IFileSystem) private readonly fs: IFileSystem,
69+
@inject(IComponentAdapter) private readonly pyenvs: IComponent
6370
) {}
6471

6572
/**
@@ -69,7 +76,11 @@ export class WindowsStoreInterpreter implements IWindowsStoreInterpreter, IInter
6976
* @returns {boolean}
7077
* @memberof WindowsStoreInterpreter
7178
*/
72-
public isWindowsStoreInterpreter(pythonPath: string): boolean {
79+
public async isWindowsStoreInterpreter(pythonPath: string): Promise<boolean> {
80+
const result = await this.pyenvs.isWindowsStoreInterpreter(pythonPath);
81+
if (result !== undefined) {
82+
return result;
83+
}
7384
const pythonPathToCompare = pythonPath.toUpperCase().replace(/\//g, '\\');
7485
return (
7586
pythonPathToCompare.includes('\\Microsoft\\WindowsApps\\'.toUpperCase())

src/client/pythonEnvironments/legacyIOC.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -191,15 +191,6 @@ class ComponentAdapter implements IComponentAdapter {
191191

192192
// IInterpreterService
193193

194-
// A result of `undefined` means "Fall back to the old code!"
195-
public get hasInterpreters(): Promise<boolean | undefined> {
196-
if (!this.enabled) {
197-
return Promise.resolve(undefined);
198-
}
199-
const iterator = this.api.iterEnvs();
200-
return iterator.next().then((res) => !res.done);
201-
}
202-
203194
// We use the same getInterpreters() here as for IInterpreterLocatorService.
204195

205196
// A result of `undefined` means "Fall back to the old code!"
@@ -274,6 +265,15 @@ class ComponentAdapter implements IComponentAdapter {
274265

275266
// IInterpreterLocatorService
276267

268+
// A result of `undefined` means "Fall back to the old code!"
269+
public get hasInterpreters(): Promise<boolean | undefined> {
270+
if (!this.enabled) {
271+
return Promise.resolve(undefined);
272+
}
273+
const iterator = this.api.iterEnvs();
274+
return iterator.next().then((res) => !res.done);
275+
}
276+
277277
// A result of `undefined` means "Fall back to the old code!"
278278
public async getInterpreters(
279279
resource?: vscode.Uri,

src/test/application/diagnostics/checks/macPythonInterpreter.unit.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ suite('Application Diagnostics - Checks Mac Python Interpreter', () => {
220220
.verifiable(typemoq.Times.once());
221221
helper
222222
.setup((i) => i.isMacDefaultPythonPath(typemoq.It.isAny()))
223-
.returns(() => false)
223+
.returns(() => Promise.resolve(false))
224224
.verifiable(typemoq.Times.once());
225225

226226
const diagnostics = await diagnosticService.diagnose(undefined);
@@ -251,7 +251,7 @@ suite('Application Diagnostics - Checks Mac Python Interpreter', () => {
251251
.verifiable(typemoq.Times.once());
252252
helper
253253
.setup((i) => i.isMacDefaultPythonPath(typemoq.It.isValue(pythonPath)))
254-
.returns(() => true)
254+
.returns(() => Promise.resolve(true))
255255
.verifiable(typemoq.Times.atLeastOnce());
256256

257257
const diagnostics = await diagnosticService.diagnose(undefined);
@@ -291,11 +291,11 @@ suite('Application Diagnostics - Checks Mac Python Interpreter', () => {
291291
.verifiable(typemoq.Times.once());
292292
helper
293293
.setup((i) => i.isMacDefaultPythonPath(typemoq.It.isValue(pythonPath)))
294-
.returns(() => true)
294+
.returns(() => Promise.resolve(true))
295295
.verifiable(typemoq.Times.atLeastOnce());
296296
helper
297297
.setup((i) => i.isMacDefaultPythonPath(typemoq.It.isValue(nonMacStandardInterpreter)))
298-
.returns(() => false)
298+
.returns(() => Promise.resolve(false))
299299
.verifiable(typemoq.Times.atLeastOnce());
300300
interpreterService
301301
.setup((i) => i.getActiveInterpreter(typemoq.It.isAny()))

0 commit comments

Comments
 (0)