mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 09:56:17 +03:00
d17947e802
when compiling Pillow with libtiff and libjpeg (with jpeg12 enabled - which is the default with libjpeg-3.0.0) the libtiff object tif_jpeg_12.c.o uses the following libjpeg12 functions: jpeg12_read_raw_data, jpeg12_read_scanlines, jpeg12_write_raw_data, jpeg12_write_scanlines. update the ordering of libs.append(feature.tiff) to be before libs.append(feature.jpeg) to allow the linker to include the required functions. this issue occurs when the libtiff and libjpeg libraries are static (not shared.) Signed-off-by: Rudi Heitbaum <rudi@heitbaum.com>
1015 lines
36 KiB
Python
Executable File
1015 lines
36 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# > pyroma .
|
|
# ------------------------------
|
|
# Checking .
|
|
# Found Pillow
|
|
# ------------------------------
|
|
# Final rating: 10/10
|
|
# Your cheese is so fresh most people think it's a cream: Mascarpone
|
|
# ------------------------------
|
|
|
|
import os
|
|
import re
|
|
import shutil
|
|
import struct
|
|
import subprocess
|
|
import sys
|
|
import warnings
|
|
|
|
from setuptools import Extension, setup
|
|
from setuptools.command.build_ext import build_ext
|
|
|
|
|
|
def get_version():
|
|
version_file = "src/PIL/_version.py"
|
|
with open(version_file, encoding="utf-8") as f:
|
|
exec(compile(f.read(), version_file, "exec"))
|
|
return locals()["__version__"]
|
|
|
|
|
|
PILLOW_VERSION = get_version()
|
|
FREETYPE_ROOT = None
|
|
HARFBUZZ_ROOT = None
|
|
FRIBIDI_ROOT = None
|
|
IMAGEQUANT_ROOT = None
|
|
JPEG2K_ROOT = None
|
|
JPEG_ROOT = None
|
|
LCMS_ROOT = None
|
|
TIFF_ROOT = None
|
|
ZLIB_ROOT = None
|
|
FUZZING_BUILD = "LIB_FUZZING_ENGINE" in os.environ
|
|
|
|
if sys.platform == "win32" and sys.version_info >= (3, 12):
|
|
import atexit
|
|
|
|
atexit.register(
|
|
lambda: warnings.warn(
|
|
f"Pillow {PILLOW_VERSION} does not support Python "
|
|
f"{sys.version_info.major}.{sys.version_info.minor} and does not provide "
|
|
"prebuilt Windows binaries. We do not recommend building from source on "
|
|
"Windows.",
|
|
RuntimeWarning,
|
|
)
|
|
)
|
|
|
|
|
|
_IMAGING = ("decode", "encode", "map", "display", "outline", "path")
|
|
|
|
_LIB_IMAGING = (
|
|
"Access",
|
|
"AlphaComposite",
|
|
"Resample",
|
|
"Reduce",
|
|
"Bands",
|
|
"BcnDecode",
|
|
"BitDecode",
|
|
"Blend",
|
|
"Chops",
|
|
"ColorLUT",
|
|
"Convert",
|
|
"ConvertYCbCr",
|
|
"Copy",
|
|
"Crop",
|
|
"Dib",
|
|
"Draw",
|
|
"Effects",
|
|
"EpsEncode",
|
|
"File",
|
|
"Fill",
|
|
"Filter",
|
|
"FliDecode",
|
|
"Geometry",
|
|
"GetBBox",
|
|
"GifDecode",
|
|
"GifEncode",
|
|
"HexDecode",
|
|
"Histo",
|
|
"JpegDecode",
|
|
"JpegEncode",
|
|
"Matrix",
|
|
"ModeFilter",
|
|
"Negative",
|
|
"Offset",
|
|
"Pack",
|
|
"PackDecode",
|
|
"Palette",
|
|
"Paste",
|
|
"Quant",
|
|
"QuantOctree",
|
|
"QuantHash",
|
|
"QuantHeap",
|
|
"PcdDecode",
|
|
"PcxDecode",
|
|
"PcxEncode",
|
|
"Point",
|
|
"RankFilter",
|
|
"RawDecode",
|
|
"RawEncode",
|
|
"Storage",
|
|
"SgiRleDecode",
|
|
"SunRleDecode",
|
|
"TgaRleDecode",
|
|
"TgaRleEncode",
|
|
"Unpack",
|
|
"UnpackYCC",
|
|
"UnsharpMask",
|
|
"XbmDecode",
|
|
"XbmEncode",
|
|
"ZipDecode",
|
|
"ZipEncode",
|
|
"TiffDecode",
|
|
"Jpeg2KDecode",
|
|
"Jpeg2KEncode",
|
|
"BoxBlur",
|
|
"QuantPngQuant",
|
|
"codec_fd",
|
|
)
|
|
|
|
DEBUG = False
|
|
|
|
|
|
class DependencyException(Exception):
|
|
pass
|
|
|
|
|
|
class RequiredDependencyException(Exception):
|
|
pass
|
|
|
|
|
|
PLATFORM_MINGW = os.name == "nt" and "GCC" in sys.version
|
|
|
|
|
|
def _dbg(s, tp=None):
|
|
if DEBUG:
|
|
if tp:
|
|
print(s % tp)
|
|
return
|
|
print(s)
|
|
|
|
|
|
def _find_library_dirs_ldconfig():
|
|
# Based on ctypes.util from Python 2
|
|
|
|
ldconfig = "ldconfig" if shutil.which("ldconfig") else "/sbin/ldconfig"
|
|
if sys.platform.startswith("linux") or sys.platform.startswith("gnu"):
|
|
if struct.calcsize("l") == 4:
|
|
machine = os.uname()[4] + "-32"
|
|
else:
|
|
machine = os.uname()[4] + "-64"
|
|
mach_map = {
|
|
"x86_64-64": "libc6,x86-64",
|
|
"ppc64-64": "libc6,64bit",
|
|
"sparc64-64": "libc6,64bit",
|
|
"s390x-64": "libc6,64bit",
|
|
"ia64-64": "libc6,IA-64",
|
|
}
|
|
abi_type = mach_map.get(machine, "libc6")
|
|
|
|
# Assuming GLIBC's ldconfig (with option -p)
|
|
# Alpine Linux uses musl that can't print cache
|
|
args = [ldconfig, "-p"]
|
|
expr = rf".*\({abi_type}.*\) => (.*)"
|
|
env = dict(os.environ)
|
|
env["LC_ALL"] = "C"
|
|
env["LANG"] = "C"
|
|
|
|
elif sys.platform.startswith("freebsd"):
|
|
args = [ldconfig, "-r"]
|
|
expr = r".* => (.*)"
|
|
env = {}
|
|
|
|
try:
|
|
p = subprocess.Popen(
|
|
args, stderr=subprocess.DEVNULL, stdout=subprocess.PIPE, env=env
|
|
)
|
|
except OSError: # E.g. command not found
|
|
return []
|
|
[data, _] = p.communicate()
|
|
if isinstance(data, bytes):
|
|
data = data.decode("latin1")
|
|
|
|
dirs = []
|
|
for dll in re.findall(expr, data):
|
|
dir = os.path.dirname(dll)
|
|
if dir not in dirs:
|
|
dirs.append(dir)
|
|
return dirs
|
|
|
|
|
|
def _add_directory(path, subdir, where=None):
|
|
if subdir is None:
|
|
return
|
|
subdir = os.path.realpath(subdir)
|
|
if os.path.isdir(subdir) and subdir not in path:
|
|
if where is None:
|
|
_dbg("Appending path %s", subdir)
|
|
path.append(subdir)
|
|
else:
|
|
_dbg("Inserting path %s", subdir)
|
|
path.insert(where, subdir)
|
|
elif subdir in path and where is not None:
|
|
path.remove(subdir)
|
|
path.insert(where, subdir)
|
|
|
|
|
|
def _find_include_file(self, include):
|
|
for directory in self.compiler.include_dirs:
|
|
_dbg("Checking for include file %s in %s", (include, directory))
|
|
if os.path.isfile(os.path.join(directory, include)):
|
|
_dbg("Found %s", include)
|
|
return 1
|
|
return 0
|
|
|
|
|
|
def _find_library_file(self, library):
|
|
ret = self.compiler.find_library_file(self.compiler.library_dirs, library)
|
|
if ret:
|
|
_dbg("Found library %s at %s", (library, ret))
|
|
else:
|
|
_dbg("Couldn't find library %s in %s", (library, self.compiler.library_dirs))
|
|
return ret
|
|
|
|
|
|
def _find_include_dir(self, dirname, include):
|
|
for directory in self.compiler.include_dirs:
|
|
_dbg("Checking for include file %s in %s", (include, directory))
|
|
if os.path.isfile(os.path.join(directory, include)):
|
|
_dbg("Found %s in %s", (include, directory))
|
|
return True
|
|
subdir = os.path.join(directory, dirname)
|
|
_dbg("Checking for include file %s in %s", (include, subdir))
|
|
if os.path.isfile(os.path.join(subdir, include)):
|
|
_dbg("Found %s in %s", (include, subdir))
|
|
return subdir
|
|
|
|
|
|
def _cmd_exists(cmd: str) -> bool:
|
|
if "PATH" not in os.environ:
|
|
return False
|
|
return any(
|
|
os.access(os.path.join(path, cmd), os.X_OK)
|
|
for path in os.environ["PATH"].split(os.pathsep)
|
|
)
|
|
|
|
|
|
def _pkg_config(name):
|
|
command = os.environ.get("PKG_CONFIG", "pkg-config")
|
|
for keep_system in (True, False):
|
|
try:
|
|
command_libs = [command, "--libs-only-L", name]
|
|
command_cflags = [command, "--cflags-only-I", name]
|
|
stderr = None
|
|
if keep_system:
|
|
command_libs.append("--keep-system-libs")
|
|
command_cflags.append("--keep-system-cflags")
|
|
stderr = subprocess.DEVNULL
|
|
if not DEBUG:
|
|
command_libs.append("--silence-errors")
|
|
command_cflags.append("--silence-errors")
|
|
libs = re.split(
|
|
r"(^|\s+)-L",
|
|
subprocess.check_output(command_libs, stderr=stderr)
|
|
.decode("utf8")
|
|
.strip(),
|
|
)[::2][1:]
|
|
cflags = re.split(
|
|
r"(^|\s+)-I",
|
|
subprocess.check_output(command_cflags).decode("utf8").strip(),
|
|
)[::2][1:]
|
|
return libs, cflags
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
class pil_build_ext(build_ext):
|
|
class feature:
|
|
features = [
|
|
"zlib",
|
|
"jpeg",
|
|
"tiff",
|
|
"freetype",
|
|
"raqm",
|
|
"lcms",
|
|
"webp",
|
|
"webpmux",
|
|
"jpeg2000",
|
|
"imagequant",
|
|
"xcb",
|
|
]
|
|
|
|
required = {"jpeg", "zlib"}
|
|
vendor = set()
|
|
|
|
def __init__(self):
|
|
for f in self.features:
|
|
setattr(self, f, None)
|
|
|
|
def require(self, feat):
|
|
return feat in self.required
|
|
|
|
def want(self, feat):
|
|
return getattr(self, feat) is None
|
|
|
|
def want_vendor(self, feat):
|
|
return feat in self.vendor
|
|
|
|
def __iter__(self):
|
|
yield from self.features
|
|
|
|
feature = feature()
|
|
|
|
user_options = (
|
|
build_ext.user_options
|
|
+ [(f"disable-{x}", None, f"Disable support for {x}") for x in feature]
|
|
+ [(f"enable-{x}", None, f"Enable support for {x}") for x in feature]
|
|
+ [
|
|
(f"vendor-{x}", None, f"Use vendored version of {x}")
|
|
for x in ("raqm", "fribidi")
|
|
]
|
|
+ [
|
|
("disable-platform-guessing", None, "Disable platform guessing on Linux"),
|
|
("debug", None, "Debug logging"),
|
|
]
|
|
+ [("add-imaging-libs=", None, "Add libs to _imaging build")]
|
|
)
|
|
|
|
def initialize_options(self):
|
|
self.disable_platform_guessing = None
|
|
self.add_imaging_libs = ""
|
|
build_ext.initialize_options(self)
|
|
for x in self.feature:
|
|
setattr(self, f"disable_{x}", None)
|
|
setattr(self, f"enable_{x}", None)
|
|
for x in ("raqm", "fribidi"):
|
|
setattr(self, f"vendor_{x}", None)
|
|
|
|
def finalize_options(self):
|
|
build_ext.finalize_options(self)
|
|
if self.debug:
|
|
global DEBUG
|
|
DEBUG = True
|
|
if not self.parallel:
|
|
# If --parallel (or -j) wasn't specified, we want to reproduce the same
|
|
# behavior as before, that is, auto-detect the number of jobs.
|
|
try:
|
|
self.parallel = int(
|
|
os.environ.get("MAX_CONCURRENCY", min(4, os.cpu_count()))
|
|
)
|
|
except TypeError:
|
|
self.parallel = None
|
|
for x in self.feature:
|
|
if getattr(self, f"disable_{x}"):
|
|
setattr(self.feature, x, False)
|
|
self.feature.required.discard(x)
|
|
_dbg("Disabling %s", x)
|
|
if getattr(self, f"enable_{x}"):
|
|
msg = f"Conflicting options: --enable-{x} and --disable-{x}"
|
|
raise ValueError(msg)
|
|
if x == "freetype":
|
|
_dbg("--disable-freetype implies --disable-raqm")
|
|
if getattr(self, "enable_raqm"):
|
|
msg = (
|
|
"Conflicting options: --enable-raqm and --disable-freetype"
|
|
)
|
|
raise ValueError(msg)
|
|
setattr(self, "disable_raqm", True)
|
|
if getattr(self, f"enable_{x}"):
|
|
_dbg("Requiring %s", x)
|
|
self.feature.required.add(x)
|
|
if x == "raqm":
|
|
_dbg("--enable-raqm implies --enable-freetype")
|
|
self.feature.required.add("freetype")
|
|
for x in ("raqm", "fribidi"):
|
|
if getattr(self, f"vendor_{x}"):
|
|
if getattr(self, "disable_raqm"):
|
|
msg = f"Conflicting options: --vendor-{x} and --disable-raqm"
|
|
raise ValueError(msg)
|
|
if x == "fribidi" and not getattr(self, "vendor_raqm"):
|
|
msg = f"Conflicting options: --vendor-{x} and not --vendor-raqm"
|
|
raise ValueError(msg)
|
|
_dbg("Using vendored version of %s", x)
|
|
self.feature.vendor.add(x)
|
|
|
|
def _update_extension(self, name, libraries, define_macros=None, sources=None):
|
|
for extension in self.extensions:
|
|
if extension.name == name:
|
|
extension.libraries += libraries
|
|
if define_macros is not None:
|
|
extension.define_macros += define_macros
|
|
if sources is not None:
|
|
extension.sources += sources
|
|
if FUZZING_BUILD:
|
|
extension.language = "c++"
|
|
extension.extra_link_args = ["--stdlib=libc++"]
|
|
break
|
|
|
|
def _remove_extension(self, name):
|
|
for extension in self.extensions:
|
|
if extension.name == name:
|
|
self.extensions.remove(extension)
|
|
break
|
|
|
|
def get_macos_sdk_path(self):
|
|
try:
|
|
sdk_path = (
|
|
subprocess.check_output(["xcrun", "--show-sdk-path"])
|
|
.strip()
|
|
.decode("latin1")
|
|
)
|
|
except Exception:
|
|
sdk_path = None
|
|
if (
|
|
not sdk_path
|
|
or sdk_path == "/Applications/Xcode.app/Contents/Developer"
|
|
"/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk"
|
|
):
|
|
commandlinetools_sdk_path = (
|
|
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"
|
|
)
|
|
if os.path.exists(commandlinetools_sdk_path):
|
|
sdk_path = commandlinetools_sdk_path
|
|
return sdk_path
|
|
|
|
def build_extensions(self):
|
|
library_dirs = []
|
|
include_dirs = []
|
|
|
|
pkg_config = None
|
|
if _cmd_exists(os.environ.get("PKG_CONFIG", "pkg-config")):
|
|
pkg_config = _pkg_config
|
|
|
|
#
|
|
# add configured kits
|
|
for root_name, lib_name in dict(
|
|
JPEG_ROOT="libjpeg",
|
|
JPEG2K_ROOT="libopenjp2",
|
|
TIFF_ROOT=("libtiff-5", "libtiff-4"),
|
|
ZLIB_ROOT="zlib",
|
|
FREETYPE_ROOT="freetype2",
|
|
HARFBUZZ_ROOT="harfbuzz",
|
|
FRIBIDI_ROOT="fribidi",
|
|
LCMS_ROOT="lcms2",
|
|
IMAGEQUANT_ROOT="libimagequant",
|
|
).items():
|
|
root = globals()[root_name]
|
|
|
|
if root is None and root_name in os.environ:
|
|
prefix = os.environ[root_name]
|
|
root = (os.path.join(prefix, "lib"), os.path.join(prefix, "include"))
|
|
|
|
if root is None and pkg_config:
|
|
if isinstance(lib_name, tuple):
|
|
for lib_name2 in lib_name:
|
|
_dbg(f"Looking for `{lib_name2}` using pkg-config.")
|
|
root = pkg_config(lib_name2)
|
|
if root:
|
|
break
|
|
else:
|
|
_dbg(f"Looking for `{lib_name}` using pkg-config.")
|
|
root = pkg_config(lib_name)
|
|
|
|
if isinstance(root, tuple):
|
|
lib_root, include_root = root
|
|
else:
|
|
lib_root = include_root = root
|
|
|
|
if lib_root is not None:
|
|
if not isinstance(lib_root, (tuple, list)):
|
|
lib_root = (lib_root,)
|
|
for lib_dir in lib_root:
|
|
_add_directory(library_dirs, lib_dir)
|
|
if include_root is not None:
|
|
if not isinstance(include_root, (tuple, list)):
|
|
include_root = (include_root,)
|
|
for include_dir in include_root:
|
|
_add_directory(include_dirs, include_dir)
|
|
|
|
# respect CFLAGS/CPPFLAGS/LDFLAGS
|
|
for k in ("CFLAGS", "CPPFLAGS", "LDFLAGS"):
|
|
if k in os.environ:
|
|
for match in re.finditer(r"-I([^\s]+)", os.environ[k]):
|
|
_add_directory(include_dirs, match.group(1))
|
|
for match in re.finditer(r"-L([^\s]+)", os.environ[k]):
|
|
_add_directory(library_dirs, match.group(1))
|
|
|
|
# include, rpath, if set as environment variables:
|
|
for k in ("C_INCLUDE_PATH", "CPATH", "INCLUDE"):
|
|
if k in os.environ:
|
|
for d in os.environ[k].split(os.path.pathsep):
|
|
_add_directory(include_dirs, d)
|
|
|
|
for k in ("LD_RUN_PATH", "LIBRARY_PATH", "LIB"):
|
|
if k in os.environ:
|
|
for d in os.environ[k].split(os.path.pathsep):
|
|
_add_directory(library_dirs, d)
|
|
|
|
_add_directory(library_dirs, os.path.join(sys.prefix, "lib"))
|
|
_add_directory(include_dirs, os.path.join(sys.prefix, "include"))
|
|
|
|
#
|
|
# add platform directories
|
|
|
|
if self.disable_platform_guessing:
|
|
pass
|
|
|
|
elif sys.platform == "cygwin":
|
|
# pythonX.Y.dll.a is in the /usr/lib/pythonX.Y/config directory
|
|
self.compiler.shared_lib_extension = ".dll.a"
|
|
_add_directory(
|
|
library_dirs,
|
|
os.path.join(
|
|
"/usr/lib", "python{}.{}".format(*sys.version_info), "config"
|
|
),
|
|
)
|
|
|
|
elif sys.platform == "darwin":
|
|
# attempt to make sure we pick freetype2 over other versions
|
|
_add_directory(include_dirs, "/sw/include/freetype2")
|
|
_add_directory(include_dirs, "/sw/lib/freetype2/include")
|
|
# fink installation directories
|
|
_add_directory(library_dirs, "/sw/lib")
|
|
_add_directory(include_dirs, "/sw/include")
|
|
# darwin ports installation directories
|
|
_add_directory(library_dirs, "/opt/local/lib")
|
|
_add_directory(include_dirs, "/opt/local/include")
|
|
|
|
# if Homebrew is installed, use its lib and include directories
|
|
try:
|
|
prefix = (
|
|
subprocess.check_output(["brew", "--prefix"])
|
|
.strip()
|
|
.decode("latin1")
|
|
)
|
|
except Exception:
|
|
# Homebrew not installed
|
|
prefix = None
|
|
|
|
ft_prefix = None
|
|
|
|
if prefix:
|
|
# add Homebrew's include and lib directories
|
|
_add_directory(library_dirs, os.path.join(prefix, "lib"))
|
|
_add_directory(include_dirs, os.path.join(prefix, "include"))
|
|
_add_directory(
|
|
include_dirs, os.path.join(prefix, "opt", "zlib", "include")
|
|
)
|
|
ft_prefix = os.path.join(prefix, "opt", "freetype")
|
|
|
|
if ft_prefix and os.path.isdir(ft_prefix):
|
|
# freetype might not be linked into Homebrew's prefix
|
|
_add_directory(library_dirs, os.path.join(ft_prefix, "lib"))
|
|
_add_directory(include_dirs, os.path.join(ft_prefix, "include"))
|
|
else:
|
|
# fall back to freetype from XQuartz if
|
|
# Homebrew's freetype is missing
|
|
_add_directory(library_dirs, "/usr/X11/lib")
|
|
_add_directory(include_dirs, "/usr/X11/include")
|
|
|
|
sdk_path = self.get_macos_sdk_path()
|
|
if sdk_path:
|
|
_add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib"))
|
|
_add_directory(include_dirs, os.path.join(sdk_path, "usr", "include"))
|
|
elif (
|
|
sys.platform.startswith("linux")
|
|
or sys.platform.startswith("gnu")
|
|
or sys.platform.startswith("freebsd")
|
|
):
|
|
for dirname in _find_library_dirs_ldconfig():
|
|
_add_directory(library_dirs, dirname)
|
|
if sys.platform.startswith("linux") and os.environ.get("ANDROID_ROOT"):
|
|
# termux support for android.
|
|
# system libraries (zlib) are installed in /system/lib
|
|
# headers are at $PREFIX/include
|
|
# user libs are at $PREFIX/lib
|
|
_add_directory(
|
|
library_dirs,
|
|
os.path.join(
|
|
os.environ["ANDROID_ROOT"],
|
|
"lib" if struct.calcsize("l") == 4 else "lib64",
|
|
),
|
|
)
|
|
|
|
elif sys.platform.startswith("netbsd"):
|
|
_add_directory(library_dirs, "/usr/pkg/lib")
|
|
_add_directory(include_dirs, "/usr/pkg/include")
|
|
|
|
elif sys.platform.startswith("sunos5"):
|
|
_add_directory(library_dirs, "/opt/local/lib")
|
|
_add_directory(include_dirs, "/opt/local/include")
|
|
|
|
# FIXME: check /opt/stuff directories here?
|
|
|
|
# standard locations
|
|
if not self.disable_platform_guessing:
|
|
_add_directory(library_dirs, "/usr/local/lib")
|
|
_add_directory(include_dirs, "/usr/local/include")
|
|
|
|
_add_directory(library_dirs, "/usr/lib")
|
|
_add_directory(include_dirs, "/usr/include")
|
|
# alpine, at least
|
|
_add_directory(library_dirs, "/lib")
|
|
|
|
if sys.platform == "win32":
|
|
# on Windows, look for the OpenJPEG libraries in the location that
|
|
# the official installer puts them
|
|
program_files = os.environ.get("ProgramFiles", "")
|
|
best_version = (0, 0)
|
|
best_path = None
|
|
for name in os.listdir(program_files):
|
|
if name.startswith("OpenJPEG "):
|
|
version = tuple(int(x) for x in name[9:].strip().split("."))
|
|
if version > best_version:
|
|
best_version = version
|
|
best_path = os.path.join(program_files, name)
|
|
|
|
if best_path:
|
|
_dbg("Adding %s to search list", best_path)
|
|
_add_directory(library_dirs, os.path.join(best_path, "lib"))
|
|
_add_directory(include_dirs, os.path.join(best_path, "include"))
|
|
|
|
#
|
|
# insert new dirs *before* default libs, to avoid conflicts
|
|
# between Python PYD stub libs and real libraries
|
|
|
|
self.compiler.library_dirs = library_dirs + self.compiler.library_dirs
|
|
self.compiler.include_dirs = include_dirs + self.compiler.include_dirs
|
|
|
|
#
|
|
# look for available libraries
|
|
|
|
feature = self.feature
|
|
|
|
if feature.want("zlib"):
|
|
_dbg("Looking for zlib")
|
|
if _find_include_file(self, "zlib.h"):
|
|
if _find_library_file(self, "z"):
|
|
feature.zlib = "z"
|
|
elif sys.platform == "win32" and _find_library_file(self, "zlib"):
|
|
feature.zlib = "zlib" # alternative name
|
|
|
|
if feature.want("jpeg"):
|
|
_dbg("Looking for jpeg")
|
|
if _find_include_file(self, "jpeglib.h"):
|
|
if _find_library_file(self, "jpeg"):
|
|
feature.jpeg = "jpeg"
|
|
elif sys.platform == "win32" and _find_library_file(self, "libjpeg"):
|
|
feature.jpeg = "libjpeg" # alternative name
|
|
|
|
feature.openjpeg_version = None
|
|
if feature.want("jpeg2000"):
|
|
_dbg("Looking for jpeg2000")
|
|
best_version = None
|
|
best_path = None
|
|
|
|
# Find the best version
|
|
for directory in self.compiler.include_dirs:
|
|
_dbg("Checking for openjpeg-#.# in %s", directory)
|
|
try:
|
|
listdir = os.listdir(directory)
|
|
except Exception:
|
|
# OSError, FileNotFoundError
|
|
continue
|
|
for name in listdir:
|
|
if name.startswith("openjpeg-") and os.path.isfile(
|
|
os.path.join(directory, name, "openjpeg.h")
|
|
):
|
|
_dbg("Found openjpeg.h in %s/%s", (directory, name))
|
|
version = tuple(int(x) for x in name[9:].split("."))
|
|
if best_version is None or version > best_version:
|
|
best_version = version
|
|
best_path = os.path.join(directory, name)
|
|
_dbg(
|
|
"Best openjpeg version %s so far in %s",
|
|
(best_version, best_path),
|
|
)
|
|
|
|
if best_version and _find_library_file(self, "openjp2"):
|
|
# Add the directory to the include path so we can include
|
|
# <openjpeg.h> rather than having to cope with the versioned
|
|
# include path
|
|
_add_directory(self.compiler.include_dirs, best_path, 0)
|
|
feature.jpeg2000 = "openjp2"
|
|
feature.openjpeg_version = ".".join(str(x) for x in best_version)
|
|
|
|
if feature.want("imagequant"):
|
|
_dbg("Looking for imagequant")
|
|
if _find_include_file(self, "libimagequant.h"):
|
|
if _find_library_file(self, "imagequant"):
|
|
feature.imagequant = "imagequant"
|
|
elif _find_library_file(self, "libimagequant"):
|
|
feature.imagequant = "libimagequant"
|
|
|
|
if feature.want("tiff"):
|
|
_dbg("Looking for tiff")
|
|
if _find_include_file(self, "tiff.h"):
|
|
if _find_library_file(self, "tiff"):
|
|
feature.tiff = "tiff"
|
|
if sys.platform in ["win32", "darwin"] and _find_library_file(
|
|
self, "libtiff"
|
|
):
|
|
feature.tiff = "libtiff"
|
|
|
|
if feature.want("freetype"):
|
|
_dbg("Looking for freetype")
|
|
if _find_library_file(self, "freetype"):
|
|
# look for freetype2 include files
|
|
freetype_version = 0
|
|
for subdir in self.compiler.include_dirs:
|
|
_dbg("Checking for include file %s in %s", ("ft2build.h", subdir))
|
|
if os.path.isfile(os.path.join(subdir, "ft2build.h")):
|
|
_dbg("Found %s in %s", ("ft2build.h", subdir))
|
|
freetype_version = 21
|
|
subdir = os.path.join(subdir, "freetype2")
|
|
break
|
|
subdir = os.path.join(subdir, "freetype2")
|
|
_dbg("Checking for include file %s in %s", ("ft2build.h", subdir))
|
|
if os.path.isfile(os.path.join(subdir, "ft2build.h")):
|
|
_dbg("Found %s in %s", ("ft2build.h", subdir))
|
|
freetype_version = 21
|
|
break
|
|
if freetype_version:
|
|
feature.freetype = "freetype"
|
|
if subdir:
|
|
_add_directory(self.compiler.include_dirs, subdir, 0)
|
|
|
|
if feature.freetype and feature.want("raqm"):
|
|
if not feature.want_vendor("raqm"): # want system Raqm
|
|
_dbg("Looking for Raqm")
|
|
if _find_include_file(self, "raqm.h"):
|
|
if _find_library_file(self, "raqm"):
|
|
feature.raqm = "raqm"
|
|
elif _find_library_file(self, "libraqm"):
|
|
feature.raqm = "libraqm"
|
|
else: # want to build Raqm from src/thirdparty
|
|
_dbg("Looking for HarfBuzz")
|
|
feature.harfbuzz = None
|
|
hb_dir = _find_include_dir(self, "harfbuzz", "hb.h")
|
|
if hb_dir:
|
|
if isinstance(hb_dir, str):
|
|
_add_directory(self.compiler.include_dirs, hb_dir, 0)
|
|
if _find_library_file(self, "harfbuzz"):
|
|
feature.harfbuzz = "harfbuzz"
|
|
if feature.harfbuzz:
|
|
if not feature.want_vendor("fribidi"): # want system FriBiDi
|
|
_dbg("Looking for FriBiDi")
|
|
feature.fribidi = None
|
|
fribidi_dir = _find_include_dir(self, "fribidi", "fribidi.h")
|
|
if fribidi_dir:
|
|
if isinstance(fribidi_dir, str):
|
|
_add_directory(
|
|
self.compiler.include_dirs, fribidi_dir, 0
|
|
)
|
|
if _find_library_file(self, "fribidi"):
|
|
feature.fribidi = "fribidi"
|
|
feature.raqm = True
|
|
else: # want to build FriBiDi shim from src/thirdparty
|
|
feature.raqm = True
|
|
|
|
if feature.want("lcms"):
|
|
_dbg("Looking for lcms")
|
|
if _find_include_file(self, "lcms2.h"):
|
|
if _find_library_file(self, "lcms2"):
|
|
feature.lcms = "lcms2"
|
|
elif _find_library_file(self, "lcms2_static"):
|
|
# alternate Windows name.
|
|
feature.lcms = "lcms2_static"
|
|
|
|
if feature.want("webp"):
|
|
_dbg("Looking for webp")
|
|
if _find_include_file(self, "webp/encode.h") and _find_include_file(
|
|
self, "webp/decode.h"
|
|
):
|
|
# In Google's precompiled zip it is call "libwebp":
|
|
if _find_library_file(self, "webp"):
|
|
feature.webp = "webp"
|
|
elif _find_library_file(self, "libwebp"):
|
|
feature.webp = "libwebp"
|
|
|
|
if feature.want("webpmux"):
|
|
_dbg("Looking for webpmux")
|
|
if _find_include_file(self, "webp/mux.h") and _find_include_file(
|
|
self, "webp/demux.h"
|
|
):
|
|
if _find_library_file(self, "webpmux") and _find_library_file(
|
|
self, "webpdemux"
|
|
):
|
|
feature.webpmux = "webpmux"
|
|
if _find_library_file(self, "libwebpmux") and _find_library_file(
|
|
self, "libwebpdemux"
|
|
):
|
|
feature.webpmux = "libwebpmux"
|
|
|
|
if feature.want("xcb"):
|
|
_dbg("Looking for xcb")
|
|
if _find_include_file(self, "xcb/xcb.h"):
|
|
if _find_library_file(self, "xcb"):
|
|
feature.xcb = "xcb"
|
|
|
|
for f in feature:
|
|
if not getattr(feature, f) and feature.require(f):
|
|
if f in ("jpeg", "zlib"):
|
|
raise RequiredDependencyException(f)
|
|
raise DependencyException(f)
|
|
|
|
#
|
|
# core library
|
|
|
|
libs = self.add_imaging_libs.split()
|
|
defs = []
|
|
if feature.tiff:
|
|
libs.append(feature.tiff)
|
|
defs.append(("HAVE_LIBTIFF", None))
|
|
if sys.platform == "win32":
|
|
# This define needs to be defined if-and-only-if it was defined
|
|
# when compiling LibTIFF. LibTIFF doesn't expose it in `tiffconf.h`,
|
|
# so we have to guess; by default it is defined in all Windows builds.
|
|
# See #4237, #5243, #5359 for more information.
|
|
defs.append(("USE_WIN32_FILEIO", None))
|
|
if feature.jpeg:
|
|
libs.append(feature.jpeg)
|
|
defs.append(("HAVE_LIBJPEG", None))
|
|
if feature.jpeg2000:
|
|
libs.append(feature.jpeg2000)
|
|
defs.append(("HAVE_OPENJPEG", None))
|
|
if sys.platform == "win32" and not PLATFORM_MINGW:
|
|
defs.append(("OPJ_STATIC", None))
|
|
if feature.zlib:
|
|
libs.append(feature.zlib)
|
|
defs.append(("HAVE_LIBZ", None))
|
|
if feature.imagequant:
|
|
libs.append(feature.imagequant)
|
|
defs.append(("HAVE_LIBIMAGEQUANT", None))
|
|
if feature.xcb:
|
|
libs.append(feature.xcb)
|
|
defs.append(("HAVE_XCB", None))
|
|
if sys.platform == "win32":
|
|
libs.extend(["kernel32", "user32", "gdi32"])
|
|
if struct.unpack("h", b"\0\1")[0] == 1:
|
|
defs.append(("WORDS_BIGENDIAN", None))
|
|
|
|
defs.append(("PILLOW_VERSION", f'"{PILLOW_VERSION}"'))
|
|
|
|
self._update_extension("PIL._imaging", libs, defs)
|
|
|
|
#
|
|
# additional libraries
|
|
|
|
if feature.freetype:
|
|
srcs = []
|
|
libs = ["freetype"]
|
|
defs = []
|
|
if feature.raqm:
|
|
if not feature.want_vendor("raqm"): # using system Raqm
|
|
defs.append(("HAVE_RAQM", None))
|
|
defs.append(("HAVE_RAQM_SYSTEM", None))
|
|
libs.append(feature.raqm)
|
|
else: # building Raqm from src/thirdparty
|
|
defs.append(("HAVE_RAQM", None))
|
|
srcs.append("src/thirdparty/raqm/raqm.c")
|
|
libs.append(feature.harfbuzz)
|
|
if not feature.want_vendor("fribidi"): # using system FriBiDi
|
|
defs.append(("HAVE_FRIBIDI_SYSTEM", None))
|
|
libs.append(feature.fribidi)
|
|
else: # building FriBiDi shim from src/thirdparty
|
|
srcs.append("src/thirdparty/fribidi-shim/fribidi.c")
|
|
self._update_extension("PIL._imagingft", libs, defs, srcs)
|
|
|
|
else:
|
|
self._remove_extension("PIL._imagingft")
|
|
|
|
if feature.lcms:
|
|
extra = []
|
|
if sys.platform == "win32":
|
|
extra.extend(["user32", "gdi32"])
|
|
self._update_extension("PIL._imagingcms", [feature.lcms] + extra)
|
|
else:
|
|
self._remove_extension("PIL._imagingcms")
|
|
|
|
if feature.webp:
|
|
libs = [feature.webp]
|
|
defs = []
|
|
|
|
if feature.webpmux:
|
|
defs.append(("HAVE_WEBPMUX", None))
|
|
libs.append(feature.webpmux)
|
|
libs.append(feature.webpmux.replace("pmux", "pdemux"))
|
|
|
|
self._update_extension("PIL._webp", libs, defs)
|
|
else:
|
|
self._remove_extension("PIL._webp")
|
|
|
|
tk_libs = ["psapi"] if sys.platform in ("win32", "cygwin") else []
|
|
self._update_extension("PIL._imagingtk", tk_libs)
|
|
|
|
build_ext.build_extensions(self)
|
|
|
|
#
|
|
# sanity checks
|
|
|
|
self.summary_report(feature)
|
|
|
|
def summary_report(self, feature):
|
|
print("-" * 68)
|
|
print("PIL SETUP SUMMARY")
|
|
print("-" * 68)
|
|
print(f"version Pillow {PILLOW_VERSION}")
|
|
v = sys.version.split("[")
|
|
print(f"platform {sys.platform} {v[0].strip()}")
|
|
for v in v[1:]:
|
|
print(f" [{v.strip()}")
|
|
print("-" * 68)
|
|
|
|
raqm_extra_info = ""
|
|
if feature.want_vendor("raqm"):
|
|
raqm_extra_info += "bundled"
|
|
if feature.want_vendor("fribidi"):
|
|
raqm_extra_info += ", FriBiDi shim"
|
|
|
|
options = [
|
|
(feature.jpeg, "JPEG"),
|
|
(feature.jpeg2000, "OPENJPEG (JPEG2000)", feature.openjpeg_version),
|
|
(feature.zlib, "ZLIB (PNG/ZIP)"),
|
|
(feature.imagequant, "LIBIMAGEQUANT"),
|
|
(feature.tiff, "LIBTIFF"),
|
|
(feature.freetype, "FREETYPE2"),
|
|
(feature.raqm, "RAQM (Text shaping)", raqm_extra_info),
|
|
(feature.lcms, "LITTLECMS2"),
|
|
(feature.webp, "WEBP"),
|
|
(feature.webpmux, "WEBPMUX"),
|
|
(feature.xcb, "XCB (X protocol)"),
|
|
]
|
|
|
|
all = 1
|
|
for option in options:
|
|
if option[0]:
|
|
extra_info = ""
|
|
if len(option) >= 3 and option[2]:
|
|
extra_info = f" ({option[2]})"
|
|
print(f"--- {option[1]} support available{extra_info}")
|
|
else:
|
|
print(f"*** {option[1]} support not available")
|
|
all = 0
|
|
|
|
print("-" * 68)
|
|
|
|
if not all:
|
|
print("To add a missing option, make sure you have the required")
|
|
print("library and headers.")
|
|
print(
|
|
"See https://pillow.readthedocs.io/en/latest/installation."
|
|
"html#building-from-source"
|
|
)
|
|
print("")
|
|
|
|
print("To check the build, run the selftest.py script.")
|
|
print("")
|
|
|
|
|
|
def debug_build():
|
|
return hasattr(sys, "gettotalrefcount") or FUZZING_BUILD
|
|
|
|
|
|
files = ["src/_imaging.c"]
|
|
for src_file in _IMAGING:
|
|
files.append("src/" + src_file + ".c")
|
|
for src_file in _LIB_IMAGING:
|
|
files.append(os.path.join("src/libImaging", src_file + ".c"))
|
|
ext_modules = [
|
|
Extension("PIL._imaging", files),
|
|
Extension("PIL._imagingft", ["src/_imagingft.c"]),
|
|
Extension("PIL._imagingcms", ["src/_imagingcms.c"]),
|
|
Extension("PIL._webp", ["src/_webp.c"]),
|
|
Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]),
|
|
Extension("PIL._imagingmath", ["src/_imagingmath.c"]),
|
|
Extension("PIL._imagingmorph", ["src/_imagingmorph.c"]),
|
|
]
|
|
|
|
try:
|
|
setup(
|
|
version=PILLOW_VERSION,
|
|
cmdclass={"build_ext": pil_build_ext},
|
|
ext_modules=ext_modules,
|
|
zip_safe=not (debug_build() or PLATFORM_MINGW),
|
|
)
|
|
except RequiredDependencyException as err:
|
|
msg = f"""
|
|
|
|
The headers or library files could not be found for {str(err)},
|
|
a required dependency when compiling Pillow from source.
|
|
|
|
Please see the install instructions at:
|
|
https://pillow.readthedocs.io/en/latest/installation.html
|
|
|
|
"""
|
|
sys.stderr.write(msg)
|
|
raise RequiredDependencyException(msg)
|
|
except DependencyException as err:
|
|
msg = f"""
|
|
|
|
The headers or library files could not be found for {str(err)},
|
|
which was requested by the option flag --enable-{str(err)}
|
|
|
|
"""
|
|
sys.stderr.write(msg)
|
|
raise DependencyException(msg)
|