Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/install-vs-components.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
text=True,
shell=True,
).strip()
components_to_add = (
["Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL.ARM64"]
components_to_add = [
"Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL.ARM64"
if platform.machine() == "ARM64"
else ["Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL"]
)
else "Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL"
]
args = (
"vs_installer.exe",
"modify",
Expand Down
37 changes: 24 additions & 13 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,25 @@ concurrency:
jobs:
test:
name: Build and test
runs-on: windows-2022
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
architecture: [x64, x86]
python-architecture: [x64, x86, arm64]
include:
- os: windows-2022
- python-architecture: arm64
os: windows-11-arm
exclude:
# actions/setup-python does not provide prebuilt arm64 Python before 3.11
- python-architecture: arm64
python-version: "3.8"
- python-architecture: arm64
python-version: "3.9"
- python-architecture: arm64
python-version: "3.10"
env:
# TODO: We can't yet run tests with PYTHONDEVMODE=1, let's emulated it as much as we can
# https://docs.python.org/3/library/devmode.html#effects-of-the-python-development-mode
Expand All @@ -37,7 +49,7 @@ jobs:
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.architecture }}
architecture: ${{ matrix.python-architecture }}
cache: pip
cache-dependency-path: .github/workflows/main.yml
check-latest: true
Expand Down Expand Up @@ -83,21 +95,20 @@ jobs:
# Upload artifacts even if tests fail
if: ${{ always() }}
with:
name: artifacts-${{ matrix.python-version }}-${{ matrix.architecture }}
name: artifacts-${{ matrix.python-version }}-${{ matrix.python-architecture }}
path: dist/*.whl
if-no-files-found: error

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

Expand All @@ -115,10 +126,10 @@ jobs:
run: pip install --upgrade build

- name: Obtain ARM64 library files
run: python .github\workflows\download-arm64-libs.py .\arm64libs
run: python .github\workflows\download-arm64-libs.py ./arm64libs

- name: Build wheels
run: python -m build --wheel --config-setting=--build-option=build_ext --config-setting=--build-option=-L.\arm64libs --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=build --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=bdist_wheel --config-setting=--build-option=--plat-name=win-arm64
run: python -m build --wheel --config-setting=--build-option="build_ext -L./arm64libs --plat-name=win-arm64 build --plat-name=win-arm64 bdist_wheel --plat-name=win-arm64"

- uses: actions/upload-artifact@v4
with:
Expand All @@ -128,7 +139,7 @@ jobs:

merge:
runs-on: windows-latest
needs: [test, build_arm64]
needs: [test, cross_compile_arm64]
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
Expand Down
9 changes: 4 additions & 5 deletions build_env.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,12 @@ configuration, please [open an issue](https://github.com/mhammond/pywin32/issues
- Follow the `For Visual Studio XXXX` instructions above and pick the optional ARM64 build tools

- Download prebuilt Python ARM64 binaries to a temporary location on your machine. You will need this location in a later step.
- This script downloads a Python ARM64 build [from NuGet](https://www.nuget.org/packages/pythonarm64/#versions-tab) that matches the version you used to run it.

```shell
python .github\workflows\download-arm64-libraries.py "<temporary path>"
python .github\workflows\download-arm64-libs.py ./arm64libs
```

- This script downloads a Python ARM64 build [from NuGet](https://www.nuget.org/packages/pythonarm64/#versions-tab) that matches the version you used to run it.
- Setup the cross-compilation environment:

```shell
Expand All @@ -156,13 +156,12 @@ configuration, please [open an issue](https://github.com/mhammond/pywin32/issues
```

- Build the extensions, passing the directory from earlier. You may optionally add the `bdist_wheel` command to generate a wheel.
- If you are not using an initialized build environment, you will need to specify the `build_ext`, `build` and `bdist_wheel` commands and pass `--plat-name win-arm64` to *each* of them separately. Otherwise you may get a mixed platform build and/or linker errors.

```shell
python -m build --wheel --config-setting=--build-option=build_ext --config-setting=--build-option=-L.\arm64libs --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=bdist_wheel --config-setting=--build-option=--plat-name=win-arm64
python -m build --wheel --config-setting=--build-option="build_ext -L./arm64libs --plat-name=win-arm64 bdist_wheel --plat-name=win-arm64"
```

- If you are not using an initialized build environment, you will need to specify the `build_ext`, `build` and `bdist_wheel` commands and pass `--plat-name win-arm64` to *each* of them separately. Otherwise you may get a mixed platform build and/or linker errors.

- Copy the built wheel to the target machine and install directly:

```shell
Expand Down
12 changes: 11 additions & 1 deletion com/win32com/test/testArrays.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# Originally contributed by Stefan Schukat as part of this arbitrary-sized
# arrays patch.
from __future__ import annotations

import platform
import unittest

from win32com.client import gencache
from win32com.test import util

ZeroD = 0
OneDEmpty = []
OneDEmpty: list[int] = []
OneD = [1, 2, 3]
TwoD = [[1, 2, 3], [1, 2, 3], [1, 2, 3]]

Expand Down Expand Up @@ -49,6 +53,12 @@ def _normalize_array(a):
return ret


@unittest.skipIf(
platform.machine() == "ARM64",
"PyCOMTest.ArrayTest cannot currently be run on ARM64 "
+ "due to lacking win32com.universal implementation "
+ "in com/win32com/src/univgw.cpp",
)
class ArrayTest(util.TestCase):
def setUp(self):
self.arr = gencache.EnsureDispatch("PyCOMTest.ArrayTest", bForDemand=False)
Expand Down
41 changes: 21 additions & 20 deletions com/win32com/test/testPyComTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import win32timezone
import winerror
from win32api import CloseHandle, GetCurrentProcessId, OpenProcess
from win32com import universal
from win32com.client import (
VARIANT,
CastTo,
Expand All @@ -25,6 +24,7 @@
gencache,
register_record_class,
)
from win32com.universal import RegisterInterfaces
from win32process import GetProcessMemoryInfo

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

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

verbose = 0


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


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


def TestQueryInterface(long_lived_server=0, iterations=5):
def TestQueryInterface(long_lived_server=False, iterations=5):
tester = win32com.client.Dispatch("PyCOMTest.PyCOMTest")
if long_lived_server:
# Create a local server
t0 = win32com.client.Dispatch(
"Python.Test.PyCOMTest", clsctx=pythoncom.CLSCTX_LOCAL_SERVER
)
# Request custom interfaces a number of times
prompt = [
"Testing QueryInterface without long-lived local-server #%d of %d...",
"Testing QueryInterface with long-lived local-server #%d of %d...",
]
# Request custom interfaces a number of time

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


class Tester(win32com.test.util.TestCase):
def testVTableInProc(self):
def testRegisterInterfacesAfterGencache(self) -> None:
# We had a bug where RegisterInterfaces would fail if gencache had
# already been run - exercise that here
RegisterInterfaces("{6BCDCB60-5605-11D0-AE5F-CADD4C000000}", 0, 1, 1)

def testVTableInProc(self) -> None:
# We used to crash running this the second time - do it a few times
for i in range(3):
progress("Testing VTables in-process #%d..." % (i + 1))
progress(f"Testing VTables in-process #{(i + 1)}...")
TestVTable(pythoncom.CLSCTX_INPROC_SERVER)

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

def testVTable2(self):
Expand All @@ -930,15 +931,15 @@ def testVTableMI(self):
TestVTableMI()

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

def testDynamic(self):
TestDynamic()
Expand Down
17 changes: 11 additions & 6 deletions com/win32com/test/testall.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import getopt
import os
import platform
import re
import sys
import traceback
Expand All @@ -22,7 +23,6 @@
win32com.__path__[0] = win32com_src_dir

import pythoncom
import win32com.client
from pywin32_testutil import TestLoader, TestRunner
from win32com.test.util import (
CapturingFunctionTestCase,
Expand Down Expand Up @@ -52,7 +52,7 @@ def CleanGenerated():

if os.path.isdir(win32com.__gen_path__):
if verbosity > 1:
print("Deleting files from %s" % (win32com.__gen_path__))
print(f"Deleting files from", win32com.__gen_path__)
shutil.rmtree(win32com.__gen_path__)
import win32com.client.gencache

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


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

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

# re-print unit-test error here so it is noticed
Expand Down
12 changes: 6 additions & 6 deletions make_all.bat
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ py -3.13 -m build --wheel
py -3.14-32 -m build --wheel
py -3.14 -m build --wheel

rem Check /build_env.md#build-environment to make sure you have all the required ARM64 components installed
py -3.10 -m build --wheel --config-setting=--build-option=build_ext --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=build --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=bdist_wheel --config-setting=--build-option=--plat-name=win-arm64
py -3.11 -m build --wheel --config-setting=--build-option=build_ext --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=build --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=bdist_wheel --config-setting=--build-option=--plat-name=win-arm64
py -3.12 -m build --wheel --config-setting=--build-option=build_ext --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=build --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=bdist_wheel --config-setting=--build-option=--plat-name=win-arm64
py -3.13 -m build --wheel --config-setting=--build-option=build_ext --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=build --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=bdist_wheel --config-setting=--build-option=--plat-name=win-arm64
py -3.14 -m build --wheel --config-setting=--build-option=build_ext --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=build --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=bdist_wheel --config-setting=--build-option=--plat-name=win-arm64
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
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"
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"
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"
py -3.13 -m build --wheel --config-setting=--build-option="build_ext --plat-name=win-arm64 build --plat-name=win-arm64 bdist_wheel --plat-name=win-arm64"
py -3.14 -m build --wheel --config-setting=--build-option="build_ext --plat-name=win-arm64 build --plat-name=win-arm64 bdist_wheel --plat-name=win-arm64"

@goto xit
:couldnt_rm
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@

For a debug (_d) version, you need a local debug build of Python, but must use
the release version executable for the build. eg:
pip install . -v --config-setting=--build-option=build --config-setting=--build-option=--debug
pip install . -v --config-setting=--build-option="build --debug"

Cross-compilation from x86 to ARM is well supported (assuming installed vs tools etc) - eg:
python -m build --wheel --config-setting=--build-option=build_ext --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=build --config-setting=--build-option=--plat-name=win-arm64 --config-setting=--build-option=bdist_wheel --config-setting=--build-option=--plat-name=win-arm64
python -m build --wheel --config-setting=--build-option="build_ext --plat-name=win-arm64 build --plat-name=win-arm64 bdist_wheel --plat-name=win-arm64"

Some modules require special SDKs or toolkits to build (eg, mapi/exchange),
which often aren't available in CI. The build process treats them as optional -
Expand Down
Loading