bpo-35920: Windows 10 ARM32 platform support (GH-11774)

diff --git a/Lib/distutils/_msvccompiler.py b/Lib/distutils/_msvccompiler.py
index 58b20a2..c7ac3f0 100644
--- a/Lib/distutils/_msvccompiler.py
+++ b/Lib/distutils/_msvccompiler.py
@@ -89,13 +89,24 @@
 
     return None, None
 
+PLAT_SPEC_TO_RUNTIME = {
+    'x86' : 'x86',
+    'x86_amd64' : 'x64',
+    'x86_arm' : 'arm',
+}
+
 def _find_vcvarsall(plat_spec):
     _, best_dir = _find_vc2017()
     vcruntime = None
-    vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86'
+
+    if plat_spec in PLAT_SPEC_TO_RUNTIME:
+        vcruntime_plat = PLAT_SPEC_TO_RUNTIME[plat_spec]
+    else:
+        vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86'
+
     if best_dir:
         vcredist = os.path.join(best_dir, "..", "..", "redist", "MSVC", "**",
-            "Microsoft.VC141.CRT", "vcruntime140.dll")
+            vcruntime_plat, "Microsoft.VC141.CRT", "vcruntime140.dll")
         try:
             import glob
             vcruntime = glob.glob(vcredist, recursive=True)[-1]
@@ -178,6 +189,7 @@
 PLAT_TO_VCVARS = {
     'win32' : 'x86',
     'win-amd64' : 'x86_amd64',
+    'win-arm32' : 'x86_arm',
 }
 
 # A set containing the DLLs that are guaranteed to be available for
diff --git a/Lib/distutils/spawn.py b/Lib/distutils/spawn.py
index d3a12c2..ceb9494 100644
--- a/Lib/distutils/spawn.py
+++ b/Lib/distutils/spawn.py
@@ -81,7 +81,6 @@
                   "command %r failed with exit status %d" % (cmd, rc))
 
 if sys.platform == 'darwin':
-    from distutils import sysconfig
     _cfg_target = None
     _cfg_target_split = None
 
@@ -95,6 +94,7 @@
     if sys.platform == 'darwin':
         global _cfg_target, _cfg_target_split
         if _cfg_target is None:
+            from distutils import sysconfig
             _cfg_target = sysconfig.get_config_var(
                                   'MACOSX_DEPLOYMENT_TARGET') or ''
             if _cfg_target:
diff --git a/Lib/distutils/sysconfig.py b/Lib/distutils/sysconfig.py
index 570a612..b51629e 100644
--- a/Lib/distutils/sysconfig.py
+++ b/Lib/distutils/sysconfig.py
@@ -15,6 +15,7 @@
 import sys
 
 from .errors import DistutilsPlatformError
+from .util import get_platform, get_host_platform
 
 # These are needed in a couple of spots, so just compute them once.
 PREFIX = os.path.normpath(sys.prefix)
diff --git a/Lib/distutils/util.py b/Lib/distutils/util.py
index 15cd2ad..50550e1 100644
--- a/Lib/distutils/util.py
+++ b/Lib/distutils/util.py
@@ -15,7 +15,7 @@
 from distutils import log
 from distutils.errors import DistutilsByteCompileError
 
-def get_platform ():
+def get_host_platform():
     """Return a string that identifies the current platform.  This is used mainly to
     distinguish platform-specific build directories and platform-specific built
     distributions.  Typically includes the OS name and version and the
@@ -38,6 +38,8 @@
     if os.name == 'nt':
         if 'amd64' in sys.version.lower():
             return 'win-amd64'
+        if '(arm)' in sys.version.lower():
+            return 'win-arm32'
         return sys.platform
 
     # Set for cross builds explicitly
@@ -90,8 +92,16 @@
 
     return "%s-%s-%s" % (osname, release, machine)
 
-# get_platform ()
-
+def get_platform():
+    if os.name == 'nt':
+        TARGET_TO_PLAT = {
+            'x86' : 'win32',
+            'x64' : 'win-amd64',
+            'arm' : 'win-arm32',
+        }
+        return TARGET_TO_PLAT.get(os.environ.get('VSCMD_ARG_TGT_ARCH')) or get_host_platform()
+    else:
+        return get_host_platform()
 
 def convert_path (pathname):
     """Return 'pathname' as a name that will work on the native filesystem,
diff --git a/Lib/platform.py b/Lib/platform.py
index 21defd1..9f7bd95 100755
--- a/Lib/platform.py
+++ b/Lib/platform.py
@@ -334,6 +334,27 @@
     (6, None): "post2012ServerR2",
 }
 
+def win32_is_iot():
+    return win32_edition() in ('IoTUAP', 'NanoServer', 'WindowsCoreHeadless', 'IoTEdgeOS')
+
+def win32_edition():
+    try:
+        try:
+            import winreg
+        except ImportError:
+            import _winreg as winreg
+    except ImportError:
+        pass
+    else:
+        try:
+            cvkey = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion'
+            with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, cvkey) as key:
+                return winreg.QueryValueEx(key, 'EditionId')[0]
+        except OSError:
+            pass
+
+    return None
+
 def win32_ver(release='', version='', csd='', ptype=''):
     try:
         from sys import getwindowsversion
diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py
index cc8c796..8446c8d 100644
--- a/Lib/sysconfig.py
+++ b/Lib/sysconfig.py
@@ -626,6 +626,8 @@
     if os.name == 'nt':
         if 'amd64' in sys.version.lower():
             return 'win-amd64'
+        if '(arm)' in sys.version.lower():
+            return 'win-arm32'
         return sys.platform
 
     if os.name != "posix" or not hasattr(os, 'uname'):
diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py
index 05843c5..027a84e 100644
--- a/Lib/test/test_codecs.py
+++ b/Lib/test/test_codecs.py
@@ -27,6 +27,26 @@
         self.assertEqual(coder(input), (expect, len(input)))
     return check
 
+# On small versions of Windows like Windows IoT or Windows Nano Server not all codepages are present
+def is_code_page_present(cp):
+    from ctypes import POINTER, WINFUNCTYPE, windll, WinError, Structure, WinDLL
+    from ctypes.wintypes import BOOL, UINT, BYTE, WCHAR, UINT, DWORD
+
+    MAX_LEADBYTES = 12  # 5 ranges, 2 bytes ea., 0 term.
+    MAX_DEFAULTCHAR = 2 # single or double byte
+    MAX_PATH = 260
+    class CPINFOEXW(ctypes.Structure):
+        _fields_ = [("MaxCharSize", UINT),
+                    ("DefaultChar", BYTE*MAX_DEFAULTCHAR),
+                    ("LeadByte", BYTE*MAX_LEADBYTES),
+                    ("UnicodeDefaultChar", WCHAR),
+                    ("CodePage", UINT),
+                    ("CodePageName", WCHAR*MAX_PATH)]
+
+    prototype = WINFUNCTYPE(BOOL, UINT, DWORD, POINTER(CPINFOEXW))
+    GetCPInfoEx = prototype(("GetCPInfoExW", WinDLL("kernel32")))
+    info = CPINFOEXW()
+    return GetCPInfoEx(cp, 0, info)
 
 class Queue(object):
     """
@@ -3078,9 +3098,19 @@
     def test_code_page_decode_flags(self):
         # Issue #36312: For some code pages (e.g. UTF-7) flags for
         # MultiByteToWideChar() must be set to 0.
+        if support.verbose:
+            sys.stdout.write('\n')
         for cp in (50220, 50221, 50222, 50225, 50227, 50229,
                    *range(57002, 57011+1), 65000):
-            self.assertEqual(codecs.code_page_decode(cp, b'abc'), ('abc', 3))
+            # On small versions of Windows like Windows IoT
+            # not all codepages are present.
+            # A missing codepage causes an OSError exception
+            # so check for the codepage before decoding
+            if is_code_page_present(cp):
+                self.assertEqual(codecs.code_page_decode(cp, b'abc'), ('abc', 3), f'cp{cp}')
+            else:
+                if support.verbose:
+                    print(f"  skipping cp={cp}")
         self.assertEqual(codecs.code_page_decode(42, b'abc'),
                          ('\uf061\uf062\uf063', 3))
 
diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py
index 554d3d5..c4b2fe2 100644
--- a/Lib/test/test_mimetypes.py
+++ b/Lib/test/test_mimetypes.py
@@ -6,6 +6,7 @@
 import unittest
 
 from test import support
+from platform import win32_edition
 
 # Tell it we don't know about external files:
 mimetypes.knownfiles = []
@@ -116,6 +117,8 @@
         mimetypes.types_map.clear()
         mimetypes.types_map.update(self.original_types_map)
 
+    @unittest.skipIf(win32_edition() in ('NanoServer', 'WindowsCoreHeadless', 'IoTEdgeOS'),
+                                         "MIME types registry keys unavailable")
     def test_registry_parsing(self):
         # the original, minimum contents of the MIME database in the
         # Windows registry is undocumented AFAIK.
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index bbadb81..a2021b1 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -28,6 +28,7 @@
 import uuid
 import warnings
 from test import support
+from platform import win32_is_iot
 
 try:
     import resource
@@ -2439,7 +2440,7 @@
         # Return None when an fd doesn't actually exist.
         self.assertIsNone(os.device_encoding(123456))
 
-    @unittest.skipUnless(os.isatty(0) and (sys.platform.startswith('win') or
+    @unittest.skipUnless(os.isatty(0) and not win32_is_iot() and (sys.platform.startswith('win') or
             (hasattr(locale, 'nl_langinfo') and hasattr(locale, 'CODESET'))),
             'test requires a tty and either Windows or nl_langinfo(CODESET)')
     def test_device_encoding(self):
diff --git a/Lib/test/test_startfile.py b/Lib/test/test_startfile.py
index f59252e..1a26a80 100644
--- a/Lib/test/test_startfile.py
+++ b/Lib/test/test_startfile.py
@@ -10,6 +10,7 @@
 import unittest
 from test import support
 import os
+import platform
 import sys
 from os import path
 
@@ -20,6 +21,7 @@
     def test_nonexisting(self):
         self.assertRaises(OSError, startfile, "nonexisting.vbs")
 
+    @unittest.skipIf(platform.win32_is_iot(), "starting files is not supported on Windows IoT Core or nanoserver")
     def test_empty(self):
         # We need to make sure the child process starts in a directory
         # we're not about to delete. If we're running under -j, that
diff --git a/Lib/test/test_sundry.py b/Lib/test/test_sundry.py
index 6e36a61..2accad1 100644
--- a/Lib/test/test_sundry.py
+++ b/Lib/test/test_sundry.py
@@ -1,5 +1,6 @@
 """Do a minimal test of all the modules that aren't otherwise tested."""
 import importlib
+import platform
 import sys
 from test import support
 import unittest
@@ -25,7 +26,7 @@
             import distutils.unixccompiler
 
             import distutils.command.bdist_dumb
-            if sys.platform.startswith('win'):
+            if sys.platform.startswith('win') and not platform.win32_is_iot():
                 import distutils.command.bdist_msi
             import distutils.command.bdist
             import distutils.command.bdist_rpm
diff --git a/Lib/test/test_winreg.py b/Lib/test/test_winreg.py
index 11d054e..dc2b46e 100644
--- a/Lib/test/test_winreg.py
+++ b/Lib/test/test_winreg.py
@@ -5,7 +5,7 @@
 import unittest
 from test import support
 import threading
-from platform import machine
+from platform import machine, win32_edition
 
 # Do this first so test will be skipped if module doesn't exist
 support.import_module('winreg', required_on=['win'])
@@ -399,6 +399,7 @@
         DeleteKeyEx(key=HKEY_CURRENT_USER, sub_key=test_key_name,
                     access=KEY_ALL_ACCESS, reserved=0)
 
+    @unittest.skipIf(win32_edition() in ('WindowsCoreHeadless', 'IoTEdgeOS'), "APIs not available on WindowsCoreHeadless")
     def test_reflection_functions(self):
         # Test that we can call the query, enable, and disable functions
         # on a key which isn't on the reflection list with no consequences.