diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 887c7dd9670..8d8cb0b159f 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -72,10 +72,10 @@ jobs: - name: Install dependencies id: install run: | - 7z x winbuild\depends\nasm-2.16.01-win64.zip "-o$env:RUNNER_WORKSPACE\" - echo "$env:RUNNER_WORKSPACE\nasm-2.16.01" >> $env:GITHUB_PATH + choco install nasm --no-progress + echo "C:\Program Files\NASM" >> $env:GITHUB_PATH - choco install ghostscript --version=10.0.0.20230317 + choco install ghostscript --version=10.0.0.20230317 --no-progress echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH # Install extra test images @@ -167,7 +167,6 @@ jobs: - name: Build Pillow run: | $FLAGS="-C raqm=vendor -C fribidi=vendor" - if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS+=" -C imagequant=disable" } 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 @@ -209,47 +208,6 @@ jobs: flags: GHA_Windows name: ${{ runner.os }} Python ${{ matrix.python-version }} - - name: Build wheel - id: wheel - if: "github.event_name != 'pull_request'" - run: | - mkdir fribidi - copy winbuild\build\bin\fribidi* fribidi - 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 - ) - ) - ) - for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo dist=dist-%%a >> %GITHUB_OUTPUT% - call winbuild\\build\\build_env.cmd - %pythonLocation%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor -C imagequant=disable . - shell: cmd - - - name: Upload wheel - uses: actions/upload-artifact@v3 - if: "github.event_name != 'pull_request'" - with: - name: ${{ steps.wheel.outputs.dist }} - path: "*.whl" - - - name: Upload fribidi.dll - if: "github.event_name != 'pull_request' && matrix.python-version == 3.11" - uses: actions/upload-artifact@v3 - with: - name: fribidi - path: fribidi\* - success: permissions: contents: none diff --git a/.github/workflows/wheels-test.ps1 b/.github/workflows/wheels-test.ps1 new file mode 100644 index 00000000000..f593c722854 --- /dev/null +++ b/.github/workflows/wheels-test.ps1 @@ -0,0 +1,22 @@ +param ([string]$venv, [string]$pillow="C:\pillow") +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' +Set-PSDebug -Trace 1 +if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") { + # unlike CPython, PyPy requires Visual C++ Redistributable to be installed + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri 'https://aka.ms/vs/15/release/vc_redist.x64.exe' -OutFile 'vc_redist.x64.exe' + C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null +} +$env:path += ";$pillow\winbuild\build\bin\" +& "$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 +& python -VV +if (!$?) { exit $LASTEXITCODE } +& python selftest.py +if (!$?) { exit $LASTEXITCODE } +& python -m pytest -vx Tests\check_wheel.py +if (!$?) { exit $LASTEXITCODE } +& python -m pytest -vx Tests +if (!$?) { exit $LASTEXITCODE } diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index 34dfeaf143c..207ec15678b 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -1,10 +1,6 @@ #!/bin/bash set -e -EXP_CODECS="jpg jpg_2000 libtiff zlib" -EXP_MODULES="freetype2 littlecms2 pil tkinter webp" -EXP_FEATURES="fribidi harfbuzz libjpeg_turbo raqm transp_webp webp_anim webp_mux xcb" - if [[ "$OSTYPE" == "darwin"* ]]; then brew install fribidi export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig" @@ -25,21 +21,5 @@ fi # Runs tests python3 selftest.py +python3 -m pytest Tests/check_wheel.py python3 -m pytest - -# Test against expected codecs, modules and features -codecs=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_codecs())))') -if [ "$codecs" != "$EXP_CODECS" ]; then - echo "Codecs should be: '$EXP_CODECS'; but are '$codecs'" - exit 1 -fi -modules=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_modules())))') -if [ "$modules" != "$EXP_MODULES" ]; then - echo "Modules should be: '$EXP_MODULES'; but are '$modules'" - exit 1 -fi -features=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_features())))') -if [ "$features" != "$EXP_FEATURES" ]; then - echo "Features should be: '$EXP_FEATURES'; but are '$features'" - exit 1 -fi diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 1b141b14fbf..c4737bfc772 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -3,14 +3,20 @@ name: Wheels on: push: paths: - - ".github/workflows/wheels*.yml" + - ".ci/requirements-cibw.txt" + - ".github/workflows/wheel*" - "wheels/*" + - "winbuild/build_prepare.py" + - "winbuild/fribidi.cmake" tags: - "*" pull_request: paths: - - ".github/workflows/wheels*.yml" + - ".ci/requirements-cibw.txt" + - ".github/workflows/wheel*" - "wheels/*" + - "winbuild/build_prepare.py" + - "winbuild/fribidi.cmake" workflow_dispatch: permissions: @@ -63,7 +69,6 @@ jobs: env: CIBW_ARCHS: ${{ matrix.archs }} CIBW_BUILD: ${{ matrix.build }} - CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_SKIP: pp38-* @@ -75,6 +80,102 @@ jobs: name: dist path: ./wheelhouse/*.whl + windows: + name: Windows ${{ matrix.arch }} + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + - arch: x86 + cibw_arch: x86 + - arch: x64 + cibw_arch: AMD64 + - arch: ARM64 + cibw_arch: ARM64 + steps: + - uses: actions/checkout@v4 + + - name: Checkout extra test images + uses: actions/checkout@v4 + with: + repository: python-pillow/test-images + path: Tests\test-images + + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - name: Prepare for build + run: | + choco install nasm --no-progress + echo "C:\Program Files\NASM" >> $env:GITHUB_PATH + + # Install extra test images + xcopy /S /Y Tests\test-images\* Tests\images + + & python.exe -m pip install -r .ci/requirements-cibw.txt + + # Cannot cross-compile FriBiDi (only used for tests) + $FLAGS = ("--no-imagequant", "--architecture=${{ matrix.arch }}") + if ('${{ matrix.arch }}' -eq 'ARM64') { $FLAGS += "--no-fribidi" } + & python.exe winbuild\build_prepare.py -v @FLAGS + shell: pwsh + + - name: Build wheels + run: | + 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_TEST_SKIP: "*-win_arm64" + CIBW_TEST_COMMAND: 'docker run --rm + -v {project}:C:\pillow + -v C:\cibw:C:\cibw + -v %CD%\..\venv-test:%CD%\..\venv-test + -e CI -e GITHUB_ACTIONS + mcr.microsoft.com/windows/servercore:ltsc2022 + powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test' + shell: cmd + + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: dist + path: ./wheelhouse/*.whl + + - name: Prepare to upload FriBiDi + if: "matrix.arch != 'ARM64'" + run: | + mkdir fribidi\${{ matrix.arch }} + copy winbuild\build\bin\fribidi* fribidi\${{ matrix.arch }} + shell: cmd + + - name: Upload fribidi.dll + if: "matrix.arch != 'ARM64'" + uses: actions/upload-artifact@v3 + with: + name: fribidi + path: fribidi\* + sdist: runs-on: ubuntu-latest steps: @@ -97,7 +198,7 @@ jobs: success: permissions: contents: none - needs: [build, sdist] + needs: [build, windows, sdist] runs-on: ubuntu-latest name: Wheels Successful steps: diff --git a/.travis.yml b/.travis.yml index 4005dbcf036..8f8250809d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ if: tag IS present OR type = api env: global: - CIBW_ARCHS=aarch64 - - CIBW_CONFIG_SETTINGS="raqm=enable raqm=vendor fribidi=vendor" - CIBW_SKIP=pp38-* language: python diff --git a/Tests/check_wheel.py b/Tests/check_wheel.py new file mode 100644 index 00000000000..cc52cb75ebb --- /dev/null +++ b/Tests/check_wheel.py @@ -0,0 +1,41 @@ +import sys + +from PIL import features + + +def test_wheel_modules(): + 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(): + expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"} + + assert set(features.get_supported_codecs()) == expected_codecs + + +def test_wheel_features(): + 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 diff --git a/Tests/helper.py b/Tests/helper.py index aacd9556493..cce7eca3a5a 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -5,6 +5,7 @@ import logging import os import shutil +import subprocess import sys import sysconfig import tempfile @@ -258,11 +259,21 @@ def hopper(mode=None, cache={}): def djpeg_available(): - return bool(shutil.which("djpeg")) + if shutil.which("djpeg"): + try: + subprocess.check_call(["djpeg", "-version"]) + return True + except subprocess.CalledProcessError: # pragma: no cover + return False def cjpeg_available(): - return bool(shutil.which("cjpeg")) + if shutil.which("cjpeg"): + try: + subprocess.check_call(["cjpeg", "-version"]) + return True + except subprocess.CalledProcessError: # pragma: no cover + return False def netpbm_available(): diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index f8059eca443..a75cbadc4ad 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -11,6 +11,10 @@ class TestImageGrab: + @pytest.mark.skipif( + os.environ.get("USERNAME") == "ContainerAdministrator", + reason="can't grab screen when running in Docker", + ) @pytest.mark.skipif( sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS" ) diff --git a/pyproject.toml b/pyproject.toml index 81a51f0d83b..41b5aa87f87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,6 +88,8 @@ version = {attr = "PIL.__version__"} [tool.cibuildwheel] before-all = ".github/workflows/wheels-dependencies.sh" +build-verbosity = 1 +config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 2db646de13c..0d111a8ecdf 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -586,14 +586,19 @@ def build_dep(name: str) -> str: def build_dep_all() -> None: lines = [r'call "{build_dir}\build_env.cmd"'] + gha_groups = "GITHUB_ACTIONS" in os.environ for dep_name in DEPS: print() if dep_name in disabled: print(f"Skipping disabled dependency {dep_name}") continue script = build_dep(dep_name) + if gha_groups: + lines.append(f"@echo ::group::Running {script}") lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"') lines.append("if errorlevel 1 echo Build failed! && exit /B 1") + if gha_groups: + lines.append("@echo ::endgroup::") print() lines.append("@echo All Pillow dependencies built successfully!") write_script("build_dep_all.cmd", lines)