From 22b2594993d89ebab21c655d32f11d46b12e953d Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 01:10:26 -0400 Subject: [PATCH 01/20] Native ARM64 CI tests and wheels --- .github/workflows/main.yml | 80 +++++++++++++++++++++++--------------- make_all.bat | 2 +- 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 72175dc0fd..5640ba2a24 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,13 +17,23 @@ env: jobs: test: name: Build and test - runs-on: windows-2019 - timeout-minutes: 20 + runs-on: ${{ matrix.os }} + timeout-minutes: 25 strategy: fail-fast: false matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] - architecture: ["x64", "x86"] + # CPython has no ARM distribution until 3.11 + python-architecture: [x64, x86] + os: [windows-2019, windows-11-arm] + exclude: + - os: windows-11-arm + python-architecture: x86 + # python-architecture: [x64, x86, arm64] + # include: + # - python-architecture: arm64 + # os: windows-11-arm + # - os: windows-2019 steps: - uses: actions/checkout@v4 @@ -32,13 +42,13 @@ jobs: uses: actions/setup-python@v5 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 - name: Fix user Scripts missing from PATH - if: matrix.architecture == 'x86' + if: matrix.python-architecture == 'x86' run: | # Work around https://github.com/actions/setup-python/issues/1005 $ScriptsPath = python -c "import sysconfig,os; print(sysconfig.get_path('scripts', f'{os.name}_user'))" @@ -65,12 +75,28 @@ jobs: # Compilation and registration of the PyCOMTest server dll - name: Set up MSVC uses: microsoft/setup-msbuild@v2 + + # See https://github.com/actions/runner-images/issues/9701 + # Adapted from https://github.com/actions/runner-images/issues/9873#issuecomment-2139288682 + - name: Install missing Visual Studio components + if: matrix.os != 'windows-2019' # Only an issue on newer versions of Windows + run: | + Set-Location "C:\Program Files (x86)\Microsoft Visual Studio\Installer\" + $VsInstallPath = vswhere.exe -latest -products * -requires Microsoft.Component.MSBuild -property installationPath + [string]$ComponentsToAdd = @( + "Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL" + "Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL.ARM64" + ) | ForEach-Object {"--add $_"} + $ArgumentList = ('modify', '--installPath', "`"$VsInstallPath`"", $ComponentsToAdd, '--quiet', '--norestart', '--nocache') + echo "vs_installer.exe $($ArgumentList -join ' ')" + # should be run twice for some reason + Start-Process -FilePath vs_installer.exe -ArgumentList $ArgumentList -Wait -PassThru -WindowStyle Hidden + Start-Process -FilePath vs_installer.exe -ArgumentList $ArgumentList -Wait -PassThru -WindowStyle Hidden + - name: Build and register the PyCOMTest server dll run: | - cd com/TestSources/PyCOMTest - msbuild .\PyCOMTest.sln -property:Configuration=Release - cd x64/Release - regsvr32 .\PyCOMTest.dll + msbuild com/TestSources/PyCOMTest/PyCOMTest.sln -property:Configuration=Release + regsvr32 com/TestSources/PyCOMTest/x64/Release/PyCOMTest.dll - name: Run tests # Run the tests directly from the source dir so support files (eg, .wav files etc) @@ -84,28 +110,23 @@ 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 }}-${{ matrix.os }} 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: + # Do a single ARM64 cross-compilation to ensure we still support it + test_cross_compile: name: Cross-compile ARM runs-on: windows-2019 - timeout-minutes: 20 - strategy: - fail-fast: false - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] + timeout-minutes: 25 steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} + - name: Set up latest stable Python uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python-version }} - architecture: "x64" + python-version: 3.x + architecture: x64 cache: pip cache-dependency-path: .github/workflows/main.yml check-latest: true @@ -119,15 +140,9 @@ jobs: - 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 - - uses: actions/upload-artifact@v4 - with: - name: artifacts-${{ matrix.python-version }}-arm64 - path: dist/*.whl - if-no-files-found: error - merge: runs-on: windows-latest - needs: [test, build_arm64] + needs: [test] steps: - name: Merge Artifacts uses: actions/upload-artifact/merge@v4 @@ -142,7 +157,7 @@ jobs: # This job can be run locally by running `pre-commit run` checkers: runs-on: windows-2019 - timeout-minutes: 20 + timeout-minutes: 25 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -157,7 +172,8 @@ jobs: with: version: "0.8.4" - run: ruff format --check - - run: | # Too many files to fit in a single command, also exclude vendored Scintilla and MAPIStubLibrary + # Too many files to fit in a single command, also exclude vendored Scintilla and MAPIStubLibrary + - run: | clang-format --Werror --dry-run $(git ls-files '*.cpp' ':!:com/win32comext/mapi/src/MAPIStubLibrary/') if ($LastExitCode -ne 0) { exit $LastExitCode } clang-format --Werror --dry-run $(git ls-files '*.h' ':!:Pythonwin/Scintilla/' ':!:com/win32comext/mapi/src/MAPIStubLibrary/') @@ -165,7 +181,7 @@ jobs: mypy: runs-on: windows-2019 - timeout-minutes: 20 + timeout-minutes: 25 strategy: fail-fast: false matrix: @@ -183,7 +199,7 @@ jobs: pyright: runs-on: windows-2019 - timeout-minutes: 20 + timeout-minutes: 25 strategy: fail-fast: false matrix: diff --git a/make_all.bat b/make_all.bat index c27ac7df0e..eeb4f2e2d2 100644 --- a/make_all.bat +++ b/make_all.bat @@ -25,7 +25,7 @@ py -3.12 -m build --wheel py -3.13-32 -m build --wheel py -3.13 -m build --wheel -rem Check /build_env.md#build-environment to make sure you have all the required ARM64 components installed +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 --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 From 2fb45fdee4e8e80f3c23e139ffc1167d972ba75a Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 01:42:46 -0400 Subject: [PATCH 02/20] Shorten ` --config-setting=--build-option` commands --- .github/workflows/main.yml | 7 ++++--- build_env.md | 11 +++++------ make_all.bat | 8 ++++---- setup.py | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 72175dc0fd..99ec368838 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -114,10 +114,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: @@ -157,7 +157,8 @@ jobs: with: version: "0.8.4" - run: ruff format --check - - run: | # Too many files to fit in a single command, also exclude vendored Scintilla and MAPIStubLibrary + # Too many files to fit in a single command, also exclude vendored Scintilla and MAPIStubLibrary + - run: | clang-format --Werror --dry-run $(git ls-files '*.cpp' ':!:com/win32comext/mapi/src/MAPIStubLibrary/') if ($LastExitCode -ne 0) { exit $LastExitCode } clang-format --Werror --dry-run $(git ls-files '*.h' ':!:Pythonwin/Scintilla/' ':!:com/win32comext/mapi/src/MAPIStubLibrary/') diff --git a/build_env.md b/build_env.md index d9f3b0b9b1..93d2395d90 100644 --- a/build_env.md +++ b/build_env.md @@ -137,32 +137,31 @@ 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 "" + 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 "C:\Program Files (x86)\Microsoft Visual Studio\XXXX\BuildTools\vc\Auxiliary\Build\vcvarsall.bat" x86_arm64 ``` -- Update `setuptools` and set the following environment variables to ensure it is used: +- Set the following environment variables to ensure it is used by `setuptools`: ```shell set DISTUTILS_USE_SDK=1 ``` - 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 diff --git a/make_all.bat b/make_all.bat index c27ac7df0e..e17eadeead 100644 --- a/make_all.bat +++ b/make_all.bat @@ -26,10 +26,10 @@ py -3.13-32 -m build --wheel py -3.13 -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.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" @goto xit :couldnt_rm diff --git a/setup.py b/setup.py index 4b4c73d3be..e146628735 100644 --- a/setup.py +++ b/setup.py @@ -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 - From f25248d02fddf0f02918c4d33849a9612daad92c Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 01:56:34 -0400 Subject: [PATCH 03/20] try specify arm64 --- .github/workflows/main.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 05fbfa4460..ab98491825 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,7 +55,14 @@ jobs: echo $ScriptsPath Add-Content $env:GITHUB_PATH $ScriptsPath - - name: Build and install + - name: Build and install (arm) + if: matrix.os == 'windows-11-arm' + run: | + python .github\workflows\download-arm64-libs.py ./arm64libs + pip install . -v --user --config-setting=--build-option="build_ext -L./arm64libs --plat-name=win-arm64 build --plat-name=win-arm64" + + - name: Build and install (x86-x64) + if: matrix.os != 'windows-11-arm' run: pip install . -v --user # This needs to happen *after* installing pywin32 since @@ -103,7 +110,12 @@ jobs: # can be found - they aren't installed into the Python tree. run: python win32/scripts/pywin32_testall.py -v -skip-adodbapi - - name: Build wheels + - name: Build wheels (arm) + if: matrix.os == 'windows-11-arm' + run: pip wheel . -v --wheel-dir=dist --config-setting=--build-option="bdist_wheel --plat-name=win-arm64" + + - name: Build wheels (x86-x64) + if: matrix.os != 'windows-11-arm' run: pip wheel . -v --wheel-dir=dist - uses: actions/upload-artifact@v4 From 25b8caae440638d02c3a6ae7b5dc9ef069315674 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 02:04:27 -0400 Subject: [PATCH 04/20] Try with wheel specified --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ab98491825..81d1367baf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -59,7 +59,7 @@ jobs: if: matrix.os == 'windows-11-arm' run: | python .github\workflows\download-arm64-libs.py ./arm64libs - pip install . -v --user --config-setting=--build-option="build_ext -L./arm64libs --plat-name=win-arm64 build --plat-name=win-arm64" + pip install . -v --user --config-setting=--build-option="build_ext -L./arm64libs --plat-name=win-arm64 build --plat-name=win-arm64 bdist_wheel --plat-name=win-arm64" - name: Build and install (x86-x64) if: matrix.os != 'windows-11-arm' From 7d7e3a859697a738e44a2c7ffaca3f3ac3c1b391 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 11:31:45 -0400 Subject: [PATCH 05/20] Work around missing pythonarm64 NuGet downloads for older Python versions --- .github/workflows/main.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 81d1367baf..ec76a185f5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,18 +22,18 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + # pythonarm64 NuGet's has no download for latest Python ~=3.9.11 + python-version: ["3.8", "3.9.10", "3.10", "3.11", "3.12", "3.13"] # CPython has no ARM distribution until 3.11 python-architecture: [x64, x86] os: [windows-2019, windows-11-arm] exclude: + # We provide arm64, not arm - os: windows-11-arm python-architecture: x86 - # python-architecture: [x64, x86, arm64] - # include: - # - python-architecture: arm64 - # os: windows-11-arm - # - os: windows-2019 + # pythonarm64 NuGet has no download for Python 3.8 + - os: windows-11-arm + python-version: "3.8" steps: - uses: actions/checkout@v4 From eca016f634e67c541accc85a7991e686239f43bc Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 12:21:01 -0400 Subject: [PATCH 06/20] Reuse setuptools' logic for plat_dir --- setup.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/setup.py b/setup.py index e146628735..b1f89a2bb0 100644 --- a/setup.py +++ b/setup.py @@ -78,6 +78,14 @@ # dll_base_address later in this file... dll_base_address = 0x1E200000 +# Same as setuptools._distutils.compilers.C.msvc._vcvars_names +_vcvars_names = { + "win32": "x86", + "win-amd64": "amd64", + "win-arm32": "arm", + "win-arm64": "arm64", +} + class WinExt(Extension): # Base class for all win32 extensions, with some predefined @@ -160,7 +168,7 @@ def __init__( ) self.depends = depends or [] # stash it here, as py22 doesn't have it. - def finalize_options(self, build_ext): + def finalize_options(self, build_ext: my_build_ext) -> None: # distutils doesn't define this function for an Extension - it is # our own invention, and called just before the extension is built. if not build_ext.mingw32: @@ -168,10 +176,7 @@ def finalize_options(self, build_ext): self.extra_compile_args = self.extra_compile_args or [] # bugger - add this to python! - if build_ext.plat_name == "win32": - self.extra_link_args.append("/MACHINE:x86") - else: - self.extra_link_args.append("/MACHINE:%s" % build_ext.plat_name[4:]) + self.extra_link_args.append(f"/MACHINE:{build_ext.plat_dir}") # like Python, always use debug info, even in release builds # (note the compiler doesn't include debug info, so you only get @@ -366,10 +371,7 @@ class my_build_ext(build_ext): def finalize_options(self): build_ext.finalize_options(self) - self.plat_dir = { - "win-amd64": "x64", - "win-arm64": "arm64", - }.get(self.plat_name, "x86") + self.plat_dir = _vcvars_names.get(self.plat_name, "x86") # The pywintypes library is created in the build_temp # directory, so we need to add this to library_dirs @@ -608,7 +610,7 @@ def build_extensions(self): return if not vcbase: raise RuntimeError("Can't find MFC redist DLLs with unkown VC base path") - redist_globs = [vcbase + r"redist\%s\*MFC\mfc140u.dll" % self.plat_dir] + redist_globs = [vcbase + r"redist\{}\*MFC\mfc140u.dll".format(self.plat_dir)] m = re.search(r"\\VC\\Tools\\", vcbase) if m: # typical path on newer Visual Studios From 99110ce3256597ab8aaec412cdcdbfb5a2f93696 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 12:26:49 -0400 Subject: [PATCH 07/20] Don't even need _vcvars_names --- setup.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/setup.py b/setup.py index b1f89a2bb0..ff63191954 100644 --- a/setup.py +++ b/setup.py @@ -78,14 +78,6 @@ # dll_base_address later in this file... dll_base_address = 0x1E200000 -# Same as setuptools._distutils.compilers.C.msvc._vcvars_names -_vcvars_names = { - "win32": "x86", - "win-amd64": "amd64", - "win-arm32": "arm", - "win-arm64": "arm64", -} - class WinExt(Extension): # Base class for all win32 extensions, with some predefined @@ -371,7 +363,7 @@ class my_build_ext(build_ext): def finalize_options(self): build_ext.finalize_options(self) - self.plat_dir = _vcvars_names.get(self.plat_name, "x86") + self.plat_dir = "x86" if self.plat_name == "win32" else self.plat_name[4:] # The pywintypes library is created in the build_temp # directory, so we need to add this to library_dirs From a99d8128eae1a89b9efe44cfc406e0deac967072 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 12:33:37 -0400 Subject: [PATCH 08/20] rename plat_dir to target_machine --- setup.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index ff63191954..12ba0f26ac 100644 --- a/setup.py +++ b/setup.py @@ -168,7 +168,7 @@ def finalize_options(self, build_ext: my_build_ext) -> None: self.extra_compile_args = self.extra_compile_args or [] # bugger - add this to python! - self.extra_link_args.append(f"/MACHINE:{build_ext.plat_dir}") + self.extra_link_args.append(f"/MACHINE:{build_ext.target_machine}") # like Python, always use debug info, even in release builds # (note the compiler doesn't include debug info, so you only get @@ -363,7 +363,8 @@ class my_build_ext(build_ext): def finalize_options(self): build_ext.finalize_options(self) - self.plat_dir = "x86" if self.plat_name == "win32" else self.plat_name[4:] + self.target_machine = "x86" if self.plat_name == "win32" else self.plat_name[4:] + """Valid value for https://learn.microsoft.com/en-us/cpp/build/reference/machine-specify-target-platform""" # The pywintypes library is created in the build_temp # directory, so we need to add this to library_dirs @@ -504,7 +505,7 @@ def _check_vc(self): # The afxres.h/atls.lib files aren't always included by default, # so find and add them if vcbase and not atlmfc_found: - atls_lib = glob.glob(vcbase + rf"ATLMFC\lib\{self.plat_dir}\atls.lib") + atls_lib = glob.glob(vcbase + rf"ATLMFC\lib\{self.target_machine}\atls.lib") if atls_lib: self.library_dirs.append(os.path.dirname(atls_lib[0])) self.include_dirs.append( @@ -602,19 +603,19 @@ def build_extensions(self): return if not vcbase: raise RuntimeError("Can't find MFC redist DLLs with unkown VC base path") - redist_globs = [vcbase + r"redist\{}\*MFC\mfc140u.dll".format(self.plat_dir)] + redist_globs = [vcbase + rf"redist\{self.target_machine}\*MFC\mfc140u.dll"] m = re.search(r"\\VC\\Tools\\", vcbase) if m: # typical path on newer Visual Studios - # prefere corresponding version but accept different version + # prefer corresponding version but accept different version same_version = vcverdir is not None and os.path.isdir( vcbase[: m.start()] - + r"\VC\Redist\MSVC\{}{}".format(vcverdir, self.plat_dir) + + rf"\VC\Redist\MSVC\{vcverdir}{self.target_machine}" ) redist_globs.append( vcbase[: m.start()] + r"\VC\Redist\MSVC\{}{}\*\mfc140u.dll".format( - vcverdir if same_version else "*\\", self.plat_dir + vcverdir if same_version else "*\\", self.target_machine ) ) # Only mfcNNNu DLL is required (mfcmNNNX is Windows Forms, rest is ANSI) From 96ea6bf3560bcd07e8225f8e8c23464058e889e0 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 12:47:22 -0400 Subject: [PATCH 09/20] Updated matrix to request native Python below 3.11 --- .github/workflows/main.yml | 50 ++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ec76a185f5..d01c941ed4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,14 +26,18 @@ jobs: python-version: ["3.8", "3.9.10", "3.10", "3.11", "3.12", "3.13"] # CPython has no ARM distribution until 3.11 python-architecture: [x64, x86] - os: [windows-2019, windows-11-arm] - exclude: - # We provide arm64, not arm - - os: windows-11-arm - python-architecture: x86 - # pythonarm64 NuGet has no download for Python 3.8 + include: - os: windows-11-arm + python-architecture: arm64 + - os: windows-2019 + exclude: + # actions/setup-python does not provide prebuilt arm 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" steps: - uses: actions/checkout@v4 @@ -55,14 +59,7 @@ jobs: echo $ScriptsPath Add-Content $env:GITHUB_PATH $ScriptsPath - - name: Build and install (arm) - if: matrix.os == 'windows-11-arm' - run: | - python .github\workflows\download-arm64-libs.py ./arm64libs - pip install . -v --user --config-setting=--build-option="build_ext -L./arm64libs --plat-name=win-arm64 build --plat-name=win-arm64 bdist_wheel --plat-name=win-arm64" - - - name: Build and install (x86-x64) - if: matrix.os != 'windows-11-arm' + - name: Build and install run: pip install . -v --user # This needs to happen *after* installing pywin32 since @@ -110,27 +107,27 @@ jobs: # can be found - they aren't installed into the Python tree. run: python win32/scripts/pywin32_testall.py -v -skip-adodbapi - - name: Build wheels (arm) - if: matrix.os == 'windows-11-arm' - run: pip wheel . -v --wheel-dir=dist --config-setting=--build-option="bdist_wheel --plat-name=win-arm64" - - - name: Build wheels (x86-x64) - if: matrix.os != 'windows-11-arm' + - name: Build wheels run: pip wheel . -v --wheel-dir=dist - uses: actions/upload-artifact@v4 # Upload artifacts even if tests fail if: ${{ always() }} with: - name: artifacts-${{ matrix.python-version }}-${{ matrix.python-architecture }}-${{ matrix.os }} + name: artifacts-${{ matrix.python-version }}-${{ matrix.python-architecture }} path: dist/*.whl if-no-files-found: error - # Do a single ARM64 cross-compilation to ensure we still support it - test_cross_compile: + # actions/setup-python does not provide prebuilt arm64 Python before 3.11, so we cross-compile. + build_arm64: name: Cross-compile ARM runs-on: windows-2019 timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + # 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 @@ -152,9 +149,14 @@ jobs: - name: Build wheels 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: + name: artifacts-${{ matrix.python-version }}-arm64 + path: dist/*.whl + if-no-files-found: error merge: runs-on: windows-latest - needs: [test] + needs: [test, build_arm64] steps: - name: Merge Artifacts uses: actions/upload-artifact/merge@v4 From bc8b19e6b4f780cfc94322fbc54897fb8999314c Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 12:48:33 -0400 Subject: [PATCH 10/20] typo --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d01c941ed4..31a788054e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,7 +31,7 @@ jobs: python-architecture: arm64 - os: windows-2019 exclude: - # actions/setup-python does not provide prebuilt arm Python before 3.11 + # actions/setup-python does not provide prebuilt arm64 Python before 3.11 - python-architecture: arm64 python-version: "3.8" - python-architecture: arm64 @@ -134,7 +134,7 @@ jobs: - name: Set up latest stable Python uses: actions/setup-python@v5 with: - python-version: 3.x + python-version: ${{ matrix.python-version }} architecture: x64 cache: pip cache-dependency-path: .github/workflows/main.yml From 46f931f351deb65606fd462e6d776736360e73c1 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 20:52:44 -0400 Subject: [PATCH 11/20] Try fix matrix --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3d2ef2dde8..809ecc52d6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,10 +25,10 @@ jobs: # pythonarm64 NuGet's has no download for latest Python ~=3.9.11 python-version: ["3.8", "3.9.10", "3.10", "3.11", "3.12", "3.13"] # CPython has no ARM distribution until 3.11 - python-architecture: [x64, x86] + python-architecture: [x64, x86, arm64] include: - - os: windows-11-arm - python-architecture: arm64 + - python-architecture: arm64 + os: windows-11-arm - os: windows-2019 exclude: # actions/setup-python does not provide prebuilt arm64 Python before 3.11 From b80db64be4c43ff67d4738f126d809c73b2adfd2 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 21:39:13 -0400 Subject: [PATCH 12/20] Discard changes to setup.py --- setup.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index 12ba0f26ac..4b4c73d3be 100644 --- a/setup.py +++ b/setup.py @@ -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 --debug" + pip install . -v --config-setting=--build-option=build --config-setting=--build-option=--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 --plat-name=win-arm64 build --plat-name=win-arm64 bdist_wheel --plat-name=win-arm64" + 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 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 - @@ -160,7 +160,7 @@ def __init__( ) self.depends = depends or [] # stash it here, as py22 doesn't have it. - def finalize_options(self, build_ext: my_build_ext) -> None: + def finalize_options(self, build_ext): # distutils doesn't define this function for an Extension - it is # our own invention, and called just before the extension is built. if not build_ext.mingw32: @@ -168,7 +168,10 @@ def finalize_options(self, build_ext: my_build_ext) -> None: self.extra_compile_args = self.extra_compile_args or [] # bugger - add this to python! - self.extra_link_args.append(f"/MACHINE:{build_ext.target_machine}") + if build_ext.plat_name == "win32": + self.extra_link_args.append("/MACHINE:x86") + else: + self.extra_link_args.append("/MACHINE:%s" % build_ext.plat_name[4:]) # like Python, always use debug info, even in release builds # (note the compiler doesn't include debug info, so you only get @@ -363,8 +366,10 @@ class my_build_ext(build_ext): def finalize_options(self): build_ext.finalize_options(self) - self.target_machine = "x86" if self.plat_name == "win32" else self.plat_name[4:] - """Valid value for https://learn.microsoft.com/en-us/cpp/build/reference/machine-specify-target-platform""" + self.plat_dir = { + "win-amd64": "x64", + "win-arm64": "arm64", + }.get(self.plat_name, "x86") # The pywintypes library is created in the build_temp # directory, so we need to add this to library_dirs @@ -505,7 +510,7 @@ def _check_vc(self): # The afxres.h/atls.lib files aren't always included by default, # so find and add them if vcbase and not atlmfc_found: - atls_lib = glob.glob(vcbase + rf"ATLMFC\lib\{self.target_machine}\atls.lib") + atls_lib = glob.glob(vcbase + rf"ATLMFC\lib\{self.plat_dir}\atls.lib") if atls_lib: self.library_dirs.append(os.path.dirname(atls_lib[0])) self.include_dirs.append( @@ -603,19 +608,19 @@ def build_extensions(self): return if not vcbase: raise RuntimeError("Can't find MFC redist DLLs with unkown VC base path") - redist_globs = [vcbase + rf"redist\{self.target_machine}\*MFC\mfc140u.dll"] + redist_globs = [vcbase + r"redist\%s\*MFC\mfc140u.dll" % self.plat_dir] m = re.search(r"\\VC\\Tools\\", vcbase) if m: # typical path on newer Visual Studios - # prefer corresponding version but accept different version + # prefere corresponding version but accept different version same_version = vcverdir is not None and os.path.isdir( vcbase[: m.start()] - + rf"\VC\Redist\MSVC\{vcverdir}{self.target_machine}" + + r"\VC\Redist\MSVC\{}{}".format(vcverdir, self.plat_dir) ) redist_globs.append( vcbase[: m.start()] + r"\VC\Redist\MSVC\{}{}\*\mfc140u.dll".format( - vcverdir if same_version else "*\\", self.target_machine + vcverdir if same_version else "*\\", self.plat_dir ) ) # Only mfcNNNu DLL is required (mfcmNNNX is Windows Forms, rest is ANSI) From 6f1162c1b8c86297ecb4a01e86053feb40b258c1 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 21:39:51 -0400 Subject: [PATCH 13/20] reverted setup.py --- .github/workflows/main.yml | 3 +-- setup.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 809ecc52d6..58e86520a9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,8 +22,7 @@ jobs: strategy: fail-fast: false matrix: - # pythonarm64 NuGet's has no download for latest Python ~=3.9.11 - python-version: ["3.8", "3.9.10", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] # CPython has no ARM distribution until 3.11 python-architecture: [x64, x86, arm64] include: diff --git a/setup.py b/setup.py index 4b4c73d3be..e146628735 100644 --- a/setup.py +++ b/setup.py @@ -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 - From 9c2d3e7c705330e6b1f115291a16533b1e0f42b7 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 21:40:58 -0400 Subject: [PATCH 14/20] Remove other comment --- .github/workflows/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 58e86520a9..b12427964b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,6 @@ jobs: fail-fast: false matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] - # CPython has no ARM distribution until 3.11 python-architecture: [x64, x86, arm64] include: - python-architecture: arm64 From 4e923cf2e09d516f35006b3109bcfeb1d75eac82 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 21:46:00 -0400 Subject: [PATCH 15/20] Flip includes --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b12427964b..7f8110772b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,9 +25,9 @@ jobs: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] python-architecture: [x64, x86, arm64] include: + - os: windows-2019 - python-architecture: arm64 os: windows-11-arm - - os: windows-2019 exclude: # actions/setup-python does not provide prebuilt arm64 Python before 3.11 - python-architecture: arm64 From fca0484763c2552c16b384c9265778ac6941211d Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 27 Apr 2025 23:12:13 -0400 Subject: [PATCH 16/20] Apply "Fix user Scripts missing from PATH" to arm64 as well --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7f8110772b..2dcb14d963 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -50,7 +50,7 @@ jobs: check-latest: true - name: Fix user Scripts missing from PATH - if: matrix.python-architecture == 'x86' + if: matrix.python-architecture == 'x86' || matrix.python-architecture == 'arm64' run: | # Work around https://github.com/actions/setup-python/issues/1005 $ScriptsPath = python -c "import sysconfig,os; print(sysconfig.get_path('scripts', f'{os.name}_user'))" From e7011238c3dcc1eb2aff2da3437aed7cffc02b1c Mon Sep 17 00:00:00 2001 From: Avasam Date: Wed, 30 Apr 2025 19:57:31 -0400 Subject: [PATCH 17/20] Maybe fix tests --- com/win32com/test/testPyComTest.py | 34 ++++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/com/win32com/test/testPyComTest.py b/com/win32com/test/testPyComTest.py index 9aa95a430f..856f1a6aeb 100644 --- a/com/win32com/test/testPyComTest.py +++ b/com/win32com/test/testPyComTest.py @@ -7,12 +7,12 @@ import datetime import decimal import os +import platform import time +from unittest import SkipTest import pythoncom -import pywintypes import win32com -import win32com.client.connect import win32com.test.util import win32timezone import winerror @@ -23,8 +23,10 @@ DispatchBaseClass, Record, constants, + gencache, register_record_class, ) +from win32com.universal import RegisterInterfaces from win32process import GetProcessMemoryInfo importMsg = "**** PyCOMTest is not installed ***\n PyCOMTest is a Python test specific COM client and server.\n It is likely this server is not installed on this machine\n To install the server, you must get the win32com sources\n and build it using MS Visual C++" @@ -35,8 +37,6 @@ "Python.Test.PyCOMTest", ) -from win32com.client import gencache - try: gencache.EnsureModule( "{6BCDCB60-5605-11D0-AE5F-CADD4C000000}", 0, 1, 1, bForDemand=False @@ -46,12 +46,6 @@ print(importMsg) raise RuntimeError(importMsg) -# We had a bg where RegisterInterfaces would fail if gencache had -# already been run - exercise that here -from win32com import universal - -universal.RegisterInterfaces("{6BCDCB60-5605-11D0-AE5F-CADD4C000000}", 0, 1, 1) - verbose = 0 @@ -886,15 +880,27 @@ def TestQueryInterface(long_lived_server=0, iterations=5): 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 + try: + RegisterInterfaces("{6BCDCB60-5605-11D0-AE5F-CADD4C000000}", 0, 1, 1) + except NotImplementedError: + if platform.machine() == "ARM64": + raise SkipTest( + "`win32com.universal.RegisterInterfaces` doesn't support ARM64 yet" + ) + raise + + 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): From f525b4b662ebfd7daf133217ff018136ad9e4675 Mon Sep 17 00:00:00 2001 From: Avasam Date: Thu, 24 Jul 2025 13:00:33 -0400 Subject: [PATCH 18/20] Try always install non ARM64 ATL --- .github/workflows/install-vs-components.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/install-vs-components.py b/.github/workflows/install-vs-components.py index ed2deb62f7..fab06e99b1 100644 --- a/.github/workflows/install-vs-components.py +++ b/.github/workflows/install-vs-components.py @@ -21,11 +21,12 @@ text=True, shell=True, ).strip() -components_to_add = ( +components_to_add = ["Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL"] + ( ["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 [] ) + args = ( "vs_installer.exe", "modify", From 391e958fa29414fd69f76fe1c2479ee2843f58ea Mon Sep 17 00:00:00 2001 From: Avasam Date: Thu, 24 Jul 2025 14:11:57 -0400 Subject: [PATCH 19/20] Try just ARM just to see --- .github/workflows/install-vs-components.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/install-vs-components.py b/.github/workflows/install-vs-components.py index fab06e99b1..7c4342d043 100644 --- a/.github/workflows/install-vs-components.py +++ b/.github/workflows/install-vs-components.py @@ -22,11 +22,13 @@ shell=True, ).strip() components_to_add = ["Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL"] + ( - ["Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL.ARM64"] + [ + "Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL.ARM", + "Microsoft.VisualStudio.Component.VC.14.29.16.11.ATL.ARM64", + ] if platform.machine() == "ARM64" else [] ) - args = ( "vs_installer.exe", "modify", From ca2ec6f86cad72774c599719c7f2dfce3ace8e3b Mon Sep 17 00:00:00 2001 From: Avasam Date: Sat, 27 Sep 2025 11:29:20 -0400 Subject: [PATCH 20/20] Apply the same retargetting as #2655 --- com/TestSources/PyCOMTest/PyCOMTest.vcxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/com/TestSources/PyCOMTest/PyCOMTest.vcxproj b/com/TestSources/PyCOMTest/PyCOMTest.vcxproj index 050d0f6b60..1bbcc2fea5 100644 --- a/com/TestSources/PyCOMTest/PyCOMTest.vcxproj +++ b/com/TestSources/PyCOMTest/PyCOMTest.vcxproj @@ -24,7 +24,7 @@ Win32Proj - 10.0.20348.0 + 10.0 @@ -42,13 +42,13 @@ DynamicLibrary true - v142 + v143 NotSet DynamicLibrary false - v142 + v143 NotSet