Skip to content

Commit fa820fe

Browse files
authored
Fixes #22 to Detect anaconda from known locations (#221)
Fixes #22 to Detect anaconda from known locations Also added a strict linter to lint modified files (background task) * look for conda file in known locations * gulp changes to watch strict linting * merged upstream * fix code review comments * inject windows flag into services
1 parent a975862 commit fa820fe

File tree

11 files changed

+175
-77
lines changed

11 files changed

+175
-77
lines changed

.vscode/tasks.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,24 @@
6565
"fileLocation": "relative"
6666
}
6767
]
68+
},
69+
{
70+
"type": "gulp",
71+
"task": "hygiene-watch",
72+
"isBackground": true,
73+
"presentation": {
74+
"echo": true,
75+
"reveal": "always",
76+
"focus": false,
77+
"panel": "shared"
78+
},
79+
"problemMatcher": [
80+
"$tsc",
81+
{
82+
"base": "$tslint5",
83+
"fileLocation": "relative"
84+
}
85+
]
6886
}
6987
]
7088
}

gulpfile.js

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ const tsfmt = require('typescript-formatter');
1212
const tslint = require('tslint');
1313
const relative = require('relative');
1414
const ts = require('gulp-typescript');
15+
const watch = require('gulp-watch');
16+
const cp = require('child_process');
1517

1618
/**
1719
* Hygiene works by creating cascading subsets of all our files and
@@ -212,47 +214,51 @@ const hygiene = exports.hygiene = (some, options) => {
212214

213215
gulp.task('hygiene', () => hygiene());
214216

215-
// this allows us to run hygiene as a git pre-commit hook.
216-
if (require.main === module) {
217-
const cp = require('child_process');
217+
gulp.task('hygiene-watch', function () {
218+
return watch(all, function () {
219+
run(true, true);
220+
});
221+
});
218222

223+
function run(lintOnlyModifiedFiles, doNotExit) {
224+
function exitProcessOnError(ex) {
225+
console.error();
226+
console.error(ex);
227+
if (!doNotExit) {
228+
process.exit(1);
229+
}
230+
}
219231
process.on('unhandledRejection', (reason, p) => {
220232
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
221-
process.exit(1);
233+
exitProcessOnError();
222234
});
223235

224236
cp.exec('git config core.autocrlf', (err, out) => {
225237
const skipEOL = out.trim() === 'true';
226-
227-
if (process.argv.length > 2) {
238+
if (!lintOnlyModifiedFiles && process.argv.length > 2) {
228239
return hygiene(process.argv.slice(2), {
229240
skipEOL: skipEOL
230-
}).on('error', err => {
231-
console.error();
232-
console.error(err);
233-
process.exit(1);
234-
});
241+
}).on('error', exitProcessOnError);
235242
}
236243

237-
cp.exec('git diff --cached --name-only', {
244+
const cmd = lintOnlyModifiedFiles ? 'git diff --name-only' : 'git diff --cached --name-only';
245+
cp.exec(cmd, {
238246
maxBuffer: 2000 * 1024
239247
}, (err, out) => {
240248
if (err) {
241-
console.error();
242-
console.error(err);
243-
process.exit(1);
249+
exitProcessOnError(err);
244250
}
245-
246251
const some = out
247252
.split(/\r?\n/)
248253
.filter(l => !!l);
254+
249255
hygiene(some, {
250256
skipEOL: skipEOL
251-
}).on('error', err => {
252-
console.error();
253-
console.error(err);
254-
process.exit(1);
255-
});
257+
}).on('error', exitProcessOnError);
256258
});
257259
});
258260
}
261+
// this allows us to run hygiene as a git pre-commit hook.
262+
if (require.main === module) {
263+
run();
264+
}

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,6 +1578,7 @@
15781578
"gulp": "^3.9.1",
15791579
"gulp-filter": "^5.0.1",
15801580
"gulp-typescript": "^3.2.2",
1581+
"gulp-watch": "^4.3.11",
15811582
"husky": "^0.14.3",
15821583
"ignore-loader": "^0.1.1",
15831584
"mocha": "^2.3.3",
@@ -1592,7 +1593,7 @@
15921593
"tslint-microsoft-contrib": "^5.0.1",
15931594
"typescript": "^2.5.2",
15941595
"typescript-formatter": "^6.0.0",
1595-
"webpack": "^1.13.2",
1596-
"vscode": "^1.1.5"
1596+
"vscode": "^1.1.5",
1597+
"webpack": "^1.13.2"
15971598
}
15981599
}

src/client/interpreter/contracts.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ export interface IInterpreterLocatorService extends Disposable {
55
getInterpreters(resource?: Uri): Promise<PythonInterpreter[]>;
66
}
77

8+
export interface ICondaLocatorService {
9+
getCondaFile(): Promise<string>;
10+
}
11+
812
export type PythonInterpreter = {
913
path: string;
1014
companyDisplayName?: string;

src/client/interpreter/helpers.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ConfigurationTarget, window, workspace } from 'vscode';
33
import { RegistryImplementation } from '../common/registry';
44
import { Is_64Bit, IS_WINDOWS } from '../common/utils';
55
import { WorkspacePythonPath } from './contracts';
6-
import { CondaEnvService } from './locators/services/condaEnvService';
6+
import { CondaLocatorService } from './locators/services/condaLocator';
77
import { WindowsRegistryService } from './locators/services/windowsRegistryService';
88

99
export function getFirstNonEmptyLineFromMultilineString(stdout: string) {
@@ -29,14 +29,10 @@ export function getActiveWorkspaceUri(): WorkspacePythonPath | undefined {
2929
return undefined;
3030
}
3131
export async function getCondaVersion() {
32-
let condaService: CondaEnvService;
33-
if (IS_WINDOWS) {
34-
const windowsRegistryProvider = new WindowsRegistryService(new RegistryImplementation(), Is_64Bit);
35-
condaService = new CondaEnvService(windowsRegistryProvider);
36-
} else {
37-
condaService = new CondaEnvService();
38-
}
39-
return condaService.getCondaFile()
32+
const windowsRegistryProvider = IS_WINDOWS ? new WindowsRegistryService(new RegistryImplementation(), Is_64Bit) : undefined;
33+
const condaLocator = new CondaLocatorService(IS_WINDOWS, windowsRegistryProvider);
34+
35+
return condaLocator.getCondaFile()
4036
.then(async condaFile => {
4137
return new Promise<string>((resolve, reject) => {
4238
child_process.execFile(condaFile, ['--version'], (_, stdout) => {

src/client/interpreter/locators/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { VirtualEnvironmentManager } from '../virtualEnvs';
99
import { fixInterpreterDisplayName, fixInterpreterPath } from './helpers';
1010
import { CondaEnvFileService, getEnvironmentsFile as getCondaEnvFile } from './services/condaEnvFileService';
1111
import { CondaEnvService } from './services/condaEnvService';
12+
import { CondaLocatorService } from './services/condaLocator';
1213
import { CurrentPathService } from './services/currentPathService';
1314
import { getKnownSearchPathsForInterpreters, KnownPathsService } from './services/KnownPathsService';
1415
import { getKnownSearchPathsForVirtualEnvs, VirtualEnvService } from './services/virtualEnvService';
@@ -66,10 +67,12 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
6667
// The order of the services is important.
6768
if (IS_WINDOWS) {
6869
const windowsRegistryProvider = new WindowsRegistryService(new RegistryImplementation(), Is_64Bit);
70+
const condaLocator = new CondaLocatorService(IS_WINDOWS, windowsRegistryProvider);
6971
locators.push(windowsRegistryProvider);
70-
locators.push(new CondaEnvService(windowsRegistryProvider));
72+
locators.push(new CondaEnvService(condaLocator));
7173
} else {
72-
locators.push(new CondaEnvService());
74+
const condaLocator = new CondaLocatorService(IS_WINDOWS);
75+
locators.push(new CondaEnvService(condaLocator));
7376
}
7477
// Supplements the above list of conda environments.
7578
locators.push(new CondaEnvFileService(getCondaEnvFile(), versionService));

src/client/interpreter/locators/services/condaEnvService.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,19 @@ import * as fs from 'fs-extra';
44
import * as path from 'path';
55
import { Uri } from 'vscode';
66
import { VersionUtils } from '../../../common/versionUtils';
7-
import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts';
7+
import { ICondaLocatorService, IInterpreterLocatorService, PythonInterpreter } from '../../contracts';
88
import { AnacondaCompanyName, CONDA_RELATIVE_PY_PATH, CondaInfo } from './conda';
99
import { CondaHelper } from './condaHelper';
1010

1111
export class CondaEnvService implements IInterpreterLocatorService {
1212
private readonly condaHelper = new CondaHelper();
13-
constructor(private registryLookupForConda?: IInterpreterLocatorService) {
13+
constructor(private condaLocator: ICondaLocatorService) {
1414
}
1515
public async getInterpreters(resource?: Uri) {
1616
return this.getSuggestionsFromConda();
1717
}
1818
// tslint:disable-next-line:no-empty
1919
public dispose() { }
20-
public async getCondaFile() {
21-
if (this.registryLookupForConda) {
22-
return this.registryLookupForConda.getInterpreters()
23-
.then(interpreters => interpreters.filter(this.isCondaEnvironment))
24-
.then(condaInterpreters => this.getLatestVersion(condaInterpreters))
25-
.then(condaInterpreter => {
26-
return condaInterpreter ? path.join(path.dirname(condaInterpreter.path), 'conda.exe') : 'conda';
27-
})
28-
.then(async condaPath => {
29-
return fs.pathExists(condaPath).then(exists => exists ? condaPath : 'conda');
30-
});
31-
}
32-
return Promise.resolve('conda');
33-
}
3420
public isCondaEnvironment(interpreter: PythonInterpreter) {
3521
return (interpreter.displayName ? interpreter.displayName : '').toUpperCase().indexOf('ANACONDA') >= 0 ||
3622
(interpreter.companyDisplayName ? interpreter.companyDisplayName : '').toUpperCase().indexOf('CONTINUUM') >= 0;
@@ -73,7 +59,7 @@ export class CondaEnvService implements IInterpreterLocatorService {
7359
.then(interpreters => interpreters.map(interpreter => interpreter!));
7460
}
7561
private async getSuggestionsFromConda(): Promise<PythonInterpreter[]> {
76-
return this.getCondaFile()
62+
return this.condaLocator.getCondaFile()
7763
.then(async condaFile => {
7864
return new Promise<PythonInterpreter[]>((resolve, reject) => {
7965
// interrogate conda (if it's on the path) to find all environments.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use strict';
2+
import * as child_process from 'child_process';
3+
import * as fs from 'fs-extra';
4+
import * as path from 'path';
5+
import { IS_WINDOWS } from '../../../common/utils';
6+
import { VersionUtils } from '../../../common/versionUtils';
7+
import { ICondaLocatorService, IInterpreterLocatorService, PythonInterpreter } from '../../contracts';
8+
// tslint:disable-next-line:no-require-imports no-var-requires
9+
const untildify: (value: string) => string = require('untildify');
10+
11+
const KNOWN_CONDA_LOCATIONS = ['~/anaconda/bin/conda', '~/miniconda/bin/conda',
12+
'~/anaconda2/bin/conda', '~/miniconda2/bin/conda',
13+
'~/anaconda3/bin/conda', '~/miniconda3/bin/conda'];
14+
15+
export class CondaLocatorService implements ICondaLocatorService {
16+
constructor(private isWindows: boolean, private registryLookupForConda?: IInterpreterLocatorService) {
17+
}
18+
// tslint:disable-next-line:no-empty
19+
public dispose() { }
20+
public async getCondaFile(): Promise<string> {
21+
const isAvailable = await this.isCondaInCurrentPath();
22+
if (isAvailable) {
23+
return 'conda';
24+
}
25+
if (this.isWindows && this.registryLookupForConda) {
26+
return this.registryLookupForConda.getInterpreters()
27+
.then(interpreters => interpreters.filter(this.isCondaEnvironment))
28+
.then(condaInterpreters => this.getLatestVersion(condaInterpreters))
29+
.then(condaInterpreter => {
30+
return condaInterpreter ? path.join(path.dirname(condaInterpreter.path), 'conda.exe') : 'conda';
31+
})
32+
.then(async condaPath => {
33+
return fs.pathExists(condaPath).then(exists => exists ? condaPath : 'conda');
34+
});
35+
}
36+
return this.getCondaFileFromKnownLocations();
37+
}
38+
public isCondaEnvironment(interpreter: PythonInterpreter) {
39+
return (interpreter.displayName ? interpreter.displayName : '').toUpperCase().indexOf('ANACONDA') >= 0 ||
40+
(interpreter.companyDisplayName ? interpreter.companyDisplayName : '').toUpperCase().indexOf('CONTINUUM') >= 0;
41+
}
42+
public getLatestVersion(interpreters: PythonInterpreter[]) {
43+
const sortedInterpreters = interpreters.filter(interpreter => interpreter.version && interpreter.version.length > 0);
44+
// tslint:disable-next-line:no-non-null-assertion
45+
sortedInterpreters.sort((a, b) => VersionUtils.compareVersion(a.version!, b.version!));
46+
if (sortedInterpreters.length > 0) {
47+
return sortedInterpreters[sortedInterpreters.length - 1];
48+
}
49+
}
50+
public async isCondaInCurrentPath() {
51+
return new Promise<boolean>((resolve, reject) => {
52+
child_process.execFile('conda', ['--version'], (_, stdout) => {
53+
if (stdout && stdout.length > 0) {
54+
resolve(true);
55+
} else {
56+
resolve(false);
57+
}
58+
});
59+
});
60+
}
61+
private async getCondaFileFromKnownLocations(): Promise<string> {
62+
const condaFiles = await Promise.all(KNOWN_CONDA_LOCATIONS
63+
.map(untildify)
64+
.map(async (condaPath: string) => fs.pathExists(condaPath).then(exists => exists ? condaPath : '')));
65+
66+
const validCondaFiles = condaFiles.filter(condaPath => condaPath.length > 0);
67+
return validCondaFiles.length === 0 ? 'conda' : validCondaFiles[0];
68+
}
69+
}

0 commit comments

Comments
 (0)