From 8dbe59004e16edba2a62323aaef787b0de03f041 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 24 Sep 2026 13:50:53 +0300 Subject: [PATCH 1/3] Update winbuild readme/docs --- winbuild/README.md | 1 + winbuild/build.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/winbuild/README.md b/winbuild/README.md index 50ad0a895d2..d6271ab1060 100644 --- a/winbuild/README.md +++ b/winbuild/README.md @@ -21,6 +21,7 @@ set PYTHON=C:\Python311\bin cd /D C:\Pillow\winbuild %PYTHON%\python.exe build_prepare.py -v --depends=C:\pillow-depends build\build_dep_all.cmd +build\build_env.cmd cd .. %PYTHON%\python.exe -m pip install -v -C raqm=vendor -C fribidi=vendor . path C:\Pillow\winbuild\build\bin;%PATH% diff --git a/winbuild/build.rst b/winbuild/build.rst index 9be9847e728..d6127c1ed0f 100644 --- a/winbuild/build.rst +++ b/winbuild/build.rst @@ -44,6 +44,7 @@ Run ``build_prepare.py`` to configure the build:: [--depends PILLOW_DEPS] [--architecture {x86,AMD64,ARM64}] [--nmake] [--no-imagequant] [--no-fribidi] + [--no-avif] Download and generate build scripts for Pillow dependencies. From 32447f6b23716a0096414dc6a63dfedf13c9d793 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 24 Sep 2026 14:10:35 +0300 Subject: [PATCH 2/3] Make cross-compilation work on Windows (tested on ARM64 host, AMD64 Python) --- winbuild/build_prepare.py | 129 ++++++++++++++++++++++++++++++++------ winbuild/fribidi.cmake | 7 +-- 2 files changed, 111 insertions(+), 25 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 2e8b3b82643..cf2b217573e 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -1,14 +1,15 @@ from __future__ import annotations import argparse +import glob import json import os import platform import re import shutil -import struct import subprocess import sys +import sysconfig from pathlib import Path from typing import Any @@ -63,6 +64,7 @@ def cmds_cmake( *params: str, build_dir: str = ".", build_type: str = "Release", + cmake_system: str = "{cmake_system}", ) -> list[str]: if not isinstance(target, str): target = " ".join(target) @@ -71,6 +73,7 @@ def cmds_cmake( " ".join( [ "{cmake}", + cmake_system, f"-DCMAKE_BUILD_TYPE={build_type}", "-DCMAKE_VERBOSE_MAKEFILE=ON", "-DCMAKE_RULE_MESSAGES:BOOL=OFF", # for NMake @@ -109,11 +112,57 @@ def cmd_msbuild( SF_PROJECTS = "https://sourceforge.net/projects" ARCHITECTURES = { - "x86": {"vcvars_arch": "x86", "msbuild_arch": "Win32"}, - "AMD64": {"vcvars_arch": "x86_amd64", "msbuild_arch": "x64"}, - "ARM64": {"vcvars_arch": "x86_arm64", "msbuild_arch": "ARM64"}, + "x86": {"msbuild_arch": "Win32"}, + "AMD64": {"msbuild_arch": "x64"}, + "ARM64": {"msbuild_arch": "ARM64"}, } +VCVARS_ARCHITECTURES = { + "x86": "x86", + "AMD64": "amd64", + "ARM64": "arm64", +} + +MSVC_HOST_DIRS = { + "x86": "Hostx86", + "AMD64": "Hostx64", + "ARM64": "Hostarm64", +} + +MSVC_TARGET_DIRS = { + "x86": "x86", + "AMD64": "x64", + "ARM64": "arm64", +} + +HOST_ARCHITECTURES = { + "x86": ["x86"], + "AMD64": ["AMD64", "x86"], + "ARM64": ["ARM64", "AMD64", "x86"], +} + +CARGO_TARGETS = { + "x86": "i686-pc-windows-msvc", + "AMD64": "x86_64-pc-windows-msvc", + "ARM64": "aarch64-pc-windows-msvc", +} + +PYTHON_ARCHITECTURES = { + "win32": "x86", + "win-amd64": "AMD64", + "win-arm64": "ARM64", +} + + +def get_host_architecture() -> str: + machine = platform.machine() + return machine if machine in HOST_ARCHITECTURES else "AMD64" + + +def get_python_architecture() -> str: + return PYTHON_ARCHITECTURES[sysconfig.get_platform()] + + V = json.loads( (Path(__file__).parents[1] / ".github" / "dependencies.json").read_text() ) @@ -327,10 +376,10 @@ def cmd_msbuild( "license": "COPYRIGHT", "build": [ cmd_cd("imagequant-sys"), - "cargo build --release", + "cargo build --release {cargo_target}", ], "headers": ["libimagequant.h"], - "libs": [r"..\target\release\imagequant_sys.lib"], + "libs": [r"..\target\{cargo_dir}release\imagequant_sys.lib"], }, "harfbuzz": { "url": f"https://github.com/harfbuzz/harfbuzz/releases/download/{V['harfbuzz']}/FILENAME", @@ -356,11 +405,16 @@ def cmd_msbuild( # generated tab.i files cannot be cross-compiled " ^&^& ".join( [ - "if {architecture}==ARM64 cmd /c call {vcvarsall} x86", - *cmds_cmake("fribidi-gen", "-DARCH=x86", build_dir="build_x86"), + "if {fribidi_gen}==FALSE cmd /c call {vcvarsall} {vcvars_host_arch}", # noqa: E501 + *cmds_cmake( + "fribidi-gen", + "-DGEN=TRUE", + build_dir="build_host", + cmake_system="", + ), ] ), - *cmds_cmake("fribidi", "-DARCH={architecture}"), + *cmds_cmake("fribidi", "-DGEN={fribidi_gen}", cmake_system=""), ], "bins": [r"*.dll"], }, @@ -391,7 +445,7 @@ def cmd_msbuild( # based on distutils._msvccompiler from CPython 3.7.4 -def find_msvs(architecture: str) -> dict[str, str] | None: +def find_msvs(architecture: str, host_architecture: str) -> dict[str, str] | None: root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles") if not root: print("Program Files not found") @@ -442,10 +496,37 @@ def find_msvs(architecture: str) -> dict[str, str] | None: print("Visual Studio vcvarsall not found") return None + # Prefer compilers that run natively on this machine, + # and fall back to ones that only run under emulation. + for host in HOST_ARCHITECTURES[host_architecture]: + if glob.glob( + os.path.join( + vspath, + "VC", + "Tools", + "MSVC", + "*", + "bin", + MSVC_HOST_DIRS[host], + MSVC_TARGET_DIRS[architecture], + "cl.exe", + ) + ): + break + else: + print(f"Visual Studio has no compiler targeting {architecture}") + return None + + vcvars_arch = VCVARS_ARCHITECTURES[architecture] + if host != architecture: + vcvars_arch = f"{VCVARS_ARCHITECTURES[host]}_{vcvars_arch}" + return { "vs_dir": vspath, "msbuild": f'"{msbuild}"', "vcvarsall": f'"{vcvarsall}"', + "vcvars_arch": vcvars_arch, + "vcvars_host_arch": VCVARS_ARCHITECTURES[host], "nmake": "nmake.exe", # nmake selected by vcvarsall } @@ -660,14 +741,7 @@ def main() -> None: parser.add_argument( "--architecture", choices=ARCHITECTURES, - default=os.environ.get( - "ARCHITECTURE", - ( - "ARM64" - if platform.machine() == "ARM64" - else ("x86" if struct.calcsize("P") == 4 else "AMD64") - ), - ), + default=os.environ.get("ARCHITECTURE", get_python_architecture()), help="build architecture (default: same as host Python)", ) parser.add_argument( @@ -697,13 +771,16 @@ def main() -> None: args = parser.parse_args() arch_prefs = ARCHITECTURES[args.architecture] + host_architecture = get_host_architecture() + print("Host architecture:", host_architecture) print("Target architecture:", args.architecture) - msvs = find_msvs(args.architecture) + msvs = find_msvs(args.architecture, host_architecture) if msvs is None: msg = "Visual Studio not found. Please install Visual Studio 2017 or newer." raise RuntimeError(msg) print("Found Visual Studio at:", msvs["vs_dir"]) + print("Using compilers:", msvs["vcvars_arch"]) # dependency cache directory args.depends_dir = os.path.abspath(args.depends_dir) @@ -737,8 +814,22 @@ def main() -> None: if args.no_avif or args.architecture == "ARM64": disabled += ["libavif"] + cmake_system = cargo_target = cargo_dir = "" + if host_architecture != args.architecture: + cmake_system = ( + f"-DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_PROCESSOR={args.architecture}" + ) + cargo_target = f"--target {CARGO_TARGETS[args.architecture]}" + cargo_dir = CARGO_TARGETS[args.architecture] + "\\" + + runnable = args.architecture in HOST_ARCHITECTURES[host_architecture] + prefs = { "architecture": args.architecture, + "cmake_system": cmake_system, + "cargo_target": cargo_target, + "cargo_dir": cargo_dir, + "fribidi_gen": "TRUE" if runnable else "FALSE", **arch_prefs, # Pillow paths "winbuild_dir": winbuild_dir, diff --git a/winbuild/fribidi.cmake b/winbuild/fribidi.cmake index b16e0784c98..47f9f2073a3 100644 --- a/winbuild/fribidi.cmake +++ b/winbuild/fribidi.cmake @@ -33,12 +33,7 @@ endfunction() fribidi_conf() -option(ARCH "Target architecture") -if(${ARCH} STREQUAL ARM64) - set(GEN FALSE) -else() - set(GEN TRUE) -endif() +option(GEN "Generate tab.i files" TRUE) message("Generate tab.i files: " ${GEN}) function(prepend var prefix) From ba85d780b52f3003dddda0042e5f9bd8e5919b54 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 24 Sep 2026 14:29:44 +0300 Subject: [PATCH 3/3] Fix Imagequant linkage on Windows --- setup.py | 2 ++ winbuild/README.md | 1 + winbuild/build.rst | 3 +++ winbuild/build_prepare.py | 8 ++++++-- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2d02cf129ca..d8a356df030 100644 --- a/setup.py +++ b/setup.py @@ -932,6 +932,8 @@ def build_extensions(self) -> None: defs.append(("HAVE_LIBZ", None)) if feature.get("imagequant"): libs.append(feature.get("imagequant")) + if sys.platform == "win32": + libs.extend(["ws2_32", "userenv", "ntdll"]) defs.append(("HAVE_LIBIMAGEQUANT", None)) if feature.get("xcb"): libs.append(feature.get("xcb")) diff --git a/winbuild/README.md b/winbuild/README.md index d6271ab1060..ba08ffffe5f 100644 --- a/winbuild/README.md +++ b/winbuild/README.md @@ -11,6 +11,7 @@ For more extensive info, see the [Windows build instructions](build.rst). * Requires Microsoft Visual Studio 2017 or newer with C++ component. * Requires NASM for libjpeg-turbo, a required dependency when using this script. * Requires CMake 3.15 or newer (available as Visual Studio component). +* Requires Rust for libimagequant; skip it with `--no-imagequant`. * Tested on Windows Server 2025 and 2022 with Visual Studio 2022 Enterprise (GitHub Actions). diff --git a/winbuild/build.rst b/winbuild/build.rst index d6127c1ed0f..a586d13ed76 100644 --- a/winbuild/build.rst +++ b/winbuild/build.rst @@ -29,6 +29,9 @@ Download and install: * x86/AMD64: `Netwide Assembler (NASM) `_ +* libimagequant: `Rust `_ + (optional, use ``--no-imagequant`` if not available) + Any version of Visual Studio 2017 or newer should be supported, including Visual Studio 2017 Community, or Build Tools for Visual Studio 2019. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index cf2b217573e..2081d57fe5b 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -371,15 +371,19 @@ def get_python_architecture() -> str: "libs": [r"bin\*.lib"], }, "libimagequant": { - "url": "https://github.com/ImageOptim/libimagequant/archive/{V['libimagequant']}.tar.gz", + "url": f"https://github.com/ImageOptim/libimagequant/archive/{V['libimagequant']}.tar.gz", "filename": f"libimagequant-{V['libimagequant']}.tar.gz", "license": "COPYRIGHT", "build": [ cmd_cd("imagequant-sys"), "cargo build --release {cargo_target}", + cmd_copy( + r"..\target\{cargo_dir}release\imagequant_sys.lib", + "imagequant.lib", + ), ], "headers": ["libimagequant.h"], - "libs": [r"..\target\{cargo_dir}release\imagequant_sys.lib"], + "libs": ["imagequant.lib"], }, "harfbuzz": { "url": f"https://github.com/harfbuzz/harfbuzz/releases/download/{V['harfbuzz']}/FILENAME",