bpo-38133: Allow py.exe launcher to locate installations from the Microsoft Store (GH-16025)
This commit is contained in:
parent
2b2ead7438
commit
ed93a8852d
|
@ -0,0 +1,2 @@
|
|||
Allow py.exe launcher to locate installations from the Microsoft Store and
|
||||
improve display of active virtual environments.
|
310
PC/launcher.c
310
PC/launcher.c
|
@ -162,12 +162,13 @@ static wchar_t * get_env(wchar_t * key)
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#define MAX_VERSION_SIZE 4
|
||||
#define MAX_VERSION_SIZE 8
|
||||
|
||||
typedef struct {
|
||||
wchar_t version[MAX_VERSION_SIZE]; /* m.n */
|
||||
int bits; /* 32 or 64 */
|
||||
wchar_t executable[MAX_PATH];
|
||||
wchar_t exe_display[MAX_PATH];
|
||||
} INSTALLED_PYTHON;
|
||||
|
||||
/*
|
||||
|
@ -185,10 +186,18 @@ static size_t num_installed_pythons = 0;
|
|||
* The version name can be longer than MAX_VERSION_SIZE, but will be
|
||||
* truncated to just X.Y for comparisons.
|
||||
*/
|
||||
#define IP_BASE_SIZE 40
|
||||
#define IP_BASE_SIZE 80
|
||||
#define IP_VERSION_SIZE 8
|
||||
#define IP_SIZE (IP_BASE_SIZE + IP_VERSION_SIZE)
|
||||
#define CORE_PATH L"SOFTWARE\\Python\\PythonCore"
|
||||
/*
|
||||
* Installations from the Microsoft Store will set the same registry keys,
|
||||
* but because of a limitation in Windows they cannot be enumerated normally
|
||||
* (unless you have no other Python installations... which is probably false
|
||||
* because that's the most likely way to get this launcher!)
|
||||
* This key is under HKEY_LOCAL_MACHINE
|
||||
*/
|
||||
#define LOOKASIDE_PATH L"SOFTWARE\\Microsoft\\AppModel\\Lookaside\\user\\Software\\Python\\PythonCore"
|
||||
|
||||
static wchar_t * location_checks[] = {
|
||||
L"\\",
|
||||
|
@ -201,7 +210,7 @@ static wchar_t * location_checks[] = {
|
|||
};
|
||||
|
||||
static INSTALLED_PYTHON *
|
||||
find_existing_python(wchar_t * path)
|
||||
find_existing_python(const wchar_t * path)
|
||||
{
|
||||
INSTALLED_PYTHON * result = NULL;
|
||||
size_t i;
|
||||
|
@ -216,15 +225,32 @@ find_existing_python(wchar_t * path)
|
|||
return result;
|
||||
}
|
||||
|
||||
static INSTALLED_PYTHON *
|
||||
find_existing_python2(int bits, const wchar_t * version)
|
||||
{
|
||||
INSTALLED_PYTHON * result = NULL;
|
||||
size_t i;
|
||||
INSTALLED_PYTHON * ip;
|
||||
|
||||
for (i = 0, ip = installed_pythons; i < num_installed_pythons; i++, ip++) {
|
||||
if (bits == ip->bits && _wcsicmp(version, ip->version) == 0) {
|
||||
result = ip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
locate_pythons_for_key(HKEY root, REGSAM flags)
|
||||
_locate_pythons_for_key(HKEY root, LPCWSTR subkey, REGSAM flags, int bits,
|
||||
int display_name_only)
|
||||
{
|
||||
HKEY core_root, ip_key;
|
||||
LSTATUS status = RegOpenKeyExW(root, CORE_PATH, 0, flags, &core_root);
|
||||
LSTATUS status = RegOpenKeyExW(root, subkey, 0, flags, &core_root);
|
||||
wchar_t message[MSGSIZE];
|
||||
DWORD i;
|
||||
size_t n;
|
||||
BOOL ok;
|
||||
BOOL ok, append_name;
|
||||
DWORD type, data_size, attrs;
|
||||
INSTALLED_PYTHON * ip, * pip;
|
||||
wchar_t ip_version[IP_VERSION_SIZE];
|
||||
|
@ -252,8 +278,16 @@ locate_pythons_for_key(HKEY root, REGSAM flags)
|
|||
else {
|
||||
wcsncpy_s(ip->version, MAX_VERSION_SIZE, ip_version,
|
||||
MAX_VERSION_SIZE-1);
|
||||
/* Still treating version as "x.y" rather than sys.winver
|
||||
* When PEP 514 tags are properly used, we shouldn't need
|
||||
* to strip this off here.
|
||||
*/
|
||||
check = wcsrchr(ip->version, L'-');
|
||||
if (check && !wcscmp(check, L"-32")) {
|
||||
*check = L'\0';
|
||||
}
|
||||
_snwprintf_s(ip_path, IP_SIZE, _TRUNCATE,
|
||||
L"%ls\\%ls\\InstallPath", CORE_PATH, ip_version);
|
||||
L"%ls\\%ls\\InstallPath", subkey, ip_version);
|
||||
status = RegOpenKeyExW(root, ip_path, 0, flags, &ip_key);
|
||||
if (status != ERROR_SUCCESS) {
|
||||
winerror(status, message, MSGSIZE);
|
||||
|
@ -262,42 +296,57 @@ locate_pythons_for_key(HKEY root, REGSAM flags)
|
|||
continue;
|
||||
}
|
||||
data_size = sizeof(ip->executable) - 1;
|
||||
status = RegQueryValueExW(ip_key, NULL, NULL, &type,
|
||||
append_name = FALSE;
|
||||
status = RegQueryValueExW(ip_key, L"ExecutablePath", NULL, &type,
|
||||
(LPBYTE)ip->executable, &data_size);
|
||||
if (status != ERROR_SUCCESS || type != REG_SZ || !data_size) {
|
||||
append_name = TRUE;
|
||||
data_size = sizeof(ip->executable) - 1;
|
||||
status = RegQueryValueExW(ip_key, NULL, NULL, &type,
|
||||
(LPBYTE)ip->executable, &data_size);
|
||||
if (status != ERROR_SUCCESS) {
|
||||
winerror(status, message, MSGSIZE);
|
||||
debug(L"%ls\\%ls: %ls\n", key_name, ip_path, message);
|
||||
RegCloseKey(ip_key);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
RegCloseKey(ip_key);
|
||||
if (status != ERROR_SUCCESS) {
|
||||
winerror(status, message, MSGSIZE);
|
||||
debug(L"%ls\\%ls: %ls\n", key_name, ip_path, message);
|
||||
if (type != REG_SZ) {
|
||||
continue;
|
||||
}
|
||||
if (type == REG_SZ) {
|
||||
data_size = data_size / sizeof(wchar_t) - 1; /* for NUL */
|
||||
if (ip->executable[data_size - 1] == L'\\')
|
||||
--data_size; /* reg value ended in a backslash */
|
||||
/* ip->executable is data_size long */
|
||||
for (checkp = location_checks; *checkp; ++checkp) {
|
||||
check = *checkp;
|
||||
|
||||
data_size = data_size / sizeof(wchar_t) - 1; /* for NUL */
|
||||
if (ip->executable[data_size - 1] == L'\\')
|
||||
--data_size; /* reg value ended in a backslash */
|
||||
/* ip->executable is data_size long */
|
||||
for (checkp = location_checks; *checkp; ++checkp) {
|
||||
check = *checkp;
|
||||
if (append_name) {
|
||||
_snwprintf_s(&ip->executable[data_size],
|
||||
MAX_PATH - data_size,
|
||||
MAX_PATH - data_size,
|
||||
L"%ls%ls", check, PYTHON_EXECUTABLE);
|
||||
attrs = GetFileAttributesW(ip->executable);
|
||||
if (attrs == INVALID_FILE_ATTRIBUTES) {
|
||||
winerror(GetLastError(), message, MSGSIZE);
|
||||
debug(L"locate_pythons_for_key: %ls: %ls",
|
||||
ip->executable, message);
|
||||
}
|
||||
else if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
debug(L"locate_pythons_for_key: '%ls' is a \
|
||||
directory\n",
|
||||
ip->executable, attrs);
|
||||
}
|
||||
else if (find_existing_python(ip->executable)) {
|
||||
debug(L"locate_pythons_for_key: %ls: already \
|
||||
found\n", ip->executable);
|
||||
}
|
||||
else {
|
||||
/* check the executable type. */
|
||||
}
|
||||
attrs = GetFileAttributesW(ip->executable);
|
||||
if (attrs == INVALID_FILE_ATTRIBUTES) {
|
||||
winerror(GetLastError(), message, MSGSIZE);
|
||||
debug(L"locate_pythons_for_key: %ls: %ls",
|
||||
ip->executable, message);
|
||||
}
|
||||
else if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
debug(L"locate_pythons_for_key: '%ls' is a directory\n",
|
||||
ip->executable, attrs);
|
||||
}
|
||||
else if (find_existing_python(ip->executable)) {
|
||||
debug(L"locate_pythons_for_key: %ls: already found\n",
|
||||
ip->executable);
|
||||
}
|
||||
else {
|
||||
/* check the executable type. */
|
||||
if (bits) {
|
||||
ip->bits = bits;
|
||||
} else {
|
||||
ok = GetBinaryTypeW(ip->executable, &attrs);
|
||||
if (!ok) {
|
||||
debug(L"Failure getting binary type: %ls\n",
|
||||
|
@ -310,32 +359,48 @@ found\n", ip->executable);
|
|||
ip->bits = 32;
|
||||
else
|
||||
ip->bits = 0;
|
||||
if (ip->bits == 0) {
|
||||
debug(L"locate_pythons_for_key: %ls: \
|
||||
}
|
||||
}
|
||||
if (ip->bits == 0) {
|
||||
debug(L"locate_pythons_for_key: %ls: \
|
||||
invalid binary type: %X\n",
|
||||
ip->executable, attrs);
|
||||
ip->executable, attrs);
|
||||
}
|
||||
else {
|
||||
if (display_name_only) {
|
||||
/* display just the executable name. This is
|
||||
* primarily for the Store installs */
|
||||
const wchar_t *name = wcsrchr(ip->executable, L'\\');
|
||||
if (name) {
|
||||
wcscpy_s(ip->exe_display, MAX_PATH, name+1);
|
||||
}
|
||||
else {
|
||||
if (wcschr(ip->executable, L' ') != NULL) {
|
||||
/* has spaces, so quote */
|
||||
n = wcslen(ip->executable);
|
||||
memmove(&ip->executable[1],
|
||||
ip->executable, n * sizeof(wchar_t));
|
||||
ip->executable[0] = L'\"';
|
||||
ip->executable[n + 1] = L'\"';
|
||||
ip->executable[n + 2] = L'\0';
|
||||
}
|
||||
debug(L"locate_pythons_for_key: %ls \
|
||||
}
|
||||
if (wcschr(ip->executable, L' ') != NULL) {
|
||||
/* has spaces, so quote, and set original as
|
||||
* the display name */
|
||||
if (!ip->exe_display[0]) {
|
||||
wcscpy_s(ip->exe_display, MAX_PATH, ip->executable);
|
||||
}
|
||||
n = wcslen(ip->executable);
|
||||
memmove(&ip->executable[1],
|
||||
ip->executable, n * sizeof(wchar_t));
|
||||
ip->executable[0] = L'\"';
|
||||
ip->executable[n + 1] = L'\"';
|
||||
ip->executable[n + 2] = L'\0';
|
||||
}
|
||||
debug(L"locate_pythons_for_key: %ls \
|
||||
is a %dbit executable\n",
|
||||
ip->executable, ip->bits);
|
||||
++num_installed_pythons;
|
||||
pip = ip++;
|
||||
if (num_installed_pythons >=
|
||||
MAX_INSTALLED_PYTHONS)
|
||||
break;
|
||||
/* Copy over the attributes for the next */
|
||||
*ip = *pip;
|
||||
}
|
||||
ip->executable, ip->bits);
|
||||
if (find_existing_python2(ip->bits, ip->version)) {
|
||||
debug(L"locate_pythons_for_key: %ls-%i: already \
|
||||
found\n", ip->version, ip->bits);
|
||||
}
|
||||
else {
|
||||
++num_installed_pythons;
|
||||
pip = ip++;
|
||||
if (num_installed_pythons >=
|
||||
MAX_INSTALLED_PYTHONS)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -359,9 +424,63 @@ compare_pythons(const void * p1, const void * p2)
|
|||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
locate_pythons_for_key(HKEY root, REGSAM flags)
|
||||
{
|
||||
_locate_pythons_for_key(root, CORE_PATH, flags, 0, FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
locate_store_pythons()
|
||||
{
|
||||
#if defined(_M_X64)
|
||||
/* 64bit process, so look in native registry */
|
||||
_locate_pythons_for_key(HKEY_LOCAL_MACHINE, LOOKASIDE_PATH,
|
||||
KEY_READ, 64, TRUE);
|
||||
#else
|
||||
/* 32bit process, so check that we're on 64bit OS */
|
||||
BOOL f64 = FALSE;
|
||||
if (IsWow64Process(GetCurrentProcess(), &f64) && f64) {
|
||||
_locate_pythons_for_key(HKEY_LOCAL_MACHINE, LOOKASIDE_PATH,
|
||||
KEY_READ | KEY_WOW64_64KEY, 64, TRUE);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
locate_venv_python()
|
||||
{
|
||||
static wchar_t venv_python[MAX_PATH];
|
||||
INSTALLED_PYTHON * ip;
|
||||
wchar_t *virtual_env = get_env(L"VIRTUAL_ENV");
|
||||
DWORD attrs;
|
||||
|
||||
/* Check for VIRTUAL_ENV environment variable */
|
||||
if (virtual_env == NULL || virtual_env[0] == L'\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check for a python executable in the venv */
|
||||
debug(L"Checking for Python executable in virtual env '%ls'\n", virtual_env);
|
||||
_snwprintf_s(venv_python, MAX_PATH, _TRUNCATE,
|
||||
L"%ls\\Scripts\\%ls", virtual_env, PYTHON_EXECUTABLE);
|
||||
attrs = GetFileAttributesW(venv_python);
|
||||
if (attrs == INVALID_FILE_ATTRIBUTES) {
|
||||
debug(L"Python executable %ls missing from virtual env\n", venv_python);
|
||||
return;
|
||||
}
|
||||
|
||||
ip = &installed_pythons[num_installed_pythons++];
|
||||
wcscpy_s(ip->executable, MAX_PATH, venv_python);
|
||||
ip->bits = 0;
|
||||
wcscpy_s(ip->version, MAX_VERSION_SIZE, L"venv");
|
||||
}
|
||||
|
||||
static void
|
||||
locate_all_pythons()
|
||||
{
|
||||
/* venv Python is highest priority */
|
||||
locate_venv_python();
|
||||
#if defined(_M_X64)
|
||||
/* If we are a 64bit process, first hit the 32bit keys. */
|
||||
debug(L"locating Pythons in 32bit registry\n");
|
||||
|
@ -380,6 +499,8 @@ locate_all_pythons()
|
|||
debug(L"locating Pythons in native registry\n");
|
||||
locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ);
|
||||
locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ);
|
||||
/* Store-installed Python is lowest priority */
|
||||
locate_store_pythons();
|
||||
qsort(installed_pythons, num_installed_pythons, sizeof(INSTALLED_PYTHON),
|
||||
compare_pythons);
|
||||
}
|
||||
|
@ -416,31 +537,6 @@ find_python_by_version(wchar_t const * wanted_ver)
|
|||
}
|
||||
|
||||
|
||||
static wchar_t *
|
||||
find_python_by_venv()
|
||||
{
|
||||
static wchar_t venv_python[MAX_PATH];
|
||||
wchar_t *virtual_env = get_env(L"VIRTUAL_ENV");
|
||||
DWORD attrs;
|
||||
|
||||
/* Check for VIRTUAL_ENV environment variable */
|
||||
if (virtual_env == NULL || virtual_env[0] == L'\0') {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Check for a python executable in the venv */
|
||||
debug(L"Checking for Python executable in virtual env '%ls'\n", virtual_env);
|
||||
_snwprintf_s(venv_python, MAX_PATH, _TRUNCATE,
|
||||
L"%ls\\Scripts\\%ls", virtual_env, PYTHON_EXECUTABLE);
|
||||
attrs = GetFileAttributesW(venv_python);
|
||||
if (attrs == INVALID_FILE_ATTRIBUTES) {
|
||||
debug(L"Python executable %ls missing from virtual env\n", venv_python);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return venv_python;
|
||||
}
|
||||
|
||||
static wchar_t appdata_ini_path[MAX_PATH];
|
||||
static wchar_t launcher_ini_path[MAX_PATH];
|
||||
|
||||
|
@ -523,9 +619,12 @@ locate_python(wchar_t * wanted_ver, BOOL from_shebang)
|
|||
}
|
||||
else {
|
||||
*last_char = L'\0'; /* look for an overall default */
|
||||
configured_value = get_configured_value(config_key);
|
||||
if (configured_value)
|
||||
result = find_python_by_version(configured_value);
|
||||
result = find_python_by_version(L"venv");
|
||||
if (result == NULL) {
|
||||
configured_value = get_configured_value(config_key);
|
||||
if (configured_value)
|
||||
result = find_python_by_version(configured_value);
|
||||
}
|
||||
/* Not found a value yet - try by major version.
|
||||
* If we're looking for an interpreter specified in a shebang line,
|
||||
* we want to try Python 2 first, then Python 3 (for Unix and backward
|
||||
|
@ -1443,7 +1542,8 @@ show_python_list(wchar_t ** argv)
|
|||
INSTALLED_PYTHON * defpy = locate_python(L"", FALSE);
|
||||
size_t i = 0;
|
||||
wchar_t *p = argv[1];
|
||||
wchar_t *fmt = L"\n -%ls-%d"; /* print VER-BITS */
|
||||
wchar_t *ver_fmt = L"-%ls-%d";
|
||||
wchar_t *fmt = L"\n %ls";
|
||||
wchar_t *defind = L" *"; /* Default indicator */
|
||||
|
||||
/*
|
||||
|
@ -1452,8 +1552,8 @@ show_python_list(wchar_t ** argv)
|
|||
*/
|
||||
fwprintf(stderr,
|
||||
L"Installed Pythons found by %s Launcher for Windows", argv[0]);
|
||||
if (!_wcsicmp(p, L"-0p") || !_wcsicmp(p, L"--list-paths")) /* Show path? */
|
||||
fmt = L"\n -%ls-%d\t%ls"; /* print VER-BITS path */
|
||||
if (!_wcsicmp(p, L"-0p") || !_wcsicmp(p, L"--list-paths"))
|
||||
fmt = L"\n %-15ls%ls"; /* include path */
|
||||
|
||||
if (num_installed_pythons == 0) /* We have somehow got here without searching for pythons */
|
||||
locate_all_pythons(); /* Find them, Populates installed_pythons */
|
||||
|
@ -1463,9 +1563,22 @@ show_python_list(wchar_t ** argv)
|
|||
else
|
||||
{
|
||||
for (i = 0; i < num_installed_pythons; i++, ip++) {
|
||||
fwprintf(stdout, fmt, ip->version, ip->bits, ip->executable);
|
||||
wchar_t version[BUFSIZ];
|
||||
if (wcscmp(ip->version, L"venv") == 0) {
|
||||
wcscpy_s(version, BUFSIZ, L"(venv)");
|
||||
}
|
||||
else {
|
||||
swprintf_s(version, BUFSIZ, ver_fmt, ip->version, ip->bits);
|
||||
}
|
||||
|
||||
if (ip->exe_display[0]) {
|
||||
fwprintf(stdout, fmt, version, ip->exe_display);
|
||||
}
|
||||
else {
|
||||
fwprintf(stdout, fmt, version, ip->executable);
|
||||
}
|
||||
/* If there is a default indicate it */
|
||||
if ((defpy != NULL) && !_wcsicmp(ip->executable, defpy->executable))
|
||||
if (defpy == ip)
|
||||
fwprintf(stderr, defind);
|
||||
}
|
||||
}
|
||||
|
@ -1850,16 +1963,11 @@ installed, use -0 for available pythons", &p[1]);
|
|||
executable = NULL; /* Info call only */
|
||||
}
|
||||
else {
|
||||
/* Look for an active virtualenv */
|
||||
executable = find_python_by_venv();
|
||||
|
||||
/* If we didn't find one, look for the default Python */
|
||||
if (executable == NULL) {
|
||||
ip = locate_python(L"", FALSE);
|
||||
if (ip == NULL)
|
||||
error(RC_NO_PYTHON, L"Can't find a default Python.");
|
||||
executable = ip->executable;
|
||||
}
|
||||
/* look for the default Python */
|
||||
ip = locate_python(L"", FALSE);
|
||||
if (ip == NULL)
|
||||
error(RC_NO_PYTHON, L"Can't find a default Python.");
|
||||
executable = ip->executable;
|
||||
}
|
||||
}
|
||||
if (executable != NULL)
|
||||
|
|
Loading…
Reference in New Issue