Compare commits
No commits in common. "main" and "11.0.x" have entirely different histories.
99
.appveyor.yml
Normal file
@ -0,0 +1,99 @@
|
||||
skip_commits:
|
||||
files:
|
||||
- ".github/**/*"
|
||||
- ".gitmodules"
|
||||
- "docs/**/*"
|
||||
- "wheels/**/*"
|
||||
|
||||
version: '{build}'
|
||||
clone_folder: c:\pillow
|
||||
init:
|
||||
- ECHO %PYTHON%
|
||||
#- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
# Uncomment previous line to get RDP access during the build.
|
||||
|
||||
environment:
|
||||
COVERAGE_CORE: sysmon
|
||||
EXECUTABLE: python.exe
|
||||
TEST_OPTIONS:
|
||||
DEPLOY: YES
|
||||
matrix:
|
||||
- PYTHON: C:/Python313
|
||||
ARCHITECTURE: x86
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||
- PYTHON: C:/Python39-x64
|
||||
ARCHITECTURE: AMD64
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
|
||||
|
||||
install:
|
||||
- '%PYTHON%\%EXECUTABLE% --version'
|
||||
- '%PYTHON%\%EXECUTABLE% -m pip install --upgrade pip'
|
||||
- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
|
||||
- 7z x pillow-test-images.zip -oc:\
|
||||
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
|
||||
- curl -fsSL -o nasm-win64.zip https://raw.githubusercontent.com/python-pillow/pillow-depends/main/nasm-2.16.03-win64.zip
|
||||
- 7z x nasm-win64.zip -oc:\
|
||||
- choco install ghostscript --version=10.4.0
|
||||
- path c:\nasm-2.16.03;C:\Program Files\gs\gs10.04.0\bin;%PATH%
|
||||
- cd c:\pillow\winbuild\
|
||||
- ps: |
|
||||
c:\python39\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
|
||||
c:\pillow\winbuild\build\build_dep_all.cmd
|
||||
$host.SetShouldExit(0)
|
||||
- path C:\pillow\winbuild\build\bin;%PATH%
|
||||
|
||||
build_script:
|
||||
- cd c:\pillow
|
||||
- winbuild\build\build_env.cmd
|
||||
- '%PYTHON%\%EXECUTABLE% -m pip install -v -C raqm=vendor -C fribidi=vendor .'
|
||||
- '%PYTHON%\%EXECUTABLE% selftest.py --installed'
|
||||
|
||||
test_script:
|
||||
- cd c:\pillow
|
||||
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout defusedxml ipython numpy olefile pyroma'
|
||||
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
|
||||
- path %PYTHON%;%PATH%
|
||||
- .ci\test.cmd
|
||||
|
||||
after_test:
|
||||
- curl -Os https://uploader.codecov.io/latest/windows/codecov.exe
|
||||
- .\codecov.exe --file coverage.xml --name %PYTHON% --flags AppVeyor
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
cache:
|
||||
- '%LOCALAPPDATA%\pip\Cache'
|
||||
|
||||
artifacts:
|
||||
- path: pillow\*.egg
|
||||
name: egg
|
||||
- path: pillow\*.whl
|
||||
name: wheel
|
||||
|
||||
before_deploy:
|
||||
- cd c:\pillow
|
||||
- '%PYTHON%\%EXECUTABLE% -m pip wheel -v -C raqm=vendor -C fribidi=vendor .'
|
||||
- ps: Get-ChildItem .\*.whl | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
|
||||
|
||||
deploy:
|
||||
provider: S3
|
||||
region: us-west-2
|
||||
access_key_id: AKIAIRAXC62ZNTVQJMOQ
|
||||
secret_access_key:
|
||||
secure: Hwb6klTqtBeMgxAjRoDltiiqpuH8xbwD4UooDzBSiCWXjuFj1lyl4kHgHwTCCGqi
|
||||
bucket: pillow-nightly
|
||||
folder: win/$(APPVEYOR_BUILD_NUMBER)/
|
||||
artifact: /.*egg|wheel/
|
||||
on:
|
||||
APPVEYOR_REPO_NAME: python-pillow/Pillow
|
||||
branch: main
|
||||
deploy: YES
|
||||
|
||||
|
||||
# Uncomment the following lines to get RDP access after the build/test and block for
|
||||
# up to the timeout limit (~1hr)
|
||||
#
|
||||
#on_finish:
|
||||
#- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
@ -2,4 +2,8 @@
|
||||
|
||||
# gather the coverage data
|
||||
python3 -m pip install coverage
|
||||
python3 -m coverage xml
|
||||
if [[ $MATRIX_DOCKER ]]; then
|
||||
python3 -m coverage xml --ignore-errors
|
||||
else
|
||||
python3 -m coverage xml
|
||||
fi
|
||||
|
||||
@ -3,5 +3,8 @@
|
||||
set -e
|
||||
|
||||
python3 -m coverage erase
|
||||
if [ $(uname) == "Darwin" ]; then
|
||||
export CPPFLAGS="-I/usr/local/miniconda/include";
|
||||
fi
|
||||
make clean
|
||||
make install-coverage
|
||||
|
||||
@ -2,25 +2,29 @@
|
||||
|
||||
aptget_update()
|
||||
{
|
||||
if [ -n "$1" ]; then
|
||||
if [ ! -z $1 ]; then
|
||||
echo ""
|
||||
echo "Retrying apt-get update..."
|
||||
echo ""
|
||||
fi
|
||||
output=$(sudo apt-get update 2>&1)
|
||||
output=`sudo apt-get update 2>&1`
|
||||
echo "$output"
|
||||
if [[ $output == *[WE]:\ * ]]; then
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
aptget_update || aptget_update retry || aptget_update retry
|
||||
if [[ $(uname) != CYGWIN* ]]; then
|
||||
aptget_update || aptget_update retry || aptget_update retry
|
||||
fi
|
||||
|
||||
set -e
|
||||
|
||||
sudo apt-get -qq install libfreetype6-dev liblcms2-dev libtiff-dev python3-tk\
|
||||
ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\
|
||||
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
||||
sway wl-clipboard libopenblas-dev nasm
|
||||
if [[ $(uname) != CYGWIN* ]]; then
|
||||
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
|
||||
ghostscript libjpeg-turbo-progs libopenjp2-7-dev\
|
||||
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
||||
sway wl-clipboard libopenblas-dev
|
||||
fi
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade wheel
|
||||
@ -32,28 +36,34 @@ python3 -m pip install -U pytest
|
||||
python3 -m pip install -U pytest-cov
|
||||
python3 -m pip install -U pytest-timeout
|
||||
python3 -m pip install pyroma
|
||||
# optional test dependencies, only install if there's a binary package.
|
||||
python3 -m pip install --only-binary=:all: numpy || true
|
||||
python3 -m pip install --only-binary=:all: pyarrow || true
|
||||
|
||||
# PyQt6 doesn't support PyPy3
|
||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
|
||||
# pyqt6 doesn't yet support free-threading; only install if a wheel is available
|
||||
python3 -m pip install --only-binary=:all: pyqt6 || true
|
||||
if [[ $(uname) != CYGWIN* ]]; then
|
||||
python3 -m pip install numpy
|
||||
|
||||
# PyQt6 doesn't support PyPy3
|
||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
|
||||
# TODO Update condition when pyqt6 supports free-threading
|
||||
if ! [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install pyqt6 ; fi
|
||||
fi
|
||||
|
||||
# Pyroma uses non-isolated build and fails with old setuptools
|
||||
if [[ $GHA_PYTHON_VERSION == 3.9 ]]; then
|
||||
# To match pyproject.toml
|
||||
python3 -m pip install "setuptools>=67.8"
|
||||
fi
|
||||
|
||||
# webp
|
||||
pushd depends && ./install_webp.sh && popd
|
||||
|
||||
# libimagequant
|
||||
pushd depends && ./install_imagequant.sh && popd
|
||||
|
||||
# raqm
|
||||
pushd depends && ./install_raqm.sh && popd
|
||||
|
||||
# extra test images
|
||||
pushd depends && ./install_extra_test_images.sh && popd
|
||||
else
|
||||
cd depends && ./install_extra_test_images.sh && cd ..
|
||||
fi
|
||||
|
||||
# webp
|
||||
pushd depends && ./install_webp.sh && popd
|
||||
|
||||
# libimagequant
|
||||
pushd depends && ./install_imagequant.sh && popd
|
||||
|
||||
# raqm
|
||||
pushd depends && sudo ./install_raqm.sh && popd
|
||||
|
||||
# libavif
|
||||
pushd depends && ./install_libavif.sh && popd
|
||||
|
||||
# extra test images
|
||||
pushd depends && ./install_extra_test_images.sh && popd
|
||||
|
||||
@ -1 +1 @@
|
||||
cibuildwheel==3.4.1
|
||||
cibuildwheel==2.21.3
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
mypy==1.20.2
|
||||
arro3-compute
|
||||
arro3-core
|
||||
mypy==1.11.2
|
||||
IceSpringPySideStubs-PyQt6
|
||||
IceSpringPySideStubs-PySide6
|
||||
ipython
|
||||
numpy
|
||||
packaging
|
||||
pyarrow-stubs
|
||||
pybind11
|
||||
pytest
|
||||
sphinx
|
||||
types-atheris
|
||||
types-defusedxml
|
||||
types-olefile
|
||||
|
||||
@ -1 +0,0 @@
|
||||
check-jsonschema==0.37.1
|
||||
@ -1,3 +1,3 @@
|
||||
python.exe -c "from PIL import Image"
|
||||
IF ERRORLEVEL 1 EXIT /B
|
||||
python.exe -bb -m pytest -vv -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests
|
||||
python.exe -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests
|
||||
|
||||
@ -4,4 +4,4 @@ set -e
|
||||
|
||||
python3 -c "from PIL import Image"
|
||||
|
||||
python3 -bb -m pytest -vv -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests $REVERSE
|
||||
python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests $REVERSE
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
# A clang-format style that approximates Python's PEP 7
|
||||
# Useful for IDE integration
|
||||
Language: C
|
||||
BasedOnStyle: Google
|
||||
AlwaysBreakAfterReturnType: All
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
@ -12,26 +11,7 @@ ColumnLimit: 88
|
||||
DerivePointerAlignment: false
|
||||
IndentGotoLabels: false
|
||||
IndentWidth: 4
|
||||
PointerAlignment: Right
|
||||
ReflowComments: true
|
||||
SortIncludes: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpacesInParentheses: false
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
AlwaysBreakAfterReturnType: All
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BreakBeforeBraces: Attach
|
||||
ColumnLimit: 88
|
||||
DerivePointerAlignment: false
|
||||
IndentGotoLabels: false
|
||||
IndentWidth: 4
|
||||
PointerAlignment: Right
|
||||
ReflowComments: true
|
||||
SortIncludes: false
|
||||
|
||||
@ -18,5 +18,6 @@ exclude_also =
|
||||
|
||||
[run]
|
||||
omit =
|
||||
checks/*.py
|
||||
Tests/32bit_segfault_check.py
|
||||
Tests/check_*.py
|
||||
Tests/createfontdatachunk.py
|
||||
|
||||
5
.github/CONTRIBUTING.md
vendored
@ -9,7 +9,7 @@ Please send a pull request to the `main` branch. Please include [documentation](
|
||||
- Fork the Pillow repository.
|
||||
- Create a branch from `main`.
|
||||
- Develop bug fixes, features, tests, etc.
|
||||
- Run the test suite. You can enable GitHub Actions (https://github.com/MY-USERNAME/Pillow/actions) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests.
|
||||
- Run the test suite. You can enable GitHub Actions (https://github.com/MY-USERNAME/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests.
|
||||
- Create a pull request to pull the changes from your branch to the Pillow `main`.
|
||||
|
||||
### Guidelines
|
||||
@ -17,8 +17,9 @@ Please send a pull request to the `main` branch. Please include [documentation](
|
||||
- Separate code commits from reformatting commits.
|
||||
- Provide tests for any newly added code.
|
||||
- Follow PEP 8.
|
||||
- When committing only documentation changes please include `[ci skip]` in the commit message to avoid running extra tests.
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
|
||||
3
.github/FUNDING.yml
vendored
@ -1,2 +1 @@
|
||||
github: python-pillow
|
||||
tidelift: pypi/pillow
|
||||
tidelift: "pypi/pillow"
|
||||
|
||||
424
.github/INCIDENT_RESPONSE.md
vendored
@ -1,424 +0,0 @@
|
||||
# Incident Response Plan — Pillow
|
||||
|
||||
This document describes how the Pillow maintainers detect, triage, fix, communicate, and
|
||||
learn from security incidents. It supplements the existing [Security Policy](SECURITY.md)
|
||||
and [Release Checklist](../RELEASING.md).
|
||||
|
||||
---
|
||||
|
||||
## 1. Preparation
|
||||
|
||||
Maintaining readiness before an incident occurs reduces response time and errors under pressure.
|
||||
|
||||
### 1.1 Version Support Matrix
|
||||
|
||||
Security fixes are applied to the **latest stable release only**. Users on older versions
|
||||
are expected to upgrade. Reporters should assume only the latest release will receive a patch.
|
||||
|
||||
| Branch | Status |
|
||||
|---|---|
|
||||
| `main` / latest stable | ✅ Security fixes applied |
|
||||
| All older releases | ❌ No security support — please upgrade |
|
||||
|
||||
### 1.2 Team Readiness
|
||||
|
||||
The four members of the Pillow core team are in regular contact and share collective
|
||||
responsibility for incident response. Any core team member may act as Incident Lead.
|
||||
Contact details are known to all team members.
|
||||
|
||||
### 1.3 Readiness Review
|
||||
|
||||
At each quarterly release, maintainers should re-read this document and update any stale content.
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope
|
||||
|
||||
This plan covers:
|
||||
|
||||
| Incident type | Examples |
|
||||
|---|---|
|
||||
| Vulnerability in Pillow's own Python or C code | Buffer overflow in an image decoder, integer overflow in `ImagingNew` |
|
||||
| Vulnerability in a bundled or wheel-shipped C library | libjpeg, libwebp, libtiff, libpng, openjpeg, libavif |
|
||||
| Supply-chain compromise | Malicious commit, stolen maintainer credentials, tampered PyPI wheel |
|
||||
| CI/CD or infrastructure compromise | GitHub Actions secret leak, Codecov breach, PyPI token exposure |
|
||||
| Critical non-security regression | Data-loss bug shipped in a release, crash on all supported platforms |
|
||||
|
||||
---
|
||||
|
||||
## 3. Definitions
|
||||
|
||||
| Term | Meaning |
|
||||
|---|---|
|
||||
| **Incident** | Any event that compromises or threatens the confidentiality, integrity, or availability of Pillow's code, release artifacts, or infrastructure. |
|
||||
| **Vulnerability** | A security flaw in Pillow or a bundled library that can be exploited by a crafted image or API call. |
|
||||
| **Incident Lead** | The maintainer who owns coordination of the response from triage to closure. |
|
||||
| **Embargo** | A period during which fix details are kept private to allow coordinated patching before public disclosure. |
|
||||
| **Yank** | A PyPI action that keeps a release downloadable by pinned users but removes it from default `pip install` resolution. |
|
||||
| **CVE** | Common Vulnerabilities and Exposures — a public identifier assigned to a specific vulnerability. |
|
||||
| **CNA** | CVE Numbering Authority — GitHub is a CNA and can assign CVEs directly through the advisory workflow. |
|
||||
|
||||
---
|
||||
|
||||
## 4. Roles
|
||||
|
||||
| Role | Responsibility |
|
||||
|---|---|
|
||||
| **Incident Lead** | First maintainer to triage the report. Owns the incident until resolution. |
|
||||
| **Patch Owner** | Writes and tests the fix (may be the same person as Incident Lead). |
|
||||
| **Release Manager** | Cuts the point release following [RELEASING.md](../RELEASING.md). |
|
||||
| **Communications Owner** | Drafts the GitHub Security Advisory, announces on Mastodon, notifies distros. |
|
||||
| **Tidelift Contact** | For reports that arrive via Tidelift, coordinate through the Tidelift security portal. |
|
||||
|
||||
One person may fill multiple roles.
|
||||
|
||||
---
|
||||
|
||||
## 5. Severity Classification
|
||||
|
||||
Use the [CVSS 4.0](https://www.first.org/cvss/v4.0/specification-document) base score as
|
||||
a guide, mapped to the following levels:
|
||||
|
||||
| Severity | CVSS | Definition | Target Response SLA |
|
||||
|---|---|---|---|
|
||||
| **Critical** | 9.0 – 10.0 | Remote code execution, arbitrary write, or complete integrity/confidentiality loss achievable by opening a crafted image | Best effort; embargoed release where possible |
|
||||
| **High** | 7.0 – 8.9 | Heap/stack buffer overflow, use-after-free, or significant information disclosure | Best effort |
|
||||
| **Medium** | 4.0 – 6.9 | Denial of service via crafted image, out-of-bounds read, limited info disclosure | Next scheduled quarterly release, or earlier point release if needed |
|
||||
| **Low** | 0.1 – 3.9 | Minor information disclosure, unlikely to be exploitable in practice | Next quarterly release |
|
||||
|
||||
Supply-chain and CI/CD incidents are always treated as **Critical** regardless of CVSS.
|
||||
|
||||
> **Note:** These are good-faith targets for a small volunteer maintainer team, not contractual SLAs. Public safety and transparency will always be prioritised, even when timing varies.
|
||||
|
||||
---
|
||||
|
||||
## 6. Detection Sources
|
||||
|
||||
Vulnerabilities and incidents may be reported or discovered through:
|
||||
|
||||
1. **GitHub private security advisory** — preferred channel; see [SECURITY.md](SECURITY.md)
|
||||
2. **Tidelift security contact** — <https://tidelift.com/docs/security>
|
||||
3. **External researcher / coordinated disclosure** — e.g. Google Project Zero, vendor PSIRT
|
||||
4. **Automated scanning** — Dependabot, GitHub code-scanning (CodeQL), CI fuzzing
|
||||
5. **Distro security teams** — Debian, Red Hat, Ubuntu, Alpine may report upstream
|
||||
6. **User bug report** — public issue (reassess if it has security implications and convert to a private advisory if needed)
|
||||
|
||||
---
|
||||
|
||||
## 7. Response Process
|
||||
|
||||
### 7.1 Triage (all severities)
|
||||
|
||||
1. **Acknowledge receipt** to the reporter within **72 hours** using the template in
|
||||
[Appendix A](#appendix-a-communication-templates). Ask the reporter:
|
||||
- How they would like to be credited (name, handle, or anonymous)
|
||||
- Whether they intend to publish their own advisory, and if so, their preferred timeline
|
||||
- Thank them explicitly — reporters do the project a favour by disclosing privately.
|
||||
2. Reproduce the issue. If the report is invalid, close it and notify the reporter.
|
||||
3. Assign a severity level ([Section 5: Severity Classification](#5-severity-classification)).
|
||||
4. If the GitHub Security Advisory was not created by the reporter, create one now and keep
|
||||
it **private** until the fix is released. Add the reporter as a collaborator if they wish
|
||||
to be involved.
|
||||
5. **Request a CVE** through the GitHub Security Advisory workflow (GitHub is a CVE
|
||||
Numbering Authority — no separate MITRE form required). The CVE is reserved privately
|
||||
and published automatically when the advisory goes public.
|
||||
6. **Escalation** — Escalate beyond the core maintainer team if any of the following apply:
|
||||
- The fix requires changes to CPython or a dependency outside Pillow's control → contact the relevant upstream immediately
|
||||
- A legal concern arises (e.g. GDPR-reportable data exposure) → contact the project's legal/fiscal sponsor
|
||||
- The Incident Lead is unreachable for > 24 hours on a Critical issue → any other maintainer may assume the role
|
||||
|
||||
### 7.2 Fix Development
|
||||
|
||||
1. Develop the fix in a **private fork** or directly in the private security advisory
|
||||
workspace on GitHub. Do **not** push to a public branch before the embargo lifts.
|
||||
2. Write a regression test that fails before the fix and passes after.
|
||||
3. Review the patch with at least one other maintainer.
|
||||
|
||||
### 7.3 Standard (Non-Embargoed) Release
|
||||
|
||||
For Medium and Low severity, or when no distro pre-notification is needed:
|
||||
|
||||
1. Merge the fix to `main`, then cherry-pick to all affected release branches
|
||||
(see [RELEASING.md — Point release](../RELEASING.md)).
|
||||
2. Amend commit messages to include the CVE identifier.
|
||||
3. Follow the [Point release](../RELEASING.md#point-release) process in RELEASING.md to
|
||||
tag, push, and confirm wheels are live on PyPI.
|
||||
4. Publish the GitHub Security Advisory (this simultaneously publishes the CVE).
|
||||
|
||||
### 7.4 Embargoed Release
|
||||
|
||||
For Critical and High severity where distro pre-notification improves user safety:
|
||||
|
||||
1. Prepare patches against all affected release branches and test locally.
|
||||
2. Agree on an **embargo date** with the reporter (typically 7–14 days out, up to 90 days for
|
||||
complex issues).
|
||||
3. Privately send the patch to distros via the
|
||||
[linux-distros](https://oss-security.openwall.org/wiki/mailing-lists/distros) mailing list
|
||||
or directly to individual distro security teams.
|
||||
4. On the embargo date:
|
||||
- Amend commit messages with the CVE identifier.
|
||||
- Follow the [Embargoed release](../RELEASING.md#embargoed-release) process in
|
||||
RELEASING.md to tag, push, and confirm wheels are live on PyPI.
|
||||
- Publish the GitHub Security Advisory.
|
||||
|
||||
### 7.5 Supply-Chain / Infrastructure Compromise
|
||||
|
||||
1. **Immediately** revoke any potentially compromised credentials:
|
||||
- PyPI API tokens
|
||||
- GitHub personal access tokens and OAuth apps
|
||||
- Codecov or other CI service tokens
|
||||
2. Audit recent commits and releases for tampering:
|
||||
- Verify release tags against known-good SHAs
|
||||
- Re-inspect any wheel published since the potential compromise window
|
||||
3. If a PyPI release is suspected to be tampered: yank it immediately via the
|
||||
[PyPI release management page](https://pypi.org/manage/project/Pillow/releases/)
|
||||
(login required); see [https://pypi.org/security/](https://pypi.org/security/) for
|
||||
reporting to the PyPI security team.
|
||||
4. Issue a public advisory describing the scope and any user action required.
|
||||
|
||||
### 7.6 Recovery
|
||||
|
||||
After the fix is released and the advisory is public:
|
||||
|
||||
1. Verify that the patched wheels are live on PyPI and passing CI across all supported platforms.
|
||||
2. Confirm any yanked releases are handled correctly .
|
||||
3. Resume normal development operations on `main`.
|
||||
4. Monitor the GitHub issue tracker and Mastodon for user reports of residual problems for at least **72 hours** post-release.
|
||||
5. Close the private GitHub Security Advisory once recovery is confirmed.
|
||||
|
||||
---
|
||||
|
||||
## 8. Communication
|
||||
|
||||
### Internal (during embargo)
|
||||
- Use the **private GitHub Security Advisory** thread for coordination with the reporter.
|
||||
- Use private communication channels for all other coordination.
|
||||
- Do not discuss details in public issues, PRs, or Gitter/IRC channels.
|
||||
|
||||
### External (at or after disclosure)
|
||||
|
||||
| Audience | Channel | Timing |
|
||||
|---|---|---|
|
||||
| General users | [GitHub Security Advisory](https://github.com/python-pillow/Pillow/security/advisories) | At release |
|
||||
| PyPI ecosystem | CVE published via advisory | At release |
|
||||
| Downstream distros | Direct email or linux-distros list | Before embargo date (embargoed) |
|
||||
| Tidelift subscribers | Tidelift security portal | At release (or coordinated) |
|
||||
| Community | [Mastodon @pillow](https://fosstodon.org/@pillow) | At release |
|
||||
|
||||
**Advisory content should include:**
|
||||
- CVE identifier and CVSS score
|
||||
- Affected Pillow versions
|
||||
- Fixed version(s)
|
||||
- Nature of the vulnerability (without full exploit details if still fresh)
|
||||
- Credit to the reporter (with their consent)
|
||||
- Upgrade instructions (`python3 -m pip install --upgrade Pillow`)
|
||||
|
||||
---
|
||||
|
||||
## 9. Dependency Map
|
||||
|
||||
Understanding what Pillow depends on (upstream) and what depends on Pillow (downstream)
|
||||
is essential for scoping impact and coordinating notifications during an incident.
|
||||
|
||||
### 9.1 Upstream Dependencies
|
||||
|
||||
#### Bundled C libraries (shipped in official wheels)
|
||||
|
||||
These libraries are compiled into Pillow's binary wheels. A CVE in any of them may
|
||||
require a Pillow point release even if Pillow's own code is unchanged.
|
||||
|
||||
| Library | Purpose | Security advisory tracker |
|
||||
|---|---|---|
|
||||
| [libjpeg-turbo](https://libjpeg-turbo.org/) | JPEG encode/decode | [GitHub](https://github.com/libjpeg-turbo/libjpeg-turbo/security) |
|
||||
| [libpng](http://www.libpng.org/pub/png/libpng.html) | PNG encode/decode within FreeType 2, OpenJPEG and WebP | [SourceForge](https://sourceforge.net/p/libpng/bugs/) |
|
||||
| [libtiff](https://libtiff.gitlab.io/libtiff/) | TIFF encode/decode | [GitLab](https://gitlab.com/libtiff/libtiff/-/work_items) |
|
||||
| [libwebp](https://chromium.googlesource.com/webm/libwebp) | WebP encode/decode | [Chromium tracker](https://issues.webmproject.org/issues) |
|
||||
| [libavif](https://github.com/AOMediaCodec/libavif) | AVIF encode/decode | [GitHub](https://github.com/AOMediaCodec/libavif/security) |
|
||||
| [aom](https://aomedia.googlesource.com/aom/) | AV1 codec (AVIF) | [Chromium tracker](https://aomedia.issues.chromium.org/issues) |
|
||||
| [dav1d](https://code.videolan.org/videolan/dav1d) | AV1 decode (AVIF) | [VideoLAN Security](https://www.videolan.org/security/) |
|
||||
| [openjpeg](https://www.openjpeg.org/) | JPEG 2000 encode/decode | [GitHub](https://github.com/uclouvain/openjpeg/security) |
|
||||
| [freetype2](https://freetype.org/) | Font rendering | [GitLab](https://gitlab.freedesktop.org/freetype/freetype/-/work_items) |
|
||||
| [lcms2](https://www.littlecms.com/) | ICC color management | [GitHub](https://github.com/mm2/Little-CMS/security) |
|
||||
| [harfbuzz](https://harfbuzz.github.io/) | Text shaping (via raqm) | [GitHub](https://github.com/harfbuzz/harfbuzz/security) |
|
||||
| [raqm](https://github.com/HOST-Oman/libraqm) | Complex text layout | [GitHub](https://github.com/HOST-Oman/libraqm) |
|
||||
| [fribidi](https://github.com/fribidi/fribidi) | Unicode bidi (via raqm) | [GitHub](https://github.com/fribidi/fribidi) |
|
||||
| [zlib](https://zlib.net/) | Deflate compression | [zlib.net](https://zlib.net/) |
|
||||
| [liblzma / xz-utils](https://tukaani.org/xz/) | XZ/LZMA compression | [GitHub](https://github.com/tukaani-project/xz/security) |
|
||||
| [bzip2](https://gitlab.com/bzip2/bzip2) | BZ2 compression | [GitLab](https://gitlab.com/bzip2/bzip2/-/work_items) |
|
||||
| [zstd](https://github.com/facebook/zstd) | Zstandard compression | [GitHub](https://github.com/facebook/zstd/security) |
|
||||
| [brotli](https://github.com/google/brotli) | Brotli compression | [GitHub](https://github.com/google/brotli/security) |
|
||||
| [libyuv](https://chromium.googlesource.com/libyuv/libyuv/) | YUV conversion | [Chromium tracker](https://libyuv.issues.chromium.org/issues) |
|
||||
|
||||
#### Python-level dependencies
|
||||
|
||||
| Package | Required? | Purpose |
|
||||
|---|---|---|
|
||||
| `setuptools` | Build-time only | Package build backend |
|
||||
| `pybind11` | Build-time only | Compile C files in parallel |
|
||||
| `olefile` | Optional (`fpx`, `mic` extras) | OLE2 container parsing (FPX, MIC formats) |
|
||||
| `defusedxml` | Optional (`xmp` extra) | Safe XML parsing for XMP metadata |
|
||||
|
||||
See [`pyproject.toml`](../pyproject.toml) for the complete and authoritative list of
|
||||
optional dependencies.
|
||||
|
||||
### 9.2 Responding to an Upstream Vulnerability
|
||||
|
||||
When a CVE is published for a bundled C library:
|
||||
|
||||
1. Assess whether the vulnerable code path is reachable through Pillow's API.
|
||||
2. If reachable, treat as a Pillow vulnerability and follow [Section 5: Severity Classification](#5-severity-classification).
|
||||
3. Update the bundled library version in the wheel build scripts and rebuild wheels.
|
||||
4. Reference the upstream CVE in Pillow's release notes and GitHub Security Advisory.
|
||||
5. If not reachable, document the rationale in a public issue so downstream distributors
|
||||
can make informed decisions about patching their system packages.
|
||||
|
||||
### 9.3 Downstream Dependencies
|
||||
|
||||
A vulnerability in Pillow can have wide impact. Notify or consider the blast radius of
|
||||
these downstream consumers when assessing severity and planning communications.
|
||||
|
||||
#### Linux distribution packages
|
||||
|
||||
| Distribution | Package name | Security contact |
|
||||
|---|---|---|
|
||||
| Debian / Ubuntu | `python3-pil` | [Debian Security](https://www.debian.org/security/) / [Ubuntu Security](https://ubuntu.com/security) |
|
||||
| Fedora / RHEL / CentOS | `python3-pillow` | [Red Hat Security](https://access.redhat.com/security/) |
|
||||
| Alpine Linux | `py3-pillow` | [Alpine security](https://security.alpinelinux.org/) |
|
||||
| Arch Linux | `python-pillow` | [Arch security tracker](https://security.archlinux.org/) |
|
||||
| Homebrew | `pillow` | [Homebrew maintainers](https://github.com/Homebrew/homebrew-core/security) |
|
||||
| conda-forge | `pillow` | [conda-forge](https://github.com/conda-forge/pillow-feedstock) |
|
||||
|
||||
#### Major Python ecosystem consumers
|
||||
|
||||
These are high-profile projects known to depend on Pillow; a critical vulnerability may
|
||||
warrant proactive notification.
|
||||
|
||||
| Project | Usage |
|
||||
|---|---|
|
||||
| [matplotlib](https://matplotlib.org/) | Image I/O for plots |
|
||||
| [scikit-image](https://scikit-image.org/) | Image processing |
|
||||
| [torchvision](https://github.com/pytorch/vision) (PyTorch) | Dataset loading, transforms |
|
||||
| [Keras / TensorFlow](https://keras.io/) | Image preprocessing utilities |
|
||||
| [Django](https://www.djangoproject.com/) | `ImageField` validation and thumbnail generation |
|
||||
| [Wagtail](https://wagtail.org/) | CMS image renditions |
|
||||
| [Plone](https://plone.org/) | CMS image handling |
|
||||
| [Jupyter / IPython](https://jupyter.org/) | Inline image display |
|
||||
| [ReportLab](https://www.reportlab.com/) | PDF image embedding |
|
||||
| [Tidelift subscribers](https://tidelift.com/) | Enterprise consumers (coordinated via Tidelift) |
|
||||
|
||||
#### Pillow ecosystem plugins
|
||||
|
||||
Third-party plugins extend Pillow and are distributed separately on PyPI. Their
|
||||
maintainers should be notified for Critical/High issues that affect the plugin API
|
||||
or the formats they decode. See the
|
||||
[full plugin list](https://pillow.readthedocs.io/en/stable/handbook/third-party-plugins.html#plugin-list).
|
||||
|
||||
---
|
||||
|
||||
## 11. Plan Maintenance
|
||||
|
||||
This document is a living record. It should be kept current so it is useful when an incident actually occurs. Revisit it during the [Section 1.3 readiness review](#13-readiness-review) at each quarterly release.
|
||||
|
||||
---
|
||||
|
||||
## 12. References
|
||||
|
||||
- [Security Policy](SECURITY.md)
|
||||
- [Release Checklist](../RELEASING.md)
|
||||
- [Contributing Guide](CONTRIBUTING.md)
|
||||
- [Tidelift Security Contact](https://tidelift.com/docs/security)
|
||||
- [GitHub: Privately reporting a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability)
|
||||
- [GitHub as a CVE Numbering Authority (CNA)](https://docs.github.com/en/code-security/security-advisories/working-with-repository-security-advisories/about-repository-security-advisories)
|
||||
- [FIRST CVSS 4.0 Calculator](https://www.first.org/cvss/calculator/4.0)
|
||||
- [linux-distros mailing list](https://oss-security.openwall.org/wiki/mailing-lists/distros)
|
||||
- [OpenSSF CVD Guide](https://github.com/ossf/oss-vulnerability-guide) *(basis for this plan)*
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: Communication Templates
|
||||
|
||||
### A.1 Reporter Acknowledgment
|
||||
|
||||
> Subject: Re: [Security] \<brief issue description\>
|
||||
>
|
||||
> Hi \<name\>,
|
||||
>
|
||||
> Thank you for taking the time to report this issue. We appreciate it.
|
||||
>
|
||||
> We have received your report and will review it as soon as possible. We will
|
||||
> keep you updated on our progress.
|
||||
>
|
||||
> Questions:
|
||||
>
|
||||
> - How would you like to be credited in the advisory? (name, handle,
|
||||
> organisation, or anonymous)
|
||||
> - Do you plan to publish your own write-up or advisory? If so, do you have a
|
||||
> disclosure date in mind?
|
||||
>
|
||||
> We apply coordinated disclosure principles to all vulnerability reports. If
|
||||
> you have any questions or concerns at any point, please reply to this thread.
|
||||
>
|
||||
> Thank you again,
|
||||
> The Pillow team
|
||||
|
||||
### A.2 Embargoed Distro Notification
|
||||
|
||||
> Subject: [EMBARGOED] Pillow security issue — \<CVE-XXXX-XXXXX\> — disclosure \<DATE\>
|
||||
>
|
||||
> This is an embargoed notification of a vulnerability in Pillow. Please keep this
|
||||
> information confidential until the disclosure date listed below.
|
||||
>
|
||||
> **CVE:** \<CVE-XXXX-XXXXX\>
|
||||
>
|
||||
> **Affected versions:** \<e.g. Pillow < 11.x.x\>
|
||||
>
|
||||
> **Fixed version:** \<version\>
|
||||
>
|
||||
> **Severity:** \<Critical / High / Medium / Low\> (CVSS \<score\>: \<vector\>)
|
||||
>
|
||||
> **Reporter:** \<name / affiliation, or "reported privately"\>
|
||||
>
|
||||
> **Public disclosure date:** \<DATE TIME UTC\>
|
||||
>
|
||||
> **Summary:**
|
||||
> \<One paragraph describing the vulnerability class and impact without a full exploit.\>
|
||||
>
|
||||
> **Proof of concept:**
|
||||
> \<Minimal reproducer or attached patch.\>
|
||||
>
|
||||
> **Remediation:**
|
||||
> Upgrade to Pillow \<fixed version\>. No known workaround.
|
||||
>
|
||||
> Please do not share this information, issue public patches, or make user communications
|
||||
> before the disclosure date. We will notify this list immediately if the date changes.
|
||||
>
|
||||
> — The Pillow maintainers
|
||||
|
||||
### A.3 Public Disclosure Advisory
|
||||
|
||||
*(Published as a GitHub Security Advisory; the CVE and date are included automatically.)*
|
||||
|
||||
> **Summary:** \<One-paragraph technical summary.\>
|
||||
>
|
||||
> **CVE:** \<CVE-XXXX-XXXXX\>
|
||||
>
|
||||
> **Affected versions:** Pillow \< \<fixed version\>
|
||||
>
|
||||
> **Fixed version:** \<version\>
|
||||
>
|
||||
> **Severity:** \<rating\> (CVSS \<score\>)
|
||||
>
|
||||
> **Reporter:** \<credited name / "reported privately"\>
|
||||
>
|
||||
> **Details:**
|
||||
> \<Fuller technical description. Include attack scenario where helpful.\>
|
||||
>
|
||||
> **Remediation:**
|
||||
> ```
|
||||
> python3 -m pip install --upgrade Pillow
|
||||
> ```
|
||||
>
|
||||
> **Timeline:**
|
||||
> - Reported: \<date\>
|
||||
> - Fixed: \<date\>
|
||||
> - Disclosed: \<date\>
|
||||
46
.github/ISSUE_TEMPLATE/RELEASE.md
vendored
@ -1,46 +0,0 @@
|
||||
---
|
||||
name: "Maintainers only: Release"
|
||||
about: For maintainers to schedule a quarterly release
|
||||
labels: Release
|
||||
---
|
||||
|
||||
## Main release
|
||||
|
||||
Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||
|
||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
||||
* [ ] Develop and prepare release in `main` branch.
|
||||
* [ ] Add release notes e.g. https://github.com/python-pillow/Pillow/pull/8885
|
||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) 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.
|
||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||
* [ ] Create branch and tag for release e.g.:
|
||||
```bash
|
||||
git branch [[MAJOR.MINOR]].x
|
||||
git tag [[MAJOR.MINOR]].0
|
||||
git push --tags
|
||||
```
|
||||
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) has passed, including the "Upload release to PyPI" job. This will have been triggered by the new tag.
|
||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases).
|
||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
|
||||
```bash
|
||||
git push --all
|
||||
```
|
||||
|
||||
## Publicize release
|
||||
|
||||
* [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321
|
||||
|
||||
## Documentation
|
||||
|
||||
* [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes
|
||||
|
||||
## Docker images
|
||||
|
||||
* [ ] Update Pillow in the Docker Images repository
|
||||
```bash
|
||||
git clone https://github.com/python-pillow/docker-images
|
||||
cd docker-images
|
||||
./update-pillow-tag.sh [[release tag]]
|
||||
```
|
||||
20
.github/SECURITY.md
vendored
@ -1,21 +1,5 @@
|
||||
# Security policy
|
||||
|
||||
## Reporting a vulnerability
|
||||
To report sensitive vulnerability information, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
|
||||
|
||||
To report sensitive vulnerability information, report it [privately on GitHub](https://github.com/python-pillow/Pillow/security/advisories/new).
|
||||
|
||||
If you cannot use GitHub, use the [Tidelift security contact](https://tidelift.com/docs/security). Tidelift will coordinate the fix and disclosure.
|
||||
|
||||
**DO NOT report sensitive vulnerability information in public.**
|
||||
|
||||
## Threat model
|
||||
|
||||
Pillow's primary attack surface is parsing untrusted image data. A full STRIDE threat model covering spoofing, tampering, repudiation, information disclosure, denial of service, and elevation of privilege is maintained in the [Security handbook page](https://pillow.readthedocs.io/en/latest/handbook/security.html).
|
||||
|
||||
Key risks to be aware of when using Pillow to process untrusted images:
|
||||
|
||||
- **Decompression bombs** — do not set `Image.MAX_IMAGE_PIXELS = None` in production.
|
||||
- **EPS files invoke Ghostscript** — block EPS input at the application layer unless strictly required.
|
||||
- **`ImageMath.unsafe_eval()`** — never pass user-controlled strings to this function; use `lambda_eval` instead.
|
||||
- **C extension memory safety** — keep Pillow and its bundled C libraries (libjpeg, libpng, libtiff, libwebp, etc.) up to date.
|
||||
- **Sandboxing** — for high-risk deployments, run image processing in a sandboxed subprocess.
|
||||
If your organisation/employer is a distributor of Pillow and would like advance notification of security-related bugs, please let us know your preferred contact method.
|
||||
|
||||
271
.github/compare-dist-sizes.py
vendored
@ -1,271 +0,0 @@
|
||||
"""Compare sizes of newly-built dists against the latest release on PyPI.
|
||||
|
||||
Fetches file sizes for the latest Pillow release from the PyPI JSON API
|
||||
(no download required) and compares them to a directory of freshly-built
|
||||
wheels and sdist. Outputs a table to stdout (and to
|
||||
`$GITHUB_STEP_SUMMARY` if set).
|
||||
|
||||
Usage:
|
||||
`uv run .github/compare-dist-sizes.py <dist-dir>`
|
||||
"""
|
||||
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "humanize",
|
||||
# "prettytable",
|
||||
# "termcolor",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
import humanize
|
||||
from prettytable import PrettyTable, TableStyle
|
||||
from termcolor import colored
|
||||
|
||||
PYPI_JSON_URL = "https://pypi.org/pypi/pillow/json"
|
||||
|
||||
# Wheel filename: {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl
|
||||
# sdist filename: {distribution}-{version}.tar.gz
|
||||
WHEEL_RE = re.compile(
|
||||
r"^[^-]+-[^-]+(?:-(?P<build>\d[^-]*))?"
|
||||
r"-(?P<python>[^-]+)-(?P<abi>[^-]+)-(?P<platform>[^-]+)\.whl$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
SDIST_RE = re.compile(
|
||||
r"^(?P<dist>[^-]+)-(?P<version>.+)\.tar\.gz$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
def key_for(filename: str) -> str:
|
||||
"""Return a version-independent identifier for a dist file."""
|
||||
if m := WHEEL_RE.match(filename):
|
||||
build = f"{m['build']}-" if m["build"] else ""
|
||||
return f"wheel:{build}{m['python']}-{m['abi']}-{m['platform']}"
|
||||
if SDIST_RE.match(filename):
|
||||
return "sdist"
|
||||
msg = f"Unexpected dist name: {filename}"
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
def display_for(filename: str) -> str:
|
||||
"""Strip the `pillow-{version}-` prefix for compact table display."""
|
||||
if m := WHEEL_RE.match(filename):
|
||||
build = f"{m['build']}-" if m["build"] else ""
|
||||
return f"{build}{m['python']}-{m['abi']}-{m['platform']}.whl"
|
||||
if SDIST_RE.match(filename):
|
||||
return "sdist (.tar.gz)"
|
||||
return filename
|
||||
|
||||
|
||||
def fetch_pypi_sizes() -> tuple[str, dict[str, tuple[str, int]]]:
|
||||
"""Return (version, {key: (filename, size)}) for the latest PyPI release."""
|
||||
with urllib.request.urlopen(PYPI_JSON_URL) as response:
|
||||
data = json.load(response)
|
||||
version = data["info"]["version"]
|
||||
sizes: dict[str, tuple[str, int]] = {}
|
||||
for entry in data.get("urls", []):
|
||||
filename = entry["filename"]
|
||||
key = key_for(filename)
|
||||
sizes[key] = (filename, entry["size"])
|
||||
return version, sizes
|
||||
|
||||
|
||||
def collect_local_sizes(dist_dir: Path) -> dict[str, tuple[str, int]]:
|
||||
sizes: dict[str, tuple[str, int]] = {}
|
||||
for path in sorted(dist_dir.iterdir()):
|
||||
if not path.is_file():
|
||||
continue
|
||||
key = key_for(path.name)
|
||||
sizes[key] = (path.name, path.stat().st_size)
|
||||
return sizes
|
||||
|
||||
|
||||
def human(n: int | None) -> str:
|
||||
if n is None:
|
||||
return "n/a"
|
||||
return humanize.naturalsize(n)
|
||||
|
||||
|
||||
def pct_change(before: int | None, after: int | None) -> str:
|
||||
if before is None or after is None:
|
||||
return "n/a"
|
||||
delta = 0 if before == 0 else (after - before) / before * 100
|
||||
return f"{delta:+.2f}%"
|
||||
|
||||
|
||||
def pct_severity(text: str) -> dict[str, str] | None:
|
||||
"""Return status indicators based on the change percent."""
|
||||
if text == "n/a":
|
||||
return None
|
||||
pct = float(text.rstrip("%"))
|
||||
if pct >= 5:
|
||||
return {"color": "red", "emoji": "🔴"}
|
||||
if pct > 0:
|
||||
return {"color": "yellow", "emoji": "🟡"}
|
||||
else:
|
||||
return {"color": "green", "emoji": "🟢"}
|
||||
|
||||
|
||||
def render_table(
|
||||
baseline_label: str,
|
||||
baseline_sizes: dict[str, tuple[str, int]],
|
||||
local_sizes: dict[str, tuple[str, int]],
|
||||
*,
|
||||
markdown: bool,
|
||||
) -> str:
|
||||
table = PrettyTable()
|
||||
table.set_style(TableStyle.MARKDOWN if markdown else TableStyle.SINGLE_BORDER)
|
||||
table.field_names = ["File", "Size before", "Size now", "Change"]
|
||||
table.align = "r"
|
||||
table.align["File"] = "l"
|
||||
|
||||
def style(cells: list[str], role: str) -> list[str]:
|
||||
severity = pct_severity(cells[3])
|
||||
if markdown:
|
||||
if severity:
|
||||
cells[3] = f"{severity['emoji']} {cells[3]}"
|
||||
if role == "orphan":
|
||||
return [f"*{c}*" for c in cells]
|
||||
if role == "summary":
|
||||
return [f"**{c}**" for c in cells]
|
||||
return cells
|
||||
|
||||
if role == "orphan":
|
||||
return [colored(c, "dark_grey") for c in cells]
|
||||
|
||||
bold_attrs = ["bold"] if role == "summary" else []
|
||||
if bold_attrs:
|
||||
cells[:3] = [colored(c, attrs=bold_attrs) for c in cells[:3]]
|
||||
if severity:
|
||||
cells[3] = colored(cells[3], severity["color"], attrs=bold_attrs)
|
||||
elif bold_attrs:
|
||||
cells[3] = colored(cells[3], attrs=bold_attrs)
|
||||
return cells
|
||||
|
||||
keys = list(set(baseline_sizes) | set(local_sizes))
|
||||
# Put sdist first for readability
|
||||
keys.sort(key=lambda k: (k != "sdist", k))
|
||||
|
||||
wheel_before = []
|
||||
wheel_after = []
|
||||
total_before = []
|
||||
total_after = []
|
||||
for key in keys:
|
||||
baseline_entry = baseline_sizes.get(key)
|
||||
local_entry = local_sizes.get(key)
|
||||
display_name = display_for((local_entry or baseline_entry)[0])
|
||||
before = baseline_entry[1] if baseline_entry else None
|
||||
after = local_entry[1] if local_entry else None
|
||||
if after is None:
|
||||
# Removed since baseline: ignore in totals
|
||||
role = "orphan"
|
||||
else:
|
||||
# Present locally (in both, or newly added): count in totals
|
||||
total_after.append(after)
|
||||
if before is not None:
|
||||
total_before.append(before)
|
||||
if key != "sdist":
|
||||
wheel_after.append(after)
|
||||
if before is not None:
|
||||
wheel_before.append(before)
|
||||
role = "data"
|
||||
cells = [
|
||||
display_name,
|
||||
human(before),
|
||||
human(after),
|
||||
pct_change(before, after),
|
||||
]
|
||||
table.add_row(style(cells, role))
|
||||
|
||||
if not markdown:
|
||||
table.add_divider()
|
||||
|
||||
if wheel_after:
|
||||
avg_before = sum(wheel_before) // len(wheel_before) if wheel_before else None
|
||||
table.add_row(
|
||||
style(
|
||||
[
|
||||
f"wheel average ({len(wheel_after)} wheels)",
|
||||
human(avg_before),
|
||||
human(sum(wheel_after) // len(wheel_after)),
|
||||
pct_change(avg_before, sum(wheel_after) // len(wheel_after)),
|
||||
],
|
||||
"summary",
|
||||
)
|
||||
)
|
||||
table.add_row(
|
||||
style(
|
||||
[
|
||||
f"wheel total ({len(wheel_after)} wheels)",
|
||||
human(sum(wheel_before)),
|
||||
human(sum(wheel_after)),
|
||||
pct_change(sum(wheel_before), sum(wheel_after)),
|
||||
],
|
||||
"summary",
|
||||
),
|
||||
divider=not markdown,
|
||||
)
|
||||
|
||||
if total_after:
|
||||
table.add_row(
|
||||
style(
|
||||
[
|
||||
f"artifacts total ({len(total_after)} artifacts)",
|
||||
human(sum(total_before)),
|
||||
human(sum(total_after)),
|
||||
pct_change(sum(total_before), sum(total_after)),
|
||||
],
|
||||
"summary",
|
||||
)
|
||||
)
|
||||
|
||||
title = f"## Dist size comparison vs {baseline_label}"
|
||||
if not markdown:
|
||||
title = colored(title, attrs=["bold"])
|
||||
return f"{title}\n\n{table.get_string()}\n"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||
)
|
||||
parser.add_argument(
|
||||
"dist_dir",
|
||||
type=Path,
|
||||
help="Directory containing newly-built wheels and sdist",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.dist_dir.is_dir():
|
||||
print(f"error: {args.dist_dir} is not a directory", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
baseline_version, baseline_sizes = fetch_pypi_sizes()
|
||||
baseline_label = f"Pillow {baseline_version} on PyPI"
|
||||
|
||||
local_sizes = collect_local_sizes(args.dist_dir)
|
||||
|
||||
print(render_table(baseline_label, baseline_sizes, local_sizes, markdown=False))
|
||||
|
||||
if summary_path := os.environ.get("GITHUB_STEP_SUMMARY"):
|
||||
with open(summary_path, "a", encoding="utf-8") as f:
|
||||
f.write(
|
||||
render_table(baseline_label, baseline_sizes, local_sizes, markdown=True)
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
19
.github/dependencies.json
vendored
@ -1,19 +0,0 @@
|
||||
{
|
||||
"brotli": "1.2.0",
|
||||
"bzip2": "1.0.8",
|
||||
"freetype": "2.14.3",
|
||||
"fribidi": "1.0.16",
|
||||
"harfbuzz": "14.2.0",
|
||||
"jpegturbo": "3.1.4.1",
|
||||
"lcms2": "2.19",
|
||||
"libavif": "1.4.1",
|
||||
"libimagequant": "4.4.1",
|
||||
"libpng": "1.6.58",
|
||||
"libwebp": "1.6.0",
|
||||
"libxcb": "1.17.0",
|
||||
"openjpeg": "2.5.4",
|
||||
"tiff": "4.7.1",
|
||||
"xz": "5.8.3",
|
||||
"zlib-ng": "2.3.3",
|
||||
"zstd": "1.5.7"
|
||||
}
|
||||
560
.github/generate-sbom.py
vendored
@ -1,560 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate a CycloneDX 1.7 SBOM for Pillow's C extensions and their
|
||||
vendored/optional native library dependencies.
|
||||
|
||||
Usage:
|
||||
python3 .github/generate-sbom.py [output-file]
|
||||
|
||||
Output defaults to pillow-{version}.cdx.json in the current directory.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import datetime as dt
|
||||
import difflib
|
||||
import hashlib
|
||||
import json
|
||||
import urllib.request
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_version() -> str:
|
||||
version_file = Path(__file__).parent.parent / "src" / "PIL" / "_version.py"
|
||||
return version_file.read_text(encoding="utf-8").split('"')[1]
|
||||
|
||||
|
||||
def load_dep_versions() -> dict[str, str]:
|
||||
deps_file = Path(__file__).parent / "dependencies.json"
|
||||
return json.loads(deps_file.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def upstream_diff_b64(
|
||||
upstream_url: str,
|
||||
upstream_display: bytes,
|
||||
local_path: Path,
|
||||
local_display: bytes,
|
||||
) -> str:
|
||||
"""
|
||||
Fetch an upstream file and return a base64-encoded unified diff vs the local copy.
|
||||
"""
|
||||
with urllib.request.urlopen(upstream_url) as resp:
|
||||
upstream_text = resp.read()
|
||||
local_text = local_path.read_bytes()
|
||||
diff_lines = difflib.diff_bytes(
|
||||
difflib.unified_diff,
|
||||
upstream_text.splitlines(keepends=True),
|
||||
local_text.splitlines(keepends=True),
|
||||
fromfile=b"a/" + upstream_display,
|
||||
tofile=b"b/" + local_display,
|
||||
)
|
||||
return base64.b64encode(b"".join(diff_lines)).decode()
|
||||
|
||||
|
||||
def generate(version: str) -> dict:
|
||||
serial = str(uuid.uuid4())
|
||||
now = dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
purl = f"pkg:pypi/pillow@{version}"
|
||||
root = Path(__file__).parent.parent
|
||||
thirdparty = root / "src" / "thirdparty"
|
||||
versions = load_dep_versions()
|
||||
|
||||
metadata_component = {
|
||||
"bom-ref": purl,
|
||||
"type": "library",
|
||||
"name": "Pillow",
|
||||
"version": version,
|
||||
"description": "Python Imaging Library (fork)",
|
||||
"licenses": [{"license": {"id": "MIT-CMU"}}],
|
||||
"purl": purl,
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://python-pillow.github.io"},
|
||||
{"type": "vcs", "url": "https://github.com/python-pillow/Pillow"},
|
||||
{"type": "documentation", "url": "https://pillow.readthedocs.io"},
|
||||
{
|
||||
"type": "security-contact",
|
||||
"url": "https://github.com/python-pillow/Pillow/security/policy",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
c_extensions = [
|
||||
("PIL._avif", "AVIF image format extension"),
|
||||
(
|
||||
"PIL._imaging",
|
||||
"Core image processing extension "
|
||||
"(decode, encode, map, display, outline, path, libImaging)",
|
||||
),
|
||||
("PIL._imagingcms", "LittleCMS2 colour management extension"),
|
||||
("PIL._imagingft", "FreeType font rendering extension"),
|
||||
("PIL._imagingmath", "Image math operations extension"),
|
||||
("PIL._imagingmorph", "Image morphology extension"),
|
||||
("PIL._imagingtk", "Tk/Tcl display extension"),
|
||||
("PIL._webp", "WebP image format extension"),
|
||||
]
|
||||
|
||||
ext_components = [
|
||||
{
|
||||
"bom-ref": f"{purl}#c-ext/{name}",
|
||||
"type": "library",
|
||||
"name": name,
|
||||
"version": version,
|
||||
"description": desc,
|
||||
"licenses": [{"license": {"id": "MIT-CMU"}}],
|
||||
"purl": f"{purl}#c-ext/{name}",
|
||||
}
|
||||
for name, desc in c_extensions
|
||||
]
|
||||
|
||||
vendored_components = [
|
||||
{
|
||||
"bom-ref": f"{purl}#thirdparty/fribidi-shim",
|
||||
"type": "library",
|
||||
"name": "fribidi-shim",
|
||||
"version": "1.x",
|
||||
"description": "FriBiDi runtime-loading shim "
|
||||
"(vendored in src/thirdparty/fribidi-shim/); "
|
||||
"loads libfribidi dynamically",
|
||||
"licenses": [{"license": {"id": "LGPL-2.1-or-later"}}],
|
||||
"hashes": [
|
||||
{
|
||||
"alg": "SHA-256",
|
||||
"content": sha256_file(thirdparty / "fribidi-shim" / "fribidi.c"),
|
||||
}
|
||||
],
|
||||
"pedigree": {
|
||||
"notes": "Pillow-authored shim; not taken from an upstream project."
|
||||
},
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://github.com/fribidi/fribidi"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:github/python/pythoncapi-compat",
|
||||
"type": "library",
|
||||
"name": "pythoncapi_compat",
|
||||
"description": "Backport header for new CPython C-API functions "
|
||||
"(vendored in src/thirdparty/pythoncapi_compat.h)",
|
||||
"licenses": [{"license": {"id": "0BSD"}}],
|
||||
"hashes": [
|
||||
{
|
||||
"alg": "SHA-256",
|
||||
"content": sha256_file(thirdparty / "pythoncapi_compat.h"),
|
||||
}
|
||||
],
|
||||
"pedigree": {
|
||||
"notes": "Vendored unmodified from upstream python/pythoncapi-compat."
|
||||
},
|
||||
"externalReferences": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/python/pythoncapi-compat",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": f"{purl}#thirdparty/raqm",
|
||||
"type": "library",
|
||||
"name": "raqm",
|
||||
"version": "0.10.5",
|
||||
"description": "Complex text layout library "
|
||||
"(vendored in src/thirdparty/raqm/)",
|
||||
"licenses": [{"license": {"id": "MIT"}}],
|
||||
"hashes": [
|
||||
{
|
||||
"alg": "SHA-256",
|
||||
"content": sha256_file(thirdparty / "raqm" / "raqm.c"),
|
||||
}
|
||||
],
|
||||
"pedigree": {
|
||||
"ancestors": [
|
||||
{
|
||||
"bom-ref": "pkg:github/HOST-Oman/libraqm@0.10.5#upstream",
|
||||
"type": "library",
|
||||
"name": "raqm",
|
||||
"version": "0.10.5",
|
||||
"purl": "pkg:github/HOST-Oman/libraqm@0.10.5",
|
||||
"externalReferences": [
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/HOST-Oman/libraqm/releases/tag/v0.10.5",
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
"patches": [
|
||||
{
|
||||
"type": "unofficial",
|
||||
"diff": {
|
||||
"text": {
|
||||
# raqm-version.h.in → raqm-version.h:
|
||||
# template @RAQM_VERSION_*@ placeholders replaced
|
||||
# with literal 0.10.5 values; filename changed to
|
||||
# drop the .in suffix; minor indentation fix.
|
||||
"content": upstream_diff_b64(
|
||||
"https://raw.githubusercontent.com/HOST-Oman/libraqm/v0.10.5/src/raqm-version.h.in",
|
||||
b"src/raqm-version.h.in",
|
||||
thirdparty / "raqm" / "raqm-version.h",
|
||||
b"src/raqm-version.h",
|
||||
),
|
||||
"encoding": "base64",
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "unofficial",
|
||||
"diff": {
|
||||
"text": {
|
||||
# raqm.c: wrap the <fribidi.h> include in an
|
||||
# #ifdef HAVE_FRIBIDI_SYSTEM guard so that when
|
||||
# building without a system FriBiDi Pillow's own
|
||||
# fribidi-shim is used instead.
|
||||
"content": upstream_diff_b64(
|
||||
"https://raw.githubusercontent.com/HOST-Oman/libraqm/v0.10.5/src/raqm.c",
|
||||
b"src/raqm.c",
|
||||
thirdparty / "raqm" / "raqm.c",
|
||||
b"src/raqm.c",
|
||||
),
|
||||
"encoding": "base64",
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
"notes": (
|
||||
"Vendored from upstream HOST-Oman/libraqm v0.10.5 with two "
|
||||
"Pillow-specific modifications: (1) raqm-version.h.in was "
|
||||
"pre-processed into raqm-version.h with version placeholders "
|
||||
"replaced by literal values; (2) raqm.c wraps the <fribidi.h> "
|
||||
"include in an #ifdef HAVE_FRIBIDI_SYSTEM guard so Pillow's "
|
||||
"bundled fribidi-shim is used when a system FriBiDi is absent."
|
||||
),
|
||||
},
|
||||
"externalReferences": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/python-pillow/Pillow/tree/main/src/thirdparty/raqm",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
native_deps = [
|
||||
{
|
||||
"bom-ref": "pkg:generic/freetype2",
|
||||
"type": "library",
|
||||
"name": "FreeType",
|
||||
"version": versions["freetype"],
|
||||
"scope": "optional",
|
||||
"description": "Font rendering (optional, used by PIL._imagingft). "
|
||||
"Required for text/font support.",
|
||||
"licenses": [{"license": {"id": "FTL"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://freetype.org"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://download.savannah.gnu.org/releases/freetype/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/fribidi",
|
||||
"type": "library",
|
||||
"name": "FriBiDi",
|
||||
"version": versions["fribidi"],
|
||||
"scope": "optional",
|
||||
"description": "Unicode bidi algorithm library (optional, "
|
||||
"loaded at runtime by fribidi-shim).",
|
||||
"licenses": [{"license": {"id": "LGPL-2.1-or-later"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://github.com/fribidi/fribidi"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/fribidi/fribidi/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/harfbuzz",
|
||||
"type": "library",
|
||||
"name": "HarfBuzz",
|
||||
"version": versions["harfbuzz"],
|
||||
"scope": "optional",
|
||||
"description": "Text shaping (optional, required by libraqm "
|
||||
"for complex text layout).",
|
||||
"licenses": [{"license": {"id": "MIT"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://harfbuzz.github.io"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/harfbuzz/harfbuzz/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/libavif",
|
||||
"type": "library",
|
||||
"name": "libavif",
|
||||
"version": versions["libavif"],
|
||||
"scope": "optional",
|
||||
"description": "AVIF codec (optional, used by PIL._avif).",
|
||||
"licenses": [{"license": {"id": "BSD-2-Clause"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://github.com/AOMediaCodec/libavif"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/AOMediaCodec/libavif/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/libimagequant",
|
||||
"type": "library",
|
||||
"name": "libimagequant",
|
||||
"version": versions["libimagequant"],
|
||||
"scope": "optional",
|
||||
"description": "Improved colour quantization (optional).",
|
||||
"licenses": [{"license": {"id": "GPL-3.0-or-later"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://pngquant.org/lib/"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/ImageOptim/libimagequant/tags",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/libjpeg",
|
||||
"type": "library",
|
||||
"name": "libjpeg / libjpeg-turbo",
|
||||
"version": versions["jpegturbo"],
|
||||
"description": "JPEG codec (required by default; disable with "
|
||||
"-C jpeg=disable).",
|
||||
"licenses": [
|
||||
{"license": {"id": "IJG"}},
|
||||
{"license": {"id": "BSD-3-Clause"}},
|
||||
],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://ijg.org"},
|
||||
{"type": "website", "url": "https://libjpeg-turbo.org"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/libjpeg-turbo/libjpeg-turbo/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/libtiff",
|
||||
"type": "library",
|
||||
"name": "libtiff",
|
||||
"version": versions["tiff"],
|
||||
"scope": "optional",
|
||||
"description": "TIFF codec (optional).",
|
||||
"licenses": [{"license": {"id": "libtiff"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://libtiff.gitlab.io/libtiff/"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://download.osgeo.org/libtiff/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/libwebp",
|
||||
"type": "library",
|
||||
"name": "libwebp",
|
||||
"version": versions["libwebp"],
|
||||
"scope": "optional",
|
||||
"description": "WebP codec (optional, used by PIL._webp).",
|
||||
"licenses": [{"license": {"id": "BSD-3-Clause"}}],
|
||||
"externalReferences": [
|
||||
{
|
||||
"type": "website",
|
||||
"url": "https://chromium.googlesource.com/webm/libwebp",
|
||||
},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://chromium.googlesource.com/webm/libwebp",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/libxcb",
|
||||
"type": "library",
|
||||
"name": "libxcb",
|
||||
"version": versions["libxcb"],
|
||||
"scope": "optional",
|
||||
"description": "X11 screen-grab support (optional, "
|
||||
"used by PIL._imaging on macOS and Linux).",
|
||||
"licenses": [{"license": {"id": "X11"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://xcb.freedesktop.org"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://xcb.freedesktop.org/dist/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/littlecms2",
|
||||
"type": "library",
|
||||
"name": "Little CMS 2",
|
||||
"version": versions["lcms2"],
|
||||
"scope": "optional",
|
||||
"description": "Colour management (optional, used by PIL._imagingcms).",
|
||||
"licenses": [{"license": {"id": "MIT"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://www.littlecms.com"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/mm2/Little-CMS/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/openjpeg",
|
||||
"type": "library",
|
||||
"name": "OpenJPEG",
|
||||
"version": versions["openjpeg"],
|
||||
"scope": "optional",
|
||||
"description": "JPEG 2000 codec (optional).",
|
||||
"licenses": [{"license": {"id": "BSD-2-Clause"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://www.openjpeg.org"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/uclouvain/openjpeg/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:pypi/pybind11",
|
||||
"type": "library",
|
||||
"name": "pybind11",
|
||||
"scope": "excluded",
|
||||
"description": "Parallel C compilation library (build-time dependency).",
|
||||
"licenses": [{"license": {"id": "BSD-3-Clause"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://pybind11.readthedocs.io"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/pybind/pybind11/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/zlib",
|
||||
"type": "library",
|
||||
"name": "zlib",
|
||||
"version": versions["zlib-ng"],
|
||||
"description": "Deflate/PNG compression (required by default; "
|
||||
"disable with -C zlib=disable).",
|
||||
"licenses": [{"license": {"id": "Zlib"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://zlib.net"},
|
||||
{"type": "distribution", "url": "https://zlib.net"},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
{
|
||||
"ref": purl,
|
||||
"dependsOn": [e["bom-ref"] for e in ext_components],
|
||||
},
|
||||
{
|
||||
"ref": f"{purl}#c-ext/PIL._avif",
|
||||
"dependsOn": ["pkg:generic/libavif"],
|
||||
},
|
||||
{
|
||||
"ref": f"{purl}#c-ext/PIL._imaging",
|
||||
"dependsOn": [
|
||||
"pkg:generic/libimagequant",
|
||||
"pkg:generic/libjpeg",
|
||||
"pkg:generic/libtiff",
|
||||
"pkg:generic/libxcb",
|
||||
"pkg:generic/openjpeg",
|
||||
"pkg:generic/zlib",
|
||||
],
|
||||
},
|
||||
{
|
||||
"ref": f"{purl}#c-ext/PIL._imagingcms",
|
||||
"dependsOn": ["pkg:generic/littlecms2"],
|
||||
},
|
||||
{
|
||||
"ref": f"{purl}#c-ext/PIL._imagingft",
|
||||
"dependsOn": [
|
||||
"pkg:generic/freetype2",
|
||||
"pkg:generic/fribidi",
|
||||
"pkg:generic/harfbuzz",
|
||||
f"{purl}#thirdparty/fribidi-shim",
|
||||
f"{purl}#thirdparty/raqm",
|
||||
],
|
||||
},
|
||||
{
|
||||
"ref": f"{purl}#c-ext/PIL._webp",
|
||||
"dependsOn": ["pkg:generic/libwebp"],
|
||||
},
|
||||
{
|
||||
"ref": f"{purl}#thirdparty/raqm",
|
||||
"dependsOn": [
|
||||
"pkg:generic/harfbuzz",
|
||||
f"{purl}#thirdparty/fribidi-shim",
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return {
|
||||
"$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json",
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.7",
|
||||
"serialNumber": f"urn:uuid:{serial}",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": now,
|
||||
"lifecycles": [{"phase": "build"}],
|
||||
"tools": {
|
||||
"components": [
|
||||
{
|
||||
"type": "application",
|
||||
"name": "generate-sbom.py",
|
||||
"group": "pillow",
|
||||
}
|
||||
]
|
||||
},
|
||||
"component": metadata_component,
|
||||
},
|
||||
"components": ext_components + vendored_components + native_deps,
|
||||
"dependencies": dependencies,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
version = get_version()
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||
)
|
||||
parser.add_argument(
|
||||
"output",
|
||||
nargs="?",
|
||||
type=Path,
|
||||
default=Path(f"pillow-{version}.cdx.json"),
|
||||
help="output file",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
sbom = generate(version)
|
||||
args.output.write_text(json.dumps(sbom, indent=2) + "\n", encoding="utf-8")
|
||||
print(
|
||||
f"Wrote {args.output} (Pillow {version}, {len(sbom['components'])} components)"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
2
.github/mergify.yml
vendored
@ -8,6 +8,8 @@ pull_request_rules:
|
||||
- status-success=Docker Test Successful
|
||||
- status-success=Windows Test Successful
|
||||
- status-success=MinGW
|
||||
- status-success=Cygwin Test Successful
|
||||
- status-success=continuous-integration/appveyor/pr
|
||||
actions:
|
||||
merge:
|
||||
method: merge
|
||||
|
||||
11
.github/release-drafter.yml
vendored
@ -3,19 +3,18 @@ tag-template: "$NEXT_MINOR_VERSION"
|
||||
change-template: '- $TITLE #$NUMBER [@$AUTHOR]'
|
||||
|
||||
categories:
|
||||
- title: "Removals"
|
||||
label: "Removal"
|
||||
- title: "Dependencies"
|
||||
label: "Dependency"
|
||||
- title: "Deprecations"
|
||||
label: "Deprecation"
|
||||
- title: "Documentation"
|
||||
label: "Documentation"
|
||||
- title: "Dependencies"
|
||||
label: "Dependency"
|
||||
- title: "Removals"
|
||||
label: "Removal"
|
||||
- title: "Testing"
|
||||
label: "Testing"
|
||||
- title: "Type hints"
|
||||
label: "Type hints"
|
||||
- title: "Other changes"
|
||||
|
||||
exclude-labels:
|
||||
- "changelog: skip"
|
||||
@ -24,4 +23,6 @@ template: |
|
||||
|
||||
https://pillow.readthedocs.io/en/stable/releasenotes/$NEXT_MINOR_VERSION.html
|
||||
|
||||
## Changes
|
||||
|
||||
$CHANGES
|
||||
|
||||
166
.github/renovate.json
vendored
@ -1,175 +1,17 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended"
|
||||
"config:base"
|
||||
],
|
||||
"labels": [
|
||||
"Dependency"
|
||||
],
|
||||
"minimumReleaseAge": "7 days",
|
||||
"prCreation": "not-pending",
|
||||
"schedule": [
|
||||
"* * 3 * *"
|
||||
],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"brotli\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "brotli",
|
||||
"packageNameTemplate": "google/brotli",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"bzip2\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "bzip2",
|
||||
"packageNameTemplate": "bzip2/bzip2",
|
||||
"datasourceTemplate": "gitlab-tags",
|
||||
"extractVersionTemplate": "^bzip2-(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"freetype\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "freetype",
|
||||
"packageNameTemplate": "freetype/freetype",
|
||||
"datasourceTemplate": "gitlab-tags",
|
||||
"registryUrlTemplate": "https://gitlab.freedesktop.org",
|
||||
"extractVersionTemplate": "^VER-(?<version>[\\d-]+)$",
|
||||
"versioningTemplate": "regex:^(?<major>\\d+)[.-](?<minor>\\d+)[.-](?<patch>\\d+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"fribidi\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "fribidi",
|
||||
"packageNameTemplate": "fribidi/fribidi",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"harfbuzz\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "harfbuzz",
|
||||
"packageNameTemplate": "harfbuzz/harfbuzz",
|
||||
"datasourceTemplate": "github-releases"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"jpegturbo\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "jpegturbo",
|
||||
"packageNameTemplate": "libjpeg-turbo/libjpeg-turbo",
|
||||
"datasourceTemplate": "github-releases"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"lcms2\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "lcms2",
|
||||
"packageNameTemplate": "mm2/Little-CMS",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^lcms(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"libavif\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "libavif",
|
||||
"packageNameTemplate": "AOMediaCodec/libavif",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"libimagequant\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "libimagequant",
|
||||
"packageNameTemplate": "ImageOptim/libimagequant",
|
||||
"datasourceTemplate": "github-tags"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"libpng\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "libpng",
|
||||
"packageNameTemplate": "pnggroup/libpng",
|
||||
"datasourceTemplate": "github-tags",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"libwebp\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "libwebp",
|
||||
"packageNameTemplate": "webmproject/libwebp",
|
||||
"datasourceTemplate": "github-tags",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"libxcb\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "libxcb",
|
||||
"packageNameTemplate": "xorg/lib/libxcb",
|
||||
"datasourceTemplate": "gitlab-tags",
|
||||
"registryUrlTemplate": "https://gitlab.freedesktop.org",
|
||||
"extractVersionTemplate": "^libxcb-(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"openjpeg\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "openjpeg",
|
||||
"packageNameTemplate": "uclouvain/openjpeg",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"tiff\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "tiff",
|
||||
"packageNameTemplate": "libtiff/libtiff",
|
||||
"datasourceTemplate": "gitlab-tags",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"xz\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "xz",
|
||||
"packageNameTemplate": "tukaani-project/xz",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"zlib-ng\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "zlib-ng",
|
||||
"packageNameTemplate": "zlib-ng/zlib-ng",
|
||||
"datasourceTemplate": "github-releases"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"zstd\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "zstd",
|
||||
"packageNameTemplate": "facebook/zstd",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
}
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"groupName": "github-actions",
|
||||
"matchManagers": ["github-actions"],
|
||||
"separateMajorMinor": false
|
||||
"separateMajorMinor": "false"
|
||||
}
|
||||
]
|
||||
],
|
||||
"schedule": ["on the 3rd day of the month"]
|
||||
}
|
||||
|
||||
13
.github/workflows/Brewfile
vendored
@ -1,13 +0,0 @@
|
||||
brew "aom"
|
||||
brew "dav1d"
|
||||
brew "freetype"
|
||||
brew "ghostscript"
|
||||
brew "jpeg-turbo"
|
||||
brew "libimagequant"
|
||||
brew "libraqm"
|
||||
brew "libtiff"
|
||||
brew "little-cms2"
|
||||
brew "openjpeg"
|
||||
brew "rav1e"
|
||||
brew "svt-av1"
|
||||
brew "webp"
|
||||
20
.github/workflows/cifuzz.yml
vendored
@ -4,14 +4,17 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths: &paths
|
||||
- ".github/dependencies.json"
|
||||
paths:
|
||||
- ".github/workflows/cifuzz.yml"
|
||||
- ".github/workflows/wheels-dependencies.sh"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
pull_request:
|
||||
paths: *paths
|
||||
paths:
|
||||
- ".github/workflows/cifuzz.yml"
|
||||
- ".github/workflows/wheels-dependencies.sh"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -21,36 +24,33 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
Fuzzing:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Build Fuzzers
|
||||
id: build
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@d87225267726cf7ce1a3e17cf103c5ac943c4f05 # master
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'pillow'
|
||||
language: python
|
||||
dry-run: false
|
||||
- name: Run Fuzzers
|
||||
id: run
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@d87225267726cf7ce1a3e17cf103c5ac943c4f05 # master
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'pillow'
|
||||
fuzz-seconds: 600
|
||||
language: python
|
||||
dry-run: false
|
||||
- name: Upload New Crash
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./out/artifacts
|
||||
- name: Upload Legacy Crash
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@v4
|
||||
if: steps.run.outcome == 'success'
|
||||
with:
|
||||
name: crash
|
||||
|
||||
31
.github/workflows/docs.yml
vendored
@ -4,12 +4,15 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths: &paths
|
||||
paths:
|
||||
- ".github/workflows/docs.yml"
|
||||
- "docs/**"
|
||||
- "src/PIL/**"
|
||||
pull_request:
|
||||
paths: *paths
|
||||
paths:
|
||||
- ".github/workflows/docs.yml"
|
||||
- "docs/**"
|
||||
- "src/PIL/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -29,12 +32,10 @@ jobs:
|
||||
name: Docs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
cache: pip
|
||||
@ -45,35 +46,19 @@ jobs:
|
||||
- name: Build system information
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
||||
- name: Cache libavif
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
id: cache-libavif
|
||||
with:
|
||||
path: ~/cache-libavif
|
||||
key: ${{ runner.os }}-libavif-${{ hashFiles('depends/install_libavif.sh', 'depends/libavif-svt4.patch') }}
|
||||
|
||||
- name: Cache libimagequant
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache@v4
|
||||
id: cache-libimagequant
|
||||
with:
|
||||
path: ~/cache-libimagequant
|
||||
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
|
||||
|
||||
- name: Cache libwebp
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
id: cache-libwebp
|
||||
with:
|
||||
path: ~/cache-libwebp
|
||||
key: ${{ runner.os }}-libwebp-${{ hashFiles('depends/install_webp.sh') }}
|
||||
|
||||
- name: Install Linux dependencies
|
||||
run: |
|
||||
.ci/install.sh
|
||||
env:
|
||||
GHA_PYTHON_VERSION: "3.x"
|
||||
GHA_LIBAVIF_CACHE_HIT: ${{ steps.cache-libavif.outputs.cache-hit }}
|
||||
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
|
||||
GHA_LIBWEBP_CACHE_HIT: ${{ steps.cache-libwebp.outputs.cache-hit }}
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
|
||||
56
.github/workflows/lint.yml
vendored
@ -2,31 +2,53 @@ name: Lint
|
||||
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
PREK_COLOR: always
|
||||
RUFF_OUTPUT_FORMAT: github
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
name: Lint
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
- name: Lint
|
||||
run: uvx --with tox-uv tox -e lint
|
||||
- name: Mypy
|
||||
run: uvx --with tox-uv tox -e mypy
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: pre-commit cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
|
||||
restore-keys: |
|
||||
lint-pre-commit-
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
cache: pip
|
||||
cache-dependency-path: "setup.py"
|
||||
|
||||
- name: Build system information
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python3 -m pip install -U pip
|
||||
python3 -m pip install -U tox
|
||||
|
||||
- name: Lint
|
||||
run: tox -e lint
|
||||
env:
|
||||
PRE_COMMIT_COLOR: always
|
||||
|
||||
- name: Mypy
|
||||
run: tox -e mypy
|
||||
|
||||
25
.github/workflows/macos-install.sh
vendored
@ -2,7 +2,23 @@
|
||||
|
||||
set -e
|
||||
|
||||
brew bundle --file=.github/workflows/Brewfile
|
||||
if [[ "$ImageOS" == "macos13" ]]; then
|
||||
brew uninstall gradle maven
|
||||
fi
|
||||
brew install \
|
||||
freetype \
|
||||
ghostscript \
|
||||
libimagequant \
|
||||
libjpeg \
|
||||
libtiff \
|
||||
little-cms2 \
|
||||
openjpeg \
|
||||
webp
|
||||
if [[ "$ImageOS" == "macos13" ]]; then
|
||||
brew install --ignore-dependencies libraqm
|
||||
else
|
||||
brew install libraqm
|
||||
fi
|
||||
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
||||
|
||||
python3 -m pip install coverage
|
||||
@ -13,12 +29,7 @@ python3 -m pip install -U pytest
|
||||
python3 -m pip install -U pytest-cov
|
||||
python3 -m pip install -U pytest-timeout
|
||||
python3 -m pip install pyroma
|
||||
# optional test dependencies, only install if there's a binary package.
|
||||
python3 -m pip install --only-binary=:all: numpy || true
|
||||
python3 -m pip install --only-binary=:all: pyarrow || true
|
||||
|
||||
# libavif
|
||||
pushd depends && ./install_libavif.sh && popd
|
||||
python3 -m pip install numpy
|
||||
|
||||
# extra test images
|
||||
pushd depends && ./install_extra_test_images.sh && popd
|
||||
|
||||
5
.github/workflows/release-drafter.yml
vendored
@ -14,9 +14,6 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
update_release_draft:
|
||||
permissions:
|
||||
@ -26,6 +23,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Drafts your next release notes as pull requests are merged into "main"
|
||||
- uses: release-drafter/release-drafter@5de93583980a40bd78603b6dfdcda5b4df377b32 # v7.2.0
|
||||
- uses: release-drafter/release-drafter@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
11
.github/workflows/stale.yml
vendored
@ -6,26 +6,21 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: github.event.repository.fork == false
|
||||
permissions:
|
||||
issues: write
|
||||
if: github.repository_owner == 'python-pillow'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: "Check issues"
|
||||
uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||
uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
only-labels: "Awaiting OP Action"
|
||||
|
||||
151
.github/workflows/test-cygwin.yml
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
name: Test Cygwin
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
COVERAGE_CORE: sysmon
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-minor-version: [9]
|
||||
|
||||
timeout-minutes: 40
|
||||
|
||||
name: Python 3.${{ matrix.python-minor-version }}
|
||||
|
||||
steps:
|
||||
- name: Fix line endings
|
||||
run: |
|
||||
git config --global core.autocrlf input
|
||||
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Cygwin
|
||||
uses: cygwin/cygwin-install-action@v4
|
||||
with:
|
||||
packages: >
|
||||
gcc-g++
|
||||
ghostscript
|
||||
git
|
||||
ImageMagick
|
||||
jpeg
|
||||
libfreetype-devel
|
||||
libimagequant-devel
|
||||
libjpeg-devel
|
||||
liblapack-devel
|
||||
liblcms2-devel
|
||||
libopenjp2-devel
|
||||
libraqm-devel
|
||||
libtiff-devel
|
||||
libwebp-devel
|
||||
libxcb-devel
|
||||
libxcb-xinerama0
|
||||
make
|
||||
netpbm
|
||||
perl
|
||||
python3${{ matrix.python-minor-version }}-cython
|
||||
python3${{ matrix.python-minor-version }}-devel
|
||||
python3${{ matrix.python-minor-version }}-ipython
|
||||
python3${{ matrix.python-minor-version }}-numpy
|
||||
python3${{ matrix.python-minor-version }}-sip
|
||||
python3${{ matrix.python-minor-version }}-tkinter
|
||||
wget
|
||||
xorg-server-extra
|
||||
zlib-devel
|
||||
|
||||
- name: Add Lapack to PATH
|
||||
uses: egor-tensin/cleanup-path@v4
|
||||
with:
|
||||
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
|
||||
|
||||
- name: Select Python version
|
||||
run: |
|
||||
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
|
||||
|
||||
- name: pip cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
||||
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-${{ hashFiles('.ci/install.sh') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-
|
||||
|
||||
- name: Build system information
|
||||
run: |
|
||||
dash.exe -c "python3 .github/workflows/system-info.py"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
bash.exe .ci/install.sh
|
||||
|
||||
- name: Build
|
||||
shell: bash.exe -eo pipefail -o igncr "{0}"
|
||||
run: |
|
||||
.ci/build.sh
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
bash.exe xvfb-run -s '-screen 0 1024x768x24' .ci/test.sh
|
||||
|
||||
- name: Prepare to upload errors
|
||||
if: failure()
|
||||
run: |
|
||||
dash.exe -c "mkdir -p Tests/errors"
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: errors
|
||||
path: Tests/errors
|
||||
|
||||
- name: After success
|
||||
run: |
|
||||
bash.exe .ci/after_success.sh
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
flags: GHA_Cygwin
|
||||
name: Cygwin Python 3.${{ matrix.python-minor-version }}
|
||||
token: ${{ secrets.CODECOV_ORG_TOKEN }}
|
||||
|
||||
success:
|
||||
permissions:
|
||||
contents: none
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
name: Cygwin Test Successful
|
||||
steps:
|
||||
- name: Success
|
||||
run: echo Cygwin Test Successful
|
||||
61
.github/workflows/test-docker.yml
vendored
@ -4,14 +4,19 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore: &paths-ignore
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore: *paths-ignore
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -21,65 +26,57 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: ["ubuntu-latest"]
|
||||
docker: [
|
||||
# Run slower jobs first to give them a headstart and reduce waiting time
|
||||
ubuntu-26.04-resolute-ppc64le,
|
||||
ubuntu-26.04-resolute-s390x,
|
||||
ubuntu-22.04-jammy-arm64v8,
|
||||
ubuntu-24.04-noble-ppc64le,
|
||||
ubuntu-24.04-noble-s390x,
|
||||
# Then run the remainder
|
||||
alpine,
|
||||
amazon-2-amd64,
|
||||
amazon-2023-amd64,
|
||||
arch,
|
||||
centos-stream-9-amd64,
|
||||
centos-stream-10-amd64,
|
||||
debian-13-trixie-x86,
|
||||
debian-13-trixie-amd64,
|
||||
fedora-43-amd64,
|
||||
fedora-44-amd64,
|
||||
debian-12-bookworm-x86,
|
||||
debian-12-bookworm-amd64,
|
||||
fedora-39-amd64,
|
||||
fedora-40-amd64,
|
||||
gentoo,
|
||||
ubuntu-22.04-jammy-amd64,
|
||||
ubuntu-24.04-noble-amd64,
|
||||
ubuntu-26.04-resolute-amd64,
|
||||
]
|
||||
dockerTag: [main]
|
||||
include:
|
||||
- docker: "ubuntu-26.04-resolute-ppc64le"
|
||||
- docker: "ubuntu-22.04-jammy-arm64v8"
|
||||
qemu-arch: "aarch64"
|
||||
- docker: "ubuntu-24.04-noble-ppc64le"
|
||||
qemu-arch: "ppc64le"
|
||||
- docker: "ubuntu-26.04-resolute-s390x"
|
||||
- docker: "ubuntu-24.04-noble-s390x"
|
||||
qemu-arch: "s390x"
|
||||
- docker: "ubuntu-26.04-resolute-arm64v8"
|
||||
os: "ubuntu-24.04-arm"
|
||||
dockerTag: main
|
||||
|
||||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build system information
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
||||
- name: Set up QEMU
|
||||
if: "matrix.qemu-arch"
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
with:
|
||||
platforms: ${{ matrix.qemu-arch }}
|
||||
run: |
|
||||
docker run --rm --privileged aptman/qus -s -- -p ${{ matrix.qemu-arch }}
|
||||
|
||||
- name: Docker pull
|
||||
run: |
|
||||
docker pull ${{ matrix.qemu-arch && format('--platform=linux/{0}', matrix.qemu-arch)}} pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
|
||||
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
|
||||
|
||||
- name: Docker build
|
||||
run: |
|
||||
@ -90,21 +87,23 @@ jobs:
|
||||
|
||||
- name: After success
|
||||
run: |
|
||||
PATH="$PATH:~/.local/bin"
|
||||
docker start pillow_container
|
||||
sudo docker cp pillow_container:/Pillow /Pillow
|
||||
sudo chown -R runner /Pillow
|
||||
pil_path=`docker exec pillow_container /vpy3/bin/python -c 'import os, PIL;print(os.path.realpath(os.path.dirname(PIL.__file__)))'`
|
||||
docker stop pillow_container
|
||||
sudo mkdir -p $pil_path
|
||||
sudo cp src/PIL/*.py $pil_path
|
||||
cd /Pillow
|
||||
.ci/after_success.sh
|
||||
env:
|
||||
MATRIX_DOCKER: ${{ matrix.docker }}
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
flags: GHA_Docker
|
||||
name: ${{ matrix.docker }}
|
||||
gcov: true
|
||||
token: ${{ secrets.CODECOV_ORG_TOKEN }}
|
||||
|
||||
success:
|
||||
permissions:
|
||||
|
||||
34
.github/workflows/test-mingw.yml
vendored
@ -4,14 +4,19 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore: &paths-ignore
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore: *paths-ignore
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -23,7 +28,6 @@ concurrency:
|
||||
|
||||
env:
|
||||
COVERAGE_CORE: sysmon
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -41,9 +45,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up shell
|
||||
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
|
||||
@ -56,25 +58,24 @@ jobs:
|
||||
mingw-w64-x86_64-gcc \
|
||||
mingw-w64-x86_64-ghostscript \
|
||||
mingw-w64-x86_64-lcms2 \
|
||||
mingw-w64-x86_64-libavif \
|
||||
mingw-w64-x86_64-libimagequant \
|
||||
mingw-w64-x86_64-libjpeg-turbo \
|
||||
mingw-w64-x86_64-libraqm \
|
||||
mingw-w64-x86_64-libtiff \
|
||||
mingw-w64-x86_64-libwebp \
|
||||
mingw-w64-x86_64-openjpeg2 \
|
||||
mingw-w64-x86_64-python-numpy \
|
||||
mingw-w64-x86_64-python-olefile \
|
||||
mingw-w64-x86_64-python-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-python3-numpy \
|
||||
mingw-w64-x86_64-python3-olefile \
|
||||
mingw-w64-x86_64-python3-setuptools \
|
||||
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
|
||||
|
||||
- name: Build Pillow
|
||||
run: CFLAGS="-coverage" python3 -m pip install .
|
||||
run: SETUPTOOLS_USE_DISTUTILS="stdlib" CFLAGS="-coverage" python3 -m pip install .
|
||||
|
||||
- name: Test Pillow
|
||||
run: |
|
||||
@ -82,8 +83,9 @@ jobs:
|
||||
.ci/test.sh
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./coverage.xml
|
||||
file: ./coverage.xml
|
||||
flags: GHA_Windows
|
||||
name: "MSYS2 MinGW"
|
||||
token: ${{ secrets.CODECOV_ORG_TOKEN }}
|
||||
|
||||
64
.github/workflows/test-valgrind-memory.yml
vendored
@ -1,64 +0,0 @@
|
||||
name: Test Valgrind Memory Leaks
|
||||
|
||||
# like the Docker tests, but running valgrind only on *.c/*.h changes.
|
||||
|
||||
# this is very expensive. Only run on the pull request.
|
||||
on:
|
||||
# push:
|
||||
# branches:
|
||||
# - "**"
|
||||
# paths:
|
||||
# - ".github/workflows/test-valgrind-memory.yml"
|
||||
# - "**.c"
|
||||
# - "**.h"
|
||||
# - "depends/docker-test-valgrind-memory.sh"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/test-valgrind-memory.yml"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
- "depends/docker-test-valgrind-memory.sh"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
docker: [
|
||||
ubuntu-22.04-jammy-amd64-valgrind,
|
||||
]
|
||||
dockerTag: [main]
|
||||
|
||||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build system information
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
||||
- name: Docker pull
|
||||
run: |
|
||||
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
|
||||
|
||||
- name: Build and Run Valgrind
|
||||
run: |
|
||||
# The Pillow user in the docker container is UID 1001
|
||||
sudo chown -R 1001 $GITHUB_WORKSPACE
|
||||
docker run --name pillow_container -e "PILLOW_VALGRIND_TEST=true" -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} /Pillow/depends/docker-test-valgrind-memory.sh
|
||||
sudo chown -R runner $GITHUB_WORKSPACE
|
||||
14
.github/workflows/test-valgrind.yml
vendored
@ -6,12 +6,15 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths: &paths
|
||||
paths:
|
||||
- ".github/workflows/test-valgrind.yml"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
pull_request:
|
||||
paths: *paths
|
||||
paths:
|
||||
- ".github/workflows/test-valgrind.yml"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -21,9 +24,6 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@ -39,9 +39,7 @@ jobs:
|
||||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build system information
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
||||
84
.github/workflows/test-windows.yml
vendored
@ -4,14 +4,19 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore: &paths-ignore
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore: *paths-ignore
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -23,70 +28,57 @@ concurrency:
|
||||
|
||||
env:
|
||||
COVERAGE_CORE: sysmon
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["pypy3.11", "3.11", "3.12", "3.13", "3.14", "3.15"]
|
||||
architecture: ["x64"]
|
||||
os: ["windows-latest"]
|
||||
include:
|
||||
# Test the oldest Python on 32-bit
|
||||
- { python-version: "3.10", architecture: "x86", os: "windows-2022" }
|
||||
python-version: ["pypy3.10", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 30
|
||||
|
||||
name: Python ${{ matrix.python-version }} (${{ matrix.architecture }})
|
||||
name: Python ${{ matrix.python-version }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout cached dependencies
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: python-pillow/pillow-depends
|
||||
path: winbuild\depends
|
||||
|
||||
- name: Checkout extra test images
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: python-pillow/test-images
|
||||
path: Tests\test-images
|
||||
|
||||
# sets env: pythonLocation
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
allow-prereleases: true
|
||||
architecture: ${{ matrix.architecture }}
|
||||
cache: pip
|
||||
cache-dependency-path: ".github/workflows/test-windows.yml"
|
||||
|
||||
- name: Print build system information
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
||||
- name: Upgrade pip
|
||||
run: |
|
||||
python3 -m pip install --upgrade pip
|
||||
|
||||
- name: Install CPython dependencies
|
||||
if: "!contains(matrix.python-version, 'pypy') && matrix.architecture != 'x86'"
|
||||
run: |
|
||||
python3 -m pip install PyQt6
|
||||
|
||||
- name: Install PyArrow dependency
|
||||
run: |
|
||||
python3 -m pip install --only-binary=:all: pyarrow || true
|
||||
- name: Install Python dependencies
|
||||
run: >
|
||||
python3 -m pip install
|
||||
coverage>=7.4.2
|
||||
defusedxml
|
||||
olefile
|
||||
pyroma
|
||||
pytest
|
||||
pytest-cov
|
||||
pytest-timeout
|
||||
|
||||
- name: Install dependencies
|
||||
id: install
|
||||
@ -94,8 +86,8 @@ jobs:
|
||||
choco install nasm --no-progress
|
||||
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
||||
|
||||
choco install ghostscript --version=10.7.0 --no-progress
|
||||
echo "C:\Program Files\gs\gs10.07.0\bin" >> $env:GITHUB_PATH
|
||||
choco install ghostscript --version=10.4.0 --no-progress
|
||||
echo "C:\Program Files\gs\gs10.04.0\bin" >> $env:GITHUB_PATH
|
||||
|
||||
# Install extra test images
|
||||
xcopy /S /Y Tests\test-images\* Tests\images
|
||||
@ -108,7 +100,7 @@ jobs:
|
||||
|
||||
- name: Cache build
|
||||
id: build-cache
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: winbuild\build
|
||||
key:
|
||||
@ -145,10 +137,6 @@ jobs:
|
||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||
run: "& winbuild\\build\\build_dep_libpng.cmd"
|
||||
|
||||
- name: Build dependencies / libavif
|
||||
if: steps.build-cache.outputs.cache-hit != 'true' && matrix.architecture == 'x64'
|
||||
run: "& winbuild\\build\\build_dep_libavif.cmd"
|
||||
|
||||
# for FreeType WOFF2 font support
|
||||
- name: Build dependencies / brotli
|
||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||
@ -184,14 +172,13 @@ jobs:
|
||||
# trim ~150MB for each job
|
||||
- name: Optimize build cache
|
||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
rm -rf winbuild\build\src
|
||||
shell: bash
|
||||
run: rmdir /S /Q winbuild\build\src
|
||||
shell: cmd
|
||||
|
||||
- name: Build Pillow
|
||||
run: |
|
||||
$FLAGS="-C raqm=vendor -C fribidi=vendor"
|
||||
cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS .[tests]"
|
||||
cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ."
|
||||
& $env:pythonLocation\python.exe selftest.py --installed
|
||||
shell: pwsh
|
||||
|
||||
@ -203,7 +190,9 @@ jobs:
|
||||
|
||||
- name: Test Pillow
|
||||
run: |
|
||||
path %GITHUB_WORKSPACE%\winbuild\build\bin;%PATH%
|
||||
.ci\test.cmd
|
||||
shell: cmd
|
||||
|
||||
- name: Prepare to upload errors
|
||||
if: failure()
|
||||
@ -212,7 +201,7 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: errors
|
||||
@ -224,11 +213,12 @@ jobs:
|
||||
shell: pwsh
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./coverage.xml
|
||||
file: ./coverage.xml
|
||||
flags: GHA_Windows
|
||||
name: ${{ runner.os }} Python ${{ matrix.python-version }}
|
||||
token: ${{ secrets.CODECOV_ORG_TOKEN }}
|
||||
|
||||
success:
|
||||
permissions:
|
||||
|
||||
78
.github/workflows/test.yml
vendored
@ -4,14 +4,19 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore: &paths-ignore
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore: *paths-ignore
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -24,7 +29,6 @@ concurrency:
|
||||
env:
|
||||
COVERAGE_CORE: sysmon
|
||||
FORCE_COLOR: 1
|
||||
PIP_DISABLE_PIP_VERSION_CHECK: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -37,34 +41,32 @@ jobs:
|
||||
"ubuntu-latest",
|
||||
]
|
||||
python-version: [
|
||||
"pypy3.11",
|
||||
"3.15t",
|
||||
"3.15",
|
||||
"3.14t",
|
||||
"3.14",
|
||||
"pypy3.10",
|
||||
"3.13",
|
||||
"3.12",
|
||||
"3.11",
|
||||
"3.10",
|
||||
"3.9",
|
||||
]
|
||||
include:
|
||||
- { python-version: "3.12", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
|
||||
- { python-version: "3.11", PYTHONOPTIMIZE: 2 }
|
||||
# Intel
|
||||
- { os: "macos-26-intel", python-version: "3.10" }
|
||||
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
|
||||
- { python-version: "3.10", PYTHONOPTIMIZE: 2 }
|
||||
# Free-threaded
|
||||
- { os: "ubuntu-latest", python-version: "3.13-dev", disable-gil: true }
|
||||
# M1 only available for 3.10+
|
||||
- { os: "macos-13", python-version: "3.9" }
|
||||
exclude:
|
||||
- { os: "macos-latest", python-version: "3.10" }
|
||||
- { os: "macos-latest", python-version: "3.9" }
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }} ${{ matrix.disable-gil && 'free-threaded' || '' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@v5
|
||||
if: "${{ !matrix.disable-gil }}"
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
allow-prereleases: true
|
||||
@ -73,42 +75,36 @@ jobs:
|
||||
".ci/*.sh"
|
||||
"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
|
||||
if: "${{ matrix.disable-gil }}"
|
||||
run: |
|
||||
echo "PYTHON_GIL=0" >> $GITHUB_ENV
|
||||
|
||||
- name: Build system information
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
||||
- name: Cache libavif
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
id: cache-libavif
|
||||
with:
|
||||
path: ~/cache-libavif
|
||||
key: ${{ runner.os }}-libavif-${{ hashFiles('depends/install_libavif.sh', 'depends/libavif-svt4.patch') }}
|
||||
|
||||
- name: Cache libimagequant
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache@v4
|
||||
id: cache-libimagequant
|
||||
with:
|
||||
path: ~/cache-libimagequant
|
||||
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
|
||||
|
||||
- name: Cache libwebp
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
id: cache-libwebp
|
||||
with:
|
||||
path: ~/cache-libwebp
|
||||
key: ${{ runner.os }}-libwebp-${{ hashFiles('depends/install_webp.sh') }}
|
||||
|
||||
- name: Install Linux dependencies
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
.ci/install.sh
|
||||
env:
|
||||
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
GHA_LIBAVIF_CACHE_HIT: ${{ steps.cache-libavif.outputs.cache-hit }}
|
||||
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
|
||||
GHA_LIBWEBP_CACHE_HIT: ${{ steps.cache-libwebp.outputs.cache-hit }}
|
||||
|
||||
- name: Install macOS dependencies
|
||||
if: startsWith(matrix.os, 'macOS')
|
||||
@ -118,7 +114,7 @@ jobs:
|
||||
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
|
||||
- name: Register gcc problem matcher
|
||||
if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.14'"
|
||||
if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'"
|
||||
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
|
||||
|
||||
- name: Build
|
||||
@ -147,7 +143,7 @@ jobs:
|
||||
mkdir -p Tests/errors
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: errors
|
||||
@ -158,10 +154,12 @@ jobs:
|
||||
.ci/after_success.sh
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }}
|
||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||
gcov: true
|
||||
token: ${{ secrets.CODECOV_ORG_TOKEN }}
|
||||
|
||||
success:
|
||||
permissions:
|
||||
|
||||
427
.github/workflows/wheels-dependencies.sh
vendored
@ -1,412 +1,145 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Safety check - Pillow builds require that CIBW_ARCHS is set, and that it only
|
||||
# contains a single value (even though cibuildwheel allows multiple values in
|
||||
# CIBW_ARCHS). This check doesn't work on Linux because of how the CIBW_ARCHS
|
||||
# variable is exposed.
|
||||
function check_cibw_archs {
|
||||
if [[ -z "$CIBW_ARCHS" ]]; then
|
||||
echo "ERROR: Pillow builds require CIBW_ARCHS be defined."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$CIBW_ARCHS" == *" "* ]]; then
|
||||
echo "ERROR: Pillow builds only support a single architecture in CIBW_ARCHS."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Setup that needs to be done before multibuild utils are invoked. Process
|
||||
# potential cross-build platforms before native platforms to ensure that we pick
|
||||
# up the cross environment.
|
||||
PROJECTDIR=$(pwd)
|
||||
if [[ "$CIBW_PLATFORM" == "ios" ]]; then
|
||||
check_cibw_archs
|
||||
# On iOS, CIBW_ARCHS is actually a multi-arch - arm64_iphoneos,
|
||||
# arm64_iphonesimulator or x86_64_iphonesimulator. Split into the CPU
|
||||
# platform, and the iOS SDK.
|
||||
PLAT=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\1/")
|
||||
IOS_SDK=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\2/")
|
||||
|
||||
# Build iOS builds in `build/iphoneos` or `build/iphonesimulator`
|
||||
# (depending on the build target). Install them into `build/deps/iphoneos`
|
||||
# or `build/deps/iphonesimulator`
|
||||
WORKDIR=$(pwd)/build/$IOS_SDK
|
||||
BUILD_PREFIX=$(pwd)/build/deps/$IOS_SDK
|
||||
|
||||
# GNU tooling insists on using aarch64 rather than arm64
|
||||
if [[ $PLAT == "arm64" ]]; then
|
||||
GNU_ARCH=aarch64
|
||||
else
|
||||
GNU_ARCH=x86_64
|
||||
fi
|
||||
|
||||
IOS_SDK_PATH=$(xcrun --sdk $IOS_SDK --show-sdk-path)
|
||||
CMAKE_SYSTEM_NAME=iOS
|
||||
IOS_HOST_TRIPLE=$PLAT-apple-ios$IPHONEOS_DEPLOYMENT_TARGET
|
||||
if [[ "$IOS_SDK" == "iphonesimulator" ]]; then
|
||||
IOS_HOST_TRIPLE=$IOS_HOST_TRIPLE-simulator
|
||||
fi
|
||||
|
||||
# GNU Autotools doesn't recognize the existence of arm64-apple-ios-simulator
|
||||
# as a valid host. However, the only difference between arm64-apple-ios and
|
||||
# arm64-apple-ios-simulator is the choice of sysroot, and that is
|
||||
# coordinated by CC, CFLAGS etc. From the perspective of configure, the two
|
||||
# platforms are identical, so we can use arm64-apple-ios consistently.
|
||||
# This (mostly) avoids us needing to patch config.sub in dependency sources.
|
||||
HOST_CONFIGURE_FLAGS="--disable-shared --enable-static --host=$GNU_ARCH-apple-ios --build=$GNU_ARCH-apple-darwin"
|
||||
|
||||
# CMake has native support for iOS. However, most of that support is based
|
||||
# on using the Xcode builder, which isn't very helpful for most of Pillow's
|
||||
# dependencies. Therefore, we lean on the OSX configurations, plus CC, CFLAGS
|
||||
# etc. to ensure the right sysroot is selected.
|
||||
HOST_CMAKE_FLAGS="-DCMAKE_SYSTEM_NAME=$CMAKE_SYSTEM_NAME -DCMAKE_SYSTEM_PROCESSOR=$GNU_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET -DCMAKE_OSX_SYSROOT=$IOS_SDK_PATH -DBUILD_SHARED_LIBS=NO -DENABLE_SHARED=NO"
|
||||
|
||||
# Meson needs to be pointed at a cross-platform configuration file
|
||||
# This will be generated once CC etc. have been evaluated.
|
||||
HOST_MESON_FLAGS="--cross-file $WORKDIR/meson-cross.txt -Dprefer_static=true -Ddefault_library=static"
|
||||
|
||||
elif [[ "$(uname -s)" == "Darwin" ]]; then
|
||||
check_cibw_archs
|
||||
# Build macOS dependencies in `build/darwin`
|
||||
# Install them into `build/deps/darwin`
|
||||
PLAT=$CIBW_ARCHS
|
||||
WORKDIR=$(pwd)/build/darwin
|
||||
BUILD_PREFIX=$(pwd)/build/deps/darwin
|
||||
else
|
||||
# Build prefix will default to /usr/local
|
||||
PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}"
|
||||
WORKDIR=$(pwd)/build
|
||||
MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
|
||||
MB_ML_VER=${AUDITWHEEL_POLICY:9}
|
||||
fi
|
||||
|
||||
# Define custom utilities
|
||||
# Test for macOS with [ -n "$IS_MACOS" ]
|
||||
if [ -z "$IS_MACOS" ]; then
|
||||
export MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
|
||||
export MB_ML_VER=${AUDITWHEEL_POLICY:9}
|
||||
fi
|
||||
export PLAT=$CIBW_ARCHS
|
||||
source wheels/multibuild/common_utils.sh
|
||||
source wheels/multibuild/library_builders.sh
|
||||
if [[ -z "$IS_MACOS" ]]; then
|
||||
if [ -z "$IS_MACOS" ]; then
|
||||
source wheels/multibuild/manylinux_utils.sh
|
||||
fi
|
||||
|
||||
ARCHIVE_SDIR=pillow-depends-main
|
||||
|
||||
VERSIONS_FILE="$PROJECTDIR/.github/dependencies.json"
|
||||
_get_ver() { python3 -c "import json; print(json.load(open('$VERSIONS_FILE'))['$1'])"; }
|
||||
FREETYPE_VERSION=$(_get_ver freetype)
|
||||
HARFBUZZ_VERSION=$(_get_ver harfbuzz)
|
||||
LIBPNG_VERSION=$(_get_ver libpng)
|
||||
JPEGTURBO_VERSION=$(_get_ver jpegturbo)
|
||||
OPENJPEG_VERSION=$(_get_ver openjpeg)
|
||||
XZ_VERSION=$(_get_ver xz)
|
||||
ZSTD_VERSION=$(_get_ver zstd)
|
||||
TIFF_VERSION=$(_get_ver tiff)
|
||||
LCMS2_VERSION=$(_get_ver lcms2)
|
||||
ZLIB_NG_VERSION=$(_get_ver zlib-ng)
|
||||
LIBWEBP_VERSION=$(_get_ver libwebp)
|
||||
BZIP2_VERSION=$(_get_ver bzip2)
|
||||
LIBXCB_VERSION=$(_get_ver libxcb)
|
||||
BROTLI_VERSION=$(_get_ver brotli)
|
||||
LIBAVIF_VERSION=$(_get_ver libavif)
|
||||
|
||||
function build_pkg_config {
|
||||
if [ -e pkg-config-stamp ]; then return; fi
|
||||
# This essentially duplicates the Homebrew recipe.
|
||||
# On iOS, we need a binary that can be executed on the build machine; but we
|
||||
# can create a host-specific pc-path to store iOS .pc files. To ensure a
|
||||
# macOS-compatible build, we temporarily clear environment flags that set
|
||||
# iOS-specific values.
|
||||
if [[ -n "$IOS_SDK" ]]; then
|
||||
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
|
||||
ORIGINAL_IPHONEOS_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET
|
||||
unset HOST_CONFIGURE_FLAGS
|
||||
unset IPHONEOS_DEPLOYMENT_TARGET
|
||||
fi
|
||||
|
||||
CFLAGS="$CFLAGS -Wno-int-conversion" CPPFLAGS="" 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
|
||||
|
||||
if [[ -n "$IOS_SDK" ]]; then
|
||||
HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS
|
||||
IPHONEOS_DEPLOYMENT_TARGET=$ORIGINAL_IPHONEOS_DEPLOYMENT_TARGET
|
||||
fi;
|
||||
|
||||
export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config
|
||||
touch pkg-config-stamp
|
||||
}
|
||||
|
||||
function build_zlib_ng {
|
||||
if [ -e zlib-stamp ]; then return; fi
|
||||
# zlib-ng uses a "configure" script, but it's not a GNU autotools script, so
|
||||
# it doesn't honor the usual flags. Temporarily disable any
|
||||
# cross-compilation flags.
|
||||
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
|
||||
unset HOST_CONFIGURE_FLAGS
|
||||
|
||||
build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --installnamedir=$BUILD_PREFIX/lib --zlib-compat
|
||||
|
||||
HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS
|
||||
touch zlib-stamp
|
||||
}
|
||||
# Package versions for fresh source builds
|
||||
FREETYPE_VERSION=2.13.2
|
||||
HARFBUZZ_VERSION=10.0.1
|
||||
LIBPNG_VERSION=1.6.44
|
||||
JPEGTURBO_VERSION=3.0.4
|
||||
OPENJPEG_VERSION=2.5.2
|
||||
XZ_VERSION=5.6.3
|
||||
TIFF_VERSION=4.6.0
|
||||
LCMS2_VERSION=2.16
|
||||
if [[ -n "$IS_MACOS" ]]; then
|
||||
GIFLIB_VERSION=5.2.2
|
||||
else
|
||||
GIFLIB_VERSION=5.2.1
|
||||
fi
|
||||
if [[ -n "$IS_MACOS" ]] || [[ "$MB_ML_VER" != 2014 ]]; then
|
||||
ZLIB_VERSION=1.3.1
|
||||
else
|
||||
ZLIB_VERSION=1.2.8
|
||||
fi
|
||||
LIBWEBP_VERSION=1.4.0
|
||||
BZIP2_VERSION=1.0.8
|
||||
LIBXCB_VERSION=1.17.0
|
||||
BROTLI_VERSION=1.1.0
|
||||
|
||||
function build_brotli {
|
||||
if [ -e brotli-stamp ]; then return; fi
|
||||
local cmake=$(get_modern_cmake)
|
||||
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
|
||||
(cd $out_dir \
|
||||
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib -DCMAKE_MACOSX_BUNDLE=OFF $HOST_CMAKE_FLAGS . \
|
||||
&& make -j4 install)
|
||||
touch brotli-stamp
|
||||
&& $cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
|
||||
&& make install)
|
||||
if [[ "$MB_ML_LIBC" == "manylinux" ]]; then
|
||||
cp /usr/local/lib64/libbrotli* /usr/local/lib
|
||||
cp /usr/local/lib64/pkgconfig/libbrotli* /usr/local/lib/pkgconfig
|
||||
fi
|
||||
}
|
||||
|
||||
function build_harfbuzz {
|
||||
if [ -e harfbuzz-stamp ]; then return; fi
|
||||
python3 -m pip install meson ninja
|
||||
|
||||
local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$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 \
|
||||
&& meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dfreetype=enabled -Dglib=disabled -Dtests=disabled $HOST_MESON_FLAGS)
|
||||
&& meson setup build --buildtype=release -Dfreetype=enabled -Dglib=disabled)
|
||||
(cd $out_dir/build \
|
||||
&& meson install)
|
||||
touch harfbuzz-stamp
|
||||
}
|
||||
|
||||
function build_libavif {
|
||||
if [ -e libavif-stamp ]; then return; fi
|
||||
|
||||
python3 -m pip install meson ninja
|
||||
|
||||
if ([[ "$PLAT" == "x86_64" ]] && [[ -z "$IOS_SDK" ]]) || [ -n "$SANITIZER" ]; then
|
||||
build_simple nasm 2.16.03 https://www.nasm.us/pub/nasm/releasebuilds/2.16.03
|
||||
if [[ "$MB_ML_LIBC" == "manylinux" ]]; then
|
||||
cp /usr/local/lib64/libharfbuzz* /usr/local/lib
|
||||
fi
|
||||
|
||||
local build_shared=ON
|
||||
local lto=ON
|
||||
|
||||
local libavif_cmake_flags
|
||||
|
||||
if [[ -n "$IS_MACOS" ]]; then
|
||||
lto=OFF
|
||||
libavif_cmake_flags=(
|
||||
-DCMAKE_C_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
|
||||
-DCMAKE_CXX_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
|
||||
-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,-S,-x,-dead_strip_dylibs" \
|
||||
)
|
||||
if [[ -n "$IOS_SDK" ]]; then
|
||||
build_shared=OFF
|
||||
fi
|
||||
else
|
||||
libavif_cmake_flags=(-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,--strip-all,-z,relro,-z,now")
|
||||
fi
|
||||
if [[ -n "$IOS_SDK" ]] && [[ "$PLAT" == "x86_64" ]]; then
|
||||
libavif_cmake_flags+=(-DAOM_TARGET_CPU=generic)
|
||||
else
|
||||
libavif_cmake_flags+=(
|
||||
-DAVIF_CODEC_AOM_DECODE=OFF \
|
||||
-DAVIF_CODEC_DAV1D=LOCAL
|
||||
)
|
||||
fi
|
||||
|
||||
local out_dir=$(fetch_unpack https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$LIBAVIF_VERSION.tar.gz libavif-$LIBAVIF_VERSION.tar.gz)
|
||||
|
||||
# CONFIG_AV1_HIGHBITDEPTH=0 is a flag for libaom (included as a subproject
|
||||
# of libavif) that disables support for encoding high bit depth images.
|
||||
(cd $out_dir \
|
||||
&& cmake \
|
||||
-DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX \
|
||||
-DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib \
|
||||
-DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib \
|
||||
-DBUILD_SHARED_LIBS=$build_shared \
|
||||
-DAVIF_LIBSHARPYUV=LOCAL \
|
||||
-DAVIF_LIBYUV=LOCAL \
|
||||
-DAVIF_CODEC_AOM=LOCAL \
|
||||
-DCONFIG_AV1_HIGHBITDEPTH=0 \
|
||||
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=$lto \
|
||||
-DCMAKE_C_VISIBILITY_PRESET=hidden \
|
||||
-DCMAKE_CXX_VISIBILITY_PRESET=hidden \
|
||||
-DCMAKE_BUILD_TYPE=MinSizeRel \
|
||||
"${libavif_cmake_flags[@]}" \
|
||||
$HOST_CMAKE_FLAGS . )
|
||||
|
||||
if [[ -n "$IOS_SDK" ]]; then
|
||||
# libavif's CMake configuration generates a meson cross file... but it
|
||||
# doesn't work for iOS cross-compilation. Copy in Pillow-generated
|
||||
# meson-cross config to replace the cmake-generated version.
|
||||
cp $WORKDIR/meson-cross.txt $out_dir/crossfile-apple.meson
|
||||
fi
|
||||
|
||||
(cd $out_dir && make -j4 install)
|
||||
|
||||
touch libavif-stamp
|
||||
}
|
||||
|
||||
function build_zstd {
|
||||
if [ -e zstd-stamp ]; then return; fi
|
||||
local out_dir=$(fetch_unpack https://github.com/facebook/zstd/releases/download/v$ZSTD_VERSION/zstd-$ZSTD_VERSION.tar.gz)
|
||||
(cd $out_dir \
|
||||
&& make -j4 install)
|
||||
touch zstd-stamp
|
||||
}
|
||||
|
||||
function build {
|
||||
if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then
|
||||
sudo chown -R runner /usr/local
|
||||
fi
|
||||
build_xz
|
||||
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
|
||||
if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
|
||||
yum remove -y zlib-devel
|
||||
fi
|
||||
if [[ -n "$IS_MACOS" ]]; then
|
||||
CFLAGS="$CFLAGS -headerpad_max_install_names" build_zlib_ng
|
||||
else
|
||||
build_zlib_ng
|
||||
fi
|
||||
build_new_zlib
|
||||
|
||||
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
|
||||
if [[ -n "$IS_MACOS" ]]; then
|
||||
build_simple xorgproto 2025.1 https://www.x.org/pub/individual/proto
|
||||
build_simple libXau 1.0.12 https://www.x.org/pub/individual/lib
|
||||
if [ -n "$IS_MACOS" ]; then
|
||||
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 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
|
||||
sed "s/\${pc_sysrootdir\}//" $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
|
||||
sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc
|
||||
fi
|
||||
build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib
|
||||
|
||||
build_libjpeg_turbo
|
||||
if [[ -n "$IS_MACOS" ]]; then
|
||||
# Custom tiff build to include jpeg; by default, configure won't include
|
||||
# headers/libs in the custom macOS/iOS 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_zstd
|
||||
build_tiff
|
||||
fi
|
||||
|
||||
build_libavif
|
||||
build_tiff
|
||||
build_libpng
|
||||
build_lcms2
|
||||
build_openjpeg
|
||||
if [ -f /usr/local/lib64/libopenjp2.so ]; then
|
||||
cp /usr/local/lib64/libopenjp2.so /usr/local/lib
|
||||
fi
|
||||
|
||||
webp_cflags="-O3 -DNDEBUG"
|
||||
ORIGINAL_CFLAGS=$CFLAGS
|
||||
CFLAGS="$CFLAGS -O3 -DNDEBUG"
|
||||
if [[ -n "$IS_MACOS" ]]; then
|
||||
webp_cflags="$webp_cflags -Wl,-headerpad_max_install_names"
|
||||
CFLAGS="$CFLAGS -Wl,-headerpad_max_install_names"
|
||||
fi
|
||||
webp_ldflags=""
|
||||
if [[ -n "$IOS_SDK" ]]; then
|
||||
webp_ldflags="$webp_ldflags -llzma -lz"
|
||||
fi
|
||||
CFLAGS="$CFLAGS $webp_cflags" LDFLAGS="$LDFLAGS $webp_ldflags" build_simple libwebp $LIBWEBP_VERSION \
|
||||
https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \
|
||||
--enable-libwebpmux --enable-libwebpdemux
|
||||
build_libwebp
|
||||
CFLAGS=$ORIGINAL_CFLAGS
|
||||
|
||||
build_brotli
|
||||
|
||||
if [[ -n "$IS_MACOS" ]]; then
|
||||
if [ -n "$IS_MACOS" ]; then
|
||||
# Custom freetype build
|
||||
build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
|
||||
else
|
||||
build_freetype
|
||||
fi
|
||||
|
||||
if [[ -z "$IOS_SDK" ]]; then
|
||||
# On iOS, there's no vendor-provided raqm, and we can't ship it due to
|
||||
# licensing, so there's no point building harfbuzz.
|
||||
build_harfbuzz
|
||||
fi
|
||||
build_harfbuzz
|
||||
}
|
||||
|
||||
function create_meson_cross_config {
|
||||
cat << EOF > $WORKDIR/meson-cross.txt
|
||||
[binaries]
|
||||
pkg-config = '$BUILD_PREFIX/bin/pkg-config'
|
||||
cmake = '$(which cmake)'
|
||||
c = '$CC'
|
||||
cpp = '$CXX'
|
||||
strip = '$STRIP'
|
||||
|
||||
[built-in options]
|
||||
c_args = '$CFLAGS -I$BUILD_PREFIX/include'
|
||||
cpp_args = '$CXXFLAGS -I$BUILD_PREFIX/include'
|
||||
c_link_args = '$CFLAGS -L$BUILD_PREFIX/lib'
|
||||
cpp_link_args = '$CFLAGS -L$BUILD_PREFIX/lib'
|
||||
|
||||
[host_machine]
|
||||
system = 'darwin'
|
||||
subsystem = 'ios'
|
||||
kernel = 'xnu'
|
||||
cpu_family = '$(uname -m)'
|
||||
cpu = '$(uname -m)'
|
||||
endian = 'little'
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# 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
|
||||
# Runs in the root directory of this repository.
|
||||
if [[ ! -d $WORKDIR/pillow-depends-main ]]; then
|
||||
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
|
||||
curl -fsSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
|
||||
untar pillow-depends-main.zip
|
||||
|
||||
if [[ -n "$IS_MACOS" ]]; then
|
||||
# Ensure the basic structure of the build prefix directory exists.
|
||||
mkdir -p "$BUILD_PREFIX/bin"
|
||||
mkdir -p "$BUILD_PREFIX/lib"
|
||||
# libtiff and libxcb cause a conflict with building libtiff and libxcb
|
||||
# libxau and libxdmcp cause an issue on macOS < 11
|
||||
# remove cairo to fix building harfbuzz on arm64
|
||||
# remove lcms2 and libpng to fix building openjpeg on arm64
|
||||
# remove jpeg-turbo to avoid inclusion on arm64
|
||||
# remove webp and zstd to avoid inclusion on x86_64
|
||||
# curl from brew requires zstd, use system curl
|
||||
brew remove --ignore-dependencies libpng libtiff libxcb libxau libxdmcp curl cairo lcms2 zstd
|
||||
if [[ "$CIBW_ARCHS" == "arm64" ]]; then
|
||||
brew remove --ignore-dependencies jpeg-turbo
|
||||
else
|
||||
brew remove --ignore-dependencies webp
|
||||
fi
|
||||
|
||||
# Ensure pkg-config is available. This is done *before* setting CC, CFLAGS
|
||||
# etc. to ensure that the build is *always* a macOS build, even when building
|
||||
# for iOS.
|
||||
build_pkg_config
|
||||
|
||||
# Ensure cmake is available, and that the default prefix used by CMake is
|
||||
# the build prefix
|
||||
python3 -m pip install cmake
|
||||
export CMAKE_PREFIX_PATH=$BUILD_PREFIX
|
||||
|
||||
if [[ -n "$IOS_SDK" ]]; then
|
||||
export AR="$(xcrun --find --sdk $IOS_SDK ar)"
|
||||
export CPP="$(xcrun --find --sdk $IOS_SDK clang) -E"
|
||||
export CC=$(xcrun --find --sdk $IOS_SDK clang)
|
||||
export CXX=$(xcrun --find --sdk $IOS_SDK clang++)
|
||||
export LD=$(xcrun --find --sdk $IOS_SDK ld)
|
||||
export STRIP=$(xcrun --find --sdk $IOS_SDK strip)
|
||||
|
||||
CPPFLAGS="$CPPFLAGS --sysroot=$IOS_SDK_PATH"
|
||||
CFLAGS="-target $IOS_HOST_TRIPLE --sysroot=$IOS_SDK_PATH -mios-version-min=$IPHONEOS_DEPLOYMENT_TARGET"
|
||||
CXXFLAGS="-target $IOS_HOST_TRIPLE --sysroot=$IOS_SDK_PATH -mios-version-min=$IPHONEOS_DEPLOYMENT_TARGET"
|
||||
|
||||
# Having IPHONEOS_DEPLOYMENT_TARGET in the environment causes problems
|
||||
# with some cross-building toolchains, because it introduces implicit
|
||||
# behavior into clang.
|
||||
unset IPHONEOS_DEPLOYMENT_TARGET
|
||||
|
||||
# Now that we know CC etc., we can create a meson cross-configuration file
|
||||
create_meson_cross_config
|
||||
fi
|
||||
brew install pkg-config
|
||||
fi
|
||||
|
||||
wrap_wheel_builder build
|
||||
|
||||
# A safety catch for iOS. iOS can't use dynamic libraries, but clang will prefer
|
||||
# to link dynamic libraries to static libraries. The only way to reliably
|
||||
# prevent this is to not have dynamic libraries available in the first place.
|
||||
# The build process *shouldn't* generate any dylibs... but just in case, purge
|
||||
# any dylibs that *have* been installed into the build prefix directory.
|
||||
if [[ -n "$IOS_SDK" ]]; then
|
||||
find "$BUILD_PREFIX" -name "*.dylib" -exec rm -rf {} \;
|
||||
fi
|
||||
|
||||
# Return to the project root to finish the build
|
||||
popd > /dev/null
|
||||
|
||||
# Append licenses
|
||||
for filename in wheels/dependency_licenses/*; do
|
||||
echo -e "\n\n----\n\n$(basename $filename | cut -f 1 -d '.')\n" | cat >> LICENSE
|
||||
|
||||
14
.github/workflows/wheels-test.ps1
vendored
@ -9,18 +9,14 @@ if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") {
|
||||
C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null
|
||||
}
|
||||
$env:path += ";$pillow\winbuild\build\bin\"
|
||||
if (Test-Path $venv\Scripts\pypy.exe) {
|
||||
$python = "pypy.exe"
|
||||
} else {
|
||||
$python = "python.exe"
|
||||
}
|
||||
& "$venv\Scripts\activate.ps1"
|
||||
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
|
||||
cd $pillow
|
||||
& $venv\Scripts\$python -VV
|
||||
& python -VV
|
||||
if (!$?) { exit $LASTEXITCODE }
|
||||
& $venv\Scripts\$python selftest.py
|
||||
& python selftest.py
|
||||
if (!$?) { exit $LASTEXITCODE }
|
||||
& $venv\Scripts\$python -m pytest -vv -x checks\check_wheel.py
|
||||
& python -m pytest -vx Tests\check_wheel.py
|
||||
if (!$?) { exit $LASTEXITCODE }
|
||||
& $venv\Scripts\$python -m pytest -vv -x Tests
|
||||
& python -m pytest -vx Tests
|
||||
if (!$?) { exit $LASTEXITCODE }
|
||||
|
||||
26
.github/workflows/wheels-test.sh
vendored
@ -1,30 +1,20 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Ensure fribidi is installed by the system.
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# If Homebrew is on the path during the build, it may leak into the wheels.
|
||||
# However, we *do* need Homebrew to provide a copy of fribidi for
|
||||
# testing purposes so that we can verify the fribidi shim works as expected.
|
||||
if [[ "$(uname -m)" == "x86_64" ]]; then
|
||||
HOMEBREW_PREFIX=/usr/local
|
||||
else
|
||||
HOMEBREW_PREFIX=/opt/homebrew
|
||||
brew install fribidi
|
||||
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
||||
if [ -f /opt/homebrew/lib/libfribidi.dylib ]; then
|
||||
sudo cp /opt/homebrew/lib/libfribidi.dylib /usr/local/lib
|
||||
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
|
||||
apk add curl fribidi
|
||||
else
|
||||
yum install -y fribidi
|
||||
fi
|
||||
|
||||
python3 -m pip install numpy
|
||||
|
||||
if [ ! -d "test-images-main" ]; then
|
||||
curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
|
||||
unzip pillow-test-images.zip
|
||||
@ -33,5 +23,5 @@ fi
|
||||
|
||||
# Runs tests
|
||||
python3 selftest.py
|
||||
python3 -m pytest -vv -x checks/check_wheel.py
|
||||
python3 -m pytest -vv -x
|
||||
python3 -m pytest Tests/check_wheel.py
|
||||
python3 -m pytest
|
||||
|
||||
313
.github/workflows/wheels.yml
vendored
@ -10,14 +10,9 @@ on:
|
||||
# │ │ │ │ │
|
||||
- cron: "42 1 * * 0,3"
|
||||
push:
|
||||
paths: &paths
|
||||
paths:
|
||||
- ".ci/requirements-cibw.txt"
|
||||
- ".ci/requirements-sbom.txt"
|
||||
- ".github/compare-dist-sizes.py"
|
||||
- ".github/dependencies.json"
|
||||
- ".github/generate-sbom.py"
|
||||
- ".github/workflows/wheels*"
|
||||
- "pyproject.toml"
|
||||
- ".github/workflows/wheel*"
|
||||
- "setup.py"
|
||||
- "wheels/*"
|
||||
- "winbuild/build_prepare.py"
|
||||
@ -25,7 +20,13 @@ on:
|
||||
tags:
|
||||
- "*"
|
||||
pull_request:
|
||||
paths: *paths
|
||||
paths:
|
||||
- ".ci/requirements-cibw.txt"
|
||||
- ".github/workflows/wheel*"
|
||||
- "setup.py"
|
||||
- "wheels/*"
|
||||
- "winbuild/build_prepare.py"
|
||||
- "winbuild/fribidi.cmake"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -36,12 +37,65 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
EXPECTED_DISTS: 66
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
build-native-wheels:
|
||||
if: github.event_name != 'schedule' || github.event.repository.fork == false
|
||||
build-1-QEMU-emulated-wheels:
|
||||
if: github.event_name != 'schedule'
|
||||
name: aarch64 ${{ matrix.python-version }} ${{ matrix.spec }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version:
|
||||
- pp310
|
||||
- cp3{9,10,11}
|
||||
- cp3{12,13}
|
||||
spec:
|
||||
- manylinux2014
|
||||
- manylinux_2_28
|
||||
- musllinux
|
||||
exclude:
|
||||
- { python-version: pp310, spec: musllinux }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
# https://github.com/docker/setup-qemu-action
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Install cibuildwheel
|
||||
run: |
|
||||
python3 -m pip install -r .ci/requirements-cibw.txt
|
||||
|
||||
- name: Build wheels
|
||||
run: |
|
||||
python3 -m cibuildwheel --output-dir wheelhouse
|
||||
env:
|
||||
# Build only the currently selected Linux architecture (so we can
|
||||
# parallelise for speed).
|
||||
CIBW_ARCHS: "aarch64"
|
||||
# Likewise, select only one Python version per job to speed this up.
|
||||
CIBW_BUILD: "${{ matrix.python-version }}-${{ matrix.spec == 'musllinux' && 'musllinux' || 'manylinux' }}*"
|
||||
CIBW_PRERELEASE_PYTHONS: True
|
||||
# Extra options for manylinux.
|
||||
CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.spec }}
|
||||
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.spec }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist-qemu-${{ matrix.python-version }}-${{ matrix.spec }}
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
build-2-native-wheels:
|
||||
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
@ -49,67 +103,38 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- name: "macOS 10.10 x86_64"
|
||||
platform: macos
|
||||
os: macos-26-intel
|
||||
os: macos-13
|
||||
cibw_arch: x86_64
|
||||
build: "cp3{10,11}*"
|
||||
build: "cp3{9,10,11}*"
|
||||
macosx_deployment_target: "10.10"
|
||||
- name: "macOS 10.13 x86_64"
|
||||
platform: macos
|
||||
os: macos-26-intel
|
||||
os: macos-13
|
||||
cibw_arch: x86_64
|
||||
build: "cp3{12,13}*"
|
||||
macosx_deployment_target: "10.13"
|
||||
- name: "macOS 10.15 x86_64"
|
||||
platform: macos
|
||||
os: macos-26-intel
|
||||
os: macos-13
|
||||
cibw_arch: x86_64
|
||||
build: "{cp314,pp3}*"
|
||||
build: "pp310*"
|
||||
macosx_deployment_target: "10.15"
|
||||
- name: "macOS arm64"
|
||||
platform: macos
|
||||
os: macos-latest
|
||||
cibw_arch: arm64
|
||||
macosx_deployment_target: "11.0"
|
||||
- name: "manylinux2014 and musllinux x86_64"
|
||||
os: ubuntu-latest
|
||||
cibw_arch: x86_64
|
||||
- name: "manylinux_2_28 x86_64"
|
||||
platform: linux
|
||||
os: ubuntu-latest
|
||||
cibw_arch: x86_64
|
||||
build: "*manylinux*"
|
||||
- name: "musllinux x86_64"
|
||||
platform: linux
|
||||
os: ubuntu-latest
|
||||
cibw_arch: x86_64
|
||||
build: "*musllinux*"
|
||||
- name: "manylinux_2_28 aarch64"
|
||||
platform: linux
|
||||
os: ubuntu-24.04-arm
|
||||
cibw_arch: aarch64
|
||||
build: "*manylinux*"
|
||||
- name: "musllinux aarch64"
|
||||
platform: linux
|
||||
os: ubuntu-24.04-arm
|
||||
cibw_arch: aarch64
|
||||
build: "*musllinux*"
|
||||
- name: "iOS arm64 device"
|
||||
platform: ios
|
||||
os: macos-latest
|
||||
cibw_arch: arm64_iphoneos
|
||||
- name: "iOS arm64 simulator"
|
||||
platform: ios
|
||||
os: macos-latest
|
||||
cibw_arch: arm64_iphonesimulator
|
||||
- name: "iOS x86_64 simulator"
|
||||
platform: ios
|
||||
os: macos-26-intel
|
||||
cibw_arch: x86_64_iphonesimulator
|
||||
manylinux: "manylinux_2_28"
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
@ -121,44 +146,40 @@ jobs:
|
||||
run: |
|
||||
python3 -m cibuildwheel --output-dir wheelhouse
|
||||
env:
|
||||
CIBW_PLATFORM: ${{ matrix.platform }}
|
||||
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
||||
CIBW_BUILD: ${{ matrix.build }}
|
||||
CIBW_ENABLE: cpython-prerelease pypy
|
||||
CIBW_FREE_THREADED_SUPPORT: True
|
||||
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
CIBW_PRERELEASE_PYTHONS: True
|
||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
||||
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist-${{ matrix.name }}
|
||||
name: dist-${{ matrix.os }}${{ matrix.macosx_deployment_target && format('-{0}', matrix.macosx_deployment_target) }}-${{ matrix.cibw_arch }}${{ matrix.manylinux && format('-{0}', matrix.manylinux) }}
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
windows:
|
||||
if: github.event_name != 'schedule' || github.event.repository.fork == false
|
||||
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
|
||||
name: Windows ${{ matrix.cibw_arch }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- cibw_arch: x86
|
||||
os: windows-latest
|
||||
- cibw_arch: AMD64
|
||||
os: windows-latest
|
||||
- cibw_arch: ARM64
|
||||
os: windows-11-arm
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout extra test images
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: python-pillow/test-images
|
||||
path: Tests\test-images
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
@ -179,23 +200,30 @@ jobs:
|
||||
|
||||
- name: Build wheels
|
||||
run: |
|
||||
for f in winbuild/build/license/*; do
|
||||
name=$(basename "${f%.*}")
|
||||
# Skip FriBiDi license, it is not included in the wheel.
|
||||
[[ $name == fribidi* ]] && continue
|
||||
# Skip imagequant license, it is not included in the wheel.
|
||||
[[ $name == libimagequant* ]] && continue
|
||||
echo "" >> LICENSE
|
||||
echo "===== $name =====" >> LICENSE
|
||||
echo "" >> LICENSE
|
||||
cat "$f" >> LICENSE
|
||||
done
|
||||
cmd //c "winbuild\\build\\build_env.cmd && $pythonLocation\\python.exe -m cibuildwheel . --output-dir wheelhouse"
|
||||
setlocal EnableDelayedExpansion
|
||||
for %%f in (winbuild\build\license\*) do (
|
||||
set x=%%~nf
|
||||
rem Skip FriBiDi license, it is not included in the wheel.
|
||||
set fribidi=!x:~0,7!
|
||||
if NOT !fribidi!==fribidi (
|
||||
rem Skip imagequant license, it is not included in the wheel.
|
||||
set libimagequant=!x:~0,13!
|
||||
if NOT !libimagequant!==libimagequant (
|
||||
echo. >> LICENSE
|
||||
echo ===== %%~nf ===== >> LICENSE
|
||||
echo. >> LICENSE
|
||||
type %%f >> LICENSE
|
||||
)
|
||||
)
|
||||
)
|
||||
call winbuild\\build\\build_env.cmd
|
||||
%pythonLocation%\python.exe -m cibuildwheel . --output-dir wheelhouse
|
||||
env:
|
||||
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
||||
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
|
||||
CIBW_CACHE_PATH: "C:\\cibw"
|
||||
CIBW_ENABLE: cpython-prerelease pypy
|
||||
CIBW_FREE_THREADED_SUPPORT: True
|
||||
CIBW_PRERELEASE_PYTHONS: True
|
||||
CIBW_TEST_SKIP: "*-win_arm64"
|
||||
CIBW_TEST_COMMAND: 'docker run --rm
|
||||
-v {project}:C:\pillow
|
||||
@ -204,151 +232,60 @@ jobs:
|
||||
-e CI -e GITHUB_ACTIONS
|
||||
mcr.microsoft.com/windows/servercore:ltsc2022
|
||||
powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test'
|
||||
shell: bash
|
||||
shell: cmd
|
||||
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist-windows-${{ matrix.cibw_arch }}
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
- name: Upload fribidi.dll
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fribidi-windows-${{ matrix.cibw_arch }}
|
||||
path: winbuild\build\bin\fribidi*
|
||||
|
||||
sdist:
|
||||
if: github.event_name != 'schedule' || github.event.repository.fork == false
|
||||
if: github.event_name != 'schedule'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
cache: pip
|
||||
cache-dependency-path: "Makefile"
|
||||
|
||||
- run: make sdist
|
||||
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist-sdist
|
||||
path: dist/*.tar.gz
|
||||
|
||||
count-dists:
|
||||
needs: [build-native-wheels, windows, sdist]
|
||||
runs-on: ubuntu-latest
|
||||
name: Count dists
|
||||
steps:
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: dist-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- name: "What did we get?"
|
||||
run: |
|
||||
ls -alR
|
||||
echo "Number of dists, should be $EXPECTED_DISTS:"
|
||||
files=$(ls dist 2>/dev/null | wc -l)
|
||||
echo $files
|
||||
[ "$files" -eq $EXPECTED_DISTS ] || exit 1
|
||||
|
||||
compare-dist-sizes:
|
||||
needs: [build-native-wheels, windows, sdist]
|
||||
runs-on: ubuntu-latest
|
||||
name: Compare dist sizes vs PyPI
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
with:
|
||||
enable-cache: false
|
||||
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: dist-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
|
||||
- name: Compare dist sizes vs latest PyPI release
|
||||
run: uv run .github/compare-dist-sizes.py dist
|
||||
|
||||
scientific-python-nightly-wheels-publish:
|
||||
if: github.event.repository.fork == false && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
||||
needs: count-dists
|
||||
if: github.repository_owner == 'python-pillow' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
||||
needs: [build-2-native-wheels, windows]
|
||||
runs-on: ubuntu-latest
|
||||
name: Upload wheels to scientific-python-nightly-wheels
|
||||
environment:
|
||||
name: release-anaconda
|
||||
url: https://anaconda.org/channels/scientific-python-nightly-wheels/packages/pillow/overview
|
||||
steps:
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: dist-!(sdist)*
|
||||
pattern: dist-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- name: Upload wheels to scientific-python-nightly-wheels
|
||||
uses: scientific-python/upload-nightly-action@e76cfec8a4611fd02808a801b0ff5a7d7c1b2d99 # 0.6.4
|
||||
uses: scientific-python/upload-nightly-action@82396a2ed4269ba06c6b2988bb4fd568ef3c3d6b # 0.6.1
|
||||
with:
|
||||
artifacts_path: dist
|
||||
anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }}
|
||||
|
||||
sbom:
|
||||
if: github.event_name != 'schedule' || github.event.repository.fork == false
|
||||
runs-on: ubuntu-latest
|
||||
name: Generate SBOM
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Generate CycloneDX SBOM
|
||||
run: python3 .github/generate-sbom.py
|
||||
|
||||
- name: Upload SBOM as workflow artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: sbom
|
||||
path: "pillow-*.cdx.json"
|
||||
|
||||
- name: Validate SBOM
|
||||
run: |
|
||||
python3 -m pip install -r .ci/requirements-sbom.txt
|
||||
check-jsonschema --schemafile "https://raw.githubusercontent.com/CycloneDX/specification/1.7/schema/bom-1.7.schema.json" pillow-*.cdx.json
|
||||
|
||||
sbom-publish:
|
||||
if: |
|
||||
github.event.repository.fork == false
|
||||
&& github.event_name == 'push'
|
||||
&& startsWith(github.ref, 'refs/tags')
|
||||
needs: [count-dists, sbom]
|
||||
runs-on: ubuntu-latest
|
||||
name: Publish SBOM to GitHub release
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: sbom
|
||||
path: .
|
||||
|
||||
- name: Attach SBOM to GitHub release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh release upload "$GITHUB_REF_NAME" pillow-*.cdx.json
|
||||
|
||||
pypi-publish:
|
||||
if: github.event.repository.fork == false && github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||
needs: count-dists
|
||||
if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||
needs: [build-1-QEMU-emulated-wheels, build-2-native-wheels, windows, sdist]
|
||||
runs-on: ubuntu-latest
|
||||
name: Upload release to PyPI
|
||||
environment:
|
||||
@ -357,12 +294,12 @@ jobs:
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: dist-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
attestations: true
|
||||
|
||||
8
.gitignore
vendored
@ -19,7 +19,6 @@ lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheelhouse/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
@ -91,12 +90,5 @@ Tests/images/msp
|
||||
Tests/images/picins
|
||||
Tests/images/sunraster
|
||||
|
||||
# Test and dependency downloads
|
||||
pillow-depends-main.zip
|
||||
pillow-test-images.zip
|
||||
|
||||
# pyinstaller
|
||||
*.spec
|
||||
|
||||
# Generated SBOM
|
||||
pillow-*.cdx.json
|
||||
|
||||
@ -1,30 +1,30 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.15.12
|
||||
rev: v0.6.9
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
- id: ruff
|
||||
args: [--exit-non-zero-on-fix]
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 26.3.1
|
||||
rev: 24.8.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
rev: 1.9.4
|
||||
rev: 1.7.10
|
||||
hooks:
|
||||
- id: bandit
|
||||
args: [--severity-level=high]
|
||||
files: ^src/
|
||||
|
||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||
rev: v1.5.6
|
||||
rev: v1.5.5
|
||||
hooks:
|
||||
- id: remove-tabs
|
||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v22.1.4
|
||||
rev: v19.1.1
|
||||
hooks:
|
||||
- id: clang-format
|
||||
types: [c]
|
||||
@ -36,53 +36,44 @@ repos:
|
||||
- id: rst-backticks
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: check-case-conflict
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-shebang-scripts-are-executable
|
||||
- id: check-merge-conflict
|
||||
- id: check-json
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
args: [--allow-multiple-documents]
|
||||
- id: end-of-file-fixer
|
||||
exclude: ^Tests/images/
|
||||
- id: file-contents-sorter
|
||||
files: .github/workflows/Brewfile
|
||||
- id: trailing-whitespace
|
||||
exclude: ^\.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.37.2
|
||||
rev: 0.29.3
|
||||
hooks:
|
||||
- id: check-github-workflows
|
||||
- id: check-readthedocs
|
||||
- id: check-renovate
|
||||
|
||||
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
||||
rev: v1.24.1
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||
rev: v1.0.2
|
||||
rev: v1.0.0
|
||||
hooks:
|
||||
- id: sphinx-lint
|
||||
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: v2.21.1
|
||||
rev: 2.2.4
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.25
|
||||
rev: v0.20.2
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
additional_dependencies: [trove-classifiers>=2024.10.12]
|
||||
|
||||
- repo: https://github.com/tox-dev/tox-ini-fmt
|
||||
rev: 1.7.1
|
||||
rev: 1.4.1
|
||||
hooks:
|
||||
- id: tox-ini-fmt
|
||||
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
version: 2
|
||||
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
formats: [pdf]
|
||||
|
||||
build:
|
||||
|
||||
@ -2,13 +2,6 @@
|
||||
Changelog (Pillow)
|
||||
==================
|
||||
|
||||
11.1.0 and newer
|
||||
----------------
|
||||
|
||||
See GitHub Releases:
|
||||
|
||||
- https://github.com/python-pillow/Pillow/releases
|
||||
|
||||
11.0.0 (2024-10-15)
|
||||
-------------------
|
||||
|
||||
|
||||
4
LICENSE
@ -5,9 +5,9 @@ The Python Imaging Library (PIL) is
|
||||
|
||||
Pillow is the friendly PIL fork. It is
|
||||
|
||||
Copyright © 2010 by Jeffrey 'Alex' Clark and contributors
|
||||
Copyright © 2010-2024 by Jeffrey A. Clark and contributors
|
||||
|
||||
Like PIL, Pillow is licensed under the open source MIT-CMU License:
|
||||
Like PIL, Pillow is licensed under the open source HPND License:
|
||||
|
||||
By obtaining, using, and/or copying this software and/or its associated
|
||||
documentation, you agree that you have read, understood, and will comply
|
||||
|
||||
14
MANIFEST.in
@ -13,8 +13,6 @@ include LICENSE
|
||||
include Makefile
|
||||
include tox.ini
|
||||
graft Tests
|
||||
graft Tests/images
|
||||
graft checks
|
||||
graft src
|
||||
graft depends
|
||||
graft winbuild
|
||||
@ -22,25 +20,15 @@ graft docs
|
||||
graft _custom_build
|
||||
|
||||
# build/src control detritus
|
||||
exclude .appveyor.yml
|
||||
exclude .clang-format
|
||||
exclude .coveragerc
|
||||
exclude .editorconfig
|
||||
exclude .readthedocs.yml
|
||||
exclude codecov.yml
|
||||
exclude renovate.json
|
||||
exclude Tests/images/README.md
|
||||
exclude Tests/images/crash*.tif
|
||||
exclude Tests/images/string_dimension.tiff
|
||||
global-exclude .git*
|
||||
global-exclude *.pyc
|
||||
global-exclude *.so
|
||||
prune .ci
|
||||
prune wheels
|
||||
prune winbuild/build
|
||||
prune winbuild/depends
|
||||
prune Tests/errors
|
||||
prune Tests/images/jpeg2000
|
||||
prune Tests/images/msp
|
||||
prune Tests/images/picins
|
||||
prune Tests/images/sunraster
|
||||
prune Tests/test-images
|
||||
|
||||
23
Makefile
@ -23,10 +23,6 @@ doc html:
|
||||
htmlview:
|
||||
$(MAKE) -C docs htmlview
|
||||
|
||||
.PHONY: htmllive
|
||||
htmllive:
|
||||
$(MAKE) -C docs htmllive
|
||||
|
||||
.PHONY: doccheck
|
||||
doccheck:
|
||||
$(MAKE) doc
|
||||
@ -47,7 +43,6 @@ help:
|
||||
@echo " docserve run an HTTP server on the docs directory"
|
||||
@echo " html make HTML docs"
|
||||
@echo " htmlview open the index page built by the html target in your browser"
|
||||
@echo " htmllive rebuild and reload HTML files in your browser"
|
||||
@echo " install make and install"
|
||||
@echo " install-coverage make and install with C coverage"
|
||||
@echo " lint run the lint checks"
|
||||
@ -75,7 +70,7 @@ debug:
|
||||
|
||||
.PHONY: release-test
|
||||
release-test:
|
||||
python3 checks/check_release_notes.py
|
||||
python3 Tests/check_release_notes.py
|
||||
python3 -m pip install -e .[tests]
|
||||
python3 selftest.py
|
||||
python3 -m pytest Tests
|
||||
@ -97,27 +92,13 @@ test:
|
||||
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
|
||||
python3 -m pytest -qq
|
||||
|
||||
.PHONY: test-p
|
||||
test-p:
|
||||
python3 -c "import xdist" > /dev/null 2>&1 || python3 -m pip install pytest-xdist
|
||||
python3 -m pytest -qq -n auto
|
||||
|
||||
|
||||
.PHONY: valgrind
|
||||
valgrind:
|
||||
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
|
||||
PILLOW_VALGRIND_TEST=true PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
|
||||
PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
|
||||
--log-file=/tmp/valgrind-output \
|
||||
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
|
||||
|
||||
.PHONY: valgrind-leak
|
||||
valgrind-leak:
|
||||
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
|
||||
PILLOW_VALGRIND_TEST=true PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp \
|
||||
--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite \
|
||||
--log-file=/tmp/valgrind-output \
|
||||
python3 -m pytest -vv --valgrind --valgrind-log=/tmp/valgrind-output
|
||||
|
||||
.PHONY: readme
|
||||
readme:
|
||||
python3 -c "import markdown2" > /dev/null 2>&1 || python3 -m pip install markdown2
|
||||
|
||||
26
README.md
@ -6,13 +6,11 @@
|
||||
|
||||
## Python Imaging Library (Fork)
|
||||
|
||||
Pillow is the friendly PIL fork by [Jeffrey 'Alex' Clark and
|
||||
Pillow is the friendly PIL fork by [Jeffrey A. Clark and
|
||||
contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
|
||||
PIL is the Python Imaging Library by Fredrik Lundh and contributors.
|
||||
Development is supported by:
|
||||
- [Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise) (since 2018)
|
||||
- [Thanks.dev](https://thanks.dev) (since 2023)
|
||||
- [GitHub Sponsors](https://github.com/sponsors/python-pillow) (since 2026)
|
||||
As of 2019, Pillow development is
|
||||
[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
@ -38,9 +36,15 @@ Development is supported by:
|
||||
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-mingw.yml"><img
|
||||
alt="GitHub Actions build status (Test MinGW)"
|
||||
src="https://github.com/python-pillow/Pillow/workflows/Test%20MinGW/badge.svg"></a>
|
||||
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-cygwin.yml"><img
|
||||
alt="GitHub Actions build status (Test Cygwin)"
|
||||
src="https://github.com/python-pillow/Pillow/workflows/Test%20Cygwin/badge.svg"></a>
|
||||
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-docker.yml"><img
|
||||
alt="GitHub Actions build status (Test Docker)"
|
||||
src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
|
||||
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
|
||||
alt="AppVeyor CI build status (Windows)"
|
||||
src="https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build"></a>
|
||||
<a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
|
||||
alt="GitHub Actions build status (Wheels)"
|
||||
src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
|
||||
@ -94,7 +98,7 @@ This library provides extensive file format support, an efficient internal repre
|
||||
|
||||
The core image library is designed for fast access to data stored in a few basic pixel formats. It should provide a solid foundation for a general image processing tool.
|
||||
|
||||
## More information
|
||||
## More Information
|
||||
|
||||
- [Documentation](https://pillow.readthedocs.io/)
|
||||
- [Installation](https://pillow.readthedocs.io/en/latest/installation/basic-installation.html)
|
||||
@ -103,13 +107,9 @@ The core image library is designed for fast access to data stored in a few basic
|
||||
- [Issues](https://github.com/python-pillow/Pillow/issues)
|
||||
- [Pull requests](https://github.com/python-pillow/Pillow/pulls)
|
||||
- [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html)
|
||||
- [Changelog](https://github.com/python-pillow/Pillow/releases)
|
||||
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
|
||||
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
|
||||
|
||||
## Report a vulnerability
|
||||
## Report a Vulnerability
|
||||
|
||||
To report sensitive vulnerability information, report it [privately on GitHub](https://github.com/python-pillow/Pillow/security/advisories/new).
|
||||
|
||||
If you cannot use GitHub, use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
|
||||
|
||||
DO NOT report sensitive vulnerability information in public.
|
||||
To report a security vulnerability, please follow the procedure described in the [Tidelift security policy](https://tidelift.com/docs/security).
|
||||
|
||||
41
RELEASING.md
@ -1,26 +1,46 @@
|
||||
# Release checklist
|
||||
# Release Checklist
|
||||
|
||||
See https://pillow.readthedocs.io/en/stable/releasenotes/versioning.html for
|
||||
information about how the version numbers line up with releases.
|
||||
|
||||
## Main release
|
||||
## Main Release
|
||||
|
||||
Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||
|
||||
* [ ] Create a new issue and select the "Maintainers only: Release" template.
|
||||
|
||||
## Point release
|
||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
||||
* [ ] Develop and prepare release 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.
|
||||
* [ ] 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.
|
||||
* [ ] Create branch and tag for release e.g.:
|
||||
```bash
|
||||
git branch 5.2.x
|
||||
git tag 5.2.0
|
||||
git push --tags
|
||||
```
|
||||
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
||||
has passed, including the "Upload release to PyPI" job. This will have been triggered
|
||||
by the new tag.
|
||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases).
|
||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/),
|
||||
increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
|
||||
```bash
|
||||
git push --all
|
||||
```
|
||||
## Point Release
|
||||
|
||||
Released as needed for security, installation or critical bug fixes.
|
||||
|
||||
* [ ] Make necessary changes in `main` branch.
|
||||
* [ ] Update `CHANGES.rst`.
|
||||
* [ ] Check out release branch e.g.:
|
||||
```bash
|
||||
git checkout -t remotes/origin/5.2.x
|
||||
```
|
||||
* [ ] Cherry pick individual commits from `main` branch to release branch e.g. `5.2.x`, then `git push`.
|
||||
* [ ] If this is a security fix: amend commits to include the CVE identifier in the commit message.
|
||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) to confirm passing tests in release branch e.g. `5.2.x`.
|
||||
* [ ] 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 release branch e.g. `5.2.x`.
|
||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||
* [ ] Run pre-release check via `make release-test`.
|
||||
* [ ] Create tag for release e.g.:
|
||||
@ -39,9 +59,8 @@ Released as needed for security, installation or critical bug fixes.
|
||||
```bash
|
||||
git push
|
||||
```
|
||||
* [ ] If this is a security fix: publish the [GitHub Security Advisory or Advisories](https://github.com/python-pillow/Pillow/security/advisories).
|
||||
|
||||
## Embargoed release
|
||||
## Embargoed Release
|
||||
|
||||
Released as needed privately to individual vendors for critical security-related bug fixes.
|
||||
|
||||
@ -65,7 +84,7 @@ Released as needed privately to individual vendors for critical security-related
|
||||
git push origin 2.5.x
|
||||
```
|
||||
|
||||
## Publicize release
|
||||
## Publicize Release
|
||||
|
||||
* [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321
|
||||
|
||||
@ -73,7 +92,7 @@ Released as needed privately to individual vendors for critical security-related
|
||||
|
||||
* [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes
|
||||
|
||||
## Docker images
|
||||
## Docker Images
|
||||
|
||||
* [ ] Update Pillow in the Docker Images repository
|
||||
```bash
|
||||
|
||||
1
checks/32bit_segfault_check.py → Tests/32bit_segfault_check.py
Normal file → Executable file
@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
@ -1,4 +1,4 @@
|
||||
Pillow tests
|
||||
Pillow Tests
|
||||
============
|
||||
|
||||
Test scripts are named ``test_xxx.py``. Helper classes and functions can be found in ``helper.py``.
|
||||
|
||||
11
checks/check_imaging_leaks.py → Tests/check_imaging_leaks.py
Normal file → Executable file
@ -1,19 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
from typing import Any, Callable
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from .helper import is_win32
|
||||
|
||||
min_iterations = 100
|
||||
max_iterations = 10000
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
sys.platform.startswith("win32"), reason="requires Unix or macOS"
|
||||
)
|
||||
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
|
||||
|
||||
|
||||
def _get_mem_usage() -> float:
|
||||
@ -1,11 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, features
|
||||
from PIL import Image
|
||||
|
||||
from .helper import is_win32, skip_unless_feature
|
||||
|
||||
# Limits for testing the leak
|
||||
mem_limit = 1024 * 1048576
|
||||
@ -14,10 +15,8 @@ iterations = int((mem_limit / stack_size) * 2)
|
||||
test_file = "Tests/images/rgb_trns_ycbc.jp2"
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.skipif(
|
||||
sys.platform.startswith("win32"), reason="requires Unix or macOS"
|
||||
),
|
||||
pytest.mark.skipif(not features.check("jpg_2000"), reason="jpg_2000 not available"),
|
||||
pytest.mark.skipif(is_win32(), reason="requires Unix or macOS"),
|
||||
skip_unless_feature("jpg_2000"),
|
||||
]
|
||||
|
||||
|
||||
@ -9,6 +9,6 @@ from PIL import Image
|
||||
|
||||
def test_j2k_overflow(tmp_path: Path) -> None:
|
||||
im = Image.new("RGBA", (1024, 131584))
|
||||
target = tmp_path / "temp.jpc"
|
||||
target = str(tmp_path / "temp.jpc")
|
||||
with pytest.raises(OSError):
|
||||
im.save(target)
|
||||
@ -1,11 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
from .helper import hopper, is_win32
|
||||
|
||||
iterations = 5000
|
||||
|
||||
@ -14,14 +13,12 @@ iterations = 5000
|
||||
When run on a system without the jpeg leak fixes,
|
||||
the valgrind runs look like this.
|
||||
|
||||
valgrind --tool=massif python test-installed.py -s -v checks/check_jpeg_leaks.py
|
||||
valgrind --tool=massif python test-installed.py -s -v Tests/check_jpeg_leaks.py
|
||||
|
||||
"""
|
||||
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
sys.platform.startswith("win32"), reason="requires Unix or macOS"
|
||||
)
|
||||
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
|
||||
|
||||
"""
|
||||
pre patch:
|
||||
@ -115,10 +112,10 @@ standard_chrominance_qtable = (
|
||||
),
|
||||
)
|
||||
def test_qtables_leak(qtables: tuple[tuple[int, ...]] | list[tuple[int, ...]]) -> None:
|
||||
with Image.open("Tests/images/hopper.ppm") as im:
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG", qtables=qtables)
|
||||
im = hopper("RGB")
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG", qtables=qtables)
|
||||
|
||||
|
||||
def test_exif_leak() -> None:
|
||||
@ -176,12 +173,12 @@ def test_exif_leak() -> None:
|
||||
0 +----------------------------------------------------------------------->Gi
|
||||
0 11.33
|
||||
"""
|
||||
im = hopper("RGB")
|
||||
exif = b"12345678" * 4096
|
||||
|
||||
with Image.open("Tests/images/hopper.ppm") as im:
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG", exif=exif)
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG", exif=exif)
|
||||
|
||||
|
||||
def test_base_save() -> None:
|
||||
@ -210,7 +207,8 @@ def test_base_save() -> None:
|
||||
| :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
|
||||
0 +----------------------------------------------------------------------->Gi
|
||||
0 7.882"""
|
||||
with Image.open("Tests/images/hopper.ppm") as im:
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG")
|
||||
im = hopper("RGB")
|
||||
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG")
|
||||
@ -32,7 +32,7 @@ pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit sy
|
||||
|
||||
|
||||
def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
|
||||
f = tmp_path / "temp.png"
|
||||
f = str(tmp_path / "temp.png")
|
||||
im = Image.new("L", (xdim, ydim), 0)
|
||||
im.save(f)
|
||||
|
||||
@ -28,7 +28,7 @@ pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit sy
|
||||
def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
|
||||
dtype = np.uint8
|
||||
a = np.zeros((xdim, ydim), dtype=dtype)
|
||||
f = tmp_path / "temp.png"
|
||||
f = str(tmp_path / "temp.png")
|
||||
im = Image.fromarray(a, "L")
|
||||
im.save(f)
|
||||
|
||||
@ -3,25 +3,26 @@ from __future__ import annotations
|
||||
import zlib
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageFile, PngImagePlugin
|
||||
|
||||
TEST_FILE = "Tests/images/png_decompression_dos.png"
|
||||
|
||||
|
||||
def test_ignore_dos_text(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
|
||||
def test_ignore_dos_text() -> None:
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||
|
||||
with Image.open(TEST_FILE) as im:
|
||||
try:
|
||||
im = Image.open(TEST_FILE)
|
||||
im.load()
|
||||
finally:
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||
|
||||
assert isinstance(im, PngImagePlugin.PngImageFile)
|
||||
for s in im.text.values():
|
||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||
assert isinstance(im, PngImagePlugin.PngImageFile)
|
||||
for s in im.text.values():
|
||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||
|
||||
for s in im.info.values():
|
||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||
for s in im.info.values():
|
||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||
|
||||
|
||||
def test_dos_text() -> None:
|
||||
43
Tests/check_wheel.py
Normal file
@ -0,0 +1,43 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from PIL import features
|
||||
|
||||
|
||||
def test_wheel_modules() -> None:
|
||||
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
|
||||
|
||||
# tkinter is not available in cibuildwheel installed CPython on Windows
|
||||
try:
|
||||
import tkinter
|
||||
|
||||
assert tkinter
|
||||
except ImportError:
|
||||
expected_modules.remove("tkinter")
|
||||
|
||||
assert set(features.get_supported_modules()) == expected_modules
|
||||
|
||||
|
||||
def test_wheel_codecs() -> None:
|
||||
expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"}
|
||||
|
||||
assert set(features.get_supported_codecs()) == expected_codecs
|
||||
|
||||
|
||||
def test_wheel_features() -> None:
|
||||
expected_features = {
|
||||
"webp_anim",
|
||||
"webp_mux",
|
||||
"transp_webp",
|
||||
"raqm",
|
||||
"fribidi",
|
||||
"harfbuzz",
|
||||
"libjpeg_turbo",
|
||||
"xcb",
|
||||
}
|
||||
|
||||
if sys.platform == "win32":
|
||||
expected_features.remove("xcb")
|
||||
|
||||
assert set(features.get_supported_features()) == expected_features
|
||||
@ -1,17 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import sys
|
||||
import sysconfig
|
||||
|
||||
import pytest
|
||||
|
||||
FREE_THREADED_BUILD = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
|
||||
|
||||
gil_enabled_at_start = True
|
||||
if FREE_THREADED_BUILD:
|
||||
gil_enabled_at_start = sys._is_gil_enabled() # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def pytest_report_header(config: pytest.Config) -> str:
|
||||
try:
|
||||
@ -24,25 +16,6 @@ def pytest_report_header(config: pytest.Config) -> str:
|
||||
return f"pytest_report_header failed: {e}"
|
||||
|
||||
|
||||
def pytest_terminal_summary(terminalreporter: pytest.TerminalReporter) -> None:
|
||||
if (
|
||||
FREE_THREADED_BUILD
|
||||
and not gil_enabled_at_start
|
||||
and sys._is_gil_enabled() # type: ignore[attr-defined]
|
||||
):
|
||||
tr = terminalreporter
|
||||
tr.ensure_newline()
|
||||
tr.section("GIL re-enabled", red=True, bold=True)
|
||||
tr.line("The GIL was re-enabled at runtime during the tests.")
|
||||
tr.line("This can happen with no test failures if the RuntimeWarning")
|
||||
tr.line("raised by Python when this happens is filtered by a test.")
|
||||
tr.line("")
|
||||
tr.line("Please ensure all new C modules declare support for running")
|
||||
tr.line("without the GIL. Any new tests that intentionally imports")
|
||||
tr.line("code that re-enables the GIL should do so in a subprocess.")
|
||||
pytest.exit("GIL re-enabled during tests", returncode=1)
|
||||
|
||||
|
||||
def pytest_configure(config: pytest.Config) -> None:
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
|
||||
1
Tests/createfontdatachunk.py
Normal file → Executable file
@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
NotoNastaliqUrdu-Regular.ttf and NotoSansSymbols-Regular.ttf, from https://github.com/googlei18n/noto-fonts
|
||||
NotoSans-Regular.ttf, from https://www.google.com/get/noto/
|
||||
NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/
|
||||
AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype. AdobeVFPrototypeDuplicates.ttf is a modified version of this
|
||||
AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype
|
||||
TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny
|
||||
ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa
|
||||
ter-x20b.pcf, from http://terminus-font.sourceforge.net/
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
STARTFONT
|
||||
FONT ÿ
|
||||
SIZE 10
|
||||
FONTBOUNDINGBOX 1 1 0 0
|
||||
CHARS 1
|
||||
FONTBOUNDINGBOX
|
||||
CHARS
|
||||
STARTCHAR
|
||||
ENCODING 65
|
||||
ENCODING
|
||||
BBX 2 5
|
||||
ENDCHAR
|
||||
ENDFONT
|
||||
|
||||
@ -9,21 +9,18 @@ import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import tempfile
|
||||
from collections.abc import Sequence
|
||||
from functools import lru_cache
|
||||
from io import BytesIO
|
||||
from typing import Any, Callable
|
||||
|
||||
import pytest
|
||||
from packaging.version import parse as parse_version
|
||||
|
||||
from PIL import Image, ImageFile, ImageMath, features
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Sequence
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
uploader = None
|
||||
@ -55,8 +52,8 @@ def convert_to_comparable(
|
||||
if a.mode == "P":
|
||||
new_a = Image.new("L", a.size)
|
||||
new_b = Image.new("L", b.size)
|
||||
new_a.putdata(a.get_flattened_data())
|
||||
new_b.putdata(b.get_flattened_data())
|
||||
new_a.putdata(a.getdata())
|
||||
new_b.putdata(b.getdata())
|
||||
elif a.mode == "I;16":
|
||||
new_a = a.convert("I")
|
||||
new_b = b.convert("I")
|
||||
@ -99,14 +96,12 @@ def assert_image_equal(a: Image.Image, b: Image.Image, msg: str | None = None) -
|
||||
|
||||
|
||||
def assert_image_equal_tofile(
|
||||
a: Image.Image,
|
||||
filename: str | Path,
|
||||
msg: str | None = None,
|
||||
mode: str | None = None,
|
||||
a: Image.Image, filename: str, msg: str | None = None, mode: str | None = None
|
||||
) -> None:
|
||||
with Image.open(filename) as im:
|
||||
converted_im = im.convert(mode) if mode else im
|
||||
assert_image_equal(a, converted_im, msg)
|
||||
with Image.open(filename) as img:
|
||||
if mode:
|
||||
img = img.convert(mode)
|
||||
assert_image_equal(a, img, msg)
|
||||
|
||||
|
||||
def assert_image_similar(
|
||||
@ -142,14 +137,21 @@ def assert_image_similar(
|
||||
|
||||
def assert_image_similar_tofile(
|
||||
a: Image.Image,
|
||||
filename: str | Path,
|
||||
filename: str,
|
||||
epsilon: float,
|
||||
msg: str | None = None,
|
||||
mode: str | None = None,
|
||||
) -> None:
|
||||
with Image.open(filename) as img:
|
||||
if mode:
|
||||
img = img.convert(mode)
|
||||
assert_image_similar(a, img, epsilon, msg)
|
||||
|
||||
|
||||
def assert_all_same(items: Sequence[Any], msg: str | None = None) -> None:
|
||||
assert items.count(items[0]) == len(items), msg
|
||||
|
||||
|
||||
def assert_not_all_same(items: Sequence[Any], msg: str | None = None) -> None:
|
||||
assert items.count(items[0]) != len(items), msg
|
||||
|
||||
@ -163,25 +165,11 @@ def assert_tuple_approx_equal(
|
||||
pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets))
|
||||
|
||||
|
||||
def timeout_unless_slower_valgrind(timeout: float) -> pytest.MarkDecorator:
|
||||
if "PILLOW_VALGRIND_TEST" in os.environ:
|
||||
return pytest.mark.pil_noop_mark()
|
||||
return pytest.mark.timeout(timeout)
|
||||
|
||||
|
||||
def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
|
||||
reason = f"{feature} not available"
|
||||
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
||||
|
||||
|
||||
def has_feature_version(feature: str, required: str) -> bool:
|
||||
version = features.version(feature)
|
||||
assert version is not None
|
||||
version_required = parse_version(required)
|
||||
version_available = parse_version(version)
|
||||
return version_available >= version_required
|
||||
|
||||
|
||||
def skip_unless_feature_version(
|
||||
feature: str, required: str, reason: str | None = None
|
||||
) -> pytest.MarkDecorator:
|
||||
@ -281,13 +269,17 @@ def _cached_hopper(mode: str) -> Image.Image:
|
||||
im = hopper("L")
|
||||
else:
|
||||
im = hopper()
|
||||
try:
|
||||
im = im.convert(mode)
|
||||
except ImportError:
|
||||
if mode == "LAB":
|
||||
im = Image.open("Tests/images/hopper.Lab.tif")
|
||||
else:
|
||||
raise
|
||||
if mode.startswith("BGR;"):
|
||||
with pytest.warns(DeprecationWarning):
|
||||
im = im.convert(mode)
|
||||
else:
|
||||
try:
|
||||
im = im.convert(mode)
|
||||
except ImportError:
|
||||
if mode == "LAB":
|
||||
im = Image.open("Tests/images/hopper.Lab.tif")
|
||||
else:
|
||||
raise
|
||||
return im
|
||||
|
||||
|
||||
@ -301,6 +293,16 @@ def djpeg_available() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def cjpeg_available() -> bool:
|
||||
if shutil.which("cjpeg"):
|
||||
try:
|
||||
subprocess.check_call(["cjpeg", "-version"])
|
||||
return True
|
||||
except subprocess.CalledProcessError: # pragma: no cover
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def netpbm_available() -> bool:
|
||||
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
|
||||
|
||||
@ -325,7 +327,16 @@ def magick_command() -> list[str] | None:
|
||||
return None
|
||||
|
||||
|
||||
def on_appveyor() -> bool:
|
||||
return "APPVEYOR" in os.environ
|
||||
|
||||
|
||||
def on_github_actions() -> bool:
|
||||
return "GITHUB_ACTIONS" in os.environ
|
||||
|
||||
|
||||
def on_ci() -> bool:
|
||||
# GitHub Actions and AppVeyor have "CI"
|
||||
return "CI" in os.environ
|
||||
|
||||
|
||||
@ -347,6 +358,10 @@ def is_pypy() -> bool:
|
||||
return hasattr(sys, "pypy_translation_info")
|
||||
|
||||
|
||||
def is_mingw() -> bool:
|
||||
return sysconfig.get_platform() == "mingw"
|
||||
|
||||
|
||||
class CachedProperty:
|
||||
def __init__(self, func: Callable[[Any], Any]) -> None:
|
||||
self.func = func
|
||||
|
||||
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
BIN
Tests/images/bmp/html/bkgd.png
Normal file
|
After Width: | Height: | Size: 126 B |
578
Tests/images/bmp/html/bmpsuite.html
Normal file
@ -0,0 +1,578 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
<title>BMP Suite Image List</title>
|
||||
|
||||
<style>
|
||||
.b { background:url(bkgd.png); }
|
||||
.q { background-color:#fff0e0; }
|
||||
.bad { background-color:#ffa0a0; }
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<h1>BMP Suite Image List</h1>
|
||||
|
||||
<p><i>For <a href="http://entropymine.com/jason/bmpsuite/">BMP Suite</a>
|
||||
version 2.3</i></p>
|
||||
|
||||
<p>This document describes the images in <i>BMP Suite</i>, and shows what
|
||||
I allege to be the correct way to interpret them. PNG and JPEG images are
|
||||
used for reference.
|
||||
</p>
|
||||
|
||||
<p>It also shows how your web browser displays the BMP images,
|
||||
but that’s not its main purpose.
|
||||
BMP is poor image format to use on web pages, so a web browser’s
|
||||
level of support for it is arguably not important.</p>
|
||||
|
||||
<table border=1 cellpadding=8>
|
||||
|
||||
<tr>
|
||||
<th>File</th>
|
||||
<th>Ver.</th>
|
||||
<th>Correct display</th>
|
||||
<th>In your browser</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal1.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal1.png"></td>
|
||||
<td class=b><img src="../g/pal1.bmp"></td>
|
||||
<td>1 bit/pixel paletted image, in which black is the first color in
|
||||
the palette.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal1wb.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal1.png"></td>
|
||||
<td class=b><img src="../g/pal1wb.bmp"></td>
|
||||
<td>1 bit/pixel paletted image, in which white is the first color in
|
||||
the palette.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal1bg.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal1bg.png"></td>
|
||||
<td class=b><img src="../g/pal1bg.bmp"></td>
|
||||
<td>1 bit/pixel paletted image, with colors other than black and white.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/pal1p1.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal1p1.png"></td>
|
||||
<td class=b><img src="../q/pal1p1.bmp"></td>
|
||||
<td>1 bit/pixel paletted image, with only one color in the palette.
|
||||
The documentation says that 1-bpp images have a palette size of 2
|
||||
(not “up to 2”), but it would be silly for a viewer not to
|
||||
support a size of 1.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/pal2.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal2.png"></td>
|
||||
<td class=b><img src="../q/pal2.bmp"></td>
|
||||
<td>A paletted image with 2 bits/pixel. Usually only 1, 4,
|
||||
and 8 are allowed, but 2 is legal on Windows CE.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal4.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal4.png"></td>
|
||||
<td class=b><img src="../g/pal4.bmp"></td>
|
||||
<td>Paletted image with 12 palette colors, and 4 bits/pixel.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal4rle.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal4.png"></td>
|
||||
<td class=b><img src="../g/pal4rle.bmp"></td>
|
||||
<td>4-bit image that uses RLE compression.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/pal4rletrns.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal4rletrns.png"><br>
|
||||
or<br><img src="pal4rletrns-0.png"><br>
|
||||
or<br><img src="pal4rletrns-b.png"></td>
|
||||
<td class=b><img src="../q/pal4rletrns.bmp"></td>
|
||||
<td>An RLE-compressed image that used “delta”
|
||||
codes to skip over some pixels, leaving them undefined. Some viewers
|
||||
make undefined pixels transparent, others make them black, and
|
||||
others assign them palette color 0 (purple, in this case).</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal8.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal8.png"></td>
|
||||
<td class=b><img src="../g/pal8.bmp"></td>
|
||||
<td>Our standard paletted image, with 252 palette colors, and 8
|
||||
bits/pixel.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal8-0.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal8.png"></td>
|
||||
<td class=b><img src="../g/pal8-0.bmp"></td>
|
||||
<td>Every field that can be set to 0 is set to 0: pixels/meter=0;
|
||||
colors used=0 (meaning the default 256); size-of-image=0.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal8rle.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal8.png"></td>
|
||||
<td class=b><img src="../g/pal8rle.bmp"></td>
|
||||
<td>8-bit image that uses RLE compression.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/pal8rletrns.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal8rletrns.png"><br>
|
||||
or<br><img src="pal8rletrns-0.png"><br>
|
||||
or<br><img src="pal8rletrns-b.png"></td>
|
||||
<td class=b><img src="../q/pal8rletrns.bmp"></td>
|
||||
<td>8-bit version of q/pal4rletrns.bmp.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal8w126.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal8w126.png"></td>
|
||||
<td class=b><img src="../g/pal8w126.bmp"></td>
|
||||
<td rowspan=3>Images with different widths and heights.
|
||||
In BMP format, rows are padded to a multiple of four bytes, so we
|
||||
test all four possibilities.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal8w125.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal8w125.png"></td>
|
||||
<td class=b><img src="../g/pal8w125.bmp"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal8w124.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal8w124.png"></td>
|
||||
<td class=b><img src="../g/pal8w124.bmp"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal8topdown.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal8.png"></td>
|
||||
<td class=b><img src="../g/pal8topdown.bmp"></td>
|
||||
<td>BMP images are normally stored from the bottom up, but
|
||||
there is a way to store them from the top down.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/pal8offs.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal8.png"></td>
|
||||
<td class=b><img src="../q/pal8offs.bmp"></td>
|
||||
<td>A file with some unused bytes between the palette and the
|
||||
image. This is probably valid, but I’m not 100% sure.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/pal8oversizepal.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="pal8.png"></td>
|
||||
<td class=b><img src="../q/pal8oversizepal.bmp"></td>
|
||||
<td>An 8-bit image with 300 palette colors. This may be invalid,
|
||||
because the documentation could
|
||||
be interpreted to imply that 8-bit images aren’t allowed
|
||||
to have more than 256 colors.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal8nonsquare.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b>
|
||||
<img src="pal8nonsquare-v.png"><br>
|
||||
or<br>
|
||||
<img src="pal8nonsquare-e.png">
|
||||
</td>
|
||||
<td class=b><img src="../g/pal8nonsquare.bmp"></td>
|
||||
<td>An image with non-square pixels: the X pixels/meter is twice
|
||||
the Y pixels/meter. Image <i>editors</i> can be expected to
|
||||
leave the image “squashed”; image <i>viewers</i> should
|
||||
consider stretching it to its correct proportions.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal8os2.bmp</td>
|
||||
<td>OS/2v1</td>
|
||||
<td class=b><img src="pal8.png"></td>
|
||||
<td class=b><img src="../g/pal8os2.bmp"></td>
|
||||
<td>An OS/2-style bitmap.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/pal8os2sp.bmp</td>
|
||||
<td>OS/2v1</td>
|
||||
<td class=b><img src="pal8.png"></td>
|
||||
<td class=b><img src="../q/pal8os2sp.bmp"></td>
|
||||
<td>An OS/2v1 with a less-than-full-sized palette.
|
||||
Probably not valid, but such files have been seen in the wild.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/pal8os2v2.bmp</td>
|
||||
<td>OS/2v2</td>
|
||||
<td class=b><img src="pal8.png"></td>
|
||||
<td class=b><img src="../q/pal8os2v2.bmp"></td>
|
||||
<td>My attempt to make an OS/2v2 bitmap.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/pal8os2v2-16.bmp</td>
|
||||
<td>OS/2v2</td>
|
||||
<td class=b><img src="pal8.png"></td>
|
||||
<td class=b><img src="../q/pal8os2v2-16.bmp"></td>
|
||||
<td>An OS/2v2 bitmap whose header has only 16 bytes, instead of the full 64.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal8v4.bmp</td>
|
||||
<td>4</td>
|
||||
<td class=b><img src="pal8.png"></td>
|
||||
<td class=b><img src="../g/pal8v4.bmp"></td>
|
||||
<td>A v4 bitmap. I’m not sure that the gamma and chromaticity values in
|
||||
this file are sensible, because I can’t find any detailed documentation
|
||||
of them.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/pal8v5.bmp</td>
|
||||
<td>5</td>
|
||||
<td class=b><img src="pal8.png"></td>
|
||||
<td class=b><img src="../g/pal8v5.bmp"></td>
|
||||
<td>A v5 bitmap. Version 5 has additional colorspace options over v4, so it
|
||||
is easier to create, and ought to be more portable.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/rgb16.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="rgb16.png"></td>
|
||||
<td class=b><img src="../g/rgb16.bmp"></td>
|
||||
<td>A 16-bit image with the default color format: 5 bits each for red,
|
||||
green, and blue, and 1 unused bit.
|
||||
The whitest colors should (I assume) be displayed as pure white:
|
||||
<span style="background-color:rgb(255,255,255)">(255,255,255)</span>, not
|
||||
<span style="background-color:rgb(248,248,248)">(248,248,248)</span>.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/rgb16-565.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="rgb16-565.png"></td>
|
||||
<td class=b><img src="../g/rgb16-565.bmp"></td>
|
||||
<td>A 16-bit image with a BITFIELDS segment indicating 5 red, 6 green,
|
||||
and 5 blue bits. This is a standard 16-bit format, even supported by
|
||||
old versions of Windows that don’t support any other non-default 16-bit
|
||||
formats.
|
||||
The whitest colors should be displayed as pure white:
|
||||
<span style="background-color:rgb(255,255,255)">(255,255,255)</span>, not
|
||||
<span style="background-color:rgb(248,252,248)">(248,252,248)</span>.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/rgb16-565pal.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="rgb16-565.png"></td>
|
||||
<td class=b><img src="../g/rgb16-565pal.bmp"></td>
|
||||
<td>A 16-bit image with both a BITFIELDS segment and a palette.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/rgb16-231.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="rgb16-231.png"></td>
|
||||
<td class=b><img src="../q/rgb16-231.bmp"></td>
|
||||
<td>An unusual and silly 16-bit image, with 2 red bits, 3 green bits, and 1
|
||||
blue bit. Most viewers do support this image, but the colors may be darkened
|
||||
with a yellow-green shadow. That’s because they’re doing simple
|
||||
bit-shifting (possibly including one round of bit replication), instead of
|
||||
proper scaling.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/rgba16-4444.bmp</td>
|
||||
<td>5</td>
|
||||
<td class=b><img src="rgba16-4444.png"></td>
|
||||
<td class=b><img src="../q/rgba16-4444.bmp"></td>
|
||||
<td>A 16-bit image with an alpha channel. There are 4 bits for each color
|
||||
channel, and 4 bits for the alpha channel.
|
||||
It’s not clear if this is valid, but I can’t find anything that
|
||||
suggests it isn’t.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/rgb24.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="rgb24.png"></td>
|
||||
<td class=b><img src="../g/rgb24.bmp"></td>
|
||||
<td>A perfectly ordinary 24-bit (truecolor) image.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/rgb24pal.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="rgb24.png"></td>
|
||||
<td class=b><img src="../g/rgb24pal.bmp"></td>
|
||||
<td>A 24-bit image, with a palette containing 256 colors. There is little if
|
||||
any reason for a truecolor image to contain a palette, but it is legal.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/rgb24largepal.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="rgb24.png"></td>
|
||||
<td class=b><img src="../q/rgb24largepal.bmp"></td>
|
||||
<td>A 24-bit image, with a palette containing 300 colors.
|
||||
The fact that the palette has more than 256 colors may cause some viewers
|
||||
to complain, but the documentation does not mention a size limit.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/rgb24prof.bmp</td>
|
||||
<td>5</td>
|
||||
<td class=b><img src="rgb24.png"></td>
|
||||
<td class=b><img src="../q/rgb24prof.bmp"></td>
|
||||
<td>My attempt to make a BMP file with an embedded color profile.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/rgb24lprof.bmp</td>
|
||||
<td>5</td>
|
||||
<td class=b><img src="rgb24.png"></td>
|
||||
<td class=b><img src="../q/rgb24lprof.bmp"></td>
|
||||
<td>My attempt to make a BMP file with a linked color profile.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/rgb24jpeg.bmp</td>
|
||||
<td>5</td>
|
||||
<td class=b><img src="rgb24.jpg"></td>
|
||||
<td class=b><img src="../q/rgb24jpeg.bmp"></td>
|
||||
<td rowspan=2>My attempt to make BMP files with embedded JPEG and PNG images.
|
||||
These are not likely to be supported by much of anything (they’re
|
||||
intended for printers).</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/rgb24png.bmp</td>
|
||||
<td>5</td>
|
||||
<td class=b><img src="rgb24.png"></td>
|
||||
<td class=b><img src="../q/rgb24png.bmp"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/rgb32.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="rgb24.png"></td>
|
||||
<td class=b><img src="../g/rgb32.bmp"></td>
|
||||
<td>A 32-bit image using the default color format for 32-bit images (no
|
||||
BITFIELDS segment). There are 8 bits per color channel, and 8 unused
|
||||
bits. The unused bits are set to 0.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>g/rgb32bf.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="rgb24.png"></td>
|
||||
<td class=b><img src="../g/rgb32bf.bmp"></td>
|
||||
<td>A 32-bit image with a BITFIELDS segment. As usual, there are 8 bits per
|
||||
color channel, and 8 unused bits. But the color channels are in an unusual
|
||||
order, so the viewer must read the BITFIELDS, and not just guess.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/rgb32fakealpha.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="rgb24.png"><br>
|
||||
or<br>
|
||||
<img class=b src="fakealpha.png">
|
||||
</td>
|
||||
<td class=b><img src="../q/rgb32fakealpha.bmp"></td>
|
||||
<td>Same as g/rgb32.bmp, except that the unused bits are set to something
|
||||
other than 0.
|
||||
If the image becomes transparent toward the bottom, it probably means
|
||||
the viewer uses heuristics to guess whether the undefined
|
||||
data represents transparency.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/rgb32-111110.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="rgb24.png"></td>
|
||||
<td class=b><img src="../q/rgb32-111110.bmp"></td>
|
||||
<td>A 32 bits/pixel image, with all 32 bits used: 11 each for red and
|
||||
green, and 10 for blue. As far as I know, this is perfectly valid, but it
|
||||
is unusual.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/rgba32.bmp</td>
|
||||
<td>5</td>
|
||||
<td class=b><img src="rgba32.png"></td>
|
||||
<td class=b><img src="../q/rgba32.bmp"></td>
|
||||
<td>A BMP with an alpha channel. Transparency is barely documented,
|
||||
so it’s <i>possible</i> that this file is not correctly formed.
|
||||
The color channels are in an unusual order, to prevent viewers from
|
||||
passing this test by making a lucky guess.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=q>q/rgba32abf.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b><img src="rgba32.png"></td>
|
||||
<td class=b><img src="../q/rgba32abf.bmp"></td>
|
||||
<td>An image of type BI_ALHPABITFIELDS. Supposedly, this was used on
|
||||
Windows CE. I don’t know whether it is constructed correctly.</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td class=bad>b/badbitcount.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b>N/A</td>
|
||||
<td class=b><img src="../b/badbitcount.bmp"></td>
|
||||
<td>Header indicates an absurdly large number of bits/pixel.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=bad>b/badbitssize.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b>N/A</td>
|
||||
<td class=b><img src="../b/badbitssize.bmp"></td>
|
||||
<td>Header incorrectly indicates that the bitmap is several GB in size.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=bad>b/baddens1.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b>N/A</td>
|
||||
<td class=b><img src="../b/baddens1.bmp"></td>
|
||||
<td rowspan=2>Density (pixels per meter) suggests the image is <i>much</i>
|
||||
larger in one dimension than the other.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=bad>b/baddens2.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b>N/A</td>
|
||||
<td class=b><img src="../b/baddens2.bmp"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=bad>b/badfilesize.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b>N/A</td>
|
||||
<td class=b><img src="../b/badfilesize.bmp"></td>
|
||||
<td>Header incorrectly indicates that the file is several GB in size.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=bad>b/badheadersize.bmp</td>
|
||||
<td>?</td>
|
||||
<td class=b>N/A</td>
|
||||
<td class=b><img src="../b/badheadersize.bmp"></td>
|
||||
<td>Header size is 66 bytes, which is not a valid size for any known BMP
|
||||
version.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=bad>b/badpalettesize.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b>N/A</td>
|
||||
<td class=b><img src="../b/badpalettesize.bmp"></td>
|
||||
<td>Header incorrectly indicates that the palette contains an absurdly large
|
||||
number of colors.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=bad>b/badplanes.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b>N/A</td>
|
||||
<td class=b><img src="../b/badplanes.bmp"></td>
|
||||
<td>The “planes” setting, which is required to be 1, is not 1.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=bad>b/badrle.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b>N/A</td>
|
||||
<td class=b><img src="../b/badrle.bmp"></td>
|
||||
<td>An invalid RLE-compressed image that tries to cause buffer overruns.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=bad>b/badwidth.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b>N/A</td>
|
||||
<td class=b><img src="../b/badwidth.bmp"></td>
|
||||
<td>The image claims to be a negative number of pixels in width.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=bad>b/pal8badindex.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b>N/A</td>
|
||||
<td class=b><img src="../b/pal8badindex.bmp"></td>
|
||||
<td>Many of the palette indices used in the image are not present in the
|
||||
palette.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=bad>b/reallybig.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b>N/A</td>
|
||||
<td class=b><img src="../b/reallybig.bmp"></td>
|
||||
<td>An image with a very large reported width and height.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=bad>b/rletopdown.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b>N/A</td>
|
||||
<td class=b><img src="../b/rletopdown.bmp"></td>
|
||||
<td>An RLE-compressed image that tries to use top-down orientation,
|
||||
which isn’t allowed.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class=bad>b/shortfile.bmp</td>
|
||||
<td>3</td>
|
||||
<td class=b>N/A</td>
|
||||
<td class=b><img src="../b/shortfile.bmp"></td>
|
||||
<td>A file that has been truncated in the middle of the bitmap.</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
Tests/images/bmp/html/fakealpha.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Tests/images/bmp/html/pal1p1.png
Normal file
|
After Width: | Height: | Size: 124 B |
BIN
Tests/images/bmp/html/pal2.png
Normal file
|
After Width: | Height: | Size: 961 B |
BIN
Tests/images/bmp/html/pal4rletrns-0.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/bmp/html/pal4rletrns-b.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Tests/images/bmp/html/pal4rletrns.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |