Skip to content

Commit 7500afe

Browse files
committed
Native ARM64 CI builds and tests
1 parent bf1a7e4 commit 7500afe

File tree

6 files changed

+70
-43
lines changed

6 files changed

+70
-43
lines changed

.github/workflows/install-vs-components.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121
text=True,
2222
shell=True,
2323
).strip()
24-
components_to_add = (
25-
["Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL.ARM64"]
24+
components_to_add = [
25+
"Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL.ARM64"
2626
if platform.machine() == "ARM64"
27-
else ["Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL"]
28-
)
27+
else "Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL"
28+
]
2929
args = (
3030
"vs_installer.exe",
3131
"modify",

.github/workflows/main.yml

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,25 @@ concurrency:
1515
jobs:
1616
test:
1717
name: Build and test
18-
runs-on: windows-2022
18+
runs-on: ${{ matrix.os }}
1919
timeout-minutes: 30
2020
strategy:
2121
fail-fast: false
2222
matrix:
2323
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
24-
architecture: [x64, x86]
24+
python-architecture: [x64, x86, arm64]
25+
include:
26+
- os: windows-2022
27+
- python-architecture: arm64
28+
os: windows-11-arm
29+
exclude:
30+
# actions/setup-python does not provide prebuilt arm64 Python before 3.11
31+
- python-architecture: arm64
32+
python-version: "3.8"
33+
- python-architecture: arm64
34+
python-version: "3.9"
35+
- python-architecture: arm64
36+
python-version: "3.10"
2537
env:
2638
# TODO: We can't yet run tests with PYTHONDEVMODE=1, let's emulated it as much as we can
2739
# https://docs.python.org/3/library/devmode.html#effects-of-the-python-development-mode
@@ -37,7 +49,7 @@ jobs:
3749
uses: actions/setup-python@v6
3850
with:
3951
python-version: ${{ matrix.python-version }}
40-
architecture: ${{ matrix.architecture }}
52+
architecture: ${{ matrix.python-architecture }}
4153
cache: pip
4254
cache-dependency-path: .github/workflows/main.yml
4355
check-latest: true
@@ -83,21 +95,20 @@ jobs:
8395
# Upload artifacts even if tests fail
8496
if: ${{ always() }}
8597
with:
86-
name: artifacts-${{ matrix.python-version }}-${{ matrix.architecture }}
98+
name: artifacts-${{ matrix.python-version }}-${{ matrix.python-architecture }}
8799
path: dist/*.whl
88100
if-no-files-found: error
89101

90-
# We cannot build and test on ARM64, so we cross-compile.
91-
# Later, when available, we can add tests using this wheel on ARM64 VMs
92-
build_arm64:
93-
name: Cross-compile ARM
102+
# actions/setup-python does not provide prebuilt arm64 Python before 3.11, so we cross-compile.
103+
cross_compile_arm64:
104+
name: Cross-compile ARM64
94105
runs-on: windows-2022
95106
timeout-minutes: 30
96107
strategy:
97108
fail-fast: false
98109
matrix:
99-
# pythonarm64 NuGet's has no download for Python ~=3.9.11
100-
python-version: ["3.9.10", "3.10", "3.11", "3.12", "3.13", "3.14"]
110+
# pythonarm64 NuGet has no download for Python 3.8 and Python ~=3.9.11
111+
python-version: ["3.9.10", "3.10"]
101112
steps:
102113
- uses: actions/checkout@v4
103114

@@ -128,7 +139,7 @@ jobs:
128139

129140
merge:
130141
runs-on: windows-latest
131-
needs: [test, build_arm64]
142+
needs: [test, cross_compile_arm64]
132143
steps:
133144
- name: Merge Artifacts
134145
uses: actions/upload-artifact/merge@v4

com/win32com/test/testArrays.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
# Originally contributed by Stefan Schukat as part of this arbitrary-sized
22
# arrays patch.
3+
from __future__ import annotations
4+
5+
import platform
6+
import unittest
37

48
from win32com.client import gencache
59
from win32com.test import util
610

711
ZeroD = 0
8-
OneDEmpty = []
12+
OneDEmpty: list[int] = []
913
OneD = [1, 2, 3]
1014
TwoD = [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
1115

@@ -49,6 +53,12 @@ def _normalize_array(a):
4953
return ret
5054

5155

56+
@unittest.skipIf(
57+
platform.machine() == "ARM64",
58+
"PyCOMTest.ArrayTest cannot currently be run on ARM64 "
59+
+ "due to lacking win32com.universal implementation "
60+
+ "in com/win32com/src/univgw.cpp",
61+
)
5262
class ArrayTest(util.TestCase):
5363
def setUp(self):
5464
self.arr = gencache.EnsureDispatch("PyCOMTest.ArrayTest", bForDemand=False)

com/win32com/test/testPyComTest.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import win32timezone
1616
import winerror
1717
from win32api import CloseHandle, GetCurrentProcessId, OpenProcess
18-
from win32com import universal
1918
from win32com.client import (
2019
VARIANT,
2120
CastTo,
@@ -25,6 +24,7 @@
2524
gencache,
2625
register_record_class,
2726
)
27+
from win32com.universal import RegisterInterfaces
2828
from win32process import GetProcessMemoryInfo
2929

3030
# This test uses a Python implemented COM server - ensure correctly registered.
@@ -46,10 +46,6 @@
4646
print(f"The PyCOMTest module can not be located or generated.\n{importMsg}\n")
4747
raise RuntimeError(importMsg) from error
4848

49-
# We had a bg where RegisterInterfaces would fail if gencache had
50-
# already been run - exercise that here
51-
universal.RegisterInterfaces("{6BCDCB60-5605-11D0-AE5F-CADD4C000000}", 0, 1, 1)
52-
5349
verbose = 0
5450

5551

@@ -175,7 +171,7 @@ def _DumpFireds(self):
175171
if not self.fireds:
176172
print("ERROR: Nothing was received!")
177173
for firedId, no in self.fireds.items():
178-
progress("ID %d fired %d times" % (firedId, no))
174+
progress(f"ID {firedId} fired {no} times")
179175

180176

181177
# Test everything which can be tested using both the "dynamic" and "generated"
@@ -891,34 +887,39 @@ def TestVTableMI():
891887
pass
892888

893889

894-
def TestQueryInterface(long_lived_server=0, iterations=5):
890+
def TestQueryInterface(long_lived_server=False, iterations=5):
895891
tester = win32com.client.Dispatch("PyCOMTest.PyCOMTest")
896892
if long_lived_server:
897893
# Create a local server
898894
t0 = win32com.client.Dispatch(
899895
"Python.Test.PyCOMTest", clsctx=pythoncom.CLSCTX_LOCAL_SERVER
900896
)
901-
# Request custom interfaces a number of times
902-
prompt = [
903-
"Testing QueryInterface without long-lived local-server #%d of %d...",
904-
"Testing QueryInterface with long-lived local-server #%d of %d...",
905-
]
897+
# Request custom interfaces a number of time
906898

907899
for i in range(iterations):
908-
progress(prompt[long_lived_server != 0] % (i + 1, iterations))
900+
progress(
901+
f"Testing QueryInterface "
902+
+ ("with" if long_lived_server else "without")
903+
+ f" long-lived local-server #{i + 1} of {iterations}..."
904+
)
909905
tester.TestQueryInterface()
910906

911907

912908
class Tester(win32com.test.util.TestCase):
913-
def testVTableInProc(self):
909+
def testRegisterInterfacesAfterGencache(self) -> None:
910+
# We had a bug where RegisterInterfaces would fail if gencache had
911+
# already been run - exercise that here
912+
RegisterInterfaces("{6BCDCB60-5605-11D0-AE5F-CADD4C000000}", 0, 1, 1)
913+
914+
def testVTableInProc(self) -> None:
914915
# We used to crash running this the second time - do it a few times
915916
for i in range(3):
916-
progress("Testing VTables in-process #%d..." % (i + 1))
917+
progress(f"Testing VTables in-process #{(i + 1)}...")
917918
TestVTable(pythoncom.CLSCTX_INPROC_SERVER)
918919

919-
def testVTableLocalServer(self):
920+
def testVTableLocalServer(self) -> None:
920921
for i in range(3):
921-
progress("Testing VTables out-of-process #%d..." % (i + 1))
922+
progress(f"Testing VTables out-of-process #{(i + 1)}...")
922923
TestVTable(pythoncom.CLSCTX_LOCAL_SERVER)
923924

924925
def testVTable2(self):
@@ -930,15 +931,15 @@ def testVTableMI(self):
930931
TestVTableMI()
931932

932933
def testMultiQueryInterface(self):
933-
TestQueryInterface(0, 6)
934+
TestQueryInterface(False, 6)
934935
# When we use the custom interface in the presence of a long-lived
935936
# local server, i.e. a local server that is already running when
936937
# we request an instance of our COM object, and remains afterwards,
937938
# then after repeated requests to create an instance of our object
938939
# the custom interface disappears -- i.e. QueryInterface fails with
939940
# E_NOINTERFACE. Set the upper range of the following test to 2 to
940-
# pass this test, i.e. TestQueryInterface(1,2)
941-
TestQueryInterface(1, 6)
941+
# pass this test, i.e. TestQueryInterface(True, 2)
942+
TestQueryInterface(True, 6)
942943

943944
def testDynamic(self):
944945
TestDynamic()

com/win32com/test/testall.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import getopt
22
import os
3+
import platform
34
import re
45
import sys
56
import traceback
@@ -22,7 +23,6 @@
2223
win32com.__path__[0] = win32com_src_dir
2324

2425
import pythoncom
25-
import win32com.client
2626
from pywin32_testutil import TestLoader, TestRunner
2727
from win32com.test.util import (
2828
CapturingFunctionTestCase,
@@ -52,7 +52,7 @@ def CleanGenerated():
5252

5353
if os.path.isdir(win32com.__gen_path__):
5454
if verbosity > 1:
55-
print("Deleting files from %s" % (win32com.__gen_path__))
55+
print(f"Deleting files from", win32com.__gen_path__)
5656
shutil.rmtree(win32com.__gen_path__)
5757
import win32com.client.gencache
5858

@@ -78,11 +78,17 @@ def ExecuteSilentlyIfOK(cmd, testcase):
7878
rc = f.close()
7979
if rc:
8080
print(data)
81-
testcase.fail("Executing '%s' failed (%d)" % (cmd, rc))
81+
testcase.fail(f"Executing '{cmd}' failed ({rc})")
8282
# for "_d" builds, strip the '[xxx refs]' line
8383
return RemoveRefCountOutput(data)
8484

8585

86+
@unittest.skipIf(
87+
platform.machine() == "ARM64",
88+
"PyCOMTest cannot currently be run on ARM64 "
89+
+ "due to lacking win32com.universal implementation "
90+
+ "in com/win32com/src/univgw.cpp",
91+
)
8692
class PyCOMTest(TestCase):
8793
no_leak_tests = True # done by the test itself
8894

@@ -291,8 +297,7 @@ def usage(why):
291297
print("These tests may take *many* minutes to run - be patient!")
292298
print("(running from python.exe will avoid these leak tests)")
293299
print(
294-
"Executing level %d tests - %d test cases will be run"
295-
% (testLevel, suite.countTestCases())
300+
f"Executing level {testLevel} tests - {suite.countTestCases()} test cases will be run"
296301
)
297302
if verbosity == 1 and suite.countTestCases() < 70:
298303
# A little row of markers so the dots show how close to finished
@@ -307,7 +312,7 @@ def usage(why):
307312
desc = "\n".join(traceback.format_exception_only(exc_type, exc_val))
308313
testResult.stream.write(f"{mod_name}: {desc}")
309314
testResult.stream.writeln(
310-
"*** %d test(s) could not be run ***" % len(import_failures)
315+
f"*** {len(import_failures)} test(s) could not be run ***"
311316
)
312317

313318
# re-print unit-test error here so it is noticed

make_all.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ py -3.13 -m build --wheel
2828
py -3.14-32 -m build --wheel
2929
py -3.14 -m build --wheel
3030

31-
rem Check /build_env.md#build-environment to make sure you have all the required ARM64 components installed
31+
rem Check /build_env.md#cross-compiling-for-arm64-microsoft-visual-c-141-and-up to make sure you have all the required ARM64 components installed
3232
py -3.10 -m build --wheel --config-setting=--build-option="build_ext --plat-name=win-arm64 build --plat-name=win-arm64 bdist_wheel --plat-name=win-arm64"
3333
py -3.11 -m build --wheel --config-setting=--build-option="build_ext --plat-name=win-arm64 build --plat-name=win-arm64 bdist_wheel --plat-name=win-arm64"
3434
py -3.12 -m build --wheel --config-setting=--build-option="build_ext --plat-name=win-arm64 build --plat-name=win-arm64 bdist_wheel --plat-name=win-arm64"

0 commit comments

Comments
 (0)