Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ npm-debug.log
!yarn.lock
coverage/
.vscode-test/**
.venv*/
**/.venv*/
precommit.hook
pythonFiles/experimental/ptvsd/**
debug_coverage*/**
Expand Down
1 change: 1 addition & 0 deletions news/1 Enhancements/656.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Clear cached list of interpreters when an interpeter is created in the workspace folder (this allows for virtual environments created in one's workspace folder to be detectable immediately).
2 changes: 1 addition & 1 deletion src/client/common/application/debugService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class DebugService implements IDebugService {
public registerDebugConfigurationProvider(debugType: string, provider: any): Disposable {
return debug.registerDebugConfigurationProvider(debugType, provider);
}
public startDebugging(folder: WorkspaceFolder, nameOrConfiguration: string | DebugConfiguration): Thenable<boolean> {
public startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration): Thenable<boolean> {
return debug.startDebugging(folder, nameOrConfiguration);
}
public addBreakpoints(breakpoints: Breakpoint[]): void {
Expand Down
10 changes: 10 additions & 0 deletions src/client/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ export const STANDARD_OUTPUT_CHANNEL = 'STANDARD_OUTPUT_CHANNEL';
export function isTestExecution(): boolean {
return process.env.VSC_PYTHON_CI_TEST === '1';
}

/**
* Whether we're running unit tests (*.unit.test.ts).
* These tests have a speacial meaning, they run fast.
* @export
* @returns {boolean}
*/
export function isUnitTestExecution(): boolean {
return process.env.VSC_PYTHON_UNIT_TEST === '1';
}
export function isLanguageServerTest(): boolean {
return process.env.VSC_PYTHON_LANGUAGE_SERVER === '1';
}
Expand Down
4 changes: 4 additions & 0 deletions src/client/common/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export class Logger implements ILogger {
public static warn(title: string = '', message: any = '') {
new Logger().logWarning(`${title}, ${message}`);
}
// tslint:disable-next-line:no-any
public static verbose(title: string = '') {
new Logger().logInformation(title);
}
@skipIfTest(false)
public logError(message: string, ex?: Error) {
if (ex) {
Expand Down
9 changes: 4 additions & 5 deletions src/client/common/utils/decorators.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import * as _ from 'lodash';
import { isTestExecution } from '../constants';

type AsyncVoidAction = (...params: {}[]) => Promise<void>;
type VoidAction = (...params: {}[]) => void;
import { isTestExecution, isUnitTestExecution } from '../constants';

/**
* Debounces a function execution. Function must return either a void or a promise that resolves to a void.
Expand All @@ -12,8 +9,10 @@ type VoidAction = (...params: {}[]) => void;
*/
export function debounce(wait?: number) {
// tslint:disable-next-line:no-any no-function-expression
return function (_target: any, _propertyName: string, descriptor: TypedPropertyDescriptor<VoidAction> | TypedPropertyDescriptor<AsyncVoidAction>) {
return function (_target: any, _propertyName: string, descriptor: TypedPropertyDescriptor<any>) {
const originalMethod = descriptor.value!;
// If running tests, lets not debounce (so tests run fast).
wait = wait && isUnitTestExecution() ? undefined : wait;
// tslint:disable-next-line:no-invalid-this no-any
(descriptor as any).value = _.debounce(function () { return originalMethod.apply(this, arguments); }, wait);
};
Expand Down
5 changes: 1 addition & 4 deletions src/client/debugger/extension/hooks/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,5 @@

export enum PTVSDEvents {
// Event sent by PTVSD when a child process is launched and ready to be attached to for multi-proc debugging.
ChildProcessLaunched = 'ptvsd_subprocess',

// Event sent by PTVSD when a process is started (identital to the `process` event in debugger protocol).
ProcessLaunched = 'ptvsd_process'
ChildProcessLaunched = 'ptvsd_subprocess'
}
484 changes: 245 additions & 239 deletions src/client/extension.ts

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions src/client/interpreter/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,13 @@ export const IInterpreterLocatorHelper = Symbol('IInterpreterLocatorHelper');
export interface IInterpreterLocatorHelper {
mergeInterpreters(interpreters: PythonInterpreter[]): PythonInterpreter[];
}

export const IInterpreterWatcher = Symbol('IInterpreterWatcher');
export interface IInterpreterWatcher {
onDidCreate: Event<void>;
}

export const IInterpreterWatcherBuilder = Symbol('IInterpreterWatcherBuilder');
export interface IInterpreterWatcherBuilder {
getWorkspaceVirtualEnvInterpreterWatcher(resource: Uri | undefined): Promise<IInterpreterWatcher>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@

import { injectable, unmanaged } from 'inversify';
import * as md5 from 'md5';
import { Uri } from 'vscode';
import { Disposable, Uri } from 'vscode';
import { IWorkspaceService } from '../../../common/application/types';
import { IPersistentStateFactory } from '../../../common/types';
import { Logger } from '../../../common/logger';
import { IDisposableRegistry, IPersistentStateFactory } from '../../../common/types';
import { createDeferred, Deferred } from '../../../common/utils/async';
import { IServiceContainer } from '../../../ioc/types';
import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts';
import { IInterpreterLocatorService, IInterpreterWatcher, PythonInterpreter } from '../../contracts';

@injectable()
export abstract class CacheableLocatorService implements IInterpreterLocatorService {
private readonly promisesPerResource = new Map<string, Deferred<PythonInterpreter[]>>();
private readonly handlersAddedToResource = new Set<string>();
private readonly cacheKeyPrefix: string;
constructor(@unmanaged() name: string,
@unmanaged() protected readonly serviceContainer: IServiceContainer,
Expand All @@ -25,9 +27,14 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ
public async getInterpreters(resource?: Uri): Promise<PythonInterpreter[]> {
const cacheKey = this.getCacheKey(resource);
let deferred = this.promisesPerResource.get(cacheKey);

if (!deferred) {
deferred = createDeferred<PythonInterpreter[]>();
this.promisesPerResource.set(cacheKey, deferred);

this.addHandlersForInterpreterWatchers(cacheKey, resource)
.ignoreErrors();

this.getInterpretersImplementation(resource)
.then(async items => {
await this.cacheInterpreters(items, resource);
Expand All @@ -42,9 +49,26 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ
const cachedInterpreters = this.getCachedInterpreters(resource);
return Array.isArray(cachedInterpreters) ? cachedInterpreters : deferred.promise;
}
protected async addHandlersForInterpreterWatchers(cacheKey: string, resource: Uri | undefined): Promise<void> {
if (this.handlersAddedToResource.has(cacheKey)) {
return;
}
this.handlersAddedToResource.add(cacheKey);
const watchers = await this.getInterpreterWatchers(resource);
const disposableRegisry = this.serviceContainer.get<Disposable[]>(IDisposableRegistry);
watchers.forEach(watcher => {
watcher.onDidCreate(() => {
Logger.verbose(`Interpreter Watcher change handler for ${this.cacheKeyPrefix}`);
this.promisesPerResource.delete(cacheKey);
}, this, disposableRegisry);
});
}
protected async getInterpreterWatchers(_resource: Uri | undefined): Promise<IInterpreterWatcher[]> {
return [];
}

protected abstract getInterpretersImplementation(resource?: Uri): Promise<PythonInterpreter[]>;
private createPersistenceStore(resource?: Uri) {
protected createPersistenceStore(resource?: Uri) {
const cacheKey = this.getCacheKey(resource);
const persistentFactory = this.serviceContainer.get<IPersistentStateFactory>(IPersistentStateFactory);
if (this.cachePerWorkspace) {
Expand All @@ -54,7 +78,7 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ
}

}
private getCachedInterpreters(resource?: Uri) {
protected getCachedInterpreters(resource?: Uri): PythonInterpreter[] | undefined {
const persistence = this.createPersistenceStore(resource);
if (!Array.isArray(persistence.value)) {
return;
Expand All @@ -66,11 +90,11 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ
};
});
}
private async cacheInterpreters(interpreters: PythonInterpreter[], resource?: Uri) {
protected async cacheInterpreters(interpreters: PythonInterpreter[], resource?: Uri) {
const persistence = this.createPersistenceStore(resource);
await persistence.updateValue(interpreters);
}
private getCacheKey(resource?: Uri) {
protected getCacheKey(resource?: Uri) {
if (!resource || !this.cachePerWorkspace) {
return this.cacheKeyPrefix;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import { inject, injectable } from 'inversify';
import { Uri } from 'vscode';
import { IWorkspaceService } from '../../../common/application/types';
import { traceVerbose } from '../../../common/logger';
import { createDeferred } from '../../../common/utils/async';
import { IServiceContainer } from '../../../ioc/types';
import { IInterpreterWatcher, IInterpreterWatcherBuilder, WORKSPACE_VIRTUAL_ENV_SERVICE } from '../../contracts';
import { WorkspaceVirtualEnvWatcherService } from './workspaceVirtualEnvWatcherService';

@injectable()
export class InterpreterWatcherBuilder implements IInterpreterWatcherBuilder {
private readonly watchersByResource = new Map<string, Promise<IInterpreterWatcher>>();
/**
* Creates an instance of InterpreterWatcherBuilder.
* Inject the DI container, as we need to get a new instance of IInterpreterWatcher to build it.
* @param {IWorkspaceService} workspaceService
* @param {IServiceContainer} serviceContainer
* @memberof InterpreterWatcherBuilder
*/
constructor(@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService,
@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer
) { }

@traceVerbose('Build the workspace interpreter watcher')
public async getWorkspaceVirtualEnvInterpreterWatcher(resource: Uri | undefined): Promise<IInterpreterWatcher> {
const key = this.getResourceKey(resource);
if (!this.watchersByResource.has(key)) {
const deferred = createDeferred<IInterpreterWatcher>();
this.watchersByResource.set(key, deferred.promise);
const watcher = this.serviceContainer.get<WorkspaceVirtualEnvWatcherService>(IInterpreterWatcher, WORKSPACE_VIRTUAL_ENV_SERVICE);
await watcher.register(resource);
deferred.resolve(watcher);
}
return this.watchersByResource.get(key)!;
}
protected getResourceKey(resource: Uri | undefined): string {
const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined;
return workspaceFolder ? workspaceFolder.uri.fsPath : '';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,28 @@

'use strict';

// tslint:disable:no-require-imports

import { inject, injectable, named } from 'inversify';
import * as path from 'path';
// tslint:disable-next-line:no-require-imports
import untildify = require('untildify');
import { Uri } from 'vscode';
import { IWorkspaceService } from '../../../common/application/types';
import { IConfigurationService } from '../../../common/types';
import { IServiceContainer } from '../../../ioc/types';
import { IVirtualEnvironmentsSearchPathProvider } from '../../contracts';
import { IInterpreterWatcher, IInterpreterWatcherBuilder, IVirtualEnvironmentsSearchPathProvider } from '../../contracts';
import { BaseVirtualEnvService } from './baseVirtualEnvService';

@injectable()
export class WorkspaceVirtualEnvService extends BaseVirtualEnvService {
public constructor(
@inject(IVirtualEnvironmentsSearchPathProvider) @named('workspace') globalVirtualEnvPathProvider: IVirtualEnvironmentsSearchPathProvider,
@inject(IServiceContainer) serviceContainer: IServiceContainer) {
super(globalVirtualEnvPathProvider, serviceContainer, 'WorkspaceVirtualEnvService', true);
@inject(IVirtualEnvironmentsSearchPathProvider) @named('workspace') workspaceVirtualEnvPathProvider: IVirtualEnvironmentsSearchPathProvider,
@inject(IServiceContainer) serviceContainer: IServiceContainer,
@inject(IInterpreterWatcherBuilder) private readonly builder: IInterpreterWatcherBuilder) {
super(workspaceVirtualEnvPathProvider, serviceContainer, 'WorkspaceVirtualEnvService', true);
}
protected async getInterpreterWatchers(resource: Uri | undefined): Promise<IInterpreterWatcher[]> {
return [await this.builder.getWorkspaceVirtualEnvInterpreterWatcher(resource)];
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import { inject, injectable } from 'inversify';
import * as path from 'path';
import { Disposable, Event, EventEmitter, FileSystemWatcher, RelativePattern, Uri } from 'vscode';
import { IWorkspaceService } from '../../../common/application/types';
import { Logger, traceVerbose } from '../../../common/logger';
import { IPlatformService } from '../../../common/platform/types';
import { IDisposableRegistry } from '../../../common/types';
import { debounce } from '../../../common/utils/decorators';
import { IInterpreterWatcher } from '../../contracts';

@injectable()
export class WorkspaceVirtualEnvWatcherService implements IInterpreterWatcher, Disposable {
private readonly didCreate;
private timer?: NodeJS.Timer;
private fsWatchers: FileSystemWatcher[] = [];
constructor(@inject(IDisposableRegistry) private readonly disposableRegistry: Disposable[],
@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService,
@inject(IPlatformService) private readonly platformService: IPlatformService) {
this.didCreate = new EventEmitter<void>();
disposableRegistry.push(this);
}
public get onDidCreate(): Event<void> {
return this.didCreate.event;
}
public dispose() {
this.clearTimer();
}
@traceVerbose('Register Intepreter Watcher')
public async register(resource: Uri | undefined): Promise<void> {
if (this.fsWatchers.length > 0) {
return;
}

const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined;
const executable = this.platformService.isWindows ? 'python.exe' : 'python';
const patterns = [path.join('*', executable), path.join('*', '*', executable)];

for (const pattern of patterns) {
const globPatern = workspaceFolder ? new RelativePattern(workspaceFolder.uri.fsPath, pattern) : pattern;
Logger.verbose(`Create file systemwatcher with pattern ${pattern}`);

const fsWatcher = this.workspaceService.createFileSystemWatcher(globPatern);
fsWatcher.onDidCreate(e => this.createHandler(e), this, this.disposableRegistry);

this.disposableRegistry.push(fsWatcher);
this.fsWatchers.push(fsWatcher);
}
}
@debounce(2000)
@traceVerbose('Intepreter Watcher change handler')
protected createHandler(e: Uri) {
this.didCreate.fire();
// On Windows, creation of environments are slow, hence lets notify again after 10 seconds.
this.clearTimer();

this.timer = setTimeout(() => {
this.timer = undefined;
this.didCreate.fire();
}, 10000);
}
private clearTimer() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = undefined;
}
}
}
7 changes: 7 additions & 0 deletions src/client/interpreter/serviceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
IInterpreterLocatorService,
IInterpreterService,
IInterpreterVersionService,
IInterpreterWatcher,
IInterpreterWatcherBuilder,
IKnownSearchPathsForInterpreters,
INTERPRETER_LOCATOR_SERVICE,
IPipEnvService,
Expand All @@ -42,10 +44,12 @@ import { CondaEnvService } from './locators/services/condaEnvService';
import { CondaService } from './locators/services/condaService';
import { CurrentPathService } from './locators/services/currentPathService';
import { GlobalVirtualEnvironmentsSearchPathProvider, GlobalVirtualEnvService } from './locators/services/globalVirtualEnvService';
import { InterpreterWatcherBuilder } from './locators/services/interpreterWatcherBuilder';
import { KnownPathsService, KnownSearchPathsForInterpreters } from './locators/services/KnownPathsService';
import { PipEnvService } from './locators/services/pipEnvService';
import { WindowsRegistryService } from './locators/services/windowsRegistryService';
import { WorkspaceVirtualEnvironmentsSearchPathProvider, WorkspaceVirtualEnvService } from './locators/services/workspaceVirtualEnvService';
import { WorkspaceVirtualEnvWatcherService } from './locators/services/workspaceVirtualEnvWatcherService';
import { VirtualEnvironmentManager } from './virtualEnvs/index';
import { IVirtualEnvironmentManager } from './virtualEnvs/types';

Expand All @@ -57,6 +61,9 @@ export function registerTypes(serviceManager: IServiceManager) {
serviceManager.addSingleton<ICondaService>(ICondaService, CondaService);
serviceManager.addSingleton<IVirtualEnvironmentManager>(IVirtualEnvironmentManager, VirtualEnvironmentManager);

serviceManager.add<IInterpreterWatcher>(IInterpreterWatcher, WorkspaceVirtualEnvWatcherService, WORKSPACE_VIRTUAL_ENV_SERVICE);
serviceManager.addSingleton<IInterpreterWatcherBuilder>(IInterpreterWatcherBuilder, InterpreterWatcherBuilder);

serviceManager.addSingleton<IInterpreterVersionService>(IInterpreterVersionService, InterpreterVersionService);
serviceManager.addSingleton<IInterpreterLocatorService>(IInterpreterLocatorService, PythonInterpreterLocatorService, INTERPRETER_LOCATOR_SERVICE);
serviceManager.addSingleton<IInterpreterLocatorService>(IInterpreterLocatorService, CondaEnvFileService, CONDA_ENV_FILE_SERVICE);
Expand Down
Loading