@@ -4,6 +4,9 @@ import * as vscode from 'vscode';
44import { SystemVariables } from './systemVariables' ;
55import { EventEmitter } from 'events' ;
66import * as path from 'path' ;
7+ import * as child_process from 'child_process' ;
8+
9+ export const IS_WINDOWS = / ^ w i n / . test ( process . platform ) ;
710
811export interface IPythonSettings {
912 pythonPath : string ;
@@ -117,7 +120,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
117120 this . disposables . push ( vscode . workspace . onDidChangeConfiguration ( ( ) => {
118121 this . initializeSettings ( ) ;
119122 } ) ) ;
120-
123+
121124 this . initializeSettings ( ) ;
122125 }
123126 public static getInstance ( ) : PythonSettings {
@@ -282,7 +285,23 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
282285 this . emit ( 'change' ) ;
283286 }
284287
285- public pythonPath : string ;
288+ private _pythonPath : string ;
289+ public get pythonPath ( ) : string {
290+ return this . _pythonPath ;
291+ }
292+ public set pythonPath ( value : string ) {
293+ if ( this . _pythonPath === value ) {
294+ return ;
295+ }
296+ // Add support for specifying just the directory where the python executable will be located
297+ // E.g. virtual directory name
298+ try {
299+ this . _pythonPath = getPythonExecutable ( value ) ;
300+ }
301+ catch ( ex ) {
302+ this . _pythonPath = value ;
303+ }
304+ }
286305 public jediPath : string ;
287306 public devOptions : string [ ] ;
288307 public linting : ILintingSettings ;
@@ -301,4 +320,47 @@ function getAbsolutePath(pathToCheck: string, rootDir: string): string {
301320 return pathToCheck ;
302321 }
303322 return path . isAbsolute ( pathToCheck ) ? pathToCheck : path . resolve ( rootDir , pathToCheck ) ;
323+ }
324+
325+ function getPythonExecutable ( pythonPath : string ) : string {
326+ // If only 'python'
327+ if ( pythonPath === 'python' ||
328+ pythonPath . indexOf ( path . sep ) === - 1 ||
329+ path . basename ( pythonPath ) === path . dirname ( pythonPath ) ) {
330+ return pythonPath ;
331+ }
332+
333+ if ( isValidPythonPath ( pythonPath ) ) {
334+ return pythonPath ;
335+ }
336+
337+ // Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows'
338+ if ( IS_WINDOWS ) {
339+ if ( isValidPythonPath ( path . join ( pythonPath , 'python.exe' ) ) ) {
340+ return path . join ( pythonPath , 'python.exe' ) ;
341+ }
342+ if ( isValidPythonPath ( path . join ( pythonPath , 'scripts' , 'python.exe' ) ) ) {
343+ return path . join ( pythonPath , 'scripts' , 'python.exe' ) ;
344+ }
345+ }
346+ else {
347+ if ( isValidPythonPath ( path . join ( pythonPath , 'python' ) ) ) {
348+ return path . join ( pythonPath , 'python' ) ;
349+ }
350+ if ( isValidPythonPath ( path . join ( pythonPath , 'bin' , 'python' ) ) ) {
351+ return path . join ( pythonPath , 'bin' , 'python' ) ;
352+ }
353+ }
354+
355+ return pythonPath ;
356+ }
357+
358+ function isValidPythonPath ( pythonPath ) : boolean {
359+ try {
360+ let output = child_process . execFileSync ( pythonPath , [ '-c' , 'print(1234)' ] , { encoding : 'utf8' } ) ;
361+ return output . startsWith ( '1234' ) ;
362+ }
363+ catch ( ex ) {
364+ return false ;
365+ }
304366}
0 commit comments