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

VS Python analysis engine integration #1144

Closed
wants to merge 41 commits into from
Closed

VS Python analysis engine integration #1144

wants to merge 41 commits into from

Conversation

MikhailArkhipov
Copy link

@MikhailArkhipov MikhailArkhipov commented Mar 21, 2018

Fixes #1101
Fixes #1087
Fixes #1063
Fixes #1051
Fixes #1004
Fixes #998
Fixes #408
Fixes #82

  • VSC part, not active for general audience yet. (Needs integration in the PTVS repo first).
  • Not all functionality is exactly like Jedi, see Meta for tracking Language Server issues #1006
  • Tests are not active on Appveyor
  • There is a task to run tests with the VS Analysis engine (couple of signature tests can fail b/c of Generate SignatureAnalysis without using GetExpressionRange PTVS#3171.
  • Jedi and VS Analysis engine are activated via python.jediEnabled setting (default true)
  • Tests pass on Mac except issue with import *
  • Activation is separated into 'classic' and 'analysis`
  • Not tested on Linux
  • .NET Core 2.x SDK or at least runtime is required for running VS Python Engine
  • Building the engine will be documented separately
  • There is nothing to add to news just yet

This pull request:

@codecov
Copy link

codecov bot commented Mar 21, 2018

Codecov Report

Merging #1144 into master will decrease coverage by 0.02%.
The diff coverage is 26.31%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1144      +/-   ##
==========================================
- Coverage   71.93%   71.91%   -0.03%     
==========================================
  Files         260      260              
  Lines       11933    11951      +18     
  Branches     2121     2123       +2     
==========================================
+ Hits         8584     8594      +10     
- Misses       3220     3231      +11     
+ Partials      129      126       -3
Impacted Files Coverage Δ
src/client/common/types.ts 100% <ø> (ø) ⬆️
src/client/common/configuration/service.ts 69.23% <26.31%> (-24.89%) ⬇️
src/client/linters/baseLinter.ts 87.5% <0%> (-1.05%) ⬇️
src/client/debugger/Main.ts 50.12% <0%> (-0.5%) ⬇️
...rc/client/debugger/PythonProcessCallbackHandler.ts 52.3% <0%> (+0.65%) ⬆️
src/client/debugger/PythonProcess.ts 48.75% <0%> (+2.91%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3b9dd72...3e071e0. Read the comment docs.

Copy link

@DonJayamanne DonJayamanne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will need to run through the code one again to review this.

synchronize: {
configurationSection: PYTHON
},
outputChannel: outputChannel,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of outputChannel: outputChannel, you can write outputChannel

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C# habits...

outputChannel: outputChannel,
initializationOptions: {
interpreter: {
properties: properties

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of properties: properties you can write properties


private async getInterpreterData(): Promise<InterpreterData> {
const execService = await this.executionFactory.create();
const result = await execService.exec(['-c', 'import sys; print(sys.version); print(sys.prefix)'], {});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remember, sys.version can contain strings. E.g. if using a beta version of Python or similar, the version could be 3.7.1Beta.
Please confirm this is not a problem.
My suggestion is to use sys.version_info (safest option).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTVS uses just major part, but yes, cleaner is better.


// Microsoft Python code analysis engine needs full path to the interpreter
const interpreterService = this.services.get<IInterpreterService>(IInterpreterService);
const interpreter = await interpreterService.getActiveInterpreter();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this analysis engine work with a multi root workspace?
E.g. workspace folder A & B contain two separate versions of Python.
Will we have only one analysis engine or two, will they all use the same version of python?

If these haven't been considered yet, please create an issue to track this so it gets resolved eventually.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good point. So far VS project has same interpreter. But not per folder. However, solution can have multiple projects with different interpreters.

#1149

}

private async getSearchPaths(): Promise<string> {
const execService = await this.executionFactory.create();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're assuming that we're always dealing with a single workspace here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Same #1149

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comments

@@ -32,6 +39,10 @@ export class ConfigurationService implements IConfigurationService {
return process.env.VSC_PYTHON_CI_TEST === '1';
}

public async checkDependencies(): Promise<boolean> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think checking dependencies should go into the ConfigurationService (configuration service deals with config settings)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Configuration sounded like related to dependency checks. I'll separate.

@@ -95,6 +97,10 @@ suite('Autocomplete', () => {

// https://github.com/DonJayamanne/pythonVSCode/issues/630
test('For "abc.decorators"', async () => {
// Disabled for MS Python Code Analysis, see https://github.com/Microsoft/PTVS/issues/3857
if (IS_ANALYSIS_ENGINE_TEST) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please can we add an issue in our Report to remove this condition. Else even if PTVS issue is resolved, we'll never end up removing this if condition unless someone remembers to do so.
Having a github issue will remind of this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is more to separate Jedi from PTVS rather than for a specific issue. Also to run PTVS tests separately on Appveyor. #1150

}).then(done, done);
});

// https://github.com/Microsoft/vscode-python/issues/110
test('Suppress in strings/comments', async () => {
// Excluded from MS Python Code Analysis b/c skipping of strings and comments
// is not yet there. See https://github.com/Microsoft/PTVS/issues/3798
if (IS_ANALYSIS_ENGINE_TEST) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please can we add an issue in our Report to remove this condition. Else even if PTVS issue is resolved, we'll never end up removing this if condition unless someone remembers to do so.
Having a github issue will remind of this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suiteSetup(async () => {
suiteSetup(async function () {
// https://github.com/Microsoft/PTVS/issues/3917
if (IS_ANALYSIS_ENGINE_TEST) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please can we add an issue in our Report to remove this condition. Else even if PTVS issue is resolved, we'll never end up removing this if condition unless someone remembers to do so.
Having a github issue will remind of this.

suiteSetup(async () => {
suiteSetup(async function () {
// https://github.com/Microsoft/PTVS/issues/3917
if (IS_ANALYSIS_ENGINE_TEST) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please can we add an issue in our Report to remove this condition. Else even if PTVS issue is resolved, we'll never end up removing this if condition unless someone remembers to do so.
Having a github issue will remind of this.

if (settings.autoComplete) {
const extraPaths = settings.autoComplete.extraPaths;
if (extraPaths && extraPaths.length > 0) {
searchPaths = `${searchPaths};${extraPaths.join(';')}`;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the path delimeter OS dependent. On Linx path seprators is :
You can use path.sep

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTVS engine split paths by ;. I believe ; is universal for providing multiple paths. Alternative is to marshal array over to LS.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is universal for providing multiple paths.

No, its not. Try the following in your terminal (linux/mac)
echo $PATH

// tslint:disable-next-line:no-string-literal
properties['PrefixPath'] = interpreterData.prefix;
// tslint:disable-next-line:no-string-literal
properties['DatabasePath'] = path.join(context.extensionPath, analysisEngineFolder);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • What is this folder path for?
  • Are we creating files?
  • Will having two instances of VS Code or multi root workspaces affect this adversely.
  • This folder will get deleted everytime the user updates their extension.
  • When storing files you're better off using context.storagePath

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it creates files. They are in unique folders for different interpreters and paths.

image

It is OK (and good) to delete folder on upgrade since format of the intellisense cache can change. It will be rebuilt.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke to @huguesv today about these directories. He mentioned that we won't need to create/delete them every time. According to him they will never change (unless of course the PTVS changes).
Possible he's not aware of some of the new changes.

Either way can we somehow try to minimize this or re-use folders rather than creating one per each interpreter.
This could result in a large number of directories (if using virtual envs or pyenv, then each workspace could potentially have a separate environment).

Or please create a separate issue for this (rather than trying to solve this in this PR).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does PTVS have the ability to re-create/rebuild this database?
I.e. do we need to consider adding the ability (later as a separate PR) for users to re-build this database?
I know a simple solution is to uninstall/re-install the extension, but that sounds like a hack and not very user friendly. But before we do, is such a feature necessary? If yes, lets do that in a separate PR/issue.

const paths = result.stdout.split(',')
.filter(p => this.isValidPath(p))
.map(p => this.pathCleanup(p));
return paths.join(';');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path separator OS dependent (if that's what this is).
You would want to use path.sep instead of hard coding ;

Copy link
Author

@MikhailArkhipov MikhailArkhipov Mar 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTVS splits multiple paths by ; while path.sep is \ vs / I believe

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, I meant path.delimiter

private async isDotNetInstalled(): Promise<boolean> {
const ps = this.services.get<IProcessService>(IProcessService);
const result = await ps.exec('dotnet', ['--version']);
return result.stdout.trim().startsWith('2.');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would need a trycatch in case dotnet crashes for some reason without returning errors in stderr

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const result = await ps.exec('dotnet', ['--version']).catch(()= > { return {stdout:''};})

await this.unpackArchive(context.extensionPath, localTempFilePath);
} finally {
fs.unlink(localTempFilePath, noop);
}
}

private async downloadFile(): Promise<string> {
const platformString = this.getPlatformDesignator();
const platformString = this.platformData.getPlatformDesignator();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to display the progress in VS Code using the VS Code API:
https://code.visualstudio.com/docs/extensionAPI/vscode-api#ProgressOptions

Check out the withProgress method.

"type": "gulp",
"task": "watch",
"task": "hygiene-watch",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this will improve performance, let me know.
The only other solution I can think of is to use tsc watch and run hygiene so it display errors ONLY in terminal window and throw errors when trying to commit (problem with this is you won't see errors in problems window, but faster).

Or the fastest approach is tsc --watch -p ./ & don't bother with hygiene just let it complain during commit

return true;
}

private reportDownloadSize(response: IncomingMessage): number {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use an existing package to download a file and report the progress (https://www.npmjs.com/package/request-progress).
This does exactly what you need.

this.output.append('done.');
}

private handleError(message: string) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use request-progress npm

if (response.rawHeaders.length >= 2 && response.rawHeaders[0] === 'Content-Length') {
const size = parseInt(response.rawHeaders[1], 10);
if (size > 0) {
this.output.append(` ${Math.round(size / 1024)} KB...`);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please report download progress using VS Code api, so users too can see this.

deferred.resolve();
})
.on('error', (err) => {
this.handleError(`Unable to unpack downloaded file. Error ${err}.`);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll need to bubble up the error, (throwing exceptions inside handleError will not work.
deferred.reject(err)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd throw an exception here and call handleError from catch in downloadAnalysisEngine.

});

let firstResponse = true;
https.get(uri, (response) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace with npm package.

deferred.resolve();
})
.on('error', (err) => {
this.handleError(`Unable to unpack downloaded file. Error ${err}.`);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to .on('error', deferred.reject.bind(deferred))
else it won't work

this.output.append('Verifying download... ');
const verifier = new HashVerifier();
if (!await verifier.verifyHash(filePath, this.platformData.getExpectedHash())) {
this.handleError('Hash of the downloaded file does not match.');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to call handleError, just throw the exception here throw new Error(.....), let the calling method handle and log all exceptions.

if (this.platform.isLinux && this.platform.is64bit) {
return 'linux-x64';
}
throw new Error('Python Analysis Engine does not support 32-bit Linux.');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've created an issue for this, we need to document this limitation in our documentation https://github.com/Microsoft/vscode-python/issues/new

readonly terminal: ITerminalSettings;
readonly sortImports: ISortImportSettings;
readonly workspaceSymbols: IWorkspaceSymbolSettings;
readonly linting: ILintingSettings | undefined;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change anything (you can also use readonly linting?: ILintingSettings)

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

export const analysis_engine_win_x86_sha512 = '6ce1f4a4a83ea7a662f2034de3f83ba8342863e9c1d97dfc23981931e69d1358b8953ae9fbec05be4d755118d695c974a39cd4fd1d6e4735bf5d60d58ab44fe9';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this change for each build? If yes, then we might want to put this into package.json

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The file is generated and injected in VSTS. Default state is empty string which means 'dont check'

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is for signing step only. VSTS task wants C# project.

@MikhailArkhipov
Copy link
Author

Need to restore lost changes after rebase

@lock lock bot locked as resolved and limited conversation to collaborators Jul 31, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.