4
4
import * as fsapi from 'fs-extra' ;
5
5
import * as path from 'path' ;
6
6
import { traceWarning } from '../../common/logger' ;
7
- import { Resource } from '../../common/types' ;
8
7
import { createDeferred } from '../../common/utils/async' ;
9
8
import { getEnvironmentVariable } from '../../common/utils/platform' ;
10
9
import { EnvironmentType } from '../info' ;
@@ -17,6 +16,10 @@ function pathExists(absPath: string): Promise<boolean> {
17
16
return deferred . promise ;
18
17
}
19
18
19
+ function readFile ( filePath : string ) : Promise < string > {
20
+ return fsapi . readFile ( filePath , 'utf-8' ) ;
21
+ }
22
+
20
23
/**
21
24
* Checks if the given interpreter path belongs to a conda environment. Using
22
25
* known folder layout, and presence of 'conda-meta' directory.
@@ -128,23 +131,22 @@ async function isWindowsStoreEnvironment(interpreterPath: string): Promise<boole
128
131
* @param cwd the directory to look into
129
132
* @param lookIntoParentDirectories set to true if we should also search for Pipfile in parent directory
130
133
*/
131
- function checkIfPipFileExists ( cwd : string , lookIntoParentDirectories : boolean ) : boolean {
132
- const pipFileName = process . env . PIPENV_PIPFILE ;
134
+ async function checkIfPipFileExists ( cwd : string , lookIntoParentDirectories : boolean ) : Promise < boolean > {
135
+ const pipFileName = getEnvironmentVariable ( ' PIPENV_PIPFILE' ) || 'Pipfile' ;
133
136
let depthToSearch = 1 ;
134
137
if ( lookIntoParentDirectories ) {
135
138
// PIPENV_MAX_DEPTH tells pipenv the maximum number of directories to recursively search for
136
139
// a Pipfile, defaults to 3: https://pipenv.pypa.io/en/latest/advanced/#pipenv.environments.PIPENV_MAX_DEPTH
137
- if ( process . env . PIPENV_MAX_DEPTH ) {
138
- depthToSearch = + process . env . PIPENV_MAX_DEPTH ;
140
+ const maxDepth = getEnvironmentVariable ( 'PIPENV_MAX_DEPTH' ) ;
141
+ if ( maxDepth ) {
142
+ depthToSearch = + maxDepth ;
139
143
} else {
140
144
depthToSearch = 3 ;
141
145
}
142
146
}
143
147
while ( depthToSearch > 0 && cwd === path . dirname ( cwd ) ) {
144
- if ( typeof pipFileName === 'string' && pathExists ( path . join ( cwd , pipFileName ) ) ) {
145
- return true ;
146
- }
147
- if ( pathExists ( path . join ( cwd , 'Pipfile' ) ) ) {
148
+ // eslint-disable-next-line no-await-in-loop
149
+ if ( await pathExists ( path . join ( cwd , pipFileName ) ) ) {
148
150
return true ;
149
151
}
150
152
cwd = path . dirname ( cwd ) ;
@@ -157,74 +159,72 @@ function checkIfPipFileExists(cwd: string, lookIntoParentDirectories: boolean):
157
159
* Returns true if interpreter path belongs to a pipenv environment which is located inside a project, false otherwise.
158
160
* @param interpreterPath Absolute path to any python interpreter.
159
161
*/
160
- function isLocalPipenvEnvironment ( interpreterPath : string ) : boolean {
162
+ async function isLocalPipenvEnvironment ( interpreterPath : string ) : Promise < boolean > {
161
163
// Local pipenv environments are created by setting PIPENV_VENV_IN_PROJECT to 1, which always names the environment
162
164
// folder '.venv': https://pipenv.pypa.io/en/latest/advanced/#pipenv.environments.PIPENV_VENV_IN_PROJECT
163
165
// This is the layout we wish to verify.
164
166
// project
165
167
// |__ Pipfile <--- check if Pipfile exists here
166
- // |__ .venv
168
+ // |__ .venv <--- check if name of the folder is '.venv'
167
169
// |__ Scripts/bin
168
170
// |__ python <--- interpreterPath
169
171
const venvFolderName = path . basename ( path . dirname ( path . dirname ( interpreterPath ) ) ) ;
170
172
if ( venvFolderName !== '.venv' ) {
171
173
return false ;
172
174
}
173
175
const directoryWhereVenvResides = path . dirname ( path . dirname ( path . dirname ( interpreterPath ) ) ) ;
174
- if ( checkIfPipFileExists ( directoryWhereVenvResides , false ) ) {
176
+ if ( await checkIfPipFileExists ( directoryWhereVenvResides , false ) ) {
175
177
// The directory must contain a Pipfile
176
178
return true ;
177
179
}
178
180
return false ;
179
181
}
180
182
181
183
/**
182
- * Returns true if interpreter path belongs to a global pipenv environment created for a particular project,
183
- * false otherwise.
184
+ * Returns true if interpreter path belongs to a global pipenv environment, false otherwise.
184
185
* @param interpreterPath Absolute path to any python interpreter.
185
- * @param project Absolute path to the project.
186
186
*/
187
- async function isGlobalPipenvEnvironmentRelatedToProject ( interpreterPath : string , project : string ) : Promise < boolean > {
187
+ async function isGlobalPipenvEnvironment ( interpreterPath : string ) : Promise < boolean > {
188
+ // Global pipenv environments have a .project file with the absolute path to the project.
189
+ // Also, the name of the project is used as a prefix in the environment folder.
190
+ // <Environment folder> <--- check if the name of the project is used as a prefix
191
+ // |__ .project <--- check if .project exists here
192
+ // |__ Scripts/bin
193
+ // |__ python <--- interpreterPath
194
+ const dotProjectFile = path . join ( path . dirname ( path . dirname ( interpreterPath ) ) , '.project' ) ;
195
+ if ( ! await pathExists ( dotProjectFile ) ) {
196
+ return false ;
197
+ }
198
+
199
+ const project = await readFile ( dotProjectFile ) ;
200
+ if ( ! await pathExists ( project ) ) {
201
+ return false ;
202
+ }
203
+
204
+ // The name of the project is used as a prefix in the environment folder.
205
+ if ( interpreterPath . indexOf ( `${ path . sep } ${ path . basename ( project ) } -` ) === - 1 ) {
206
+ return false ;
207
+ }
208
+
188
209
// PIPENV_NO_INHERIT is used to tell pipenv not to look for Pipfile in parent directories
189
210
// https://pipenv.pypa.io/en/latest/advanced/#pipenv.environments.PIPENV_NO_INHERIT
190
- if ( ! checkIfPipFileExists ( project , ! process . env . PIPENV_NO_INHERIT ) ) {
211
+ if ( ! checkIfPipFileExists ( project , ! getEnvironmentVariable ( ' PIPENV_NO_INHERIT' ) ) ) {
191
212
return false ;
192
213
}
193
214
194
- /**
195
- * Global pipenv environments can be stored in 3 possible locations:
196
- * * WORKON_HOME environment variable: https://pipenv-fork.readthedocs.io/en/latest/advanced.html#custom-virtual-environment-location
197
- * * ~/.virtualenvs - for windows
198
- * * ~/.local/share/virtualenvs
199
- *
200
- * The name of the project is used as a prefix in the virtual env. We're assuming it's unique enough
201
- * detail so we don't have further ensure that the environment exists within these locations.
202
- */
203
- if ( interpreterPath . indexOf ( `${ path . sep } ${ path . basename ( project ) } -` ) !== - 1 ) {
204
- // Note it's still possible that this virtual environment belongs to another project with the same name,
205
- // and not this project. But the type of environment would still be pipenv, so we need not care.
206
- return true ;
207
- }
208
- return false ;
215
+ return true ;
209
216
}
210
217
211
218
/**
212
219
* Checks if the given interpreter path belongs to a pipenv environment, by locating the Pipfile which was used to
213
220
* create the environment.
214
221
* @param interpreterPath: Absolute path to any python interpreter.
215
- * @param resource Uri of the project
216
222
*/
217
- async function isPipenvEnvironment ( interpreterPath : string , resource : Resource ) {
218
- if ( isLocalPipenvEnvironment ( interpreterPath ) ) {
223
+ async function isPipenvEnvironment ( interpreterPath : string ) {
224
+ if ( await isLocalPipenvEnvironment ( interpreterPath ) ) {
219
225
return true ;
220
226
}
221
- /**
222
- * Otherwise it's a globally stored environment. Unfortunately, we cannot tell if a global environment is a pipenv
223
- * environment just by looking around the interpreter path. Also, because we cannot get project path from the pipenv
224
- * environment, we cannot locate the Pipfile which was used to create the environment. The best we can do is guess
225
- * the project it belongs to and see if everything else adds up. If it does, voila, we have a pipenv environment.
226
- */
227
- if ( resource && isGlobalPipenvEnvironmentRelatedToProject ( interpreterPath , resource . fsPath ) ) {
227
+ if ( await isGlobalPipenvEnvironment ( interpreterPath ) ) {
228
228
return true ;
229
229
}
230
230
return false ;
@@ -253,7 +253,7 @@ async function isPipenvEnvironment(interpreterPath: string, resource: Resource)
253
253
*
254
254
* Last category is globally installed python, or system python.
255
255
*/
256
- export async function identifyEnvironment ( interpreterPath : string , resource ?: Resource ) : Promise < EnvironmentType > {
256
+ export async function identifyEnvironment ( interpreterPath : string ) : Promise < EnvironmentType > {
257
257
if ( await isCondaEnvironment ( interpreterPath ) ) {
258
258
return EnvironmentType . Conda ;
259
259
}
@@ -262,7 +262,7 @@ export async function identifyEnvironment(interpreterPath: string, resource?: Re
262
262
return EnvironmentType . WindowsStore ;
263
263
}
264
264
265
- if ( await isPipenvEnvironment ( interpreterPath , resource ) ) {
265
+ if ( await isPipenvEnvironment ( interpreterPath ) ) {
266
266
return EnvironmentType . Pipenv ;
267
267
}
268
268
0 commit comments