Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/PC/launcher2.c
12 views
1
/*
2
* Rewritten Python launcher for Windows
3
*
4
* This new rewrite properly handles PEP 514 and allows any registered Python
5
* runtime to be launched. It also enables auto-install of versions when they
6
* are requested but no installation can be found.
7
*/
8
9
#define __STDC_WANT_LIB_EXT1__ 1
10
11
#include <windows.h>
12
#include <pathcch.h>
13
#include <fcntl.h>
14
#include <io.h>
15
#include <shlobj.h>
16
#include <stdio.h>
17
#include <stdbool.h>
18
#include <tchar.h>
19
#include <assert.h>
20
21
#define MS_WINDOWS
22
#include "patchlevel.h"
23
24
#define MAXLEN PATHCCH_MAX_CCH
25
#define MSGSIZE 1024
26
27
#define RC_NO_STD_HANDLES 100
28
#define RC_CREATE_PROCESS 101
29
#define RC_BAD_VIRTUAL_PATH 102
30
#define RC_NO_PYTHON 103
31
#define RC_NO_MEMORY 104
32
#define RC_NO_SCRIPT 105
33
#define RC_NO_VENV_CFG 106
34
#define RC_BAD_VENV_CFG 107
35
#define RC_NO_COMMANDLINE 108
36
#define RC_INTERNAL_ERROR 109
37
#define RC_DUPLICATE_ITEM 110
38
#define RC_INSTALLING 111
39
#define RC_NO_PYTHON_AT_ALL 112
40
#define RC_NO_SHEBANG 113
41
#define RC_RECURSIVE_SHEBANG 114
42
43
static FILE * log_fp = NULL;
44
45
void
46
debug(wchar_t * format, ...)
47
{
48
va_list va;
49
50
if (log_fp != NULL) {
51
wchar_t buffer[MAXLEN];
52
int r = 0;
53
va_start(va, format);
54
r = vswprintf_s(buffer, MAXLEN, format, va);
55
va_end(va);
56
57
if (r <= 0) {
58
return;
59
}
60
fputws(buffer, log_fp);
61
while (r && isspace(buffer[r])) {
62
buffer[r--] = L'\0';
63
}
64
if (buffer[0]) {
65
OutputDebugStringW(buffer);
66
}
67
}
68
}
69
70
71
void
72
formatWinerror(int rc, wchar_t * message, int size)
73
{
74
FormatMessageW(
75
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
76
NULL, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
77
message, size, NULL);
78
}
79
80
81
void
82
winerror(int err, wchar_t * format, ... )
83
{
84
va_list va;
85
wchar_t message[MSGSIZE];
86
wchar_t win_message[MSGSIZE];
87
int len;
88
89
if (err == 0) {
90
err = GetLastError();
91
}
92
93
va_start(va, format);
94
len = _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va);
95
va_end(va);
96
97
formatWinerror(err, win_message, MSGSIZE);
98
if (len >= 0) {
99
_snwprintf_s(&message[len], MSGSIZE - len, _TRUNCATE, L": %s",
100
win_message);
101
}
102
103
#if !defined(_WINDOWS)
104
fwprintf(stderr, L"%s\n", message);
105
#else
106
MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...",
107
MB_OK);
108
#endif
109
}
110
111
112
void
113
error(wchar_t * format, ... )
114
{
115
va_list va;
116
wchar_t message[MSGSIZE];
117
118
va_start(va, format);
119
_vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va);
120
va_end(va);
121
122
#if !defined(_WINDOWS)
123
fwprintf(stderr, L"%s\n", message);
124
#else
125
MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...",
126
MB_OK);
127
#endif
128
}
129
130
131
typedef BOOL (*PIsWow64Process2)(HANDLE, USHORT*, USHORT*);
132
133
134
USHORT
135
_getNativeMachine(void)
136
{
137
static USHORT _nativeMachine = IMAGE_FILE_MACHINE_UNKNOWN;
138
if (_nativeMachine == IMAGE_FILE_MACHINE_UNKNOWN) {
139
USHORT processMachine;
140
HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
141
PIsWow64Process2 IsWow64Process2 = kernel32 ?
142
(PIsWow64Process2)GetProcAddress(kernel32, "IsWow64Process2") :
143
NULL;
144
if (!IsWow64Process2) {
145
BOOL wow64Process;
146
if (!IsWow64Process(NULL, &wow64Process)) {
147
winerror(0, L"Checking process type");
148
} else if (wow64Process) {
149
// We should always be a 32-bit executable, so if running
150
// under emulation, it must be a 64-bit host.
151
_nativeMachine = IMAGE_FILE_MACHINE_AMD64;
152
} else {
153
// Not running under emulation, and an old enough OS to not
154
// have IsWow64Process2, so assume it's x86.
155
_nativeMachine = IMAGE_FILE_MACHINE_I386;
156
}
157
} else if (!IsWow64Process2(NULL, &processMachine, &_nativeMachine)) {
158
winerror(0, L"Checking process type");
159
}
160
}
161
return _nativeMachine;
162
}
163
164
165
bool
166
isAMD64Host(void)
167
{
168
return _getNativeMachine() == IMAGE_FILE_MACHINE_AMD64;
169
}
170
171
172
bool
173
isARM64Host(void)
174
{
175
return _getNativeMachine() == IMAGE_FILE_MACHINE_ARM64;
176
}
177
178
179
bool
180
isEnvVarSet(const wchar_t *name)
181
{
182
/* only looking for non-empty, which means at least one character
183
and the null terminator */
184
return GetEnvironmentVariableW(name, NULL, 0) >= 2;
185
}
186
187
188
bool
189
join(wchar_t *buffer, size_t bufferLength, const wchar_t *fragment)
190
{
191
if (SUCCEEDED(PathCchCombineEx(buffer, bufferLength, buffer, fragment, PATHCCH_ALLOW_LONG_PATHS))) {
192
return true;
193
}
194
return false;
195
}
196
197
198
int
199
_compare(const wchar_t *x, int xLen, const wchar_t *y, int yLen)
200
{
201
// Empty strings sort first
202
if (!x || !xLen) {
203
return (!y || !yLen) ? 0 : -1;
204
} else if (!y || !yLen) {
205
return 1;
206
}
207
switch (CompareStringEx(
208
LOCALE_NAME_INVARIANT, NORM_IGNORECASE | SORT_DIGITSASNUMBERS,
209
x, xLen, y, yLen,
210
NULL, NULL, 0
211
)) {
212
case CSTR_LESS_THAN:
213
return -1;
214
case CSTR_EQUAL:
215
return 0;
216
case CSTR_GREATER_THAN:
217
return 1;
218
default:
219
winerror(0, L"Error comparing '%.*s' and '%.*s' (compare)", xLen, x, yLen, y);
220
return -1;
221
}
222
}
223
224
225
int
226
_compareArgument(const wchar_t *x, int xLen, const wchar_t *y, int yLen)
227
{
228
// Empty strings sort first
229
if (!x || !xLen) {
230
return (!y || !yLen) ? 0 : -1;
231
} else if (!y || !yLen) {
232
return 1;
233
}
234
switch (CompareStringEx(
235
LOCALE_NAME_INVARIANT, 0,
236
x, xLen, y, yLen,
237
NULL, NULL, 0
238
)) {
239
case CSTR_LESS_THAN:
240
return -1;
241
case CSTR_EQUAL:
242
return 0;
243
case CSTR_GREATER_THAN:
244
return 1;
245
default:
246
winerror(0, L"Error comparing '%.*s' and '%.*s' (compareArgument)", xLen, x, yLen, y);
247
return -1;
248
}
249
}
250
251
int
252
_comparePath(const wchar_t *x, int xLen, const wchar_t *y, int yLen)
253
{
254
// Empty strings sort first
255
if (!x || !xLen) {
256
return !y || !yLen ? 0 : -1;
257
} else if (!y || !yLen) {
258
return 1;
259
}
260
switch (CompareStringOrdinal(x, xLen, y, yLen, TRUE)) {
261
case CSTR_LESS_THAN:
262
return -1;
263
case CSTR_EQUAL:
264
return 0;
265
case CSTR_GREATER_THAN:
266
return 1;
267
default:
268
winerror(0, L"Error comparing '%.*s' and '%.*s' (comparePath)", xLen, x, yLen, y);
269
return -1;
270
}
271
}
272
273
274
bool
275
_startsWith(const wchar_t *x, int xLen, const wchar_t *y, int yLen)
276
{
277
if (!x || !y) {
278
return false;
279
}
280
yLen = yLen < 0 ? (int)wcsnlen_s(y, MAXLEN) : yLen;
281
xLen = xLen < 0 ? (int)wcsnlen_s(x, MAXLEN) : xLen;
282
return xLen >= yLen && 0 == _compare(x, yLen, y, yLen);
283
}
284
285
286
bool
287
_startsWithArgument(const wchar_t *x, int xLen, const wchar_t *y, int yLen)
288
{
289
if (!x || !y) {
290
return false;
291
}
292
yLen = yLen < 0 ? (int)wcsnlen_s(y, MAXLEN) : yLen;
293
xLen = xLen < 0 ? (int)wcsnlen_s(x, MAXLEN) : xLen;
294
return xLen >= yLen && 0 == _compareArgument(x, yLen, y, yLen);
295
}
296
297
298
// Unlike regular startsWith, this function requires that the following
299
// character is either NULL (that is, the entire string matches) or is one of
300
// the characters in 'separators'.
301
bool
302
_startsWithSeparated(const wchar_t *x, int xLen, const wchar_t *y, int yLen, const wchar_t *separators)
303
{
304
if (!x || !y) {
305
return false;
306
}
307
yLen = yLen < 0 ? (int)wcsnlen_s(y, MAXLEN) : yLen;
308
xLen = xLen < 0 ? (int)wcsnlen_s(x, MAXLEN) : xLen;
309
if (xLen < yLen) {
310
return false;
311
}
312
if (xLen == yLen) {
313
return 0 == _compare(x, xLen, y, yLen);
314
}
315
return separators &&
316
0 == _compare(x, yLen, y, yLen) &&
317
wcschr(separators, x[yLen]) != NULL;
318
}
319
320
321
322
/******************************************************************************\
323
*** HELP TEXT ***
324
\******************************************************************************/
325
326
327
int
328
showHelpText(wchar_t ** argv)
329
{
330
// The help text is stored in launcher-usage.txt, which is compiled into
331
// the launcher and loaded at runtime if needed.
332
//
333
// The file must be UTF-8. There are two substitutions:
334
// %ls - PY_VERSION (as wchar_t*)
335
// %ls - argv[0] (as wchar_t*)
336
HRSRC res = FindResourceExW(NULL, L"USAGE", MAKEINTRESOURCE(1), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
337
HGLOBAL resData = res ? LoadResource(NULL, res) : NULL;
338
const char *usage = resData ? (const char*)LockResource(resData) : NULL;
339
if (usage == NULL) {
340
winerror(0, L"Unable to load usage text");
341
return RC_INTERNAL_ERROR;
342
}
343
344
DWORD cbData = SizeofResource(NULL, res);
345
DWORD cchUsage = MultiByteToWideChar(CP_UTF8, 0, usage, cbData, NULL, 0);
346
if (!cchUsage) {
347
winerror(0, L"Unable to preprocess usage text");
348
return RC_INTERNAL_ERROR;
349
}
350
351
cchUsage += 1;
352
wchar_t *wUsage = (wchar_t*)malloc(cchUsage * sizeof(wchar_t));
353
cchUsage = MultiByteToWideChar(CP_UTF8, 0, usage, cbData, wUsage, cchUsage);
354
if (!cchUsage) {
355
winerror(0, L"Unable to preprocess usage text");
356
free((void *)wUsage);
357
return RC_INTERNAL_ERROR;
358
}
359
// Ensure null termination
360
wUsage[cchUsage] = L'\0';
361
362
fwprintf(stdout, wUsage, (L"" PY_VERSION), argv[0]);
363
fflush(stdout);
364
365
free((void *)wUsage);
366
367
return 0;
368
}
369
370
371
/******************************************************************************\
372
*** SEARCH INFO ***
373
\******************************************************************************/
374
375
376
struct _SearchInfoBuffer {
377
struct _SearchInfoBuffer *next;
378
wchar_t buffer[0];
379
};
380
381
382
typedef struct {
383
// the original string, managed by the OS
384
const wchar_t *originalCmdLine;
385
// pointer into the cmdline to mark what we've consumed
386
const wchar_t *restOfCmdLine;
387
// if known/discovered, the full executable path of our runtime
388
const wchar_t *executablePath;
389
// pointer and length into cmdline for the file to check for a
390
// shebang line, if any. Length can be -1 if the string is null
391
// terminated.
392
const wchar_t *scriptFile;
393
int scriptFileLength;
394
// pointer and length into cmdline or a static string with the
395
// name of the target executable. Length can be -1 if the string
396
// is null terminated.
397
const wchar_t *executable;
398
int executableLength;
399
// pointer and length into a string with additional interpreter
400
// arguments to include before restOfCmdLine. Length can be -1 if
401
// the string is null terminated.
402
const wchar_t *executableArgs;
403
int executableArgsLength;
404
// pointer and length into cmdline or a static string with the
405
// company name for PEP 514 lookup. Length can be -1 if the string
406
// is null terminated.
407
const wchar_t *company;
408
int companyLength;
409
// pointer and length into cmdline or a static string with the
410
// tag for PEP 514 lookup. Length can be -1 if the string is
411
// null terminated.
412
const wchar_t *tag;
413
int tagLength;
414
// if true, treats 'tag' as a non-PEP 514 filter
415
bool oldStyleTag;
416
// if true, ignores 'tag' when a high priority environment is found
417
// gh-92817: This is currently set when a tag is read from configuration or
418
// the environment, rather than the command line or a shebang line, and the
419
// only currently possible high priority environment is an active virtual
420
// environment
421
bool lowPriorityTag;
422
// if true, allow PEP 514 lookup to override 'executable'
423
bool allowExecutableOverride;
424
// if true, allow a nearby pyvenv.cfg to locate the executable
425
bool allowPyvenvCfg;
426
// if true, allow defaults (env/py.ini) to clarify/override tags
427
bool allowDefaults;
428
// if true, prefer windowed (console-less) executable
429
bool windowed;
430
// if true, only list detected runtimes without launching
431
bool list;
432
// if true, only list detected runtimes with paths without launching
433
bool listPaths;
434
// if true, display help message before contiuning
435
bool help;
436
// if set, limits search to registry keys with the specified Company
437
// This is intended for debugging and testing only
438
const wchar_t *limitToCompany;
439
// dynamically allocated buffers to free later
440
struct _SearchInfoBuffer *_buffer;
441
} SearchInfo;
442
443
444
wchar_t *
445
allocSearchInfoBuffer(SearchInfo *search, int wcharCount)
446
{
447
struct _SearchInfoBuffer *buffer = (struct _SearchInfoBuffer*)malloc(
448
sizeof(struct _SearchInfoBuffer) +
449
wcharCount * sizeof(wchar_t)
450
);
451
if (!buffer) {
452
return NULL;
453
}
454
buffer->next = search->_buffer;
455
search->_buffer = buffer;
456
return buffer->buffer;
457
}
458
459
460
void
461
freeSearchInfo(SearchInfo *search)
462
{
463
struct _SearchInfoBuffer *b = search->_buffer;
464
search->_buffer = NULL;
465
while (b) {
466
struct _SearchInfoBuffer *nextB = b->next;
467
free((void *)b);
468
b = nextB;
469
}
470
}
471
472
473
void
474
_debugStringAndLength(const wchar_t *s, int len, const wchar_t *name)
475
{
476
if (!s) {
477
debug(L"%s: (null)\n", name);
478
} else if (len == 0) {
479
debug(L"%s: (empty)\n", name);
480
} else if (len < 0) {
481
debug(L"%s: %s\n", name, s);
482
} else {
483
debug(L"%s: %.*ls\n", name, len, s);
484
}
485
}
486
487
488
void
489
dumpSearchInfo(SearchInfo *search)
490
{
491
if (!log_fp) {
492
return;
493
}
494
495
#ifdef __clang__
496
#define DEBUGNAME(s) L # s
497
#else
498
#define DEBUGNAME(s) # s
499
#endif
500
#define DEBUG(s) debug(L"SearchInfo." DEBUGNAME(s) L": %s\n", (search->s) ? (search->s) : L"(null)")
501
#define DEBUG_2(s, sl) _debugStringAndLength((search->s), (search->sl), L"SearchInfo." DEBUGNAME(s))
502
#define DEBUG_BOOL(s) debug(L"SearchInfo." DEBUGNAME(s) L": %s\n", (search->s) ? L"True" : L"False")
503
DEBUG(originalCmdLine);
504
DEBUG(restOfCmdLine);
505
DEBUG(executablePath);
506
DEBUG_2(scriptFile, scriptFileLength);
507
DEBUG_2(executable, executableLength);
508
DEBUG_2(executableArgs, executableArgsLength);
509
DEBUG_2(company, companyLength);
510
DEBUG_2(tag, tagLength);
511
DEBUG_BOOL(oldStyleTag);
512
DEBUG_BOOL(lowPriorityTag);
513
DEBUG_BOOL(allowDefaults);
514
DEBUG_BOOL(allowExecutableOverride);
515
DEBUG_BOOL(windowed);
516
DEBUG_BOOL(list);
517
DEBUG_BOOL(listPaths);
518
DEBUG_BOOL(help);
519
DEBUG(limitToCompany);
520
#undef DEBUG_BOOL
521
#undef DEBUG_2
522
#undef DEBUG
523
#undef DEBUGNAME
524
}
525
526
527
int
528
findArgv0Length(const wchar_t *buffer, int bufferLength)
529
{
530
// Note: this implements semantics that are only valid for argv0.
531
// Specifically, there is no escaping of quotes, and quotes within
532
// the argument have no effect. A quoted argv0 must start and end
533
// with a double quote character; otherwise, it ends at the first
534
// ' ' or '\t'.
535
int quoted = buffer[0] == L'"';
536
for (int i = 1; bufferLength < 0 || i < bufferLength; ++i) {
537
switch (buffer[i]) {
538
case L'\0':
539
return i;
540
case L' ':
541
case L'\t':
542
if (!quoted) {
543
return i;
544
}
545
break;
546
case L'"':
547
if (quoted) {
548
return i + 1;
549
}
550
break;
551
}
552
}
553
return bufferLength;
554
}
555
556
557
const wchar_t *
558
findArgv0End(const wchar_t *buffer, int bufferLength)
559
{
560
return &buffer[findArgv0Length(buffer, bufferLength)];
561
}
562
563
564
/******************************************************************************\
565
*** COMMAND-LINE PARSING ***
566
\******************************************************************************/
567
568
569
int
570
parseCommandLine(SearchInfo *search)
571
{
572
if (!search || !search->originalCmdLine) {
573
return RC_NO_COMMANDLINE;
574
}
575
576
const wchar_t *argv0End = findArgv0End(search->originalCmdLine, -1);
577
const wchar_t *tail = argv0End; // will be start of the executable name
578
const wchar_t *end = argv0End; // will be end of the executable name
579
search->restOfCmdLine = argv0End; // will be first space after argv0
580
while (--tail != search->originalCmdLine) {
581
if (*tail == L'"' && end == argv0End) {
582
// Move the "end" up to the quote, so we also allow moving for
583
// a period later on.
584
end = argv0End = tail;
585
} else if (*tail == L'.' && end == argv0End) {
586
end = tail;
587
} else if (*tail == L'\\' || *tail == L'/') {
588
++tail;
589
break;
590
}
591
}
592
if (tail == search->originalCmdLine && tail[0] == L'"') {
593
++tail;
594
}
595
// Without special cases, we can now fill in the search struct
596
int tailLen = (int)(end ? (end - tail) : wcsnlen_s(tail, MAXLEN));
597
search->executableLength = -1;
598
599
// Our special cases are as follows
600
#define MATCHES(s) (0 == _comparePath(tail, tailLen, (s), -1))
601
#define STARTSWITH(s) _startsWith(tail, tailLen, (s), -1)
602
if (MATCHES(L"py")) {
603
search->executable = L"python.exe";
604
search->allowExecutableOverride = true;
605
search->allowDefaults = true;
606
} else if (MATCHES(L"pyw")) {
607
search->executable = L"pythonw.exe";
608
search->allowExecutableOverride = true;
609
search->allowDefaults = true;
610
search->windowed = true;
611
} else if (MATCHES(L"py_d")) {
612
search->executable = L"python_d.exe";
613
search->allowExecutableOverride = true;
614
search->allowDefaults = true;
615
} else if (MATCHES(L"pyw_d")) {
616
search->executable = L"pythonw_d.exe";
617
search->allowExecutableOverride = true;
618
search->allowDefaults = true;
619
search->windowed = true;
620
} else if (STARTSWITH(L"python3")) {
621
search->executable = L"python.exe";
622
search->tag = &tail[6];
623
search->tagLength = tailLen - 6;
624
search->allowExecutableOverride = true;
625
search->oldStyleTag = true;
626
search->allowPyvenvCfg = true;
627
} else if (STARTSWITH(L"pythonw3")) {
628
search->executable = L"pythonw.exe";
629
search->tag = &tail[7];
630
search->tagLength = tailLen - 7;
631
search->allowExecutableOverride = true;
632
search->oldStyleTag = true;
633
search->allowPyvenvCfg = true;
634
search->windowed = true;
635
} else {
636
search->executable = tail;
637
search->executableLength = tailLen;
638
search->allowPyvenvCfg = true;
639
}
640
#undef STARTSWITH
641
#undef MATCHES
642
643
// First argument might be one of our options. If so, consume it,
644
// update flags and then set restOfCmdLine.
645
const wchar_t *arg = search->restOfCmdLine;
646
while(*arg && isspace(*arg)) { ++arg; }
647
#define MATCHES(s) (0 == _compareArgument(arg, argLen, (s), -1))
648
#define STARTSWITH(s) _startsWithArgument(arg, argLen, (s), -1)
649
if (*arg && *arg == L'-' && *++arg) {
650
tail = arg;
651
while (*tail && !isspace(*tail)) { ++tail; }
652
int argLen = (int)(tail - arg);
653
if (argLen > 0) {
654
if (STARTSWITH(L"2") || STARTSWITH(L"3")) {
655
// All arguments starting with 2 or 3 are assumed to be version tags
656
search->tag = arg;
657
search->tagLength = argLen;
658
search->oldStyleTag = true;
659
search->restOfCmdLine = tail;
660
} else if (STARTSWITH(L"V:") || STARTSWITH(L"-version:")) {
661
// Arguments starting with 'V:' specify company and/or tag
662
const wchar_t *argStart = wcschr(arg, L':') + 1;
663
const wchar_t *tagStart = wcschr(argStart, L'/') ;
664
if (tagStart) {
665
search->company = argStart;
666
search->companyLength = (int)(tagStart - argStart);
667
search->tag = tagStart + 1;
668
} else {
669
search->tag = argStart;
670
}
671
search->tagLength = (int)(tail - search->tag);
672
search->allowDefaults = false;
673
search->restOfCmdLine = tail;
674
} else if (MATCHES(L"0") || MATCHES(L"-list")) {
675
search->list = true;
676
search->restOfCmdLine = tail;
677
} else if (MATCHES(L"0p") || MATCHES(L"-list-paths")) {
678
search->listPaths = true;
679
search->restOfCmdLine = tail;
680
} else if (MATCHES(L"h") || MATCHES(L"-help")) {
681
search->help = true;
682
// Do not update restOfCmdLine so that we trigger the help
683
// message from whichever interpreter we select
684
}
685
}
686
}
687
#undef STARTSWITH
688
#undef MATCHES
689
690
// Might have a script filename. If it looks like a filename, add
691
// it to the SearchInfo struct for later reference.
692
arg = search->restOfCmdLine;
693
while(*arg && isspace(*arg)) { ++arg; }
694
if (*arg && *arg != L'-') {
695
search->scriptFile = arg;
696
if (*arg == L'"') {
697
++search->scriptFile;
698
while (*++arg && *arg != L'"') { }
699
} else {
700
while (*arg && !isspace(*arg)) { ++arg; }
701
}
702
search->scriptFileLength = (int)(arg - search->scriptFile);
703
}
704
705
return 0;
706
}
707
708
709
int
710
_decodeShebang(SearchInfo *search, const char *buffer, int bufferLength, bool onlyUtf8, wchar_t **decoded, int *decodedLength)
711
{
712
DWORD cp = CP_UTF8;
713
int wideLen = MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, buffer, bufferLength, NULL, 0);
714
if (!wideLen) {
715
cp = CP_ACP;
716
wideLen = MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, buffer, bufferLength, NULL, 0);
717
if (!wideLen) {
718
debug(L"# Failed to decode shebang line (0x%08X)\n", GetLastError());
719
return RC_BAD_VIRTUAL_PATH;
720
}
721
}
722
wchar_t *b = allocSearchInfoBuffer(search, wideLen + 1);
723
if (!b) {
724
return RC_NO_MEMORY;
725
}
726
wideLen = MultiByteToWideChar(cp, 0, buffer, bufferLength, b, wideLen + 1);
727
if (!wideLen) {
728
debug(L"# Failed to decode shebang line (0x%08X)\n", GetLastError());
729
return RC_BAD_VIRTUAL_PATH;
730
}
731
b[wideLen] = L'\0';
732
*decoded = b;
733
*decodedLength = wideLen;
734
return 0;
735
}
736
737
738
bool
739
_shebangStartsWith(const wchar_t *buffer, int bufferLength, const wchar_t *prefix, const wchar_t **rest, int *firstArgumentLength)
740
{
741
int prefixLength = (int)wcsnlen_s(prefix, MAXLEN);
742
if (bufferLength < prefixLength || !_startsWithArgument(buffer, bufferLength, prefix, prefixLength)) {
743
return false;
744
}
745
if (rest) {
746
*rest = &buffer[prefixLength];
747
}
748
if (firstArgumentLength) {
749
int i = prefixLength;
750
while (i < bufferLength && !isspace(buffer[i])) {
751
i += 1;
752
}
753
*firstArgumentLength = i - prefixLength;
754
}
755
return true;
756
}
757
758
759
int
760
searchPath(SearchInfo *search, const wchar_t *shebang, int shebangLength)
761
{
762
if (isEnvVarSet(L"PYLAUNCHER_NO_SEARCH_PATH")) {
763
return RC_NO_SHEBANG;
764
}
765
766
wchar_t *command;
767
int commandLength;
768
if (!_shebangStartsWith(shebang, shebangLength, L"/usr/bin/env ", &command, &commandLength)) {
769
return RC_NO_SHEBANG;
770
}
771
772
if (!commandLength || commandLength == MAXLEN) {
773
return RC_BAD_VIRTUAL_PATH;
774
}
775
776
int lastDot = commandLength;
777
while (lastDot > 0 && command[lastDot] != L'.') {
778
lastDot -= 1;
779
}
780
if (!lastDot) {
781
lastDot = commandLength;
782
}
783
784
wchar_t filename[MAXLEN];
785
if (wcsncpy_s(filename, MAXLEN, command, lastDot)) {
786
return RC_BAD_VIRTUAL_PATH;
787
}
788
789
const wchar_t *ext = L".exe";
790
// If the command already has an extension, we do not want to add it again
791
if (!lastDot || _comparePath(&filename[lastDot], -1, ext, -1)) {
792
if (wcscat_s(filename, MAXLEN, L".exe")) {
793
return RC_BAD_VIRTUAL_PATH;
794
}
795
}
796
797
wchar_t pathVariable[MAXLEN];
798
int n = GetEnvironmentVariableW(L"PATH", pathVariable, MAXLEN);
799
if (!n) {
800
if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
801
return RC_NO_SHEBANG;
802
}
803
winerror(0, L"Failed to read PATH\n", filename);
804
return RC_INTERNAL_ERROR;
805
}
806
807
wchar_t buffer[MAXLEN];
808
n = SearchPathW(pathVariable, filename, NULL, MAXLEN, buffer, NULL);
809
if (!n) {
810
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
811
debug(L"# Did not find %s on PATH\n", filename);
812
// If we didn't find it on PATH, let normal handling take over
813
return RC_NO_SHEBANG;
814
}
815
// Other errors should cause us to break
816
winerror(0, L"Failed to find %s on PATH\n", filename);
817
return RC_BAD_VIRTUAL_PATH;
818
}
819
820
// Check that we aren't going to call ourselves again
821
// If we are, pretend there was no shebang and let normal handling take over
822
if (GetModuleFileNameW(NULL, filename, MAXLEN) &&
823
0 == _comparePath(filename, -1, buffer, -1)) {
824
debug(L"# ignoring recursive shebang command\n");
825
return RC_RECURSIVE_SHEBANG;
826
}
827
828
wchar_t *buf = allocSearchInfoBuffer(search, n + 1);
829
if (!buf || wcscpy_s(buf, n + 1, buffer)) {
830
return RC_NO_MEMORY;
831
}
832
833
search->executablePath = buf;
834
search->executableArgs = &command[commandLength];
835
search->executableArgsLength = shebangLength - commandLength;
836
debug(L"# Found %s on PATH\n", buf);
837
838
return 0;
839
}
840
841
842
int
843
_readIni(const wchar_t *section, const wchar_t *settingName, wchar_t *buffer, int bufferLength)
844
{
845
wchar_t iniPath[MAXLEN];
846
int n;
847
if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, iniPath)) &&
848
join(iniPath, MAXLEN, L"py.ini")) {
849
debug(L"# Reading from %s for %s/%s\n", iniPath, section, settingName);
850
n = GetPrivateProfileStringW(section, settingName, NULL, buffer, bufferLength, iniPath);
851
if (n) {
852
debug(L"# Found %s in %s\n", settingName, iniPath);
853
return n;
854
} else if (GetLastError() == ERROR_FILE_NOT_FOUND) {
855
debug(L"# Did not find file %s\n", iniPath);
856
} else {
857
winerror(0, L"Failed to read from %s\n", iniPath);
858
}
859
}
860
if (GetModuleFileNameW(NULL, iniPath, MAXLEN) &&
861
SUCCEEDED(PathCchRemoveFileSpec(iniPath, MAXLEN)) &&
862
join(iniPath, MAXLEN, L"py.ini")) {
863
debug(L"# Reading from %s for %s/%s\n", iniPath, section, settingName);
864
n = GetPrivateProfileStringW(section, settingName, NULL, buffer, MAXLEN, iniPath);
865
if (n) {
866
debug(L"# Found %s in %s\n", settingName, iniPath);
867
return n;
868
} else if (GetLastError() == ERROR_FILE_NOT_FOUND) {
869
debug(L"# Did not find file %s\n", iniPath);
870
} else {
871
winerror(0, L"Failed to read from %s\n", iniPath);
872
}
873
}
874
return 0;
875
}
876
877
878
bool
879
_findCommand(SearchInfo *search, const wchar_t *command, int commandLength)
880
{
881
wchar_t commandBuffer[MAXLEN];
882
wchar_t buffer[MAXLEN];
883
wcsncpy_s(commandBuffer, MAXLEN, command, commandLength);
884
int n = _readIni(L"commands", commandBuffer, buffer, MAXLEN);
885
if (!n) {
886
return false;
887
}
888
wchar_t *path = allocSearchInfoBuffer(search, n + 1);
889
if (!path) {
890
return false;
891
}
892
wcscpy_s(path, n + 1, buffer);
893
search->executablePath = path;
894
return true;
895
}
896
897
898
int
899
_useShebangAsExecutable(SearchInfo *search, const wchar_t *shebang, int shebangLength)
900
{
901
wchar_t buffer[MAXLEN];
902
wchar_t script[MAXLEN];
903
wchar_t command[MAXLEN];
904
905
int commandLength = 0;
906
int inQuote = 0;
907
908
if (!shebang || !shebangLength) {
909
return 0;
910
}
911
912
wchar_t *pC = command;
913
for (int i = 0; i < shebangLength; ++i) {
914
wchar_t c = shebang[i];
915
if (isspace(c) && !inQuote) {
916
commandLength = i;
917
break;
918
} else if (c == L'"') {
919
inQuote = !inQuote;
920
} else if (c == L'/' || c == L'\\') {
921
*pC++ = L'\\';
922
} else {
923
*pC++ = c;
924
}
925
}
926
*pC = L'\0';
927
928
if (!GetCurrentDirectoryW(MAXLEN, buffer) ||
929
wcsncpy_s(script, MAXLEN, search->scriptFile, search->scriptFileLength) ||
930
FAILED(PathCchCombineEx(buffer, MAXLEN, buffer, script,
931
PATHCCH_ALLOW_LONG_PATHS)) ||
932
FAILED(PathCchRemoveFileSpec(buffer, MAXLEN)) ||
933
FAILED(PathCchCombineEx(buffer, MAXLEN, buffer, command,
934
PATHCCH_ALLOW_LONG_PATHS))
935
) {
936
return RC_NO_MEMORY;
937
}
938
939
int n = (int)wcsnlen(buffer, MAXLEN);
940
wchar_t *path = allocSearchInfoBuffer(search, n + 1);
941
if (!path) {
942
return RC_NO_MEMORY;
943
}
944
wcscpy_s(path, n + 1, buffer);
945
search->executablePath = path;
946
if (commandLength) {
947
search->executableArgs = &shebang[commandLength];
948
search->executableArgsLength = shebangLength - commandLength;
949
}
950
return 0;
951
}
952
953
954
int
955
checkShebang(SearchInfo *search)
956
{
957
// Do not check shebang if a tag was provided or if no script file
958
// was found on the command line.
959
if (search->tag || !search->scriptFile) {
960
return 0;
961
}
962
963
if (search->scriptFileLength < 0) {
964
search->scriptFileLength = (int)wcsnlen_s(search->scriptFile, MAXLEN);
965
}
966
967
wchar_t *scriptFile = (wchar_t*)malloc(sizeof(wchar_t) * (search->scriptFileLength + 1));
968
if (!scriptFile) {
969
return RC_NO_MEMORY;
970
}
971
972
wcsncpy_s(scriptFile, search->scriptFileLength + 1,
973
search->scriptFile, search->scriptFileLength);
974
975
HANDLE hFile = CreateFileW(scriptFile, GENERIC_READ,
976
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
977
NULL, OPEN_EXISTING, 0, NULL);
978
979
if (hFile == INVALID_HANDLE_VALUE) {
980
debug(L"# Failed to open %s for shebang parsing (0x%08X)\n",
981
scriptFile, GetLastError());
982
free(scriptFile);
983
return 0;
984
}
985
986
DWORD bytesRead = 0;
987
char buffer[4096];
988
if (!ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL)) {
989
debug(L"# Failed to read %s for shebang parsing (0x%08X)\n",
990
scriptFile, GetLastError());
991
free(scriptFile);
992
return 0;
993
}
994
995
CloseHandle(hFile);
996
debug(L"# Read %d bytes from %s to find shebang line\n", bytesRead, scriptFile);
997
free(scriptFile);
998
999
1000
char *b = buffer;
1001
bool onlyUtf8 = false;
1002
if (bytesRead > 3 && *b == 0xEF) {
1003
if (*++b == 0xBB && *++b == 0xBF) {
1004
// Allow a UTF-8 BOM
1005
++b;
1006
bytesRead -= 3;
1007
onlyUtf8 = true;
1008
} else {
1009
debug(L"# Invalid BOM in shebang line");
1010
return 0;
1011
}
1012
}
1013
if (bytesRead <= 2 || b[0] != '#' || b[1] != '!') {
1014
// No shebang (#!) at start of line
1015
debug(L"# No valid shebang line");
1016
return 0;
1017
}
1018
++b;
1019
--bytesRead;
1020
while (--bytesRead > 0 && isspace(*++b)) { }
1021
char *start = b;
1022
while (--bytesRead > 0 && *++b != '\r' && *b != '\n') { }
1023
wchar_t *shebang;
1024
int shebangLength;
1025
// We add 1 when bytesRead==0, as in that case we hit EOF and b points
1026
// to the last character in the file, not the newline
1027
int exitCode = _decodeShebang(search, start, (int)(b - start + (bytesRead == 0)), onlyUtf8, &shebang, &shebangLength);
1028
if (exitCode) {
1029
return exitCode;
1030
}
1031
debug(L"Shebang: %s\n", shebang);
1032
1033
// Handle shebangs that we should search PATH for
1034
exitCode = searchPath(search, shebang, shebangLength);
1035
if (exitCode != RC_NO_SHEBANG) {
1036
return exitCode;
1037
}
1038
1039
// Handle some known, case-sensitive shebangs
1040
const wchar_t *command;
1041
int commandLength;
1042
// Each template must end with "python"
1043
static const wchar_t *shebangTemplates[] = {
1044
L"/usr/bin/env python",
1045
L"/usr/bin/python",
1046
L"/usr/local/bin/python",
1047
L"python",
1048
NULL
1049
};
1050
1051
for (const wchar_t **tmpl = shebangTemplates; *tmpl; ++tmpl) {
1052
// Just to make sure we don't mess this up in the future
1053
assert(0 == wcscmp(L"python", (*tmpl) + wcslen(*tmpl) - 6));
1054
1055
if (_shebangStartsWith(shebang, shebangLength, *tmpl, &command, &commandLength)) {
1056
// Search for "python{command}" overrides. All templates end with
1057
// "python", so we prepend it by jumping back 6 characters
1058
if (_findCommand(search, &command[-6], commandLength + 6)) {
1059
search->executableArgs = &command[commandLength];
1060
search->executableArgsLength = shebangLength - commandLength;
1061
debug(L"# Treating shebang command '%.*s' as %s\n",
1062
commandLength + 6, &command[-6], search->executablePath);
1063
return 0;
1064
}
1065
1066
search->tag = command;
1067
search->tagLength = commandLength;
1068
// If we had 'python3.12.exe' then we want to strip the suffix
1069
// off of the tag
1070
if (search->tagLength > 4) {
1071
const wchar_t *suffix = &search->tag[search->tagLength - 4];
1072
if (0 == _comparePath(suffix, 4, L".exe", -1)) {
1073
search->tagLength -= 4;
1074
}
1075
}
1076
// If we had 'python3_d' then we want to strip the '_d' (any
1077
// '.exe' is already gone)
1078
if (search->tagLength > 2) {
1079
const wchar_t *suffix = &search->tag[search->tagLength - 2];
1080
if (0 == _comparePath(suffix, 2, L"_d", -1)) {
1081
search->tagLength -= 2;
1082
}
1083
}
1084
search->oldStyleTag = true;
1085
search->executableArgs = &command[commandLength];
1086
search->executableArgsLength = shebangLength - commandLength;
1087
if (search->tag && search->tagLength) {
1088
debug(L"# Treating shebang command '%.*s' as 'py -%.*s'\n",
1089
commandLength, command, search->tagLength, search->tag);
1090
} else {
1091
debug(L"# Treating shebang command '%.*s' as 'py'\n",
1092
commandLength, command);
1093
}
1094
return 0;
1095
}
1096
}
1097
1098
// Unrecognised executables are first tried as command aliases
1099
commandLength = 0;
1100
while (commandLength < shebangLength && !isspace(shebang[commandLength])) {
1101
commandLength += 1;
1102
}
1103
if (_findCommand(search, shebang, commandLength)) {
1104
search->executableArgs = &shebang[commandLength];
1105
search->executableArgsLength = shebangLength - commandLength;
1106
debug(L"# Treating shebang command '%.*s' as %s\n",
1107
commandLength, shebang, search->executablePath);
1108
return 0;
1109
}
1110
1111
// Unrecognised commands are joined to the script's directory and treated
1112
// as the executable path
1113
return _useShebangAsExecutable(search, shebang, shebangLength);
1114
}
1115
1116
1117
int
1118
checkDefaults(SearchInfo *search)
1119
{
1120
if (!search->allowDefaults) {
1121
return 0;
1122
}
1123
1124
// Only resolve old-style (or absent) tags to defaults
1125
if (search->tag && search->tagLength && !search->oldStyleTag) {
1126
return 0;
1127
}
1128
1129
// If tag is only a major version number, expand it from the environment
1130
// or an ini file
1131
const wchar_t *iniSettingName = NULL;
1132
const wchar_t *envSettingName = NULL;
1133
if (!search->tag || !search->tagLength) {
1134
iniSettingName = L"python";
1135
envSettingName = L"py_python";
1136
} else if (0 == wcsncmp(search->tag, L"3", search->tagLength)) {
1137
iniSettingName = L"python3";
1138
envSettingName = L"py_python3";
1139
} else if (0 == wcsncmp(search->tag, L"2", search->tagLength)) {
1140
iniSettingName = L"python2";
1141
envSettingName = L"py_python2";
1142
} else {
1143
debug(L"# Cannot select defaults for tag '%.*s'\n", search->tagLength, search->tag);
1144
return 0;
1145
}
1146
1147
// First, try to read an environment variable
1148
wchar_t buffer[MAXLEN];
1149
int n = GetEnvironmentVariableW(envSettingName, buffer, MAXLEN);
1150
1151
// If none found, check in our two .ini files instead
1152
if (!n) {
1153
n = _readIni(L"defaults", iniSettingName, buffer, MAXLEN);
1154
}
1155
1156
if (n) {
1157
wchar_t *tag = allocSearchInfoBuffer(search, n + 1);
1158
if (!tag) {
1159
return RC_NO_MEMORY;
1160
}
1161
wcscpy_s(tag, n + 1, buffer);
1162
wchar_t *slash = wcschr(tag, L'/');
1163
if (!slash) {
1164
search->tag = tag;
1165
search->tagLength = n;
1166
search->oldStyleTag = true;
1167
} else {
1168
search->company = tag;
1169
search->companyLength = (int)(slash - tag);
1170
search->tag = slash + 1;
1171
search->tagLength = n - (search->companyLength + 1);
1172
search->oldStyleTag = false;
1173
}
1174
// gh-92817: allow a high priority env to be selected even if it
1175
// doesn't match the tag
1176
search->lowPriorityTag = true;
1177
}
1178
1179
return 0;
1180
}
1181
1182
/******************************************************************************\
1183
*** ENVIRONMENT SEARCH ***
1184
\******************************************************************************/
1185
1186
typedef struct EnvironmentInfo {
1187
/* We use a binary tree and sort on insert */
1188
struct EnvironmentInfo *prev;
1189
struct EnvironmentInfo *next;
1190
/* parent is only used when constructing */
1191
struct EnvironmentInfo *parent;
1192
const wchar_t *company;
1193
const wchar_t *tag;
1194
int internalSortKey;
1195
const wchar_t *installDir;
1196
const wchar_t *executablePath;
1197
const wchar_t *executableArgs;
1198
const wchar_t *architecture;
1199
const wchar_t *displayName;
1200
bool highPriority;
1201
} EnvironmentInfo;
1202
1203
1204
int
1205
copyWstr(const wchar_t **dest, const wchar_t *src)
1206
{
1207
if (!dest) {
1208
return RC_NO_MEMORY;
1209
}
1210
if (!src) {
1211
*dest = NULL;
1212
return 0;
1213
}
1214
size_t n = wcsnlen_s(src, MAXLEN - 1) + 1;
1215
wchar_t *buffer = (wchar_t*)malloc(n * sizeof(wchar_t));
1216
if (!buffer) {
1217
return RC_NO_MEMORY;
1218
}
1219
wcsncpy_s(buffer, n, src, n - 1);
1220
*dest = (const wchar_t*)buffer;
1221
return 0;
1222
}
1223
1224
1225
EnvironmentInfo *
1226
newEnvironmentInfo(const wchar_t *company, const wchar_t *tag)
1227
{
1228
EnvironmentInfo *env = (EnvironmentInfo *)malloc(sizeof(EnvironmentInfo));
1229
if (!env) {
1230
return NULL;
1231
}
1232
memset(env, 0, sizeof(EnvironmentInfo));
1233
int exitCode = copyWstr(&env->company, company);
1234
if (exitCode) {
1235
free((void *)env);
1236
return NULL;
1237
}
1238
exitCode = copyWstr(&env->tag, tag);
1239
if (exitCode) {
1240
free((void *)env->company);
1241
free((void *)env);
1242
return NULL;
1243
}
1244
return env;
1245
}
1246
1247
1248
void
1249
freeEnvironmentInfo(EnvironmentInfo *env)
1250
{
1251
if (env) {
1252
free((void *)env->company);
1253
free((void *)env->tag);
1254
free((void *)env->installDir);
1255
free((void *)env->executablePath);
1256
free((void *)env->executableArgs);
1257
free((void *)env->displayName);
1258
freeEnvironmentInfo(env->prev);
1259
env->prev = NULL;
1260
freeEnvironmentInfo(env->next);
1261
env->next = NULL;
1262
free((void *)env);
1263
}
1264
}
1265
1266
1267
/* Specific string comparisons for sorting the tree */
1268
1269
int
1270
_compareCompany(const wchar_t *x, const wchar_t *y)
1271
{
1272
if (!x && !y) {
1273
return 0;
1274
} else if (!x) {
1275
return -1;
1276
} else if (!y) {
1277
return 1;
1278
}
1279
1280
bool coreX = 0 == _compare(x, -1, L"PythonCore", -1);
1281
bool coreY = 0 == _compare(y, -1, L"PythonCore", -1);
1282
if (coreX) {
1283
return coreY ? 0 : -1;
1284
} else if (coreY) {
1285
return 1;
1286
}
1287
return _compare(x, -1, y, -1);
1288
}
1289
1290
1291
int
1292
_compareTag(const wchar_t *x, const wchar_t *y)
1293
{
1294
if (!x && !y) {
1295
return 0;
1296
} else if (!x) {
1297
return -1;
1298
} else if (!y) {
1299
return 1;
1300
}
1301
1302
// Compare up to the first dash. If not equal, that's our sort order
1303
const wchar_t *xDash = wcschr(x, L'-');
1304
const wchar_t *yDash = wcschr(y, L'-');
1305
int xToDash = xDash ? (int)(xDash - x) : -1;
1306
int yToDash = yDash ? (int)(yDash - y) : -1;
1307
int r = _compare(x, xToDash, y, yToDash);
1308
if (r) {
1309
return r;
1310
}
1311
// If we're equal up to the first dash, we want to sort one with
1312
// no dash *after* one with a dash. Otherwise, a reversed compare.
1313
// This works out because environments are sorted in descending tag
1314
// order, so that higher versions (probably) come first.
1315
// For PythonCore, our "X.Y" structure ensures that higher versions
1316
// come first. Everyone else will just have to deal with it.
1317
if (xDash && yDash) {
1318
return _compare(yDash, -1, xDash, -1);
1319
} else if (xDash) {
1320
return -1;
1321
} else if (yDash) {
1322
return 1;
1323
}
1324
return 0;
1325
}
1326
1327
1328
int
1329
addEnvironmentInfo(EnvironmentInfo **root, EnvironmentInfo* parent, EnvironmentInfo *node)
1330
{
1331
EnvironmentInfo *r = *root;
1332
if (!r) {
1333
*root = node;
1334
node->parent = parent;
1335
return 0;
1336
}
1337
// Sort by company name
1338
switch (_compareCompany(node->company, r->company)) {
1339
case -1:
1340
return addEnvironmentInfo(&r->prev, r, node);
1341
case 1:
1342
return addEnvironmentInfo(&r->next, r, node);
1343
case 0:
1344
break;
1345
}
1346
// Then by tag (descending)
1347
switch (_compareTag(node->tag, r->tag)) {
1348
case -1:
1349
return addEnvironmentInfo(&r->next, r, node);
1350
case 1:
1351
return addEnvironmentInfo(&r->prev, r, node);
1352
case 0:
1353
break;
1354
}
1355
// Then keep the one with the lowest internal sort key
1356
if (node->internalSortKey < r->internalSortKey) {
1357
// Replace the current node
1358
node->parent = r->parent;
1359
if (node->parent) {
1360
if (node->parent->prev == r) {
1361
node->parent->prev = node;
1362
} else if (node->parent->next == r) {
1363
node->parent->next = node;
1364
} else {
1365
debug(L"# Inconsistent parent value in tree\n");
1366
freeEnvironmentInfo(node);
1367
return RC_INTERNAL_ERROR;
1368
}
1369
} else {
1370
// If node has no parent, then it is the root.
1371
*root = node;
1372
}
1373
1374
node->next = r->next;
1375
node->prev = r->prev;
1376
1377
debug(L"# replaced %s/%s/%i in tree\n", node->company, node->tag, node->internalSortKey);
1378
freeEnvironmentInfo(r);
1379
} else {
1380
debug(L"# not adding %s/%s/%i to tree\n", node->company, node->tag, node->internalSortKey);
1381
return RC_DUPLICATE_ITEM;
1382
}
1383
return 0;
1384
}
1385
1386
1387
/******************************************************************************\
1388
*** REGISTRY SEARCH ***
1389
\******************************************************************************/
1390
1391
1392
int
1393
_registryReadString(const wchar_t **dest, HKEY root, const wchar_t *subkey, const wchar_t *value)
1394
{
1395
// Note that this is bytes (hence 'cb'), not characters ('cch')
1396
DWORD cbData = 0;
1397
DWORD flags = RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ;
1398
1399
if (ERROR_SUCCESS != RegGetValueW(root, subkey, value, flags, NULL, NULL, &cbData)) {
1400
return 0;
1401
}
1402
1403
wchar_t *buffer = (wchar_t*)malloc(cbData);
1404
if (!buffer) {
1405
return RC_NO_MEMORY;
1406
}
1407
1408
if (ERROR_SUCCESS == RegGetValueW(root, subkey, value, flags, NULL, buffer, &cbData)) {
1409
*dest = buffer;
1410
} else {
1411
free((void *)buffer);
1412
}
1413
return 0;
1414
}
1415
1416
1417
int
1418
_combineWithInstallDir(const wchar_t **dest, const wchar_t *installDir, const wchar_t *fragment, int fragmentLength)
1419
{
1420
wchar_t buffer[MAXLEN];
1421
wchar_t fragmentBuffer[MAXLEN];
1422
if (wcsncpy_s(fragmentBuffer, MAXLEN, fragment, fragmentLength)) {
1423
return RC_NO_MEMORY;
1424
}
1425
1426
if (FAILED(PathCchCombineEx(buffer, MAXLEN, installDir, fragmentBuffer, PATHCCH_ALLOW_LONG_PATHS))) {
1427
return RC_NO_MEMORY;
1428
}
1429
1430
return copyWstr(dest, buffer);
1431
}
1432
1433
1434
bool
1435
_isLegacyVersion(EnvironmentInfo *env)
1436
{
1437
// Check if backwards-compatibility is required.
1438
// Specifically PythonCore versions 2.X and 3.0 - 3.5 do not implement PEP 514.
1439
if (0 != _compare(env->company, -1, L"PythonCore", -1)) {
1440
return false;
1441
}
1442
1443
int versionMajor, versionMinor;
1444
int n = swscanf_s(env->tag, L"%d.%d", &versionMajor, &versionMinor);
1445
if (n != 2) {
1446
debug(L"# %s/%s has an invalid version tag\n", env->company, env->tag);
1447
return false;
1448
}
1449
1450
return versionMajor == 2
1451
|| (versionMajor == 3 && versionMinor >= 0 && versionMinor <= 5);
1452
}
1453
1454
int
1455
_registryReadLegacyEnvironment(const SearchInfo *search, HKEY root, EnvironmentInfo *env, const wchar_t *fallbackArch)
1456
{
1457
// Backwards-compatibility for PythonCore versions which do not implement PEP 514.
1458
int exitCode = _combineWithInstallDir(
1459
&env->executablePath,
1460
env->installDir,
1461
search->executable,
1462
search->executableLength
1463
);
1464
if (exitCode) {
1465
return exitCode;
1466
}
1467
1468
if (search->windowed) {
1469
exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"WindowedExecutableArguments");
1470
}
1471
else {
1472
exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"ExecutableArguments");
1473
}
1474
if (exitCode) {
1475
return exitCode;
1476
}
1477
1478
if (fallbackArch) {
1479
copyWstr(&env->architecture, fallbackArch);
1480
} else {
1481
DWORD binaryType;
1482
BOOL success = GetBinaryTypeW(env->executablePath, &binaryType);
1483
if (!success) {
1484
return RC_NO_PYTHON;
1485
}
1486
1487
switch (binaryType) {
1488
case SCS_32BIT_BINARY:
1489
copyWstr(&env->architecture, L"32bit");
1490
break;
1491
case SCS_64BIT_BINARY:
1492
copyWstr(&env->architecture, L"64bit");
1493
break;
1494
default:
1495
return RC_NO_PYTHON;
1496
}
1497
}
1498
1499
if (0 == _compare(env->architecture, -1, L"32bit", -1)) {
1500
size_t tagLength = wcslen(env->tag);
1501
if (tagLength <= 3 || 0 != _compare(&env->tag[tagLength - 3], 3, L"-32", 3)) {
1502
const wchar_t *rawTag = env->tag;
1503
wchar_t *realTag = (wchar_t*) malloc(sizeof(wchar_t) * (tagLength + 4));
1504
if (!realTag) {
1505
return RC_NO_MEMORY;
1506
}
1507
1508
int count = swprintf_s(realTag, tagLength + 4, L"%s-32", env->tag);
1509
if (count == -1) {
1510
free(realTag);
1511
return RC_INTERNAL_ERROR;
1512
}
1513
1514
env->tag = realTag;
1515
free((void*)rawTag);
1516
}
1517
}
1518
1519
wchar_t buffer[MAXLEN];
1520
if (swprintf_s(buffer, MAXLEN, L"Python %s", env->tag)) {
1521
copyWstr(&env->displayName, buffer);
1522
}
1523
1524
return 0;
1525
}
1526
1527
1528
int
1529
_registryReadEnvironment(const SearchInfo *search, HKEY root, EnvironmentInfo *env, const wchar_t *fallbackArch)
1530
{
1531
int exitCode = _registryReadString(&env->installDir, root, L"InstallPath", NULL);
1532
if (exitCode) {
1533
return exitCode;
1534
}
1535
if (!env->installDir) {
1536
return RC_NO_PYTHON;
1537
}
1538
1539
if (_isLegacyVersion(env)) {
1540
return _registryReadLegacyEnvironment(search, root, env, fallbackArch);
1541
}
1542
1543
// If pythonw.exe requested, check specific value
1544
if (search->windowed) {
1545
exitCode = _registryReadString(&env->executablePath, root, L"InstallPath", L"WindowedExecutablePath");
1546
if (!exitCode && env->executablePath) {
1547
exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"WindowedExecutableArguments");
1548
}
1549
}
1550
if (exitCode) {
1551
return exitCode;
1552
}
1553
1554
// Missing windowed path or non-windowed request means we use ExecutablePath
1555
if (!env->executablePath) {
1556
exitCode = _registryReadString(&env->executablePath, root, L"InstallPath", L"ExecutablePath");
1557
if (!exitCode && env->executablePath) {
1558
exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"ExecutableArguments");
1559
}
1560
}
1561
if (exitCode) {
1562
return exitCode;
1563
}
1564
1565
if (!env->executablePath) {
1566
debug(L"# %s/%s has no executable path\n", env->company, env->tag);
1567
return RC_NO_PYTHON;
1568
}
1569
1570
exitCode = _registryReadString(&env->architecture, root, NULL, L"SysArchitecture");
1571
if (exitCode) {
1572
return exitCode;
1573
}
1574
1575
exitCode = _registryReadString(&env->displayName, root, NULL, L"DisplayName");
1576
if (exitCode) {
1577
return exitCode;
1578
}
1579
1580
return 0;
1581
}
1582
1583
int
1584
_registrySearchTags(const SearchInfo *search, EnvironmentInfo **result, HKEY root, int sortKey, const wchar_t *company, const wchar_t *fallbackArch)
1585
{
1586
wchar_t buffer[256];
1587
int err = 0;
1588
int exitCode = 0;
1589
for (int i = 0; exitCode == 0; ++i) {
1590
DWORD cchBuffer = sizeof(buffer) / sizeof(buffer[0]);
1591
err = RegEnumKeyExW(root, i, buffer, &cchBuffer, NULL, NULL, NULL, NULL);
1592
if (err) {
1593
if (err != ERROR_NO_MORE_ITEMS) {
1594
winerror(0, L"Failed to read installs (tags) from the registry");
1595
}
1596
break;
1597
}
1598
HKEY subkey;
1599
if (ERROR_SUCCESS == RegOpenKeyExW(root, buffer, 0, KEY_READ, &subkey)) {
1600
EnvironmentInfo *env = newEnvironmentInfo(company, buffer);
1601
env->internalSortKey = sortKey;
1602
exitCode = _registryReadEnvironment(search, subkey, env, fallbackArch);
1603
RegCloseKey(subkey);
1604
if (exitCode == RC_NO_PYTHON) {
1605
freeEnvironmentInfo(env);
1606
exitCode = 0;
1607
} else if (!exitCode) {
1608
exitCode = addEnvironmentInfo(result, NULL, env);
1609
if (exitCode) {
1610
freeEnvironmentInfo(env);
1611
if (exitCode == RC_DUPLICATE_ITEM) {
1612
exitCode = 0;
1613
}
1614
}
1615
}
1616
}
1617
}
1618
return exitCode;
1619
}
1620
1621
1622
int
1623
registrySearch(const SearchInfo *search, EnvironmentInfo **result, HKEY root, int sortKey, const wchar_t *fallbackArch)
1624
{
1625
wchar_t buffer[256];
1626
int err = 0;
1627
int exitCode = 0;
1628
for (int i = 0; exitCode == 0; ++i) {
1629
DWORD cchBuffer = sizeof(buffer) / sizeof(buffer[0]);
1630
err = RegEnumKeyExW(root, i, buffer, &cchBuffer, NULL, NULL, NULL, NULL);
1631
if (err) {
1632
if (err != ERROR_NO_MORE_ITEMS) {
1633
winerror(0, L"Failed to read distributors (company) from the registry");
1634
}
1635
break;
1636
}
1637
if (search->limitToCompany && 0 != _compare(search->limitToCompany, -1, buffer, cchBuffer)) {
1638
debug(L"# Skipping %s due to PYLAUNCHER_LIMIT_TO_COMPANY\n", buffer);
1639
continue;
1640
}
1641
HKEY subkey;
1642
if (ERROR_SUCCESS == RegOpenKeyExW(root, buffer, 0, KEY_READ, &subkey)) {
1643
exitCode = _registrySearchTags(search, result, subkey, sortKey, buffer, fallbackArch);
1644
RegCloseKey(subkey);
1645
}
1646
}
1647
return exitCode;
1648
}
1649
1650
1651
/******************************************************************************\
1652
*** APP PACKAGE SEARCH ***
1653
\******************************************************************************/
1654
1655
int
1656
appxSearch(const SearchInfo *search, EnvironmentInfo **result, const wchar_t *packageFamilyName, const wchar_t *tag, int sortKey)
1657
{
1658
wchar_t realTag[32];
1659
wchar_t buffer[MAXLEN];
1660
const wchar_t *exeName = search->executable;
1661
if (!exeName || search->allowExecutableOverride) {
1662
exeName = search->windowed ? L"pythonw.exe" : L"python.exe";
1663
}
1664
1665
if (FAILED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, buffer)) ||
1666
!join(buffer, MAXLEN, L"Microsoft\\WindowsApps") ||
1667
!join(buffer, MAXLEN, packageFamilyName) ||
1668
!join(buffer, MAXLEN, exeName)) {
1669
return RC_INTERNAL_ERROR;
1670
}
1671
1672
if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(buffer)) {
1673
return RC_NO_PYTHON;
1674
}
1675
1676
// Assume packages are native architecture, which means we need to append
1677
// the '-arm64' on ARM64 host.
1678
wcscpy_s(realTag, 32, tag);
1679
if (isARM64Host()) {
1680
wcscat_s(realTag, 32, L"-arm64");
1681
}
1682
1683
EnvironmentInfo *env = newEnvironmentInfo(L"PythonCore", realTag);
1684
if (!env) {
1685
return RC_NO_MEMORY;
1686
}
1687
env->internalSortKey = sortKey;
1688
if (isAMD64Host()) {
1689
copyWstr(&env->architecture, L"64bit");
1690
} else if (isARM64Host()) {
1691
copyWstr(&env->architecture, L"ARM64");
1692
}
1693
1694
copyWstr(&env->executablePath, buffer);
1695
1696
if (swprintf_s(buffer, MAXLEN, L"Python %s (Store)", tag)) {
1697
copyWstr(&env->displayName, buffer);
1698
}
1699
1700
int exitCode = addEnvironmentInfo(result, NULL, env);
1701
if (exitCode) {
1702
freeEnvironmentInfo(env);
1703
if (exitCode == RC_DUPLICATE_ITEM) {
1704
exitCode = 0;
1705
}
1706
}
1707
1708
1709
return exitCode;
1710
}
1711
1712
1713
/******************************************************************************\
1714
*** OVERRIDDEN EXECUTABLE PATH ***
1715
\******************************************************************************/
1716
1717
1718
int
1719
explicitOverrideSearch(const SearchInfo *search, EnvironmentInfo **result)
1720
{
1721
if (!search->executablePath) {
1722
return 0;
1723
}
1724
1725
EnvironmentInfo *env = newEnvironmentInfo(NULL, NULL);
1726
if (!env) {
1727
return RC_NO_MEMORY;
1728
}
1729
env->internalSortKey = 10;
1730
int exitCode = copyWstr(&env->executablePath, search->executablePath);
1731
if (exitCode) {
1732
goto abort;
1733
}
1734
exitCode = copyWstr(&env->displayName, L"Explicit override");
1735
if (exitCode) {
1736
goto abort;
1737
}
1738
exitCode = addEnvironmentInfo(result, NULL, env);
1739
if (exitCode) {
1740
goto abort;
1741
}
1742
return 0;
1743
1744
abort:
1745
freeEnvironmentInfo(env);
1746
if (exitCode == RC_DUPLICATE_ITEM) {
1747
exitCode = 0;
1748
}
1749
return exitCode;
1750
}
1751
1752
1753
/******************************************************************************\
1754
*** ACTIVE VIRTUAL ENVIRONMENT SEARCH ***
1755
\******************************************************************************/
1756
1757
int
1758
virtualenvSearch(const SearchInfo *search, EnvironmentInfo **result)
1759
{
1760
int exitCode = 0;
1761
EnvironmentInfo *env = NULL;
1762
wchar_t buffer[MAXLEN];
1763
int n = GetEnvironmentVariableW(L"VIRTUAL_ENV", buffer, MAXLEN);
1764
if (!n || !join(buffer, MAXLEN, L"Scripts") || !join(buffer, MAXLEN, search->executable)) {
1765
return 0;
1766
}
1767
1768
if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(buffer)) {
1769
debug(L"Python executable %s missing from virtual env\n", buffer);
1770
return 0;
1771
}
1772
1773
env = newEnvironmentInfo(NULL, NULL);
1774
if (!env) {
1775
return RC_NO_MEMORY;
1776
}
1777
env->highPriority = true;
1778
env->internalSortKey = 20;
1779
exitCode = copyWstr(&env->displayName, L"Active venv");
1780
if (exitCode) {
1781
goto abort;
1782
}
1783
exitCode = copyWstr(&env->executablePath, buffer);
1784
if (exitCode) {
1785
goto abort;
1786
}
1787
exitCode = addEnvironmentInfo(result, NULL, env);
1788
if (exitCode) {
1789
goto abort;
1790
}
1791
return 0;
1792
1793
abort:
1794
freeEnvironmentInfo(env);
1795
if (exitCode == RC_DUPLICATE_ITEM) {
1796
return 0;
1797
}
1798
return exitCode;
1799
}
1800
1801
/******************************************************************************\
1802
*** COLLECT ENVIRONMENTS ***
1803
\******************************************************************************/
1804
1805
1806
struct RegistrySearchInfo {
1807
// Registry subkey to search
1808
const wchar_t *subkey;
1809
// Registry hive to search
1810
HKEY hive;
1811
// Flags to use when opening the subkey
1812
DWORD flags;
1813
// Internal sort key to select between "identical" environments discovered
1814
// through different methods
1815
int sortKey;
1816
// Fallback value to assume for PythonCore entries missing a SysArchitecture value
1817
const wchar_t *fallbackArch;
1818
};
1819
1820
1821
struct RegistrySearchInfo REGISTRY_SEARCH[] = {
1822
{
1823
L"Software\\Python",
1824
HKEY_CURRENT_USER,
1825
KEY_READ,
1826
1,
1827
NULL
1828
},
1829
{
1830
L"Software\\Python",
1831
HKEY_LOCAL_MACHINE,
1832
KEY_READ | KEY_WOW64_64KEY,
1833
3,
1834
L"64bit"
1835
},
1836
{
1837
L"Software\\Python",
1838
HKEY_LOCAL_MACHINE,
1839
KEY_READ | KEY_WOW64_32KEY,
1840
4,
1841
L"32bit"
1842
},
1843
{ NULL, 0, 0, 0, NULL }
1844
};
1845
1846
1847
struct AppxSearchInfo {
1848
// The package family name. Can be found for an installed package using the
1849
// Powershell "Get-AppxPackage" cmdlet
1850
const wchar_t *familyName;
1851
// The tag to treat the installation as
1852
const wchar_t *tag;
1853
// Internal sort key to select between "identical" environments discovered
1854
// through different methods
1855
int sortKey;
1856
};
1857
1858
1859
struct AppxSearchInfo APPX_SEARCH[] = {
1860
// Releases made through the Store
1861
{ L"PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0", L"3.12", 10 },
1862
{ L"PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0", L"3.11", 10 },
1863
{ L"PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0", L"3.10", 10 },
1864
{ L"PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0", L"3.9", 10 },
1865
{ L"PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0", L"3.8", 10 },
1866
1867
// Side-loadable releases. Note that the publisher ID changes whenever we
1868
// renew our code-signing certificate, so the newer ID has a higher
1869
// priority (lower sortKey)
1870
{ L"PythonSoftwareFoundation.Python.3.12_3847v3x7pw1km", L"3.12", 11 },
1871
{ L"PythonSoftwareFoundation.Python.3.11_3847v3x7pw1km", L"3.11", 11 },
1872
{ L"PythonSoftwareFoundation.Python.3.11_hd69rhyc2wevp", L"3.11", 12 },
1873
{ L"PythonSoftwareFoundation.Python.3.10_3847v3x7pw1km", L"3.10", 11 },
1874
{ L"PythonSoftwareFoundation.Python.3.10_hd69rhyc2wevp", L"3.10", 12 },
1875
{ L"PythonSoftwareFoundation.Python.3.9_3847v3x7pw1km", L"3.9", 11 },
1876
{ L"PythonSoftwareFoundation.Python.3.9_hd69rhyc2wevp", L"3.9", 12 },
1877
{ L"PythonSoftwareFoundation.Python.3.8_hd69rhyc2wevp", L"3.8", 12 },
1878
{ NULL, NULL, 0 }
1879
};
1880
1881
1882
int
1883
collectEnvironments(const SearchInfo *search, EnvironmentInfo **result)
1884
{
1885
int exitCode = 0;
1886
HKEY root;
1887
EnvironmentInfo *env = NULL;
1888
1889
if (!result) {
1890
return RC_INTERNAL_ERROR;
1891
}
1892
*result = NULL;
1893
1894
exitCode = explicitOverrideSearch(search, result);
1895
if (exitCode) {
1896
return exitCode;
1897
}
1898
1899
exitCode = virtualenvSearch(search, result);
1900
if (exitCode) {
1901
return exitCode;
1902
}
1903
1904
// If we aren't collecting all items to list them, we can exit now.
1905
if (env && !(search->list || search->listPaths)) {
1906
return 0;
1907
}
1908
1909
for (struct RegistrySearchInfo *info = REGISTRY_SEARCH; info->subkey; ++info) {
1910
if (ERROR_SUCCESS == RegOpenKeyExW(info->hive, info->subkey, 0, info->flags, &root)) {
1911
exitCode = registrySearch(search, result, root, info->sortKey, info->fallbackArch);
1912
RegCloseKey(root);
1913
}
1914
if (exitCode) {
1915
return exitCode;
1916
}
1917
}
1918
1919
if (search->limitToCompany) {
1920
debug(L"# Skipping APPX search due to PYLAUNCHER_LIMIT_TO_COMPANY\n");
1921
return 0;
1922
}
1923
1924
for (struct AppxSearchInfo *info = APPX_SEARCH; info->familyName; ++info) {
1925
exitCode = appxSearch(search, result, info->familyName, info->tag, info->sortKey);
1926
if (exitCode && exitCode != RC_NO_PYTHON) {
1927
return exitCode;
1928
}
1929
}
1930
1931
return 0;
1932
}
1933
1934
1935
/******************************************************************************\
1936
*** INSTALL ON DEMAND ***
1937
\******************************************************************************/
1938
1939
struct StoreSearchInfo {
1940
// The tag a user is looking for
1941
const wchar_t *tag;
1942
// The Store ID for a package if it can be installed from the Microsoft
1943
// Store. These are obtained from the dashboard at
1944
// https://partner.microsoft.com/dashboard
1945
const wchar_t *storeId;
1946
};
1947
1948
1949
struct StoreSearchInfo STORE_SEARCH[] = {
1950
{ L"3", /* 3.11 */ L"9NRWMJP3717K" },
1951
{ L"3.12", L"9NCVDN91XZQP" },
1952
{ L"3.11", L"9NRWMJP3717K" },
1953
{ L"3.10", L"9PJPW5LDXLZ5" },
1954
{ L"3.9", L"9P7QFQMJRFP7" },
1955
{ L"3.8", L"9MSSZTT1N39L" },
1956
{ NULL, NULL }
1957
};
1958
1959
1960
int
1961
_installEnvironment(const wchar_t *command, const wchar_t *arguments)
1962
{
1963
SHELLEXECUTEINFOW siw = {
1964
sizeof(SHELLEXECUTEINFOW),
1965
SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE,
1966
NULL, NULL,
1967
command, arguments, NULL,
1968
SW_SHOWNORMAL
1969
};
1970
1971
debug(L"# Installing with %s %s\n", command, arguments);
1972
if (isEnvVarSet(L"PYLAUNCHER_DRYRUN")) {
1973
debug(L"# Exiting due to PYLAUNCHER_DRYRUN\n");
1974
fflush(stdout);
1975
int mode = _setmode(_fileno(stdout), _O_U8TEXT);
1976
if (arguments) {
1977
fwprintf_s(stdout, L"\"%s\" %s\n", command, arguments);
1978
} else {
1979
fwprintf_s(stdout, L"\"%s\"\n", command);
1980
}
1981
fflush(stdout);
1982
if (mode >= 0) {
1983
_setmode(_fileno(stdout), mode);
1984
}
1985
return RC_INSTALLING;
1986
}
1987
1988
if (!ShellExecuteExW(&siw)) {
1989
return RC_NO_PYTHON;
1990
}
1991
1992
if (!siw.hProcess) {
1993
return RC_INSTALLING;
1994
}
1995
1996
WaitForSingleObjectEx(siw.hProcess, INFINITE, FALSE);
1997
DWORD exitCode = 0;
1998
if (GetExitCodeProcess(siw.hProcess, &exitCode) && exitCode == 0) {
1999
return 0;
2000
}
2001
return RC_INSTALLING;
2002
}
2003
2004
2005
const wchar_t *WINGET_COMMAND = L"Microsoft\\WindowsApps\\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\\winget.exe";
2006
const wchar_t *WINGET_ARGUMENTS = L"install -q %s --exact --accept-package-agreements --source msstore";
2007
2008
const wchar_t *MSSTORE_COMMAND = L"ms-windows-store://pdp/?productid=%s";
2009
2010
int
2011
installEnvironment(const SearchInfo *search)
2012
{
2013
// No tag? No installing
2014
if (!search->tag || !search->tagLength) {
2015
debug(L"# Cannot install Python with no tag specified\n");
2016
return RC_NO_PYTHON;
2017
}
2018
2019
// PEP 514 tag but not PythonCore? No installing
2020
if (!search->oldStyleTag &&
2021
search->company && search->companyLength &&
2022
0 != _compare(search->company, search->companyLength, L"PythonCore", -1)) {
2023
debug(L"# Cannot install for company %.*s\n", search->companyLength, search->company);
2024
return RC_NO_PYTHON;
2025
}
2026
2027
const wchar_t *storeId = NULL;
2028
for (struct StoreSearchInfo *info = STORE_SEARCH; info->tag; ++info) {
2029
if (0 == _compare(search->tag, search->tagLength, info->tag, -1)) {
2030
storeId = info->storeId;
2031
break;
2032
}
2033
}
2034
2035
if (!storeId) {
2036
return RC_NO_PYTHON;
2037
}
2038
2039
int exitCode;
2040
wchar_t command[MAXLEN];
2041
wchar_t arguments[MAXLEN];
2042
if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, command)) &&
2043
join(command, MAXLEN, WINGET_COMMAND) &&
2044
swprintf_s(arguments, MAXLEN, WINGET_ARGUMENTS, storeId)) {
2045
if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(command)) {
2046
formatWinerror(GetLastError(), arguments, MAXLEN);
2047
debug(L"# Skipping %s: %s\n", command, arguments);
2048
} else {
2049
fputws(L"Launching winget to install Python. The following output is from the install process\n\
2050
***********************************************************************\n", stdout);
2051
exitCode = _installEnvironment(command, arguments);
2052
if (exitCode == RC_INSTALLING) {
2053
fputws(L"***********************************************************************\n\
2054
Please check the install status and run your command again.", stderr);
2055
return exitCode;
2056
} else if (exitCode) {
2057
return exitCode;
2058
}
2059
fputws(L"***********************************************************************\n\
2060
Install appears to have succeeded. Searching for new matching installs.\n", stdout);
2061
return 0;
2062
}
2063
}
2064
2065
if (swprintf_s(command, MAXLEN, MSSTORE_COMMAND, storeId)) {
2066
fputws(L"Opening the Microsoft Store to install Python. After installation, "
2067
L"please run your command again.\n", stderr);
2068
exitCode = _installEnvironment(command, NULL);
2069
if (exitCode) {
2070
return exitCode;
2071
}
2072
return 0;
2073
}
2074
2075
return RC_NO_PYTHON;
2076
}
2077
2078
/******************************************************************************\
2079
*** ENVIRONMENT SELECT ***
2080
\******************************************************************************/
2081
2082
bool
2083
_companyMatches(const SearchInfo *search, const EnvironmentInfo *env)
2084
{
2085
if (!search->company || !search->companyLength) {
2086
return true;
2087
}
2088
return 0 == _compare(env->company, -1, search->company, search->companyLength);
2089
}
2090
2091
2092
bool
2093
_tagMatches(const SearchInfo *search, const EnvironmentInfo *env, int searchTagLength)
2094
{
2095
if (searchTagLength < 0) {
2096
searchTagLength = search->tagLength;
2097
}
2098
if (!search->tag || !searchTagLength) {
2099
return true;
2100
}
2101
return _startsWithSeparated(env->tag, -1, search->tag, searchTagLength, L".-");
2102
}
2103
2104
2105
bool
2106
_is32Bit(const EnvironmentInfo *env)
2107
{
2108
if (env->architecture) {
2109
return 0 == _compare(env->architecture, -1, L"32bit", -1);
2110
}
2111
return false;
2112
}
2113
2114
2115
int
2116
_selectEnvironment(const SearchInfo *search, EnvironmentInfo *env, EnvironmentInfo **best)
2117
{
2118
int exitCode = 0;
2119
while (env) {
2120
exitCode = _selectEnvironment(search, env->prev, best);
2121
2122
if (exitCode && exitCode != RC_NO_PYTHON) {
2123
return exitCode;
2124
} else if (!exitCode && *best) {
2125
return 0;
2126
}
2127
2128
if (env->highPriority && search->lowPriorityTag) {
2129
// This environment is marked high priority, and the search allows
2130
// it to be selected even though a tag is specified, so select it
2131
// gh-92817: this allows an active venv to be selected even when a
2132
// default tag has been found in py.ini or the environment
2133
*best = env;
2134
return 0;
2135
}
2136
2137
if (!search->oldStyleTag) {
2138
if (_companyMatches(search, env) && _tagMatches(search, env, -1)) {
2139
// Because of how our sort tree is set up, we will walk up the
2140
// "prev" side and implicitly select the "best" best. By
2141
// returning straight after a match, we skip the entire "next"
2142
// branch and won't ever select a "worse" best.
2143
*best = env;
2144
return 0;
2145
}
2146
} else if (0 == _compare(env->company, -1, L"PythonCore", -1)) {
2147
// Old-style tags can only match PythonCore entries
2148
2149
// If the tag ends with -64, we want to exclude 32-bit runtimes
2150
// (If the tag ends with -32, it will be filtered later)
2151
int tagLength = search->tagLength;
2152
bool exclude32Bit = false, only32Bit = false;
2153
if (tagLength > 3) {
2154
if (0 == _compareArgument(&search->tag[tagLength - 3], 3, L"-64", 3)) {
2155
tagLength -= 3;
2156
exclude32Bit = true;
2157
} else if (0 == _compareArgument(&search->tag[tagLength - 3], 3, L"-32", 3)) {
2158
tagLength -= 3;
2159
only32Bit = true;
2160
}
2161
}
2162
2163
if (_tagMatches(search, env, tagLength)) {
2164
if (exclude32Bit && _is32Bit(env)) {
2165
debug(L"# Excluding %s/%s because it looks like 32bit\n", env->company, env->tag);
2166
} else if (only32Bit && !_is32Bit(env)) {
2167
debug(L"# Excluding %s/%s because it doesn't look 32bit\n", env->company, env->tag);
2168
} else {
2169
*best = env;
2170
return 0;
2171
}
2172
}
2173
}
2174
2175
env = env->next;
2176
}
2177
return RC_NO_PYTHON;
2178
}
2179
2180
int
2181
selectEnvironment(const SearchInfo *search, EnvironmentInfo *root, EnvironmentInfo **best)
2182
{
2183
if (!best) {
2184
return RC_INTERNAL_ERROR;
2185
}
2186
if (!root) {
2187
*best = NULL;
2188
return RC_NO_PYTHON_AT_ALL;
2189
}
2190
2191
EnvironmentInfo *result = NULL;
2192
int exitCode = _selectEnvironment(search, root, &result);
2193
if (!exitCode) {
2194
*best = result;
2195
}
2196
2197
return exitCode;
2198
}
2199
2200
2201
/******************************************************************************\
2202
*** LIST ENVIRONMENTS ***
2203
\******************************************************************************/
2204
2205
#define TAGWIDTH 16
2206
2207
int
2208
_printEnvironment(const EnvironmentInfo *env, FILE *out, bool showPath, const wchar_t *argument)
2209
{
2210
if (showPath) {
2211
if (env->executablePath && env->executablePath[0]) {
2212
if (env->executableArgs && env->executableArgs[0]) {
2213
fwprintf(out, L" %-*s %s %s\n", TAGWIDTH, argument, env->executablePath, env->executableArgs);
2214
} else {
2215
fwprintf(out, L" %-*s %s\n", TAGWIDTH, argument, env->executablePath);
2216
}
2217
} else if (env->installDir && env->installDir[0]) {
2218
fwprintf(out, L" %-*s %s\n", TAGWIDTH, argument, env->installDir);
2219
} else {
2220
fwprintf(out, L" %s\n", argument);
2221
}
2222
} else if (env->displayName) {
2223
fwprintf(out, L" %-*s %s\n", TAGWIDTH, argument, env->displayName);
2224
} else {
2225
fwprintf(out, L" %s\n", argument);
2226
}
2227
return 0;
2228
}
2229
2230
2231
int
2232
_listAllEnvironments(EnvironmentInfo *env, FILE * out, bool showPath, EnvironmentInfo *defaultEnv)
2233
{
2234
wchar_t buffer[256];
2235
const int bufferSize = 256;
2236
while (env) {
2237
int exitCode = _listAllEnvironments(env->prev, out, showPath, defaultEnv);
2238
if (exitCode) {
2239
return exitCode;
2240
}
2241
2242
if (!env->company || !env->tag) {
2243
buffer[0] = L'\0';
2244
} else if (0 == _compare(env->company, -1, L"PythonCore", -1)) {
2245
swprintf_s(buffer, bufferSize, L"-V:%s", env->tag);
2246
} else {
2247
swprintf_s(buffer, bufferSize, L"-V:%s/%s", env->company, env->tag);
2248
}
2249
2250
if (env == defaultEnv) {
2251
wcscat_s(buffer, bufferSize, L" *");
2252
}
2253
2254
if (buffer[0]) {
2255
exitCode = _printEnvironment(env, out, showPath, buffer);
2256
if (exitCode) {
2257
return exitCode;
2258
}
2259
}
2260
2261
env = env->next;
2262
}
2263
return 0;
2264
}
2265
2266
2267
int
2268
listEnvironments(EnvironmentInfo *env, FILE * out, bool showPath, EnvironmentInfo *defaultEnv)
2269
{
2270
if (!env) {
2271
fwprintf_s(stdout, L"No installed Pythons found!\n");
2272
return 0;
2273
}
2274
2275
/* TODO: Do we want to display these?
2276
In favour, helps users see that '-3' is a good option
2277
Against, repeats the next line of output
2278
SearchInfo majorSearch;
2279
EnvironmentInfo *major;
2280
int exitCode;
2281
2282
if (showPath) {
2283
memset(&majorSearch, 0, sizeof(majorSearch));
2284
majorSearch.company = L"PythonCore";
2285
majorSearch.companyLength = -1;
2286
majorSearch.tag = L"3";
2287
majorSearch.tagLength = -1;
2288
majorSearch.oldStyleTag = true;
2289
major = NULL;
2290
exitCode = selectEnvironment(&majorSearch, env, &major);
2291
if (!exitCode && major) {
2292
exitCode = _printEnvironment(major, out, showPath, L"-3 *");
2293
isDefault = false;
2294
if (exitCode) {
2295
return exitCode;
2296
}
2297
}
2298
majorSearch.tag = L"2";
2299
major = NULL;
2300
exitCode = selectEnvironment(&majorSearch, env, &major);
2301
if (!exitCode && major) {
2302
exitCode = _printEnvironment(major, out, showPath, L"-2");
2303
if (exitCode) {
2304
return exitCode;
2305
}
2306
}
2307
}
2308
*/
2309
2310
int mode = _setmode(_fileno(out), _O_U8TEXT);
2311
int exitCode = _listAllEnvironments(env, out, showPath, defaultEnv);
2312
fflush(out);
2313
if (mode >= 0) {
2314
_setmode(_fileno(out), mode);
2315
}
2316
return exitCode;
2317
}
2318
2319
2320
/******************************************************************************\
2321
*** INTERPRETER LAUNCH ***
2322
\******************************************************************************/
2323
2324
2325
int
2326
calculateCommandLine(const SearchInfo *search, const EnvironmentInfo *launch, wchar_t *buffer, int bufferLength)
2327
{
2328
int exitCode = 0;
2329
const wchar_t *executablePath = NULL;
2330
2331
// Construct command line from a search override, or else the selected
2332
// environment's executablePath
2333
if (search->executablePath) {
2334
executablePath = search->executablePath;
2335
} else if (launch && launch->executablePath) {
2336
executablePath = launch->executablePath;
2337
}
2338
2339
// If we have an executable path, put it at the start of the command, but
2340
// only if the search allowed an override.
2341
// Otherwise, use the environment's installDir and the search's default
2342
// executable name.
2343
if (executablePath && search->allowExecutableOverride) {
2344
if (wcschr(executablePath, L' ') && executablePath[0] != L'"') {
2345
buffer[0] = L'"';
2346
exitCode = wcscpy_s(&buffer[1], bufferLength - 1, executablePath);
2347
if (!exitCode) {
2348
exitCode = wcscat_s(buffer, bufferLength, L"\"");
2349
}
2350
} else {
2351
exitCode = wcscpy_s(buffer, bufferLength, executablePath);
2352
}
2353
} else if (launch) {
2354
if (!launch->installDir) {
2355
fwprintf_s(stderr, L"Cannot launch %s %s because no install directory was specified",
2356
launch->company, launch->tag);
2357
exitCode = RC_NO_PYTHON;
2358
} else if (!search->executable || !search->executableLength) {
2359
fwprintf_s(stderr, L"Cannot launch %s %s because no executable name is available",
2360
launch->company, launch->tag);
2361
exitCode = RC_NO_PYTHON;
2362
} else {
2363
wchar_t executable[256];
2364
wcsncpy_s(executable, 256, search->executable, search->executableLength);
2365
if ((wcschr(launch->installDir, L' ') && launch->installDir[0] != L'"') ||
2366
(wcschr(executable, L' ') && executable[0] != L'"')) {
2367
buffer[0] = L'"';
2368
exitCode = wcscpy_s(&buffer[1], bufferLength - 1, launch->installDir);
2369
if (!exitCode) {
2370
exitCode = join(buffer, bufferLength, executable) ? 0 : RC_NO_MEMORY;
2371
}
2372
if (!exitCode) {
2373
exitCode = wcscat_s(buffer, bufferLength, L"\"");
2374
}
2375
} else {
2376
exitCode = wcscpy_s(buffer, bufferLength, launch->installDir);
2377
if (!exitCode) {
2378
exitCode = join(buffer, bufferLength, executable) ? 0 : RC_NO_MEMORY;
2379
}
2380
}
2381
}
2382
} else {
2383
exitCode = RC_NO_PYTHON;
2384
}
2385
2386
if (!exitCode && launch && launch->executableArgs) {
2387
exitCode = wcscat_s(buffer, bufferLength, L" ");
2388
if (!exitCode) {
2389
exitCode = wcscat_s(buffer, bufferLength, launch->executableArgs);
2390
}
2391
}
2392
2393
if (!exitCode && search->executableArgs) {
2394
if (search->executableArgsLength < 0) {
2395
exitCode = wcscat_s(buffer, bufferLength, search->executableArgs);
2396
} else if (search->executableArgsLength > 0) {
2397
int end = (int)wcsnlen_s(buffer, MAXLEN);
2398
if (end < bufferLength - (search->executableArgsLength + 1)) {
2399
exitCode = wcsncpy_s(&buffer[end], bufferLength - end,
2400
search->executableArgs, search->executableArgsLength);
2401
}
2402
}
2403
}
2404
2405
if (!exitCode && search->restOfCmdLine) {
2406
exitCode = wcscat_s(buffer, bufferLength, search->restOfCmdLine);
2407
}
2408
2409
return exitCode;
2410
}
2411
2412
2413
2414
BOOL
2415
_safeDuplicateHandle(HANDLE in, HANDLE * pout, const wchar_t *nameForError)
2416
{
2417
BOOL ok;
2418
HANDLE process = GetCurrentProcess();
2419
DWORD rc;
2420
2421
*pout = NULL;
2422
ok = DuplicateHandle(process, in, process, pout, 0, TRUE,
2423
DUPLICATE_SAME_ACCESS);
2424
if (!ok) {
2425
rc = GetLastError();
2426
if (rc == ERROR_INVALID_HANDLE) {
2427
debug(L"DuplicateHandle returned ERROR_INVALID_HANDLE\n");
2428
ok = TRUE;
2429
}
2430
else {
2431
winerror(0, L"Failed to duplicate %s handle", nameForError);
2432
}
2433
}
2434
return ok;
2435
}
2436
2437
BOOL WINAPI
2438
ctrl_c_handler(DWORD code)
2439
{
2440
return TRUE; /* We just ignore all control events. */
2441
}
2442
2443
2444
int
2445
launchEnvironment(const SearchInfo *search, const EnvironmentInfo *launch, wchar_t *launchCommand)
2446
{
2447
HANDLE job;
2448
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
2449
DWORD rc;
2450
BOOL ok;
2451
STARTUPINFOW si;
2452
PROCESS_INFORMATION pi;
2453
2454
// If this is a dryrun, do not actually launch
2455
if (isEnvVarSet(L"PYLAUNCHER_DRYRUN")) {
2456
debug(L"LaunchCommand: %s\n", launchCommand);
2457
debug(L"# Exiting due to PYLAUNCHER_DRYRUN variable\n");
2458
fflush(stdout);
2459
int mode = _setmode(_fileno(stdout), _O_U8TEXT);
2460
fwprintf(stdout, L"%s\n", launchCommand);
2461
fflush(stdout);
2462
if (mode >= 0) {
2463
_setmode(_fileno(stdout), mode);
2464
}
2465
return 0;
2466
}
2467
2468
#if defined(_WINDOWS)
2469
/*
2470
When explorer launches a Windows (GUI) application, it displays
2471
the "app starting" (the "pointer + hourglass") cursor for a number
2472
of seconds, or until the app does something UI-ish (eg, creating a
2473
window, or fetching a message). As this launcher doesn't do this
2474
directly, that cursor remains even after the child process does these
2475
things. We avoid that by doing a simple post+get message.
2476
See http://bugs.python.org/issue17290
2477
*/
2478
MSG msg;
2479
2480
PostMessage(0, 0, 0, 0);
2481
GetMessage(&msg, 0, 0, 0);
2482
#endif
2483
2484
debug(L"# about to run: %s\n", launchCommand);
2485
job = CreateJobObject(NULL, NULL);
2486
ok = QueryInformationJobObject(job, JobObjectExtendedLimitInformation,
2487
&info, sizeof(info), &rc);
2488
if (!ok || (rc != sizeof(info)) || !job) {
2489
winerror(0, L"Failed to query job information");
2490
return RC_CREATE_PROCESS;
2491
}
2492
info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
2493
JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
2494
ok = SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info,
2495
sizeof(info));
2496
if (!ok) {
2497
winerror(0, L"Failed to update job information");
2498
return RC_CREATE_PROCESS;
2499
}
2500
memset(&si, 0, sizeof(si));
2501
GetStartupInfoW(&si);
2502
if (!_safeDuplicateHandle(GetStdHandle(STD_INPUT_HANDLE), &si.hStdInput, L"stdin") ||
2503
!_safeDuplicateHandle(GetStdHandle(STD_OUTPUT_HANDLE), &si.hStdOutput, L"stdout") ||
2504
!_safeDuplicateHandle(GetStdHandle(STD_ERROR_HANDLE), &si.hStdError, L"stderr")) {
2505
return RC_NO_STD_HANDLES;
2506
}
2507
2508
ok = SetConsoleCtrlHandler(ctrl_c_handler, TRUE);
2509
if (!ok) {
2510
winerror(0, L"Failed to update Control-C handler");
2511
return RC_NO_STD_HANDLES;
2512
}
2513
2514
si.dwFlags = STARTF_USESTDHANDLES;
2515
ok = CreateProcessW(NULL, launchCommand, NULL, NULL, TRUE,
2516
0, NULL, NULL, &si, &pi);
2517
if (!ok) {
2518
winerror(0, L"Unable to create process using '%s'", launchCommand);
2519
return RC_CREATE_PROCESS;
2520
}
2521
AssignProcessToJobObject(job, pi.hProcess);
2522
CloseHandle(pi.hThread);
2523
WaitForSingleObjectEx(pi.hProcess, INFINITE, FALSE);
2524
ok = GetExitCodeProcess(pi.hProcess, &rc);
2525
if (!ok) {
2526
winerror(0, L"Failed to get exit code of process");
2527
return RC_CREATE_PROCESS;
2528
}
2529
debug(L"child process exit code: %d\n", rc);
2530
return rc;
2531
}
2532
2533
2534
/******************************************************************************\
2535
*** PROCESS CONTROLLER ***
2536
\******************************************************************************/
2537
2538
2539
int
2540
performSearch(SearchInfo *search, EnvironmentInfo **envs)
2541
{
2542
// First parse the command line for options
2543
int exitCode = parseCommandLine(search);
2544
if (exitCode) {
2545
return exitCode;
2546
}
2547
2548
// Check for a shebang line in our script file
2549
// (or return quickly if no script file was specified)
2550
exitCode = checkShebang(search);
2551
switch (exitCode) {
2552
case 0:
2553
case RC_NO_SHEBANG:
2554
case RC_RECURSIVE_SHEBANG:
2555
break;
2556
default:
2557
return exitCode;
2558
}
2559
2560
// Resolve old-style tags (possibly from a shebang) against py.ini entries
2561
// and environment variables.
2562
exitCode = checkDefaults(search);
2563
if (exitCode) {
2564
return exitCode;
2565
}
2566
2567
// If debugging is enabled, list our search criteria
2568
dumpSearchInfo(search);
2569
2570
// Find all matching environments
2571
exitCode = collectEnvironments(search, envs);
2572
if (exitCode) {
2573
return exitCode;
2574
}
2575
2576
return 0;
2577
}
2578
2579
2580
int
2581
process(int argc, wchar_t ** argv)
2582
{
2583
int exitCode = 0;
2584
int searchExitCode = 0;
2585
SearchInfo search = {0};
2586
EnvironmentInfo *envs = NULL;
2587
EnvironmentInfo *env = NULL;
2588
wchar_t launchCommand[MAXLEN];
2589
2590
memset(launchCommand, 0, sizeof(launchCommand));
2591
2592
if (isEnvVarSet(L"PYLAUNCHER_DEBUG")) {
2593
setvbuf(stderr, (char *)NULL, _IONBF, 0);
2594
log_fp = stderr;
2595
debug(L"argv0: %s\nversion: %S\n", argv[0], PY_VERSION);
2596
}
2597
2598
DWORD len = GetEnvironmentVariableW(L"PYLAUNCHER_LIMIT_TO_COMPANY", NULL, 0);
2599
if (len > 1) {
2600
wchar_t *limitToCompany = allocSearchInfoBuffer(&search, len);
2601
search.limitToCompany = limitToCompany;
2602
if (0 == GetEnvironmentVariableW(L"PYLAUNCHER_LIMIT_TO_COMPANY", limitToCompany, len)) {
2603
exitCode = RC_INTERNAL_ERROR;
2604
winerror(0, L"Failed to read PYLAUNCHER_LIMIT_TO_COMPANY variable");
2605
goto abort;
2606
}
2607
}
2608
2609
search.originalCmdLine = GetCommandLineW();
2610
2611
exitCode = performSearch(&search, &envs);
2612
if (exitCode) {
2613
goto abort;
2614
}
2615
2616
// Display the help text, but only exit on error
2617
if (search.help) {
2618
exitCode = showHelpText(argv);
2619
if (exitCode) {
2620
goto abort;
2621
}
2622
}
2623
2624
// Select best environment
2625
// This is early so that we can show the default when listing, but all
2626
// responses to any errors occur later.
2627
searchExitCode = selectEnvironment(&search, envs, &env);
2628
2629
// List all environments, then exit
2630
if (search.list || search.listPaths) {
2631
exitCode = listEnvironments(envs, stdout, search.listPaths, env);
2632
goto abort;
2633
}
2634
2635
// When debugging, list all discovered environments anyway
2636
if (log_fp) {
2637
exitCode = listEnvironments(envs, log_fp, true, NULL);
2638
if (exitCode) {
2639
goto abort;
2640
}
2641
}
2642
2643
// We searched earlier, so if we didn't find anything, now we react
2644
exitCode = searchExitCode;
2645
// If none found, and if permitted, install it
2646
if (exitCode == RC_NO_PYTHON && isEnvVarSet(L"PYLAUNCHER_ALLOW_INSTALL") ||
2647
isEnvVarSet(L"PYLAUNCHER_ALWAYS_INSTALL")) {
2648
exitCode = installEnvironment(&search);
2649
if (!exitCode) {
2650
// Successful install, so we need to re-scan and select again
2651
env = NULL;
2652
exitCode = performSearch(&search, &envs);
2653
if (exitCode) {
2654
goto abort;
2655
}
2656
exitCode = selectEnvironment(&search, envs, &env);
2657
}
2658
}
2659
if (exitCode == RC_NO_PYTHON) {
2660
fputws(L"No suitable Python runtime found\n", stderr);
2661
fputws(L"Pass --list (-0) to see all detected environments on your machine\n", stderr);
2662
if (!isEnvVarSet(L"PYLAUNCHER_ALLOW_INSTALL") && search.oldStyleTag) {
2663
fputws(L"or set environment variable PYLAUNCHER_ALLOW_INSTALL to use winget\n"
2664
L"or open the Microsoft Store to the requested version.\n", stderr);
2665
}
2666
goto abort;
2667
}
2668
if (exitCode == RC_NO_PYTHON_AT_ALL) {
2669
fputws(L"No installed Python found!\n", stderr);
2670
goto abort;
2671
}
2672
if (exitCode) {
2673
goto abort;
2674
}
2675
2676
if (env) {
2677
debug(L"env.company: %s\nenv.tag: %s\n", env->company, env->tag);
2678
} else {
2679
debug(L"env.company: (null)\nenv.tag: (null)\n");
2680
}
2681
2682
exitCode = calculateCommandLine(&search, env, launchCommand, sizeof(launchCommand) / sizeof(launchCommand[0]));
2683
if (exitCode) {
2684
goto abort;
2685
}
2686
2687
// Launch selected runtime
2688
exitCode = launchEnvironment(&search, env, launchCommand);
2689
2690
abort:
2691
freeSearchInfo(&search);
2692
freeEnvironmentInfo(envs);
2693
return exitCode;
2694
}
2695
2696
2697
#if defined(_WINDOWS)
2698
2699
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
2700
LPWSTR lpstrCmd, int nShow)
2701
{
2702
return process(__argc, __wargv);
2703
}
2704
2705
#else
2706
2707
int cdecl wmain(int argc, wchar_t ** argv)
2708
{
2709
return process(argc, argv);
2710
}
2711
2712
#endif
2713
2714