summaryrefslogtreecommitdiffstats
path: root/PC
diff options
context:
space:
mode:
Diffstat (limited to 'PC')
-rw-r--r--PC/classicAppCompat.can.xml1
-rw-r--r--PC/classicAppCompat.catbin0 -> 10984 bytes
-rw-r--r--PC/classicAppCompat.sccd28
-rw-r--r--PC/getpathp.c8
-rw-r--r--PC/icons/pythonwx150.pngbin0 -> 8187 bytes
-rw-r--r--PC/icons/pythonwx44.pngbin0 -> 2232 bytes
-rw-r--r--PC/icons/pythonx150.pngbin0 -> 8271 bytes
-rw-r--r--PC/icons/pythonx44.pngbin0 -> 2178 bytes
-rw-r--r--PC/icons/pythonx50.pngbin0 -> 2190 bytes
-rw-r--r--PC/launcher.c220
-rw-r--r--PC/layout/__init__.py0
-rw-r--r--PC/layout/__main__.py14
-rw-r--r--PC/layout/main.py612
-rw-r--r--PC/layout/support/__init__.py0
-rw-r--r--PC/layout/support/appxmanifest.py487
-rw-r--r--PC/layout/support/catalog.py44
-rw-r--r--PC/layout/support/constants.py28
-rw-r--r--PC/layout/support/distutils.command.bdist_wininst.py25
-rw-r--r--PC/layout/support/filesets.py100
-rw-r--r--PC/layout/support/logging.py93
-rw-r--r--PC/layout/support/options.py122
-rw-r--r--PC/layout/support/pip.py79
-rw-r--r--PC/layout/support/props.py110
-rw-r--r--PC/layout/support/python.props56
-rw-r--r--PC/pylauncher.rc6
-rw-r--r--PC/python_uwp.cpp226
-rw-r--r--PC/store_info.txt146
27 files changed, 2389 insertions, 16 deletions
diff --git a/PC/classicAppCompat.can.xml b/PC/classicAppCompat.can.xml
new file mode 100644
index 0000000..f00475c
--- /dev/null
+++ b/PC/classicAppCompat.can.xml
@@ -0,0 +1 @@
+<CustomCapabilityDescriptor xmlns="http://schemas.microsoft.com/appx/2016/sccd" xmlns:s="http://schemas.microsoft.com/appx/2016/sccd"><CustomCapabilities><CustomCapability Name="Microsoft.classicAppCompat_8wekyb3d8bbwe"></CustomCapability></CustomCapabilities><AuthorizedEntities><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.14_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.15_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.14_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"></AuthorizedEntity><AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.15_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"></AuthorizedEntity></AuthorizedEntities></CustomCapabilityDescriptor> \ No newline at end of file
diff --git a/PC/classicAppCompat.cat b/PC/classicAppCompat.cat
new file mode 100644
index 0000000..3d21359
--- /dev/null
+++ b/PC/classicAppCompat.cat
Binary files differ
diff --git a/PC/classicAppCompat.sccd b/PC/classicAppCompat.sccd
new file mode 100644
index 0000000..9764898
--- /dev/null
+++ b/PC/classicAppCompat.sccd
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<CustomCapabilityDescriptor xmlns="http://schemas.microsoft.com/appx/2016/sccd" xmlns:s="http://schemas.microsoft.com/appx/2016/sccd">
+ <CustomCapabilities>
+ <CustomCapability Name="Microsoft.classicAppCompat_8wekyb3d8bbwe"/>
+ </CustomCapabilities>
+ <AuthorizedEntities>
+ <!--PFN for store installation-->
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.14_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.15_qbz5n2kfra8p0" CertificateSignatureHash="0000000000000000000000000000000000000000000000000000000000000000"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.14_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"/>
+ <AuthorizedEntity AppPackageFamilyName="PythonSoftwareFoundation.Python.3.15_qbz5n2kfra8p0" CertificateSignatureHash="279cd652c4e252bfbe5217ac722205d7729ba409148cfa9e6d9e5b1cb94eaff1"/>
+ </AuthorizedEntities>
+ <!--Once signed, this file can no longer be modified-->
+ <Catalog>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</Catalog></CustomCapabilityDescriptor>
diff --git a/PC/getpathp.c b/PC/getpathp.c
index 25f371f..452501a 100644
--- a/PC/getpathp.c
+++ b/PC/getpathp.c
@@ -536,10 +536,16 @@ static _PyInitError
get_program_full_path(const _PyCoreConfig *core_config,
PyCalculatePath *calculate, _PyPathConfig *config)
{
+ const wchar_t *pyvenv_launcher;
wchar_t program_full_path[MAXPATHLEN+1];
memset(program_full_path, 0, sizeof(program_full_path));
- if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) {
+ /* The launcher may need to force the executable path to a
+ * different environment, so override it here. */
+ pyvenv_launcher = _wgetenv(L"__PYVENV_LAUNCHER__");
+ if (pyvenv_launcher && pyvenv_launcher[0]) {
+ wcscpy_s(program_full_path, MAXPATHLEN+1, pyvenv_launcher);
+ } else if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) {
/* GetModuleFileName should never fail when passed NULL */
return _Py_INIT_ERR("Cannot determine program path");
}
diff --git a/PC/icons/pythonwx150.png b/PC/icons/pythonwx150.png
new file mode 100644
index 0000000..4c3eb31
--- /dev/null
+++ b/PC/icons/pythonwx150.png
Binary files differ
diff --git a/PC/icons/pythonwx44.png b/PC/icons/pythonwx44.png
new file mode 100644
index 0000000..e3b32a8
--- /dev/null
+++ b/PC/icons/pythonwx44.png
Binary files differ
diff --git a/PC/icons/pythonx150.png b/PC/icons/pythonx150.png
new file mode 100644
index 0000000..5f8d304
--- /dev/null
+++ b/PC/icons/pythonx150.png
Binary files differ
diff --git a/PC/icons/pythonx44.png b/PC/icons/pythonx44.png
new file mode 100644
index 0000000..3881daa
--- /dev/null
+++ b/PC/icons/pythonx44.png
Binary files differ
diff --git a/PC/icons/pythonx50.png b/PC/icons/pythonx50.png
new file mode 100644
index 0000000..7cc3aec
--- /dev/null
+++ b/PC/icons/pythonx50.png
Binary files differ
diff --git a/PC/launcher.c b/PC/launcher.c
index 2c2da76..0242f26 100644
--- a/PC/launcher.c
+++ b/PC/launcher.c
@@ -28,7 +28,7 @@
#define RC_NO_PYTHON 103
#define RC_NO_MEMORY 104
/*
- * SCRIPT_WRAPPER is used to choose between two variants of an executable built
+ * SCRIPT_WRAPPER is used to choose one of the variants of an executable built
* from this source file. If not defined, the PEP 397 Python launcher is built;
* if defined, a script launcher of the type used by setuptools is built, which
* looks for a script name related to the executable name and runs that script
@@ -40,6 +40,15 @@
#if defined(SCRIPT_WRAPPER)
#define RC_NO_SCRIPT 105
#endif
+/*
+ * VENV_REDIRECT is used to choose the variant that looks for an adjacent or
+ * one-level-higher pyvenv.cfg, and uses its "home" property to locate and
+ * launch the original python.exe.
+ */
+#if defined(VENV_REDIRECT)
+#define RC_NO_VENV_CFG 106
+#define RC_BAD_VENV_CFG 107
+#endif
/* Just for now - static definition */
@@ -97,7 +106,7 @@ error(int rc, wchar_t * format, ... )
#if !defined(_WINDOWS)
fwprintf(stderr, L"%ls\n", message);
#else
- MessageBox(NULL, message, TEXT("Python Launcher is sorry to say ..."),
+ MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...",
MB_OK);
#endif
exit(rc);
@@ -131,6 +140,17 @@ static wchar_t * get_env(wchar_t * key)
return buf;
}
+#if defined(_DEBUG)
+#if defined(_WINDOWS)
+
+#define PYTHON_EXECUTABLE L"pythonw_d.exe"
+
+#else
+
+#define PYTHON_EXECUTABLE L"python_d.exe"
+
+#endif
+#else
#if defined(_WINDOWS)
#define PYTHON_EXECUTABLE L"pythonw.exe"
@@ -140,6 +160,7 @@ static wchar_t * get_env(wchar_t * key)
#define PYTHON_EXECUTABLE L"python.exe"
#endif
+#endif
#define MAX_VERSION_SIZE 4
@@ -1457,6 +1478,87 @@ show_python_list(wchar_t ** argv)
return FALSE; /* If this has been called we cannot continue */
}
+#if defined(VENV_REDIRECT)
+
+static int
+find_home_value(const char *buffer, const char **start, DWORD *length)
+{
+ for (const char *s = strstr(buffer, "home"); s; s = strstr(s + 1, "\nhome")) {
+ if (*s == '\n') {
+ ++s;
+ }
+ for (int i = 4; i > 0 && *s; --i, ++s);
+
+ while (*s && iswspace(*s)) {
+ ++s;
+ }
+ if (*s != L'=') {
+ continue;
+ }
+
+ do {
+ ++s;
+ } while (*s && iswspace(*s));
+
+ *start = s;
+ char *nl = strchr(s, '\n');
+ if (nl) {
+ *length = (DWORD)((ptrdiff_t)nl - (ptrdiff_t)s);
+ } else {
+ *length = (DWORD)strlen(s);
+ }
+ return 1;
+ }
+ return 0;
+}
+#endif
+
+static wchar_t *
+wcsdup_pad(const wchar_t *s, int padding, int *newlen)
+{
+ size_t len = wcslen(s);
+ len += 1 + padding;
+ wchar_t *r = (wchar_t *)malloc(len * sizeof(wchar_t));
+ if (!r) {
+ return NULL;
+ }
+ if (wcscpy_s(r, len, s)) {
+ free(r);
+ return NULL;
+ }
+ *newlen = len < MAXINT ? (int)len : MAXINT;
+ return r;
+}
+
+static wchar_t *
+get_process_name()
+{
+ DWORD bufferLen = MAX_PATH;
+ DWORD len = bufferLen;
+ wchar_t *r = NULL;
+
+ while (!r) {
+ r = (wchar_t *)malloc(bufferLen * sizeof(wchar_t));
+ if (!r) {
+ error(RC_NO_MEMORY, L"out of memory");
+ return NULL;
+ }
+ len = GetModuleFileNameW(NULL, r, bufferLen);
+ if (len == 0) {
+ free(r);
+ error(0, L"Failed to get module name");
+ return NULL;
+ } else if (len == bufferLen &&
+ GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ free(r);
+ r = NULL;
+ bufferLen *= 2;
+ }
+ }
+
+ return r;
+}
+
static int
process(int argc, wchar_t ** argv)
{
@@ -1464,21 +1566,27 @@ process(int argc, wchar_t ** argv)
wchar_t * command;
wchar_t * executable;
wchar_t * p;
+ wchar_t * argv0;
int rc = 0;
- size_t plen;
INSTALLED_PYTHON * ip;
BOOL valid;
DWORD size, attrs;
- HRESULT hr;
wchar_t message[MSGSIZE];
void * version_data;
VS_FIXEDFILEINFO * file_info;
UINT block_size;
- int index;
-#if defined(SCRIPT_WRAPPER)
+#if defined(VENV_REDIRECT)
+ wchar_t * venv_cfg_path;
int newlen;
+#elif defined(SCRIPT_WRAPPER)
wchar_t * newcommand;
wchar_t * av[2];
+ int newlen;
+ HRESULT hr;
+ int index;
+#else
+ HRESULT hr;
+ int index;
#endif
setvbuf(stderr, (char *)NULL, _IONBF, 0);
@@ -1496,6 +1604,7 @@ process(int argc, wchar_t ** argv)
#else
debug(L"launcher executable: Console\n");
#endif
+#if !defined(VENV_REDIRECT)
/* Get the local appdata folder (non-roaming) */
hr = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA,
NULL, 0, appdata_ini_path);
@@ -1504,9 +1613,7 @@ process(int argc, wchar_t ** argv)
appdata_ini_path[0] = L'\0';
}
else {
- plen = wcslen(appdata_ini_path);
- p = &appdata_ini_path[plen];
- wcsncpy_s(p, MAX_PATH - plen, L"\\py.ini", _TRUNCATE);
+ wcsncat_s(appdata_ini_path, MAX_PATH, L"\\py.ini", _TRUNCATE);
attrs = GetFileAttributesW(appdata_ini_path);
if (attrs == INVALID_FILE_ATTRIBUTES) {
debug(L"File '%ls' non-existent\n", appdata_ini_path);
@@ -1515,8 +1622,9 @@ process(int argc, wchar_t ** argv)
debug(L"Using local configuration file '%ls'\n", appdata_ini_path);
}
}
- plen = GetModuleFileNameW(NULL, launcher_ini_path, MAX_PATH);
- size = GetFileVersionInfoSizeW(launcher_ini_path, &size);
+#endif
+ argv0 = get_process_name();
+ size = GetFileVersionInfoSizeW(argv0, &size);
if (size == 0) {
winerror(GetLastError(), message, MSGSIZE);
debug(L"GetFileVersionInfoSize failed: %ls\n", message);
@@ -1524,7 +1632,7 @@ process(int argc, wchar_t ** argv)
else {
version_data = malloc(size);
if (version_data) {
- valid = GetFileVersionInfoW(launcher_ini_path, 0, size,
+ valid = GetFileVersionInfoW(argv0, 0, size,
version_data);
if (!valid)
debug(L"GetFileVersionInfo failed: %X\n", GetLastError());
@@ -1541,15 +1649,51 @@ process(int argc, wchar_t ** argv)
free(version_data);
}
}
+
+#if defined(VENV_REDIRECT)
+ /* Allocate some extra space for new filenames */
+ venv_cfg_path = wcsdup_pad(argv0, 32, &newlen);
+ if (!venv_cfg_path) {
+ error(RC_NO_MEMORY, L"Failed to copy module name");
+ }
+ p = wcsrchr(venv_cfg_path, L'\\');
+
+ if (p == NULL) {
+ error(RC_NO_VENV_CFG, L"No pyvenv.cfg file");
+ }
+ p[0] = L'\0';
+ wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg");
+ attrs = GetFileAttributesW(venv_cfg_path);
+ if (attrs == INVALID_FILE_ATTRIBUTES) {
+ debug(L"File '%ls' non-existent\n", venv_cfg_path);
+ p[0] = '\0';
+ p = wcsrchr(venv_cfg_path, L'\\');
+ if (p != NULL) {
+ p[0] = '\0';
+ wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg");
+ attrs = GetFileAttributesW(venv_cfg_path);
+ if (attrs == INVALID_FILE_ATTRIBUTES) {
+ debug(L"File '%ls' non-existent\n", venv_cfg_path);
+ error(RC_NO_VENV_CFG, L"No pyvenv.cfg file");
+ }
+ }
+ }
+ debug(L"Using venv configuration file '%ls'\n", venv_cfg_path);
+#else
+ /* Allocate some extra space for new filenames */
+ if (wcscpy_s(launcher_ini_path, MAX_PATH, argv0)) {
+ error(RC_NO_MEMORY, L"Failed to copy module name");
+ }
p = wcsrchr(launcher_ini_path, L'\\');
+
if (p == NULL) {
debug(L"GetModuleFileNameW returned value has no backslash: %ls\n",
launcher_ini_path);
launcher_ini_path[0] = L'\0';
}
else {
- wcsncpy_s(p, MAX_PATH - (p - launcher_ini_path), L"\\py.ini",
- _TRUNCATE);
+ p[0] = L'\0';
+ wcscat_s(launcher_ini_path, MAX_PATH, L"\\py.ini");
attrs = GetFileAttributesW(launcher_ini_path);
if (attrs == INVALID_FILE_ATTRIBUTES) {
debug(L"File '%ls' non-existent\n", launcher_ini_path);
@@ -1558,6 +1702,7 @@ process(int argc, wchar_t ** argv)
debug(L"Using global configuration file '%ls'\n", launcher_ini_path);
}
}
+#endif
command = skip_me(GetCommandLineW());
debug(L"Called with command line: %ls\n", command);
@@ -1593,6 +1738,52 @@ process(int argc, wchar_t ** argv)
command = newcommand;
valid = FALSE;
}
+#elif defined(VENV_REDIRECT)
+ {
+ FILE *f;
+ char buffer[4096]; /* 4KB should be enough for anybody */
+ char *start;
+ DWORD len, cch, cch_actual;
+ size_t cb;
+ if (_wfopen_s(&f, venv_cfg_path, L"r")) {
+ error(RC_BAD_VENV_CFG, L"Cannot read '%ls'", venv_cfg_path);
+ }
+ cb = fread_s(buffer, sizeof(buffer), sizeof(buffer[0]),
+ sizeof(buffer) / sizeof(buffer[0]), f);
+ fclose(f);
+
+ if (!find_home_value(buffer, &start, &len)) {
+ error(RC_BAD_VENV_CFG, L"Cannot find home in '%ls'",
+ venv_cfg_path);
+ }
+
+ cch = MultiByteToWideChar(CP_UTF8, 0, start, len, NULL, 0);
+ if (!cch) {
+ error(0, L"Cannot determine memory for home path");
+ }
+ cch += (DWORD)wcslen(PYTHON_EXECUTABLE) + 1 + 1; /* include sep and null */
+ executable = (wchar_t *)malloc(cch * sizeof(wchar_t));
+ cch_actual = MultiByteToWideChar(CP_UTF8, 0, start, len, executable, cch);
+ if (!cch_actual) {
+ error(RC_BAD_VENV_CFG, L"Cannot decode home path in '%ls'",
+ venv_cfg_path);
+ }
+ if (executable[cch_actual - 1] != L'\\') {
+ executable[cch_actual++] = L'\\';
+ executable[cch_actual] = L'\0';
+ }
+ if (wcscat_s(executable, cch, PYTHON_EXECUTABLE)) {
+ error(RC_BAD_VENV_CFG, L"Cannot create executable path from '%ls'",
+ venv_cfg_path);
+ }
+ if (GetFileAttributesW(executable) == INVALID_FILE_ATTRIBUTES) {
+ error(RC_NO_PYTHON, L"No Python at '%ls'", executable);
+ }
+ if (!SetEnvironmentVariableW(L"__PYVENV_LAUNCHER__", argv0)) {
+ error(0, L"Failed to set launcher environment");
+ }
+ valid = 1;
+ }
#else
if (argc <= 1) {
valid = FALSE;
@@ -1600,7 +1791,6 @@ process(int argc, wchar_t ** argv)
}
else {
p = argv[1];
- plen = wcslen(p);
if ((argc == 2) && // list version args
(!wcsncmp(p, L"-0", wcslen(L"-0")) ||
!wcsncmp(p, L"--list", wcslen(L"--list"))))
diff --git a/PC/layout/__init__.py b/PC/layout/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/PC/layout/__init__.py
diff --git a/PC/layout/__main__.py b/PC/layout/__main__.py
new file mode 100644
index 0000000..f7aa1e6
--- /dev/null
+++ b/PC/layout/__main__.py
@@ -0,0 +1,14 @@
+import sys
+
+try:
+ import layout
+except ImportError:
+ # Failed to import our package, which likely means we were started directly
+ # Add the additional search path needed to locate our module.
+ from pathlib import Path
+
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
+
+from layout.main import main
+
+sys.exit(int(main() or 0))
diff --git a/PC/layout/main.py b/PC/layout/main.py
new file mode 100644
index 0000000..82d0536
--- /dev/null
+++ b/PC/layout/main.py
@@ -0,0 +1,612 @@
+"""
+Generates a layout of Python for Windows from a build.
+
+See python make_layout.py --help for usage.
+"""
+
+__author__ = "Steve Dower <steve.dower@python.org>"
+__version__ = "3.8"
+
+import argparse
+import functools
+import os
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+import zipfile
+
+from pathlib import Path
+
+if __name__ == "__main__":
+ # Started directly, so enable relative imports
+ __path__ = [str(Path(__file__).resolve().parent)]
+
+from .support.appxmanifest import *
+from .support.catalog import *
+from .support.constants import *
+from .support.filesets import *
+from .support.logging import *
+from .support.options import *
+from .support.pip import *
+from .support.props import *
+
+BDIST_WININST_FILES_ONLY = FileNameSet("wininst-*", "bdist_wininst.py")
+BDIST_WININST_STUB = "PC/layout/support/distutils.command.bdist_wininst.py"
+
+TEST_PYDS_ONLY = FileStemSet("xxlimited", "_ctypes_test", "_test*")
+TEST_DIRS_ONLY = FileNameSet("test", "tests")
+
+IDLE_DIRS_ONLY = FileNameSet("idlelib")
+
+TCLTK_PYDS_ONLY = FileStemSet("tcl*", "tk*", "_tkinter")
+TCLTK_DIRS_ONLY = FileNameSet("tkinter", "turtledemo")
+TCLTK_FILES_ONLY = FileNameSet("turtle.py")
+
+VENV_DIRS_ONLY = FileNameSet("venv", "ensurepip")
+
+EXCLUDE_FROM_PYDS = FileStemSet("python*", "pyshellext")
+EXCLUDE_FROM_LIB = FileNameSet("*.pyc", "__pycache__", "*.pickle")
+EXCLUDE_FROM_PACKAGED_LIB = FileNameSet("readme.txt")
+EXCLUDE_FROM_COMPILE = FileNameSet("badsyntax_*", "bad_*")
+EXCLUDE_FROM_CATALOG = FileSuffixSet(".exe", ".pyd", ".dll")
+
+REQUIRED_DLLS = FileStemSet("libcrypto*", "libssl*")
+
+LIB2TO3_GRAMMAR_FILES = FileNameSet("Grammar.txt", "PatternGrammar.txt")
+
+PY_FILES = FileSuffixSet(".py")
+PYC_FILES = FileSuffixSet(".pyc")
+CAT_FILES = FileSuffixSet(".cat")
+CDF_FILES = FileSuffixSet(".cdf")
+
+DATA_DIRS = FileNameSet("data")
+
+TOOLS_DIRS = FileNameSet("scripts", "i18n", "pynche", "demo", "parser")
+TOOLS_FILES = FileSuffixSet(".py", ".pyw", ".txt")
+
+
+def get_lib_layout(ns):
+ def _c(f):
+ if f in EXCLUDE_FROM_LIB:
+ return False
+ if f.is_dir():
+ if f in TEST_DIRS_ONLY:
+ return ns.include_tests
+ if f in TCLTK_DIRS_ONLY:
+ return ns.include_tcltk
+ if f in IDLE_DIRS_ONLY:
+ return ns.include_idle
+ if f in VENV_DIRS_ONLY:
+ return ns.include_venv
+ else:
+ if f in TCLTK_FILES_ONLY:
+ return ns.include_tcltk
+ if f in BDIST_WININST_FILES_ONLY:
+ return ns.include_bdist_wininst
+ return True
+
+ for dest, src in rglob(ns.source / "Lib", "**/*", _c):
+ yield dest, src
+
+ if not ns.include_bdist_wininst:
+ src = ns.source / BDIST_WININST_STUB
+ yield Path("distutils/command/bdist_wininst.py"), src
+
+
+def get_tcltk_lib(ns):
+ if not ns.include_tcltk:
+ return
+
+ tcl_lib = os.getenv("TCL_LIBRARY")
+ if not tcl_lib or not os.path.isdir(tcl_lib):
+ try:
+ with open(ns.build / "TCL_LIBRARY.env", "r", encoding="utf-8-sig") as f:
+ tcl_lib = f.read().strip()
+ except FileNotFoundError:
+ pass
+ if not tcl_lib or not os.path.isdir(tcl_lib):
+ warn("Failed to find TCL_LIBRARY")
+ return
+
+ for dest, src in rglob(Path(tcl_lib).parent, "**/*"):
+ yield "tcl/{}".format(dest), src
+
+
+def get_layout(ns):
+ def in_build(f, dest="", new_name=None):
+ n, _, x = f.rpartition(".")
+ n = new_name or n
+ src = ns.build / f
+ if ns.debug and src not in REQUIRED_DLLS:
+ if not src.stem.endswith("_d"):
+ src = src.parent / (src.stem + "_d" + src.suffix)
+ if not n.endswith("_d"):
+ n += "_d"
+ f = n + "." + x
+ yield dest + n + "." + x, src
+ if ns.include_symbols:
+ pdb = src.with_suffix(".pdb")
+ if pdb.is_file():
+ yield dest + n + ".pdb", pdb
+ if ns.include_dev:
+ lib = src.with_suffix(".lib")
+ if lib.is_file():
+ yield "libs/" + n + ".lib", lib
+
+ yield from in_build("python_uwp.exe", new_name="python")
+ yield from in_build("pythonw_uwp.exe", new_name="pythonw")
+
+ yield from in_build(PYTHON_DLL_NAME)
+
+ if ns.include_launchers:
+ if ns.include_pip:
+ yield from in_build("python_uwp.exe", new_name="pip")
+ if ns.include_idle:
+ yield from in_build("pythonw_uwp.exe", new_name="idle")
+
+ if ns.include_stable:
+ yield from in_build(PYTHON_STABLE_DLL_NAME)
+
+ for dest, src in rglob(ns.build, "vcruntime*.dll"):
+ yield dest, src
+
+ for dest, src in rglob(ns.build, ("*.pyd", "*.dll")):
+ if src.stem.endswith("_d") != bool(ns.debug) and src not in REQUIRED_DLLS:
+ continue
+ if src in EXCLUDE_FROM_PYDS:
+ continue
+ if src in TEST_PYDS_ONLY and not ns.include_tests:
+ continue
+ if src in TCLTK_PYDS_ONLY and not ns.include_tcltk:
+ continue
+
+ yield from in_build(src.name, dest="" if ns.flat_dlls else "DLLs/")
+
+ if ns.zip_lib:
+ zip_name = PYTHON_ZIP_NAME
+ yield zip_name, ns.temp / zip_name
+ else:
+ for dest, src in get_lib_layout(ns):
+ yield "Lib/{}".format(dest), src
+
+ if ns.include_venv:
+ yield from in_build("venvlauncher.exe", "Lib/venv/scripts/nt/", "python")
+ yield from in_build("venvwlauncher.exe", "Lib/venv/scripts/nt/", "pythonw")
+
+ if ns.include_tools:
+
+ def _c(d):
+ if d.is_dir():
+ return d in TOOLS_DIRS
+ return d in TOOLS_FILES
+
+ for dest, src in rglob(ns.source / "Tools", "**/*", _c):
+ yield "Tools/{}".format(dest), src
+
+ if ns.include_underpth:
+ yield PYTHON_PTH_NAME, ns.temp / PYTHON_PTH_NAME
+
+ if ns.include_dev:
+
+ def _c(d):
+ if d.is_dir():
+ return d.name != "internal"
+ return True
+
+ for dest, src in rglob(ns.source / "Include", "**/*.h", _c):
+ yield "include/{}".format(dest), src
+ src = ns.source / "PC" / "pyconfig.h"
+ yield "include/pyconfig.h", src
+
+ for dest, src in get_tcltk_lib(ns):
+ yield dest, src
+
+ if ns.include_pip:
+ pip_dir = get_pip_dir(ns)
+ if not pip_dir.is_dir():
+ log_warning("Failed to find {} - pip will not be included", pip_dir)
+ else:
+ pkg_root = "packages/{}" if ns.zip_lib else "Lib/site-packages/{}"
+ for dest, src in rglob(pip_dir, "**/*"):
+ if src in EXCLUDE_FROM_LIB or src in EXCLUDE_FROM_PACKAGED_LIB:
+ continue
+ yield pkg_root.format(dest), src
+
+ if ns.include_chm:
+ for dest, src in rglob(ns.doc_build / "htmlhelp", PYTHON_CHM_NAME):
+ yield "Doc/{}".format(dest), src
+
+ if ns.include_html_doc:
+ for dest, src in rglob(ns.doc_build / "html", "**/*"):
+ yield "Doc/html/{}".format(dest), src
+
+ if ns.include_props:
+ for dest, src in get_props_layout(ns):
+ yield dest, src
+
+ for dest, src in get_appx_layout(ns):
+ yield dest, src
+
+ if ns.include_cat:
+ if ns.flat_dlls:
+ yield ns.include_cat.name, ns.include_cat
+ else:
+ yield "DLLs/{}".format(ns.include_cat.name), ns.include_cat
+
+
+def _compile_one_py(src, dest, name, optimize):
+ import py_compile
+
+ if dest is not None:
+ dest = str(dest)
+
+ try:
+ return Path(
+ py_compile.compile(
+ str(src),
+ dest,
+ str(name),
+ doraise=True,
+ optimize=optimize,
+ invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH,
+ )
+ )
+ except py_compile.PyCompileError:
+ log_warning("Failed to compile {}", src)
+ return None
+
+
+def _py_temp_compile(src, ns, dest_dir=None):
+ if not ns.precompile or src not in PY_FILES or src.parent in DATA_DIRS:
+ return None
+
+ dest = (dest_dir or ns.temp) / (src.stem + ".py")
+ return _compile_one_py(src, dest.with_suffix(".pyc"), dest, optimize=2)
+
+
+def _write_to_zip(zf, dest, src, ns):
+ pyc = _py_temp_compile(src, ns)
+ if pyc:
+ try:
+ zf.write(str(pyc), dest.with_suffix(".pyc"))
+ finally:
+ try:
+ pyc.unlink()
+ except:
+ log_exception("Failed to delete {}", pyc)
+ return
+
+ if src in LIB2TO3_GRAMMAR_FILES:
+ from lib2to3.pgen2.driver import load_grammar
+
+ tmp = ns.temp / src.name
+ try:
+ shutil.copy(src, tmp)
+ load_grammar(str(tmp))
+ for f in ns.temp.glob(src.stem + "*.pickle"):
+ zf.write(str(f), str(dest.parent / f.name))
+ try:
+ f.unlink()
+ except:
+ log_exception("Failed to delete {}", f)
+ except:
+ log_exception("Failed to compile {}", src)
+ finally:
+ try:
+ tmp.unlink()
+ except:
+ log_exception("Failed to delete {}", tmp)
+
+ zf.write(str(src), str(dest))
+
+
+def generate_source_files(ns):
+ if ns.zip_lib:
+ zip_name = PYTHON_ZIP_NAME
+ zip_path = ns.temp / zip_name
+ if zip_path.is_file():
+ zip_path.unlink()
+ elif zip_path.is_dir():
+ log_error(
+ "Cannot create zip file because a directory exists by the same name"
+ )
+ return
+ log_info("Generating {} in {}", zip_name, ns.temp)
+ ns.temp.mkdir(parents=True, exist_ok=True)
+ with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
+ for dest, src in get_lib_layout(ns):
+ _write_to_zip(zf, dest, src, ns)
+
+ if ns.include_underpth:
+ log_info("Generating {} in {}", PYTHON_PTH_NAME, ns.temp)
+ ns.temp.mkdir(parents=True, exist_ok=True)
+ with open(ns.temp / PYTHON_PTH_NAME, "w", encoding="utf-8") as f:
+ if ns.zip_lib:
+ print(PYTHON_ZIP_NAME, file=f)
+ if ns.include_pip:
+ print("packages", file=f)
+ else:
+ print("Lib", file=f)
+ print("Lib/site-packages", file=f)
+ if not ns.flat_dlls:
+ print("DLLs", file=f)
+ print(".", file=f)
+ print(file=f)
+ print("# Uncomment to run site.main() automatically", file=f)
+ print("#import site", file=f)
+
+ if ns.include_appxmanifest:
+ log_info("Generating AppxManifest.xml in {}", ns.temp)
+ ns.temp.mkdir(parents=True, exist_ok=True)
+
+ with open(ns.temp / "AppxManifest.xml", "wb") as f:
+ f.write(get_appxmanifest(ns))
+
+ with open(ns.temp / "_resources.xml", "wb") as f:
+ f.write(get_resources_xml(ns))
+
+ if ns.include_pip:
+ pip_dir = get_pip_dir(ns)
+ if not (pip_dir / "pip").is_dir():
+ log_info("Extracting pip to {}", pip_dir)
+ pip_dir.mkdir(parents=True, exist_ok=True)
+ extract_pip_files(ns)
+
+ if ns.include_props:
+ log_info("Generating {} in {}", PYTHON_PROPS_NAME, ns.temp)
+ ns.temp.mkdir(parents=True, exist_ok=True)
+ with open(ns.temp / PYTHON_PROPS_NAME, "wb") as f:
+ f.write(get_props(ns))
+
+
+def _create_zip_file(ns):
+ if not ns.zip:
+ return None
+
+ if ns.zip.is_file():
+ try:
+ ns.zip.unlink()
+ except OSError:
+ log_exception("Unable to remove {}", ns.zip)
+ sys.exit(8)
+ elif ns.zip.is_dir():
+ log_error("Cannot create ZIP file because {} is a directory", ns.zip)
+ sys.exit(8)
+
+ ns.zip.parent.mkdir(parents=True, exist_ok=True)
+ return zipfile.ZipFile(ns.zip, "w", zipfile.ZIP_DEFLATED)
+
+
+def copy_files(files, ns):
+ if ns.copy:
+ ns.copy.mkdir(parents=True, exist_ok=True)
+
+ try:
+ total = len(files)
+ except TypeError:
+ total = None
+ count = 0
+
+ zip_file = _create_zip_file(ns)
+ try:
+ need_compile = []
+ in_catalog = []
+
+ for dest, src in files:
+ count += 1
+ if count % 10 == 0:
+ if total:
+ log_info("Processed {:>4} of {} files", count, total)
+ else:
+ log_info("Processed {} files", count)
+ log_debug("Processing {!s}", src)
+
+ if (
+ ns.precompile
+ and src in PY_FILES
+ and src not in EXCLUDE_FROM_COMPILE
+ and src.parent not in DATA_DIRS
+ and os.path.normcase(str(dest)).startswith(os.path.normcase("Lib"))
+ ):
+ if ns.copy:
+ need_compile.append((dest, ns.copy / dest))
+ else:
+ (ns.temp / "Lib" / dest).parent.mkdir(parents=True, exist_ok=True)
+ shutil.copy2(src, ns.temp / "Lib" / dest)
+ need_compile.append((dest, ns.temp / "Lib" / dest))
+
+ if src not in EXCLUDE_FROM_CATALOG:
+ in_catalog.append((src.name, src))
+
+ if ns.copy:
+ log_debug("Copy {} -> {}", src, ns.copy / dest)
+ (ns.copy / dest).parent.mkdir(parents=True, exist_ok=True)
+ try:
+ shutil.copy2(src, ns.copy / dest)
+ except shutil.SameFileError:
+ pass
+
+ if ns.zip:
+ log_debug("Zip {} into {}", src, ns.zip)
+ zip_file.write(src, str(dest))
+
+ if need_compile:
+ for dest, src in need_compile:
+ compiled = [
+ _compile_one_py(src, None, dest, optimize=0),
+ _compile_one_py(src, None, dest, optimize=1),
+ _compile_one_py(src, None, dest, optimize=2),
+ ]
+ for c in compiled:
+ if not c:
+ continue
+ cdest = Path(dest).parent / Path(c).relative_to(src.parent)
+ if ns.zip:
+ log_debug("Zip {} into {}", c, ns.zip)
+ zip_file.write(c, str(cdest))
+ in_catalog.append((cdest.name, cdest))
+
+ if ns.catalog:
+ # Just write out the CDF now. Compilation and signing is
+ # an extra step
+ log_info("Generating {}", ns.catalog)
+ ns.catalog.parent.mkdir(parents=True, exist_ok=True)
+ write_catalog(ns.catalog, in_catalog)
+
+ finally:
+ if zip_file:
+ zip_file.close()
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-v", help="Increase verbosity", action="count")
+ parser.add_argument(
+ "-s",
+ "--source",
+ metavar="dir",
+ help="The directory containing the repository root",
+ type=Path,
+ default=None,
+ )
+ parser.add_argument(
+ "-b", "--build", metavar="dir", help="Specify the build directory", type=Path
+ )
+ parser.add_argument(
+ "--doc-build",
+ metavar="dir",
+ help="Specify the docs build directory",
+ type=Path,
+ default=None,
+ )
+ parser.add_argument(
+ "--copy",
+ metavar="directory",
+ help="The name of the directory to copy an extracted layout to",
+ type=Path,
+ default=None,
+ )
+ parser.add_argument(
+ "--zip",
+ metavar="file",
+ help="The ZIP file to write all files to",
+ type=Path,
+ default=None,
+ )
+ parser.add_argument(
+ "--catalog",
+ metavar="file",
+ help="The CDF file to write catalog entries to",
+ type=Path,
+ default=None,
+ )
+ parser.add_argument(
+ "--log",
+ metavar="file",
+ help="Write all operations to the specified file",
+ type=Path,
+ default=None,
+ )
+ parser.add_argument(
+ "-t",
+ "--temp",
+ metavar="file",
+ help="A temporary working directory",
+ type=Path,
+ default=None,
+ )
+ parser.add_argument(
+ "-d", "--debug", help="Include debug build", action="store_true"
+ )
+ parser.add_argument(
+ "-p",
+ "--precompile",
+ help="Include .pyc files instead of .py",
+ action="store_true",
+ )
+ parser.add_argument(
+ "-z", "--zip-lib", help="Include library in a ZIP file", action="store_true"
+ )
+ parser.add_argument(
+ "--flat-dlls", help="Does not create a DLLs directory", action="store_true"
+ )
+ parser.add_argument(
+ "-a",
+ "--include-all",
+ help="Include all optional components",
+ action="store_true",
+ )
+ parser.add_argument(
+ "--include-cat",
+ metavar="file",
+ help="Specify the catalog file to include",
+ type=Path,
+ default=None,
+ )
+ for opt, help in get_argparse_options():
+ parser.add_argument(opt, help=help, action="store_true")
+
+ ns = parser.parse_args()
+ update_presets(ns)
+
+ ns.source = ns.source or (Path(__file__).resolve().parent.parent.parent)
+ ns.build = ns.build or Path(sys.executable).parent
+ ns.temp = ns.temp or Path(tempfile.mkdtemp())
+ ns.doc_build = ns.doc_build or (ns.source / "Doc" / "build")
+ if not ns.source.is_absolute():
+ ns.source = (Path.cwd() / ns.source).resolve()
+ if not ns.build.is_absolute():
+ ns.build = (Path.cwd() / ns.build).resolve()
+ if not ns.temp.is_absolute():
+ ns.temp = (Path.cwd() / ns.temp).resolve()
+ if not ns.doc_build.is_absolute():
+ ns.doc_build = (Path.cwd() / ns.doc_build).resolve()
+ if ns.include_cat and not ns.include_cat.is_absolute():
+ ns.include_cat = (Path.cwd() / ns.include_cat).resolve()
+
+ if ns.copy and not ns.copy.is_absolute():
+ ns.copy = (Path.cwd() / ns.copy).resolve()
+ if ns.zip and not ns.zip.is_absolute():
+ ns.zip = (Path.cwd() / ns.zip).resolve()
+ if ns.catalog and not ns.catalog.is_absolute():
+ ns.catalog = (Path.cwd() / ns.catalog).resolve()
+
+ configure_logger(ns)
+
+ log_info(
+ """OPTIONS
+Source: {ns.source}
+Build: {ns.build}
+Temp: {ns.temp}
+
+Copy to: {ns.copy}
+Zip to: {ns.zip}
+Catalog: {ns.catalog}""",
+ ns=ns,
+ )
+
+ if ns.include_idle and not ns.include_tcltk:
+ log_warning("Assuming --include-tcltk to support --include-idle")
+ ns.include_tcltk = True
+
+ try:
+ generate_source_files(ns)
+ files = list(get_layout(ns))
+ copy_files(files, ns)
+ except KeyboardInterrupt:
+ log_info("Interrupted by Ctrl+C")
+ return 3
+ except SystemExit:
+ raise
+ except:
+ log_exception("Unhandled error")
+
+ if error_was_logged():
+ log_error("Errors occurred.")
+ return 1
+
+
+if __name__ == "__main__":
+ sys.exit(int(main() or 0))
diff --git a/PC/layout/support/__init__.py b/PC/layout/support/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/PC/layout/support/__init__.py
diff --git a/PC/layout/support/appxmanifest.py b/PC/layout/support/appxmanifest.py
new file mode 100644
index 0000000..c5dda70
--- /dev/null
+++ b/PC/layout/support/appxmanifest.py
@@ -0,0 +1,487 @@
+"""
+File generation for APPX/MSIX manifests.
+"""
+
+__author__ = "Steve Dower <steve.dower@python.org>"
+__version__ = "3.8"
+
+
+import collections
+import ctypes
+import io
+import os
+import sys
+
+from pathlib import Path, PureWindowsPath
+from xml.etree import ElementTree as ET
+
+from .constants import *
+
+__all__ = []
+
+
+def public(f):
+ __all__.append(f.__name__)
+ return f
+
+
+APPX_DATA = dict(
+ Name="PythonSoftwareFoundation.Python.{}".format(VER_DOT),
+ Version="{}.{}.{}.0".format(VER_MAJOR, VER_MINOR, VER_FIELD3),
+ Publisher=os.getenv(
+ "APPX_DATA_PUBLISHER", "CN=4975D53F-AA7E-49A5-8B49-EA4FDC1BB66B"
+ ),
+ DisplayName="Python {}".format(VER_DOT),
+ Description="The Python {} runtime and console.".format(VER_DOT),
+ ProcessorArchitecture="x64" if IS_X64 else "x86",
+)
+
+PYTHON_VE_DATA = dict(
+ DisplayName="Python {}".format(VER_DOT),
+ Description="Python interactive console",
+ Square150x150Logo="_resources/pythonx150.png",
+ Square44x44Logo="_resources/pythonx44.png",
+ BackgroundColor="transparent",
+)
+
+PYTHONW_VE_DATA = dict(
+ DisplayName="Python {} (Windowed)".format(VER_DOT),
+ Description="Python windowed app launcher",
+ Square150x150Logo="_resources/pythonwx150.png",
+ Square44x44Logo="_resources/pythonwx44.png",
+ BackgroundColor="transparent",
+ AppListEntry="none",
+)
+
+PIP_VE_DATA = dict(
+ DisplayName="pip (Python {})".format(VER_DOT),
+ Description="pip package manager for Python {}".format(VER_DOT),
+ Square150x150Logo="_resources/pythonx150.png",
+ Square44x44Logo="_resources/pythonx44.png",
+ BackgroundColor="transparent",
+ AppListEntry="none",
+)
+
+IDLE_VE_DATA = dict(
+ DisplayName="IDLE (Python {})".format(VER_DOT),
+ Description="IDLE editor for Python {}".format(VER_DOT),
+ Square150x150Logo="_resources/pythonwx150.png",
+ Square44x44Logo="_resources/pythonwx44.png",
+ BackgroundColor="transparent",
+)
+
+APPXMANIFEST_NS = {
+ "": "http://schemas.microsoft.com/appx/manifest/foundation/windows10",
+ "m": "http://schemas.microsoft.com/appx/manifest/foundation/windows10",
+ "uap": "http://schemas.microsoft.com/appx/manifest/uap/windows10",
+ "rescap": "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities",
+ "rescap4": "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/4",
+ "desktop4": "http://schemas.microsoft.com/appx/manifest/desktop/windows10/4",
+ "desktop6": "http://schemas.microsoft.com/appx/manifest/desktop/windows10/6",
+ "uap3": "http://schemas.microsoft.com/appx/manifest/uap/windows10/3",
+ "uap4": "http://schemas.microsoft.com/appx/manifest/uap/windows10/4",
+ "uap5": "http://schemas.microsoft.com/appx/manifest/uap/windows10/5",
+}
+
+APPXMANIFEST_TEMPLATE = """<?xml version="1.0" encoding="utf-8"?>
+<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
+ xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
+ xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
+ xmlns:rescap4="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/4"
+ xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
+ xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4"
+ xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5">
+ <Identity Name=""
+ Version=""
+ Publisher=""
+ ProcessorArchitecture="" />
+ <Properties>
+ <DisplayName></DisplayName>
+ <PublisherDisplayName>Python Software Foundation</PublisherDisplayName>
+ <Description></Description>
+ <Logo>_resources/pythonx50.png</Logo>
+ </Properties>
+ <Resources>
+ <Resource Language="en-US" />
+ </Resources>
+ <Dependencies>
+ <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="" />
+ </Dependencies>
+ <Capabilities>
+ <rescap:Capability Name="runFullTrust"/>
+ </Capabilities>
+ <Applications>
+ </Applications>
+ <Extensions>
+ </Extensions>
+</Package>"""
+
+
+RESOURCES_XML_TEMPLATE = r"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--This file is input for makepri.exe. It should be excluded from the final package.-->
+<resources targetOsVersion="10.0.0" majorVersion="1">
+ <packaging>
+ <autoResourcePackage qualifier="Language"/>
+ <autoResourcePackage qualifier="Scale"/>
+ <autoResourcePackage qualifier="DXFeatureLevel"/>
+ </packaging>
+ <index root="\" startIndexAt="\">
+ <default>
+ <qualifier name="Language" value="en-US"/>
+ <qualifier name="Contrast" value="standard"/>
+ <qualifier name="Scale" value="100"/>
+ <qualifier name="HomeRegion" value="001"/>
+ <qualifier name="TargetSize" value="256"/>
+ <qualifier name="LayoutDirection" value="LTR"/>
+ <qualifier name="Theme" value="dark"/>
+ <qualifier name="AlternateForm" value=""/>
+ <qualifier name="DXFeatureLevel" value="DX9"/>
+ <qualifier name="Configuration" value=""/>
+ <qualifier name="DeviceFamily" value="Universal"/>
+ <qualifier name="Custom" value=""/>
+ </default>
+ <indexer-config type="folder" foldernameAsQualifier="true" filenameAsQualifier="true" qualifierDelimiter="$"/>
+ <indexer-config type="resw" convertDotsToSlashes="true" initialPath=""/>
+ <indexer-config type="resjson" initialPath=""/>
+ <indexer-config type="PRI"/>
+ </index>
+</resources>"""
+
+
+SCCD_FILENAME = "PC/classicAppCompat.sccd"
+
+REGISTRY = {
+ "HKCU\\Software\\Python\\PythonCore": {
+ VER_DOT: {
+ "DisplayName": APPX_DATA["DisplayName"],
+ "SupportUrl": "https://www.python.org/",
+ "SysArchitecture": "64bit" if IS_X64 else "32bit",
+ "SysVersion": VER_DOT,
+ "Version": "{}.{}.{}".format(VER_MAJOR, VER_MINOR, VER_MICRO),
+ "InstallPath": {
+ # I have no idea why the trailing spaces are needed, but they seem to be needed.
+ "": "[{AppVPackageRoot}][ ]",
+ "ExecutablePath": "[{AppVPackageRoot}]python.exe[ ]",
+ "WindowedExecutablePath": "[{AppVPackageRoot}]pythonw.exe[ ]",
+ },
+ "Help": {
+ "Main Python Documentation": {
+ "_condition": lambda ns: ns.include_chm,
+ "": "[{{AppVPackageRoot}}]Doc\\{}[ ]".format(
+ PYTHON_CHM_NAME
+ ),
+ },
+ "Local Python Documentation": {
+ "_condition": lambda ns: ns.include_html_doc,
+ "": "[{AppVPackageRoot}]Doc\\html\\index.html[ ]",
+ },
+ "Online Python Documentation": {
+ "": "https://docs.python.org/{}".format(VER_DOT)
+ },
+ },
+ "Idle": {
+ "_condition": lambda ns: ns.include_idle,
+ "": "[{AppVPackageRoot}]Lib\\idlelib\\idle.pyw[ ]",
+ },
+ }
+ }
+}
+
+
+def get_packagefamilyname(name, publisher_id):
+ class PACKAGE_ID(ctypes.Structure):
+ _fields_ = [
+ ("reserved", ctypes.c_uint32),
+ ("processorArchitecture", ctypes.c_uint32),
+ ("version", ctypes.c_uint64),
+ ("name", ctypes.c_wchar_p),
+ ("publisher", ctypes.c_wchar_p),
+ ("resourceId", ctypes.c_wchar_p),
+ ("publisherId", ctypes.c_wchar_p),
+ ]
+ _pack_ = 4
+
+ pid = PACKAGE_ID(0, 0, 0, name, publisher_id, None, None)
+ result = ctypes.create_unicode_buffer(256)
+ result_len = ctypes.c_uint32(256)
+ r = ctypes.windll.kernel32.PackageFamilyNameFromId(
+ pid, ctypes.byref(result_len), result
+ )
+ if r:
+ raise OSError(r, "failed to get package family name")
+ return result.value[: result_len.value]
+
+
+def _fixup_sccd(ns, sccd, new_hash=None):
+ if not new_hash:
+ return sccd
+
+ NS = dict(s="http://schemas.microsoft.com/appx/2016/sccd")
+ with open(sccd, "rb") as f:
+ xml = ET.parse(f)
+
+ pfn = get_packagefamilyname(APPX_DATA["Name"], APPX_DATA["Publisher"])
+
+ ae = xml.find("s:AuthorizedEntities", NS)
+ ae.clear()
+
+ e = ET.SubElement(ae, ET.QName(NS["s"], "AuthorizedEntity"))
+ e.set("AppPackageFamilyName", pfn)
+ e.set("CertificateSignatureHash", new_hash)
+
+ for e in xml.findall("s:Catalog", NS):
+ e.text = "FFFF"
+
+ sccd = ns.temp / sccd.name
+ sccd.parent.mkdir(parents=True, exist_ok=True)
+ with open(sccd, "wb") as f:
+ xml.write(f, encoding="utf-8")
+
+ return sccd
+
+
+@public
+def get_appx_layout(ns):
+ if not ns.include_appxmanifest:
+ return
+
+ yield "AppxManifest.xml", ns.temp / "AppxManifest.xml"
+ yield "_resources.xml", ns.temp / "_resources.xml"
+ icons = ns.source / "PC" / "icons"
+ yield "_resources/pythonx44.png", icons / "pythonx44.png"
+ yield "_resources/pythonx44$targetsize-44_altform-unplated.png", icons / "pythonx44.png"
+ yield "_resources/pythonx50.png", icons / "pythonx50.png"
+ yield "_resources/pythonx50$targetsize-50_altform-unplated.png", icons / "pythonx50.png"
+ yield "_resources/pythonx150.png", icons / "pythonx150.png"
+ yield "_resources/pythonx150$targetsize-150_altform-unplated.png", icons / "pythonx150.png"
+ yield "_resources/pythonwx44.png", icons / "pythonwx44.png"
+ yield "_resources/pythonwx44$targetsize-44_altform-unplated.png", icons / "pythonwx44.png"
+ yield "_resources/pythonwx150.png", icons / "pythonwx150.png"
+ yield "_resources/pythonwx150$targetsize-150_altform-unplated.png", icons / "pythonwx150.png"
+ sccd = ns.source / SCCD_FILENAME
+ if sccd.is_file():
+ # This should only be set for side-loading purposes.
+ sccd = _fixup_sccd(ns, sccd, os.getenv("APPX_DATA_SHA256"))
+ yield sccd.name, sccd
+
+
+def find_or_add(xml, element, attr=None, always_add=False):
+ if always_add:
+ e = None
+ else:
+ q = element
+ if attr:
+ q += "[@{}='{}']".format(*attr)
+ e = xml.find(q, APPXMANIFEST_NS)
+ if e is None:
+ prefix, _, name = element.partition(":")
+ name = ET.QName(APPXMANIFEST_NS[prefix or ""], name)
+ e = ET.SubElement(xml, name)
+ if attr:
+ e.set(*attr)
+ return e
+
+
+def _get_app(xml, appid):
+ if appid:
+ app = xml.find(
+ "m:Applications/m:Application[@Id='{}']".format(appid), APPXMANIFEST_NS
+ )
+ if app is None:
+ raise LookupError(appid)
+ else:
+ app = xml
+ return app
+
+
+def add_visual(xml, appid, data):
+ app = _get_app(xml, appid)
+ e = find_or_add(app, "uap:VisualElements")
+ for i in data.items():
+ e.set(*i)
+ return e
+
+
+def add_alias(xml, appid, alias, subsystem="windows"):
+ app = _get_app(xml, appid)
+ e = find_or_add(app, "m:Extensions")
+ e = find_or_add(e, "uap5:Extension", ("Category", "windows.appExecutionAlias"))
+ e = find_or_add(e, "uap5:AppExecutionAlias")
+ e.set(ET.QName(APPXMANIFEST_NS["desktop4"], "Subsystem"), subsystem)
+ e = find_or_add(e, "uap5:ExecutionAlias", ("Alias", alias))
+
+
+def add_file_type(xml, appid, name, suffix, parameters='"%1"'):
+ app = _get_app(xml, appid)
+ e = find_or_add(app, "m:Extensions")
+ e = find_or_add(e, "uap3:Extension", ("Category", "windows.fileTypeAssociation"))
+ e = find_or_add(e, "uap3:FileTypeAssociation", ("Name", name))
+ e.set("Parameters", parameters)
+ e = find_or_add(e, "uap:SupportedFileTypes")
+ if isinstance(suffix, str):
+ suffix = [suffix]
+ for s in suffix:
+ ET.SubElement(e, ET.QName(APPXMANIFEST_NS["uap"], "FileType")).text = s
+
+
+def add_application(
+ ns, xml, appid, executable, aliases, visual_element, subsystem, file_types
+):
+ node = xml.find("m:Applications", APPXMANIFEST_NS)
+ suffix = "_d.exe" if ns.debug else ".exe"
+ app = ET.SubElement(
+ node,
+ ET.QName(APPXMANIFEST_NS[""], "Application"),
+ {
+ "Id": appid,
+ "Executable": executable + suffix,
+ "EntryPoint": "Windows.FullTrustApplication",
+ ET.QName(APPXMANIFEST_NS["desktop4"], "SupportsMultipleInstances"): "true",
+ },
+ )
+ if visual_element:
+ add_visual(app, None, visual_element)
+ for alias in aliases:
+ add_alias(app, None, alias + suffix, subsystem)
+ if file_types:
+ add_file_type(app, None, *file_types)
+ return app
+
+
+def _get_registry_entries(ns, root="", d=None):
+ r = root if root else PureWindowsPath("")
+ if d is None:
+ d = REGISTRY
+ for key, value in d.items():
+ if key == "_condition":
+ continue
+ elif isinstance(value, dict):
+ cond = value.get("_condition")
+ if cond and not cond(ns):
+ continue
+ fullkey = r
+ for part in PureWindowsPath(key).parts:
+ fullkey /= part
+ if len(fullkey.parts) > 1:
+ yield str(fullkey), None, None
+ yield from _get_registry_entries(ns, fullkey, value)
+ elif len(r.parts) > 1:
+ yield str(r), key, value
+
+
+def add_registry_entries(ns, xml):
+ e = find_or_add(xml, "m:Extensions")
+ e = find_or_add(e, "rescap4:Extension")
+ e.set("Category", "windows.classicAppCompatKeys")
+ e.set("EntryPoint", "Windows.FullTrustApplication")
+ e = ET.SubElement(e, ET.QName(APPXMANIFEST_NS["rescap4"], "ClassicAppCompatKeys"))
+ for name, valuename, value in _get_registry_entries(ns):
+ k = ET.SubElement(
+ e, ET.QName(APPXMANIFEST_NS["rescap4"], "ClassicAppCompatKey")
+ )
+ k.set("Name", name)
+ if value:
+ k.set("ValueName", valuename)
+ k.set("Value", value)
+ k.set("ValueType", "REG_SZ")
+
+
+def disable_registry_virtualization(xml):
+ e = find_or_add(xml, "m:Properties")
+ e = find_or_add(e, "desktop6:RegistryWriteVirtualization")
+ e.text = "disabled"
+ e = find_or_add(xml, "m:Capabilities")
+ e = find_or_add(e, "rescap:Capability", ("Name", "unvirtualizedResources"))
+
+
+@public
+def get_appxmanifest(ns):
+ for k, v in APPXMANIFEST_NS.items():
+ ET.register_namespace(k, v)
+ ET.register_namespace("", APPXMANIFEST_NS["m"])
+
+ xml = ET.parse(io.StringIO(APPXMANIFEST_TEMPLATE))
+ NS = APPXMANIFEST_NS
+ QN = ET.QName
+
+ node = xml.find("m:Identity", NS)
+ for k in node.keys():
+ value = APPX_DATA.get(k)
+ if value:
+ node.set(k, value)
+
+ for node in xml.find("m:Properties", NS):
+ value = APPX_DATA.get(node.tag.rpartition("}")[2])
+ if value:
+ node.text = value
+
+ winver = sys.getwindowsversion()[:3]
+ if winver < (10, 0, 17763):
+ winver = 10, 0, 17763
+ find_or_add(xml, "m:Dependencies/m:TargetDeviceFamily").set(
+ "MaxVersionTested", "{}.{}.{}.0".format(*winver)
+ )
+
+ if winver > (10, 0, 17763):
+ disable_registry_virtualization(xml)
+
+ app = add_application(
+ ns,
+ xml,
+ "Python",
+ "python",
+ ["python", "python{}".format(VER_MAJOR), "python{}".format(VER_DOT)],
+ PYTHON_VE_DATA,
+ "console",
+ ("python.file", [".py"]),
+ )
+
+ add_application(
+ ns,
+ xml,
+ "PythonW",
+ "pythonw",
+ ["pythonw", "pythonw{}".format(VER_MAJOR), "pythonw{}".format(VER_DOT)],
+ PYTHONW_VE_DATA,
+ "windows",
+ ("python.windowedfile", [".pyw"]),
+ )
+
+ if ns.include_pip and ns.include_launchers:
+ add_application(
+ ns,
+ xml,
+ "Pip",
+ "pip",
+ ["pip", "pip{}".format(VER_MAJOR), "pip{}".format(VER_DOT)],
+ PIP_VE_DATA,
+ "console",
+ ("python.wheel", [".whl"], 'install "%1"'),
+ )
+
+ if ns.include_idle and ns.include_launchers:
+ add_application(
+ ns,
+ xml,
+ "Idle",
+ "idle",
+ ["idle", "idle{}".format(VER_MAJOR), "idle{}".format(VER_DOT)],
+ IDLE_VE_DATA,
+ "windows",
+ None,
+ )
+
+ if (ns.source / SCCD_FILENAME).is_file():
+ add_registry_entries(ns, xml)
+ node = xml.find("m:Capabilities", NS)
+ node = ET.SubElement(node, QN(NS["uap4"], "CustomCapability"))
+ node.set("Name", "Microsoft.classicAppCompat_8wekyb3d8bbwe")
+
+ buffer = io.BytesIO()
+ xml.write(buffer, encoding="utf-8", xml_declaration=True)
+ return buffer.getbuffer()
+
+
+@public
+def get_resources_xml(ns):
+ return RESOURCES_XML_TEMPLATE.encode("utf-8")
diff --git a/PC/layout/support/catalog.py b/PC/layout/support/catalog.py
new file mode 100644
index 0000000..4312118
--- /dev/null
+++ b/PC/layout/support/catalog.py
@@ -0,0 +1,44 @@
+"""
+File generation for catalog signing non-binary contents.
+"""
+
+__author__ = "Steve Dower <steve.dower@python.org>"
+__version__ = "3.8"
+
+
+import sys
+
+__all__ = ["PYTHON_CAT_NAME", "PYTHON_CDF_NAME"]
+
+
+def public(f):
+ __all__.append(f.__name__)
+ return f
+
+
+PYTHON_CAT_NAME = "python.cat"
+PYTHON_CDF_NAME = "python.cdf"
+
+
+CATALOG_TEMPLATE = r"""[CatalogHeader]
+Name={target.stem}.cat
+ResultDir={target.parent}
+PublicVersion=1
+CatalogVersion=2
+HashAlgorithms=SHA256
+PageHashes=false
+EncodingType=
+
+[CatalogFiles]
+"""
+
+
+def can_sign(file):
+ return file.is_file() and file.stat().st_size
+
+
+@public
+def write_catalog(target, files):
+ with target.open("w", encoding="utf-8") as cat:
+ cat.write(CATALOG_TEMPLATE.format(target=target))
+ cat.writelines("<HASH>{}={}\n".format(n, f) for n, f in files if can_sign(f))
diff --git a/PC/layout/support/constants.py b/PC/layout/support/constants.py
new file mode 100644
index 0000000..88ea410
--- /dev/null
+++ b/PC/layout/support/constants.py
@@ -0,0 +1,28 @@
+"""
+Constants for generating the layout.
+"""
+
+__author__ = "Steve Dower <steve.dower@python.org>"
+__version__ = "3.8"
+
+import struct
+import sys
+
+VER_MAJOR, VER_MINOR, VER_MICRO, VER_FIELD4 = struct.pack(">i", sys.hexversion)
+VER_FIELD3 = VER_MICRO << 8 | VER_FIELD4
+VER_NAME = {"alpha": "a", "beta": "b", "rc": "rc"}.get(
+ sys.version_info.releaselevel, ""
+)
+VER_SERIAL = sys.version_info.serial if VER_NAME else ""
+VER_DOT = "{}.{}".format(VER_MAJOR, VER_MINOR)
+
+PYTHON_DLL_NAME = "python{}{}.dll".format(VER_MAJOR, VER_MINOR)
+PYTHON_STABLE_DLL_NAME = "python{}.dll".format(VER_MAJOR)
+PYTHON_ZIP_NAME = "python{}{}.zip".format(VER_MAJOR, VER_MINOR)
+PYTHON_PTH_NAME = "python{}{}._pth".format(VER_MAJOR, VER_MINOR)
+
+PYTHON_CHM_NAME = "python{}{}{}{}{}.chm".format(
+ VER_MAJOR, VER_MINOR, VER_MICRO, VER_NAME, VER_SERIAL
+)
+
+IS_X64 = sys.maxsize > 2 ** 32
diff --git a/PC/layout/support/distutils.command.bdist_wininst.py b/PC/layout/support/distutils.command.bdist_wininst.py
new file mode 100644
index 0000000..6e9b49f
--- /dev/null
+++ b/PC/layout/support/distutils.command.bdist_wininst.py
@@ -0,0 +1,25 @@
+"""distutils.command.bdist_wininst
+
+Suppress the 'bdist_wininst' command, while still allowing
+setuptools to import it without breaking."""
+
+from distutils.core import Command
+from distutils.errors import DistutilsPlatformError
+
+
+class bdist_wininst(Command):
+ description = "create an executable installer for MS Windows"
+
+ # Marker for tests that we have the unsupported bdist_wininst
+ _unsupported = True
+
+ def initialize_options(self):
+ pass
+
+ def finalize_options(self):
+ pass
+
+ def run(self):
+ raise DistutilsPlatformError(
+ "bdist_wininst is not supported in this Python distribution"
+ )
diff --git a/PC/layout/support/filesets.py b/PC/layout/support/filesets.py
new file mode 100644
index 0000000..47f727c
--- /dev/null
+++ b/PC/layout/support/filesets.py
@@ -0,0 +1,100 @@
+"""
+File sets and globbing helper for make_layout.
+"""
+
+__author__ = "Steve Dower <steve.dower@python.org>"
+__version__ = "3.8"
+
+import os
+
+
+class FileStemSet:
+ def __init__(self, *patterns):
+ self._names = set()
+ self._prefixes = []
+ self._suffixes = []
+ for p in map(os.path.normcase, patterns):
+ if p.endswith("*"):
+ self._prefixes.append(p[:-1])
+ elif p.startswith("*"):
+ self._suffixes.append(p[1:])
+ else:
+ self._names.add(p)
+
+ def _make_name(self, f):
+ return os.path.normcase(f.stem)
+
+ def __contains__(self, f):
+ bn = self._make_name(f)
+ return (
+ bn in self._names
+ or any(map(bn.startswith, self._prefixes))
+ or any(map(bn.endswith, self._suffixes))
+ )
+
+
+class FileNameSet(FileStemSet):
+ def _make_name(self, f):
+ return os.path.normcase(f.name)
+
+
+class FileSuffixSet:
+ def __init__(self, *patterns):
+ self._names = set()
+ self._prefixes = []
+ self._suffixes = []
+ for p in map(os.path.normcase, patterns):
+ if p.startswith("*."):
+ self._names.add(p[1:])
+ elif p.startswith("*"):
+ self._suffixes.append(p[1:])
+ elif p.endswith("*"):
+ self._prefixes.append(p[:-1])
+ elif p.startswith("."):
+ self._names.add(p)
+ else:
+ self._names.add("." + p)
+
+ def _make_name(self, f):
+ return os.path.normcase(f.suffix)
+
+ def __contains__(self, f):
+ bn = self._make_name(f)
+ return (
+ bn in self._names
+ or any(map(bn.startswith, self._prefixes))
+ or any(map(bn.endswith, self._suffixes))
+ )
+
+
+def _rglob(root, pattern, condition):
+ dirs = [root]
+ recurse = pattern[:3] in {"**/", "**\\"}
+ if recurse:
+ pattern = pattern[3:]
+
+ while dirs:
+ d = dirs.pop(0)
+ if recurse:
+ dirs.extend(
+ filter(
+ condition, (type(root)(f2) for f2 in os.scandir(d) if f2.is_dir())
+ )
+ )
+ yield from (
+ (f.relative_to(root), f)
+ for f in d.glob(pattern)
+ if f.is_file() and condition(f)
+ )
+
+
+def _return_true(f):
+ return True
+
+
+def rglob(root, patterns, condition=None):
+ if isinstance(patterns, tuple):
+ for p in patterns:
+ yield from _rglob(root, p, condition or _return_true)
+ else:
+ yield from _rglob(root, patterns, condition or _return_true)
diff --git a/PC/layout/support/logging.py b/PC/layout/support/logging.py
new file mode 100644
index 0000000..30869b9
--- /dev/null
+++ b/PC/layout/support/logging.py
@@ -0,0 +1,93 @@
+"""
+Logging support for make_layout.
+"""
+
+__author__ = "Steve Dower <steve.dower@python.org>"
+__version__ = "3.8"
+
+import logging
+import sys
+
+__all__ = []
+
+LOG = None
+HAS_ERROR = False
+
+
+def public(f):
+ __all__.append(f.__name__)
+ return f
+
+
+@public
+def configure_logger(ns):
+ global LOG
+ if LOG:
+ return
+
+ LOG = logging.getLogger("make_layout")
+ LOG.level = logging.DEBUG
+
+ if ns.v:
+ s_level = max(logging.ERROR - ns.v * 10, logging.DEBUG)
+ f_level = max(logging.WARNING - ns.v * 10, logging.DEBUG)
+ else:
+ s_level = logging.ERROR
+ f_level = logging.INFO
+
+ handler = logging.StreamHandler(sys.stdout)
+ handler.setFormatter(logging.Formatter("{levelname:8s} {message}", style="{"))
+ handler.setLevel(s_level)
+ LOG.addHandler(handler)
+
+ if ns.log:
+ handler = logging.FileHandler(ns.log, encoding="utf-8", delay=True)
+ handler.setFormatter(
+ logging.Formatter("[{asctime}]{levelname:8s}: {message}", style="{")
+ )
+ handler.setLevel(f_level)
+ LOG.addHandler(handler)
+
+
+class BraceMessage:
+ def __init__(self, fmt, *args, **kwargs):
+ self.fmt = fmt
+ self.args = args
+ self.kwargs = kwargs
+
+ def __str__(self):
+ return self.fmt.format(*self.args, **self.kwargs)
+
+
+@public
+def log_debug(msg, *args, **kwargs):
+ return LOG.debug(BraceMessage(msg, *args, **kwargs))
+
+
+@public
+def log_info(msg, *args, **kwargs):
+ return LOG.info(BraceMessage(msg, *args, **kwargs))
+
+
+@public
+def log_warning(msg, *args, **kwargs):
+ return LOG.warning(BraceMessage(msg, *args, **kwargs))
+
+
+@public
+def log_error(msg, *args, **kwargs):
+ global HAS_ERROR
+ HAS_ERROR = True
+ return LOG.error(BraceMessage(msg, *args, **kwargs))
+
+
+@public
+def log_exception(msg, *args, **kwargs):
+ global HAS_ERROR
+ HAS_ERROR = True
+ return LOG.exception(BraceMessage(msg, *args, **kwargs))
+
+
+@public
+def error_was_logged():
+ return HAS_ERROR
diff --git a/PC/layout/support/options.py b/PC/layout/support/options.py
new file mode 100644
index 0000000..76d9e34
--- /dev/null
+++ b/PC/layout/support/options.py
@@ -0,0 +1,122 @@
+"""
+List of optional components.
+"""
+
+__author__ = "Steve Dower <steve.dower@python.org>"
+__version__ = "3.8"
+
+
+__all__ = []
+
+
+def public(f):
+ __all__.append(f.__name__)
+ return f
+
+
+OPTIONS = {
+ "stable": {"help": "stable ABI stub"},
+ "pip": {"help": "pip"},
+ "distutils": {"help": "distutils"},
+ "tcltk": {"help": "Tcl, Tk and tkinter"},
+ "idle": {"help": "Idle"},
+ "tests": {"help": "test suite"},
+ "tools": {"help": "tools"},
+ "venv": {"help": "venv"},
+ "dev": {"help": "headers and libs"},
+ "symbols": {"help": "symbols"},
+ "bdist-wininst": {"help": "bdist_wininst support"},
+ "underpth": {"help": "a python._pth file", "not-in-all": True},
+ "launchers": {"help": "specific launchers"},
+ "appxmanifest": {"help": "an appxmanifest"},
+ "props": {"help": "a python.props file"},
+ "chm": {"help": "the CHM documentation"},
+ "html-doc": {"help": "the HTML documentation"},
+}
+
+
+PRESETS = {
+ "appx": {
+ "help": "APPX package",
+ "options": [
+ "stable",
+ "pip",
+ "distutils",
+ "tcltk",
+ "idle",
+ "venv",
+ "dev",
+ "launchers",
+ "appxmanifest",
+ # XXX: Disabled for now "precompile",
+ ],
+ },
+ "nuget": {
+ "help": "nuget package",
+ "options": ["stable", "pip", "distutils", "dev", "props"],
+ },
+ "default": {
+ "help": "development kit package",
+ "options": [
+ "stable",
+ "pip",
+ "distutils",
+ "tcltk",
+ "idle",
+ "tests",
+ "tools",
+ "venv",
+ "dev",
+ "symbols",
+ "bdist-wininst",
+ "chm",
+ ],
+ },
+ "embed": {
+ "help": "embeddable package",
+ "options": ["stable", "zip-lib", "flat-dlls", "underpth", "precompile"],
+ },
+}
+
+
+@public
+def get_argparse_options():
+ for opt, info in OPTIONS.items():
+ help = "When specified, includes {}".format(info["help"])
+ if info.get("not-in-all"):
+ help = "{}. Not affected by --include-all".format(help)
+
+ yield "--include-{}".format(opt), help
+
+ for opt, info in PRESETS.items():
+ help = "When specified, includes default options for {}".format(info["help"])
+ yield "--preset-{}".format(opt), help
+
+
+def ns_get(ns, key, default=False):
+ return getattr(ns, key.replace("-", "_"), default)
+
+
+def ns_set(ns, key, value=True):
+ k1 = key.replace("-", "_")
+ k2 = "include_{}".format(k1)
+ if hasattr(ns, k2):
+ setattr(ns, k2, value)
+ elif hasattr(ns, k1):
+ setattr(ns, k1, value)
+ else:
+ raise AttributeError("no argument named '{}'".format(k1))
+
+
+@public
+def update_presets(ns):
+ for preset, info in PRESETS.items():
+ if ns_get(ns, "preset-{}".format(preset)):
+ for opt in info["options"]:
+ ns_set(ns, opt)
+
+ if ns.include_all:
+ for opt in OPTIONS:
+ if OPTIONS[opt].get("not-in-all"):
+ continue
+ ns_set(ns, opt)
diff --git a/PC/layout/support/pip.py b/PC/layout/support/pip.py
new file mode 100644
index 0000000..369a923
--- /dev/null
+++ b/PC/layout/support/pip.py
@@ -0,0 +1,79 @@
+"""
+Extraction and file list generation for pip.
+"""
+
+__author__ = "Steve Dower <steve.dower@python.org>"
+__version__ = "3.8"
+
+
+import os
+import shutil
+import subprocess
+import sys
+
+__all__ = []
+
+
+def public(f):
+ __all__.append(f.__name__)
+ return f
+
+
+@public
+def get_pip_dir(ns):
+ if ns.copy:
+ if ns.zip_lib:
+ return ns.copy / "packages"
+ return ns.copy / "Lib" / "site-packages"
+ else:
+ return ns.temp / "packages"
+
+
+@public
+def extract_pip_files(ns):
+ dest = get_pip_dir(ns)
+ dest.mkdir(parents=True, exist_ok=True)
+
+ src = ns.source / "Lib" / "ensurepip" / "_bundled"
+
+ ns.temp.mkdir(parents=True, exist_ok=True)
+ wheels = [shutil.copy(whl, ns.temp) for whl in src.glob("*.whl")]
+ search_path = os.pathsep.join(wheels)
+ if os.environ.get("PYTHONPATH"):
+ search_path += ";" + os.environ["PYTHONPATH"]
+
+ env = os.environ.copy()
+ env["PYTHONPATH"] = search_path
+
+ output = subprocess.check_output(
+ [
+ sys.executable,
+ "-m",
+ "pip",
+ "--no-color",
+ "install",
+ "pip",
+ "setuptools",
+ "--upgrade",
+ "--target",
+ str(dest),
+ "--no-index",
+ "--no-cache-dir",
+ "-f",
+ str(src),
+ "--only-binary",
+ ":all:",
+ ],
+ env=env,
+ )
+
+ try:
+ shutil.rmtree(dest / "bin")
+ except OSError:
+ pass
+
+ for file in wheels:
+ try:
+ os.remove(file)
+ except OSError:
+ pass
diff --git a/PC/layout/support/props.py b/PC/layout/support/props.py
new file mode 100644
index 0000000..3a047d2
--- /dev/null
+++ b/PC/layout/support/props.py
@@ -0,0 +1,110 @@
+"""
+Provides .props file.
+"""
+
+import os
+
+from .constants import *
+
+__all__ = ["PYTHON_PROPS_NAME"]
+
+
+def public(f):
+ __all__.append(f.__name__)
+ return f
+
+
+PYTHON_PROPS_NAME = "python.props"
+
+PROPS_DATA = {
+ "PYTHON_TAG": VER_DOT,
+ "PYTHON_VERSION": os.getenv("PYTHON_NUSPEC_VERSION"),
+ "PYTHON_PLATFORM": os.getenv("PYTHON_PROPS_PLATFORM"),
+ "PYTHON_TARGET": "",
+}
+
+if not PROPS_DATA["PYTHON_VERSION"]:
+ if VER_NAME:
+ PROPS_DATA["PYTHON_VERSION"] = "{}.{}-{}{}".format(
+ VER_DOT, VER_MICRO, VER_NAME, VER_SERIAL
+ )
+ else:
+ PROPS_DATA["PYTHON_VERSION"] = "{}.{}".format(VER_DOT, VER_MICRO)
+
+if not PROPS_DATA["PYTHON_PLATFORM"]:
+ PROPS_DATA["PYTHON_PLATFORM"] = "x64" if IS_X64 else "Win32"
+
+PROPS_DATA["PYTHON_TARGET"] = "_GetPythonRuntimeFilesDependsOn{}{}_{}".format(
+ VER_MAJOR, VER_MINOR, PROPS_DATA["PYTHON_PLATFORM"]
+)
+
+PROPS_TEMPLATE = r"""<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup Condition="$(Platform) == '{PYTHON_PLATFORM}'">
+ <PythonHome Condition="$(Configuration) == 'Debug'">$([msbuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), "python_d.exe")</PythonHome>
+ <PythonHome Condition="$(PythonHome) == ''">$([msbuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), "python.exe")</PythonHome>
+ <PythonInclude>$(PythonHome)\include</PythonInclude>
+ <PythonLibs>$(PythonHome)\libs</PythonLibs>
+ <PythonTag>{PYTHON_TAG}</PythonTag>
+ <PythonVersion>{PYTHON_VERSION}</PythonVersion>
+
+ <IncludePythonExe Condition="$(IncludePythonExe) == ''">true</IncludePythonExe>
+ <IncludeDistutils Condition="$(IncludeDistutils) == ''">false</IncludeDistutils>
+ <IncludeLib2To3 Condition="$(IncludeLib2To3) == ''">false</IncludeLib2To3>
+ <IncludeVEnv Condition="$(IncludeVEnv) == ''">false</IncludeVEnv>
+
+ <GetPythonRuntimeFilesDependsOn>{PYTHON_TARGET};$(GetPythonRuntimeFilesDependsOn)</GetPythonRuntimeFilesDependsOn>
+ </PropertyGroup>
+
+ <ItemDefinitionGroup Condition="$(Platform) == '{PYTHON_PLATFORM}'">
+ <ClCompile>
+ <AdditionalIncludeDirectories>$(PythonInclude);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <AdditionalLibraryDirectories>$(PythonLibs);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+
+ <Target Name="GetPythonRuntimeFiles" Returns="@(PythonRuntime)" DependsOnTargets="$(GetPythonRuntimeFilesDependsOn)" />
+
+ <Target Name="{PYTHON_TARGET}" Returns="@(PythonRuntime)">
+ <ItemGroup>
+ <_PythonRuntimeExe Include="$(PythonHome)\python*.dll" />
+ <_PythonRuntimeExe Include="$(PythonHome)\python*.exe" Condition="$(IncludePythonExe) == 'true'" />
+ <_PythonRuntimeExe>
+ <Link>%(Filename)%(Extension)</Link>
+ </_PythonRuntimeExe>
+ <_PythonRuntimeDlls Include="$(PythonHome)\DLLs\*.pyd" />
+ <_PythonRuntimeDlls Include="$(PythonHome)\DLLs\*.dll" />
+ <_PythonRuntimeDlls>
+ <Link>DLLs\%(Filename)%(Extension)</Link>
+ </_PythonRuntimeDlls>
+ <_PythonRuntimeLib Include="$(PythonHome)\Lib\**\*" Exclude="$(PythonHome)\Lib\**\*.pyc;$(PythonHome)\Lib\site-packages\**\*" />
+ <_PythonRuntimeLib Remove="$(PythonHome)\Lib\distutils\**\*" Condition="$(IncludeDistutils) != 'true'" />
+ <_PythonRuntimeLib Remove="$(PythonHome)\Lib\lib2to3\**\*" Condition="$(IncludeLib2To3) != 'true'" />
+ <_PythonRuntimeLib Remove="$(PythonHome)\Lib\ensurepip\**\*" Condition="$(IncludeVEnv) != 'true'" />
+ <_PythonRuntimeLib Remove="$(PythonHome)\Lib\venv\**\*" Condition="$(IncludeVEnv) != 'true'" />
+ <_PythonRuntimeLib>
+ <Link>Lib\%(RecursiveDir)%(Filename)%(Extension)</Link>
+ </_PythonRuntimeLib>
+ <PythonRuntime Include="@(_PythonRuntimeExe);@(_PythonRuntimeDlls);@(_PythonRuntimeLib)" />
+ </ItemGroup>
+
+ <Message Importance="low" Text="Collected Python runtime from $(PythonHome):%0D%0A@(PythonRuntime->' %(Link)','%0D%0A')" />
+ </Target>
+</Project>
+"""
+
+
+@public
+def get_props_layout(ns):
+ if ns.include_all or ns.include_props:
+ yield "python.props", ns.temp / "python.props"
+
+
+@public
+def get_props(ns):
+ # TODO: Filter contents of props file according to included/excluded items
+ props = PROPS_TEMPLATE.format_map(PROPS_DATA)
+ return props.encode("utf-8")
diff --git a/PC/layout/support/python.props b/PC/layout/support/python.props
new file mode 100644
index 0000000..4cc7008
--- /dev/null
+++ b/PC/layout/support/python.props
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup Condition="$(Platform) == '$$PYTHON_PLATFORM$$'">
+ <PythonHome>$(MSBuildThisFileDirectory)\..\..\tools</PythonHome>
+ <PythonInclude>$(PythonHome)\include</PythonInclude>
+ <PythonLibs>$(PythonHome)\libs</PythonLibs>
+ <PythonTag>$$PYTHON_TAG$$</PythonTag>
+ <PythonVersion>$$PYTHON_VERSION$$</PythonVersion>
+
+ <IncludePythonExe Condition="$(IncludePythonExe) == ''">true</IncludePythonExe>
+ <IncludeDistutils Condition="$(IncludeDistutils) == ''">false</IncludeDistutils>
+ <IncludeLib2To3 Condition="$(IncludeLib2To3) == ''">false</IncludeLib2To3>
+ <IncludeVEnv Condition="$(IncludeVEnv) == ''">false</IncludeVEnv>
+
+ <GetPythonRuntimeFilesDependsOn>$$PYTHON_TARGET$$;$(GetPythonRuntimeFilesDependsOn)</GetPythonRuntimeFilesDependsOn>
+ </PropertyGroup>
+
+ <ItemDefinitionGroup Condition="$(Platform) == '$$PYTHON_PLATFORM$$'">
+ <ClCompile>
+ <AdditionalIncludeDirectories>$(PythonInclude);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <AdditionalLibraryDirectories>$(PythonLibs);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+
+ <Target Name="GetPythonRuntimeFiles" Returns="@(PythonRuntime)" DependsOnTargets="$(GetPythonRuntimeFilesDependsOn)" />
+
+ <Target Name="$$PYTHON_TARGET$$" Returns="@(PythonRuntime)">
+ <ItemGroup>
+ <_PythonRuntimeExe Include="$(PythonHome)\python*.dll" />
+ <_PythonRuntimeExe Include="$(PythonHome)\vcruntime140.dll" />
+ <_PythonRuntimeExe Include="$(PythonHome)\python*.exe" Condition="$(IncludePythonExe) == 'true'" />
+ <_PythonRuntimeExe>
+ <Link>%(Filename)%(Extension)</Link>
+ </_PythonRuntimeExe>
+ <_PythonRuntimeDlls Include="$(PythonHome)\DLLs\*.pyd" />
+ <_PythonRuntimeDlls Include="$(PythonHome)\DLLs\*.dll" />
+ <_PythonRuntimeDlls>
+ <Link>DLLs\%(Filename)%(Extension)</Link>
+ </_PythonRuntimeDlls>
+ <_PythonRuntimeLib Include="$(PythonHome)\Lib\**\*" Exclude="$(PythonHome)\Lib\**\*.pyc;$(PythonHome)\Lib\site-packages\**\*" />
+ <_PythonRuntimeLib Remove="$(PythonHome)\Lib\distutils\**\*" Condition="$(IncludeDistutils) != 'true'" />
+ <_PythonRuntimeLib Remove="$(PythonHome)\Lib\lib2to3\**\*" Condition="$(IncludeLib2To3) != 'true'" />
+ <_PythonRuntimeLib Remove="$(PythonHome)\Lib\ensurepip\**\*" Condition="$(IncludeVEnv) != 'true'" />
+ <_PythonRuntimeLib Remove="$(PythonHome)\Lib\venv\**\*" Condition="$(IncludeVEnv) != 'true'" />
+ <_PythonRuntimeLib>
+ <Link>Lib\%(RecursiveDir)%(Filename)%(Extension)</Link>
+ </_PythonRuntimeLib>
+ <PythonRuntime Include="@(_PythonRuntimeExe);@(_PythonRuntimeDlls);@(_PythonRuntimeLib)" />
+ </ItemGroup>
+
+ <Message Importance="low" Text="Collected Python runtime from $(PythonHome):%0D%0A@(PythonRuntime->' %(Link)','%0D%0A')" />
+ </Target>
+</Project>
diff --git a/PC/pylauncher.rc b/PC/pylauncher.rc
index 3da3445..92987af 100644
--- a/PC/pylauncher.rc
+++ b/PC/pylauncher.rc
@@ -7,6 +7,11 @@
#include <winuser.h>
1 RT_MANIFEST "python.manifest"
+#if defined(PY_ICON)
+1 ICON DISCARDABLE "icons\python.ico"
+#elif defined(PYW_ICON)
+1 ICON DISCARDABLE "icons\pythonw.ico"
+#else
1 ICON DISCARDABLE "icons\launcher.ico"
2 ICON DISCARDABLE "icons\py.ico"
3 ICON DISCARDABLE "icons\pyc.ico"
@@ -14,6 +19,7 @@
5 ICON DISCARDABLE "icons\python.ico"
6 ICON DISCARDABLE "icons\pythonw.ico"
7 ICON DISCARDABLE "icons\setup.ico"
+#endif
/////////////////////////////////////////////////////////////////////////////
//
diff --git a/PC/python_uwp.cpp b/PC/python_uwp.cpp
new file mode 100644
index 0000000..1658d05
--- /dev/null
+++ b/PC/python_uwp.cpp
@@ -0,0 +1,226 @@
+/* Main program when embedded in a UWP application on Windows */
+
+#include "Python.h"
+#include <string.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <shellapi.h>
+#include <winrt\Windows.ApplicationModel.h>
+#include <winrt\Windows.Storage.h>
+
+#ifdef PYTHONW
+#ifdef _DEBUG
+const wchar_t *PROGNAME = L"pythonw_d.exe";
+#else
+const wchar_t *PROGNAME = L"pythonw.exe";
+#endif
+#else
+#ifdef _DEBUG
+const wchar_t *PROGNAME = L"python_d.exe";
+#else
+const wchar_t *PROGNAME = L"python.exe";
+#endif
+#endif
+
+static void
+set_user_base()
+{
+ wchar_t envBuffer[2048];
+ try {
+ const auto appData = winrt::Windows::Storage::ApplicationData::Current();
+ if (appData) {
+ const auto localCache = appData.LocalCacheFolder();
+ if (localCache) {
+ auto path = localCache.Path();
+ if (!path.empty() &&
+ !wcscpy_s(envBuffer, path.c_str()) &&
+ !wcscat_s(envBuffer, L"\\local-packages")
+ ) {
+ _wputenv_s(L"PYTHONUSERBASE", envBuffer);
+ }
+ }
+ }
+ } catch (...) {
+ }
+}
+
+static const wchar_t *
+get_argv0(const wchar_t *argv0)
+{
+ winrt::hstring installPath;
+ const wchar_t *launcherPath;
+ wchar_t *buffer;
+ size_t len;
+
+ launcherPath = _wgetenv(L"__PYVENV_LAUNCHER__");
+ if (launcherPath && launcherPath[0]) {
+ len = wcslen(launcherPath) + 1;
+ buffer = (wchar_t *)malloc(sizeof(wchar_t) * len);
+ if (!buffer) {
+ Py_FatalError("out of memory");
+ return NULL;
+ }
+ if (wcscpy_s(buffer, len, launcherPath)) {
+ Py_FatalError("failed to copy to buffer");
+ return NULL;
+ }
+ return buffer;
+ }
+
+ try {
+ const auto package = winrt::Windows::ApplicationModel::Package::Current();
+ if (package) {
+ const auto install = package.InstalledLocation();
+ if (install) {
+ installPath = install.Path();
+ }
+ }
+ }
+ catch (...) {
+ }
+
+ if (!installPath.empty()) {
+ len = installPath.size() + wcslen(PROGNAME) + 2;
+ } else {
+ len = wcslen(argv0) + wcslen(PROGNAME) + 1;
+ }
+
+ buffer = (wchar_t *)malloc(sizeof(wchar_t) * len);
+ if (!buffer) {
+ Py_FatalError("out of memory");
+ return NULL;
+ }
+
+ if (!installPath.empty()) {
+ if (wcscpy_s(buffer, len, installPath.c_str())) {
+ Py_FatalError("failed to copy to buffer");
+ return NULL;
+ }
+ if (wcscat_s(buffer, len, L"\\")) {
+ Py_FatalError("failed to concatenate backslash");
+ return NULL;
+ }
+ } else {
+ if (wcscpy_s(buffer, len, argv0)) {
+ Py_FatalError("failed to copy argv[0]");
+ return NULL;
+ }
+
+ wchar_t *name = wcsrchr(buffer, L'\\');
+ if (name) {
+ name[1] = L'\0';
+ } else {
+ buffer[0] = L'\0';
+ }
+ }
+
+ if (wcscat_s(buffer, len, PROGNAME)) {
+ Py_FatalError("failed to concatenate program name");
+ return NULL;
+ }
+
+ return buffer;
+}
+
+static wchar_t *
+get_process_name()
+{
+ DWORD bufferLen = MAX_PATH;
+ DWORD len = bufferLen;
+ wchar_t *r = NULL;
+
+ while (!r) {
+ r = (wchar_t *)malloc(bufferLen * sizeof(wchar_t));
+ if (!r) {
+ Py_FatalError("out of memory");
+ return NULL;
+ }
+ len = GetModuleFileNameW(NULL, r, bufferLen);
+ if (len == 0) {
+ free((void *)r);
+ return NULL;
+ } else if (len == bufferLen &&
+ GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ free(r);
+ r = NULL;
+ bufferLen *= 2;
+ }
+ }
+
+ return r;
+}
+
+int
+wmain(int argc, wchar_t **argv)
+{
+ const wchar_t **new_argv;
+ int new_argc;
+ const wchar_t *exeName;
+
+ new_argc = argc;
+ new_argv = (const wchar_t**)malloc(sizeof(wchar_t *) * (argc + 2));
+ if (new_argv == NULL) {
+ Py_FatalError("out of memory");
+ return -1;
+ }
+
+ exeName = get_process_name();
+
+ new_argv[0] = get_argv0(exeName ? exeName : argv[0]);
+ for (int i = 1; i < argc; ++i) {
+ new_argv[i] = argv[i];
+ }
+
+ set_user_base();
+
+ if (exeName) {
+ const wchar_t *p = wcsrchr(exeName, L'\\');
+ if (p) {
+ const wchar_t *moduleName = NULL;
+ if (*p++ == L'\\') {
+ if (wcsnicmp(p, L"pip", 3) == 0) {
+ moduleName = L"pip";
+ _wputenv_s(L"PIP_USER", L"true");
+ }
+ else if (wcsnicmp(p, L"idle", 4) == 0) {
+ moduleName = L"idlelib";
+ }
+ }
+
+ if (moduleName) {
+ new_argc += 2;
+ for (int i = argc; i >= 1; --i) {
+ new_argv[i + 2] = new_argv[i];
+ }
+ new_argv[1] = L"-m";
+ new_argv[2] = moduleName;
+ }
+ }
+ }
+
+ /* Override program_full_path from here so that
+ sys.executable is set correctly. */
+ _Py_SetProgramFullPath(new_argv[0]);
+
+ int result = Py_Main(new_argc, (wchar_t **)new_argv);
+
+ free((void *)exeName);
+ free((void *)new_argv);
+
+ return result;
+}
+
+#ifdef PYTHONW
+
+int WINAPI wWinMain(
+ HINSTANCE hInstance, /* handle to current instance */
+ HINSTANCE hPrevInstance, /* handle to previous instance */
+ LPWSTR lpCmdLine, /* pointer to command line */
+ int nCmdShow /* show state of window */
+)
+{
+ return wmain(__argc, __wargv);
+}
+
+#endif
diff --git a/PC/store_info.txt b/PC/store_info.txt
new file mode 100644
index 0000000..ed40a91
--- /dev/null
+++ b/PC/store_info.txt
@@ -0,0 +1,146 @@
+# Overview
+
+NOTE: This file requires more content.
+
+Since Python 3.8.2, releases have been made through the Microsoft Store
+to allow easy installation on Windows 10.0.17763.0 and later.
+
+# Building
+
+To build the store package, the PC/layout script should be used.
+Execute the directory with the build of Python to package, and pass
+"-h" for full command-line options.
+
+To sideload test builds, you will need a local certificate.
+Instructions are available at
+https://docs.microsoft.com/windows/uwp/packaging/create-certificate-package-signing.
+
+After exporting your certificate, you will need the subject name and
+SHA256 hash. The `certutil -dump <cert file>` command will display this
+information.
+
+To build for sideloading, use these commands in PowerShell:
+
+```
+$env:APPX_DATA_PUBLISHER=<your certificate subject name>
+$env:APPX_DATA_SHA256=<your certificate SHA256>
+$env:SigningCertificateFile=<your certificate file>
+
+python PC/layout --copy <layout directory> --include-appxmanifest
+Tools/msi/make_appx.ps1 <layout directory> python.msix -sign
+
+Add-AppxPackage python.msix
+```
+
+(Note that only the last command requires PowerShell, and the others
+can be used from Command Prompt. You can also double-click to install
+the final package.)
+
+To build for publishing to the Store, use these commands:
+
+```
+$env:APPX_DATA_PUBLISHER = $null
+$env:APPX_DATA_SHA256 = $null
+
+python PC/layout --copy <layout directory> --preset-appxmanifest --precompile
+Tools/msi/make_appx.ps1 <layout directory> python.msix
+```
+
+Note that this package cannot be installed locally. It may only be
+added to a submission for the store.
+
+
+# Submission Metadata
+
+This file contains the text that we use to fill out the store listing
+for the Microsoft Store. It needs to be entered manually when creating
+a new submission via the dashboard at
+https://partner.microsoft.com/dashboard.
+
+We keep it here for convenience and to allow it to be updated via pull
+requests.
+
+## Title
+
+Python 3.8
+
+## Short Title
+
+Python
+
+## Description
+
+Python is an easy to learn, powerful programming language. It has efficient high-level data structures and a simple but effective approach to object-oriented programming. Python’s elegant syntax and dynamic typing, together with its interpreted nature, make it an ideal language for scripting and rapid application development in many areas on most platforms.
+
+The Python interpreter and the extensive standard library are freely available in source or binary form for all major platforms from the Python Web site, https://www.python.org/, and may be freely distributed. The same site also contains distributions of and pointers to many free third party Python modules, programs and tools, and additional documentation.
+
+The Python interpreter is easily extended with new functions and data types implemented in C or C++ (or other languages callable from C). Python is also suitable as an extension language for customizable applications.
+
+## ShortDescription
+
+The Python 3.8 interpreter and runtime.
+
+## Copyright Trademark Information
+
+(c) Python Software Foundation
+
+## Additional License Terms
+
+Visit https://docs.python.org/3.8/license.html for latest license terms.
+
+PSF LICENSE AGREEMENT FOR PYTHON 3.8
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and
+ the Individual or Organization ("Licensee") accessing and otherwise using Python
+ 3.8 software in source or binary form and its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+ grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+ analyze, test, perform and/or display publicly, prepare derivative works,
+ distribute, and otherwise use Python 3.8 alone or in any derivative
+ version, provided, however, that PSF's License Agreement and PSF's notice of
+ copyright, i.e., "Copyright © 2001-2018 Python Software Foundation; All Rights
+ Reserved" are retained in Python 3.8 alone or in any derivative version
+ prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on or
+ incorporates Python 3.8 or any part thereof, and wants to make the
+ derivative work available to others as provided herein, then Licensee hereby
+ agrees to include in any such work a brief summary of the changes made to Python
+ 3.8.
+
+4. PSF is making Python 3.8 available to Licensee on an "AS IS" basis.
+ PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF
+ EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR
+ WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE
+ USE OF PYTHON 3.8 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.8
+ FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF
+ MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.8, OR ANY DERIVATIVE
+ THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material breach of
+ its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any relationship
+ of agency, partnership, or joint venture between PSF and Licensee. This License
+ Agreement does not grant permission to use PSF trademarks or trade name in a
+ trademark sense to endorse or promote products or services of Licensee, or any
+ third party.
+
+8. By copying, installing or otherwise using Python 3.8, Licensee agrees
+ to be bound by the terms and conditions of this License Agreement.
+
+## Features
+
+* Easy to install Python runtime
+* Supported by core CPython team
+* Find Python, Pip and Idle on PATH
+
+## Search Terms
+
+* Python
+* Scripting
+* Interpreter
+