forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add APIs needed by windows store locator (#13740)
* Initial commit * Add tests
- Loading branch information
1 parent
fb797a3
commit f41bef9
Showing
18 changed files
with
224 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import * as path from 'path'; | ||
|
||
/** | ||
* Checks if a given path ends with python*.exe | ||
* @param {string} interpreterPath : Path to python interpreter. | ||
* @returns {boolean} : Returns true if the path matches pattern for windows python executable. | ||
*/ | ||
export function isWindowsPythonExe(interpreterPath:string): boolean { | ||
/** | ||
* This Reg-ex matches following file names: | ||
* python.exe | ||
* python3.exe | ||
* python38.exe | ||
* python3.8.exe | ||
*/ | ||
const windowsPythonExes = /^python(\d+(.\d+)?)?\.exe$/; | ||
|
||
return windowsPythonExes.test(path.basename(interpreterPath)); | ||
} |
111 changes: 111 additions & 0 deletions
111
src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import * as fsapi from 'fs-extra'; | ||
import * as path from 'path'; | ||
import { traceWarning } from '../../../../common/logger'; | ||
import { getEnvironmentVariable } from '../../../../common/utils/platform'; | ||
import { isWindowsPythonExe } from '../../../common/windowsUtils'; | ||
|
||
/** | ||
* Gets path to the Windows Apps directory. | ||
* @returns {string} : Returns path to the Windows Apps directory under | ||
* `%LOCALAPPDATA%/Microsoft/WindowsApps`. | ||
*/ | ||
export function getWindowsStoreAppsRoot(): string { | ||
const localAppData = getEnvironmentVariable('LOCALAPPDATA') || ''; | ||
return path.join(localAppData, 'Microsoft', 'WindowsApps'); | ||
} | ||
|
||
/** | ||
* Checks if a given path is under the forbidden windows store directory. | ||
* @param {string} interpreterPath : Absolute path to the python interpreter. | ||
* @returns {boolean} : Returns true if `interpreterPath` is under | ||
* `%ProgramFiles%/WindowsApps`. | ||
*/ | ||
export function isForbiddenStorePath(interpreterPath:string):boolean { | ||
const programFilesStorePath = path | ||
.join(getEnvironmentVariable('ProgramFiles') || 'Program Files', 'WindowsApps') | ||
.normalize() | ||
.toUpperCase(); | ||
return path.normalize(interpreterPath).toUpperCase().includes(programFilesStorePath); | ||
} | ||
|
||
/** | ||
* Checks if the given interpreter belongs to Windows Store Python environment. | ||
* @param interpreterPath: Absolute path to any python interpreter. | ||
* | ||
* Remarks: | ||
* 1. Checking if the path includes `Microsoft\WindowsApps`, `Program Files\WindowsApps`, is | ||
* NOT enough. In WSL, `/mnt/c/users/user/AppData/Local/Microsoft/WindowsApps` is available as a search | ||
* path. It is possible to get a false positive for that path. So the comparison should check if the | ||
* absolute path to 'WindowsApps' directory is present in the given interpreter path. The WSL path to | ||
* 'WindowsApps' is not a valid path to access, Windows Store Python. | ||
* | ||
* 2. 'startsWith' comparison may not be right, user can provide '\\?\C:\users\' style long paths in windows. | ||
* | ||
* 3. A limitation of the checks here is that they don't handle 8.3 style windows paths. | ||
* For example, | ||
* `C:\Users\USER\AppData\Local\MICROS~1\WINDOW~1\PYTHON~2.EXE` | ||
* is the shortened form of | ||
* `C:\Users\USER\AppData\Local\Microsoft\WindowsApps\python3.7.exe` | ||
* | ||
* The correct way to compare these would be to always convert given paths to long path (or to short path). | ||
* For either approach to work correctly you need actual file to exist, and accessible from the user's | ||
* account. | ||
* | ||
* To convert to short path without using N-API in node would be to use this command. This is very expensive: | ||
* `> cmd /c for %A in ("C:\Users\USER\AppData\Local\Microsoft\WindowsApps\python3.7.exe") do @echo %~sA` | ||
* The above command will print out this: | ||
* `C:\Users\USER\AppData\Local\MICROS~1\WINDOW~1\PYTHON~2.EXE` | ||
* | ||
* If we go down the N-API route, use node-ffi and either call GetShortPathNameW or GetLongPathNameW from, | ||
* Kernel32 to convert between the two path variants. | ||
* | ||
*/ | ||
export async function isWindowsStoreEnvironment(interpreterPath: string): Promise<boolean> { | ||
const pythonPathToCompare = path.normalize(interpreterPath).toUpperCase(); | ||
const localAppDataStorePath = path | ||
.normalize(getWindowsStoreAppsRoot()) | ||
.toUpperCase(); | ||
if (pythonPathToCompare.includes(localAppDataStorePath)) { | ||
return true; | ||
} | ||
|
||
// Program Files store path is a forbidden path. Only admins and system has access this path. | ||
// We should never have to look at this path or even execute python from this path. | ||
if (isForbiddenStorePath(pythonPathToCompare)) { | ||
traceWarning('isWindowsStoreEnvironment called with Program Files store path.'); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Gets paths to the Python executable under Windows Store apps. | ||
* @returns: Returns python*.exe for the windows store app root directory. | ||
* | ||
* Remarks: We don't need to find the path to the interpreter under the specific application | ||
* directory. Such as: | ||
* `%LOCALAPPDATA%/Microsoft/WindowsApps/PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0` | ||
* The same python executable is also available at: | ||
* `%LOCALAPPDATA%/Microsoft/WindowsApps` | ||
* It would be a duplicate. | ||
* | ||
* All python executable under `%LOCALAPPDATA%/Microsoft/WindowsApps` or the sub-directories | ||
* are 'reparse points' that point to the real executable at `%PROGRAMFILES%/WindowsApps`. | ||
* However, that directory is off limits to users. So no need to populate interpreters from | ||
* that location. | ||
*/ | ||
export async function getWindowsStorePythonExes(): Promise<string[]> { | ||
const windowsAppsRoot = getWindowsStoreAppsRoot(); | ||
|
||
// Collect python*.exe directly under %LOCALAPPDATA%/Microsoft/WindowsApps | ||
const files = await fsapi.readdir(windowsAppsRoot); | ||
return files | ||
.map((filename:string) => path.join(windowsAppsRoot, filename)) | ||
.filter(isWindowsPythonExe); | ||
} | ||
|
||
// tslint:disable-next-line: no-suspicious-comment | ||
// TODO: The above APIs will be consumed by the Windows Store locator class when we have it. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import * as path from 'path'; | ||
|
||
export const TEST_LAYOUT_ROOT = path.join( | ||
__dirname, | ||
'..', | ||
'..', | ||
'..', | ||
'..', | ||
'src', | ||
'test', | ||
'pythonEnvironments', | ||
'common', | ||
'envlayouts', | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
...reApps/Microsoft/WindowsApps/PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0/python.exe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Not a real exe. |
1 change: 1 addition & 0 deletions
1
...pps/Microsoft/WindowsApps/PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0/python3.7.exe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Not a real exe. |
1 change: 1 addition & 0 deletions
1
...eApps/Microsoft/WindowsApps/PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0/python3.exe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Not a real exe. |
1 change: 1 addition & 0 deletions
1
...reApps/Microsoft/WindowsApps/PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0/python.exe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Not a real exe. |
1 change: 1 addition & 0 deletions
1
...pps/Microsoft/WindowsApps/PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0/python3.8.exe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Not a real exe. |
1 change: 1 addition & 0 deletions
1
...eApps/Microsoft/WindowsApps/PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0/python3.exe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Not a real exe. |
1 change: 1 addition & 0 deletions
1
src/test/pythonEnvironments/common/envlayouts/storeApps/Microsoft/WindowsApps/idle.exe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Not a real exe. |
1 change: 1 addition & 0 deletions
1
src/test/pythonEnvironments/common/envlayouts/storeApps/Microsoft/WindowsApps/python.exe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Not a real exe. |
1 change: 1 addition & 0 deletions
1
src/test/pythonEnvironments/common/envlayouts/storeApps/Microsoft/WindowsApps/python3.7.exe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Not a real exe. |
1 change: 1 addition & 0 deletions
1
src/test/pythonEnvironments/common/envlayouts/storeApps/Microsoft/WindowsApps/python3.8.exe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Not a real exe. |
1 change: 1 addition & 0 deletions
1
src/test/pythonEnvironments/common/envlayouts/storeApps/Microsoft/WindowsApps/python3.exe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Not a real exe. |
29 changes: 29 additions & 0 deletions
29
src/test/pythonEnvironments/common/windowsUtils.unit.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import * as assert from 'assert'; | ||
import { isWindowsPythonExe } from '../../../client/pythonEnvironments/common/windowsUtils'; | ||
|
||
suite('Windows Utils tests', () => { | ||
const testParams = [ | ||
{ path: 'python.exe', expected: true }, | ||
{ path: 'python3.exe', expected: true }, | ||
{ path: 'python38.exe', expected: true }, | ||
{ path: 'python3.8.exe', expected: true }, | ||
{ path: 'python', expected: false }, | ||
{ path: 'python3', expected: false }, | ||
{ path: 'python38', expected: false }, | ||
{ path: 'python3.8', expected: false }, | ||
{ path: 'idle.exe', expected: false }, | ||
{ path: 'pip.exe', expected: false }, | ||
{ path: 'python.dll', expected: false }, | ||
{ path: 'python3.dll', expected: false }, | ||
{ path: 'python3.8.dll', expected: false }, | ||
]; | ||
|
||
testParams.forEach((testParam) => { | ||
test(`Python executable check ${testParam.expected ? 'should match' : 'should not match'} this path: ${testParam.path}`, () => { | ||
assert.deepEqual(isWindowsPythonExe(testParam.path), testParam.expected); | ||
}); | ||
}); | ||
}); |
33 changes: 33 additions & 0 deletions
33
src/test/pythonEnvironments/discovery/locators/windowsStoreLocator.unit.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import * as assert from 'assert'; | ||
import * as path from 'path'; | ||
import * as sinon from 'sinon'; | ||
import * as platformApis from '../../../../client/common/utils/platform'; | ||
import * as storeApis from '../../../../client/pythonEnvironments/discovery/locators/services/windowsStoreLocator'; | ||
import { TEST_LAYOUT_ROOT } from '../../common/commonTestConstants'; | ||
|
||
suite('Windows Store Utils', () => { | ||
let getEnvVar: sinon.SinonStub; | ||
const testLocalAppData = path.join(TEST_LAYOUT_ROOT, 'storeApps'); | ||
const testStoreAppRoot = path.join(testLocalAppData, 'Microsoft', 'WindowsApps'); | ||
setup(() => { | ||
getEnvVar = sinon.stub(platformApis, 'getEnvironmentVariable'); | ||
getEnvVar.withArgs('LOCALAPPDATA').returns(testLocalAppData); | ||
}); | ||
teardown(() => { | ||
getEnvVar.restore(); | ||
}); | ||
test('Store Python Interpreters', async () => { | ||
const expected = [ | ||
path.join(testStoreAppRoot, 'python.exe'), | ||
path.join(testStoreAppRoot, 'python3.7.exe'), | ||
path.join(testStoreAppRoot, 'python3.8.exe'), | ||
path.join(testStoreAppRoot, 'python3.exe'), | ||
]; | ||
|
||
const actual = await storeApis.getWindowsStorePythonExes(); | ||
assert.deepEqual(actual, expected); | ||
}); | ||
}); |