bpo-33125: Add support for building and releasing Windows ARM64 packages (GH-16828)

Note that the support is not actually enabled yet, and so we won't be publishing these packages. However, for those who want to build it themselves (even by reusing the Azure Pipelines definition), it's now relatively easy to enable.
This commit is contained in:
Steve Dower 2019-11-20 09:30:47 -08:00 committed by GitHub
parent abce2d9bc6
commit de148f263f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 286 additions and 74 deletions

View file

@ -285,14 +285,13 @@ def _compile_one_py(src, dest, name, optimize, checked=True):
log_warning("Failed to compile {}", src)
return None
# name argument added to address bpo-37641
def _py_temp_compile(src, name, ns, dest_dir=None, checked=True):
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 + ".pyc")
return _compile_one_py(
src, dest, name, optimize=2, checked=checked
)
return _compile_one_py(src, dest, name, optimize=2, checked=checked)
def _write_to_zip(zf, dest, src, ns, checked=True):
@ -496,6 +495,13 @@ def main():
parser.add_argument(
"-b", "--build", metavar="dir", help="Specify the build directory", type=Path
)
parser.add_argument(
"--arch",
metavar="architecture",
help="Specify the target architecture",
type=str,
default=None,
)
parser.add_argument(
"--doc-build",
metavar="dir",
@ -587,6 +593,8 @@ def main():
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 not ns.arch:
ns.arch = "amd64" if sys.maxsize > 2 ** 32 else "win32"
if ns.copy and not ns.copy.is_absolute():
ns.copy = (Path.cwd() / ns.copy).resolve()
@ -602,6 +610,7 @@ def main():
Source: {ns.source}
Build: {ns.build}
Temp: {ns.temp}
Arch: {ns.arch}
Copy to: {ns.copy}
Zip to: {ns.zip}
@ -609,6 +618,15 @@ Catalog: {ns.catalog}""",
ns=ns,
)
if ns.arch not in ("win32", "amd64", "arm32", "arm64"):
log_error("--arch is not a valid value (win32, amd64, arm32, arm64)")
return 4
if ns.arch in ("arm32", "arm64"):
for n in ("include_idle", "include_tcltk"):
if getattr(ns, n):
log_warning(f"Disabling --{n.replace('_', '-')} on unsupported platform")
setattr(ns, n, False)
if ns.include_idle and not ns.include_tcltk:
log_warning("Assuming --include-tcltk to support --include-idle")
ns.include_tcltk = True

View file

@ -28,7 +28,14 @@ APPX_DATA = dict(
),
DisplayName="Python {}".format(VER_DOT),
Description="The Python {} runtime and console.".format(VER_DOT),
ProcessorArchitecture="x64" if IS_X64 else "x86",
)
APPX_PLATFORM_DATA = dict(
_keys=("ProcessorArchitecture",),
win32=("x86",),
amd64=("x64",),
arm32=("arm",),
arm64=("arm64",),
)
PYTHON_VE_DATA = dict(
@ -65,7 +72,7 @@ IDLE_VE_DATA = dict(
BackgroundColor="transparent",
)
PY_PNG = '_resources/py.png'
PY_PNG = "_resources/py.png"
APPXMANIFEST_NS = {
"": "http://schemas.microsoft.com/appx/manifest/foundation/windows10",
@ -147,18 +154,22 @@ RESOURCES_XML_TEMPLATE = r"""<?xml version="1.0" encoding="UTF-8" standalone="ye
SCCD_FILENAME = "PC/classicAppCompat.sccd"
SPECIAL_LOOKUP = object()
REGISTRY = {
"HKCU\\Software\\Python\\PythonCore": {
VER_DOT: {
"DisplayName": APPX_DATA["DisplayName"],
"SupportUrl": "https://www.python.org/",
"SysArchitecture": "64bit" if IS_X64 else "32bit",
"SysArchitecture": SPECIAL_LOOKUP,
"SysVersion": VER_DOT,
"Version": "{}.{}.{}".format(VER_MAJOR, VER_MINOR, VER_MICRO),
"InstallPath": {
"": "[{AppVPackageRoot}]",
"ExecutablePath": "[{{AppVPackageRoot}}]\\python{}.exe".format(VER_DOT),
"WindowedExecutablePath": "[{{AppVPackageRoot}}]\\pythonw{}.exe".format(VER_DOT),
"WindowedExecutablePath": "[{{AppVPackageRoot}}]\\pythonw{}.exe".format(
VER_DOT
),
},
"Help": {
"Main Python Documentation": {
@ -338,6 +349,16 @@ def _get_registry_entries(ns, root="", d=None):
if len(fullkey.parts) > 1:
yield str(fullkey), None, None
yield from _get_registry_entries(ns, fullkey, value)
elif value is SPECIAL_LOOKUP:
if key == "SysArchitecture":
return {
"win32": "32bit",
"amd64": "64bit",
"arm32": "32bit",
"arm64": "64bit",
}[ns.arch]
else:
raise ValueError(f"Key '{key}' unhandled for special lookup")
elif len(r.parts) > 1:
yield str(r), key, value
@ -376,14 +397,18 @@ def get_appxmanifest(ns):
NS = APPXMANIFEST_NS
QN = ET.QName
data = dict(APPX_DATA)
for k, v in zip(APPX_PLATFORM_DATA["_keys"], APPX_PLATFORM_DATA[ns.arch]):
data[k] = v
node = xml.find("m:Identity", NS)
for k in node.keys():
value = APPX_DATA.get(k)
value = 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])
value = data.get(node.tag.rpartition("}")[2])
if value:
node.text = value
@ -405,7 +430,7 @@ def get_appxmanifest(ns):
["python", "python{}".format(VER_MAJOR), "python{}".format(VER_DOT)],
PYTHON_VE_DATA,
"console",
("python.file", [".py"], '"%1"', 'Python File', PY_PNG),
("python.file", [".py"], '"%1"', "Python File", PY_PNG),
)
add_application(
@ -416,7 +441,7 @@ def get_appxmanifest(ns):
["pythonw", "pythonw{}".format(VER_MAJOR), "pythonw{}".format(VER_DOT)],
PYTHONW_VE_DATA,
"windows",
("python.windowedfile", [".pyw"], '"%1"', 'Python File (no console)', PY_PNG),
("python.windowedfile", [".pyw"], '"%1"', "Python File (no console)", PY_PNG),
)
if ns.include_pip and ns.include_launchers:
@ -428,7 +453,7 @@ def get_appxmanifest(ns):
["pip", "pip{}".format(VER_MAJOR), "pip{}".format(VER_DOT)],
PIP_VE_DATA,
"console",
("python.wheel", [".whl"], 'install "%1"', 'Python Wheel'),
("python.wheel", [".whl"], 'install "%1"', "Python Wheel"),
)
if ns.include_idle and ns.include_launchers:

View file

@ -5,15 +5,31 @@ Constants for generating the layout.
__author__ = "Steve Dower <steve.dower@python.org>"
__version__ = "3.8"
import os
import re
import struct
import sys
VER_MAJOR, VER_MINOR, VER_MICRO, VER_FIELD4 = struct.pack(">i", sys.hexversion)
def _unpack_hexversion():
try:
hexversion = int(os.getenv("PYTHON_HEXVERSION"), 16)
except (TypeError, ValueError):
hexversion = sys.hexversion
return struct.pack(">i", sys.hexversion)
def _get_suffix(field4):
name = {0xA0: "a", 0xB0: "b", 0xC0: "c"}.get(field4 & 0xF0, "")
if name:
serial = field4 & 0x0F
return f"{name}{serial}"
return ""
VER_MAJOR, VER_MINOR, VER_MICRO, VER_FIELD4 = _unpack_hexversion()
VER_SUFFIX = _get_suffix(VER_FIELD4)
VER_FIELD3 = VER_MICRO << 8 | VER_FIELD4
VER_NAME = {"alpha": "a", "beta": "b", "candidate": "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)
@ -21,8 +37,6 @@ 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
PYTHON_CHM_NAME = "python{}{}{}{}.chm".format(
VER_MAJOR, VER_MINOR, VER_MICRO, VER_SUFFIX
)
IS_X64 = sys.maxsize > 2 ** 32

View file

@ -13,25 +13,21 @@ PYTHON_NUSPEC_NAME = "python.nuspec"
NUSPEC_DATA = {
"PYTHON_TAG": VER_DOT,
"PYTHON_VERSION": os.getenv("PYTHON_NUSPEC_VERSION"),
"PYTHON_BITNESS": "64-bit" if IS_X64 else "32-bit",
"PACKAGENAME": os.getenv("PYTHON_NUSPEC_PACKAGENAME"),
"PACKAGETITLE": os.getenv("PYTHON_NUSPEC_PACKAGETITLE"),
"FILELIST": r' <file src="**\*" target="tools" />',
}
NUSPEC_PLATFORM_DATA = dict(
_keys=("PYTHON_BITNESS", "PACKAGENAME", "PACKAGETITLE"),
win32=("32-bit", "pythonx86", "Python (32-bit)"),
amd64=("64-bit", "python", "Python"),
arm32=("ARM", "pythonarm", "Python (ARM)"),
arm64=("ARM64", "pythonarm64", "Python (ARM64)"),
)
if not NUSPEC_DATA["PYTHON_VERSION"]:
if VER_NAME:
NUSPEC_DATA["PYTHON_VERSION"] = "{}.{}-{}{}".format(
VER_DOT, VER_MICRO, VER_NAME, VER_SERIAL
)
else:
NUSPEC_DATA["PYTHON_VERSION"] = "{}.{}".format(VER_DOT, VER_MICRO)
if not NUSPEC_DATA["PACKAGETITLE"]:
NUSPEC_DATA["PACKAGETITLE"] = "Python" if IS_X64 else "Python (32-bit)"
if not NUSPEC_DATA["PACKAGENAME"]:
NUSPEC_DATA["PACKAGENAME"] = "python" if IS_X64 else "pythonx86"
NUSPEC_DATA["PYTHON_VERSION"] = "{}.{}{}{}".format(
VER_DOT, VER_MICRO, "-" if VER_SUFFIX else "", VER_SUFFIX
)
FILELIST_WITH_PROPS = r""" <file src="**\*" exclude="python.props" target="tools" />
<file src="python.props" target="build\native" />"""
@ -56,11 +52,21 @@ NUSPEC_TEMPLATE = r"""<?xml version="1.0"?>
"""
def _get_nuspec_data_overrides(ns):
for k, v in zip(NUSPEC_PLATFORM_DATA["_keys"], NUSPEC_PLATFORM_DATA[ns.arch]):
ev = os.getenv("PYTHON_NUSPEC_" + k)
if ev:
yield k, ev
yield k, v
def get_nuspec_layout(ns):
if ns.include_all or ns.include_nuspec:
data = NUSPEC_DATA
data = dict(NUSPEC_DATA)
for k, v in _get_nuspec_data_overrides(ns):
if not data.get(k):
data[k] = v
if ns.include_all or ns.include_props:
data = dict(data)
data["FILELIST"] = FILELIST_WITH_PROPS
nuspec = NUSPEC_TEMPLATE.format_map(data)
yield "python.nuspec", ("python.nuspec", nuspec.encode("utf-8"))

View file

@ -18,15 +18,9 @@ PROPS_DATA = {
}
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_VERSION"] = "{}.{}{}{}".format(
VER_DOT, VER_MICRO, "-" if VER_SUFFIX else "", VER_SUFFIX
)
PROPS_DATA["PYTHON_TARGET"] = "_GetPythonRuntimeFilesDependsOn{}{}_{}".format(
VER_MAJOR, VER_MINOR, PROPS_DATA["PYTHON_PLATFORM"]
@ -94,5 +88,13 @@ PROPS_TEMPLATE = r"""<?xml version="1.0" encoding="utf-8"?>
def get_props_layout(ns):
if ns.include_all or ns.include_props:
# TODO: Filter contents of props file according to included/excluded items
props = PROPS_TEMPLATE.format_map(PROPS_DATA)
d = dict(PROPS_DATA)
if not d.get("PYTHON_PLATFORM"):
d["PYTHON_PLATFORM"] = {
"win32": "Win32",
"amd64": "X64",
"arm32": "ARM",
"arm64": "ARM64",
}[ns.arch]
props = PROPS_TEMPLATE.format_map(d)
yield "python.props", ("python.props", props.encode("utf-8"))