From f71d13bff64a95f6242771a20d85cb1cc1c77683 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 18 Jun 2026 23:37:01 +0200 Subject: [PATCH 01/11] Adding Ubunut 26 support --- scripts/makedeb.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/makedeb.sh b/scripts/makedeb.sh index 3932b43ee..6f86f6b08 100755 --- a/scripts/makedeb.sh +++ b/scripts/makedeb.sh @@ -41,6 +41,9 @@ ubuntu22) ubuntu24) depends="libqt5core5t64 (>= 5.15.13), libqt5designer5 (>= 5.15.13), libqt5gui5t64 (>= 5.15.13), libqt5multimedia5 (>= 5.15.13), libqt5multimediawidgets5 (>= 5.15.13), libqt5network5t64 (>= 5.15.13), libqt5opengl5t64 (>= 5.15.13), libqt5printsupport5t64 (>= 5.15.13), libqt5sql5t64 (>= 5.15.13), libqt5svg5 (>= 5.15.13), libqt5widgets5t64 (>= 5.15.13), libqt5xml5t64 (>= 5.15.13), libqt5xmlpatterns5 (>= 5.15.13), zlib1g (>= 1.3), libgit2-1.7 (>= 1.7.2), libruby3.2 (>= 3.2.3), python3 (>= 3.12.3), libpython3.12 (>= 3.12.3), libstdc++6 (>=14), libc6 (>= 2.39)" ;; +ubuntu26) + depends="libqt5core5t64 (>= 5.15.18), libqt5designer5 (>= 5.15.18), libqt5gui5t64 (>= 5.15.18), libqt5multimedia5 (>= 5.15.18), libqt5multimediawidgets5 (>= 5.15.18), libqt5network5t64 (>= 5.15.18), libqt5opengl5t64 (>= 5.15.18), libqt5printsupport5t64 (>= 5.15.18), libqt5sql5t64 (>= 5.15.18), libqt5svg5 (>= 5.15.18), libqt5widgets5t64 (>= 5.15.18), libqt5xml5t64 (>= 5.15.18), libqt5xmlpatterns5 (>= 5.15.18), zlib1g (>= 1.3), libgit2-1.7 (>= 1.9.1), libruby3.3 (>= 3.3.8), python3 (>= 3.14.3), libpython3.12 (>= 3.14.3), libstdc++6 (>=16), libc6 (>= 2.43)" + ;; *) echo "Unknown target '$target' (given as first argument)" exit 1 From 025a30ab2e6379969f2379309c3118ffb95053fa Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 18 Jun 2026 23:52:46 +0200 Subject: [PATCH 02/11] Fixing package dependencies for Ubuntu 26 --- scripts/makedeb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/makedeb.sh b/scripts/makedeb.sh index 6f86f6b08..690ecac45 100755 --- a/scripts/makedeb.sh +++ b/scripts/makedeb.sh @@ -42,7 +42,7 @@ ubuntu24) depends="libqt5core5t64 (>= 5.15.13), libqt5designer5 (>= 5.15.13), libqt5gui5t64 (>= 5.15.13), libqt5multimedia5 (>= 5.15.13), libqt5multimediawidgets5 (>= 5.15.13), libqt5network5t64 (>= 5.15.13), libqt5opengl5t64 (>= 5.15.13), libqt5printsupport5t64 (>= 5.15.13), libqt5sql5t64 (>= 5.15.13), libqt5svg5 (>= 5.15.13), libqt5widgets5t64 (>= 5.15.13), libqt5xml5t64 (>= 5.15.13), libqt5xmlpatterns5 (>= 5.15.13), zlib1g (>= 1.3), libgit2-1.7 (>= 1.7.2), libruby3.2 (>= 3.2.3), python3 (>= 3.12.3), libpython3.12 (>= 3.12.3), libstdc++6 (>=14), libc6 (>= 2.39)" ;; ubuntu26) - depends="libqt5core5t64 (>= 5.15.18), libqt5designer5 (>= 5.15.18), libqt5gui5t64 (>= 5.15.18), libqt5multimedia5 (>= 5.15.18), libqt5multimediawidgets5 (>= 5.15.18), libqt5network5t64 (>= 5.15.18), libqt5opengl5t64 (>= 5.15.18), libqt5printsupport5t64 (>= 5.15.18), libqt5sql5t64 (>= 5.15.18), libqt5svg5 (>= 5.15.18), libqt5widgets5t64 (>= 5.15.18), libqt5xml5t64 (>= 5.15.18), libqt5xmlpatterns5 (>= 5.15.18), zlib1g (>= 1.3), libgit2-1.7 (>= 1.9.1), libruby3.3 (>= 3.3.8), python3 (>= 3.14.3), libpython3.12 (>= 3.14.3), libstdc++6 (>=16), libc6 (>= 2.43)" + depends="libqt5core5t64 (>= 5.15.18), libqt5designer5 (>= 5.15.18), libqt5gui5t64 (>= 5.15.18), libqt5multimedia5 (>= 5.15.18), libqt5multimediawidgets5 (>= 5.15.18), libqt5network5t64 (>= 5.15.18), libqt5opengl5t64 (>= 5.15.18), libqt5printsupport5t64 (>= 5.15.18), libqt5sql5t64 (>= 5.15.18), libqt5svg5 (>= 5.15.18), libqt5widgets5t64 (>= 5.15.18), libqt5xml5t64 (>= 5.15.18), libqt5xmlpatterns5 (>= 5.15.18), zlib1g (>= 1.3), libgit2-1.9 (>= 1.9.1), libruby3.3 (>= 3.3.8), python3 (>= 3.14.3), libpython3.14 (>= 3.14.3), libstdc++6 (>=16), libc6 (>= 2.43)" ;; *) echo "Unknown target '$target' (given as first argument)" From d352db146fb62f0bf73f5a6175a99f53fee6c933 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 20 Jun 2026 00:05:09 +0200 Subject: [PATCH 03/11] Trying to fix unit tests with Python 3.14 - it delivers a different reference count under unit test conditions --- testdata/python/dbShapesTest.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/testdata/python/dbShapesTest.py b/testdata/python/dbShapesTest.py index 2be2b5c49..47622e442 100644 --- a/testdata/python/dbShapesTest.py +++ b/testdata/python/dbShapesTest.py @@ -53,13 +53,18 @@ class DBShapesTest(unittest.TestCase): # Issue #2012 (reference count) def test_2(self): + # an empty dict provides the normal reference count + obj = {} + rc = sys.getrefcount(obj) + ly = pya.Layout() + self.assertEqual(sys.getrefcount(ly), rc) top = ly.create_cell("TOP") l1 = ly.layer(1, 0) top.shapes(l1).insert(pya.Box(0, 0, 100, 200)) shapes = top.shapes(l1) - self.assertEqual(sys.getrefcount(shapes), 2) + self.assertEqual(sys.getrefcount(shapes), rc) # Tests the ability to take PolygonWithProperties instead of base class # for setter From 463f51dcb03d01b027fd35ddf0632386225f4ac7 Mon Sep 17 00:00:00 2001 From: Niko Savola Date: Fri, 5 Jun 2026 12:00:04 +0300 Subject: [PATCH 04/11] Remove dead CI configurations --- .travis.yml | 582 ----------------------------------------- dropbox-deployment.yml | 4 - travis-build.sh | 37 --- 3 files changed, 623 deletions(-) delete mode 100644 .travis.yml delete mode 100644 dropbox-deployment.yml delete mode 100755 travis-build.sh diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1d653e497..000000000 --- a/.travis.yml +++ /dev/null @@ -1,582 +0,0 @@ -branches: - only: - - /v\d+\.\d+(\.\d+)?(\S*)?$/ # tag format: *v0.25(.8)(dev14) - - staging - - master - -matrix: - include: - # python manylinux packages - - name: "cp39-cp39m-manylinux2014_x86_64.whl" - os: linux - sudo: true - language: python - python: '3.9' - services: - - docker - env: - - DOCKER_IMAGE="quay.io/pypa/manylinux2014_x86_64" - - PY_VERSION="cp39-cp39" - - DOCKER_BUILD=true - - TEST_IN_HOST=true - - MATRIX_EVAL="" - cache: - directories: - - ccache - - - name: "cp38-cp38m-manylinux2014_x86_64.whl" - os: linux - sudo: true - language: python - python: '3.8' - services: - - docker - env: - - DOCKER_IMAGE="quay.io/pypa/manylinux2014_x86_64" - - PY_VERSION="cp38-cp38" - - DOCKER_BUILD=true - - TEST_IN_HOST=true - - MATRIX_EVAL="" - cache: - directories: - - ccache - - - name: "cp37-cp37m-manylinux2014_x86_64.whl" - os: linux - sudo: true - language: python - python: '3.7-dev' - services: - - docker - env: - - DOCKER_IMAGE="quay.io/pypa/manylinux2014_x86_64" - - PY_VERSION="cp37-cp37m" - - DOCKER_BUILD=true - - TEST_IN_HOST=true - - MATRIX_EVAL="" - cache: - directories: - - ccache - - - name: "cp36-cp36m-manylinux2014_x86_64.whl" - os: linux - sudo: true - language: python - python: '3.6' - services: - - docker - env: - - DOCKER_IMAGE="quay.io/pypa/manylinux2014_x86_64" - - PY_VERSION="cp36-cp36m" - - DOCKER_BUILD=true - - TEST_IN_HOST=true - - MATRIX_EVAL="" - cache: - directories: - - ccache - - - name: "cp35-cp35m-manylinux2014_x86_64.whl" - os: linux - sudo: true - language: python - python: '3.5' - services: - - docker - env: - - DOCKER_IMAGE="quay.io/pypa/manylinux2014_x86_64" - - PY_VERSION="cp35-cp35m" - - DOCKER_BUILD=true - - TEST_IN_HOST=true - - MATRIX_EVAL="" - cache: - directories: - - ccache - - - name: "cp27-cp27m-manylinux2014_x86_64.whl" - os: linux - sudo: true - language: python - python: '2.7' - services: - - docker - env: - - DOCKER_IMAGE="quay.io/pypa/manylinux2014_x86_64" - - PY_VERSION="cp27-cp27m" - - DOCKER_BUILD=true - - TEST_IN_HOST=false # travis's python 2.7 uses ucs4 (mu), so this test fails. - - MATRIX_EVAL="" - cache: - directories: - - ccache - - # python 3 osx - - # MacOS 10.15 - - # MacOS 10.15, Python 3.9 - - name: "cp39-cp39m-macosx_10_15_x86_64.whl" - os: osx - osx_image: xcode12 # macOS 10.15 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - python@3.9 - - ccache - update: true - env: - - MATRIX_EVAL="shopt -s expand_aliases; alias python='/usr/local/opt/python@3.9/bin/python3'; alias pip='/usr/local/opt/python@3.9/bin/pip3';" - - ARCHFLAGS="-std=c++11" - - PIP_UPDATE="1" - - PYTHON_BUILD=true - - # MacOS 10.15, Python 3.8 - - name: "cp38-cp38m-macosx_10_15_x86_64.whl" - os: osx - osx_image: xcode12 # macOS 10.15 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - python@3.8 - - ccache - update: true - env: - - MATRIX_EVAL="shopt -s expand_aliases; alias python='/usr/local/opt/python@3.8/bin/python3'; alias pip='/usr/local/opt/python@3.8/bin/pip3';" - - ARCHFLAGS="-std=c++11" - - PIP_UPDATE="1" - - PYTHON_BUILD=true - - # MacOS 10.15, Python 3.7 - - name: "cp37-cp37m-macosx_10_15_x86_64.whl" - os: osx - osx_image: xcode12 # macOS 10.15 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - python@3.7 - - ccache - update: true - env: - - MATRIX_EVAL="shopt -s expand_aliases; alias python='/usr/local/opt/python@3.7/bin/python3'; alias pip='/usr/local/opt/python@3.7/bin/pip3';" - - ARCHFLAGS="-std=c++11" - - PIP_UPDATE="1" - - PYTHON_BUILD=true - - # MacOS 10.14 - - # MacOS 10.14, Python 3.9 - - name: "cp39-cp39m-macosx_10_14_x86_64.whl" - os: osx - osx_image: xcode11 # macOS 10.14 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - python@3.9 - - ccache - update: true - env: - - MATRIX_EVAL="shopt -s expand_aliases; alias python='/usr/local/opt/python@3.9/bin/python3'; alias pip='/usr/local/opt/python@3.9/bin/pip3';" - - ARCHFLAGS="-std=c++11" - - PIP_UPDATE="1" - - PYTHON_BUILD=true - - # MacOS 10.14, Python 3.8 - - name: "cp38-cp38m-macosx_10_14_x86_64.whl" - os: osx - osx_image: xcode11 # macOS 10.14 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - python@3.8 - - ccache - update: true - env: - - MATRIX_EVAL="shopt -s expand_aliases; alias python='/usr/local/opt/python@3.8/bin/python3'; alias pip='/usr/local/opt/python@3.8/bin/pip3';" - - ARCHFLAGS="-std=c++11" - - PIP_UPDATE="1" - - PYTHON_BUILD=true - - # MacOS 10.14, Python 3.7 - - name: "cp37-cp37m-macosx_10_14_x86_64.whl" - os: osx - osx_image: xcode11 # macOS 10.14 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - python@3.7 - - ccache - update: true - env: - - MATRIX_EVAL="shopt -s expand_aliases; alias python='/usr/local/opt/python@3.7/bin/python3'; alias pip='/usr/local/opt/python@3.7/bin/pip3';" - - ARCHFLAGS="-std=c++11" - - PIP_UPDATE="1" - - PYTHON_BUILD=true - - # MacOS 10.13 - - # MacOS 10.13, Python 3.9 - - name: "cp39-cp39m-macosx_10_13_x86_64.whl" - os: osx - osx_image: xcode10.1 # macOS 10.13 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - python@3.9 - - ccache - update: true - env: - - MATRIX_EVAL="shopt -s expand_aliases; alias python='/usr/local/opt/python@3.9/bin/python3'; alias pip='/usr/local/opt/python@3.9/bin/pip3';" - - ARCHFLAGS="-std=c++11" - - PIP_UPDATE="1" - - PYTHON_BUILD=true - - # MacOS 10.13, Python 3.8 - - name: "cp38-cp38m-macosx_10_13_x86_64.whl" - os: osx - osx_image: xcode10.1 # macOS 10.13 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - python@3.8 - - ccache - update: true - env: - - MATRIX_EVAL="shopt -s expand_aliases; alias python='/usr/local/opt/python@3.8/bin/python3'; alias pip='/usr/local/opt/python@3.8/bin/pip3';" - - ARCHFLAGS="-std=c++11" - - PIP_UPDATE="1" - - PYTHON_BUILD=true - - # MacOS 10.13, Python 3.7 - - name: "cp37-cp37m-macosx_10_13_x86_64.whl" - os: osx - osx_image: xcode10.1 # macOS 10.13 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - python@3.7 - - ccache - update: true - env: - - MATRIX_EVAL="shopt -s expand_aliases; alias python='/usr/local/opt/python@3.7/bin/python3'; alias pip='/usr/local/opt/python@3.7/bin/pip3';" - - ARCHFLAGS="-std=c++11" - - PIP_UPDATE="1" - - PYTHON_BUILD=true - - # MacOS 10.12, Python 3.7 - - # - name: "klayout python3 osx10.12" - - name: "cp37-cp37m-macosx_10_12_x86_64.whl" - os: osx - osx_image: xcode9.2 # macOS 10.12 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - python3 - - ccache - update: true - env: - - MATRIX_EVAL="shopt -s expand_aliases; alias python='python3'; alias pip='pip3';" - - ARCHFLAGS="-std=c++11" - - PIP_UPDATE="1" - - PYTHON_BUILD=true - - exclude: - - # No XCode 8.2.1 for Mac OS 10.11 - - # - name: "klayout python3 osx10.11" - - name: "cp37-cp37m-macosx_10_11_x86_64.whl" - os: osx - osx_image: xcode8 # macOS 10.11 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - ccache - update: true - env: - - MATRIX_EVAL="brew update; brew config; brew upgrade python; brew postinstall python; ls -l /usr/local/opt/python/libexec/bin/; shopt -s expand_aliases; alias python='/usr/local/opt/python/libexec/bin/python'; alias pip='/usr/local/opt/python/libexec/bin/pip';" - - ARCHFLAGS="-std=c++11" - - PIP_UPDATE="1" - - PYTHON_BUILD=true - - # Python 2 is EOL - - # python 2 osx - - # - name: "klayout python2 osx10.13" - - name: "cp27-cp27m-macosx_10_13_x86_64.whl" - os: osx - osx_image: xcode9.4 # macOS 10.13 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - ccache - update: true - env: - - MATRIX_EVAL="" - - ARCHFLAGS="-std=c++11" - - PIP_UPDATE="1" - - PYTHON_BUILD=true - - # - name: "klayout python2 osx10.12" - - name: "cp27-cp27m-macosx_10_12_x86_64.whl" - os: osx - osx_image: xcode8.3 # macOS 10.12 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - ccache - update: true - env: - - MATRIX_EVAL="brew install python2 || brew link --overwrite python@2" # deficient python2 in travis's xcode8.3 (no ssl) - - ARCHFLAGS="-std=c++11" - - PIP_UPDATE="1" - - PYTHON_BUILD=true - - # - name: "klayout python2 osx10.11" - - name: "cp27-cp27m-macosx_10_11_x86_64.whl" - os: osx - osx_image: xcode8 # macOS 10.11 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - ccache - update: true - env: - - MATRIX_EVAL="" - - ARCHFLAGS="-std=c++11" - - PIP_UPDATE="1" - - PYTHON_BUILD=true - - # KLayout builds not enabled for now on MacOS - - # KLayout builds for mac - # Python 3 - - name: "KLayout macOS 10.13 with py3.7" - os: osx - osx_image: xcode9.4 # macOS 10.13 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - python3 - - qt - - ccache - update: true - env: - - MATRIX_EVAL="" - - PYTHON_VERSION=B37 - - MACOS_VERSION=HighSierra - - KLAYOUT_BUILD=true - - - name: "KLayout macOS 10.12 with py3.7" - os: osx - osx_image: xcode8.3 # macOS 10.12 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - python3 - - qt - - ccache - update: true - env: - - MATRIX_EVAL="" - - PYTHON_VERSION=B37 - - MACOS_VERSION=Sierra - - KLAYOUT_BUILD=true - - - name: "KLayout macOS 10.11 with py3.7" - os: osx - osx_image: xcode8 # macOS 10.11 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - python3 - - ccache - update: true - env: - - MATRIX_EVAL="brew update; brew config; brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/56c500b569c724b049be7ab9e12d9693f85522f9/Formula/qt.rb" # Qt 5.11.2 - - PYTHON_VERSION=B37 - - MACOS_VERSION=ElCapitan - - KLAYOUT_BUILD=true - - # Python 2 - - name: "KLayout macOS 10.13 with py2.7" - os: osx - osx_image: xcode9.4 # macOS 10.13 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - qt - - ccache - update: true - env: - - MATRIX_EVAL="" - - PYTHON_VERSION=Sys - - MACOS_VERSION=HighSierra - - KLAYOUT_BUILD=true - - - name: "KLayout macOS 10.12 with py2.7" - os: osx - osx_image: xcode8.3 # macOS 10.12 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - qt - - ccache - update: true - env: - - MATRIX_EVAL="" - - PYTHON_VERSION=Sys - - MACOS_VERSION=Sierra - - KLAYOUT_BUILD=true - - - name: "KLayout macOS 10.11 with py2.7" - os: osx - osx_image: xcode8 # macOS 10.11 - cache: - directories: - - ccache - addons: - homebrew: - packages: - - ccache - update: true - env: - - MATRIX_EVAL="brew update; brew config; brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/56c500b569c724b049be7ab9e12d9693f85522f9/Formula/qt.rb" # Qt 5.11.2 - - PYTHON_VERSION=Sys - - MACOS_VERSION=ElCapitan - - KLAYOUT_BUILD=true - -before_install: - - env - - gem install dropbox-deployment - - if [ "${TRAVIS_OS_NAME}" == "osx" ]; then - export PATH="/usr/local/opt/ccache/libexec:$PATH"; - mkdir -p ccache; - export CCACHE_DIR="`pwd`/ccache"; - ccache -s; - fi - - eval "${MATRIX_EVAL}" - - if [ "${PIP_UPDATE}" == "1" ]; then - pip --version; - pip install --upgrade pip || sudo pip install --upgrade pip; - pip --version; - pip install --upgrade setuptools wheel || sudo pip install --upgrade setuptools wheel; - fi - - python -c "import distutils.sysconfig as sysconfig; print(sysconfig.__file__)" - -install: - - if [ "$DOCKER_BUILD" = true ]; then - docker pull $DOCKER_IMAGE; - fi - -script: - - if [ "$DOCKER_BUILD" = true ]; then - mkdir -p ccache; - mkdir -p wheelhouse; - docker run --rm -e DOCKER_IMAGE -e PY_VERSION -v `pwd`:/io $DOCKER_IMAGE $PRE_CMD "/io/ci-scripts/docker/docker_build.sh"; - klayout_version=$(python -c 'import setup; print(setup.Config().version())'); - mkdir -p deploy/dist-pymod/$klayout_version; - cp -a wheelhouse/{klayout-*manylinux2014*.whl,*.zip} deploy/dist-pymod/$klayout_version; - if [ "$TEST_IN_HOST" = true ]; then - pip install klayout --no-index -f ./wheelhouse; - python testdata/pymod/import_db.py; - python testdata/pymod/import_rdb.py; - python testdata/pymod/import_tl.py; - python testdata/pymod/import_lib.py; - python testdata/pymod/pya_tests.py; - fi - fi - - if [ "$PYTHON_BUILD" = true ]; then - python setup.py build; - python setup.py bdist_wheel; - python setup.py install; - python setup.py sdist --formats=zip; - python testdata/pymod/import_db.py; - python testdata/pymod/import_rdb.py; - python testdata/pymod/import_tl.py; - python testdata/pymod/import_lib.py; - python testdata/pymod/pya_tests.py; - klayout_version=$(python -c 'import setup; print(setup.Config().version())'); - mkdir -p deploy/dist-pymod/$klayout_version; - cp -a dist/{*.whl,*.zip} deploy/dist-pymod/$klayout_version; - python -c 'import klayout.db as db; print(dir(db))'; - python -c 'import klayout.rdb as rdb; print(dir(rdb))'; - python -c 'import klayout.tl as tl; print(dir(tl))'; - fi - - if [ "$KLAYOUT_BUILD" = true ]; then - ./travis-build.sh; - fi - -before_cache: - - if [ "${TRAVIS_OS_NAME}" == "osx" ]; then - ccache -s; - fi - -after_success: - # upload to dropbox - # need DROPBOX_OAUTH_BEARER environment variable - - dropbox-deployment - - # uploading to pypi using twine - # need TWINE_USERNAME, TWINE_PASSWORD and TWINE_REPOSITORY_URL env variable - - if [ ! -z "$TRAVIS_TAG" ] && [ ! -z "$TWINE_PASSWORD" ] && [[ "$PYTHON_BUILD" = true || "$DOCKER_BUILD" = true ]]; then - pip install -U twine || sudo pip install -U twine; - twine upload --skip-existing deploy/dist-pymod/$klayout_version/*; - fi diff --git a/dropbox-deployment.yml b/dropbox-deployment.yml deleted file mode 100644 index f9ee0f21b..000000000 --- a/dropbox-deployment.yml +++ /dev/null @@ -1,4 +0,0 @@ -deploy: - dropbox_path: /Builds/klayout # The path to the folder on Dropbox where the files will go - artifacts_path: deploy # can be a single file, or a path - debug: true # if you want to see more logs diff --git a/travis-build.sh b/travis-build.sh deleted file mode 100755 index 9d5acabc3..000000000 --- a/travis-build.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -set -e - -export PING_SLEEP=30s - -bash -c "while true; do find qt* | wc -l; sleep $PING_SLEEP; done" & -PING_LOOP_PID=$! - -touch build.txt - -# Configure ccache -mkdir -p ccache; -export CCACHE_DIR="`pwd`/ccache" -export QMAKE_CCACHE=1 - -# Show ccache stats -echo "Cache stats:" -ccache -s - -echo "build" -make build >> build.txt 2>&1 || tail -500 build.txt -echo "deploy" -make deploy >> build.txt 2>&1 || tail -500 build.txt -echo "test" -make test >> build.txt 2>&1 || tail -500 build.txt -echo "dropbox-deploy" -make dropbox-deploy - -# Show ccache stats -echo "Cache stats:" -ccache -s - -echo "build finished" - -kill $PING_LOOP_PID - -exit 0 From af66d6c4ab4445c476e39be6a136508b740792f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:12:34 +0000 Subject: [PATCH 05/11] Bump pypa/cibuildwheel from 3.4.1 to 4.1.0 Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 3.4.1 to 4.1.0. - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](https://github.com/pypa/cibuildwheel/compare/v3.4.1...v4.1.0) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-version: 4.1.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 01b41cd1e..9b34ec823 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -95,7 +95,7 @@ jobs: mkdir -p $HOST_CCACHE_DIR - name: Build wheels (ARM) if: matrix.os == 'ubuntu-24.04-arm' - uses: pypa/cibuildwheel@v3.4.1 + uses: pypa/cibuildwheel@v4.1.0 env: # override the default CentOS “yum install … ccache” and drop ccache CIBW_BEFORE_ALL_LINUX: | @@ -110,7 +110,7 @@ jobs: - name: Build wheels (all other platforms) if: matrix.os != 'ubuntu-24.04-arm' - uses: pypa/cibuildwheel@v3.4.1 + uses: pypa/cibuildwheel@v4.1.0 env: CIBW_BUILD: ${{ matrix.cibuild }} CIBW_ARCHS_MACOS: ${{ matrix.macos-arch }} From b57b862a023e4aae4a3568ae774af87223143576 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:12:39 +0000 Subject: [PATCH 06/11] Bump actions/checkout from 6 to 7 Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 6 +++--- .github/workflows/cache-maintenance.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 01b41cd1e..52676f638 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: outputs: code: ${{ steps.filter.outputs.code == 'true' || github.event_name == 'release' }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: dorny/paths-filter@v4 id: filter if: github.event_name != 'release' @@ -78,7 +78,7 @@ jobs: haskell: true large-packages: true - uses: hmarr/debug-action@v3 - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: ccache if: matrix.os != 'ubuntu-24.04-arm' uses: hendrikmuhs/ccache-action@v1.2 @@ -139,7 +139,7 @@ jobs: if: needs.changes.outputs.code == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Build SDist run: pipx run build --sdist diff --git a/.github/workflows/cache-maintenance.yml b/.github/workflows/cache-maintenance.yml index f57190c28..8208d0e97 100644 --- a/.github/workflows/cache-maintenance.yml +++ b/.github/workflows/cache-maintenance.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Cleanup run: | From 7d6aa2e728fa5efa1c80ef8eb7cf9a1ab31411a6 Mon Sep 17 00:00:00 2001 From: Kirill Osipov Date: Tue, 30 Jun 2026 23:09:20 +0200 Subject: [PATCH 07/11] Use multiple drawing workers for image_with_options rendering LayoutCanvas::image_with_options always rendered the layout with a single thread (workers = 0), which serialized image generation even on views configured for parallel drawing. Now it honors the view's drawing_workers() count when the view is not in synchronous mode, and waits for the asynchronous workers to finish before reading back the rendered bitmaps. This speeds up image export on large layouts while preserving the original single-threaded behavior in synchronous mode. --- src/laybasic/laybasic/layLayoutCanvas.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/laybasic/laybasic/layLayoutCanvas.cc b/src/laybasic/laybasic/layLayoutCanvas.cc index 2d93485f0..6ddd22956 100644 --- a/src/laybasic/laybasic/layLayoutCanvas.cc +++ b/src/laybasic/laybasic/layLayoutCanvas.cc @@ -855,7 +855,9 @@ LayoutCanvas::image_with_options (unsigned int width, unsigned int height, int l lay::RedrawThread redraw_thread (&rd_canvas, mp_view); // render the layout - redraw_thread.start (0 /*synchronous*/, m_layers, vp, resolution, font_resolution, true); + int workers = mp_view->synchronous () ? 0 : mp_view->drawing_workers (); + redraw_thread.start (workers, m_layers, vp, resolution, font_resolution, true); + redraw_thread.wait (); redraw_thread.stop (); // safety // paint the background objects. It uses "img" to paint on. From 35ec1107b6e7eec6ee65e6a9cbd36d49543d7d38 Mon Sep 17 00:00:00 2001 From: Kirill Osipov Date: Tue, 30 Jun 2026 17:08:18 +0200 Subject: [PATCH 08/11] Add PixelBuffer#to_bytes for zero-overhead RGBA pixel access Exposes a GSI method on tl::PixelBuffer that returns the raw ARGB32 contents as a byte vector (4 bytes per pixel, row-major, top to bottom) via a single memcpy. Unlike to_png_data this skips all image encoding, which is useful when the caller just wants direct pixel data. --- src/gsi/gsi/gsiDeclTlPixelBuffer.cc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/gsi/gsi/gsiDeclTlPixelBuffer.cc b/src/gsi/gsi/gsiDeclTlPixelBuffer.cc index c78cd1b1d..a0fae9f7a 100644 --- a/src/gsi/gsi/gsiDeclTlPixelBuffer.cc +++ b/src/gsi/gsi/gsiDeclTlPixelBuffer.cc @@ -126,6 +126,14 @@ static std::vector pixel_buffer_to_png (const tl::PixelBuffer *pb) #endif } +// Returns the raw ARGB32 pixel data as a byte vector (no encoding overhead) +static std::vector pixel_buffer_to_bytes (const tl::PixelBuffer *pb) +{ + const char *p = (const char *) pb->data (); + size_t n = (size_t) pb->width () * (size_t) pb->height () * 4; + return std::vector (p, p + n); +} + Class decl_PixelBuffer ("lay", "PixelBuffer", gsi::constructor ("new", &create_pixel_buffer, gsi::arg ("width"), gsi::arg ("height"), @@ -188,6 +196,14 @@ Class decl_PixelBuffer ("lay", "PixelBuffer", "\n" "This method may not be available if PNG support is not compiled into KLayout." ) + + gsi::method_ext ("to_bytes", &pixel_buffer_to_bytes, + "@brief Converts the pixel buffer to a raw byte stream\n" + "\n" + "Returns the raw ARGB32 pixel data with 4 bytes per pixel in row-major order, " + "top to bottom. Unlike \\to_png_data this method has zero encoding overhead.\n" + "\n" + "This method has been added in version 0.30.9." + ) + gsi::method ("patch", &tl::PixelBuffer::patch, gsi::arg ("other"), "@brief Patches another pixel buffer into this one\n" "\n" From 43a5d8c548bd103ef9d634f48493260f4bdd5f82 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 9 Jul 2026 23:43:25 +0200 Subject: [PATCH 09/11] Fixed issue #2384 Problem was that the magnification was applied twice when running the checks inside compound DRC checks. --- src/db/db/dbCompoundOperation.cc | 12 ++---------- src/drc/unit_tests/drcSimpleTests.cc | 10 ++++++++++ testdata/drc/drcSimpleTests_149.drc | 18 ++++++++++++++++++ testdata/drc/drcSimpleTests_149.gds | Bin 0 -> 287 bytes testdata/drc/drcSimpleTests_au149.gds | Bin 0 -> 660 bytes testdata/drc/drcSimpleTests_au149d.gds | Bin 0 -> 660 bytes 6 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 testdata/drc/drcSimpleTests_149.drc create mode 100644 testdata/drc/drcSimpleTests_149.gds create mode 100644 testdata/drc/drcSimpleTests_au149.gds create mode 100644 testdata/drc/drcSimpleTests_au149d.gds diff --git a/src/db/db/dbCompoundOperation.cc b/src/db/db/dbCompoundOperation.cc index dc9953582..cd4ea75a9 100644 --- a/src/db/db/dbCompoundOperation.cc +++ b/src/db/db/dbCompoundOperation.cc @@ -1682,13 +1682,9 @@ CompoundRegionCheckOperationNode::computed_dist () const void CompoundRegionCheckOperationNode::do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase *proc) const { - // consider magnification variants - db::EdgeRelationFilter check = m_check; - check.set_distance (proc->dist_for_cell (cell, check.distance ())); - // TODO: needs a concept to deal with merged/non-merged inputs bool is_merged = true; - db::check_local_operation op (check, m_different_polygons, is_merged, m_has_other, m_is_other_merged, m_options); + db::check_local_operation op (m_check, m_different_polygons, is_merged, m_has_other, m_is_other_merged, m_options); tl_assert (results.size () == 1); if (results.front ().empty ()) { @@ -1704,13 +1700,9 @@ CompoundRegionCheckOperationNode::do_compute_local (CompoundRegionOperationCache void CompoundRegionCheckOperationNode::do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase *proc) const { - // consider magnification variants - db::EdgeRelationFilter check = m_check; - check.set_distance (proc->dist_for_cell (cell, check.distance ())); - // TODO: needs a concept to deal with merged/non-merged inputs bool is_merged = true; - db::check_local_operation op (check, m_different_polygons, is_merged, m_has_other, m_is_other_merged, m_options); + db::check_local_operation op (m_check, m_different_polygons, is_merged, m_has_other, m_is_other_merged, m_options); tl_assert (results.size () == 1); if (results.front ().empty ()) { diff --git a/src/drc/unit_tests/drcSimpleTests.cc b/src/drc/unit_tests/drcSimpleTests.cc index 2d6db90f1..40fcf0376 100644 --- a/src/drc/unit_tests/drcSimpleTests.cc +++ b/src/drc/unit_tests/drcSimpleTests.cc @@ -2113,3 +2113,13 @@ TEST(148_sparse_array_limit) run_test (_this, "148", true); } +// issue 2384 +TEST(149_compound_drc_with_mag) +{ + run_test (_this, "149", false); +} + +TEST(149d_compound_drc_with_mag) +{ + run_test (_this, "149", true); +} diff --git a/testdata/drc/drcSimpleTests_149.drc b/testdata/drc/drcSimpleTests_149.drc new file mode 100644 index 000000000..d08836713 --- /dev/null +++ b/testdata/drc/drcSimpleTests_149.drc @@ -0,0 +1,18 @@ +source $drc_test_source +target $drc_test_target + +if $drc_test_deep + deep +end + +deep + +metal2_drawn = polygons(36, 0) +metal2_slot = polygons(36, 3) + +metal2_drawn.output(36, 0) +metal2_slot.output(36, 3) + +violations_enclosed = metal2_slot.drc(enclosed(metal2_drawn) < 10.um) +violations_enclosed.output(100, 0) + diff --git a/testdata/drc/drcSimpleTests_149.gds b/testdata/drc/drcSimpleTests_149.gds new file mode 100644 index 0000000000000000000000000000000000000000..a1d4e0d451d81326d9c4a7827f639b406347ce99 GIT binary patch literal 287 zcmV+)0pR{0iwFP!000001AWmkOT$1E2k?Jil9wo#P)l^sfQ#TFSkS49*IE%FXod>? z0`4v%nlB(Iegi?oRgsR)x;yBuj^ZwEK6@S~X!@34_;dffg9C%8kE}-F8$yg>3Mr!B z--w9wjSi4{!Ag4gvVNVGt;vJa*XBtJSy0_m%$;_3buG^UKLR)=%z+bvhlRQamG7ck zYU4!#)zeO!i~fZ4LyX)NG559#IX4cdhTF^2HApfO=%wK-LuG-+&nUTto#B>$Zb=_c zhzHtuTL*paIBh=A)&0F2B=e8P#Y@n>cG_J0+lT3Io+CbP8lU^1vm>X?#mfG}y*c-- l^t{W(o+)U2%kTK#{qkP!AVAcEU!w9m${4~JW>6hm!zK}}wgJzuTHgNa z%9WQiNB5538b?j|Rr>;?!mb*t`|Ag9imzkR4%qP0^vhYcvc^gt&Ea5Qz!Ec74lGe0GJ#^hzr z)bH;X`rO~eo$Bvye1MV|Kh!yM(BD+A+@C+`z@4h`f4BG^9y8W2_&c00bTab3^7)lb F6W>qqVq5?K literal 0 HcmV?d00001 diff --git a/testdata/drc/drcSimpleTests_au149d.gds b/testdata/drc/drcSimpleTests_au149d.gds new file mode 100644 index 0000000000000000000000000000000000000000..0bc742300ba5e2b90ddd418f9a0dbc82e206dfd6 GIT binary patch literal 660 zcmaKpF-yZh6vzL4Nv=^Yp-M!k0TG0P2rVcMT^egdgrF%@=m*fW(o+)U2%kTK#{qkP!AVAcEU!w9m${4~pW>6hm!zK}}wgJzuTHgNa z%9WQi$M%lj8b?j|Rr>;?!mb*t`|Ag9imzkR4%qP0^vhYcvc^gt&Ea5Qz!Ec74lGe0GJ#^hzr z)bH;X`rO~eo$Bvye1MV|Kh!yM(BD+A+@C+`z@4h`f4BG^9y8W2_&c00bTab3^7)lb F6W`M*VweB` literal 0 HcmV?d00001 From 2fa7b2434563c90aefb2c9a29a41bf7027d67873 Mon Sep 17 00:00:00 2001 From: Kirill Osipov Date: Thu, 16 Jul 2026 19:03:34 +0200 Subject: [PATCH 10/11] Add PixelBuffer#from_bytes and width/height header to to_bytes --- src/gsi/gsi/gsiDeclTlPixelBuffer.cc | 43 ++++++++++++++++++++++++----- testdata/python/layPixelBuffer.py | 21 ++++++++++++++ testdata/ruby/layPixelBuffer.rb | 26 +++++++++++++++++ 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/gsi/gsi/gsiDeclTlPixelBuffer.cc b/src/gsi/gsi/gsiDeclTlPixelBuffer.cc index a0fae9f7a..a02a98eeb 100644 --- a/src/gsi/gsi/gsiDeclTlPixelBuffer.cc +++ b/src/gsi/gsi/gsiDeclTlPixelBuffer.cc @@ -126,12 +126,32 @@ static std::vector pixel_buffer_to_png (const tl::PixelBuffer *pb) #endif } -// Returns the raw ARGB32 pixel data as a byte vector (no encoding overhead) +// Returns an 8-byte header (width, height as uint32) followed by the raw ARGB32 pixel data static std::vector pixel_buffer_to_bytes (const tl::PixelBuffer *pb) { - const char *p = (const char *) pb->data (); - size_t n = (size_t) pb->width () * (size_t) pb->height () * 4; - return std::vector (p, p + n); + uint32_t w = pb->width (), h = pb->height (); + size_t n = (size_t) w * (size_t) h * 4; + std::vector data (8 + n); + memcpy (data.data (), &w, 4); + memcpy (data.data () + 4, &h, 4); + memcpy (data.data () + 8, pb->data (), n); + return data; +} + +// Inverse of to_bytes: reads width/height from the header and checks the length +static tl::PixelBuffer pixel_buffer_from_bytes (const std::vector &data) +{ + uint32_t w = 0, h = 0; + if (data.size () >= 8) { + memcpy (&w, data.data (), 4); + memcpy (&h, data.data () + 4, 4); + } + if (data.size () != 8 + (size_t) w * (size_t) h * 4) { + throw tl::Exception (tl::to_string (tr ("Invalid PixelBuffer byte stream: length does not match the width and height in the header"))); + } + tl::PixelBuffer pb (w, h, (const tl::color_t *) (data.data () + 8)); + pb.set_transparent (true); + return pb; } @@ -199,10 +219,19 @@ Class decl_PixelBuffer ("lay", "PixelBuffer", gsi::method_ext ("to_bytes", &pixel_buffer_to_bytes, "@brief Converts the pixel buffer to a raw byte stream\n" "\n" - "Returns the raw ARGB32 pixel data with 4 bytes per pixel in row-major order, " - "top to bottom. Unlike \\to_png_data this method has zero encoding overhead.\n" + "The stream starts with an 8-byte header (width and height as 32-bit unsigned integers) " + "followed by the raw ARGB32 pixel data with 4 bytes per pixel in row-major order, " + "top to bottom. Unlike \\to_png_data this method has zero encoding overhead. " + "Use \\from_bytes to reconstruct the pixel buffer.\n" "\n" - "This method has been added in version 0.30.9." + "This method has been added in version 0.30.10." + ) + + gsi::method ("from_bytes", &pixel_buffer_from_bytes, gsi::arg ("data"), + "@brief Reconstructs a pixel buffer from a byte stream produced by \\to_bytes\n" + "\n" + "The width and height are taken from the header and the stream length is checked against them.\n" + "\n" + "This method has been added in version 0.30.10." ) + gsi::method ("patch", &tl::PixelBuffer::patch, gsi::arg ("other"), "@brief Patches another pixel buffer into this one\n" diff --git a/testdata/python/layPixelBuffer.py b/testdata/python/layPixelBuffer.py index 8a91ea189..77e24183d 100644 --- a/testdata/python/layPixelBuffer.py +++ b/testdata/python/layPixelBuffer.py @@ -57,6 +57,27 @@ class LAYPixelBufferTests(unittest.TestCase): pb_copy = pya.PixelBuffer.read_png(tmp) self.assertEqual(compare(pb, pb_copy), True) + def test_2(self): + + # to_bytes / from_bytes round-trip + + import struct + + pb = pya.PixelBuffer(10, 20) + pb.transparent = True + pb.fill(0xf0010203) + pb.set_pixel(1, 2, 0x80102030) + + data = bytes(pb.to_bytes()) + self.assertEqual(len(data), 8 + 10 * 20 * 4) + self.assertEqual(struct.unpack("=II", data[0:8]), (10, 20)) # width, height header + + self.assertEqual(pb == pya.PixelBuffer.from_bytes(data), True) + + # a mismatched stream is rejected + with self.assertRaises(Exception): + pya.PixelBuffer.from_bytes(data[:-4]) + # run unit tests if __name__ == '__main__': diff --git a/testdata/ruby/layPixelBuffer.rb b/testdata/ruby/layPixelBuffer.rb index abcb3254b..e8c0db63f 100644 --- a/testdata/ruby/layPixelBuffer.rb +++ b/testdata/ruby/layPixelBuffer.rb @@ -152,6 +152,32 @@ class LAYPixelBuffer_TestClass < TestBase end + def test_5 + + # to_bytes / from_bytes round-trip + + pb = RBA::PixelBuffer::new(10, 20) + pb.transparent = true + pb.fill(0xf0010203) + pb.set_pixel(1, 2, 0x80102030) + + bytes = pb.to_bytes + assert_equal(bytes.size, 8 + 10 * 20 * 4) + assert_equal(bytes[0, 8].unpack("VV"), [ 10, 20 ]) # width, height header + + assert_equal(pb == RBA::PixelBuffer.from_bytes(bytes), true) + + # a mismatched stream is rejected + error = false + begin + RBA::PixelBuffer.from_bytes(bytes[0, bytes.size - 4]) + rescue + error = true + end + assert_equal(error, true) + + end + def test_11 pb = RBA::BitmapBuffer::new From 9a606ec4e9f6979c4828815865d8fed6ae87e9d4 Mon Sep 17 00:00:00 2001 From: Kirill Osipov Date: Thu, 16 Jul 2026 19:03:34 +0200 Subject: [PATCH 11/11] Cap PixelBuffer.from_bytes dimensions at 64k x 64k Following review feedback, the reader now rejects a header claiming a width or height beyond 65536, guarding against malformed byte streams that would otherwise trigger an unreasonable allocation. The writer stays full 32-bit ('unlimited'), and native little-endian order and the transparency mask are kept as-is. Adds Ruby and Python tests for the oversized-header rejection. Ref: https://github.com/KLayout/klayout/pull/2387#issuecomment-4984941409 --- src/gsi/gsi/gsiDeclTlPixelBuffer.cc | 9 ++++++++- testdata/python/layPixelBuffer.py | 4 ++++ testdata/ruby/layPixelBuffer.rb | 9 +++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/gsi/gsi/gsiDeclTlPixelBuffer.cc b/src/gsi/gsi/gsiDeclTlPixelBuffer.cc index a02a98eeb..56b45a655 100644 --- a/src/gsi/gsi/gsiDeclTlPixelBuffer.cc +++ b/src/gsi/gsi/gsiDeclTlPixelBuffer.cc @@ -146,6 +146,12 @@ static tl::PixelBuffer pixel_buffer_from_bytes (const std::vector &data) memcpy (&w, data.data (), 4); memcpy (&h, data.data () + 4, 4); } + // sanity guard against malformed headers: reader caps dimensions at 64k x 64k + // (generous for finely-resolved drawings), writer stays full 32-bit + const uint32_t max_dim = 65536; + if (w > max_dim || h > max_dim) { + throw tl::Exception (tl::to_string (tr ("Invalid PixelBuffer byte stream: width or height exceeds the 65536 x 65536 limit"))); + } if (data.size () != 8 + (size_t) w * (size_t) h * 4) { throw tl::Exception (tl::to_string (tr ("Invalid PixelBuffer byte stream: length does not match the width and height in the header"))); } @@ -229,7 +235,8 @@ Class decl_PixelBuffer ("lay", "PixelBuffer", gsi::method ("from_bytes", &pixel_buffer_from_bytes, gsi::arg ("data"), "@brief Reconstructs a pixel buffer from a byte stream produced by \\to_bytes\n" "\n" - "The width and height are taken from the header and the stream length is checked against them.\n" + "The width and height are taken from the header and the stream length is checked against them. " + "The dimensions are capped at 65536 x 65536; a header exceeding this limit raises an error.\n" "\n" "This method has been added in version 0.30.10." ) + diff --git a/testdata/python/layPixelBuffer.py b/testdata/python/layPixelBuffer.py index 77e24183d..f04ebfde1 100644 --- a/testdata/python/layPixelBuffer.py +++ b/testdata/python/layPixelBuffer.py @@ -78,6 +78,10 @@ class LAYPixelBufferTests(unittest.TestCase): with self.assertRaises(Exception): pya.PixelBuffer.from_bytes(data[:-4]) + # a header beyond the 64k x 64k limit is rejected + with self.assertRaises(Exception): + pya.PixelBuffer.from_bytes(struct.pack("=II", 65537, 1)) + # run unit tests if __name__ == '__main__': diff --git a/testdata/ruby/layPixelBuffer.rb b/testdata/ruby/layPixelBuffer.rb index e8c0db63f..d6054c453 100644 --- a/testdata/ruby/layPixelBuffer.rb +++ b/testdata/ruby/layPixelBuffer.rb @@ -176,6 +176,15 @@ class LAYPixelBuffer_TestClass < TestBase end assert_equal(error, true) + # a header beyond the 64k x 64k limit is rejected + error = false + begin + RBA::PixelBuffer.from_bytes([ 65537, 1 ].pack("VV")) + rescue + error = true + end + assert_equal(error, true) + end def test_11