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
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
2 changes: 2 additions & 0 deletions winbuild/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand All @@ -21,6 +22,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%
Expand Down
4 changes: 4 additions & 0 deletions winbuild/build.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ Download and install:

* x86/AMD64: `Netwide Assembler (NASM) <https://www.nasm.us/pub/nasm/releasebuilds/?C=M;O=D>`_

* libimagequant: `Rust <https://rustup.rs>`_
(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.

Expand All @@ -44,6 +47,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.

Expand Down
135 changes: 115 additions & 20 deletions winbuild/build_prepare.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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()
)
Expand Down Expand Up @@ -322,15 +371,19 @@ def cmd_msbuild(
"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 build --release {cargo_target}",
cmd_copy(
r"..\target\{cargo_dir}release\imagequant_sys.lib",
"imagequant.lib",
),
],
"headers": ["libimagequant.h"],
"libs": [r"..\target\release\imagequant_sys.lib"],
"libs": ["imagequant.lib"],
},
"harfbuzz": {
"url": f"https://github.com/harfbuzz/harfbuzz/releases/download/{V['harfbuzz']}/FILENAME",
Expand All @@ -356,11 +409,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"],
},
Expand Down Expand Up @@ -391,7 +449,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")
Expand Down Expand Up @@ -442,10 +500,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
}

Expand Down Expand Up @@ -660,14 +745,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(
Expand Down Expand Up @@ -697,13 +775,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)
Expand Down Expand Up @@ -737,8 +818,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,
Expand Down
7 changes: 1 addition & 6 deletions winbuild/fribidi.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading