Merge branch 'main' into context_manager

This commit is contained in:
Andrew Murray 2024-11-29 13:48:16 +11:00
commit 8790593f8f
23 changed files with 190 additions and 115 deletions

View File

@ -19,7 +19,6 @@ Please send a pull request to the `main` branch. Please include [documentation](
- Follow PEP 8. - Follow PEP 8.
- When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor. - When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor.
- Include [release notes](https://github.com/python-pillow/Pillow/tree/main/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests. - Include [release notes](https://github.com/python-pillow/Pillow/tree/main/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests.
- Do not add to the [changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) for proposed changes, as that is updated after changes are merged.
## Reporting Issues ## Reporting Issues

View File

@ -3,18 +3,19 @@ tag-template: "$NEXT_MINOR_VERSION"
change-template: '- $TITLE #$NUMBER [@$AUTHOR]' change-template: '- $TITLE #$NUMBER [@$AUTHOR]'
categories: categories:
- title: "Dependencies" - title: "Removals"
label: "Dependency" label: "Removal"
- title: "Deprecations" - title: "Deprecations"
label: "Deprecation" label: "Deprecation"
- title: "Documentation" - title: "Documentation"
label: "Documentation" label: "Documentation"
- title: "Removals" - title: "Dependencies"
label: "Removal" label: "Dependency"
- title: "Testing" - title: "Testing"
label: "Testing" label: "Testing"
- title: "Type hints" - title: "Type hints"
label: "Type hints" label: "Type hints"
- title: "Other changes"
exclude-labels: exclude-labels:
- "changelog: skip" - "changelog: skip"
@ -23,6 +24,4 @@ template: |
https://pillow.readthedocs.io/en/stable/releasenotes/$NEXT_MINOR_VERSION.html https://pillow.readthedocs.io/en/stable/releasenotes/$NEXT_MINOR_VERSION.html
## Changes
$CHANGES $CHANGES

View File

@ -133,11 +133,12 @@ jobs:
- name: After success - name: After success
run: | run: |
bash.exe .ci/after_success.sh bash.exe .ci/after_success.sh
rm C:\cygwin\bin\bash.EXE
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v5
with: with:
file: ./coverage.xml files: ./coverage.xml
flags: GHA_Cygwin flags: GHA_Cygwin
name: Cygwin Python 3.${{ matrix.python-minor-version }} name: Cygwin Python 3.${{ matrix.python-minor-version }}
token: ${{ secrets.CODECOV_ORG_TOKEN }} token: ${{ secrets.CODECOV_ORG_TOKEN }}

View File

@ -100,7 +100,7 @@ jobs:
MATRIX_DOCKER: ${{ matrix.docker }} MATRIX_DOCKER: ${{ matrix.docker }}
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v5
with: with:
flags: GHA_Docker flags: GHA_Docker
name: ${{ matrix.docker }} name: ${{ matrix.docker }}

View File

@ -68,16 +68,16 @@ jobs:
mingw-w64-x86_64-openjpeg2 \ mingw-w64-x86_64-openjpeg2 \
mingw-w64-x86_64-python3-numpy \ mingw-w64-x86_64-python3-numpy \
mingw-w64-x86_64-python3-olefile \ mingw-w64-x86_64-python3-olefile \
mingw-w64-x86_64-python3-setuptools \ mingw-w64-x86_64-python3-pip \
mingw-w64-x86_64-python-pytest \
mingw-w64-x86_64-python-pytest-cov \
mingw-w64-x86_64-python-pytest-timeout \
mingw-w64-x86_64-python-pyqt6 mingw-w64-x86_64-python-pyqt6
python3 -m ensurepip
python3 -m pip install pyroma pytest pytest-cov pytest-timeout
pushd depends && ./install_extra_test_images.sh && popd pushd depends && ./install_extra_test_images.sh && popd
- name: Build Pillow - name: Build Pillow
run: SETUPTOOLS_USE_DISTUTILS="stdlib" CFLAGS="-coverage" python3 -m pip install . run: CFLAGS="-coverage" python3 -m pip install .
- name: Test Pillow - name: Test Pillow
run: | run: |
@ -85,9 +85,9 @@ jobs:
.ci/test.sh .ci/test.sh
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v5
with: with:
file: ./coverage.xml files: ./coverage.xml
flags: GHA_Windows flags: GHA_Windows
name: "MSYS2 MinGW" name: "MSYS2 MinGW"
token: ${{ secrets.CODECOV_ORG_TOKEN }} token: ${{ secrets.CODECOV_ORG_TOKEN }}

View File

@ -215,9 +215,9 @@ jobs:
shell: pwsh shell: pwsh
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v5
with: with:
file: ./coverage.xml files: ./coverage.xml
flags: GHA_Windows flags: GHA_Windows
name: ${{ runner.os }} Python ${{ matrix.python-version }} name: ${{ runner.os }} Python ${{ matrix.python-version }}
token: ${{ secrets.CODECOV_ORG_TOKEN }} token: ${{ secrets.CODECOV_ORG_TOKEN }}

View File

@ -42,6 +42,7 @@ jobs:
] ]
python-version: [ python-version: [
"pypy3.10", "pypy3.10",
"3.13t",
"3.13", "3.13",
"3.12", "3.12",
"3.11", "3.11",
@ -52,14 +53,14 @@ jobs:
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" } - { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
- { python-version: "3.10", PYTHONOPTIMIZE: 2 } - { python-version: "3.10", PYTHONOPTIMIZE: 2 }
# Free-threaded # Free-threaded
- { os: "ubuntu-latest", python-version: "3.13-dev", disable-gil: true } - { python-version: "3.13t", disable-gil: true }
# M1 only available for 3.10+ # M1 only available for 3.10+
- { os: "macos-13", python-version: "3.9" } - { os: "macos-13", python-version: "3.9" }
exclude: exclude:
- { os: "macos-latest", python-version: "3.9" } - { os: "macos-latest", python-version: "3.9" }
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }} ${{ matrix.disable-gil && 'free-threaded' || '' }} name: ${{ matrix.os }} Python ${{ matrix.python-version }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -67,8 +68,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5 uses: Quansight-Labs/setup-python@v5
if: "${{ !matrix.disable-gil }}"
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
allow-prereleases: true allow-prereleases: true
@ -77,13 +77,6 @@ jobs:
".ci/*.sh" ".ci/*.sh"
"pyproject.toml" "pyproject.toml"
- name: Set up Python ${{ matrix.python-version }} (free-threaded)
uses: deadsnakes/action@v3.2.0
if: "${{ matrix.disable-gil }}"
with:
python-version: ${{ matrix.python-version }}
nogil: ${{ matrix.disable-gil }}
- name: Set PYTHON_GIL - name: Set PYTHON_GIL
if: "${{ matrix.disable-gil }}" if: "${{ matrix.disable-gil }}"
run: | run: |
@ -116,7 +109,7 @@ jobs:
GHA_PYTHON_VERSION: ${{ matrix.python-version }} GHA_PYTHON_VERSION: ${{ matrix.python-version }}
- name: Register gcc problem matcher - name: Register gcc problem matcher
if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'" if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'"
run: echo "::add-matcher::.github/problem-matchers/gcc.json" run: echo "::add-matcher::.github/problem-matchers/gcc.json"
- name: Build - name: Build
@ -156,7 +149,7 @@ jobs:
.ci/after_success.sh .ci/after_success.sh
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v5
with: with:
flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }} flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }} name: ${{ matrix.os }} Python ${{ matrix.python-version }}

View File

@ -1,11 +1,33 @@
#!/bin/bash #!/bin/bash
# Define custom utilities
# Test for macOS with [ -n "$IS_MACOS" ] # Setup that needs to be done before multibuild utils are invoked
if [ -z "$IS_MACOS" ]; then PROJECTDIR=$(pwd)
export MB_ML_LIBC=${AUDITWHEEL_POLICY::9} if [[ "$(uname -s)" == "Darwin" ]]; then
export MB_ML_VER=${AUDITWHEEL_POLICY:9} # Safety check - macOS builds require that CIBW_ARCHS is set, and that it
# only contains a single value (even though cibuildwheel allows multiple
# values in CIBW_ARCHS).
if [[ -z "$CIBW_ARCHS" ]]; then
echo "ERROR: Pillow macOS builds require CIBW_ARCHS be defined."
exit 1
fi
if [[ "$CIBW_ARCHS" == *" "* ]]; then
echo "ERROR: Pillow macOS builds only support a single architecture in CIBW_ARCHS."
exit 1
fi
# Build macOS dependencies in `build/darwin`
# Install them into `build/deps/darwin`
WORKDIR=$(pwd)/build/darwin
BUILD_PREFIX=$(pwd)/build/deps/darwin
else
# Build prefix will default to /usr/local
WORKDIR=$(pwd)/build
MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
MB_ML_VER=${AUDITWHEEL_POLICY:9}
fi fi
export PLAT=$CIBW_ARCHS PLAT=$CIBW_ARCHS
# Define custom utilities
source wheels/multibuild/common_utils.sh source wheels/multibuild/common_utils.sh
source wheels/multibuild/library_builders.sh source wheels/multibuild/library_builders.sh
if [ -z "$IS_MACOS" ]; then if [ -z "$IS_MACOS" ]; then
@ -38,35 +60,42 @@ BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0 LIBXCB_VERSION=1.17.0
BROTLI_VERSION=1.1.0 BROTLI_VERSION=1.1.0
function build_pkg_config {
if [ -e pkg-config-stamp ]; then return; fi
# This essentially duplicates the Homebrew recipe
ORIGINAL_CFLAGS=$CFLAGS
CFLAGS="$CFLAGS -Wno-int-conversion"
build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \
--disable-debug --disable-host-tool --with-internal-glib \
--with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \
--with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include
CFLAGS=$ORIGINAL_CFLAGS
export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config
touch pkg-config-stamp
}
function build_brotli { function build_brotli {
local cmake=$(get_modern_cmake) if [ -e brotli-stamp ]; then return; fi
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz) local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
(cd $out_dir \ (cd $out_dir \
&& $cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \ && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
&& make install) && make install)
if [[ "$MB_ML_LIBC" == "manylinux" ]]; then touch brotli-stamp
cp /usr/local/lib64/libbrotli* /usr/local/lib
cp /usr/local/lib64/pkgconfig/libbrotli* /usr/local/lib/pkgconfig
fi
} }
function build_harfbuzz { function build_harfbuzz {
if [ -e harfbuzz-stamp ]; then return; fi
python3 -m pip install meson ninja python3 -m pip install meson ninja
local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz) local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
(cd $out_dir \ (cd $out_dir \
&& meson setup build --buildtype=release -Dfreetype=enabled -Dglib=disabled) && meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=release -Dfreetype=enabled -Dglib=disabled)
(cd $out_dir/build \ (cd $out_dir/build \
&& meson install) && meson install)
if [[ "$MB_ML_LIBC" == "manylinux" ]]; then touch harfbuzz-stamp
cp /usr/local/lib64/libharfbuzz* /usr/local/lib
fi
} }
function build { function build {
if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then
sudo chown -R runner /usr/local
fi
build_xz build_xz
if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
yum remove -y zlib-devel yum remove -y zlib-devel
@ -78,16 +107,24 @@ function build {
build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto
build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
if [[ "$CIBW_ARCHS" == "arm64" ]]; then
cp /usr/local/share/pkgconfig/xcb-proto.pc /usr/local/lib/pkgconfig
fi
else else
sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
fi fi
build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib
build_libjpeg_turbo build_libjpeg_turbo
build_tiff if [ -n "$IS_MACOS" ]; then
# Custom tiff build to include jpeg; by default, configure won't include
# headers/libs in the custom macOS prefix. Explicitly disable webp,
# libdeflate and zstd, because on x86_64 macs, it will pick up the
# Homebrew versions of those libraries from /usr/local.
build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \
--with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \
--disable-webp --disable-libdeflate --disable-zstd
else
build_tiff
fi
build_libpng build_libpng
build_lcms2 build_lcms2
build_openjpeg build_openjpeg
@ -112,32 +149,47 @@ function build {
build_harfbuzz build_harfbuzz
} }
# Perform all dependency builds in the build subfolder.
mkdir -p $WORKDIR
pushd $WORKDIR > /dev/null
# Any stuff that you need to do before you start building the wheels # Any stuff that you need to do before you start building the wheels
# Runs in the root directory of this repository. # Runs in the root directory of this repository.
curl -fsSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip if [[ ! -d $WORKDIR/pillow-depends-main ]]; then
untar pillow-depends-main.zip if [[ ! -f $PROJECTDIR/pillow-depends-main.zip ]]; then
echo "Download pillow dependency sources..."
curl -fSL -o $PROJECTDIR/pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
fi
echo "Unpacking pillow dependency sources..."
untar $PROJECTDIR/pillow-depends-main.zip
fi
if [[ -n "$IS_MACOS" ]]; then if [[ -n "$IS_MACOS" ]]; then
# libdeflate may cause a minimum target error when repairing the wheel # Homebrew (or similar packaging environments) install can contain some of
# libtiff and libxcb cause a conflict with building libtiff and libxcb # the libraries that we're going to build. However, they may be compiled
# libxau and libxdmcp cause an issue on macOS < 11 # with a MACOSX_DEPLOYMENT_TARGET that doesn't match what we want to use,
# remove cairo to fix building harfbuzz on arm64 # and they may bring in other dependencies that we don't want. The same will
# remove lcms2 and libpng to fix building openjpeg on arm64 # be true of any other locations on the path. To avoid conflicts, strip the
# remove jpeg-turbo to avoid inclusion on arm64 # path down to the bare minimum (which, on macOS, won't include any
# remove webp and zstd to avoid inclusion on x86_64 # development dependencies).
# curl from brew requires zstd, use system curl export PATH="$BUILD_PREFIX/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
brew remove --ignore-dependencies libpng libtiff libxcb libxau libxdmcp curl cairo lcms2 zstd export CMAKE_PREFIX_PATH=$BUILD_PREFIX
if [[ "$CIBW_ARCHS" == "arm64" ]]; then
brew remove --ignore-dependencies jpeg-turbo
else
brew remove --ignore-dependencies libdeflate webp
fi
brew install pkg-config # Ensure the basic structure of the build prefix directory exists.
mkdir -p "$BUILD_PREFIX/bin"
mkdir -p "$BUILD_PREFIX/lib"
# Ensure pkg-config is available
build_pkg_config
# Ensure cmake is available
python3 -m pip install cmake
fi fi
wrap_wheel_builder build wrap_wheel_builder build
# Return to the project root to finish the build
popd > /dev/null
# Append licenses # Append licenses
for filename in wheels/dependency_licenses/*; do for filename in wheels/dependency_licenses/*; do
echo -e "\n\n----\n\n$(basename $filename | cut -f 1 -d '.')\n" | cat >> LICENSE echo -e "\n\n----\n\n$(basename $filename | cut -f 1 -d '.')\n" | cat >> LICENSE

View File

@ -1,12 +1,24 @@
#!/bin/bash #!/bin/bash
set -e set -e
# Ensure fribidi is installed by the system.
if [[ "$OSTYPE" == "darwin"* ]]; then if [[ "$OSTYPE" == "darwin"* ]]; then
brew install fribidi # If Homebrew is on the path during the build, it may leak into the wheels.
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig" # However, we *do* need Homebrew to provide a copy of fribidi for
if [ -f /opt/homebrew/lib/libfribidi.dylib ]; then # testing purposes so that we can verify the fribidi shim works as expected.
sudo cp /opt/homebrew/lib/libfribidi.dylib /usr/local/lib if [[ "$(uname -m)" == "x86_64" ]]; then
HOMEBREW_PREFIX=/usr/local
else
HOMEBREW_PREFIX=/opt/homebrew
fi fi
$HOMEBREW_PREFIX/bin/brew install fribidi
# Add the lib folder for fribidi so that the vendored library can be found.
# Don't use $HOMEWBREW_PREFIX/lib directly - use the lib folder where the
# installed copy of fribidi is cellared. This ensures we don't pick up the
# Homebrew version of any other library that we're dependent on (most notably,
# freetype).
export DYLD_LIBRARY_PATH=$(dirname $(realpath $HOMEBREW_PREFIX/lib/libfribidi.dylib))
elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then
apk add curl fribidi apk add curl fribidi
else else

5
.gitignore vendored
View File

@ -19,6 +19,7 @@ lib64/
parts/ parts/
sdist/ sdist/
var/ var/
wheelhouse/
*.egg-info/ *.egg-info/
.installed.cfg .installed.cfg
*.egg *.egg
@ -90,5 +91,9 @@ Tests/images/msp
Tests/images/picins Tests/images/picins
Tests/images/sunraster Tests/images/sunraster
# Test and dependency downloads
pillow-depends-main.zip
pillow-test-images.zip
# pyinstaller # pyinstaller
*.spec *.spec

View File

@ -2,20 +2,12 @@
Changelog (Pillow) Changelog (Pillow)
================== ==================
11.1.0 (unreleased) 11.1.0 and newer
------------------- ----------------
- Detach PyQt6 QPixmap instance before returning #8509 See GitHub Releases:
[radarhere]
- Corrected EMF DPI #8485 - https://github.com/python-pillow/Pillow/releases
[radarhere]
- Fix IFDRational with a zero denominator #8474
[radarhere]
- Fixed disabling a feature during install #8469
[radarhere]
11.0.0 (2024-10-15) 11.0.0 (2024-10-15)
------------------- -------------------

View File

@ -107,7 +107,7 @@ The core image library is designed for fast access to data stored in a few basic
- [Issues](https://github.com/python-pillow/Pillow/issues) - [Issues](https://github.com/python-pillow/Pillow/issues)
- [Pull requests](https://github.com/python-pillow/Pillow/pulls) - [Pull requests](https://github.com/python-pillow/Pillow/pulls)
- [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html) - [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Changelog](https://github.com/python-pillow/Pillow/releases)
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork) - [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
## Report a Vulnerability ## Report a Vulnerability

View File

@ -12,7 +12,6 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch. * [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
* [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them. * [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them.
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py` * [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
* [ ] Update `CHANGES.rst`.
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo. * [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
* [ ] Create branch and tag for release e.g.: * [ ] Create branch and tag for release e.g.:
```bash ```bash
@ -34,7 +33,6 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
Released as needed for security, installation or critical bug fixes. Released as needed for security, installation or critical bug fixes.
* [ ] Make necessary changes in `main` branch. * [ ] Make necessary changes in `main` branch.
* [ ] Update `CHANGES.rst`.
* [ ] Check out release branch e.g.: * [ ] Check out release branch e.g.:
```bash ```bash
git checkout -t remotes/origin/5.2.x git checkout -t remotes/origin/5.2.x

View File

@ -1129,6 +1129,25 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_similar(base_im, transposed_im, 0.7) assert_image_similar(base_im, transposed_im, 0.7)
@pytest.mark.parametrize(
"test_file",
[
"Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif",
"Tests/images/old-style-jpeg-compression.tif",
],
)
def test_buffering(self, test_file: str) -> None:
# load exif first
with Image.open(open(test_file, "rb", buffering=1048576)) as im:
exif = dict(im.getexif())
# load image before exif
with Image.open(open(test_file, "rb", buffering=1048576)) as im2:
im2.load()
exif_after_load = dict(im2.getexif())
assert exif == exif_after_load
@pytest.mark.valgrind_known_error(reason="Backtrace in Python Core") @pytest.mark.valgrind_known_error(reason="Backtrace in Python Core")
def test_sampleformat_not_corrupted(self) -> None: def test_sampleformat_not_corrupted(self) -> None:
# Assert that a TIFF image with SampleFormat=UINT tag is not corrupted # Assert that a TIFF image with SampleFormat=UINT tag is not corrupted

View File

@ -1,7 +1,7 @@
# Documentation: https://docs.codecov.com/docs/codecov-yaml # Documentation: https://docs.codecov.com/docs/codecov-yaml
codecov: codecov:
# Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]" # Avoid "Missing base report" due to committing with "[CI skip]"
# https://github.com/codecov/support/issues/363 # https://github.com/codecov/support/issues/363
# https://docs.codecov.com/docs/comparing-commits # https://docs.codecov.com/docs/comparing-commits
allow_coverage_offsets: true allow_coverage_offsets: true

View File

@ -195,11 +195,6 @@ Many of Pillow's features require external libraries:
mingw-w64-x86_64-libimagequant \ mingw-w64-x86_64-libimagequant \
mingw-w64-x86_64-libraqm mingw-w64-x86_64-libraqm
https://www.msys2.org/docs/python/ states that setuptools >= 60 does not work with
MSYS2. To workaround this, before installing Pillow you must run::
export SETUPTOOLS_USE_DISTUTILS=stdlib
.. tab:: FreeBSD .. tab:: FreeBSD
.. Note:: Only FreeBSD 10 and 11 tested .. Note:: Only FreeBSD 10 and 11 tested

View File

@ -73,7 +73,7 @@ optional-dependencies.typing = [
optional-dependencies.xmp = [ optional-dependencies.xmp = [
"defusedxml", "defusedxml",
] ]
urls.Changelog = "https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst" urls.Changelog = "https://github.com/python-pillow/Pillow/releases"
urls.Documentation = "https://pillow.readthedocs.io" urls.Documentation = "https://pillow.readthedocs.io"
urls.Funding = "https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi" urls.Funding = "https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi"
urls.Homepage = "https://python-pillow.org" urls.Homepage = "https://python-pillow.org"
@ -94,10 +94,17 @@ version = { attr = "PIL.__version__" }
[tool.cibuildwheel] [tool.cibuildwheel]
before-all = ".github/workflows/wheels-dependencies.sh" before-all = ".github/workflows/wheels-dependencies.sh"
build-verbosity = 1 build-verbosity = 1
config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable" config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable"
# Disable platform guessing on macOS
macos.config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable platform-guessing=disable"
test-command = "cd {project} && .github/workflows/wheels-test.sh" test-command = "cd {project} && .github/workflows/wheels-test.sh"
test-extras = "tests" test-extras = "tests"
[tool.cibuildwheel.macos.environment]
PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
[tool.black] [tool.black]
exclude = "wheels/multibuild" exclude = "wheels/multibuild"

View File

@ -448,7 +448,7 @@ class pil_build_ext(build_ext):
def get_macos_sdk_path(self) -> str | None: def get_macos_sdk_path(self) -> str | None:
try: try:
sdk_path = ( sdk_path = (
subprocess.check_output(["xcrun", "--show-sdk-path"]) subprocess.check_output(["xcrun", "--show-sdk-path", "--sdk", "macosx"])
.strip() .strip()
.decode("latin1") .decode("latin1")
) )
@ -606,6 +606,7 @@ class pil_build_ext(build_ext):
_add_directory(library_dirs, "/usr/X11/lib") _add_directory(library_dirs, "/usr/X11/lib")
_add_directory(include_dirs, "/usr/X11/include") _add_directory(include_dirs, "/usr/X11/include")
# Add the macOS SDK path.
sdk_path = self.get_macos_sdk_path() sdk_path = self.get_macos_sdk_path()
if sdk_path: if sdk_path:
_add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib"))
@ -690,6 +691,8 @@ class pil_build_ext(build_ext):
feature.set("zlib", "z") feature.set("zlib", "z")
elif sys.platform == "win32" and _find_library_file(self, "zlib"): elif sys.platform == "win32" and _find_library_file(self, "zlib"):
feature.set("zlib", "zlib") # alternative name feature.set("zlib", "zlib") # alternative name
elif sys.platform == "win32" and _find_library_file(self, "zdll"):
feature.set("zlib", "zdll") # dll import library
if feature.want("jpeg"): if feature.want("jpeg"):
_dbg("Looking for jpeg") _dbg("Looking for jpeg")

View File

@ -2493,7 +2493,7 @@ class Image:
filename: str | bytes = "" filename: str | bytes = ""
open_fp = False open_fp = False
if is_path(fp): if is_path(fp):
filename = os.path.realpath(os.fspath(fp)) filename = os.fspath(fp)
open_fp = True open_fp = True
elif fp == sys.stdout: elif fp == sys.stdout:
try: try:
@ -2502,7 +2502,7 @@ class Image:
pass pass
if not filename and hasattr(fp, "name") and is_path(fp.name): if not filename and hasattr(fp, "name") and is_path(fp.name):
# only set the name for metadata purposes # only set the name for metadata purposes
filename = os.path.realpath(os.fspath(fp.name)) filename = os.fspath(fp.name)
# may mutate self! # may mutate self!
self._ensure_mutable() self._ensure_mutable()
@ -3406,7 +3406,7 @@ def open(
exclusive_fp = False exclusive_fp = False
filename: str | bytes = "" filename: str | bytes = ""
if is_path(fp): if is_path(fp):
filename = os.path.realpath(os.fspath(fp)) filename = os.fspath(fp)
if filename: if filename:
fp = builtins.open(filename, "rb") fp = builtins.open(filename, "rb")

View File

@ -135,7 +135,7 @@ class ImageFile(Image.Image):
if is_path(fp): if is_path(fp):
# filename # filename
self.fp = open(fp, "rb") self.fp = open(fp, "rb")
self.filename = os.path.realpath(os.fspath(fp)) self.filename = os.fspath(fp)
self._exclusive_fp = True self._exclusive_fp = True
else: else:
# stream # stream

View File

@ -270,7 +270,7 @@ class FreeTypeFont:
) )
if is_path(font): if is_path(font):
font = os.path.realpath(os.fspath(font)) font = os.fspath(font)
if sys.platform == "win32": if sys.platform == "win32":
font_bytes_path = font if isinstance(font, bytes) else font.encode() font_bytes_path = font if isinstance(font, bytes) else font.encode()
try: try:

View File

@ -1219,10 +1219,6 @@ class TiffImageFile(ImageFile.ImageFile):
raise self._fp.ex raise self._fp.ex
self.fp = self._fp self.fp = self._fp
# reset buffered io handle in case fp
# was passed to libtiff, invalidating the buffer
self.fp.tell()
while len(self._frame_pos) <= frame: while len(self._frame_pos) <= frame:
if not self.__next: if not self.__next:
msg = "no more images in TIFF file" msg = "no more images in TIFF file"
@ -1306,11 +1302,6 @@ class TiffImageFile(ImageFile.ImageFile):
if not self.is_animated: if not self.is_animated:
self._close_exclusive_fp_after_loading = True self._close_exclusive_fp_after_loading = True
# reset buffered io handle in case fp
# was passed to libtiff, invalidating the buffer
assert self.fp is not None
self.fp.tell()
# load IFD data from fp before it is closed # load IFD data from fp before it is closed
exif = self.getexif() exif = self.getexif()
for key in TiffTags.TAGS_V2_GROUPS: for key in TiffTags.TAGS_V2_GROUPS:
@ -1386,8 +1377,17 @@ class TiffImageFile(ImageFile.ImageFile):
logger.debug("have fileno, calling fileno version of the decoder.") logger.debug("have fileno, calling fileno version of the decoder.")
if not close_self_fp: if not close_self_fp:
self.fp.seek(0) self.fp.seek(0)
# Save and restore the file position, because libtiff will move it
# outside of the Python runtime, and that will confuse
# io.BufferedReader and possible others.
# NOTE: This must use os.lseek(), and not fp.tell()/fp.seek(),
# because the buffer read head already may not equal the actual
# file position, and fp.seek() may just adjust it's internal
# pointer and not actually seek the OS file handle.
pos = os.lseek(fp, 0, os.SEEK_CUR)
# 4 bytes, otherwise the trace might error out # 4 bytes, otherwise the trace might error out
n, err = decoder.decode(b"fpfp") n, err = decoder.decode(b"fpfp")
os.lseek(fp, pos, os.SEEK_SET)
else: else:
# we have something else. # we have something else.
logger.debug("don't have fileno or getvalue. just reading") logger.debug("don't have fileno or getvalue. just reading")

View File

@ -47,7 +47,7 @@ class SupportsRead(Protocol[_T_co]):
def read(self, __length: int = ...) -> _T_co: ... def read(self, __length: int = ...) -> _T_co: ...
StrOrBytesPath = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"] StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]]
__all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"] __all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"]