diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..9dafb8115 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# note: edits here should cross-check with MANIFEST.in +# note: setuptools-scm will prioritize MANIFEST.in over git attrs + +# someone wanting these would clone git instead of using source tarball +.github export-ignore +appveyor.yml export-ignore +.readthedocs.yaml export-ignore + +# Debian prefers upstream source *without* packaging metadata +.github/packaging/ export-ignore diff --git a/.github/packaging/debian/changelog b/.github/packaging/debian/changelog new file mode 100644 index 000000000..e4eba3835 --- /dev/null +++ b/.github/packaging/debian/changelog @@ -0,0 +1,6 @@ +gunicorn (22.1.0-0~ci0+github1) unstable; urgency=low + + * Initial release (not meant to be installed. CI testing artifact only). + + -- Octocat Sun, 10 Dec 2023 23:28:24 +0100 + diff --git a/.github/packaging/debian/clean b/.github/packaging/debian/clean new file mode 100644 index 000000000..2675facbe --- /dev/null +++ b/.github/packaging/debian/clean @@ -0,0 +1,8 @@ +gunicorn.egg-info/ +gunicorn.egg-info/entry_points.txt +gunicorn.egg-info/dependency_links.txt +gunicorn.egg-info/SOURCES.txt +gunicorn.egg-info/not-zip-safe +gunicorn.egg-info/top_level.txt +gunicorn.egg-info/requires.txt +gunicorn.egg-info/PKG-INFO diff --git a/.github/packaging/debian/control b/.github/packaging/debian/control new file mode 100644 index 000000000..82b90db94 --- /dev/null +++ b/.github/packaging/debian/control @@ -0,0 +1,68 @@ +Source: gunicorn +Section: httpd +Priority: optional +Homepage: https://gunicorn.org/ +Maintainer: Octocat +Build-Depends: + debhelper-compat (= 13), + dh-python, + pybuild-plugin-pyproject, + python3-all, + python3-setuptools, +Standards-Version: 4.6.2 +Testsuite: autopkgtest-pkg-python +Rules-Requires-Root: no + +Package: gunicorn +Section: httpd +Priority: optional +Architecture: all +Depends: + python3-gunicorn (= ${binary:Version}), + ${misc:Depends}, + ${python3:Depends}, +Provides: + gunicorn3, + httpd-wsgi3 +Conflicts: + gunicorn3, +Replaces: + gunicorn3, +Suggests: + python3-setproctitle, + python3-pastedeploy, + python3-eventlet, + python3-tornado, +Description: Event-based HTTP/WSGI server + Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork + worker model ported from Ruby's Unicorn_ project. The Gunicorn server is broadly + compatible with various web frameworks, simply implemented, light on server + resource usage, and fairly speedy. + . + This is the server. + +Package: gunicorn-examples +Section: python +Priority: optional +Architecture: all +Description: Event-based HTTP/WSGI server (examples) + Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork + worker model ported from Ruby's Unicorn_ project. The Gunicorn server is broadly + compatible with various web frameworks, simply implemented, light on server + resource usage, and fairly speedy. + . + These are the examples. + +Package: python3-gunicorn +Section: python +Priority: optional +Architecture: all +Suggests: gunicorn +Depends: ${misc:Depends}, ${python3:Depends} +Description: Event-based HTTP/WSGI server (Python 3 libraries) + Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork + worker model ported from Ruby's Unicorn_ project. The Gunicorn server is broadly + compatible with various web frameworks, simply implemented, light on server + resource usage, and fairly speedy. + . + This is the Python library for Python 3. diff --git a/.github/packaging/debian/copyright b/.github/packaging/debian/copyright new file mode 100644 index 000000000..88ce2f7df --- /dev/null +++ b/.github/packaging/debian/copyright @@ -0,0 +1,87 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: gunicorn +Upstream-Contact: Benoît Chesneau +Source: http://gunicorn.org/ + +Files: * +Copyright: +2009-2023 (c) Benoît Chesneau +2009-2015 (c) Paul J. Davis +License: MIT + +Files: debian/* +Copyright: invalid +License: Expat + +Files: docs/sitemap_gen.py +Copyright: © 2004, 2005 Google Inc. +License: BSD-3-Clause + +License: Expat + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +License: MIT +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + + +License: BSD-3-Clause +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.github/packaging/debian/gunicorn-examples.examples b/.github/packaging/debian/gunicorn-examples.examples new file mode 100644 index 000000000..e39721e20 --- /dev/null +++ b/.github/packaging/debian/gunicorn-examples.examples @@ -0,0 +1 @@ +examples/* diff --git a/.github/packaging/debian/rules b/.github/packaging/debian/rules new file mode 100755 index 000000000..f0a5c0f92 --- /dev/null +++ b/.github/packaging/debian/rules @@ -0,0 +1,30 @@ +#!/usr/bin/make -f + +export DH_VERBOSE = 1 +export PYBUILD_NAME = gunicorn + +export PYBUILD_DISABLE = test + +# until this works: use debian/tests/upstream +# export PYBUILD_TEST_PYTEST = 0 + +# pyproject.toml referring to not installed plugins could otherwise confuse pytest +export PYBUILD_TEST_ARGS = --override-ini=addopts= + +# distutils: try running via setup.py +# assuming pyproject is broken, as it decided to create UNKNOWN.egg-info +# export PYBUILD_SYSTEM = distutils +export PYBUILD_SYSTEM = pyproject + +%: + dh $@ --with=python3 --buildsystem=pybuild + +override_dh_auto_install: + # super() + dh_auto_install + # split binary/library into separate packages + mkdir -p debian/gunicorn/usr/bin + find debian/ + mv debian/python3-gunicorn/usr/bin/gunicorn debian/gunicorn/usr/bin + +# split of examples into separate package is handled by dh_installexamples(1) diff --git a/.github/packaging/debian/source/format b/.github/packaging/debian/source/format new file mode 100644 index 000000000..163aaf8d8 --- /dev/null +++ b/.github/packaging/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/.github/packaging/debian/source/linitian-overrides b/.github/packaging/debian/source/linitian-overrides new file mode 100644 index 000000000..fa22c1501 --- /dev/null +++ b/.github/packaging/debian/source/linitian-overrides @@ -0,0 +1 @@ +debian-watch-does-not-check-openpgp-signature diff --git a/.github/packaging/debian/tests/control b/.github/packaging/debian/tests/control new file mode 100644 index 000000000..f6d18e336 --- /dev/null +++ b/.github/packaging/debian/tests/control @@ -0,0 +1,10 @@ +Tests: upstream +Depends: + python3-all, + @, + @builddeps@, + +Tests: curl + curl, + procps, + @, diff --git a/.github/packaging/debian/tests/curl b/.github/packaging/debian/tests/curl new file mode 100644 index 000000000..66f2f8a34 --- /dev/null +++ b/.github/packaging/debian/tests/curl @@ -0,0 +1,11 @@ +#!/bin/sh + +set -eu + +cd debian/tests +gunicorn --daemon --bind unix:gunicorn.sock --pid=gunicorn.pid --workers=2 test:app +sleep 5 + +curl --unix-socket gunicorn.sock http://localhost/ | grep -F 'DEBIAN' +kill $(cat gunicorn.pid) +rm -f gunicorn.pid gunicorn.sock diff --git a/.github/packaging/debian/tests/test.py b/.github/packaging/debian/tests/test.py new file mode 100644 index 000000000..e78b5c655 --- /dev/null +++ b/.github/packaging/debian/tests/test.py @@ -0,0 +1,8 @@ +def app(env, start): + body = b'DEBIAN\n' + header = [ + ('CONTENT-LENGTH', str(len(body))), + ('CONTENT-TYPE', 'text/plain'), + ] + start_response("200 OK", header) + return iter([body]) diff --git a/.github/packaging/debian/tests/upstream b/.github/packaging/debian/tests/upstream new file mode 100644 index 000000000..ffc2cdccb --- /dev/null +++ b/.github/packaging/debian/tests/upstream @@ -0,0 +1,14 @@ +#!/bin/sh + +set -e + +cp -r tests ${AUTOPKGTEST_TMP} +cd ${AUTOPKGTEST_TMP} + +# why that --override-ini=? +# pyproject.toml could mention plugins not installed during build +# => workaround by setting empty + +for p in $(pyversions -s); do + $p -m pytest --override-ini=addopts= tests/ +done diff --git a/.github/packaging/debian/watch b/.github/packaging/debian/watch new file mode 100644 index 000000000..fd097e778 --- /dev/null +++ b/.github/packaging/debian/watch @@ -0,0 +1,2 @@ +version=4 +https://github.com/benoitc/gunicorn/tags .*/(.*)\.tar\.gz diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml new file mode 100644 index 000000000..d6cb08c41 --- /dev/null +++ b/.github/workflows/packaging.yml @@ -0,0 +1,86 @@ +name: packaging +# Goal: produce .deb files on Ubuntu >= 24.04 (noble) or Debian >= 12 (bookworm/bullseye-backports) +# https://wiki.debian.org/Packaging +# https://www.debian.org/doc/debian-policy/ +# https://www.debian.org/doc/packaging-manuals/python-policy/ + +# https://docs.github.com/articles/events-that-trigger-workflows +on: + push: + tags: + - '*' + pull_request: + # self + paths: + - .github/workflows/packaging.yml + - .github/packaging/debian/* + - pyproject.toml + workflow_dispatch: + # https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + # note that some tools care only for the name, not the value + FORCE_COLOR: 1 + + # reduce metadata. unlike elsewhere, build artifacts should differ by content only + SOURCE_DATE_EPOCH: 0 + +jobs: + dpkg-buildpackage: + name: buildpackage-${{ matrix.python-version }} + # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idtimeout-minutes + timeout-minutes: 5 + # https://docs.github.com/articles/virtual-environments-for-github-actions + runs-on: ubuntu-24.04 + strategy: + fail-fast: true + # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix + matrix: + python-version: [ "os-py" ] + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + path: source + - name: prepare deb source dir (debian) + # why the incorrect version? because this way we can skip rewrite debian/changelog for now + shell: bash + run: | + mkdir --verbose --parents upload/debian + mkdir --verbose --parents debian + ( cd source/ && git archive --format=tar --prefix=gunicorn-22.1.0/ HEAD | gzip ) > debian/gunicorn_22.1.0.orig.tar.gz + ( cd debian/ && tar --extract --file gunicorn_22.1.0.orig.tar.gz gunicorn-22.1.0 ) + test -s debian/gunicorn-22.1.0/pyproject.toml + rsync -vrlt source/.github/packaging/debian/ debian/gunicorn-22.1.0/debian + chmod --changes +x debian/gunicorn-22.1.0/debian/control + ls -l debian/gunicorn-22.1.0/ + # ideally, build-dep step would be executed by dkpg scripts + - name: Install dpkg Dependencies + shell: bash + run: | + sudo apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get -y install build-essential dpkg-dev make python3-all quilt debhelper dh-python python3-setuptools pybuild-plugin-pyproject + # print versions + apt policy python3-all python3-all build-essential debhelper dh-python pybuild-plugin-pyproject + apt policy python3-toml python3-tomli + apt policy python3-setuptools python3-distutils python3-setuptools-whl python3-pep517 python3-build + - name: build deb (debian) + shell: bash + run: | + test -s debian/gunicorn-22.1.0/pyproject.toml + test -s debian/gunicorn-22.1.0/debian/control + test -d debian/gunicorn-22.1.0/tests + ( cd debian/gunicorn-22.1.0/ && dpkg-buildpackage --unsigned-source --unsigned-changes ) + # note that Ubuntu 22.04 does not allow zstd in dpkg tools + rsync --ignore-missing-args -trv debian/*.{deb,tar.gz,tar.xz,tar.zstd,buildinfo,changes,dsc} upload/debian/ + - uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 + with: + path: | + upload/debian/* + name: deb + retention-days: 5 + # deb and source tarball are already compressed + compression-level: 0 + if-no-files-found: error diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..fcf7438d0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,38 @@ +name: release + +on: + push: + tags: + - '*' + # FIXME: enable for for CI-triggered pypi uploads: + # release: + # types: + # - published + workflow_dispatch: + # https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow + +permissions: + contents: read + # FIXME: enable for "trusted" publishing: + # id-token: write + +jobs: + pypi-publish: + runs-on: ubuntu-latest + environment: + name: gunicorn + # FIXME: this is just the TEST pypi url! + url: https://test.pypi.org/p/gunicorn + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Install dist Dependencies + run: | + python -m pip install build + - run: python -m build + - name: do release + if: ${{ github.event_name == 'release' }} + uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf # v1.8.11 + with: + # FIXME: this is just the TEST pypi url! + repository-url: https://test.pypi.org/legacy/ + verbose: true diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 759800eb1..5628d9e9c 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -1,5 +1,7 @@ name: tox -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] +# https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow + permissions: contents: read # to fetch code (actions/checkout) env: @@ -14,6 +16,7 @@ jobs: strategy: fail-fast: false matrix: + mindep: [false] unsupported: [false] os: - ubuntu-latest @@ -36,7 +39,6 @@ jobs: # https://github.com/actions/runner-images/issues/9741 - os: macos-latest python-version: "3.12" - unsupported: false # will run these without showing red CI results should they fail - os: macos-latest python-version: "3.13" @@ -44,6 +46,11 @@ jobs: - os: ubuntu-latest python-version: "3.13" unsupported: true + # pin OS lower than usual tests, even if that is slow / soon removed + # point is to prove we got our minimum deps documented + - os: ubuntu-20.04 + python-version: "3.7" + mindep: true steps: - uses: actions/checkout@v4 - name: Using Python ${{ matrix.python-version }} @@ -53,14 +60,16 @@ jobs: cache: pip cache-dependency-path: requirements_test.txt check-latest: true - allow-prereleases: ${{ matrix.unsupported }} + allow-prereleases: ${{ matrix.unsupported || false }} - name: Install Dependencies run: | python -m pip install --upgrade pip python -m pip install tox - run: tox -e run-module - continue-on-error: ${{ matrix.unsupported }} + continue-on-error: ${{ matrix.unsupported || false }} - run: tox -e run-entrypoint - continue-on-error: ${{ matrix.unsupported }} + continue-on-error: ${{ matrix.unsupported || false }} - run: tox -e py - continue-on-error: ${{ matrix.unsupported }} + continue-on-error: ${{ matrix.unsupported || false }} + - if: ${{ matrix.mindep || false }} + run: tox -e mindep diff --git a/pyproject.toml b/pyproject.toml index eaca1eac0..2aacb7ba4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ classifiers = [ requires-python = ">=3.7" dependencies = [ 'importlib_metadata; python_version<"3.8"', + #"packaging>=21.0", "packaging", ] dynamic = ["version"] @@ -50,11 +51,53 @@ Documentation = "https://docs.gunicorn.org" Changelog = "https://docs.gunicorn.org/en/stable/news.html" [project.optional-dependencies] -gevent = ["gevent>=1.4.0"] -eventlet = ["eventlet>=0.24.1,!=0.36.0"] -tornado = ["tornado>=0.2"] +gevent = [ + # 21.8.0 is what Ubuntu 22.04 shipped + # 22.8.0 is lowest tested OK on 3.11 + # 22.10 is the last version to support 3.7 + #'gevent>=21.8.0; python_version<"3.11"', + #'gevent>=22.8.0; python_version>="3.11"', + # gevent 1.4.0 does not build for 3.11 (incompat Cython) + "gevent>=1.4.0", +] +eventlet = [ + # eventlet 0.34.1 https://github.com/eventlet/eventlet/issues/859 + # eventlet 0.36.0 https://github.com/eventlet/eventlet/issues/946 + # eventlet 0.26.0 needed for 3.7 + # eventlet 0.26.1 needed for 3.7 dependencies + # eventlet 0.33.0 needed for python 3.10 + #'eventlet>=0.26.1; python_version<"3.10"', + #'eventlet>=0.33.3; python_version>="3.10"', + "eventlet>=0.24.1,!=0.36.0", +] +tornado = [ + # arbitrary unsupported release with grave issues + # TODO: work our way up from this one and work our way up + #"tornado>=3.0.2", + "tornado>=0.2", +] gthread = [] -setproctitle = ["setproctitle"] +setproctitle = [ + # 1.2.0 needed for Python 3.8 + #"setproctitle>=1.2.0", + "setproctitle", +] +testing-mindep = [ + # this sections helps tox/pip select lowest version + # needs to be kept in sync with sections above until either pip can do it: + # https://github.com/pypa/pip/issues/8085 + # .. or use timemachine + "coverage[toml]==5.2.1", + "packaging==21.0", + "pytest==7.2.0", + "setproctitle==1.2.0", + "setuptools==61.2", + "tornado==3.0.2", + 'eventlet==0.26.1; python_version<"3.10"', + 'eventlet==0.33.3; python_version>="3.10"', + 'gevent==21.8.0; python_version<"3.11"', + 'gevent==22.8.0; python_version>="3.11"', +] testing = [ "gevent", "eventlet", @@ -67,12 +110,21 @@ testing = [ # duplicates "python -m gunicorn" handling in __main__.py gunicorn = "gunicorn.app.wsgiapp:run" -# note the quotes around "paste.server_runner" to escape the dot [project.entry-points."paste.server_runner"] +# note the quotes around "paste.server_runner" to escape the dot main = "gunicorn.app.pasterapp:serve" +[tool.coverage.report] +omit = [ + # workaround for bad test tripping pytest+coverage: + # tests will load such file as configuration + # when we delete the source, we do not care for its coverage + "*/gunicorn.conf.py", +] + [tool.pytest.ini_options] -# # can override these: python -m pytest --override-ini="addopts=" +# can override these: python -m pytest --override-ini="addopts=" +faulthandler_timeout = 25 norecursedirs = ["examples", "lib", "local", "src"] testpaths = ["tests/"] addopts = "--assert=plain --cov=gunicorn --cov-report=xml" diff --git a/tox.ini b/tox.ini index 9bf99e1be..3ce51b7e2 100644 --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,15 @@ commands = pytest --cov=gunicorn {posargs} deps = -rrequirements_test.txt +[testenv:mindep] +# this need not match default testenv. both editable & wheel must work! +package = wheel +extras = testing-mindep +commands = + python -m pip freeze + python -m coverage run -m pytest {posargs} + python -m coverage xml + [testenv:run-entrypoint] package = wheel deps =