summaryrefslogtreecommitdiffstats
path: root/PC/layout/support
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@microsoft.com>2018-12-07 05:09:20 (GMT)
committerGitHub <noreply@github.com>2018-12-07 05:09:20 (GMT)
commit468a15aaf9206448a744fc5eab3fc21f51966aad (patch)
tree75c8f2aae7835ae7ca718ef8660a4c0899837bd3 /PC/layout/support
parentc9566b8c454120e3d0ddb5ab970f262a6cd80077 (diff)
downloadcpython-468a15aaf9206448a744fc5eab3fc21f51966aad.zip
cpython-468a15aaf9206448a744fc5eab3fc21f51966aad.tar.gz
cpython-468a15aaf9206448a744fc5eab3fc21f51966aad.tar.bz2
bpo-34977: Add Windows App Store package (GH-10245)
Diffstat (limited to 'PC/layout/support')
-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
11 files changed, 1144 insertions, 0 deletions
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>