Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to logic for selection of best (default) version of Python interpreter #3813

Merged
merged 48 commits into from Jan 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
e82a3f2
Refactor version in interpreter information
DonJayamanne Dec 26, 2018
73344c9
Fix tests
DonJayamanne Dec 27, 2018
ece8afd
Fix test
DonJayamanne Dec 27, 2018
2bc224e
First batch of changes
DonJayamanne Dec 27, 2018
03f6535
temporarily skip failing tests
DonJayamanne Dec 27, 2018
410d5e1
Skip failing tests
DonJayamanne Dec 27, 2018
aca0ed9
Fix recursion
DonJayamanne Dec 27, 2018
1cef8e3
Fix tests
DonJayamanne Dec 27, 2018
e93d015
refactor
DonJayamanne Dec 27, 2018
57c9d52
Add tests
DonJayamanne Dec 28, 2018
d51aadb
Fixed tests
DonJayamanne Dec 28, 2018
228821d
Added tests
DonJayamanne Dec 28, 2018
cc66f4c
News entry
DonJayamanne Dec 28, 2018
11e9741
Add telemetry
DonJayamanne Dec 28, 2018
669a44b
Merge branch 'master' into issue3369AutoX
DonJayamanne Dec 28, 2018
2a394e6
Changed version to a simple struct
DonJayamanne Dec 28, 2018
e18d234
Rename
DonJayamanne Dec 28, 2018
5bf691f
Fix tests
DonJayamanne Dec 28, 2018
5429b76
Fixes
DonJayamanne Dec 28, 2018
1d58cd5
Fixes
DonJayamanne Dec 28, 2018
7b7b53a
Fix tests
DonJayamanne Dec 29, 2018
e3b4522
Disable linter tests
DonJayamanne Dec 29, 2018
249105e
Fix broken tests
DonJayamanne Dec 29, 2018
3b56b15
Comments
DonJayamanne Dec 29, 2018
ef23e0a
Tests for config settings
DonJayamanne Dec 29, 2018
b32d5d8
Fix issue
DonJayamanne Dec 29, 2018
1b462aa
Fixed tests
DonJayamanne Dec 29, 2018
c33c8d0
Remove unnecessary messages
DonJayamanne Dec 29, 2018
0d9b5c2
Fixed linter command tests
DonJayamanne Dec 30, 2018
51b3675
Create linter manage unit tests
DonJayamanne Dec 30, 2018
8a91233
Refactor
DonJayamanne Dec 31, 2018
622bdae
Refactor with comments
DonJayamanne Dec 31, 2018
aa37815
Bust old cache
DonJayamanne Dec 31, 2018
5165ebd
Bust old cache
DonJayamanne Dec 31, 2018
9c1e8af
Merge branch 'master' into issue3369AutoX
DonJayamanne Dec 31, 2018
0380759
Remove old test
DonJayamanne Dec 31, 2018
e7fbbf3
Fix tests
DonJayamanne Dec 31, 2018
1b88ea0
Improvements
DonJayamanne Dec 31, 2018
a31f7ff
Improvements and fixes
DonJayamanne Dec 31, 2018
b4e59ba
Fix tests
DonJayamanne Dec 31, 2018
9f7697b
Refactor and add telemetry
DonJayamanne Dec 31, 2018
f06e857
Better tests
DonJayamanne Jan 1, 2019
2afc958
Fix code review comments
DonJayamanne Jan 3, 2019
74c0278
Fix tests
DonJayamanne Jan 3, 2019
b0084e3
Resolve code review comments
DonJayamanne Jan 3, 2019
d0b70bc
merge master
DonJayamanne Jan 3, 2019
83e2492
merge master
DonJayamanne Jan 3, 2019
8c439c2
Fixed linter issues
DonJayamanne Jan 3, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 0 additions & 4 deletions build/ci/mocha-vsts-reporter.js
Expand Up @@ -15,11 +15,9 @@ function MochaVstsReporter(runner, options) {

runner.on('suite', function(suite){
if (suite.root === true){
console.log('Begin test run.............');
indentLevel++;
indenter = INDENT_BASE.repeat(indentLevel);
} else {
console.log('%sStart "%s"', indenter, suite.title);
DonJayamanne marked this conversation as resolved.
Show resolved Hide resolved
indentLevel++;
indenter = INDENT_BASE.repeat(indentLevel);
}
Expand All @@ -29,9 +27,7 @@ function MochaVstsReporter(runner, options) {
if (suite.root === true) {
indentLevel=0;
indenter = '';
console.log('.............End test run.');
} else {
console.log('%sEnd "%s"', indenter, suite.title);
indentLevel--;
indenter = INDENT_BASE.repeat(indentLevel);
// ##vso[task.setprogress]current operation
Expand Down
1 change: 1 addition & 0 deletions news/1 Enhancements/3369.md
@@ -0,0 +1 @@
Improvements to automatic selection of the python interpreter.
71 changes: 47 additions & 24 deletions src/client/common/configSettings.ts
Expand Up @@ -3,12 +3,13 @@
import * as child_process from 'child_process';
import { EventEmitter } from 'events';
import * as path from 'path';
import {
ConfigurationChangeEvent, ConfigurationTarget, DiagnosticSeverity, Disposable, Uri,
workspace, WorkspaceConfiguration
} from 'vscode';
import { ConfigurationChangeEvent, ConfigurationTarget, DiagnosticSeverity, Disposable, Uri, WorkspaceConfiguration } from 'vscode';
import '../common/extensions';
import { IInterpreterAutoSeletionProxyService } from '../interpreter/autoSelection/types';
import { sendTelemetryEvent } from '../telemetry';
import { COMPLETION_ADD_BRACKETS, FORMAT_ON_TYPE } from '../telemetry/constants';
import { IWorkspaceService } from './application/types';
import { WorkspaceService } from './application/workspace';
import { isTestExecution } from './constants';
import { IS_WINDOWS } from './platform/constants';
import {
Expand Down Expand Up @@ -57,21 +58,28 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
private disposables: Disposable[] = [];
// tslint:disable-next-line:variable-name
private _pythonPath = '';
private readonly workspace: IWorkspaceService;

constructor(workspaceFolder?: Uri) {
constructor(workspaceFolder: Uri | undefined, private readonly InterpreterAutoSelectionService: IInterpreterAutoSeletionProxyService,
workspace?: IWorkspaceService) {
super();
this.workspace = workspace || new WorkspaceService();
this.workspaceRoot = workspaceFolder ? workspaceFolder : Uri.file(__dirname);
this.initialize();
}
// tslint:disable-next-line:function-name
public static getInstance(resource?: Uri): PythonSettings {
const workspaceFolderUri = PythonSettings.getSettingsUriAndTarget(resource).uri;
public static getInstance(resource: Uri | undefined, interpreterAutoSelectionService: IInterpreterAutoSeletionProxyService,
workspace?: IWorkspaceService): PythonSettings {
workspace = workspace || new WorkspaceService();
const workspaceFolderUri = PythonSettings.getSettingsUriAndTarget(resource, workspace).uri;
const workspaceFolderKey = workspaceFolderUri ? workspaceFolderUri.fsPath : '';

if (!PythonSettings.pythonSettings.has(workspaceFolderKey)) {
const settings = new PythonSettings(workspaceFolderUri);
const settings = new PythonSettings(workspaceFolderUri, interpreterAutoSelectionService, workspace);
PythonSettings.pythonSettings.set(workspaceFolderKey, settings);
const config = workspace.getConfiguration('editor', resource ? resource : null);
// Pass null to avoid VSC from complaining about not passing in a value.
// tslint:disable-next-line:no-any
const config = workspace.getConfiguration('editor', resource ? resource : null as any);
const formatOnType = config ? config.get('formatOnType', false) : false;
sendTelemetryEvent(COMPLETION_ADD_BRACKETS, undefined, { enabled: settings.autoComplete ? settings.autoComplete.addBrackets : false });
sendTelemetryEvent(FORMAT_ON_TYPE, undefined, { enabled: formatOnType });
Expand All @@ -81,7 +89,8 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
}

// tslint:disable-next-line:type-literal-delimiter
public static getSettingsUriAndTarget(resource?: Uri): { uri: Uri | undefined, target: ConfigurationTarget } {
public static getSettingsUriAndTarget(resource: Uri | undefined, workspace?: IWorkspaceService): { uri: Uri | undefined, target: ConfigurationTarget } {
workspace = workspace || new WorkspaceService();
const workspaceFolder = resource ? workspace.getWorkspaceFolder(resource) : undefined;
let workspaceFolderUri: Uri | undefined = workspaceFolder ? workspaceFolder.uri : undefined;

Expand All @@ -99,21 +108,29 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
throw new Error('Dispose can only be called from unit tests');
}
// tslint:disable-next-line:no-void-expression
PythonSettings.pythonSettings.forEach(item => item.dispose());
PythonSettings.pythonSettings.forEach(item => item && item.dispose());
PythonSettings.pythonSettings.clear();
}
public dispose() {
// tslint:disable-next-line:no-unsafe-any
this.disposables.forEach(disposable => disposable.dispose());
this.disposables.forEach(disposable => disposable && disposable.dispose());
this.disposables = [];
}
// tslint:disable-next-line:cyclomatic-complexity max-func-body-length
public update(pythonSettings: WorkspaceConfiguration) {
protected update(pythonSettings: WorkspaceConfiguration) {
const workspaceRoot = this.workspaceRoot.fsPath;
const systemVariables: SystemVariables = new SystemVariables(this.workspaceRoot ? this.workspaceRoot.fsPath : undefined);

// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
this.pythonPath = systemVariables.resolveAny(pythonSettings.get<string>('pythonPath'))!;
// If user has defined a custom value, use it else try to get the best interpreter ourselves.
if (this.pythonPath.length === 0 || this.pythonPath === 'python') {
const autoSelectedPythonInterpreter = this.InterpreterAutoSelectionService.getAutoSelectedInterpreter(this.workspaceRoot);
if (autoSelectedPythonInterpreter) {
this.InterpreterAutoSelectionService.setWorkspaceInterpreter(this.workspaceRoot, autoSelectedPythonInterpreter).ignoreErrors();
}
this.pythonPath = autoSelectedPythonInterpreter ? autoSelectedPythonInterpreter.path : this.pythonPath;
}
this.pythonPath = getAbsolutePath(this.pythonPath, workspaceRoot);
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
this.venvPath = systemVariables.resolveAny(pythonSettings.get<string>('venvPath'))!;
Expand Down Expand Up @@ -342,25 +359,31 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
// Add support for specifying just the directory where the python executable will be located.
// E.g. virtual directory name.
try {
this._pythonPath = getPythonExecutable(value);
this._pythonPath = this.getPythonExecutable(value);
} catch (ex) {
this._pythonPath = value;
}
}
protected getPythonExecutable(pythonPath: string) {
return getPythonExecutable(pythonPath);
}
protected initialize(): void {
this.disposables.push(workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => {
if (event.affectsConfiguration('python'))
{
const currentConfig = workspace.getConfiguration('python', this.workspaceRoot);
this.update(currentConfig);

// If workspace config changes, then we could have a cascading effect of on change events.
// Let's defer the change notification.
setTimeout(() => this.emit('change'), 1);
const onDidChange = () => {
const currentConfig = this.workspace.getConfiguration('python', this.workspaceRoot);
this.update(currentConfig);

// If workspace config changes, then we could have a cascading effect of on change events.
// Let's defer the change notification.
setTimeout(() => this.emit('change'), 1);
};
this.disposables.push(this.InterpreterAutoSelectionService.onDidChangeAutoSelectedInterpreter(onDidChange.bind(this)));
this.disposables.push(this.workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => {
if (event.affectsConfiguration('python')) {
onDidChange();
}
}));

const initialConfig = workspace.getConfiguration('python', this.workspaceRoot);
const initialConfig = this.workspace.getConfiguration('python', this.workspaceRoot);
if (initialConfig) {
this.update(initialConfig);
}
Expand Down
17 changes: 14 additions & 3 deletions src/client/common/configuration/service.ts
@@ -1,23 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { injectable } from 'inversify';
import { inject, injectable } from 'inversify';
import { ConfigurationTarget, Uri, workspace, WorkspaceConfiguration } from 'vscode';
import { IInterpreterAutoSeletionProxyService } from '../../interpreter/autoSelection/types';
import { IServiceContainer } from '../../ioc/types';
import { IWorkspaceService } from '../application/types';
import { PythonSettings } from '../configSettings';
import { IConfigurationService, IPythonSettings } from '../types';

@injectable()
export class ConfigurationService implements IConfigurationService {
private readonly workspaceService: IWorkspaceService;
constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) {
this.workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
}
public getSettings(resource?: Uri): IPythonSettings {
return PythonSettings.getInstance(resource);
const InterpreterAutoSelectionService = this.serviceContainer.get<IInterpreterAutoSeletionProxyService>(IInterpreterAutoSeletionProxyService);
return PythonSettings.getInstance(resource, InterpreterAutoSelectionService, this.workspaceService);
}

public async updateSectionSetting(section: string, setting: string, value?: {}, resource?: Uri, configTarget?: ConfigurationTarget): Promise<void> {
const defaultSetting = {
uri: resource,
target: configTarget || ConfigurationTarget.WorkspaceFolder
};
const settingsInfo = section === 'python' && configTarget !== ConfigurationTarget.Global ? PythonSettings.getSettingsUriAndTarget(resource) : defaultSetting;
let settingsInfo = defaultSetting;
if (section === 'python' && configTarget !== ConfigurationTarget.Global) {
settingsInfo = PythonSettings.getSettingsUriAndTarget(resource, this.workspaceService);
}

const configSection = workspace.getConfiguration(section, settingsInfo.uri);
const currentValue = configSection.inspect(setting);
Expand Down
10 changes: 5 additions & 5 deletions src/client/common/installer/productInstaller.ts
Expand Up @@ -197,16 +197,16 @@ export class LinterInstaller extends BaseInstaller {
}
const response = await this.appShell.showErrorMessage(message, ...options);
if (response === install) {
sendTelemetryEvent(LINTER_NOT_INSTALLED_PROMPT, undefined, { tool: productName as LinterId, action: 'install'});
sendTelemetryEvent(LINTER_NOT_INSTALLED_PROMPT, undefined, { tool: productName as LinterId, action: 'install' });
return this.install(product, resource);
} else if (response === disableInstallPrompt) {
await this.setStoredResponse(disableLinterInstallPromptKey, true);
sendTelemetryEvent(LINTER_NOT_INSTALLED_PROMPT, undefined, { tool: productName as LinterId, action: 'disablePrompt'});
sendTelemetryEvent(LINTER_NOT_INSTALLED_PROMPT, undefined, { tool: productName as LinterId, action: 'disablePrompt' });
return InstallerResponse.Ignore;
}

if (response === selectLinter){
sendTelemetryEvent(LINTER_NOT_INSTALLED_PROMPT, undefined, { action: 'select'});
if (response === selectLinter) {
sendTelemetryEvent(LINTER_NOT_INSTALLED_PROMPT, undefined, { action: 'select' });
const commandManager = this.serviceContainer.get<ICommandManager>(ICommandManager);
await commandManager.executeCommand(Commands.Set_Linter);
}
Expand All @@ -224,7 +224,7 @@ export class LinterInstaller extends BaseInstaller {
protected getStoredResponse(key: string): boolean {
const factory = this.serviceContainer.get<IPersistentStateFactory>(IPersistentStateFactory);
const state = factory.createGlobalPersistentState<boolean | undefined>(key, undefined);
return state.value;
return state.value === true;
DonJayamanne marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/client/common/persistentState.ts
Expand Up @@ -7,7 +7,7 @@ import { inject, injectable, named } from 'inversify';
import { Memento } from 'vscode';
import { GLOBAL_MEMENTO, IMemento, IPersistentState, IPersistentStateFactory, WORKSPACE_MEMENTO } from './types';

class PersistentState<T> implements IPersistentState<T>{
export class PersistentState<T> implements IPersistentState<T>{
constructor(private storage: Memento, private key: string, private defaultValue?: T, private expiryDurationMs?: number) { }

public get value(): T {
Expand Down
13 changes: 10 additions & 3 deletions src/client/common/platform/registry.ts
@@ -1,5 +1,5 @@
import { injectable } from 'inversify';
import * as Registry from 'winreg';
import { Options } from 'winreg';
DonJayamanne marked this conversation as resolved.
Show resolved Hide resolved
import { Architecture } from '../utils/platform';
import { IRegistry, RegistryHive } from './types';

Expand Down Expand Up @@ -29,7 +29,9 @@ export function getArchitectureDisplayName(arch?: Architecture) {
}
}

async function getRegistryValue(options: Registry.Options, name: string = '') {
async function getRegistryValue(options: Options, name: string = '') {
// tslint:disable-next-line:no-require-imports
const Registry = require('winreg') as typeof import('winreg');
return new Promise<string | undefined | null>((resolve, reject) => {
new Registry(options).get(name, (error, result) => {
if (error || !result || typeof result.value !== 'string') {
Expand All @@ -39,7 +41,10 @@ async function getRegistryValue(options: Registry.Options, name: string = '') {
});
});
}
async function getRegistryKeys(options: Registry.Options): Promise<string[]> {

async function getRegistryKeys(options: Options): Promise<string[]> {
// tslint:disable-next-line:no-require-imports
const Registry = require('winreg') as typeof import('winreg');
// https://github.com/python/peps/blob/master/pep-0514.txt#L85
return new Promise<string[]>((resolve, reject) => {
new Registry(options).keys((error, result) => {
Expand All @@ -61,6 +66,8 @@ function translateArchitecture(arch?: Architecture): RegistryArchitectures | und
}
}
function translateHive(hive: RegistryHive): string | undefined {
// tslint:disable-next-line:no-require-imports
const Registry = require('winreg') as typeof import('winreg');
switch (hive) {
case RegistryHive.HKCU:
return Registry.HKCU;
Expand Down
4 changes: 1 addition & 3 deletions src/client/common/platform/serviceRegistry.ts
Expand Up @@ -11,7 +11,5 @@ import { IFileSystem, IPlatformService, IRegistry } from './types';
export function registerTypes(serviceManager: IServiceManager) {
serviceManager.addSingleton<IPlatformService>(IPlatformService, PlatformService);
serviceManager.addSingleton<IFileSystem>(IFileSystem, FileSystem);
if (serviceManager.get<IPlatformService>(IPlatformService).isWindows) {
serviceManager.addSingleton<IRegistry>(IRegistry, RegistryImplementation);
}
serviceManager.addSingleton<IRegistry>(IRegistry, RegistryImplementation);
}
4 changes: 2 additions & 2 deletions src/client/common/process/pythonProcess.ts
Expand Up @@ -10,7 +10,7 @@ import { ModuleNotInstalledError } from '../errors/moduleNotInstalledError';
import { traceError } from '../logger';
import { IFileSystem } from '../platform/types';
import { Architecture } from '../utils/platform';
import { convertPythonVersionToSemver } from '../utils/version';
import { parsePythonVersion } from '../utils/version';
import { ExecutionResult, InterpreterInfomation, IProcessService, IPythonExecutionService, ObservableExecutionResult, PythonVersionInfo, SpawnOptions } from './types';

@injectable()
Expand Down Expand Up @@ -42,7 +42,7 @@ export class PythonExecutionService implements IPythonExecutionService {
return {
architecture: json.is64Bit ? Architecture.x64 : Architecture.x86,
path: this.pythonPath,
version: convertPythonVersionToSemver(versionValue),
version: parsePythonVersion(versionValue),
sysVersion: json.sysVersion,
sysPrefix: json.sysPrefix
};
Expand Down
6 changes: 2 additions & 4 deletions src/client/common/process/types.ts
Expand Up @@ -3,9 +3,7 @@
import { ChildProcess, ExecOptions, SpawnOptions as ChildProcessSpawnOptions } from 'child_process';
import { Observable } from 'rxjs/Observable';
import { CancellationToken, Uri } from 'vscode';

import { SemVer } from 'semver';
import { ExecutionInfo } from '../types';
import { ExecutionInfo, Version } from '../types';
import { Architecture } from '../utils/platform';
import { EnvironmentVariables } from '../variables/types';

Expand Down Expand Up @@ -64,7 +62,7 @@ export type ReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final' | 'unknown';
export type PythonVersionInfo = [number, number, number, ReleaseLevel];
export type InterpreterInfomation = {
path: string;
version?: SemVer;
version?: Version;
sysVersion: string;
architecture: Architecture;
sysPrefix: string;
Expand Down
11 changes: 10 additions & 1 deletion src/client/common/types.ts
Expand Up @@ -15,10 +15,19 @@ export const IMemento = Symbol('IGlobalMemento');
export const GLOBAL_MEMENTO = Symbol('IGlobalMemento');
export const WORKSPACE_MEMENTO = Symbol('IWorkspaceMemento');

export type Resource = Uri | undefined;
export interface IPersistentState<T> {
readonly value: T;
updateValue(value: T): Promise<void>;
}
export type Version = {
raw: string;
major: number;
minor: number;
patch: number;
build: string[];
prerelease: string[];
};

export const IPersistentStateFactory = Symbol('IPersistentStateFactory');

Expand Down Expand Up @@ -365,7 +374,7 @@ export interface IEditorUtils {
}

export interface IDisposable {
dispose(): Promise<void> | undefined;
dispose(): Promise<void> | undefined | void;
}

export const IAsyncDisposableRegistry = Symbol('IAsyncDisposableRegistry');
Expand Down
19 changes: 15 additions & 4 deletions src/client/common/utils/async.ts
Expand Up @@ -39,12 +39,14 @@ class DeferredImpl<T> implements Deferred<T> {
});
}
public resolve(value?: T | PromiseLike<T>) {
this._resolve.apply(this.scope ? this.scope : this, arguments);
// tslint:disable-next-line:no-any
this._resolve.apply(this.scope ? this.scope : this, arguments as any);
this._resolved = true;
}
// tslint:disable-next-line:no-any
public reject(reason?: any) {
this._reject.apply(this.scope ? this.scope : this, arguments);
// tslint:disable-next-line:no-any
this._reject.apply(this.scope ? this.scope : this, arguments as any);
this._rejected = true;
}
get promise(): Promise<T> {
Expand All @@ -67,9 +69,18 @@ export function createDeferred<T>(scope: any = null): Deferred<T> {

export function createDeferredFrom<T>(...promises: Promise<T>[]): Deferred<T> {
const deferred = createDeferred<T>();
Promise.all(promises)
Promise.all<T>(promises)
// tslint:disable-next-line:no-any
.then(deferred.resolve.bind(deferred) as any)
// tslint:disable-next-line:no-any
.catch(deferred.reject.bind(deferred) as any);

return deferred;
}
export function createDeferredFromPromise<T>(promise: Promise<T>): Deferred<T> {
const deferred = createDeferred<T>();
promise
.then(deferred.resolve.bind(deferred))
.catch(deferred.reject.bind(deferred));

return deferred;
}