Skip to content

Commit 443ccc8

Browse files
committed
Merge branch 'main' into rip-setup-py
2 parents bfd1f18 + 767a3f1 commit 443ccc8

99 files changed

Lines changed: 2107 additions & 844 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.ci/requirements-cibw.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
cibuildwheel==4.0.0
1+
cibuildwheel==4.1.0

.ci/requirements-sbom.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
check-jsonschema==0.37.2
1+
check-jsonschema==0.37.3

.coveragerc

Lines changed: 0 additions & 22 deletions
This file was deleted.

.github/dependencies.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"bzip2": "1.0.8",
44
"freetype": "2.14.3",
55
"fribidi": "1.0.16",
6-
"harfbuzz": "14.2.0",
6+
"harfbuzz": "14.2.1",
77
"jpegturbo": "3.1.4.1",
88
"lcms2": "2.19.1",
99
"libavif": "1.4.2",

.github/embed-sbom.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""Embed Pillow's SBOM into each wheel's `.dist-info/sboms/` directory,
2+
as specified by PEP 770.
3+
4+
The SBOM (produced by `generate-sbom.py`) is injected into every `.whl` in
5+
the given directory, updating each wheel's `RECORD` so the result remains a
6+
valid, installable wheel.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import argparse
12+
import base64
13+
import hashlib
14+
import zipfile
15+
from pathlib import Path
16+
17+
18+
def record_entry(path: str, data: bytes) -> bytes:
19+
"""Build a RECORD line: `path,sha256=<base64url-nopad>,<size>`."""
20+
digest = base64.urlsafe_b64encode(hashlib.sha256(data).digest())
21+
return (
22+
path.encode("utf-8")
23+
+ b",sha256="
24+
+ digest.rstrip(b"=")
25+
+ b","
26+
+ str(len(data)).encode()
27+
)
28+
29+
30+
def embed(wheel: Path, sbom: Path) -> None:
31+
with zipfile.ZipFile(wheel) as zf:
32+
infos = zf.infolist()
33+
contents = {info.filename: zf.read(info.filename) for info in infos}
34+
35+
record_name = next(
36+
name
37+
for name in contents
38+
if name.endswith(".dist-info/RECORD") and name.count("/") == 1
39+
)
40+
dist_info = record_name.split("/", 1)[0]
41+
42+
sbom_bytes = sbom.read_bytes()
43+
sbom_path = f"{dist_info}/sboms/{sbom.name}"
44+
45+
# Append a matching RECORD line for the SBOM (RECORD's own line has no hash).
46+
lines = contents[record_name].splitlines()
47+
lines.append(record_entry(sbom_path, sbom_bytes))
48+
contents[record_name] = b"\n".join(lines) + b"\n"
49+
50+
with zipfile.ZipFile(wheel, "w", zipfile.ZIP_DEFLATED) as zf:
51+
# Re-use each original ZipInfo to preserve timestamps, mode bits and
52+
# compression; only RECORD's contents change
53+
for info in infos:
54+
zf.writestr(info, contents[info.filename])
55+
zf.writestr(sbom_path, sbom_bytes)
56+
57+
print(f"Embedded {sbom.name} in {wheel.name}")
58+
59+
60+
def scan_dir(path: Path) -> Path:
61+
"""If `path` is a directory, return the path of the SBOM within."""
62+
if path.is_dir():
63+
candidates = list(path.glob("*.cdx.json"))
64+
if len(candidates) != 1:
65+
msg = f"expected exactly one *.cdx.json in {path}, found {len(candidates)}"
66+
raise SystemExit(msg)
67+
return candidates[0]
68+
return path
69+
70+
71+
def main() -> None:
72+
parser = argparse.ArgumentParser(
73+
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
74+
)
75+
parser.add_argument(
76+
"wheelhouse", type=Path, help="directory of wheels to embed the SBOM into"
77+
)
78+
parser.add_argument(
79+
"sbom",
80+
type=Path,
81+
help="SBOM file, or a directory containing a single `.cdx.json`",
82+
)
83+
args = parser.parse_args()
84+
85+
sbom = scan_dir(args.sbom)
86+
87+
wheels = sorted(args.wheelhouse.glob("*.whl"))
88+
if not wheels:
89+
parser.error(f"no wheels found in {args.wheelhouse}")
90+
91+
for wheel in wheels:
92+
embed(wheel, sbom)
93+
94+
95+
if __name__ == "__main__":
96+
main()

.github/renovate.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,17 @@
165165
"extractVersionTemplate": "^v(?<version>.+)$"
166166
}
167167
],
168+
"ignoreDeps": ["numpy"],
168169
"packageRules": [
169170
{
170171
"groupName": "github-actions",
171172
"matchManagers": ["github-actions"],
172173
"separateMajorMinor": false
174+
},
175+
{
176+
"matchManagers": ["github-actions"],
177+
"matchPackageNames": ["google/oss-fuzz"],
178+
"minimumReleaseAge": "0 days"
173179
}
174180
]
175181
}

.github/workflows/benchmark.yml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
name: Benchmark
2+
3+
on:
4+
push:
5+
branches:
6+
- "**"
7+
paths-ignore: &paths-ignore
8+
- ".github/workflows/docs.yml"
9+
- ".github/workflows/wheels*"
10+
- ".gitmodules"
11+
- "docs/**"
12+
- "wheels/**"
13+
pull_request:
14+
paths-ignore: *paths-ignore
15+
workflow_dispatch:
16+
17+
permissions:
18+
contents: read
19+
20+
concurrency:
21+
group: ${{ github.workflow }}-${{ github.ref }}
22+
cancel-in-progress: true
23+
24+
env:
25+
FORCE_COLOR: 1
26+
PIP_DISABLE_PIP_VERSION_CHECK: 1
27+
28+
jobs:
29+
benchmark:
30+
31+
strategy:
32+
fail-fast: false
33+
matrix:
34+
python-version: [
35+
# NB: Codspeed doesn't seem to support multiple runs of the same benchmark:
36+
# > At least one benchmark was run multiple times in your benchmarking workflow.
37+
# > This could be because you used a matrix that runs your benchmarks multiple times.
38+
# Hence, this list should only ever have exactly one version.
39+
"3.14",
40+
]
41+
42+
runs-on: ubuntu-latest
43+
name: Benchmark Python ${{ matrix.python-version }}
44+
45+
steps:
46+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
47+
with:
48+
persist-credentials: false
49+
50+
- name: Set up Python ${{ matrix.python-version }}
51+
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
52+
with:
53+
python-version: ${{ matrix.python-version }}
54+
allow-prereleases: true
55+
cache: pip
56+
cache-dependency-path: |
57+
".ci/*.sh"
58+
"pyproject.toml"
59+
60+
- name: Cache libavif
61+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
62+
id: cache-libavif
63+
with:
64+
path: ~/cache-libavif
65+
key: ${{ runner.os }}-libavif-${{ hashFiles('depends/install_libavif.sh') }}
66+
67+
- name: Cache libimagequant
68+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
69+
id: cache-libimagequant
70+
with:
71+
path: ~/cache-libimagequant
72+
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
73+
74+
- name: Cache libwebp
75+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
76+
id: cache-libwebp
77+
with:
78+
path: ~/cache-libwebp
79+
key: ${{ runner.os }}-libwebp-${{ hashFiles('depends/install_webp.sh') }}
80+
81+
- name: Install Linux dependencies
82+
run: |
83+
.ci/install.sh
84+
env:
85+
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
86+
GHA_LIBAVIF_CACHE_HIT: ${{ steps.cache-libavif.outputs.cache-hit }}
87+
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
88+
GHA_LIBWEBP_CACHE_HIT: ${{ steps.cache-libwebp.outputs.cache-hit }}
89+
90+
- name: Run CodSpeed benchmarks
91+
uses: CodSpeedHQ/action@a4a36bb07c0638b0b4ca52bf1f3dad1b4289e52f # v4.18.1
92+
with:
93+
mode: simulation
94+
run: |
95+
python3 -m pip install -e . pytest-codspeed
96+
pytest -vv --codspeed Tests/benchmarks.py

.github/workflows/cifuzz.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ jobs:
3030
steps:
3131
- name: Build Fuzzers
3232
id: build
33-
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@ed7c2f7301eecb625a3d427549056b0a90546bb5 # master
33+
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@5c437ffb9e5945151b70cdd31ae7feeeeb43afd0 # master
3434
with:
3535
oss-fuzz-project-name: 'pillow'
3636
language: python
3737
dry-run: false
3838
- name: Run Fuzzers
3939
id: run
40-
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@ed7c2f7301eecb625a3d427549056b0a90546bb5 # master
40+
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@5c437ffb9e5945151b70cdd31ae7feeeeb43afd0 # master
4141
with:
4242
oss-fuzz-project-name: 'pillow'
4343
fuzz-seconds: 600

.github/workflows/docs.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ jobs:
2929
name: Docs
3030

3131
steps:
32-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
32+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
3333
with:
3434
persist-credentials: false
3535

3636
- name: Set up Python
37-
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
37+
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
3838
with:
3939
python-version: "3.x"
4040
cache: pip
@@ -46,21 +46,21 @@ jobs:
4646
run: python3 .github/workflows/system-info.py
4747

4848
- name: Cache libavif
49-
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
49+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
5050
id: cache-libavif
5151
with:
5252
path: ~/cache-libavif
53-
key: ${{ runner.os }}-libavif-${{ hashFiles('depends/install_libavif.sh', 'depends/libavif-svt4.patch') }}
53+
key: ${{ runner.os }}-libavif-${{ hashFiles('depends/install_libavif.sh') }}
5454

5555
- name: Cache libimagequant
56-
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
56+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
5757
id: cache-libimagequant
5858
with:
5959
path: ~/cache-libimagequant
6060
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
6161

6262
- name: Cache libwebp
63-
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
63+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
6464
id: cache-libwebp
6565
with:
6666
path: ~/cache-libwebp

.github/workflows/lint.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ jobs:
1818
runs-on: ubuntu-latest
1919
name: Lint
2020
steps:
21-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
21+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
2222
with:
2323
persist-credentials: false
24-
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
24+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
2525
with:
2626
python-version: "3.x"
2727
- name: Install uv
28-
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
28+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
2929
- name: Lint
3030
run: uvx --with tox-uv tox -e lint
3131
- name: Mypy

0 commit comments

Comments
 (0)