diff --git a/news/2 Fixes/15439.md b/news/2 Fixes/15439.md new file mode 100644 index 000000000000..e6e4b7429126 --- /dev/null +++ b/news/2 Fixes/15439.md @@ -0,0 +1 @@ +Fix for missing pyenv virtual environments from selectable environments. diff --git a/src/client/pythonEnvironments/common/externalDependencies.ts b/src/client/pythonEnvironments/common/externalDependencies.ts index 062a8923245c..059a0bdd387f 100644 --- a/src/client/pythonEnvironments/common/externalDependencies.ts +++ b/src/client/pythonEnvironments/common/externalDependencies.ts @@ -97,23 +97,35 @@ export async function getFileInfo(filePath: string): Promise<{ ctime: number; mt } } -export async function resolveSymbolicLink(filepath: string): Promise { - const stats = await fsapi.lstat(filepath); +export async function resolveSymbolicLink(absPath: string): Promise { + const stats = await fsapi.lstat(absPath); if (stats.isSymbolicLink()) { - const link = await fsapi.readlink(filepath); + const link = await fsapi.readlink(absPath); return resolveSymbolicLink(link); } - return filepath; + return absPath; } -export async function* getSubDirs(root: string): AsyncIterableIterator { - const dirContents = await fsapi.readdir(root); +/** + * Returns full path to sub directories of a given directory. + * @param root + * @param resolveSymlinks + */ +export async function* getSubDirs(root: string, resolveSymlinks: boolean): AsyncIterableIterator { + const dirContents = await fsapi.promises.readdir(root, { withFileTypes: true }); const generators = dirContents.map((item) => { async function* generator() { - const stat = await fsapi.lstat(path.join(root, item)); - - if (stat.isDirectory()) { - yield item; + const fullPath = path.join(root, item.name); + if (item.isDirectory()) { + yield fullPath; + } else if (resolveSymlinks && item.isSymbolicLink()) { + // The current FS item is a symlink. It can potentially be a file + // or a directory. Resolve it first and then check if it is a directory. + const resolvedPath = await resolveSymbolicLink(fullPath); + const resolvedPathStat = await fsapi.lstat(resolvedPath); + if (resolvedPathStat.isDirectory()) { + yield resolvedPath; + } } } diff --git a/src/client/pythonEnvironments/discovery/locators/services/pyenvLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/pyenvLocator.ts index ddac1574932d..550dffa3dafc 100644 --- a/src/client/pythonEnvironments/discovery/locators/services/pyenvLocator.ts +++ b/src/client/pythonEnvironments/discovery/locators/services/pyenvLocator.ts @@ -259,15 +259,15 @@ export function parsePyenvVersion(str: string): Promise { const pyenvVersionDir = getPyenvVersionsDir(); - const subDirs = getSubDirs(pyenvVersionDir); - for await (const subDir of subDirs) { - const envDir = path.join(pyenvVersionDir, subDir); - const interpreterPath = await getInterpreterPathFromDir(envDir); + const subDirs = getSubDirs(pyenvVersionDir, true); + for await (const subDirPath of subDirs) { + const envDirName = path.basename(subDirPath); + const interpreterPath = await getInterpreterPathFromDir(subDirPath); if (interpreterPath) { // The sub-directory name sometimes can contain distro and python versions. // here we attempt to extract the texts out of the name. - const versionStrings = await parsePyenvVersion(subDir); + const versionStrings = await parsePyenvVersion(envDirName); // Here we look for near by files, or config files to see if we can get python version info // without running python itself. @@ -290,7 +290,7 @@ async function* getPyenvEnvironments(): AsyncIterableIterator { // `pyenv local|global ` or `pyenv shell ` // // For the display name we are going to treat these as `pyenv` environments. - const display = `${subDir}:pyenv`; + const display = `${envDirName}:pyenv`; const org = versionStrings && versionStrings.distro ? versionStrings.distro : ''; @@ -299,14 +299,14 @@ async function* getPyenvEnvironments(): AsyncIterableIterator { const envInfo = buildEnvInfo({ kind: PythonEnvKind.Pyenv, executable: interpreterPath, - location: envDir, + location: subDirPath, version: pythonVersion, source: [PythonEnvSource.Pyenv], display, org, fileInfo, }); - envInfo.name = subDir; + envInfo.name = envDirName; yield envInfo; }